BFS H3 Native Shot Loop Start
Slice a 24 fps clip into H3-sized shots — with your cuts, not a detector's
- images
- loop_state
- shot_images
- loop_state
- source_fps
- shot_index
- shot_count
What it does
BFS H3 Native Shot Loop Start takes one loaded video and hands your graph a single shot at a time. Wire it right after the video loader, run the queue once, and the graph will end up sampling every chunk of the source clip in sequence. Its sibling Loop End is what makes that happen; this node's job is to decide where the chunks begin and end.
Small thing with a big consequence: it does no automatic scene detection. camera_cuts is a text field where you type frame indices. If you were expecting PySceneDetect to find the cuts for you, that lives in the pack's separate Shot Planner, not here. The native loop is deliberately manual.
The 24 fps hard gate
source_fps must be 24. Not "about 24" - the node compares with a 1e-6 tolerance and raises if it isn't:
Native H3 shot loop requires a 24 fps loaded video. Set VHS Load Video force_rate=24 before this node
So the first thing you do in the workflow is set VideoHelperSuite's force_rate to 24 and select_every_nth to 1. Two reasons: H3 works at 24 fps and the join writes 24 fps, so any resampling downstream would desync your audio. And camera_cuts is in source frame indices relative to the loaded clip - if the loader is retiming or skipping frames, your typed indices point at the wrong moments. Enter cuts after any trim or skip settings on the loader, not before.
Out comes source_fps as a float, plus shot_index and shot_count, which exist so you can display "shot 3 of 7" and know whether you're on the last pass. shot_images is the frame batch for the current shot; loop_state is the wire that has to reach Continuity, the cast-binding nodes and Loop End.
Picking your segment size
Two modes, and only one field is read:
segment_unit = frameswithchunk_frames(default 107, min 5, max 3600). 107 is the H3-friendly default: 6×17+5, one latent grid's worth.segment_unit = secondswithchunk_seconds(default 4.4583 s). Rounded to the nearest 24 fps source frame.
Old workflows keep frame mode, which is why the enum defaults to frames. Switch to seconds if you're matching an edit - "five seconds of this" is easier to reason about than "one hundred and twenty source frames," and the padding for H3's grid is handled downstream and trimmed before the join anyway.
camera_cuts takes a comma-separated list. Any cut always starts a new segment, and chunking continues from there - so 0, 96, 210 gives you three shots regardless of chunk_frames. Leave it empty and you get one continuous take chopped into equal chunks. Which is fine for a locked-off shot and terrible for anything with an edit in it: an H3 segment that straddles a cut has to invent a transition, and it will.
enabled off means one segment containing the whole clip - no repeated sampling, no continuity handoff. Useful escape hatch when you want to sanity-check a single pass with the same graph.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/alisson-anjos/ComfyUI-BFSNodes.git
cd ComfyUI-BFSNodes
pip install -r requirements.txt
Restart ComfyUI and hard-refresh the browser. Beyond that you need a ComfyUI new enough for dynamic graph execution (Loop End depends on it), VideoHelperSuite to load the clip, and the usual H3 loaders. No models are downloaded by this node.
Caveat worth stating plainly: the pack has almost no Reddit footprint - two threads in the whole modidex corpus mention it - so there's no crowd of people who've already worked around whatever you hit. The repo docs are thorough and honest (H3_NATIVE_SHOT_LOOP.md spells out the wiring and the limits), and the author states the validation is CPU graph checks, not a validated GPU render. You're an early user.
Common issues
It raises on frame rate. You skipped force_rate=24. This is 90% of first-run errors.
The loop runs forever / repeats the first shot. The loop_state link is broken somewhere between Start and End. Loop End scans upstream for exactly one Start node and clones the body between them; a second Start in the graph, or a fancier graph with the body split across a subgraph, will confuse it.
Cuts land a beat off. Confirm the loader produced exactly the frames you think it did - check the loader's frame count against the clip. Trim and skip downstream of the loader, not upstream, or shift your indices by the trimmed amount.
The last shot is very short. A tail under five frames gets its last source frame repeated internally so H3 meets its minimum; Loop End drops the duplicates afterward. So a two-frame tail still samples, it just doesn't add length.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | — | |
| source_fps | FLOAT | 24.00 | — |
| chunk_frames | INT | 1075–3600 | — |
| camera_cuts | STRING | Comma-separated SOURCE frame indices, relative to the loaded clip. Empty = one continuous take split by chunk_frames. No automatic cut detection. | |
| loop_stateopt | BFS_H3_NATIVE_LOOP | — | |
| enabledopt | BOOLEAN | true | Off = one segment containing the entire loaded clip. No repeated sampling or continuity handoff. |
| segment_unitopt | COMBO | frames | Choose which segment size field to use. Existing workflows keep frames. |
| chunk_secondsopt | FLOAT | 4.460.2083333333333333–150 | Used only in seconds mode. Rounded to the nearest source frame at 24 fps; H3 padding is handled downstream. |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| shot_images | IMAGE | — |
| loop_state | BFS_H3_NATIVE_LOOP | — |
| source_fps | FLOAT | — |
| shot_index | INT | — |
| shot_count | INT | — |