Proper fix for DSNP
DSNP service is currently stable
This commit is contained in:
parent
b01bbdb53f
commit
1867fb9e5e
@ -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:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -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."""
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -23,6 +23,7 @@ cdm_api:
|
||||
credentials:
|
||||
iTunes: ''
|
||||
Hotstar: 'username:password'
|
||||
DisneyPlus: 'email:password'
|
||||
|
||||
directories:
|
||||
temp: ''
|
||||
|
||||
Loading…
Reference in New Issue
Block a user