---
name: "hdr_delivery"
description: "Deliver HDR video correctly: choose PQ or HLG, encode HDR10 with static metadata, make tone-mapped SDR versions, and match platform specs (YouTube and others). Use when exporting HDR, tagging HDR files, converting HDR to SDR, or debugging washed-out or rejected HDR uploads."
---

# HDR Delivery

## Purpose

HDR delivery is metadata plus math, not just pixels. A 10-bit PQ or HLG file with wrong or missing tags plays washed out on one device and gets rejected on another. This skill takes a graded master and produces deliverables that survive the real world: a properly tagged HDR10/HEVC file, an SDR version with a sane tone map, and a verification pass that proves the tags match the pixels.

Use it when: exporting an HDR edit for YouTube/Vimeo, converting HDR to SDR for SDR-only platforms, fixing an upload that looks wrong, or checking whether a file is really HDR.

## Workflow

### 1. Probe the master first

Run the helper before doing anything else:

```bash
python3 bin/hdr_deliver.py probe INPUT
```

It reports transfer (smpte2084/PQ, arib-std-b67/HLG, bt709/SDR), primaries, bit depth, whether ST 2086 mastering-display and MaxCLL/MaxFALL side data exist, and an HDR/SDR verdict. Never trust the filename. If `probe` says the pixels are SDR but the tags say PQ, the file is broken: retag only after converting the pixels, never before.

### 2. Pick the transfer function for the audience

- **PQ (ST 2084)** when you need the widest device support and precise creative intent: YouTube HDR, HDR10 Blu-ray-style masters, anything with HDR10 static metadata. PQ is absolute: 0 to 10,000 nits means the same thing on every display, so dimmer displays tone-map it down using the metadata.
- **HLG (ARIB STD-B67)** when backwards compatibility matters: live streams, broadcast, iPhone footage (iPhone "HDR" is Dolby Vision profile 8.4 on an HLG base layer). HLG is relative and degrades gracefully on SDR displays with no metadata needed.

Both use BT.2020 primaries. Both need 10-bit to avoid banding. Bit depth alone does not make HDR: 10-bit sRGB is still SDR, 10-bit PQ is HDR.

### 3. Encode the HDR10 deliverable

From a graded HDR master (or a correctly tagged PQ source), encode HEVC Main 10 with the tags and metadata intact:

```bash
python3 bin/hdr_deliver.py hdr10 master.mov deliverable.mp4 \
  --primaries p3 --peak-nits 1000 --max-cll 1000 --max-fall 400
```

This sets `-color_primaries bt2020 -color_trc smpte2084 -colorspace bt2020nc`, writes the `colr` box, forces the `hvc1` tag (Apple and iOS reject `hev1`-tagged HDR in MP4), and attaches the ST 2086 mastering-display and content-light metadata. See `references/platform-specs.md` for platform specifics.

### 4. Make the SDR version with a real tone map

The conversion must pass through linear light in floating point, or highlights clip silently:

```bash
python3 bin/hdr_deliver.py to-sdr deliverable.mp4 deliverable-sdr.mp4
```

The helper builds the correct filter chain (`zscale=t=linear:npl=100` to linear, `format=gbrpf32le`, primaries to BT.709, `tonemap`, then BT.709 transfer and matrix) and forces BT.709 output tags so the SDR file never carries leftover HDR tags. Use `--tonemap bt2390` for the broadcast-standard curve, `hable` for a pleasing default, `mobius` to keep more highlight detail. Recipe variants (libplacebo, HLG sources, Dolby Vision) live in `references/tonemap-recipes.md`.

### 5. Verify before upload

```bash
python3 bin/hdr_deliver.py verify deliverable.mp4
```

It re-probes and fails loudly on the common killers: tags that disagree with pixels, missing mastering-display metadata on PQ files, 8-bit HDR, or `hev1` tags aimed at Apple devices. A deliverable that does not pass `verify` does not ship.

## Output Contract

Each deliverable run produces:

- `deliverable.mp4`: HEVC Main 10, BT.2020 / PQ tags, `hvc1`, ST 2086 + MaxCLL/MaxFALL metadata, `verify` clean.
- `deliverable-sdr.mp4`: H.264, BT.709 tags, yuv420p, tone-mapped, `verify` clean.
- A `delivery-notes.txt` next to the outputs: source probe result, transfer choice and why, tone map used, and the exact commands run (reproducible without the helper).

## Operating Rules

1. **Tags and pixels must agree.** The #1 cause of broken HDR is tagging SDR pixels as PQ or vice versa. Probe first, convert pixels with a real transform, retag after.
2. **Never invent metadata.** MaxCLL/MaxFALL and mastering-display values must come from the grade or be measured, not guessed. If unknown, omit the light levels rather than fabricate them (missing metadata tone-maps conservatively; wrong metadata tone-maps wrong).
3. **SDR from HDR is a creative decision, not a checkbox.** Preview the tone map on a bright scene and a skin-tone scene. `bt2390` for broadcast compliance, `hable` for pleasant, `mobius` for highlight preservation.
4. **Dolby Vision profiles 7/8 need `dovi_tool`** for full decode; ffmpeg alone resolves only the base layer (profile 5 is PQ-based and tone-maps like HDR10). HDR10+ dynamic metadata must be extracted and re-injected after the final encode with `hdr10plus_tool`; it does not survive a transcode.
5. **Do not upconvert SDR to HDR** and call it HDR. Real HDR requires real dynamic range. The helper refuses `--to-hdr` on SDR inputs unless `--force` is given.
