7.9 KiB
BFS Shot Planner / Shot Loop
Turn a long video into shots a video model can actually follow, give every shot its own reference image and prompt, run the rest of the workflow once per shot, and join the results back into one video with the original timing and soundtrack.
Why
A video model only follows a guide reliably inside the clip length it was trained on. An H3 body-swap LoRA trained on 107-frame clips keeps the scene, framing and motion at 4.5 s and loses them at 10 s, where it starts summarising the clip instead of following it frame by frame. Splitting the source into model-sized shots, at camera cuts when there are any, keeps every shot inside that range.
Nodes
| node | what it does |
|---|---|
| BFS Shot Planner | Pick a video, split it on a timeline, set a reference and prompt per shot. Outputs the shots as a ComfyUI list. |
| BFS Shot Unpack | Opens one shot into plain values: guide frames, reference, second reference, prompt, length, first frame, audio, size, index. Use it with any model. |
| BFS Shot Repack | Puts edited pieces back into a shot: Unpack's shot output goes to Repack's shot, the edited piece (e.g. the reference with its background removed) to its input. Unconnected inputs keep their values; timing, cuts and audio are unchanged. |
| BFS Shot H3 Conditioning | Ready-made MiniMax H3 conditioning for one shot, built with the native nodes (Reference to Video + Add Guide). |
| BFS Shot Join | Concatenates the decoded shots in order, trims each to its true length, cross-fades soft joins and returns the soundtrack. |
How the loop works
ComfyUI runs any node that receives a list once per item and pairs several lists by index. The
planner's shots output is a list, so everything downstream of it (conditioning, sampler,
decode) runs once per shot, and shot i always gets guide i, reference i and prompt i.
Single values such as the model and VAE are shared by every run. The join takes the whole list
back (it is an INPUT_IS_LIST node) and puts the video together.
Two run modes:
- Auto loop (one run): every shot in one queue run. Simple; holds all decoded shots in memory.
- Queue loop (one shot per run): each run generates the next pending shot and stores it on disk. The join blocks every node after it until the last shot, then outputs the full video, so a Save Video node only fires once. With Auto-queue the next shot on, the panel re-queues the workflow after each shot. Reset loop starts over. Changing the plan starts a new run.
Guide frames and lengths
Each shot is generated at a length the model accepts (frame grid: H3 17n+5, LTX/Wan 8n+1,
Wan 4n+1, or any). When a shot is shorter than that length, the extra guide frames come from
the video that follows it, so the guide never repeats or stretches. The join cuts each result
back to the shot's own frames, which keeps the timing identical to the source. Those extra
frames are also a real overlap, which the join uses to cross-fade boundaries that are not
camera cuts.
Splitting
- Camera cuts (default): cuts from PySceneDetect (
adaptiveorcontent; installscenedetect), or the built-in detector when the package is missing. Shots shorter than the minimum merge into a neighbour when the merge still fits; shots longer than the maximum split into equal parts. - Fixed length: equal parts no longer than the maximum.
- By hand: drag the white handles on the timeline to move a boundary, double-click the shot bar to split, Merge with next to join. Your edits are what runs.
Max seconds / shot is converted to frames and snapped down to the grid (4.5 s at 24 fps = 107 frames for H3). Max shots and Max total seconds cap a long source.
Preview
Play shot plays only the selected shot, from its first to its last frame, and stops (or loops with loop shot); every shot card has its own ▶. Play all plays the whole plan and jumps over shots that will not run. The panel shows the shot's start, end and current time (and frame), a yellow playhead follows on the timeline, and clicking the timeline seeks. The source video streams from the input folder, so only what you play is loaded.
Filters
Skip shots that should not run, decided per shot from a few sampled frames:
| filter | skips a shot when |
|---|---|
| Needs a person | no person is detected |
| Min person size | the largest person covers less than that fraction of the frame (wide shots) |
| Max people | more people than that (crowds) |
| Needs a face | no face is detected |
| Skip dark / fades | the mean brightness is below the level |
| Skip static | there is almost no motion (title cards, freeze frames) |
| Min frames | the shot is shorter than that |
People and faces use YOLO from models/ultralytics (person_yolov8m-seg.pt, face_yolov8m.pt, the
Impact Pack files) with OpenCV fallbacks. Analyse people & faces shows the numbers on every shot
card. Each shot can override the filters (always run / always skip).
Skipped shots do not run. Connect the planner's timeline output to BFS Shot Join and they are
filled with the original video, keeping the full duration and soundtrack, or removed (with the
matching audio) when Skipped shots in the output is set to remove them.
Cast (people by face)
Find people samples the video every 0.5 s, detects faces and groups them into people by face
identity (InsightFace buffalo_l in models/insightface, on the CPU with a few threads so it stays
light and never touches the GPU; the result is cached per video). Each
person card shows the face, screen share and first/last appearance (click the face to seek).
Give a person a reference (and a second one) by clicking the slot, or pick one from the recent
references; ignore leaves that person out. Every shot card shows who is in it, with the main
person (most screen time) highlighted and linked people outlined in green.
| option | effect |
|---|---|
| Shots use their main person's reference | a shot without its own reference takes its main person's (shown with a dashed green outline) |
| Split where the main person changes | adds a boundary where the largest face switches to another person for at least a second, then re-splits |
| Only run shots with a linked person | shots where no linked, non-ignored person appears are skipped ("no linked person") |
A reference set on the shot itself always wins; the global reference is the last fallback.
References and prompts
Every shot can have its own reference, second reference and prompt; shots without them use the
global ones. Click an empty reference slot to upload an image straight from your computer; a filled
slot has ✕ to remove it and, on a shot, → all to use it for every shot. The last ten
references you picked are shown under the shot editor for one-click reuse (click = reference,
shift+click = second reference). Connected ref_image, ref_image_2 and prompt inputs override
the panel's global values, so an edited first frame or a prompt from another node can drive the
defaults.
The planner also outputs ref_image and ref_image_2: the references the shots actually use,
without repeats, so a plan where every shot shares one reference returns a single image.
MiniMax H3 with an aligned guide (example)
BFS Shot Planner ── shots ──► BFS Shot H3 Conditioning (clip, vae, guide_mode = aligned guide)
└─ positive, latent ─► CFGGuider / sampler ─► VAE Decode ─┐
BFS Shot Join ◄── images ─────────────────────────────────────────────────────────────────┘
└─► Create Video (fps, audio) ─► Save Video
For other models, use BFS Shot Unpack and wire guide_frames, ref_image, prompt and
length into that model's own guide and reference nodes; the list mapping works the same way.