Zura · Reference Audio for Video
The same audio swap, but paranoid and memory-bounded
- video
- clip_details
- audio
- video_with_reference_audio
- clip_details
If you read the GenjApplyReferenceAudio page and thought "that's simple", this is its sibling with trust issues. ZuraApplyBoundedReferenceAudio ("Zura · Reference Audio for Video") has the same exact inputs and outputs - video, clip_details, audio_start_seconds, optional audio - but does the swap in a completely different way, and it's the one the pack's streamed Wan route uses.
Why two nodes for one job
Because the simple one decodes your video to RGB frames to rebuild it. On a long clip that's gigabytes of float tensors sitting in RAM, and on a 16 GB GPU with a 40-minute Wan queue in front of it, that's the difference between a run and an OOM. The bounded version never holds the picture in memory at all.
The mechanism:
- It demands a native file-backed video (
VideoFromFile), not a memory buffer.Bounded audio requires a native file-backed VIDEO.is the error if you hand it an in-memory clip. - It walks the container packet by packet, counting and checking presentation timestamps, and refuses anything that isn't constant 24 fps, single-stream, un-rotated, without duplicate or variable presentation timestamps.
Prepare the selected video at constant 24 fps.covers a lot of phone footage. - It remuxes the original H.264 video packets byte-for-byte - no re-encode, no quality loss - and attaches the selected audio as raw PCM float32, positioning the audio with an exact sample offset instead of emitting leading silence.
Bounded lossless audio attachment currently requires a prepared H.264 clip.is what you get otherwise. - Everything is written to
output/genj/reference_video/as<hash>_<uuid>.partial.movand atomically renamed, plus the selected track as a.wavinoutput/genj/reference_audio/. - Then it verifies its own work: that the remux produced an identical packet signature, that decoding the new file gives back exactly the PCM you selected, that the source file's digest hasn't changed since the read started, and that the output directory didn't get swapped. If any check fails it deletes the partials and raises.
clip_details comes back with a bounded_audio block recording the source sha256, the output sha256, the selected-audio signature and the trim, so later stages can prove the track is the one you approved.
Install
ComfyUI Manager → search Zura Nodes → install → restart. Manual:
cd ComfyUI/custom_nodes
git clone https://github.com/ZURAVFX/ComfyUI_zura_nodes
python -m pip install -r ComfyUI/custom_nodes/ComfyUI_zura_nodes/requirements.txt
PyAV does the packet work; ffmpeg isn't needed on PATH for this node's core path, though the rest of the pack needs it. No models involved.
When to reach for it
Honestly: when your clip is long, or when you noticed the audio was slightly early. In Studio you don't choose - the streamed Wan route picks this node automatically and the standard routes use the plain one. In a hand-built graph:
- Use this node if you're on the Wan streamed/looped route, if your clip is more than about a minute, or if you've been bitten by a half-second audio drift you couldn't explain.
- Use
GenjApplyReferenceAudiofor everything else. It's more forgiving: it'll take a re-encoded clip, a memory-buffer video, or a slightly odd timestamp layout, and it produces the same shape ofclip_details.
Common issues
The selected clip metadata differs from its actual native VIDEO.- theclip_detailsyou passed don't describe the video you passed. Usually one of them went through an extra node (a resizer, a trim) that changed the raster or frame count without updating the details. Re-runGenjSelectClip, or feed the details from the node that actually produced the video.An audio offset requires a separate reference track.- you setaudio_start_secondsabove 0 without wiringaudio. The offset only means something relative to an external track; the clip's own audio already starts where the clip starts.This explicit native trim cannot retain the exact rounded audio sample count. Bake the selected clip first.- you applied a native trim to the video and the rounded sample count for that trim doesn't line up. Run the selection throughGenjSelectClipso the clip is a real file with a clean timeline.- Silence where you expected a soundtrack. Check the video has an audio stream at all; the bounded path inspects the actual container rather than assuming, and a silent source plus no reference track gives you a silent result.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| video | VIDEO | — | |
| clip_details | GENJ_CLIP | — | |
| audio_start_seconds | FLOAT | 0.00–86400 | — |
| audioopt | AUDIO | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| video_with_reference_audio | VIDEO | — |
| clip_details | GENJ_CLIP | — |