1 Commits
5.1.0 ... main

Author SHA1 Message Date
imSp4rky
d109fe63eb fix(dl): mux hybrid ingredients standalone only when range explicitly requested
-r HYBRID alone muxed the HDR10/HDR10+ base layer as a standalone output because only the ingredient DV track was flagged hybrid_base_only. The inverse was also broken: HDR10/HDR10+/DV tracks never entered the standalone-deliverable pool, so -r HYBRID,HDR10P only delivered the standalone HDR10+ by accident of the first bug.

- Add Tracks.partition_hybrid_videos: ingredient ranges (HDR10/HDR10+/DV) enter the deliverable pool only when their range is explicitly requested alongside HYBRID; replaces the duplicated filter in dl.py.
- Add Tracks.flag_hybrid_ingredients: any track in the hybrid selection but not in the deliverable selection is flagged hybrid_base_only; replaces and generalises the DV-only dv_is_deliverable special case.
2026-06-06 16:03:16 -06:00
3 changed files with 130 additions and 37 deletions

View File

@@ -6,10 +6,14 @@ Covers the selection primitives that back `-r ...,DV,HYBRID` downloads:
single lowest DV track used as a hybrid ingredient. single lowest DV track used as a hybrid ingredient.
- ``Tracks.merge_video_selections`` — de-duplicates the ingredient/deliverable - ``Tracks.merge_video_selections`` — de-duplicates the ingredient/deliverable
overlap so a DV track that is chosen as both is not muxed/downloaded twice. overlap so a DV track that is chosen as both is not muxed/downloaded twice.
- ``Tracks.partition_hybrid_videos`` — splits the ladder into hybrid-ingredient
candidates and the standalone-deliverable pool; HDR10/HDR10+/DV only enter
the pool when their range was explicitly requested alongside HYBRID.
- ``Tracks.flag_hybrid_ingredients`` — marks ingredient-only tracks with
``hybrid_base_only`` so the standalone mux loop skips them.
The full ``dl`` selection pipeline (range filtering, the ``dv_is_deliverable`` The remaining ``dl`` glue (the Cartesian deliverable product and the mux loop)
partition, the ``hybrid_base_only`` ingredient flag and the standalone mux loop) is orchestration inside the Click command; these tests lock down the pure
is orchestration glue inside the Click command; these tests lock down the pure
units it relies on plus the documented end-state of a realistic ATV-style ladder. units it relies on plus the documented end-state of a realistic ATV-style ladder.
""" """
@@ -137,6 +141,93 @@ def test_merge_dedup_uses_track_identity_by_id() -> None:
assert len(Tracks.merge_video_selections([a], [b])) == 1 assert len(Tracks.merge_video_selections([a], [b])) == 1
# ---------------------------------------------------------------------------
# partition_hybrid_videos
# ---------------------------------------------------------------------------
def test_partition_hybrid_only_keeps_ingredients_out_of_pool(ladder: list[Video]) -> None:
"""`-r HYBRID`: HDR10+/DV are ingredients only; pool holds just SDR."""
candidates, pool = Tracks.partition_hybrid_videos(ladder, [])
assert ids(candidates) == {"hdr10p-2160", "hdr10p-1080", "dv-2160", "dv-1080", "dv-360"}
assert ids(pool) == {"sdr-2160", "sdr-1080-avc", "sdr-1080-hevc"}
def test_partition_admits_hdr10p_to_pool_when_requested(ladder: list[Video]) -> None:
"""`-r HYBRID,HDR10P`: HDR10+ tracks become standalone deliverable candidates."""
candidates, pool = Tracks.partition_hybrid_videos(ladder, [Video.Range.HDR10P])
assert {"hdr10p-2160", "hdr10p-1080"} <= ids(pool)
assert not any(t.range == Video.Range.DV for t in pool)
# Candidates are unaffected by the requested ranges.
assert ids(candidates) == {"hdr10p-2160", "hdr10p-1080", "dv-2160", "dv-1080", "dv-360"}
def test_partition_admits_dv_and_hdr10p_when_both_requested(ladder: list[Video]) -> None:
"""`-r HYBRID,HDR10P,DV`: both ranges enter the deliverable pool."""
_, pool = Tracks.partition_hybrid_videos(ladder, [Video.Range.HDR10P, Video.Range.DV])
assert {"hdr10p-2160", "hdr10p-1080", "dv-2160", "dv-1080", "dv-360"} <= ids(pool)
def test_partition_hdr10_requested_does_not_admit_hdr10p() -> None:
H = Video.Codec.HEVC
tracks = [
make_video("hdr10-2160", range_=Video.Range.HDR10, height=2160, bitrate=20_000_000, codec=H),
make_video("hdr10p-2160", range_=Video.Range.HDR10P, height=2160, bitrate=20_000_000, codec=H),
]
_, pool = Tracks.partition_hybrid_videos(tracks, [Video.Range.HDR10])
assert ids(pool) == {"hdr10-2160"}
# ---------------------------------------------------------------------------
# flag_hybrid_ingredients
# ---------------------------------------------------------------------------
def flagged(tracks: list[Video]) -> set[str]:
return {t.id for t in tracks if t.hybrid_base_only}
def test_flag_hybrid_only_flags_base_and_ingredient_dv(ladder: list[Video]) -> None:
"""`-r HYBRID`: no deliverables, so the base and the ingredient DV are both
skipped by the standalone mux loop — only the hybrid output remains."""
hybrid_selected = list(filter(Tracks().select_hybrid(ladder, [1080]), ladder))
Tracks.flag_hybrid_ingredients(hybrid_selected, [])
assert flagged(ladder) == {"hdr10p-1080", "dv-360"}
def test_flag_hybrid_plus_hdr10p_keeps_base_deliverable(ladder: list[Video]) -> None:
"""`-r HYBRID,HDR10P`: the base is also an explicit deliverable, only the
ingredient DV is skipped — hybrid + standalone HDR10+ are muxed."""
hybrid_selected = list(filter(Tracks().select_hybrid(ladder, [1080]), ladder))
base = next(t for t in ladder if t.id == "hdr10p-1080")
Tracks.flag_hybrid_ingredients(hybrid_selected, [base])
assert flagged(ladder) == {"dv-360"}
def test_flag_hybrid_plus_hdr10p_and_dv_keeps_both_deliverables(ladder: list[Video]) -> None:
"""`-r HYBRID,HDR10P,DV`: best DV is a deliverable, lowest DV stays
ingredient-only — hybrid + HDR10+ + DV are muxed."""
hybrid_selected = list(filter(Tracks().select_hybrid(ladder, [1080]), ladder))
base = next(t for t in ladder if t.id == "hdr10p-1080")
best_dv = next(t for t in ladder if t.id == "dv-1080")
Tracks.flag_hybrid_ingredients(hybrid_selected, [base, best_dv])
assert flagged(ladder) == {"dv-360"}
def test_flag_single_dv_rendition_as_deliverable_stays_unflagged() -> None:
"""`-r HYBRID,DV` with one DV rendition: the same track is ingredient and
deliverable, so it must still be muxed standalone."""
H = Video.Codec.HEVC
tracks = [
make_video("hdr10p-1080", range_=Video.Range.HDR10P, height=1080, bitrate=9_000_000, codec=H),
make_video("dv-1080", range_=Video.Range.DV, height=1080, bitrate=9_000_000, codec=H),
]
hybrid_selected = list(filter(Tracks().select_hybrid(tracks, [1080]), tracks))
dv = next(t for t in tracks if t.id == "dv-1080")
Tracks.flag_hybrid_ingredients(hybrid_selected, [dv])
assert flagged(tracks) == {"hdr10p-1080"}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# documented end-state for the reported command # documented end-state for the reported command
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@@ -1697,24 +1697,12 @@ class dl:
has_hybrid = any(r == Video.Range.HYBRID for r in range_) has_hybrid = any(r == Video.Range.HYBRID for r in range_)
non_hybrid_ranges = [r for r in range_ if r != Video.Range.HYBRID] non_hybrid_ranges = [r for r in range_ if r != Video.Range.HYBRID]
# DV is both a hybrid ingredient (lowest track) and, when explicitly
# requested, a standalone deliverable (best track per resolution).
dv_is_deliverable = Video.Range.DV in non_hybrid_ranges
if quality: if quality:
missing_resolutions = [] missing_resolutions = []
if has_hybrid: if has_hybrid:
hybrid_candidate_tracks = [ hybrid_candidate_tracks, non_hybrid_tracks = Tracks.partition_hybrid_videos(
v title.tracks.videos, non_hybrid_ranges
for v in title.tracks.videos )
if v.range in (Video.Range.HDR10, Video.Range.HDR10P, Video.Range.DV)
]
non_hybrid_tracks = [
v
for v in title.tracks.videos
if v.range not in (Video.Range.HDR10, Video.Range.HDR10P, Video.Range.DV)
or (dv_is_deliverable and v.range == Video.Range.DV)
]
hybrid_filter = title.tracks.select_hybrid(hybrid_candidate_tracks, quality, worst=worst) hybrid_filter = title.tracks.select_hybrid(hybrid_candidate_tracks, quality, worst=worst)
hybrid_selected = list(filter(hybrid_filter, hybrid_candidate_tracks)) hybrid_selected = list(filter(hybrid_filter, hybrid_candidate_tracks))
@@ -1757,17 +1745,9 @@ class dl:
pre_hybrid_videos: list[Video] = list(title.tracks.videos) if has_hybrid else [] pre_hybrid_videos: list[Video] = list(title.tracks.videos) if has_hybrid else []
if has_hybrid: if has_hybrid:
# Apply hybrid selection for HYBRID tracks # Apply hybrid selection for HYBRID tracks
hybrid_candidate_tracks = [ hybrid_candidate_tracks, non_hybrid_tracks = Tracks.partition_hybrid_videos(
v title.tracks.videos, non_hybrid_ranges
for v in title.tracks.videos )
if v.range in (Video.Range.HDR10, Video.Range.HDR10P, Video.Range.DV)
]
non_hybrid_tracks = [
v
for v in title.tracks.videos
if v.range not in (Video.Range.HDR10, Video.Range.HDR10P, Video.Range.DV)
or (dv_is_deliverable and v.range == Video.Range.DV)
]
if not quality: if not quality:
best_resolution = max((v.height for v in hybrid_candidate_tracks), default=None) best_resolution = max((v.height for v in hybrid_candidate_tracks), default=None)
@@ -1811,14 +1791,10 @@ class dl:
title.tracks.videos = Tracks.merge_video_selections(hybrid_selected, non_hybrid_selected) title.tracks.videos = Tracks.merge_video_selections(hybrid_selected, non_hybrid_selected)
# Flag the lowest DV track as ingredient-only so mux skips it standalone, # Flag tracks selected only as hybrid ingredients (the HDR base and/or
# unless it is itself the chosen DV deliverable (single DV rendition). # the lowest DV) so the standalone mux loop skips them. Tracks also
selected_dv = [v for v in title.tracks.videos if v.range == Video.Range.DV] # picked as explicit deliverables stay unflagged.
if selected_dv: Tracks.flag_hybrid_ingredients(hybrid_selected, non_hybrid_selected)
ingredient_dv = min(selected_dv, key=lambda v: v.height)
deliverable_dv = [v for v in non_hybrid_selected if v.range == Video.Range.DV]
if not (dv_is_deliverable and ingredient_dv in deliverable_dv):
ingredient_dv.hybrid_base_only = True
else: else:
selected_videos: list[Video] = [] selected_videos: list[Video] = []
if video_multi_lang: if video_multi_lang:

View File

@@ -339,6 +339,32 @@ class Tracks:
merged.append(video) merged.append(video)
return merged return merged
@staticmethod
def partition_hybrid_videos(
videos: list[Video], non_hybrid_ranges: list[Video.Range]
) -> tuple[list[Video], list[Video]]:
"""Split videos into hybrid-ingredient candidates and the standalone-deliverable pool.
HDR10/HDR10+/DV tracks are hybrid ingredients; they only enter the standalone
pool when their range was explicitly requested alongside HYBRID, so e.g.
`-r HYBRID` muxes only the hybrid while `-r HYBRID,HDR10P` also delivers HDR10+.
"""
ingredient_ranges = (Video.Range.HDR10, Video.Range.HDR10P, Video.Range.DV)
hybrid_candidates = [v for v in videos if v.range in ingredient_ranges]
non_hybrid = [v for v in videos if v.range not in ingredient_ranges or v.range in non_hybrid_ranges]
return hybrid_candidates, non_hybrid
@staticmethod
def flag_hybrid_ingredients(hybrid_selected: list[Video], non_hybrid_selected: list[Video]) -> None:
"""Mark tracks selected only as hybrid ingredients so the standalone mux loop skips them.
A track that was also picked as an explicit deliverable (same track in both
selections) stays unflagged and is muxed standalone alongside the hybrid.
"""
for video in hybrid_selected:
if video not in non_hybrid_selected:
video.hybrid_base_only = True
def select_hybrid(self, tracks, quality, worst: bool = False): def select_hybrid(self, tracks, quality, worst: bool = False):
# Prefer HDR10+ over HDR10 as the base layer (preserves dynamic metadata) # Prefer HDR10+ over HDR10 as the base layer (preserves dynamic metadata)
base_ranges = (Video.Range.HDR10P, Video.Range.HDR10) base_ranges = (Video.Range.HDR10P, Video.Range.HDR10)