AV Sync Fixer
The boring node that stops your audio and frames disagreeing by a hair
- images
- audio
- images
- audio
The failure this thing exists for
Sooner or later you feed a video model a clip and a soundtrack that were never quite the same length. A muxed MP4's audio stream almost never ends on the exact sample where its frames end, and the encoders that turn both into latents are picky: audio one token short gives you a shape mismatch, silent trailing frames, or a run that dies in the concat node. It's the complaint that dogged LTX-2/2.3's native audio, and why hand-rolled if audio is shorter: pad with silence blocks get pasted into shared workflows.
AV Sync Fixer is that block, packaged. Frames in, audio in, frames out unchanged, audio out trimmed or padded to exactly the length those frames call for. It never resamples, so whatever survives stays where it was on the timeline. It's the standalone cousin of the fix the pack's WorkflowConfig nodes apply to their own video slots: the version for frames and audio from loaders of your own - a plain Load Video, a chunked Wan/SVI chain, music from an audio pack.
How it works
Two behaviours, chosen by target.
Wan2.2 / LTX2.3 / LTX2.5 (video duration) fits the audio to the clip's duration: sample count becomes ceil(frames × sample_rate / fps). A frame batch carries no rate of its own, which is why fps is an input rather than something the node reads off the tensor.
MiniMax H3 (40 Hz audio tokens) does the grid math H3 actually validates against. The pack takes the rate pair from its own MiniMax H3 config node - 40 Hz audio tokens against a 24 fps video rate - computes round(frames × 40 / 24) tokens, then ceil(tokens × sample_rate / 40) samples. 121 frames at 24fps lands on 202 tokens and 222,705 samples, where the duration rule would only ask for 222,338. That 367-sample gap is the difference between "runs" and "rejects the pair", so pick the target that matches where the audio is going, not the model that made the video.
Both branches round the sample count up - a token boundary can land on half a sample at 44.1kHz, and rounding down leaves you one token short to an encoder that floors.
The length change is a tail slice or a tail pad with zeros. No resampling, no pitch shift. Your frames tensor passes straight through, same object, no copy, and the audio dict is copied first so the node never writes back into the loader's own output. It also says what it did - A/V sync fixed for 'input' - its audio track ran 367 samples short of 121 frames at 24.000 fps, so it was padded with silence to 222705 samples, or simply already in sync. That console line is the main reason to prefer this over a silent fix buried in another pack.
The inputs and outputs that matter
- images (IMAGE) - the frame batch. Only its frame count is read.
- audio (AUDIO) - the standard ComfyUI audio dict, copied rather than modified in place.
- fps (FLOAT, default 24, min 0.01, max 240) - wire the loader's own fps output here.
- target - one of the two choices above; defaults to the video-duration one.
Outputs are images and audio. Frames on to your VAE-encode path, audio to the audio VAE encode or the AV concat node. No model, no seed, nothing else to wire.
Installing it
ComfyUI Manager: search comfyui-daz-tools. Manually:
cd ComfyUI/custom_nodes
git clone https://github.com/denyazzolin/comfyui-daz-tools
Restart ComfyUI and look for AV Sync Fixer under utils.
The dependency story is genuinely nil: no requirements.txt in the repo, none declared in pyproject.toml, and this node never touches an audio file - it takes a dict something else already decoded. No pip install, no model download. Rare enough here to be worth saying out loud.
Common issues and traps
"no audio came in - the loader wired to audio handed on nothing." Your upstream output None - usually a video loader whose clip has no audio track, or a conditional branch that resolved to nothing. Fix the upstream, not the node. Same for no frames came in on images.
You wired the wrong fps. The quiet one. The node trusts your number, so a 16fps batch told it's 24fps gets audio fitted to a length 50% too short - and logged as a successful fix, because arithmetically it was. H3 mode warns when the source isn't 24fps; duration mode never cross-checks.
It won't fix latency. If speech lands a second late, padding does nothing - this only moves where the tail ends, never where a phoneme starts. The known LTX-2.3 talking-head drift is a timing problem in generation or conditioning, not a length problem. Nor is it a lip-sync tool, name notwithstanding.
Silence gets sized, not "fixed". An all-zeros waveform is logged as silence being sized rather than as a sync fix.
Put it before the encoders. Downstream of the VAE encode it does nothing - the shape is already whatever it was going to be.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | — | |
| audio | AUDIO | — | |
| fps | FLOAT | 24.0000.01–240 | — |
| target | COMBO | Wan2.2 / LTX2.3 / LTX2.5 (video duration) | 2 options: Wan2.2 / LTX2.3 / LTX2.5 (video duration), MiniMax H3 (40 Hz audio tokens) |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| images | IMAGE | — |
| audio | AUDIO | — |