π¬ Melite Concat Videos (file tail)
Stitch saved windows back into one clip β and refuse when it can't
- images
- audio
- fps
Assembling a long video inside ComfyUI usually means holding every frame of every window in the graph at once, which is how you turn a 24 GB card into a 43-hour render that ends in an out-of-memory error. The alternative is to let each window save its own file - its tensors die at the save - and then decode the files back in at the end. MeliteConcatVideos is that second half. It reads your saved window files and hands you back one IMAGE batch, one AUDIO track and the film's fps, as a normal graph output.
Where it fits
If you're generating a long sequence in windows - whatever the model is, the same problem shows up across the video side of the ecosystem - each window is a separate stretch of the graph that ends in a save. This node is the tail: comma-separated file prefixes in film order in, one assembled film out. The film becomes something the graph produces, not something a wrapper script does with ffmpeg afterwards.
How it works
videos is a list of output-directory prefixes, not paths - for example h3-film/w1,h3-film/w2,h3-film/w3. For each prefix it globs the output directory for <prefix>_*.mp4, filters for files ending in a five-digit counter (_00007_.mp4), and takes the highest counter as this run's write. That five-digit counter is SaveVideo's own naming convention and it's how the node knows it's looking at a fresh file rather than one from your last run.
Then it decodes. PyAV opens each file, frames come out as float tensors scaled to 0β1, audio comes out as a waveform plus a sample rate. Frames concatenate along the batch axis; audio concatenates along time. If a window's file has no audio track at all, the node synthesises silent stereo for that window's duration rather than producing something a downstream muxer will choke on. Mono gets duplicated to match the widest channel count in the film, so nothing gets dropped.
And then the refusals, which are the best thing about this node:
- a prefix with no matching file β hard error naming the prefix and telling you to wire the ordering token
- windows that disagree on fps (more than 0.01 apart) β hard error, because silently restamping part of your film is worse than failing
- windows that disagree on audio sample rate β hard error
- empty
videosor emptyafterβ hard error
Every one of those is a case where "helpfully continue" produces a plausible-looking wrong film.
Inputs and outputs
videos - STRING, comma-separated output-dir prefixes in film order. It's a text field, so watch your separators.
after - STRING, required. Wire MeliteUnload's output here. It's demanded rather than optional because the files are written by nodes upstream of this prompt, so there's no way to validate them at load time - the ordering token is the only proof they exist yet.
images - IMAGE. One batch containing every frame of the film, in order. Feed a preview, a save, or an upscale.
audio - AUDIO, the concatenated track, in the {waveform, sample_rate} shape ComfyUI's audio nodes expect.
fps - FLOAT, taken from the first window and enforced on the rest.
Installing it
cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/JayDataEngineer/melite-utility-nodes.git
Restart, or install Melite Utility Nodes via ComfyUI Manager. This one genuinely needs the pack's av dependency - PyAV is what does the decoding, and without it the node fails with a message saying exactly that. matplotlib comes along too and is used by other nodes in the pack. The node also needs numpy and torch, which your ComfyUI environment already has.
Where it bites
- The naming convention is not negotiable. The glob only accepts
<prefix>_<five digits>_.mp4. A saver that writeswindow1.mp4or an.mkv, or a prefix you typed with the counter already in it, won't match, and you'll get the loud no-match error. Match your prefix to your saver'sfilename_prefix, not to the filename. - Set the prefix correctly and the rest is boring. If a window fails to save for an unrelated reason, this node tells you which prefix is missing, which is a much better debugging experience than a half-length film.
- The output is a full frame batch. You've moved the memory problem to the end of the run rather than abolishing it - decoding a two-minute film at 24 fps is thousands of frames in RAM. It's still the right trade, because nothing upstream had to hold all of it, but don't wire this into three concurrent previews.
afteris the point. If you feed a string literal into it just to make the node run, you've disabled the only guarantee it offers, and you're back to a coin flip on whether your loader beat your writer.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| videos | STRING | Comma list of output-dir SaveVideo prefixes, in film order | |
| after | STRING | The MeliteUnload boundary token β proof every save completed | |
| appendopt | STRING | Optional EXACT saved-file path appended as the last member (e.g. ChainAssemble's wired video_path) β the in-chain assembly compose face |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| images | IMAGE | β |
| audio | AUDIO | β |
| fps | FLOAT | β |