VideoKeyFramesExtractor
The node that decides where you get to paint
- output_dir
- INT
VideoKeyFramesExtractor picks which frames become your keyframes - the ones you're going to img2img, inpaint, or otherwise restyle by hand before EBSynth propagates the style across the rest of the clip. If that sounds like the most important decision in the whole workflow, it is. EBSynth is only as good as the frames you feed it as reference, and this node automates the "which frames?" question so you don't have to eyeball hundreds of frames.
It's a direct port of the keyframe-selection logic from the ebsynth_utility extension this pack is based on, which itself borrows the scene-change math from PySceneDetect.
How it works
The node compares frames to the last accepted keyframe and watches how much the edges change. Specifically: it converts each frame to HSV, detects edges with Canny, and measures the mean pixel difference between the current frame's edge map and the stored keyframe's edge map. When the difference crosses the threshold, the current frame becomes a new key. It's scene-change detection in disguise - a shot cut or a big movement produces a big edge delta, and that's exactly the moment you want a new reference frame.
Two knobs shape the whole thing:
min_gap/max_gap- keyframes can't be closer thanmin_gapframes apart, and can't be farther thanmax_gap.max_gapis the insurance policy: even if nothing changes, EBSynth gets a fresh reference everymax_gapframes so drift can't accumulate.key_th- the sensitivity. Crucially, the effective threshold scales down as the gap grows: the closer you get tomax_gap, the easier it is to accept a new key. So a busy scene gets lots of keys, a static one gets few, and you never starve.
key_add_last_frame (default on) appends the final frame as a key so the clip ends cleanly. is_invert_mask flips mask interpretation for background-focused workflows.
The paths (read this before it bites you)
Like image2mask, this node appends subfolders to your inputs. It reads from <png_dir>/video_frame/ and writes to <output_dir>/img2img_key/. So pass it your project directory for both fields - not the frames folder directly - or it finds nothing. The selected frames are copied (not moved) into img2img_key/, which is where ebsynth_process later expects your painted keyframes to be.
Inputs and outputs
png_dir- project dir (frames are expected inpng_dir/video_frame).output_dir- project dir (keys go tooutput_dir/img2img_key).min_gap(default 10),max_gap(default 300),key_th(default 0.1),key_add_last_frame,is_invert_mask.
It returns the output_dir STRING and an INT with the number of keyframes selected - handy for a quick "how many do I need to paint?" sanity check.
Installing it
Part of comfyui-imgmake - Manager (search comfyui-imgmake) or:
cd ComfyUI/custom_nodes
git clone https://github.com/dafeng012/comfyui-imgmake
No extra system dependencies for this node specifically (ffmpeg is needed later in the pipeline, so you'll want it on PATH anyway per the README).
Troubleshooting
- "No PNG files found" - you passed the frames folder, not the project dir. Remember it's looking for
png_dir/video_frame. - Too many / too few keys - that's
key_thand the gaps. Lowerkey_th= more keys; raisemin_gapto thin them out. There's no universally correct setting, it depends on how much the camera moves. - Quirk worth knowing - the node actually runs the analysis twice in its current code (harmless, just wastes a few seconds and both runs agree). If you see the progress double, that's not a crash.
For your first run, keep the defaults. EBSynth handles a reasonable keyframe count fine, and you can always tighten after you see which frames EBSynth chokes on.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| png_dir | STRING | — | |
| output_dir | STRING | — | |
| min_gap | INT | 101–300 | — |
| max_gap | INT | 30010–300 | — |
| key_th | FLOAT | 0.10–1 | — |
| key_add_last_frame | BOOLEAN | true | — |
| is_invert_mask | BOOLEAN | false | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| output_dir | STRING | — |
| INT | INT | — |