🎞️ Video Crossfade (Rennart)
Dissolve between two clips without leaving the node graph
- video_a
- video_b
- images
- count
So you've generated clip A, generated clip B, and now you want the classic A-then-B edit with a soft dissolve where they meet. The honest default is exporting both to a video editor and doing it there. This node exists for the times you'd rather not - it takes two sequences of frames and stitches them together with a crossfade entirely inside ComfyUI, in tensor space. No encoder, no editor, no leaving the graph.
It's the lone video node in ComfyUI-Rennart, a MIT-licensed grab-bag of small utilities (color wheels, palettes, date strings, a random number node) that Rennart2025 mostly builds for their own workflows. Several nodes in the pack trace back to WAS Node Suite; this one is original, and it's only a few hundred lines of torch math. That's not a knock. Sometimes a utility that does one boring job well beats another 2,000-line "complete video suite."
What it actually does
The key mental shift: in ComfyUI, a "video" is just an image batch - a tensor whose first dimension is the frame count, each slice an IMAGE. This node never touches a video file. Feed it video_a and video_b (both IMAGE batches) and it hands back one longer batch.
Under the hood (create_crossfade in Rennart_Video_Crossfade.py), the output is three chunks concatenated:
- the front of clip A, up to
transition_framesframes before its end, - a blend zone where the last frames of A and the first frames of B are faded into each other frame by frame,
- the remainder of clip B after that zone.
The blend uses (1 - alpha) * A[i] + alpha * B[i], where alpha ramps from 0 to 1 across the transition. Total frame count comes out to len_a + len_b - transition, and the transition length is min(transition_frames, len_a, len_b) - so if your clips are shorter than the requested fade, it just fades across the whole overlap instead of crashing.
The inputs that matter
Only three, and none of them are scary:
- transition_frames (int, default 10) - how many frames the dissolve spans. For 24–30 fps clips, 10–15 reads as a gentle dip; anything under ~6 starts to look like a hard cut with smearing.
- mode - the easing curve on the fade:
linear,ease_in,ease_out,ease_in_out, orsine.linearis fine for short fades;ease_in_outandsineare the ones that look "produced" for longer ones.ease_instarts slow,ease_outends slow. - offset (int, default 0) - skip the first N frames of video B before starting the fade. Handy when clip B opens with noise or a blank lead-in you don't want dissolving over.
It returns images (the stitched IMAGE batch) and count (an INT - the new total frame count, which is genuinely useful for wiring downstream without recomputing). Wire images into whatever encodes: a save-animated-node, VHS_VideoCombine if you run VideoHelperSuite, or a frame-interpolator if you want to smooth the fade further.
Installing it
Standard, nothing exotic. Either search "ComfyUI-Rennart" in the Manager's Install Custom Nodes tab, or:
cd ComfyUI/custom_nodes
git clone https://github.com/Rennart2025/ComfyUI-Rennart
Then restart ComfyUI. There are no model downloads - the node only needs torch, and the pack's requirements.txt is a one-liner (torch\nnumpy) because everything else ComfyUI already ships. If the node doesn't appear under Rennart/Video, check the console for the [Rennart] loader warnings; the pack auto-imports every .py in its folder, so one broken file in the set can silence the others.
Where people get burned
The trap to internalize: you can't drop a video file path into this node. It takes image batches, so you must load your clips as frame sequences first - via a video-loader node (VideoHelperSuite or similar) or by feeding it the frames from an AnimateDiff-style generator. Beginners paste a file path in, get a type error, and assume the node is broken. It isn't; it's just not a video codec tool.
Second gotcha, straight from the source: if the two batches have different resolutions, the node silently resizes clip B down to clip A's size with bilinear interpolation and prints a warning to the console. Your output resolution follows clip A. For clean results, match resolutions upstream instead of letting it squash one side.
And a genuinely sharp edge: if offset is larger than clip B's frame count, create_crossfade bails and returns video_a untouched with count = 0 - no exception, no obvious error on the canvas. You'll just see your render "finish" with only clip A. If your output suddenly looks like clip B vanished, check offset against B's length.
For a dissolve that never leaves your workflow and needs no external encoder, it's hard to fault. Feed it matched-resolution frame batches, pick ease_in_out, and move on.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| video_a | IMAGE | — | |
| video_b | IMAGE | — | |
| transition_frames | INT | 101–500 | Количество кадров в зоне перехода |
| mode | COMBO | linear | Кривая изменения прозрачности |
| offset | INT | 00–1000 | Сдвиг начала перехода в кадрах (пропустить первые N кадров видео B) |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| images | IMAGE | — |
| count | INT | — |