mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-14 21:15:06 +00:00
feat(hybrid): generate dolby vision from hdr10+ with --generate-dv
Builds per-scene L1 in unshackle (PQ mean, letterbox-aware) with a generate_dv_mode config (v29/v40/legacy); a failed hybrid no longer exits 0 with no video.
This commit is contained in:
@@ -156,8 +156,15 @@ class TrackRequest:
|
||||
codecs: list[Video.Codec] = [] # empty == accept any codec
|
||||
ranges: list[Video.Range] = [Video.Range.SDR]
|
||||
best_available: bool = False
|
||||
generate_dv: bool = False # --generate-dv: make Dolby Vision from HDR10+
|
||||
```
|
||||
|
||||
!!! note "`generate_dv` and a hand-made HYBRID"
|
||||
A service that fetches the HYBRID variants itself (HDR10 and DV) must also fetch
|
||||
HDR10P when `track_request.generate_dv` is set and the title has no DV track.
|
||||
The conversion needs an HEVC HDR10+ track as its base.
|
||||
`get_tracks_for_variants` does this for you.
|
||||
|
||||
!!! note "Read the request, don't second-guess the user"
|
||||
Services may narrow `track_request` for hard technical constraints (e.g.
|
||||
*"this service delivers HDR only as HEVC"*), but must **not** filter
|
||||
|
||||
@@ -471,6 +471,7 @@ Make a download job. It requires `service` and `title_id`. Every other field is
|
||||
| `downloads` | int | `1` | Concurrent tracks. |
|
||||
| `worst` | boolean | `false` | Lowest bitrate within quality. |
|
||||
| `best_available` | boolean | `false` | Fall back to best available. With `all_drm`, a DRM system that does not license is a warning. |
|
||||
| `generate_dv` | boolean | `false` | With `HYBRID` in `range`, make Dolby Vision from the HDR10+ metadata when a title has no DV track. Needs `hdr10plus_tool` on the server. Without `HYBRID` in `range`, the request returns `400`. |
|
||||
| `repack` | boolean | `false` | Add REPACK tag. |
|
||||
| `tag` | string | `null` | Release group tag. |
|
||||
| `tmdb_id` / `imdb_id` / `tvdb_id` / `anilist_id` | - | `null` | External ID overrides. Each resolves its metadata directly instead of by a title search, and is used for tagging. Set `enrich` to also take the title, year and original language. Give at most one of `tmdb_id`, `imdb_id` and `tvdb_id`, since unshackle resolves the others from it. Sending two returns `400`. `anilist_id` still combines with one of them. `tmdb_id` and `tvdb_id` must be positive integers, `imdb_id` must look like `tt1375666`, and `anilist_id` must be a positive integer or a string like `mal:12345`, or the request returns `400`. An ID whose provider is unconfigured, such as `tmdb_id` with no `tmdb_api_key`, fails the job rather than returning `400`. `anilist_id` needs no API key. |
|
||||
|
||||
@@ -130,8 +130,8 @@ hood the client walks a remote session through its lifecycle.
|
||||
The client also downloads playlists and segments through the same proxy,
|
||||
as a local run does. `--no-proxy-download` and `--proxy-download` change
|
||||
that for downloads only.
|
||||
- Track-selection hints (`range_`, `vcodec`, `quality`, `best_available`) so
|
||||
the server fetches the right manifests
|
||||
- Track-selection hints (`range_`, `vcodec`, `quality`, `best_available`,
|
||||
`generate_dv`) so the server fetches the right manifests
|
||||
- Your language and audio codec selection (`-l`, `-vl`, `-al`, `-a`, `-fs`)
|
||||
in a `dl_params` object. Some services fetch one manifest
|
||||
for each audio language, and pick the languages from these values
|
||||
@@ -428,7 +428,7 @@ The server mounts all these routes, even in `--remote-only` mode. Paths use the
|
||||
answers CDM calls), `cdm_system_id` and `cdm_device_type` (Widevine only), `service_config`
|
||||
(the identity config keys the service lists in `client_config`), `client` (a freeform identity
|
||||
object the dashboard shows as sent), and the track-selection hints `range_`, `vcodec`,
|
||||
`quality`, `best_available`, plus arbitrary service CLI options
|
||||
`quality`, `best_available`, `generate_dv`, plus arbitrary service CLI options
|
||||
(`additionalProperties: true`).
|
||||
|
||||
`dl_params` holds the `dl` track selection: `lang`, `v_lang` and `a_lang`
|
||||
|
||||
@@ -133,8 +133,10 @@ See [Proxies and VPN](../guide/proxies-and-vpn.md).
|
||||
!!! note "`TrackRequest`"
|
||||
`self.track_request` is a small dataclass built from the CLI: `codecs`
|
||||
(from `--vcodec`, empty means "accept any"), `ranges` (from `--range`, default
|
||||
`[Video.Range.SDR]`), and `best_available`. Use it in `get_tracks` to decide which
|
||||
variants to fetch.
|
||||
`[Video.Range.SDR]`), `best_available`, and `generate_dv` (from `--generate-dv`).
|
||||
Use it in `get_tracks` to decide which variants to fetch. A service that fetches
|
||||
the HYBRID variants itself must also fetch HDR10P when `generate_dv` is set and
|
||||
the title has no DV track, because the conversion needs an HEVC HDR10+ base.
|
||||
|
||||
## Titles and the `Title` hierarchy
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ unshackle dl [OPTIONS] SERVICE [SERVICE ARGS...]
|
||||
| `-vb-range`, `--vbitrate-range` | - | Video bitrate range in kbps, e.g. `6000-7000`; picks highest within. Mutually exclusive with `--vbitrate`. |
|
||||
| `-ab-range`, `--abitrate-range` | - | Audio bitrate range in kbps, e.g. `128-256`. Mutually exclusive with `--abitrate`. |
|
||||
| `-r`, `--range` | `SDR` | Colour range(s): `SDR`, `HLG`, `HDR10`, `HDR10P` (HDR10+), `DV`, `HYBRID`. |
|
||||
| `--generate-dv` | off | With `-r HYBRID`, make Dolby Vision from the HDR10+ metadata when a title has no Dolby Vision track. Needs `hdr10plus_tool`. See [Dolby Vision from HDR10+](downloading.md#dolby-vision-from-hdr10). |
|
||||
| `-c`, `--channels` | - | Audio channels; matches sub-layouts (5.1 ≈ 6.0). |
|
||||
| `-naa`, `--noatmos` | off | Exclude Dolby Atmos audio tracks. |
|
||||
| `--worst` | off | Pick the lowest bitrate within the requested quality. **Requires `-q`.** |
|
||||
|
||||
@@ -149,14 +149,54 @@ unshackle dl -q 2160 -r HDR10,DV EXAMPLE '...'
|
||||
`-r HYBRID` makes a single hybrid track by injecting the Dolby Vision RPU
|
||||
metadata onto an HDR10/HDR10+ base layer with **dovi_tool**.
|
||||
It requires the `dovi_tool` binary, resolved from unshackle's `binaries/` folder,
|
||||
the `binaries` folder of your user data directory, or your `PATH`. The normal case is a DV track plus an HDR10 or HDR10+ base. With HDR10+
|
||||
and no DV, unshackle converts the HDR10+ metadata to DV instead, which also needs
|
||||
`hdr10plus_tool`. With no DV and no HDR10+, the title fails.
|
||||
When HDR10+ is present, unshackle prefers it over HDR10 as the base layer.
|
||||
the `binaries` folder of your user data directory, or your `PATH`. A hybrid needs a DV track plus an HDR10 or
|
||||
HDR10+ base. With no DV track, the title fails, unless you add `--generate-dv` (see below).
|
||||
When HDR10+ is present, unshackle prefers it over HDR10 as the base layer. The base must be
|
||||
HEVC; a title whose HDR10/HDR10+ tracks are all in another codec fails before it downloads.
|
||||
If the hybrid step fails for one resolution, unshackle logs an error, skips that resolution
|
||||
and goes on with the other resolutions of the title. When no hybrid file at all was made,
|
||||
the title fails.
|
||||
|
||||
To keep an HDR10+ deliverable *alongside* the hybrid, request both ranges:
|
||||
`-r HYBRID,HDR10P`. `-r HYBRID` on its own muxes only the merged hybrid.
|
||||
|
||||
#### Dolby Vision from HDR10+
|
||||
|
||||
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.
|
||||
|
||||
```shell title="Generate Dolby Vision from HDR10+, and keep the HDR10+ file too"
|
||||
unshackle dl -q 2160 -r HYBRID,HDR10P --generate-dv EXAMPLE '...'
|
||||
```
|
||||
|
||||
Each HDR10+ scene becomes one Dolby Vision shot, with its own scene cut and its own Level 1
|
||||
brightness values (peak and average). unshackle calculates the average itself, as a PQ mean of
|
||||
the HDR10+ brightness distribution without the letterbox bars. A scene with no black pixels keeps
|
||||
its whole frame, so a title that changes aspect ratio is also correct. The `dovi_tool generate
|
||||
--hdr10plus-json` average makes most scenes too bright.
|
||||
|
||||
The [`generate_dv_mode`](../reference/configuration/download.md#generate_dv_mode) setting in
|
||||
`unshackle.yaml` changes how the average is set: `v29` (default), `v40` for the 10-nit floor of studio
|
||||
Dolby Vision, or `legacy` for the `dovi_tool` average.
|
||||
|
||||
!!! warning "Generated Dolby Vision is not studio Dolby Vision"
|
||||
- There are no colourist trims (Level 2 and Level 8). The TV does all the tone mapping
|
||||
from the Level 1 values.
|
||||
- Dolby Vision does not let the average go below about 2.4 nits (10 nits with
|
||||
`generate_dv_mode: v40`), so a very dark scene reads a little brighter than its pixels.
|
||||
- The HDR10+ tone-mapping curve (Profile B) has no Dolby Vision equivalent. The HDR10+
|
||||
metadata stays in the file, so an HDR10+ TV still reads it.
|
||||
- The result can be no better than the HDR10+ metadata. Profile A metadata is often less
|
||||
reliable than Profile B, and unshackle shows a warning for Profile A.
|
||||
- A TV or player that supports both formats almost always picks Dolby Vision. On such a
|
||||
TV, the generated Dolby Vision replaces the original HDR10+. To keep the original, also
|
||||
request `HDR10P`, as in the example above.
|
||||
|
||||
An HDR10 track that a service labels HDR10 but that carries HDR10+ metadata does not
|
||||
convert. Only a track with the `HDR10P` range does.
|
||||
|
||||
## Bitrate selection
|
||||
|
||||
By default unshackle keeps the highest-bitrate rendition for each selected
|
||||
|
||||
@@ -52,6 +52,7 @@ Common config keys. This is a useful subset, and every `dl` flag works:
|
||||
| `speed_limit` | str | *(unlimited)* | Total download speed cap across all downloads combined, e.g. `500k`, `5M`, `1.5G` or plain bytes/sec. Values are bytes, not bits (`5M` = 5.0 MB/s). |
|
||||
| `slow` | str | *(unset)* | Inter-title delay, e.g. `"20-40"`. |
|
||||
| `best_available` | bool | `false` | Warn instead of failing when a requested resolution, range, or language is missing, or when one DRM system does not license under `--all-drm`. The CLI flag is `--best-available`, also spelled `--warn-only`. |
|
||||
| `generate_dv` | bool | `false` | With `-r HYBRID`, make Dolby Vision from the HDR10+ metadata when a title has no DV track. Needs `hdr10plus_tool`. See [Dolby Vision from HDR10+](../../guide/downloading.md#dolby-vision-from-hdr10). |
|
||||
| `proxy` | str | *(unset)* | Default proxy URI or 2-letter country. |
|
||||
| `no_folder` | bool | `false` | Do not create a per-title folder. |
|
||||
|
||||
@@ -368,3 +369,24 @@ dual_multi_mode: strict
|
||||
!!! note "Dialects count as one language"
|
||||
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.
|
||||
|
||||
## `generate_dv_mode`
|
||||
|
||||
- **Type:** `str` · **Default:** `v29`
|
||||
|
||||
How [`--generate-dv`](../../guide/downloading.md#dolby-vision-from-hdr10) sets the Level 1 brightness
|
||||
values of the Dolby Vision it makes from HDR10+. All modes make one shot per HDR10+ scene and keep the
|
||||
same peak and letterbox area. They differ in the scene average, which the TV uses to tone-map.
|
||||
|
||||
| Mode | Scene average |
|
||||
|------|---------------|
|
||||
| `v29` (default) | unshackle's average from the HDR10+ brightness distribution, at least about 2.4 nits. Closest to the pixels. |
|
||||
| `v40` | The same average, at least about 10 nits, as in studio Dolby Vision (CM v4.0). Dark scenes read brighter than `v29`. |
|
||||
| `legacy` | The `dovi_tool generate --hdr10plus-json` average. Most scenes read brighter than the pixels. |
|
||||
|
||||
```yaml
|
||||
generate_dv_mode: v29
|
||||
```
|
||||
|
||||
The setting applies on the machine that runs the hybrid step: the client for a remote download, the
|
||||
server for a REST API job.
|
||||
|
||||
@@ -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` |
|
||||
| [Download & processing](download.md) | `dl`, `continue_downloads`, `merge_segments`, `subtitle`, `audio`, `muxing`, `language_tags`, `dual_multi_mode`, `generate_dv_mode` |
|
||||
| [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` |
|
||||
|
||||
Reference in New Issue
Block a user