Nodes/LumaFlux/Save HDR Video (HEVC 10-bit PQ)
ComfyUI Node

Save HDR Video (HEVC 10-bit PQ)

10-bit HEVC, and the ffmpeg Check That Decides If It Runs

By shreshthsaini·Created 3 months ago·Updated 8 days ago· 2
Save HDR Video (HEVC 10-bit PQ)
  • hdr_pq
    filename_prefixlumaflux/hdr
    fps24.000
    crf16

    A batch of PQ frames is not a video. To become one it needs to be encoded at 10 bits per channel, tagged so players know the transfer function is PQ and the primaries are BT.2020, and given mastering metadata that tells a display what the content was graded against. Get any of those wrong and you've made an 8-bit file wearing an HDR badge - which is the exact failure this node refuses to commit.

    It takes the hdr_pq batch from LumaFlux SDR to HDR and encodes it as yuv420p10le HEVC with 1000-nit mastering metadata. That's the delivery format from the paper, and if you're going to ship HDR video anywhere, it's the one that plays.

    How it works

    The node pipes raw 16-bit RGB frames into a system ffmpeg over stdin and runs libx265 at CRF 16 and preset medium, with -pix_fmt yuv420p10le, -color_primaries bt2020, -color_trc smpte2084, -colorspace bt2020nc, matching x265 params, and master-display / max-cll metadata for a 1000-nit peak. The hvc1 tag is set so QuickTime and Apple's players pick it up. Four encoder threads, one mp4 out, in ComfyUI/output/lumaflux/ by default.

    Before any of that, it checks your ffmpeg. Not by version string - it asks ffmpeg -h encoder=libx265 and looks for yuv420p10le in the answer. If it isn't there, the node stops with an error instead of letting ffmpeg silently downgrade the encode to 8-bit, which under PQ bands so badly the file is worthless. You can run the same check yourself:

    ffmpeg -hide_banner -h encoder=libx265 | grep yuv420p10le
    

    No output means your build is 8-bit-only. ComfyUI's portable bundled ffmpeg is the usual culprit; a stock distro or Homebrew ffmpeg is normally fine.

    The inputs

    hdr_pq is the PQ/BT.2020 batch, one frame per batch entry, and this is where the batch-as-sequence convention bites: the video node has no idea about time, so fps is the only thing setting playback speed. Set it to the frame rate you actually want, not the rate you think your source was. It takes 1–240 with a fine step, so 23.976 is expressible.

    crf is libx265 quality on the usual 0–51 scale, default 16, lower is bigger and better. 16 is already a high-quality target; go to 14 if you can see banding in gradients and 18–20 if you're just checking a shot. filename_prefix behaves like every other ComfyUI saver - lumaflux/hdr by default, slash creates a subfolder, a counter suffix gets appended.

    No outputs. It's a terminal node; the UI shows an animated thumbnail when it's done.

    Install

    cd ComfyUI/custom_nodes
    git clone https://github.com/shreshthsaini/LumaFlux
    pip install -r LumaFlux/requirements.txt
    

    Both by hand and through ComfyUI Manager (search "LumaFlux") this is the whole install - the repository root is the node pack. Note what the requirements file does not contain: no PyAV, no ffmpeg Python binding. The node shells out to whatever ffmpeg is on your PATH, so making sure that binary is 10-bit capable is part of your setup, not something pip can fix. Restart ComfyUI after install.

    Where people get burned

    Chroma is 4:2:0. That's normal for consumer HDR delivery, and it means the color channels were subsampled - the HDR signal survives, the chroma resolution doesn't. If you're handing frames to a compositor or a grade, give them the 16-bit PNGs instead and let them encode it themselves.

    There is no audio track. The encoder reads frames from stdin; nothing maps an audio input, because the node doesn't have one. Mux it afterwards without re-encoding the video:

    ffmpeg -i hdr_00001_.mp4 -i source.mp4 -c:v copy -c:a copy -map 0:v:0 -map 1:a:0 out_hdr.mp4
    

    Odd dimensions get trimmed, not padded. The writer rounds height and width down to even numbers before encoding, so a 641-pixel-wide frame loses a column. In practice you won't see it; if you're doing pixel-exact work, work from the PNG sequence.

    Player support is where reality sets in. The file is correctly tagged and correctly encoded, and a player that ignores HDR metadata will still show it dim and desaturated - the same "my HDR looks broken" experience people report across every HDR toolchain. Check it in something that tone-maps properly before deciding the conversion failed. And in a browser, YouTube-class platforms need the HDR10 tag on upload; a self-hosted mp4 only looks right in a player that reads the metadata.

    It's the slowest node in the pack, and it runs after the conversion has already cost you ~5 seconds and 27 GB of VRAM per 1080p frame. Encode a few frames and watch them before you commit to a 700-frame batch.

    CategoryLumaFlux

    Inputs (4)

    NameTypeDefaultDescription
    hdr_pqIMAGEPQ / BT.2020 frames from LumaFlux SDR to HDR.
    filename_prefixSTRINGlumaflux/hdr
    fpsFLOAT24.0001–240
    crfINT160–51libx265 quality; lower is larger and better.

    Outputs (0)

    No outputs