85453da49f
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled
198 lines
10 KiB
Plaintext
198 lines
10 KiB
Plaintext
---
|
|
title: HDR Rendering
|
|
description: "Render compositions to HDR10 MP4 (BT.2020 PQ or HLG, 10-bit H.265) when sources contain HDR video or images."
|
|
---
|
|
|
|
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.
|
|
|
|
<Note>
|
|
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.
|
|
</Note>
|
|
|
|
## Quickstart
|
|
|
|
<Steps>
|
|
<Step title="Add an HDR source to your composition">
|
|
Hyperframes detects HDR from the source's color space metadata. The most reliable HDR sources are:
|
|
|
|
- **HDR video** tagged BT.2020 with PQ (`smpte2084`) or HLG (`arib-std-b67`) transfer
|
|
- **HDR still images** as 16-bit PNG with BT.2020 PQ encoding
|
|
|
|
See [Source Media](#source-media-requirements) for full details.
|
|
</Step>
|
|
<Step title="Render normally">
|
|
```bash Terminal
|
|
npx hyperframes render --output output.mp4
|
|
```
|
|
|
|
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.
|
|
</Step>
|
|
<Step title="Verify the output is HDR">
|
|
Use `ffprobe` to confirm the encoded stream carries HDR color tagging and HDR10 metadata:
|
|
|
|
```bash Terminal
|
|
ffprobe -v error -show_streams output.mp4 | grep -E 'color_transfer|color_primaries|color_space'
|
|
```
|
|
|
|
See [Verifying HDR output](#verifying-hdr-output) for what to look for.
|
|
</Step>
|
|
</Steps>
|
|
|
|
## How HDR Mode Works
|
|
|
|
During render, the producer:
|
|
|
|
<Steps>
|
|
<Step title="Probes every video and image source">
|
|
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`.
|
|
</Step>
|
|
<Step title="Picks the dominant HDR transfer">
|
|
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.
|
|
</Step>
|
|
<Step title="Encodes to H.265 10-bit BT.2020">
|
|
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.
|
|
</Step>
|
|
<Step title="Composites HDR sources natively, converts SDR overlays">
|
|
HDR videos and images are extracted as 16-bit linear-light pixels via FFmpeg, kept out of the DOM screenshot, and composited server-side at full bit depth. SDR DOM overlays (text, shapes, UI from your HTML) are converted from **sRGB** to **BT.2020** before being layered on top, so colors do not shift.
|
|
</Step>
|
|
</Steps>
|
|
|
|
## Source Media Requirements
|
|
|
|
### HDR video
|
|
|
|
Hyperframes recognizes HDR video from its `ffprobe` color space metadata:
|
|
|
|
| Indicator | Recognized as HDR |
|
|
|-----------|-------------------|
|
|
| `color_primaries` contains `bt2020` | Yes |
|
|
| `color_space` contains `bt2020` | Yes |
|
|
| `color_transfer = smpte2084` (PQ) | Yes — PQ |
|
|
| `color_transfer = arib-std-b67` (HLG) | Yes — HLG |
|
|
| All else (e.g. `bt709`, `smpte170m`) | No — treated as SDR |
|
|
|
|
A valid HDR source is any MP4 whose stream metadata reports BT.2020 primaries plus PQ or HLG transfer. Hyperframes will detect it automatically:
|
|
|
|
```bash Terminal
|
|
ffprobe -v error -show_streams assets/clip.mp4 | grep color
|
|
# color_primaries=bt2020
|
|
# color_transfer=smpte2084
|
|
# color_space=bt2020nc
|
|
```
|
|
|
|
### HDR still images
|
|
|
|
Hyperframes supports HDR still images delivered as **16-bit PNGs** tagged with BT.2020 primaries and PQ transfer. Drop them into the composition as a normal `<img>`:
|
|
|
|
```html index.html
|
|
<img class="clip" data-start="0" data-duration="3"
|
|
src="./assets/hdr-photo.png" />
|
|
```
|
|
|
|
When HDR is enabled, the image is decoded once to 16-bit linear-light RGB and composited natively into the HDR output.
|
|
|
|
<Note>
|
|
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.
|
|
</Note>
|
|
|
|
### SDR sources mixed with HDR
|
|
|
|
You can freely mix SDR and HDR media in the same composition:
|
|
|
|
- **SDR videos** stay in the DOM screenshot path and get the sRGB → BT.2020 conversion described above
|
|
- **HDR videos** are extracted natively at 16-bit and composited underneath the SDR DOM layer
|
|
- **SDR images and DOM elements** (text, shapes, gradients, GSAP animations) are converted from sRGB to BT.2020
|
|
|
|
This is the same pipeline that handles compositions where, for example, an HDR drone clip plays under an SDR lower-third with animated text.
|
|
|
|
## Output Format Requirements
|
|
|
|
| Output format | HDR supported |
|
|
|---------------|---------------|
|
|
| `mp4` | Yes — H.265 10-bit BT.2020, HDR10 metadata |
|
|
| `mov` | No — falls back to SDR |
|
|
| `webm` | No — falls back to SDR |
|
|
| `gif` | No — falls back to SDR |
|
|
|
|
If HDR is enabled and you also pass `--format mov`, `--format webm`, or `--format gif`, 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.
|
|
|
|
## Verifying HDR Output
|
|
|
|
Use `ffprobe` to confirm both the color tagging and the HDR10 static metadata are present:
|
|
|
|
```bash Terminal
|
|
ffprobe -v error -show_streams -select_streams v:0 output.mp4 \
|
|
| grep -E 'codec_name|pix_fmt|color_transfer|color_primaries|color_space'
|
|
```
|
|
|
|
For a PQ HDR10 render you should see:
|
|
|
|
```
|
|
codec_name=hevc
|
|
pix_fmt=yuv420p10le
|
|
color_space=bt2020nc
|
|
color_primaries=bt2020
|
|
color_transfer=smpte2084
|
|
```
|
|
|
|
Then check the HDR10 SEI / container boxes:
|
|
|
|
```bash Terminal
|
|
ffprobe -v error -show_frames -read_intervals "%+#1" \
|
|
-show_entries frame=side_data_list output.mp4
|
|
```
|
|
|
|
You should see entries for **Mastering display metadata** and **Content light level metadata**. Without them, HDR-aware players will treat the file as SDR BT.2020 and the colors will look washed-out or wrong on an HDR display.
|
|
|
|
For HLG renders the only difference is `color_transfer=arib-std-b67` — the rest of the checks are the same.
|
|
|
|
## Docker Rendering
|
|
|
|
Docker uses the same auto-detect logic as local rendering, so you can produce HDR10 MP4 output from the containerized renderer without extra flags:
|
|
|
|
```bash Terminal
|
|
npx hyperframes render --docker --output output.mp4
|
|
```
|
|
|
|
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).
|
|
|
|
<Note>
|
|
Docker HDR rendering currently runs **CPU-side** for the SDR DOM layer (the container falls back to software WebGL because GPU passthrough is not configured by default). Frame capture is therefore slower than local headed Chrome — measure your own composition with `--quiet` off and compare wall-clock times before sizing CI runners. The encoded HDR10 metadata and pixel data are identical to a local render.
|
|
</Note>
|
|
|
|
## Limitations
|
|
|
|
- **MP4 only** — HDR output with `--format mov` or `--format webm` falls back to SDR
|
|
- **HDR images: 16-bit PNG only** — other formats (JPEG, WebP, AVIF, APNG) are not decoded as HDR and fall through the SDR DOM path
|
|
- **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.
|
|
- **GPU H.265 emits color tags but no static mastering metadata** — `useGpu: true` with HDR (nvenc, videotoolbox, amf, 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.
|
|
- **Player support** — the [`<hyperframes-player>`](/packages/player) web component plays back the encoded MP4 in the browser and inherits whatever HDR support the host browser provides; it does not implement its own HDR pipeline
|
|
- **Headed Chrome HDR DOM capture** — the engine ships a separate WebGPU-based capture path for rendering CSS-animated DOM directly into HDR (`initHdrReadback`, `launchHdrBrowser`). It requires headed Chrome with `--enable-unsafe-webgpu` and is not used by the default render pipeline. See [Engine: HDR](/packages/engine#hdr-apis) if you are building a custom integration.
|
|
|
|
## Common Pitfalls
|
|
|
|
| Symptom | Likely cause |
|
|
|---------|--------------|
|
|
| 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 |
|
|
| 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 |
|
|
| Docker render is much slower than local | Expected — the container falls back to software WebGL for SDR DOM capture. Pixel output is the same |
|
|
| Used `--format webm` and got SDR | Expected — HDR output is MP4 only |
|
|
| HDR `<img>` looks SDR / washed out | Source is not a 16-bit PNG. Re-export as 16-bit PNG (BT.2020 PQ) or use a `<video>` element instead |
|
|
|
|
## Next Steps
|
|
|
|
<CardGroup cols={2}>
|
|
<Card title="Rendering" icon="film" href="/guides/rendering">
|
|
Local vs Docker, quality presets, workers
|
|
</Card>
|
|
<Card title="CLI" icon="terminal" href="/packages/cli">
|
|
Full `render` command reference including HDR auto-detect, `--hdr`, and `--sdr`
|
|
</Card>
|
|
<Card title="Engine: HDR APIs" icon="gear" href="/packages/engine#hdr-apis">
|
|
Public HDR utilities exported from `@hyperframes/engine`
|
|
</Card>
|
|
<Card title="Common Mistakes" icon="triangle-exclamation" href="/guides/common-mistakes">
|
|
Pitfalls that affect render output
|
|
</Card>
|
|
</CardGroup>
|