H3RelayRemovalAppend
The join that keeps removal windows on the source clock
- frames
- audio
- previous
- H3_REMOVAL_STATE
- IMAGE
- AUDIO
H3 Relay Person Remover renders a clip as a chain of ~1-second windows, and every one of them has to be welded onto the previous one without a visible seam, a doubled frame, or audio that drifts by a few samples each time. H3RelayRemovalAppend is the weld. It's an internal node - the Person Remover generates one per window when it expands into a native graph - and it's the same shared implementation the character-swap workflow uses.
If you're reading this because you're staring at a graph with nine of these in a chain: that's what it's supposed to look like. Don't delete them.
The job
Each window comes back from the sampler with a batch of frames that is longer or shifted relative to what you actually want to keep. H3 Relay asks for window_frames + 17 on continuations and lets the history logic shorten the sampled target; the assembly then has to:
- refuse a batch that isn't exactly the length the wrapper planned for,
- convert the freshly generated audio into source-clock time (slice by sample offset from
start, at 24 fps), - discard the retained boundary frame it deliberately overlapped with the previous window,
- concatenate the surviving frames and waveform onto the running state,
- and write the whole thing to an integrity-checked disk checkpoint.
It checks its work, too. If the waveform length after all that doesn't equal round(end * rate / 24), it raises Generated audio no longer matches the source clock rather than emitting a clip that slowly desyncs. If the assembled frame count doesn't land on the source position, same story. Those checks are why a failed run costs you an error message instead of a 40-minute render you only notice is broken at the end.
Inputs
Required: frames and audio (this window's decode), start (source-frame index for the window), source_count (total frames in the source clip) and, optionally, window_frames (default 22).
Optional, and mostly for the cache and preview systems:
previous- the runningH3_REMOVAL_STATE. Empty means "this is window one".cache_scope,config_key,window_index,window_seed,reuse_record- the checkpoint identity fields.config_keyhashes real pixels, the prompt, steps, window size and the model/encoder/VAE graph; when a record is saved it stores the chain of predecessor records too, so a checkpoint can't be replayed into a different sequence.reuse_recordmeans "this window is a restore, validate it don't rewrite it".preview_run- which preview card this window feeds.
Outputs: the updated H3_REMOVAL_STATE, the assembled IMAGE, and the assembled AUDIO. In the removal graph those last two are the node's answer.
Two behaviours worth knowing
The one-frame hold. The currently installed H3 VAE can decode one frame short at an exact window boundary. The removal path requires the exact count and errors if it's off; the character-swap variant wraps this node, logs decoded one frame short; holding its final frame, and duplicates the last frame so the timing survives. If the missing frame was tail padding it gets discarded anyway and all your real source positions stay intact.
Window size is validated, not trusted. The size has to be a legal H3 grid value (22, 39, 56 … 362). If you hand-edited window_frames in the graph and it disagrees with what the wrapper planned, you get Expected N decoded frames, received M - the honest version of "you broke it".
Install
Part of the pack:
cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/akatz-ai/h3-relay.git
Manager → H3 Relay also works. ComfyUI 0.32.0+, FFmpeg on PATH, H3 weights from the pack manifest, no pip dependencies.
Errors and what they mean
Invalid source-aligned overlap- the previous state ends after this window starts, or the overlap is a whole window or more. Graph shape problem.Generated audio sample rate changed between windows- something upstream is inconsistent; usually a source clip that isn't actually 24 fps.Saved window history does not match this render- the saved checkpoint's predecessor chain orconfig_keydisagrees with the currently running graph. Something changed: pixels, prompt, steps, a model file, or the window size.Assembly no longer matches source frame positions- the assembled length doesn't equalsource_countframes' worth. Usually a source length that was reported differently between the preparation node and the graph.
Inputs (12)
| Name | Type | Default | Description |
|---|---|---|---|
| frames | IMAGE | — | |
| audio | AUDIO | — | |
| start | INT | 0 | — |
| source_count | INT | 22 | — |
| previousopt | H3_REMOVAL_STATE | — | |
| window_framesopt | INT | 22 | — |
| preview_runopt | STRING | — | |
| window_indexopt | INT | 0 | — |
| cache_scopeopt | STRING | — | |
| config_keyopt | STRING | — | |
| window_seedopt | STRING | 0 | — |
| reuse_recordopt | STRING | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| H3_REMOVAL_STATE | H3_REMOVAL_STATE | — |
| IMAGE | IMAGE | — |
| AUDIO | AUDIO | — |