DRMLab
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
from vinetrimmer.objects.tracks import Tracks
|
||||
from vinetrimmer.utils.widevine.device import LocalDevice
|
||||
|
||||
|
||||
def _hydrate_hidden_tracks(self, title, manifest, tracks, license_url):
|
||||
"""
|
||||
Hydrate hidden audio and subtitle tracks.
|
||||
Netflix does not include all download URLs for audio/subtitle tracks in the initial manifest for performance optimization.
|
||||
This method sequentially requests these hidden tracks.
|
||||
"""
|
||||
from itertools import zip_longest
|
||||
|
||||
# Collect unhydrated audio tracks
|
||||
unavailable_audio_tracks = []
|
||||
for audio in manifest.get("audio_tracks", []):
|
||||
if len(audio.get("streams", [])) == 0:
|
||||
# Audio is available but has no stream information
|
||||
self.log.info(f"Found hidden audio track: {audio.get('languageDescription', 'Unknown')}")
|
||||
unavailable_audio_tracks.append((
|
||||
audio.get("new_track_id"),
|
||||
audio.get("id")
|
||||
))
|
||||
|
||||
# Collect unhydrated subtitle tracks
|
||||
unavailable_subtitle_tracks = []
|
||||
for subtitle in manifest.get("timedtexttracks", []):
|
||||
if subtitle.get("isNoneTrack"):
|
||||
continue
|
||||
if subtitle.get("hydrated") == False:
|
||||
# Subtitle is not hydrated
|
||||
# self.log.info(f"Found hidden subtitle track: {subtitle.get('languageDescription', 'Unknown')}")
|
||||
unavailable_subtitle_tracks.append((
|
||||
subtitle.get("new_track_id"),
|
||||
subtitle.get("id")
|
||||
))
|
||||
|
||||
if not unavailable_audio_tracks and not unavailable_subtitle_tracks:
|
||||
self.log.info("No hidden audio or subtitle tracks found")
|
||||
return
|
||||
|
||||
self.log.info(f"Fetching {len(unavailable_audio_tracks)} hidden audio track(s) and {len(unavailable_subtitle_tracks)} hidden subtitle track(s)...")
|
||||
|
||||
# Hydrate hidden tracks one by one
|
||||
for audio_info, subtitle_info in zip_longest(
|
||||
unavailable_audio_tracks,
|
||||
unavailable_subtitle_tracks,
|
||||
fillvalue=(None, None)
|
||||
):
|
||||
audio_track_id, audio_id = audio_info if audio_info[0] else (None, None)
|
||||
subtitle_track_id, subtitle_id = subtitle_info if subtitle_info[0] else (None, None)
|
||||
|
||||
try:
|
||||
# Request specific hidden track
|
||||
hydrated_manifest = self.get_manifest(
|
||||
title,
|
||||
self.profiles,
|
||||
required_audio_track_id=audio_track_id,
|
||||
required_text_track_id=subtitle_track_id
|
||||
)
|
||||
|
||||
# Parse hydrated tracks
|
||||
hydrated_tracks = self.manifest_as_tracks(hydrated_manifest)
|
||||
|
||||
# Add audio track
|
||||
if audio_track_id:
|
||||
for audio in manifest.get("audio_tracks", []):
|
||||
if audio.get("id") == audio_id:
|
||||
for track in hydrated_tracks.audio:
|
||||
if track.encrypted:
|
||||
track.extra["license_url"] = license_url
|
||||
tracks.add(track, warn_only=True)
|
||||
# self.log.info(f"Added hidden audio track: {track.language}")
|
||||
break
|
||||
|
||||
# Add subtitle track
|
||||
if subtitle_track_id:
|
||||
for subtitle in manifest.get("timedtexttracks", []):
|
||||
if subtitle.get("id") == subtitle_id:
|
||||
for track in hydrated_tracks.subtitles:
|
||||
tracks.add(track, warn_only=True)
|
||||
# self.log.info(f"Added hidden subtitle track: {track.language}")
|
||||
break
|
||||
except Exception as e:
|
||||
self.log.warning(f"Error hydrating hidden track: {e}")
|
||||
continue
|
||||
|
||||
|
||||
def get_tracks(self, title):
|
||||
if self.vcodec == "H264":
|
||||
# If H.264, get both MPL and HPL tracks as they alternate in terms of bitrate
|
||||
tracks = Tracks()
|
||||
|
||||
self.config["profiles"]["video"]["H264"]["MPL+HPL+QC"] = (
|
||||
self.config["profiles"]["video"]["H264"]["MPL"] + self.config["profiles"]["video"]["H264"]["HPL"] +
|
||||
self.config["profiles"]["video"]["H264"]["QC"]
|
||||
)
|
||||
|
||||
if self.audio_only or self.subs_only or self.chapters_only:
|
||||
profiles = ["MPL+HPL+QC"]
|
||||
else:
|
||||
profiles = self.profile.split("+")
|
||||
|
||||
for profile in profiles:
|
||||
try:
|
||||
manifest = self.get_manifest(title, self.config["profiles"]["video"]["H264"][profile])
|
||||
except:
|
||||
manifest = self.get_manifest(title, self.config["profiles"]["video"]["H264"]["MPL"] +
|
||||
self.config["profiles"]["video"]["H264"]["HPL"])
|
||||
manifest_tracks = self.manifest_as_tracks(manifest)
|
||||
license_url = manifest["links"]["license"]["href"]
|
||||
|
||||
if self.cdm.device.security_level == 3 and self.cdm.device.type == LocalDevice.Types.CHROME:
|
||||
self.cdm.uuid = self.cdm.chrome_uuid
|
||||
self.cdm.urn = f"urn:uuid:{self.cdm.uuid}"
|
||||
max_quality = max(x.height for x in manifest_tracks.videos)
|
||||
# if profile == "MPL" and max_quality >= 720:
|
||||
# manifest_sd = self.get_manifest(title, self.config["profiles"]["video"]["H264"]["BPL"])
|
||||
# license_url_sd = manifest_sd["links"]["license"]["href"]
|
||||
# if "SD_LADDER" in manifest_sd["video_tracks"][0]["streams"][0]["tags"]:
|
||||
# # SD manifest is new encode encrypted with different keys that won't work for HD
|
||||
# continue
|
||||
# license_url = license_url_sd
|
||||
if profile == "HPL" and max_quality >= 1080:
|
||||
if "SEGMENT_MAP_2KEY" in manifest["video_tracks"][0]["streams"][0]["tags"]:
|
||||
# 1080p license restricted from Android L3, 720p license will work for 1080p
|
||||
manifest_720 = self.get_manifest(
|
||||
title, [x for x in self.config["profiles"]["video"]["H264"]["HPL"] if "l40" not in x]
|
||||
)
|
||||
license_url = manifest_720["links"]["license"]["href"]
|
||||
else:
|
||||
# Older encode, can't use 720p keys for 1080p
|
||||
continue
|
||||
|
||||
for track in manifest_tracks:
|
||||
if track.encrypted:
|
||||
track.extra["license_url"] = license_url
|
||||
# Fix PSSH with correct KID
|
||||
# track.pssh.init_data = b'\x08\x01\x12\x10' + bytes.fromhex(track.kid)
|
||||
tracks.add(manifest_tracks, warn_only=True)
|
||||
|
||||
# Hydrate hidden audio and subtitle tracks (using the last manifest)
|
||||
_hydrate_hidden_tracks(title, manifest, tracks, license_url)
|
||||
return tracks
|
||||
else:
|
||||
pass
|
||||
Reference in New Issue
Block a user