Camera Sync
One JSON file, and every optical node agrees on where the camera was
- camera
- focal_length
- f_stop
- shutter_angle
What it is
A reader. You give it JSON describing a camera - focal length, f-stop, shutter angle, an optional 4×4 transform - and it outputs a RADIANCE_CAMERA bundle plus the three numbers broken out individually. Other nodes in the pack (depth of field, rolling shutter and the rest of the optics family) consume that bundle so they all agree about which lens shot the frame.
It's plumbing in the truest sense: it doesn't change a pixel, and it exists to stop you typing "35" into four different nodes. The KB's node-plumbing essay makes the general point well - the value-node layer exists to fight repetition, so a number that five consumers need should have exactly one source. That's this node, for camera metadata.
It matters more than it sounds, because mismatched camera parameters between nodes produce results that look almost right - a defocus that's slightly the wrong size, a rolling shutter that skews the wrong amount. Those are hard to diagnose by eye and trivial to avoid by having one source of truth.
Inputs
camera_data is a multiline string holding JSON, defaulting to {}. The tooltip gives the shape:
{"focal_length": 35.0, "f_stop": 2.8, "shutter_angle": 180.0, "transform": [ ... ]}
For an animated camera, wrap entries in a frames list instead; frame_offset indexes into that list (and is ignored entirely for a single camera). shutter is accepted as an alias for shutter_angle, which is a small kindness - it's the name half the world uses.
camera_file is optional and takes precedence over camera_data. Point it at a .json file and the inline JSON is ignored. So you can leave the multiline box alone and drive the node from a file exported by your 3D tool, which is the workflow this exists for.
Outputs
camera is a RADIANCE_CAMERA - the bundle type, wired into the optical nodes that want everything at once. Then focal_length (FLOAT), f_stop (FLOAT) and shutter_angle (INT) as individual scalars for anything that only needs one number. Note the shutter comes out as an integer: shutter angles in practice are whole degrees (180, 172.8 rounds, 90), and the output type says so.
Error behaviour, which is the good part
The node raises rather than guessing. A missing file raises FileNotFoundError. Malformed JSON raises with the parse error. And asking for an .abc file raises with a message that tells you why: Alembic isn't supported, export the camera to JSON.
That's deliberate, and the source comment spells out the old behaviour it replaced: a missing file or bad JSON used to fall back to a default 35mm f/2.8 camera with the node still green. Which is worse than an error, because you'd render a shot with a silent wrong lens and only find out later. If your workflow breaks, that's the node doing its job.
Writing the JSON
Keys are focal_length (mm), f_stop, shutter_angle (degrees, or shutter), and transform as a flat 4×4. A single camera is a flat object; an animated one is {"frames": [ {...}, {...} ]}. If you're exporting from Blender, Maya or Nuke, whatever script you write needs to emit that shape - it's small enough to hand-write for a static camera and worth automating for anything animated.
Install
ComfyUI Manager → search Radiance → install → restart → refresh the browser. Manually:
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 should run pip with ComfyUI's bundled python_embeded\python.exe. This node needs nothing but the standard library's json - the heavy dependencies in the pack are for the file-format and model-driven nodes elsewhere.
Gotchas
- Malformed JSON. Multiline string widgets are not JSON editors; a trailing comma fails the run. Paste into a linter if it's complaining.
.abcfiles. Not supported, by design, with an error that says so. Export JSON.- Assuming
camera_datawins.camera_fileoverrides it whenever it's set. If you're editing the JSON and nothing changes, check the file path first. frame_offseton a static camera. Ignored; it only indexes aframeslist. It's clamped to the list, so a too-large offset gives you the last frame rather than an error.- Units. Focal length is millimetres and shutter angle is degrees. Nothing converts for you.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| camera_data | STRING | {} | JSON camera data: {"focal_length": 35.0, "f_stop": 2.8, "shutter_angle": 180.0, "transform": [...]} or {"frames": [ ... ]} |
| frame_offset | INT | 0-10000–10000 | Index into a "frames" list (animated camera). Ignored for a single camera. |
| camera_fileopt | STRING | Path to a .json camera file. Takes precedence over camera_data. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| camera | RADIANCE_CAMERA | — |
| focal_length | FLOAT | — |
| f_stop | FLOAT | — |
| shutter_angle | INT | — |