Nodes/Comfyui-SceneDetect/PySceneDetect: Scenes → Images (Legacy VHS)
ComfyUI Node

PySceneDetect: Scenes → Images (Legacy VHS)

Cutting a video into shots right inside the graph (no ffmpeg CLI needed)

By hndrr·Created 12 months ago·Updated 3 days ago· 5
PySceneDetect: Scenes → Images (Legacy VHS)
  • image
  • video_info
  • images
  • scenes_json
  • scene_count
◄methodcontent►
◄threshold27.0►
◄min_scene_len_sec0.00►
◄min_scene_len_frames15►
◄luma_onlytrue►
◄representativestart►
◄max_width0►
◄max_height0►
◄limit_scenes0►
◄write_thumbsfalse►
◄thumbs_dir►
◄hash_threshold0.395►
◄hist_threshold0.050►
◄downscale0►
◄detector_settingsdefault►
◄adaptive_threshold3.0►
◄window_width2►
◄min_content_val15.0►
◄delta_hue1.00►
◄delta_sat1.00►
◄delta_lum1.00►
◄delta_edges0.00►
◄kernel_size0►
◄fade_bias0.00►
◄add_final_scenefalse►
◄threshold_methodfloor►

You've got a long video loaded into ComfyUI and you need it split into shots. Maybe you want a clean keyframe to feed an img2vid node, maybe you're building training data and need scene-level cuts instead of arbitrary 2-second windows, maybe you just want a storyboard of the whole thing. Whatever the reason, the answer is the same: stop doing it by hand, and don't leave the graph to do it. That's what ZNGB-PySceneDetect is for.

This is a single-node pack wrapping PySceneDetect, the long-running Python shot-detection library, into a ComfyUI node. Good news up front: no API, no key, no model download - the only "heavy" thing is a couple of pip packages. It's a preprocessing utility, and in ComfyUI's video pipeline it fills a hole that has no button anywhere else in the UI.

How it works

The name is the honest part: you feed it a batch of frames and it detects where the scenes change. The mechanism is worth understanding because it explains both gotchas below. The node takes your IMAGE batch plus metadata from a video loader, writes those frames back to a temporary video file with OpenCV, runs PySceneDetect's detector on it, and then hands you three things per scene: a representative frame, a metadata blob, and an exported mp4 segment.

Under the hood it's using PySceneDetect's classic detectors - ContentDetector, AdaptiveDetector, ThresholdDetector - so if you've ever used the library from the CLI, the tuning concepts carry straight over.

The inputs that matter

  • image and video_info - these are the whole point. image is the frame batch, and video_info is the fourth output from VHS Load Video (or another compatible loader). The node requires a valid fps value in there; without it, execution fails hard.
  • method - content (default), adaptive, or threshold. Content compares frame-to-frame differences with a fixed threshold, which is the good all-rounder for most footage. Adaptive tunes its own threshold per-window, so it's the one to reach for when your footage mixes slow pans with fast action. Threshold is for detecting hard cuts and brightness flashes - more of a niche tool.
  • threshold - default 27, the classic content-detector value. Higher = fewer, longer scenes.
  • min_scene_len_sec / min_scene_len_frames - the minimum shot length, your filter against noise. If you set the seconds field, it wins over the frames field.
  • luma_only - on by default, and worth leaving on for most video: it ignores color changes and only looks at brightness, which avoids false cuts on hue shifts and runs a bit faster.

The rest are optional: representative picks which frame from each scene you get back (start/middle/end), max_width/max_height downscale those frames (0 = keep original), limit_scenes caps the count, and write_thumbs dumps a jpg per scene into thumbs_dir (defaults to ./scene_thumbs).

The outputs

Four of them, and they cover the realistic use cases:

  • images - a batch of representative frames, one per scene. Wire this into a preview or a Save Image node and you instantly get a storyboard of your footage.
  • scenes_json - a JSON string with fps, method, threshold, and per-scene start/end frame, timecode, and duration. Feed it to a JSON-parsing node for metadata-driven logic, or just read it.
  • scene_count - an INT. Handy for a conditional that only proceeds if there are enough scenes.
  • path_string - newline-separated absolute paths of the exported mp4 segments.

Note the segments are written to ComfyUI's temp directory, in a fresh random transnet_segments_xxxx/ folder per run, not your output folder. They're also re-encoded by OpenCV (MJPG/mp4v), so don't treat them as frame-perfect lossless rips - they're for convenience, not archival.

Install

ComfyUI Manager will find it if you search "Comfyui-ZNGB-SceneDetect". Otherwise:

cd ComfyUI/custom_nodes
git clone https://github.com/zhinangubei/Comfyui-ZNGB-SceneDetect
pip install -r requirements.txt

The dependencies are just scenedetect, opencv-python, and numpy - no model files, no ffmpeg to hunt down. Restart ComfyUI and you're done.

Where people get burned

The recurring complaint with PySceneDetect in general (one r/StableDiffusion user hit it exactly) is that the cuts come back too granular - a slow pan or a flicker splits into three "scenes" and destroys the consistency you were trying to preserve. Fix: raise threshold, bump min_scene_len_frames, or switch to adaptive. A few minutes of tuning here is the difference between a useful storyboard and a chaotic one.

The other classic footgun is wiring the wrong thing into image. The node rejects LATENT tensors at runtime with a fairly explicit error - if you see it, you've connected the VAE-decoded output from your video loader instead of the frame/image output. And if you skip video_info entirely and feed it from a plain image loader, you'll get the "no valid FPS" error. This node is built around VHS Load Video; give it the fourth output and it's happy.

CategoryVideo/PySceneDetect

Inputs (28)

NameTypeDefaultDescription
imageIMAGE—
video_infoVHS_VIDEOINFO—
methodCOMBOcontent5 options: content, adaptive, threshold, hash, histogram
thresholdFLOAT27.00–1000—
min_scene_len_secFLOAT0.00—
min_scene_len_framesINT15—
luma_onlyBOOLEANtrue—
representativeoptCOMBOstart3 options: start, middle, end
max_widthoptINT0—
max_heightoptINT0—
limit_scenesoptINT0—
write_thumbsoptBOOLEANfalse—
thumbs_diroptSTRING—
hash_thresholdoptFLOAT0.3950–1Cut threshold for hash detection. Lower values detect more cuts.
hist_thresholdoptFLOAT0.0500–1Cut threshold for histogram detection. Lower values detect more cuts.
downscaleoptINT0Detection only: 0 = automatic, 1 = full resolution, 2 = half width/height. Representative images keep their original resolution.
detector_settingsoptCOMBOdefaultdefault uses PySceneDetect defaults. custom shows and applies the detailed detector settings.
adaptive_thresholdoptFLOAT3.0custom / adaptive: change relative to surrounding frames needed for a cut. Lower values detect more cuts.
window_widthoptINT2custom / adaptive: number of frames before and after each frame used for comparison.
min_content_valoptFLOAT15.0custom / adaptive: minimum absolute change needed for a cut, even if the relative change is large.
delta_hueoptFLOAT1.00custom / content or adaptive: weight of hue changes. Ignored when luma_only is true.
delta_satoptFLOAT1.00custom / content or adaptive: weight of saturation changes. Ignored when luma_only is true.
delta_lumoptFLOAT1.00custom / content or adaptive: weight of brightness changes. Ignored when luma_only is true.
delta_edgesoptFLOAT0.00custom / content or adaptive: weight of edge changes. Ignored when luma_only is true.
kernel_sizeoptINT0custom / content or adaptive: edge expansion size. 0–2 = automatic; larger even values round up to the next odd size.
fade_biasoptFLOAT0.00-1–1custom / threshold: -1 places the cut at fade-out, 0 midway, +1 at fade-in.
add_final_sceneoptBOOLEANfalsecustom / threshold: add a cut at the final fade-out when the video ends without fading back in.
threshold_methodoptCOMBOfloorcustom / threshold: floor detects fades to black; ceiling detects fades to white.

Outputs (3)

NameTypeDescription
imagesIMAGE—
scenes_jsonSTRING—
scene_countINT—