From 1867fb9e5e70a105bc3d7c0050560dc9dfcfca60 Mon Sep 17 00:00:00 2001 From: chu23465 <130033130+chu23465@users.noreply.github.com> Date: Wed, 16 Apr 2025 21:47:14 +0530 Subject: [PATCH] Proper fix for DSNP DSNP service is currently stable --- vinetrimmer/commands/dl.py | 22 +++++- .../devices/mtc_mtc_atv_atv_sl3000.prd | Bin 2060 -> 2060 bytes vinetrimmer/key_store.db | Bin 356352 -> 356352 bytes vinetrimmer/objects/tracks.py | 45 ++++++----- vinetrimmer/services/disneyplus.py | 70 +++++++++++++++++- vinetrimmer/vinetrimmer.yml | 1 + 6 files changed, 112 insertions(+), 26 deletions(-) diff --git a/vinetrimmer/commands/dl.py b/vinetrimmer/commands/dl.py index ede629d..3097259 100644 --- a/vinetrimmer/commands/dl.py +++ b/vinetrimmer/commands/dl.py @@ -307,7 +307,6 @@ def dl(ctx, profile, cdm, *_, **__): credentials=credentials, ) - @dl.result_callback() @click.pass_context def result(ctx, service, quality, closest_resolution, range_, wanted, alang, slang, audio_only, subs_only, chapters_only, audio_description, @@ -333,10 +332,15 @@ def result(ctx, service, quality, closest_resolution, range_, wanted, alang, sla else: log.info(" + No captions found") + global content_keys + log = service.log service_name = service.__class__.__name__ + if service_name == "DisneyPlus": # Always retrieve fresh keys for DSNP so that content_keys variable has 2 kid:key pairs + no_cache = True + log.info("Retrieving Titles") try: titles = Titles(as_list(service.get_titles())) @@ -451,7 +455,7 @@ def result(ctx, service, quality, closest_resolution, range_, wanted, alang, sla continue # only wanted to see what tracks were available and chosen skip_title = False - + #Download might fail as auth token expires quickly for Hotstar. This is a problem for big downloads like a 4k track. So we reverse tracks and download audio first and large video later. for track in (list(title.tracks)[::-1] if service_name == "Hotstar" else title.tracks): if not keys: @@ -477,6 +481,7 @@ def result(ctx, service, quality, closest_resolution, range_, wanted, alang, sla proxy = None track.download(directories.temp, headers=service.session.headers, proxy=proxy) log.info(" + Downloaded") + # To-Do: For DSNP KID add -> mp4info (Bento4) with --verbose option show default kid from init.mp4 file if isinstance(track, VideoTrack) and track.needs_ccextractor_first and not no_subs: ccextractor() if track.encrypted: @@ -617,7 +622,18 @@ def result(ctx, service, quality, closest_resolution, range_, wanted, alang, sla f"label=0:key_id={track.kid.lower()}:key={track.key.lower()}", # Apple TV+ needs this as shaka pulls the incorrect KID, idk why f"label=1:key_id=00000000000000000000000000000000:key={track.key.lower()}", - ]) if service_name != "DisneyPlus" else ",".join([f"label={index}:key_id={key}:key={content_keys[key]}" for index, key in enumerate(content_keys)]), # This right here is a hack as DSNP has 2 kids and returns 2 keys. FFS. + ]) if service_name != "DisneyPlus" else + ",".join( + [# This right here is a hack as DSNP sometimes has 2 kids and returns 2 keys. FFS. + "label={}:key_id={}:key={}".format( + content_keys.index(pair), + pair[0], + pair[1] + ) + for pair + in content_keys + ] + ), "--temp_dir", directories.temp ], check=True) except subprocess.CalledProcessError: diff --git a/vinetrimmer/devices/mtc_mtc_atv_atv_sl3000.prd b/vinetrimmer/devices/mtc_mtc_atv_atv_sl3000.prd index 86570915c1b754996c154096215f8c051aaef681..6cd7466e38978a96fabb5ad9f10c75d2035a7d9c 100644 GIT binary patch delta 509 zcmeAX=ntVvpgJM>%A8$?-d_60>N?|s=hJ5XxVK7%XI`<)et5gXamE?7oe6qIycdro z?cs8{=Dc=j8gF{6d~E!;>`O93Y|yIuwZ99`y*hPH zdhrIO$tsKn0kgL@d07|UdUNZvl7!hpv(*d?+&dT`fJyqNb|_!$?M;C;GU6%+t$rPl z)(F|^9Ur4sUH4sogMILSATaUaZ=cqhaKz$&j@y6t3s)yEVRWdc)MY^X8Nry53G5US zgHjM9EGL&R**JKz&8t1|K2{}+C2r0$OPPX{9apE#R9s?rVx86R!y-950(VWy3UE-} kt5~{fKF|E1`v=t4{u4d1CVL2>UyB$a7`^~E_eXI@nTYGJXYN*Qoo2Nom*k`$>nU|}Z_3;zH` zf|ZXVg-vR!Ev%OmHfm#KW#MEMQ42RA#lW3=xxepsX1@KC#r>1TgX^us$4j?bhp+EF zU%Ap{#}%8hFKoh&-(FpP#TM#rXStO|+nafB>;CR`|G{MK;bg5Y8n{gbR3qoWgfcp># zRIomh3I?5wIvVE)ePWf~n3C_aW>YTD7kl+!0j6o6t=3vW!CYRKhq}NUajiKzUN=}su4hZqoautrv~QP_xim+F<+&!jSncs2%Jo{8 delta 68 zcmZp8AlmRibb>VFl8G|Tj7v5qEV1We=D)zee~6POP1H(&5?zu?aZ#7scU Xy#0bdi^+lIEEAXvwzD{}{?P{jp*|X+ diff --git a/vinetrimmer/objects/tracks.py b/vinetrimmer/objects/tracks.py index 62b4aef..c700ac8 100644 --- a/vinetrimmer/objects/tracks.py +++ b/vinetrimmer/objects/tracks.py @@ -226,20 +226,7 @@ class Track: True if KID is now available, False otherwise. KID will be stored in Track.kid automatically. """ - if self.encrypted and self.source == "DSNP": - try: - log = logging.getLogger("Tracks") - log.info("+ Replacing KID with correct track KID (DSNP workaround)") - xml_str = base64.b64decode(self.psshPR).decode("utf-16-le", "ignore") - xml_str = xml_str[xml_str.index("<"):] - kids = [uuid.UUID(base64.b64decode(kid_xml['@VALUE']).hex()).bytes_le.hex() for kid_xml in xmltodict.parse(xml_str)['WRMHEADER']['DATA']['CUSTOMATTRIBUTES']['KIDS']['KID']] - if self.kid: - kids.remove(self.kid) - self.kid = kids[-1] - except: - raise log.exit("Failed To Replace Correct KID for DSNP") - - elif self.source != "DSNP" and self.psshPR: + if self.encrypted and self.psshPR: xml_str = base64.b64decode(self.psshPR).decode("utf-16-le", "ignore") xml_str = xml_str[xml_str.index("<"):] xml = load_xml(xml_str).find("DATA") # root: WRMHEADER @@ -249,8 +236,11 @@ class Track: self.kid = next(iter(xml.xpath("PROTECTINFO/KID/@VALUE")), None) if not self.kid: # v4.3.0.0 self.kid = next(iter(xml.xpath("PROTECTINFO/KIDS/KID/@VALUE")), None) # can be multiple? - if not self.kid: - raise + if not self.kid and self.source == "DSNP": + xml_str = base64.b64decode(self.psshPR).decode("utf-16-le", "ignore") + xml_str = xml_str[xml_str.index("<"):] + kids = [uuid.UUID(base64.b64decode(kid_xml['@VALUE']).hex()).bytes_le.hex() for kid_xml in xmltodict.parse(xml_str)['WRMHEADER']['DATA']['CUSTOMATTRIBUTES']['KIDS']['KID']] + self.kid = kids[-1] self.kid = uuid.UUID(base64.b64decode(self.kid).hex()).bytes_le.hex() @@ -347,6 +337,12 @@ class Track: ) self.url = segments + if Path(save_path).is_file() and not (os.stat(save_path).st_size <= 3): + log = logging.getLogger("Tracks") + log.info("File already exists, assuming it's from previous unfinished download") + self._location = save_path + return save_path + if self.source == "CORE": asyncio.run(saldl( self.url, @@ -1025,10 +1021,19 @@ class Tracks: language = next((x.language for x in self.audios if x.is_original_lang), "") if not language: continue - self.audios = sorted( - self.audios, - key=str(x.language) if str(x.language) != "" else "und" #lambda x: "" if is_close_match(language, [x.language]) else str(x.language) - ) + try: + self.audios = sorted( + self.audios, + key=lambda x: "" if is_close_match(language, [x.language]) else str(x.language) + ) + + except: + self.audios = sorted( + self.audios, + key=lambda x: "und" if str(x.language) == "" else str(x.language) + + ) + def sort_subtitles(self, by_language=None): """Sort subtitle tracks by sdh, cc, forced, and optionally language.""" diff --git a/vinetrimmer/services/disneyplus.py b/vinetrimmer/services/disneyplus.py index 6425700..eb4b235 100644 --- a/vinetrimmer/services/disneyplus.py +++ b/vinetrimmer/services/disneyplus.py @@ -44,8 +44,68 @@ class DisneyPlus(BaseService): @click.command(name="DisneyPlus", short_help="https://disneyplus.com") @click.argument("title", type=str, required=False) @click.option("-m", "--movie", is_flag=True, default=False, help="Title is a movie.") - @click.option("-s", "--scenario", default="tv-drm-ctr", type=str, - help="Capability profile that specifies compatible codecs, streams, bit-rates, resolutions and such.") + @click.option("-s", "--scenario", default="tv-drm-ctr", help="Capability profile that specifies compatible codecs, streams, bit-rates, resolutions and such.", + type=click.Choice([ + "android", + "android-mobile", + "android-mobile-drm", + "android-mobile-drm-ctr", + "android-tablet", + "android-tablet-drm", + "android-tablet-drm-ctr", + "android-tv", + "android-tv-drm", + "android-tv-drm-ctr", + "android~unlimited", + "apple", + "apple-mobile", + "apple-mobile-drm", + "apple-mobile-drm-cbcs", + "apple-tablet", + "apple-tablet-drm", + "apple-tablet-drm-cbcs", + "apple-tv", + "apple-tv-drm", + "apple-tv-drm-cbcs", + "apple~unlimited", + "browser", + "browser-cbcs", + "browser-drm-cbcs", + "browser-drm-ctr", + "browser~unlimited", + "cbcs-high", + "cbcs-regular", + "chromecast", + "chromecast-drm", + "chromecast-drm-cbcs", + "ctr-high", + "ctr-regular", + "handset-drm-cbcs", + "handset-drm-cbcs-multi", + "handset-drm-ctr", + "handset-drm-ctr-lfr", + "playstation", + "playstation-drm", + "playstation-drm-cbcs", + "restricted-drm-ctr-sw", + "roku", + "roku-drm", + "roku-drm-cbcs", + "roku-drm-ctr", + "tizen", + "tizen-drm", + "tizen-drm-ctr", + "tv-drm-cbcs", + "tv-drm-ctr", + "tvs-drm-cbcs", + "tvs-drm-ctr", + "webos", + "webos-drm", + "webos-drm-ctr", + # 1080p bypass for downgraded l3 devices, keep private! + "drm-cbcs-multi" + ]) + ) @click.pass_context def cli(ctx, **kwargs): return DisneyPlus(ctx, **kwargs) @@ -61,6 +121,7 @@ class DisneyPlus(BaseService): self.acodec = ctx.parent.params["acodec"] self.range = ctx.parent.params["range_"] self.wanted = ctx.parent.params["wanted"] + self.quality = ctx.parent.params["quality"] or 1080 self.playready = True if "certificate_chain" in dir(ctx.obj.cdm) else False # ctx.obj.cdm.device.type == LocalDevice.Types.PLAYREADY @@ -178,6 +239,8 @@ class DisneyPlus(BaseService): tracks.audios.extend(atmos_scenario.audios) tracks.subtitles.extend(atmos_scenario.subtitles) + for track in tracks: + track.needs_proxy = False return tracks def get_chapters(self, title): @@ -216,7 +279,8 @@ class DisneyPlus(BaseService): }) self.log.info("Preparing") - if self.range != "SDR" and self.vcodec != "H265": + + if (self.range != "SDR" or self.quality > 1080) and self.vcodec != "H265": # vcodec must be H265 for High Dynamic Range self.vcodec = "H265" self.log.info(f" + Switched video codec to H265 to be able to get {self.range} dynamic range") diff --git a/vinetrimmer/vinetrimmer.yml b/vinetrimmer/vinetrimmer.yml index 0f2f978..e80cff5 100644 --- a/vinetrimmer/vinetrimmer.yml +++ b/vinetrimmer/vinetrimmer.yml @@ -23,6 +23,7 @@ cdm_api: credentials: iTunes: '' Hotstar: 'username:password' + DisneyPlus: 'email:password' directories: temp: ''