Zura · Choose Clip
Cut your source clip to 24 fps before anything else touches it
- video
- audio
- selected_clip
- clip_details
- clip_summary
Every video-editing pipeline in ComfyUI eventually hits the same wall: the models want a specific frame rate, a specific raster, and a specific length, and your phone footage is none of those. GenjSelectClip (display name "Zura · Choose Clip") is the blunt instrument that fixes all three at the front door, before a mask is painted or a single latent exists.
It's the first real node in Zura Studio's prepare stage. If you're building a graph by hand, it's the sane way to get from "I have an mp4" to "I have a clip the sampler will accept".
What it actually does
It reads your file with PyAV, works out the real duration from the container (falling back through stream duration and stream time base), then shells out to ffmpeg once to write a cached, normalised copy:
- 24 fps, always. Everything downstream in this pack - H3, LTX, the Wan route - assumes constant 24 fps.
- Lanczos scaled to a long edge you pick (default 768, range 256–1920), rounded to even dimensions, not a 64-pixel grid. That detail is deliberate: rounding to a diffusion grid turns 16:9 into roughly 2:1 and distorts the camera geometry before you've even made your opening look, which matters because the mask and review videos get generated from this clip.
- H.264 crf 16 at the trimmed length, plus a soundtrack: the clip's own audio if it has any, a silence track if it doesn't, or your explicit reference track if you wired one.
The output file lands in output/genj/clips/<hash>.mp4, keyed by a hash of the clip details, so re-running the same selection costs you nothing.
Feeding clip_details forward is the point of the node. That dict carries the source path, a sha256 of the source, the start time, duration, frame count, fps, final width/height, whether the selection is the whole source, and whether audio exists. Later nodes use it to prove nothing moved under them - GenjRestoreSoundtrack refuses to re-attach a soundtrack if the original file's hash has changed, and GenjLoadReviewedShot rebuilds full-resolution footage using the recorded start and duration.
The inputs that matter
Set start_seconds and duration_seconds (0.1–30 s, step 0.1) to frame your take, and long_edge to your working size - 768 is a good review/working number, 1280 if you're rendering something you care about.
Wire audio only if you want to replace the source soundtrack with a separate track, and then set audio_start_seconds to where that track should start. use_original_length overrides the start and duration and takes the whole file from the beginning, which is the "just give me everything at 24 fps" switch.
Outputs: selected_clip (a normal VIDEO), clip_details (GENJ_CLIP), and clip_summary - a one-line string like 4.000s from 0.000s | 96 frames | 768 x 432 | 24 fps. Wire clip_summary into a text display if you want to see what you actually got without opening a calculator.
Installing it
It ships in Zura Nodes, so ComfyUI Manager → search Zura Nodes → install → restart. By hand:
cd ComfyUI/custom_nodes
git clone https://github.com/ZURAVFX/ComfyUI_zura_nodes
# then, into the same Python that runs ComfyUI:
python -m pip install -r ComfyUI/custom_nodes/ComfyUI_zura_nodes/requirements.txt
That requirements file pulls av, requests, yt-dlp[default], ultralytics, face-alignment==1.4.1, soundfile, aiohttp and Pillow - heavier than it looks, because the whole pack does its own video IO. You also need ffmpeg and ffprobe on your PATH: this node writes its clip by calling ffmpeg directly, so without it you get Install FFmpeg and add it to PATH, then restart ComfyUI. The pack's own INSTALL_STUDIO.md spells the model downloads out per engine; nothing in this node needs a model.
Where people get burned
- Start beyond the end.
The start time is beyond the end of this video.- check the file first; a 4-second clip and astart_secondsof 4.0 is a zero-length selection, not a warning. - Expecting exact frame counts from floating durations. The node rounds to the nearest frame at 24 fps, which is why the summary string exists. If you need a hard 120 frames, pick
duration_seconds: 5.0and readclip_details["frames"]. - Feeding it a memory-buffer video. It resolves the clip through the file path, so a
VIDEOthat only exists in RAM gets written tooutput/genj/scratchfirst. That works, it's just a copy you didn't plan for.
Pick your clip here, then never think about frame rate again.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| video | VIDEO | — | |
| start_seconds | FLOAT | 0.00–86400 | — |
| duration_seconds | FLOAT | 4.00.1–30 | — |
| long_edge | INT | 768256–1920 | — |
| audioopt | AUDIO | — | |
| audio_start_secondsopt | FLOAT | 0.00–86400 | — |
| use_original_lengthopt | BOOLEAN | false | Use the whole source video from the beginning. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| selected_clip | VIDEO | — |
| clip_details | GENJ_CLIP | — |
| clip_summary | STRING | — |