You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(render): auto-detect HDR from media probes, add --sdr flag (heygen-com#526)
* feat(render): auto-detect HDR from media probes, add --sdr flag
Replace the --hdr opt-in model with automatic detection. When no flags
are passed, the renderer probes all video/image sources and enables HDR
output if any HDR color space is detected. Existing --hdr flag becomes
a force override. New --sdr flag forces SDR output.
Behavior matrix:
(no flags) + HDR content → HDR output
(no flags) + SDR content → SDR output
--hdr → force HDR (defaults to HLG if no HDR sources)
--sdr → force SDR (skips probing)
--hdr --sdr → error
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: align HDR auto-detect docs and tests
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/guides/common-mistakes.mdx
+8-4Lines changed: 8 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -190,11 +190,11 @@ These are mistakes that cannot be caught by the linter. For automated checks, ru
190
190
</Accordion>
191
191
192
192
<Accordiontitle="Expected HDR output but got SDR">
193
-
**Symptom:**Rendered with `--hdr`, but the output looks the same as SDR or `ffprobe` reports `color_transfer=bt709`.
193
+
**Symptom:**Expected an HDR render, but the output looks the same as SDR or `ffprobe` reports `color_transfer=bt709`.
194
194
195
-
**Cause:**`--hdr` is a *detection* flag, not a *force* flag. Hyperframes only switches to HDR encoding when a source `<video>` or `<img>` is tagged with BT.2020 / PQ / HLG color metadata. Two common reasons HDR is not engaged:
195
+
**Cause:**By default, Hyperframes only switches to HDR encoding when a source `<video>` or `<img>` is tagged with BT.2020 / PQ / HLG color metadata. Common reasons HDR is not engaged:
196
196
197
-
1.**All sources are SDR.**`--hdr` is a no-op on SDR-only compositions. Verify with `ffprobe`:
197
+
1.**All sources are SDR.**Auto-detect leaves SDR-only compositions in SDR. Verify with `ffprobe`:
@@ -204,7 +204,11 @@ These are mistakes that cannot be caught by the linter. For automated checks, ru
204
204
205
205
2.**Wrong output format.** HDR output requires MP4. `--format mov` and `--format webm` fall back to SDR — Hyperframes logs a warning when this happens.
206
206
207
-
`--docker` works the same as local rendering — `--hdr` is forwarded into the container and produces the same HDR10 MP4 output (slower, since the container falls back to software WebGL for SDR DOM capture).
207
+
3.**SDR was forced.**`--sdr` disables HDR even when HDR sources are present.
208
+
209
+
If you need HDR regardless of source metadata, use `--hdr` to force it.
210
+
211
+
`--docker` works the same as local rendering — auto-detect, `--hdr`, and `--sdr` are all forwarded into the container and produce the same output decisions (slower, since the container falls back to software WebGL for SDR DOM capture).
208
212
209
213
See [HDR Rendering](/guides/hdr) for the full source requirements and verification steps.
Copy file name to clipboardExpand all lines: docs/guides/hdr.mdx
+15-15Lines changed: 15 additions & 15 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,10 +3,10 @@ title: HDR Rendering
3
3
description: "Render compositions to HDR10 MP4 (BT.2020 PQ or HLG, 10-bit H.265) when sources contain HDR video or images."
4
4
---
5
5
6
-
Hyperframes can render to HDR10 MP4 (H.265 10-bit, BT.2020) when your composition references HDR video or HDR still images. HDR is opt-in via the `--hdr` flag — it auto-detects HDR sources and falls back to SDR when none are present.
6
+
Hyperframes can render to HDR10 MP4 (H.265 10-bit, BT.2020) when your composition references HDR video or HDR still images. HDR is auto-detected by default from your media sources and falls back to SDR when none are present.
7
7
8
8
<Note>
9
-
The `--hdr` flag does not *force*HDR. It enables HDR detection. If your composition contains only SDR media, the flag is a no-op and you get a normal SDR render.
9
+
By default, Hyperframes probes your media and enables HDR only when HDR sources are present. Use `--hdr` to force HDR even without HDR sources, or `--sdr` to force SDR even when HDR sources are present.
10
10
</Note>
11
11
12
12
## Quickstart
@@ -20,12 +20,12 @@ Hyperframes can render to HDR10 MP4 (H.265 10-bit, BT.2020) when your compositio
20
20
21
21
See [Source Media](#source-media-requirements) for full details.
22
22
</Step>
23
-
<Steptitle="Render with --hdr">
23
+
<Steptitle="Render normally">
24
24
```bash Terminal
25
-
npx hyperframes render --hdr --output output.mp4
25
+
npx hyperframes render --output output.mp4
26
26
```
27
27
28
-
HDR output requires `--format mp4`. If you also pass `--format mov` or `--format webm`, Hyperframes logs a warning and falls back to SDR.
28
+
HDR output requires `--format mp4`. If Hyperframes detects HDR sources, it renders HDR automatically. If you also pass `--format mov` or `--format webm`, Hyperframes logs a warning and falls back to SDR.
29
29
</Step>
30
30
<Steptitle="Verify the output is HDR">
31
31
Use `ffprobe` to confirm the encoded stream carries HDR color tagging and HDR10 metadata:
@@ -40,14 +40,14 @@ Hyperframes can render to HDR10 MP4 (H.265 10-bit, BT.2020) when your compositio
40
40
41
41
## How HDR Mode Works
42
42
43
-
When `--hdr` is set, the producer:
43
+
During render, the producer:
44
44
45
45
<Steps>
46
46
<Steptitle="Probes every video and image source">
47
-
Runs `ffprobe` on each `<video>` and `<img>` source to read its color space (primaries, transfer function, matrix). Probing is gated on `--hdr` to avoid `ffprobe` overhead on SDR-only renders.
47
+
Runs `ffprobe` on each `<video>` and `<img>` source to read its color space (primaries, transfer function, matrix). This probe drives the default auto-detect behavior and is skipped only when you explicitly force SDR with `--sdr`.
48
48
</Step>
49
49
<Steptitle="Picks the dominant HDR transfer">
50
-
If any source uses PQ (`smpte2084`), the output uses **PQ**. Otherwise, if any source uses HLG (`arib-std-b67`), the output uses **HLG**. If no HDR sources are found, the flag is a no-op and you get an SDR render.
50
+
If any source uses PQ (`smpte2084`), the output uses **PQ**. Otherwise, if any source uses HLG (`arib-std-b67`), the output uses **HLG**. If no HDR sources are found, the render stays SDR.
51
51
</Step>
52
52
<Steptitle="Encodes to H.265 10-bit BT.2020">
53
53
The video encoder switches to `libx265` with `-pix_fmt yuv420p10le`, color tagging `colorprim=bt2020:transfer=<smpte2084|arib-std-b67>:colormatrix=bt2020nc`, and HDR10 static metadata (`master-display` and `max-cll`). Without that metadata, players (QuickTime, YouTube, HDR TVs) tone-map the stream as if it were SDR BT.2020 — which looks wrong.
@@ -89,7 +89,7 @@ Hyperframes supports HDR still images delivered as **16-bit PNGs** tagged with B
89
89
src="./assets/hdr-photo.png" />
90
90
```
91
91
92
-
When `--hdr` is set, the image is decoded once to 16-bit linear-light RGB and composited natively into the HDR output.
92
+
When HDR is enabled, the image is decoded once to 16-bit linear-light RGB and composited natively into the HDR output.
93
93
94
94
<Note>
95
95
HDR `<img>` decoding is limited to **16-bit PNG**. JPEG, WebP, AVIF, and APNG are not recognized as HDR sources — they load through the normal SDR DOM path. For HDR motion, use a `<video>` element.
@@ -113,7 +113,7 @@ This is the same pipeline that handles compositions where, for example, an HDR d
113
113
|`mov`| No — falls back to SDR |
114
114
|`webm`| No — falls back to SDR |
115
115
116
-
If you set `--hdr` together with`--format mov` or `--format webm`, Hyperframes logs a message and produces the equivalent SDR render. There is no error — the render still completes — so check the logs (or your verification step) to confirm you got HDR.
116
+
If HDR is enabled and you also pass`--format mov` or `--format webm`, Hyperframes logs a message and produces the equivalent SDR render. There is no error — the render still completes — so check the logs (or your verification step) to confirm you got HDR.
117
117
118
118
## Verifying HDR Output
119
119
@@ -147,10 +147,10 @@ For HLG renders the only difference is `color_transfer=arib-std-b67` — the res
147
147
148
148
## Docker Rendering
149
149
150
-
`--hdr` is forwarded into the Docker render pipeline, so you can produce HDR10 MP4 output from the containerized renderer:
150
+
Docker uses the same auto-detect logic as local rendering, so you can produce HDR10 MP4 output from the containerized renderer without extra flags:
The container runs the same probe → composite → encode pipeline as the local renderer. Verify the output with the same `ffprobe` checks described in [Verifying HDR output](#verifying-hdr-output).
@@ -161,7 +161,7 @@ The container runs the same probe → composite → encode pipeline as the local
161
161
162
162
## Limitations
163
163
164
-
-**MP4 only** — `--hdr` with `--format mov` or `--format webm` falls back to SDR
164
+
-**MP4 only** — HDR output with `--format mov` or `--format webm` falls back to SDR
165
165
-**HDR images: 16-bit PNG only** — other formats (JPEG, WebP, AVIF, APNG) are not decoded as HDR and fall through the SDR DOM path
166
166
-**H.265 only — H.264 is stripped** — calling the encoder with `codec: "h264"` and `hdr: { transfer }` is rejected; the encoder logs a warning, drops `hdr`, and tags the output as SDR/BT.709. `libx264` cannot encode HDR, so the alternative would be a "half-HDR" file (BT.2020 container tags but a BT.709 VUI block in the bitstream) which confuses HDR-aware players.
167
167
-**GPU H.265 emits color tags but no static mastering metadata** — `useGpu: true` with HDR (nvenc, videotoolbox, qsv, vaapi) tags the stream with BT.2020 + the correct transfer (smpte2084 / arib-std-b67) but does **not** embed `master-display` or `max-cll` SEI. ffmpeg does not let those flags pass through hardware encoders. The output is suitable for previews and authoring but not for HDR10-aware delivery (Apple TV, YouTube, Netflix). For spec-compliant HDR10 production output, leave `useGpu: false` so the SW `libx265` path embeds the mastering metadata.
@@ -172,7 +172,7 @@ The container runs the same probe → composite → encode pipeline as the local
172
172
173
173
| Symptom | Likely cause |
174
174
|---------|--------------|
175
-
| Output looks identical to SDR | Source media is SDR — `--hdr` is a no-op without an HDR source. Run `ffprobe` on your inputs |
175
+
| Output looks identical to SDR | Source media is SDR, or SDR was forced with `--sdr`. Run `ffprobe` on your inputs and check the render logs|
176
176
| Output is "kind of HDR" but tone-mapped wrong on YouTube/QuickTime | Missing HDR10 static metadata on the encoded stream. Verify with the ffprobe snippet above |
177
177
| Docker render is much slower than local | Expected — the container falls back to software WebGL for SDR DOM capture. Pixel output is the same |
178
178
| Used `--format webm` and got SDR | Expected — HDR output is MP4 only |
@@ -185,7 +185,7 @@ The container runs the same probe → composite → encode pipeline as the local
Copy file name to clipboardExpand all lines: docs/packages/engine.mdx
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -282,7 +282,7 @@ await server.close();
282
282
283
283
The engine exports two layers of HDR support: **color-space utilities** that classify sources and configure the FFmpeg encoder, and a **WebGPU readback runtime** for capturing CSS-animated DOM directly into HDR.
284
284
285
-
For end-to-end HDR rendering (HDR video and image sources composited into an HDR10 MP4) use the [producer](/packages/producer) or the CLI's `--hdr`flag — see [HDR Rendering](/guides/hdr). The APIs below are for custom integrations.
285
+
For end-to-end HDR rendering (HDR video and image sources composited into an HDR10 MP4) use the [producer](/packages/producer) or the CLI render pipeline with HDR auto-detect / `--hdr`/ `--sdr` — see [HDR Rendering](/guides/hdr). The APIs below are for custom integrations.
This path requires **headed Chrome with `--enable-unsafe-webgpu`** — WebGPU is unavailable in `chrome-headless-shell`. It is *not* used by the default `--hdr` render pipeline (which extracts HDR pixels from sources via FFmpeg and composites in Node). Use it only for advanced custom pipelines that need CSS animations driving HDR pixel output.
347
+
This path requires **headed Chrome with `--enable-unsafe-webgpu`** — WebGPU is unavailable in `chrome-headless-shell`. It is *not* used by the default HDR-aware render pipeline (which extracts HDR pixels from sources via FFmpeg and composites in Node). Use it only for advanced custom pipelines that need CSS animations driving HDR pixel output.
0 commit comments