feat(hybrid): use a dv track that carries hdr10+ as the hybrid of its resolution

This commit is contained in:
imSp4rky
2026-10-18 12:11:02 -06:00
parent c0917c6537
commit 768e35d905
8 changed files with 114 additions and 22 deletions
+5
View File
@@ -157,6 +157,11 @@ unshackle dl -q 2160 -r HDR10,DV EXAMPLE '...'
and goes on with the other resolutions of the title. When no hybrid file at all was made,
the title fails.
A DV track that carries HDR10+ (`DV+HDR10P` in `--list`) is already a hybrid. At a resolution
that has one, `-r HYBRID` selects the DV+HDR10P track with the highest bitrate (the lowest with
`--worst`) and muxes it as it is, with no base track and no dovi_tool step. The other
resolutions get a hybrid that unshackle makes.
To keep an HDR10+ deliverable *alongside* the hybrid, request both ranges:
`-r HYBRID,HDR10P`. `-r HYBRID` on its own muxes only the merged hybrid.
+2 -1
View File
@@ -380,7 +380,8 @@ whose first frame carries HDR10+ (SMPTE ST 2094-40) metadata becomes `HDR10P`. T
`--list` and [`--generate-dv`](../../guide/downloading.md#dolby-vision-from-hdr10) use it.
unshackle also probes HEVC Dolby Vision tracks. A DV track that carries HDR10+ keeps its `DV`
range, so `-r DV` and `-r HYBRID` select it as before, and `--list` shows it as `DV+HDR10P`. The
range, so `-r DV` selects it, and `--list` shows it as `DV+HDR10P`. `-r HYBRID` uses it as the
[hybrid](../../guide/downloading.md#colour-range) of its resolution, with no base track. The
HDR10 tracks and the DV tracks of a title are sampled as two separate sets.
When two HDR10 tracks have the same resolution, they can be an HDR10 encode and an HDR10+ encode
+1 -1
View File
@@ -193,7 +193,7 @@ the remote client's `-q` when the remote session starts:
The service sees the DRM system and security level of the client's device. A client with no
local CDM gets a `SERVER_CDM_CAPPED` error.
- A download job must pass a `quality` at or below the limit, and must not set `best_available` or
a `HYBRID` range. `HYBRID` always keeps the lowest Dolby Vision track, at any quality. A job with
a `HYBRID` range. `HYBRID` can keep the lowest Dolby Vision track, at any quality. A job with
no `quality`, with a higher one, with `best_available`, or with `HYBRID` gets a `SERVER_CDM_CAPPED`
error. The server checks the values the job runs with, after it applies the service defaults
and the `serve:` download defaults.
+18 -2
View File
@@ -640,8 +640,14 @@ def test_a_remote_session_puts_the_flag_in_the_service_ctx(monkeypatch: pytest.M
assert seen["ctx"].params["generate_dv"] is to_dv
def video(id_: str, range_: Video.Range, height: int = 2160, codec: Optional[Video.Codec] = Video.Codec.HEVC) -> Any:
return SimpleNamespace(id=id_, height=height, range=range_, codec=codec)
def video(
id_: str,
range_: Video.Range,
height: int = 2160,
codec: Optional[Video.Codec] = Video.Codec.HEVC,
native_hybrid: bool = False,
) -> Any:
return SimpleNamespace(id=id_, height=height, range=range_, codec=codec, native_hybrid=native_hybrid)
def test_hybrid_fails_with_neither_a_base_nor_dv() -> None:
@@ -734,3 +740,13 @@ def test_the_flag_without_dv_takes_only_hdr10plus_bases() -> None:
def test_no_dv_and_no_flag_gives_no_bases() -> None:
assert hybrid_plan([video("h", Video.Range.HDR10), video("p", Video.Range.HDR10P)], False) == ([], None)
def test_a_native_hybrid_passes_the_gate_and_takes_no_base_at_its_resolution() -> None:
videos = [video("dvp2160", Video.Range.DV, native_hybrid=True), video("h1080", Video.Range.HDR10, 1080)]
assert hybrid_gate_failure(videos[:1], False) is None
bases, dv = hybrid_plan(videos + [video("p2160", Video.Range.HDR10P)], False)
assert [v.id for v in bases] == ["h1080"]
assert dv is not None and dv.id == "dvp2160"
+43
View File
@@ -115,6 +115,49 @@ def test_select_hybrid_no_dv_selects_only_base() -> None:
assert ids(chosen) == {"hdr10p-2160"}
def dv_hdr10p(track_id: str, height: int, bitrate: int) -> Video:
video = make_video(track_id, range_=Video.Range.DV, height=height, bitrate=bitrate, codec=Video.Codec.HEVC)
video.hdr10plus = True
return video
def test_select_hybrid_takes_the_highest_dv_hdr10p_track_over_a_base_and_the_lowest_dv() -> None:
H = Video.Codec.HEVC
tracks = [
dv_hdr10p("dvp-2160-hi", 2160, 26_000_000),
dv_hdr10p("dvp-2160-lo", 2160, 21_000_000),
make_video("hdr10-2160", range_=Video.Range.HDR10, height=2160, bitrate=24_000_000, codec=H),
dv_hdr10p("dvp-360", 360, 700_000),
]
assert ids(list(filter(Tracks().select_hybrid(tracks, [2160]), tracks))) == {"dvp-2160-hi"}
assert ids(list(filter(Tracks().select_hybrid(tracks, [2160], worst=True), tracks))) == {"dvp-2160-lo"}
def test_select_hybrid_builds_a_hybrid_only_where_no_dv_track_carries_hdr10p() -> None:
H = Video.Codec.HEVC
tracks = [
dv_hdr10p("dvp-2160", 2160, 26_000_000),
make_video("hdr10-1080", range_=Video.Range.HDR10, height=1080, bitrate=8_000_000, codec=H),
make_video("dv-360", range_=Video.Range.DV, height=360, bitrate=1_000_000, codec=H),
]
chosen = list(filter(Tracks().select_hybrid(tracks, [2160, 1080]), tracks))
assert ids(chosen) == {"dvp-2160", "hdr10-1080", "dv-360"}
Tracks.flag_hybrid_ingredients(chosen, [])
assert {t.id for t in chosen if t.hybrid_base_only} == {"hdr10-1080", "dv-360"}
def test_a_dv_hdr10p_track_used_only_for_its_rpu_stays_an_ingredient() -> None:
H = Video.Codec.HEVC
tracks = [
make_video("hdr10-1080", range_=Video.Range.HDR10, height=1080, bitrate=8_000_000, codec=H),
dv_hdr10p("dvp-360", 360, 700_000),
]
chosen = list(filter(Tracks().select_hybrid(tracks, [1080]), tracks))
Tracks.flag_hybrid_ingredients(chosen, [])
assert all(t.hybrid_base_only for t in chosen) and ids(chosen) == {"hdr10-1080", "dvp-360"}
def test_merge_dedups_shared_ingredient_and_deliverable() -> None:
H = Video.Codec.HEVC
dv = make_video("dv-1080", range_=Video.Range.DV, height=1080, bitrate=9_000_000, codec=H)
+19 -12
View File
@@ -235,6 +235,8 @@ def select_hybrid_bases(videos: Sequence[Video]) -> list[Video]:
def hybrid_gate_failure(videos: Sequence[Video], generate_dv: bool) -> Optional[tuple[str, str]]:
"""The (message, detail) that stops HYBRID for these selected videos, or None when it can go on."""
if any(v.native_hybrid for v in videos):
return None
hdr_tracks = [v for v in videos if v.range in (Video.Range.HDR10, Video.Range.HDR10P)]
base_tracks = [v for v in hdr_tracks if v.codec in (Video.Codec.HEVC, None)]
dv_tracks = [v for v in videos if v.range == Video.Range.DV]
@@ -270,10 +272,16 @@ def hybrid_gate_failure(videos: Sequence[Video], generate_dv: bool) -> Optional[
def hybrid_plan(videos: Sequence[Video], generate_dv: bool) -> tuple[list[Video], Optional[Video]]:
"""The hybrid bases and the DV track to pair with them.
With no DV track, only HDR10+ bases qualify, and only with ``--generate-dv``.
With no DV track, only HDR10+ bases qualify, and only with ``--generate-dv``. A resolution with a
DV track that carries HDR10+ gets no base: that track is its hybrid.
"""
dv_tracks = [v for v in videos if v.range == Video.Range.DV]
base_pool = [v for v in videos if dv_tracks or (generate_dv and v.range == Video.Range.HDR10P)]
native_heights = {v.height for v in videos if v.native_hybrid}
base_pool = [
v
for v in videos
if v.height not in native_heights and (dv_tracks or (generate_dv and v.range == Video.Range.HDR10P))
]
matching_dv = min(dv_tracks, key=lambda v: v.height or 0) if dv_tracks else None
return select_hybrid_bases(base_pool), matching_dv
@@ -2759,18 +2767,14 @@ class dl:
title.tracks.videos, non_hybrid_ranges
)
if not effective_quality:
hybrid_quality = effective_quality
if not hybrid_quality:
best_resolution = max((v.height for v in hybrid_candidate_tracks), default=None)
if best_resolution:
hybrid_filter = title.tracks.select_hybrid(
hybrid_candidate_tracks, [best_resolution], worst=worst
)
hybrid_selected = list(filter(hybrid_filter, hybrid_candidate_tracks))
else:
hybrid_selected = []
else:
hybrid_quality = [best_resolution] if best_resolution else []
hybrid_selected = []
if hybrid_quality:
hybrid_filter = title.tracks.select_hybrid(
hybrid_candidate_tracks, effective_quality, worst=worst
hybrid_candidate_tracks, hybrid_quality, worst=worst
)
hybrid_selected = list(filter(hybrid_filter, hybrid_candidate_tracks))
@@ -3666,6 +3670,9 @@ class dl:
hybrid_bases, matching_dv = hybrid_plan(title.tracks.videos, generate_dv)
hybrid_base_ids.update(v.id for v in hybrid_bases)
hybrids_made = 0
for video in (v for v in original_videos if v.native_hybrid):
self.log.info(f"The {video.height}p DV track carries HDR10+: it is the hybrid")
hybrids_made += 1
for group in sorted(standalone_groups, key=group_file_size, reverse=True):
mux_video_group(group)
+25 -6
View File
@@ -41,6 +41,11 @@ MP4BOX_PROGRESS = re.compile(r"\((\d{1,3})/100\)")
MP4BOX_OPEN_FAILED = re.compile(r"error while opening|Error opening file|Invalid IsoMedia File", re.I)
def at_resolution(video: Video, res: int) -> bool:
"""Whether a track has this height, or this height from a 16:9 width (a letterboxed encode)."""
return video.height == res or bool(video.width and int(video.width * 9 / 16) == res)
class Tracks:
"""
Video, Audio, Subtitle, Chapter, and Attachment Track Store.
@@ -434,12 +439,27 @@ class Tracks:
A track that the caller also selected as an explicit deliverable (same track in both
selections) stays unflagged, and the standalone mux loop muxes it alongside the hybrid.
A ``native_hybrid`` track is the hybrid itself, so it stays unflagged too.
"""
for video in hybrid_selected:
if video not in non_hybrid_selected:
if video not in non_hybrid_selected and not video.native_hybrid:
video.hybrid_base_only = True
def select_hybrid(self, tracks, quality, worst: bool = False):
"""A filter for the hybrid ingredients of each resolution. It sets ``native_hybrid`` on every track.
A DV track that carries HDR10+ is already a hybrid, so its resolution takes that track and no base.
"""
pick = min if worst else max
natives = {}
for res in quality:
candidates = [v for v in tracks if v.range == Video.Range.DV and v.hdr10plus and at_resolution(v, res)]
if candidates:
natives[res] = pick(candidates, key=lambda v: v.bitrate or 0)
for video in tracks:
video.native_hybrid = any(video is v for v in natives.values())
quality = [res for res in quality if res not in natives]
# Prefer HDR10+ over HDR10 as the base layer (preserves dynamic metadata).
# Only an HEVC base qualifies, because dovi_tool injects the RPU into an HEVC stream.
base_ranges = (Video.Range.HDR10P, Video.Range.HDR10)
@@ -450,24 +470,23 @@ class Tracks:
for v in tracks
if v.range == range_type
and v.codec in (Video.Codec.HEVC, None)
and (v.height in quality or (v.width and int(v.width * 9 / 16) in quality))
and any(at_resolution(v, res) for res in quality)
]
if base_tracks:
break
pick = min if worst else max
base_selected = []
for res in quality:
candidates = [v for v in base_tracks if v.height == res or (v.width and int(v.width * 9 / 16) == res)]
candidates = [v for v in base_tracks if at_resolution(v, res)]
if candidates:
chosen = pick(candidates, key=lambda v: v.bitrate)
base_selected.append(chosen)
dv_tracks = [v for v in tracks if v.range == Video.Range.DV]
lowest_dv = min(dv_tracks, key=lambda v: v.height) if dv_tracks else None
lowest_dv = min(dv_tracks, key=lambda v: v.height) if dv_tracks and (base_selected or not natives) else None
def select(x):
if x in base_selected:
if x in base_selected or x.native_hybrid:
return True
if lowest_dv and x is lowest_dv:
return True
+1
View File
@@ -303,6 +303,7 @@ class Video(Track):
self.dv_compatible_bitstream = dv_compatible_bitstream
self.hdr10plus = hdr10plus
self.hybrid_base_only = False
self.native_hybrid = False
def to_dict(self) -> dict[str, Any]:
data = super().to_dict()