refactor(netflix): support multiple video ranges and improve profile handling

- Add support for processing multiple video ranges in parallel
- Handle HYBRID range by fetching HDR10 and DV profiles separately
- Introduce get_profiles_for_range method to retrieve profiles for given range
- Refactor profile fetching logic with detailed logging and error handling
- Validate all requested video ranges are supported by the current codec
- Allow H.264 codec only with SDR range and enforce checks for multiple ranges
- Improve track hydration logic to avoid duplicates across ranges and profiles
- Add logging for multi-range processing and profile fetching details
This commit is contained in:
2025-09-08 20:42:33 +07:00
parent aae9fb1927
commit 7fe4be4542

View File

@@ -207,15 +207,20 @@ class Netflix(Service):
except Exception as e:
self.log.error(e)
else:
if self.range[0] == Video.Range.HYBRID:
# Handle HYBRID mode by getting HDR10 and DV profiles separately
# Handle multiple video ranges
for range_index, video_range in enumerate(self.range):
try:
# Only hydrate tracks on the first range to avoid duplicates
should_hydrate = self.hydrate_track and range_index == 0
if video_range == Video.Range.HYBRID:
# Handle HYBRID mode by getting HDR10 and DV profiles separately
# Get HDR10 profiles for the current codec
hdr10_profiles = self.config["profiles"]["video"][self.vcodec.extension.upper()].get("HDR10", [])
if hdr10_profiles:
self.log.info("Fetching HDR10 tracks for hybrid processing")
self.log.info(f"Fetching HDR10 tracks for HYBRID processing (range {range_index + 1}/{len(self.range)})")
hdr10_manifest = self.get_manifest(title, hdr10_profiles)
hdr10_tracks = self.manifest_as_tracks(hdr10_manifest, title, self.hydrate_track)
hdr10_tracks = self.manifest_as_tracks(hdr10_manifest, title, should_hydrate)
tracks.add(hdr10_tracks)
else:
self.log.warning(f"No HDR10 profiles found for codec {self.vcodec.extension.upper()}")
@@ -223,33 +228,45 @@ class Netflix(Service):
# Get DV profiles for the current codec
dv_profiles = self.config["profiles"]["video"][self.vcodec.extension.upper()].get("DV", [])
if dv_profiles:
self.log.info("Fetching DV tracks for hybrid processing")
self.log.info(f"Fetching DV tracks for HYBRID processing (range {range_index + 1}/{len(self.range)})")
dv_manifest = self.get_manifest(title, dv_profiles)
dv_tracks = self.manifest_as_tracks(dv_manifest, title, False) # Don't hydrate again
dv_tracks = self.manifest_as_tracks(dv_manifest, title, False) # Don't hydrate DV tracks
tracks.add(dv_tracks.videos)
else:
self.log.warning(f"No DV profiles found for codec {self.vcodec.extension.upper()}")
except Exception as e:
self.log.error(f"Error in HYBRID mode processing: {e}")
elif self.high_bitrate:
splitted_profiles = self.split_profiles(self.profiles)
for index, profile_list in enumerate(splitted_profiles):
# Get profiles for the current range
range_profiles = self.get_profiles_for_range(video_range)
if not range_profiles:
self.log.warning(f"No profiles found for range {video_range.name}")
continue
splitted_profiles = self.split_profiles(range_profiles)
for profile_index, profile_list in enumerate(splitted_profiles):
try:
self.log.debug(f"Index: {index}. Getting profiles: {profile_list}")
self.log.debug(f"Range {range_index + 1}/{len(self.range)} ({video_range.name}), Profile Index: {profile_index}. Getting profiles: {profile_list}")
manifest = self.get_manifest(title, profile_list)
manifest_tracks = self.manifest_as_tracks(manifest, title, self.hydrate_track if index == 0 else False)
tracks.add(manifest_tracks if index == 0 else manifest_tracks.videos)
manifest_tracks = self.manifest_as_tracks(manifest, title, should_hydrate and profile_index == 0)
tracks.add(manifest_tracks if should_hydrate and profile_index == 0 else manifest_tracks.videos)
except Exception:
self.log.error(f"Error getting profile: {profile_list}. Skipping")
self.log.error(f"Error getting profile: {profile_list} for range {video_range.name}. Skipping")
continue
else:
try:
manifest = self.get_manifest(title, self.profiles)
manifest_tracks = self.manifest_as_tracks(manifest, title, self.hydrate_track)
tracks.add(manifest_tracks)
# Get profiles for the current range
range_profiles = self.get_profiles_for_range(video_range)
if not range_profiles:
self.log.warning(f"No profiles found for range {video_range.name}")
continue
self.log.info(f"Processing range {range_index + 1}/{len(self.range)}: {video_range.name}")
manifest = self.get_manifest(title, range_profiles)
manifest_tracks = self.manifest_as_tracks(manifest, title, should_hydrate)
tracks.add(manifest_tracks if should_hydrate else manifest_tracks.videos)
except Exception as e:
self.log.error(e)
self.log.error(f"Error processing range {video_range.name}: {e}")
continue
@@ -400,15 +417,25 @@ class Netflix(Service):
self.log.error(f"Video range {self.range[0].name} is not supported by Video Codec: {self.vcodec}")
sys.exit(1)
if len(self.range) > 1:
self.log.error(f"Multiple video range is not supported right now.")
# Validate all ranges are supported
for video_range in self.range:
if video_range.name not in list(self.config["profiles"]["video"][self.vcodec.extension.upper()].keys()) and video_range != Video.Range.HYBRID and self.vcodec != Video.Codec.AVC and self.vcodec != Video.Codec.VP9:
self.log.error(f"Video range {video_range.name} is not supported by Video Codec: {self.vcodec}")
sys.exit(1)
if self.vcodec == Video.Codec.AVC and self.range[0] != Video.Range.SDR:
self.log.error(f"H.264 Video Codec only supports SDR")
if self.vcodec == Video.Codec.AVC:
for video_range in self.range:
if video_range != Video.Range.SDR:
self.log.error(f"H.264 Video Codec only supports SDR, but {video_range.name} was requested")
sys.exit(1)
self.profiles = self.get_profiles()
# Log information about video ranges being processed
if len(self.range) > 1:
range_names = [r.name for r in self.range]
self.log.info(f"Processing multiple video ranges: {', '.join(range_names)}")
self.log.info("Intializing a MSL client")
self.get_esn()
# if self.cdm.security_level == 1:
@@ -472,6 +499,40 @@ class Netflix(Service):
self.log.debug(f"Result_profiles: {result_profiles}")
return result_profiles
def get_profiles_for_range(self, video_range: Video.Range) -> List[str]:
"""
Get profiles for a specific video range.
Args:
video_range: The video range to get profiles for
Returns:
List of profile strings for the specified range
"""
result_profiles = []
# Handle case for codec VP9
if self.vcodec == Video.Codec.VP9 and video_range != Video.Range.HDR10:
result_profiles.extend(self.config["profiles"]["video"][self.vcodec.extension.upper()].values())
return result_profiles
# Get profiles for the specific range
codec_profiles = self.config["profiles"]["video"][self.vcodec.extension.upper()]
if video_range.name in codec_profiles:
result_profiles.extend(codec_profiles[video_range.name])
elif video_range == Video.Range.HYBRID:
# For hybrid, use HDR10 profiles
if "HDR10" in codec_profiles:
result_profiles.extend(codec_profiles["HDR10"])
else:
self.log.warning(f"No HDR10 profiles found for HYBRID range in codec {self.vcodec.extension.upper()}")
else:
self.log.warning(f"Range {video_range.name} not found in codec {self.vcodec.extension.upper()} profiles")
self.log.debug(f"Profiles for range {video_range.name}: {result_profiles}")
return result_profiles
def get_esn(self):
if self.cdm.device_type == DeviceTypes.ANDROID:
try: