Nodes/Sagado Nodes for ComfyUI/Video Thumbnail Grid
ComfyUI Node

Video Thumbnail Grid

A Contact Sheet of Any Clip, If ffmpeg Is on Your PATH

By 5agado·Created about a year ago·Updated a day ago· 3
Video Thumbnail Grid
    • grid
    ◄video_path►
    ◄rows3►
    ◄cols3►
    ◄max_height1024►
    ◄reversefalse►

    What it's for

    You generated a clip. Or fifty, across four seeds and two checkpoints, and you want to know what actually happened in them without opening a player and scrubbing. This node takes a video file and hands back one image - a grid of evenly spaced frames, like a contact sheet from a film shoot.

    That's the job and it's a good one: quick QC on a render, a storyboard-ish preview for a post, a reference board to feed back into img2img, or the cheapest way to eyeball a batch of video results. Open a folder, look at twenty finished clips as a wall of thumbnails instead of opening twenty files. In a corpus where video generation is the loud half of the ecosystem, a contact sheet is still the least glamorous and most useful QC tool there is.

    How it works

    It shells out to ffmpeg, in three steps. First ffprobe reads the duration from the container. Then it works out rows × cols timestamps as duration / (frames + 1), which deliberately skips both the very start and the very end - so you don't get frame zero, which on a generated clip is often the least representative one (fade-in, first-step smear). Then each frame is extracted with its own ffmpeg -ss <timestamp> -frames:v 1 call into a temp directory.

    Assembly is fixed: cell height is max_height // rows, cell width is derived from the first frame's aspect ratio, every frame is resized to that cell with Lanczos, and they're pasted into the grid. The result comes back as a normal ComfyUI IMAGE tensor - float 0–1, batch of one - so Preview Image and Save Image both just work.

    The inputs that matter

    • video_path - a filesystem path to a video on the machine running ComfyUI. This is not an input socket that accepts the pack's Video Loader output or a video latent; it's a text field pointing at a file. Remote ComfyUI, remote path.
    • rows and cols (1–10 each) - and rows × cols is quite literally how many frames you get. 3×3 is the default and a good place to start.
    • max_height (640–2048, steps of 128) - sets the total grid height, not the cell height. The floor is 640, so despite the name you cannot ask this node for a small thumbnail; 1024 is a comfortable default.

    One output: grid. Wire it into Preview Image while iterating, Save Image when it's worth keeping. If you wanted the individual frames rather than the tiled image, that's a different tool - the same pack also ships Video Loader and Get Num Frames.

    Install

    ComfyUI Manager, search Sagado Nodes, or:

    cd ComfyUI/custom_nodes
    git clone https://github.com/5agado/ComfyUI-Sagado-Nodes
    cd ComfyUI-Sagado-Nodes
    pip install -r requirements.txt
    

    Then install the dependency that requirements.txt will never install for you, because it isn't a Python package:

    sudo apt install ffmpeg     # macOS: brew install ffmpeg   Windows: winget install Gyan.FFmpeg
    ffmpeg -version && ffprobe -version
    

    Restart ComfyUI afterwards - the process inherits its PATH when it launches, so a terminal that can see ffmpeg doesn't mean the running server can. If ComfyUI lives in Docker, ffmpeg goes in the image, not on the host.

    Where people get burned

    FileNotFoundError: [Errno 2] No such file or directory: 'ffprobe' is the failure you will actually hit, and it means ffmpeg isn't on the PATH of the ComfyUI process. There's no bundled binary and no fallback path - the node calls the binaries by bare name. Fix PATH, restart, done.

    Frame count is drive-by cost. Every frame is a separate ffmpeg process that seeks into the file from the start. 5×5 is 25 spawns; 10×10 is 100 of them, plus a temp PNG each. Great on one clip for inspection, genuinely stupid inside a loop over two hundred renders.

    One aspect ratio for every cell. Cell width comes from frame one and is then applied to all of them, so a clip whose aspect changes mid-way - or one with a rotation flag that makes frame one look odd - gives you squished cells. Only the grid's height is bounded, too: an ultrawide source at 3 columns produces a very wide sheet, which is fine but rarely what you pictured.

    A confusing JSON error instead of a clean one. Duration is read from the container header. A file that doesn't report one (some raw or webm streams) makes the JSON parse of ffprobe's output fail with a decode error rather than a friendly message, which reads like a bug in the node and is really a quirk of the file.

    CategorySagado-Nodes

    Inputs (5)

    NameTypeDefaultDescription
    video_pathSTRING—
    rowsINT31–10—
    colsINT31–10—
    max_heightINT1024640–2048—
    reverseBOOLEANfalse—

    Outputs (1)

    NameTypeDescription
    gridIMAGE—