feat(tracks): probe HDR10 tracks for HDR10+ SEI before selection

Relabel HEVC HDR10 tracks whose first fragment carries ST 2094-40 SEI as HDR10P, for dl, the REST API and remote sessions. Config key hdr10plus_probe (default true).
This commit is contained in:
imSp4rky
2026-10-17 18:58:48 -06:00
parent 338adc5ff0
commit 811e0a19c0
11 changed files with 484 additions and 22 deletions
+10 -7
View File
@@ -497,13 +497,16 @@ audio, add hand-built subtitles or an `Attachment` (e.g. cover art). Do *not*
filter for resolution/bitrate. See `fetch_dash_manifest` in the EXAMPLE service
for a thorough demonstration.
!!! note "Flip HDR10 → HDR10+ by hand when you know the platform embeds it"
HDR10+ is a **bitstream (SEI)** feature. The HLS parser intentionally does
not sniff it from the bitstream, so a manifest that embeds HDR10+ SEI but labels
the variant plain HDR10 will parse as HDR10. The label is the only signal
the parser has, and it is wrong. Only the service author knows the platform
embeds HDR10+, so it is on you to correct `video.range` to HDR10+ here for
those services.
!!! note "HDR10+ in tracks labelled HDR10"
HDR10+ is a **bitstream (SEI)** feature, and many manifests label an HDR10+
variant as plain HDR10. Before unshackle lists or selects tracks, the
[`hdr10plus_probe`](../reference/configuration/download.md#hdr10plus_probe)
reads the first fragment of each HEVC HDR10 track and changes `video.range` to
HDR10+ when the fragment carries HDR10+ metadata. Set `video.range` yourself
only when the probe cannot read the fragment, for example an ISM track whose
SEI is encrypted, or when the platform gives a reliable signal of its own.
If the playback API has no HDR10+ value, request HDR10 for an HDR10+ request
and let the probe relabel the tracks.
!!! warning "`get_tracks` is your only chance to capture license inputs"
The license callbacks (`get_widevine_license` and the others) fire **much later**, in
+2 -1
View File
@@ -165,7 +165,8 @@ unshackle dl -q 2160 -r HDR10,DV EXAMPLE '...'
When a title has HDR10+ but no DV track, `-r HYBRID --generate-dv` makes a Dolby Vision
Profile 8.1 track from the HDR10+ metadata. It needs `hdr10plus_tool` as well as `dovi_tool`.
When a title has a DV track, unshackle uses that track and ignores the flag. When it has none,
the flag also makes unshackle ask the service for its HDR10+ tracks.
the flag also makes unshackle ask the service for its HDR10+ tracks. A track that the service labels HDR10 but
that carries HDR10+ metadata also qualifies (see [`hdr10plus_probe`](../reference/configuration/download.md#hdr10plus_probe)).
```shell title="Generate Dolby Vision from HDR10+, and keep the HDR10+ file too"
unshackle dl -q 2160 -r HYBRID,HDR10P --generate-dv EXAMPLE '...'
+31
View File
@@ -370,6 +370,37 @@ dual_multi_mode: strict
Regional variants of the same base language (e.g. `en-US` and `en-GB`) collapse to a single
language in both modes, so they never trigger `{dual}` on their own.
## `hdr10plus_probe`
- **Type:** `bool`  ·  **Default:** `true`
Find the HDR10+ tracks that a service labels as HDR10. Before unshackle lists or selects tracks, it
reads the first fragment of HEVC HDR10 tracks from DASH, HLS fMP4 and one-file sources. A track
whose first frame carries HDR10+ (SMPTE ST 2094-40) metadata becomes `HDR10P`. Then `-r HDR10P`,
`--list` and [`--generate-dv`](../../guide/downloading.md#dolby-vision-from-hdr10) use it.
When two HDR10 tracks have the same resolution, they can be an HDR10 encode and an HDR10+ encode
side by side, so unshackle probes every track. When each track has its own resolution, unshackle
probes the two highest and the two lowest bitrates first:
| Result | Action |
|--------|--------|
| All four carry HDR10+ | Every HDR10 track of the title becomes `HDR10P`. |
| Some carry HDR10+ | The title has HDR10 and HDR10+ encodes side by side, so unshackle probes every track. |
| None carry HDR10+ | Every track stays `HDR10`. |
A probe that fails, or a fragment whose metadata is encrypted, keeps the label from the service.
unshackle does not probe ISM tracks. Each probe makes one to three small requests: the 64 KiB
fragment, plus the playlist of an HLS track or the header of a one-file track. The probes run in
parallel, with no more at a time than `--workers` (or `dl.workers`) sets for a track download.
```yaml
hdr10plus_probe: true
```
A remote server probes its tracks before it sends them, so the setting on the server applies to
remote downloads and to the REST API.
## `generate_dv_mode`
- **Type:** `str`  ·  **Default:** `v29`
+1 -1
View File
@@ -31,7 +31,7 @@ Where the file lives, how unshackle discovers it, and how to edit it:
|------|------|
| [Directories](directories.md) | `directories`, `filenames`, `services_repo_force` |
| [Services & authentication](services.md) | `services`, `credentials`, `firefox_cookies`, `remote_services`, `serve` |
| [Download & processing](download.md) | `dl`, `continue_downloads`, `merge_segments`, `subtitle`, `audio`, `muxing`, `language_tags`, `dual_multi_mode`, `generate_dv_mode` |
| [Download & processing](download.md) | `dl`, `continue_downloads`, `merge_segments`, `subtitle`, `audio`, `muxing`, `language_tags`, `dual_multi_mode`, `generate_dv_mode`, `hdr10plus_probe` |
| [Output & naming](output.md) | `output_template`, `tag`, `tag_rules`, `tag_group_name`, `tag_imdb_tmdb`, `chapter_fallback_name`, `unicode_filenames` |
| [DRM & CDM](drm.md) | `cdm`, `remote_cdm`, `decryption` |
| [Network & proxy](network.md) | `network`, `headers`, `proxy_providers` |