Video Comparer (Swipe)
Side-by-Side Video A/B That Records Its Own Verdict
- video_a
- video_b
- recorded_video
Video generation is a lot of GPU time for a decision you end up making by squinting at two clips. You've rendered an original, then an upscaled or refined pass, and now you need to know which one is actually better. Tabbing between two players doesn't work - they're never in sync, so the flicker you think you spotted is usually just playback drift. That's the gap Video Comparer (Swipe) (IndiVideoComparer, part of the AIEGOBOT/ComfyUI-VideoComparer pack) exists for: it takes two core VIDEO inputs, plays them in lockstep, and lets you drag a vertical divider across the preview to wipe between A and B. Then - the clever part - it records the whole comparison, divider and labels included, and hands you a file you can wire straight into Save Video.
How the swipe works
The node is is_output_node and lives under video/preview, so you'll find it next to the other preview gadgets. When you queue, it saves both inputs to ComfyUI's temp directory as MP4/H.264 (ComfyUI remuxes where it can and transcodes otherwise), then ships the browser a small UI payload. In the browser, two video elements are stacked under a draggable divider. On every animation frame the frontend checks whether B has drifted from A by more than sync_tolerance_ms and, if so, snaps B back to A's position. That constant drift correction is the whole reason this works better than opening two windows.
The inputs you'll actually touch
video_a/video_b- the twoVIDEOinputs.video_ais your base/original,video_bis the improved one revealed as you push the divider right. Hook these to coreLoad Videonodes (or the output of an upscale/refine chain).label_a/label_b- text shown on the preview, defaults "A" and "B". Rename them "original"/"upscaled" and the recording becomes self-documenting.muted- defaults to true. When you unmute, audio comes from A only.initial_split- where the divider starts (0.5 = halfway).sync_tolerance_ms- advanced, 80 by default. Lower it if B visibly skips; raise it if A and B are slightly different lengths and B keeps fighting you.
recording_file is a socketless, auto-managed string - don't touch it. The one output, recorded_video, is a VIDEO you connect to the core Save Video node.
The recording part is the trick
The preview itself is nice, but the recording is why this node earns its place. Hit REC and the frontend composites both videos, the divider, the handle, and the A/B labels onto a canvas, captures it with captureStream + MediaRecorder into a WebM, and uploads it to the node's /indi_video_comparer/recording endpoint. The backend checks it's a real WebM, parks it in ComfyUI/input/video_comparer_recordings, and on the next queue pass recorded_video resolves to that file. So you end up with a timestamped .webm of you doing the A/B wipe - which is exactly the clip to send someone when you say "look at the eyes when the divider crosses."
Two gotchas built into the design. Before any recording exists, recorded_video deliberately blocks execution (an ExecutionBlocker), so your first queue is preview-only and Save Video does nothing - that's intentional, so you can't accidentally save a raw input as if it were a comparison. And the shared timeline is the shorter input's duration, with recording capped at 2 minutes and 512 MiB.
Installing it
Search "video-comparer" in ComfyUI Manager, or:
cd ComfyUI/custom_nodes
git clone https://github.com/AIEGOBOT/ComfyUI-VideoComparer.git
Then restart ComfyUI and hard-refresh the browser. There's no requirements.txt and no models - it's pure browser + backend plumbing. The one real requirement: ComfyUI 0.33.1 or newer and Python 3.10+. This pack is written against ComfyUI's newer extension API, so if you're sitting on an old install, update ComfyUI first or the node simply won't appear.
When it bites you
- Empty preview - connect two real
VIDEOinputs and queue once. It needs both to render anything. - Recording won't start - you need a current Chromium-based browser. MediaRecorder +
captureStreamis spotty in Firefox and Safari. - Node missing after install - restart, refresh, and read the startup log.
- Your VHS path doesn't connect -
VHS_FILENAMESis a different type. Load MP4s with coreLoad Video, which gives you theVIDEOsockets this node wants. - Recordings pile up - completed WebMs stay in
video_comparer_recordingsuntil you delete them; preview temp files prune themselves (keeps the last 12).
For a young pack with zero name recognition, the implementation is suspiciously polished - drift-corrected sync, safe recording paths, size caps, and a model-free smoke-test workflow included. If you review more than one video render a week, it pays for itself in the first A/B session.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| video_a | VIDEO | Base/original video. | |
| video_b | VIDEO | Improved video revealed progressively as the divider moves right. | |
| label_a | STRING | A | Label for the base/original video. |
| label_b | STRING | B | Label for the improved video. |
| autoplay | BOOLEAN | true | — |
| loop | BOOLEAN | true | — |
| muted | BOOLEAN | true | Mute preview audio. When unmuted, audio comes from video A only. |
| initial_split | FLOAT | 0.500.05–0.95 | Initial position of the A/B divider. |
| sync_tolerance_ms | INT | 8010–500 | Resynchronize video B when drift exceeds this value. |
| recording_file | STRING | Managed automatically after browser recording. Connect the recorded_video output to the core Save Video node. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| recorded_video | VIDEO | The latest completed swipe recording. |