Video HDR Decode
You feed it pixels, not latents
- image
- hdr_image
- sdr_preview
- decode_report
The name says "decode" and it means the other decode. This node does not touch a VAE and does not want a latent. Its input is image - decoded, display-referred sRGB video frames in 0–1 - and its job is to turn that SDR signal into an HDR one at a chosen peak and gamut, with PQ or HLG output and a tone-mapped SDR preview on the side.
It's the last stop on the pack's HDR-video path: sample, decode with Video Batch Decode, then encode to a delivery signal here.
How it works
Frames arrive linearised with a pure 2.2 gamma, and 1.0 is mapped to peak_nits. That's the author's description and it's worth internalising, because it means the node assumes your frames are plain sRGB-ish display values - if you already handed it scene-linear float, you've gamma-decoded something twice. (Which is exactly why Video Batch Decode has an output_linear switch: use it when the next node wants linear, not when it wants this one.)
hdr_metadata_json (default {"peak_nits":1000,"gamut":"BT.2020","eotf":"PQ (ST.2084)"}) sets the target - hand it the string from Video HDR Conditioner, or type it yourself. peak_nits is the mastering peak your 1.0 maps to, and gamut is where the primaries conversion goes (BT.2020 in the default, and if the key is absent).
tonemap is the curve doing the work. Reinhard (default) is an extended Reinhard whose white point is the brightest input, lifted by any positive exposure_compensation_ev, so that value lands exactly on peak_nits and everything above it rolls off. The tooltip adds the practical catch: "at 0 EV or less there is nothing to compress," so with exposure_compensation_ev at its 0 default the tonemap does less than you might expect. Linear clip clamps at 10,000 nits, Pass-through applies no curve at all (still clamped at 10,000 by the encode).
output_eotf picks the actual signal: PQ (ST.2084) code values where 1.0 = 10,000 nits, HLG (BT.2100)'s OETF, Linear clamped light normalised to 10,000 nits, or sRGB / BT.1886 - the sRGB curve on light relative to peak_nits, which is an SDR signal, useful when you want an HDR-mastered look in an SDR container.
sdr_preview_nits (default 100, max 203) sets how the sdr_preview output reads: light is measured in units of that value and rolled off so the brightest input hits display white. Lower means a brighter preview. It's a legitimate sanity check - if the preview looks blown out, your tonemap isn't holding the highlights.
gamut_clip (default on) clamps the encode container to 0–1 after the primaries conversion, catching negatives from out-of-gamut colours and values above peak.
Outputs: hdr_image, sdr_preview and decode_report.
Honest expectations
HLG clips above 1000 nits by convention - BT.2100 / BT.2408 / OCIO all reference it to a 1000-nit display - so for a 4000- or 10000-nit master, use PQ. That's in the pack's known issues, not folklore.
And note what this node can and cannot do. It's deterministic: it takes the dynamic range the sampler gave it and places it correctly in an HDR container, with contrast and roll-off under your control. It cannot find highlight detail that isn't in the input. On AI-generated video that's usually fine, because generated frames already have a specular-heavy look when you asked for one - the conditioner's descriptors do the aiming, this node does the delivery.
Finally, reviewing an HDR signal needs a viewer that understands it. The pack's Viewer canvas is SDR by design (its own known-issues entry says as much), so use sdr_preview for checking work and put hdr_image on a real HDR display, or through the pack's HDR Monitor, before you decide the grade is right. A PQ signal viewed as if it were sRGB looks washed out, and people conclude their pipeline is broken.
Install
- ComfyUI Manager → search Radiance → Install → restart → refresh the browser.
- Or:
cd ComfyUI/custom_nodes
git clone https://github.com/fxtd-studios/radiance.git
cd radiance
python -m pip install -r requirements.txt
Windows portable users: run that pip line with python_embeded\python.exe. The pack's requirements are the long list you'd expect from a colour-management pack - OpenEXR, OpenImageIO, OpenColorIO, transformers, scipy, tifffile - and its install.py re-checks the first two after a Manager install. This node downloads no weights. If Manager installs an older build than the README's 3.5.0, run Update or git pull in custom_nodes/radiance.
Gotchas
The eotf key in your JSON is not used - the node's own output_eotf decides, always. Ask for PQ in the JSON and set sRGB / BT.1886 here and you'll get sRGB with a PQ label in the report.
And if you're writing out for delivery, take hdr_image to the pack's Video Export with mode: hdr_decode only if you want PQ - that path re-runs a Reinhard/PQ encode and ignores the JSON's EOTF. For HLG or Linear delivery, write from this node's output instead.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | Decoded video frames, display-referred sRGB Rec.709 in [0, 1]. Linearised with a pure 2.2 gamma; 1.0 is mapped to peak_nits. | |
| hdr_metadata_json | STRING | {"peak_nits":1000,"gamut":"BT.2020","eotf":"PQ (ST.2084)"} | JSON from RadianceVideoHDRConditioner or manually entered |
| tonemap | COMBO | Reinhard | Reinhard: extended Reinhard whose white point is the brightest input (1.0 lifted by a positive exposure_compensation_ev), so that value lands exactly on peak_nits and the highlights above it roll off; at 0 EV or less there is nothing to compress. Linear clip: clamp at 10,000 nits. Pass-through: no curve (clamped at 10,000 nits by the encode). |
| exposure_compensation_evopt | FLOAT | 0.0-6–6 | EV adjustment before tone-mapping |
| output_eotfopt | COMBO | PQ (ST.2084) | Encoding of hdr_image. PQ: ST 2084 code values (1.0 = 10,000 nits). HLG: BT.2100 OETF. Linear: clamped linear light normalised to 10,000 nits. sRGB / BT.1886: the sRGB curve on light relative to peak_nits (1.0 = peak), an SDR signal. |
| sdr_preview_nitsopt | FLOAT | 100.001–203 | Nits shown as white-ish mid-range in sdr_preview: light is measured in units of this value and rolled off with extended Reinhard so the brightest input reaches display white (1.0). Lower = brighter preview. |
| gamut_clipopt | BOOLEAN | true | Clamp to [0, 1] of the encode container after the primaries conversion (negatives from out-of-gamut colours, values above peak) |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| hdr_image | IMAGE | — |
| sdr_preview | IMAGE | — |
| decode_report | STRING | — |