ANDRO Save EXR
Real float32 EXR out of ComfyUI — with the file that tells you how it was made
- images
- folder
ANDRO Save EXR is where the float32 decode in this pack actually lands. It writes an IMAGE batch as a real float32 EXR sequence, values untouched - including everything outside [0,1] - and it's the only sane target for keep_out_of_range decodes that are headed into a grade. Wire ANDRO VAE Decode in, save, and out come frame.00001.exr files that carry the headroom a colourist actually spends. The pack's own arithmetic: 121 frames of 1280×704 float32 EXR is about 1.2 GB, so give it a folder of its own.
It uses the OpenEXR module, not cv2, and verifies the file landed rather than trusting the writer - both deliberate. OpenCV compiles the EXR codec in but leaves it disabled unless OPENCV_IO_ENABLE_OPENEXR=1 is set before cv2 imports, which no ComfyUI launcher does. Any node writing EXR through cv2.imwrite silently produces nothing; this one sidesteps the whole class of bug. No EXR backend available? It falls back to a 32-bit float TIFF carrying the same values under a different extension (needs pip install OpenEXR to avoid the fallback).
The part no other save node does
The header records how its own pixels were made. With write_metadata on, each file carries andro/* attributes - measured range, how much sat outside [0,1], bit depth, frame count - and wiring ANDRO VAE Decode's range_report into the decode_report input stores the decode's own account verbatim, dtypes included. Six months later the file still answers "was this the float32 pass?" on its own. That's the workflow-in-the-file philosophy ComfyUI PNGs made famous, transplanted to a format that can hold out-of-range values.
colorspace(defaultsrgb_display) labels what the numbers are: a decoded SDR frame is display-referred sRGB/Rec.709 gamma, not linear, which is what most compositors would wrongly assume about an untagged EXR. Two exceptions to remember: LTX-2.5 HDR decodes are ACEScct, and the LTX-2.3 HDR IC-LoRA is LogC3. This is a label, not a conversion - not one pixel is touched. Nuke or Resolve still has to be told the same colourspace on input.clipped_layeradds a second EXR layer holding exactly what the stock clamp would have deleted, zero everywhere it would have kept the value. The loss then travels inside the file instead of in a screenshot you have to show someone.start_frame/padding- defaults 1 and 5 give the historicstem.00001.exr; VFX pipelines set 1001 with padding 4 and land onframe.1001.exr, the convention Nuke and Resolve expect. Numbers too big for the padding are written in full, never truncated (truncation would collide two frames onto one filename).embed_workflow(on by default) stores the whole workflow and prompt in the header plus a summary of every checkpoint, VAE, seed and prompt - turn it off for confidential graphs; the summary fields still get written. A<stem>.manifest.jsonsidecar carries the same facts for scripts without an EXR library.output_folder- absolute path to drop the sequence straight into a project folder; empty means the ComfyUI output directory.
half_float (16f) halves file size at roughly 11 bits of mantissa - far above bf16, so a 16f EXR of a float32 decode keeps most of what the decode won. Use 32f when frames will be stretched hard under a grade.
Install
ANDRO Save EXR ships in the comfyui-vae-float32 pack. ComfyUI-Manager → search comfyui-vae-float32 → install → restart, or:
cd ComfyUI/custom_nodes
git clone https://github.com/AndreiOrehov/comfyui-vae-float32
pip install OpenEXR
Restart and find it under ANDRO. One operational gotcha shared across the pack: a measuring/saving node that "never runs" usually means you hit the selection strip's ▶ button, which executes only the selected output node and drops everything else while still reporting success. Hit a plain Run. The author's full pipeline example, LTX2.5_float32_EXR.json, shows the intended whole - float32 decode, EXR sequence, video, and the decode's range report all from one image.
Inputs (13)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | The batch to write. Values are written exactly as they arrive - nothing is clamped, scaled or tone-mapped on the way out. | |
| filename_prefix | STRING | float32/frame | Subfolder and file stem. Frames are numbered from 1 as stem.00001.exr, so 'float32/frame' gives float32/frame.00001.exr. |
| half_float | BOOLEAN | false | 16f halves the file size and still carries everything outside [0,1] - what it costs is precision, roughly 11 bits of mantissa against 24. That is far above bfloat16, so a 16f EXR of a float32 decode keeps most of what the decode won. Use 32f when the frames are going into a grade that will stretch them hard, 16f when disk or a downstream tool argues. |
| output_folderopt | STRING | Absolute path to write to, e.g. D:/renders/shot_04. Empty means the ComfyUI output directory. Use this to drop a sequence straight into a project folder instead of fishing it out of output/ later; filename_prefix still applies inside it. | |
| write_metadataopt | BOOLEAN | true | Record how these pixels were made in the EXR header: measured range, how much sat outside [0,1], bit depth, and - if you wire the decode report in - the dtypes the decode actually ran in. Six months later the file still answers 'was this the float32 pass?' on its own. Needs the OpenEXR backend; the TIFF fallback drops it. |
| decode_reportopt | STRING | Wire ANDRO VAE Decode's range_report here and its contents are stored in the file's header. Optional - without it the metadata still carries everything measurable from the pixels themselves. | |
| clipped_layeropt | BOOLEAN | false | Add a second EXR layer named 'clipped' holding exactly what the stock [0,1] clamp would have deleted - zero everywhere it would have kept the value. The evidence then travels inside the file instead of in a screenshot. Costs roughly double the file size, and readers that ignore extra layers are unaffected. |
| colorspaceopt | COMBO | srgb_display | What these pixels ARE, written into the header. A decoded SDR generation is display-referred sRGB/Rec.709 gamma, not linear - that is what most VAEs produce (SD, Flux, Wan, LTX SDR), so srgb_display is the honest default. Two exceptions: LTX-2.5 HDR decodes are ACEScct, and the LTX-2.3 HDR IC-LoRA is LogC3. This writes a label - no pixel is converted. |
| colorspace_noteopt | STRING | Free text appended to the label, e.g. 'Flux.2 decode' or 'after OCIO ColorSpace to ACEScg'. | |
| start_frameopt | INT | 10–999999 | Number the first frame gets. 1 keeps the historic stem.00001.exr; VFX pipelines start a sequence at 1001. |
| paddingopt | INT | 51–8 | Digits in the frame number: 5 gives shot.00001.exr, and 4 with start_frame 1001 gives shot.1001.exr. A number too large for the padding is written in full rather than truncated. |
| shot_infoopt | STRING | Project / shot / artist / note. Free text; it goes into the EXR header as andro/shotInfo and into the manifest beside it. | |
| embed_workflowopt | BOOLEAN | true | Store the full workflow graph in the EXR header and in the sidecar manifest, so the file can rebuild the run that made it. Turn it off for confidential graphs - the summary fields (models, seeds, prompts, API settings, hash) are still written. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| folder | STRING | The folder actually written to - wire it onward to a node that reads the sequence back. |