Nodes/ComfyUI-BFSNodes/BFS H3 Duet Conditioning (prompt writer)
ComfyUI Node

BFS H3 Duet Conditioning (prompt writer)

The duet panel, without the sampler

By alisson-anjos·Created 7 months ago·Updated about 18 hours ago· 129
BFS H3 Duet Conditioning (prompt writer)
  • clip
  • vae
  • panel
  • vlm
  • model
  • audio_vae
  • guide
  • keep_mask
  • keep_video
  • ref_images
  • setting
  • positive
  • latent
  • model
  • panel_info
  • prompt
  • canvas_preview
◄taskcharacter swap►
◄instruction►
◄prompt►
◄width448►
◄height800►
◄length0►
◄positionleft►
◄size1.00►
◄panel_noise0.00►
◄rope_modecanvas►
◄fitcontain►
◄gap0►
◄holdall frames►
◄rope_gap0►
◄ref_image_sizematch►
◄vlm_max_tokens1024►
◄source_bluroff►
◄source_blur_segmenterSAM 3.1►
◄source_blur_effectblur►
◄panel_staticoff►

What this one exists for

BFS H3 Duet is the friendly version: it builds the conditioning, pins the panel, samples and decodes, all behind one node. That's great until you want something the author didn't anticipate - a different guider, a LoRA you load yourself, an inpainting pass, your own noise. So there's this: the same pinned-panel conditioning, handed to you as conditioning to wire into whatever sampler you like.

Use it when you care about the graph. Use the all-in-one when you just want the result.

What it hands you

The node pins a panel (your source clip, or a still) beside the video inside the same latent, then outputs positive, latent, model, panel_info, prompt and canvas_preview.

That model output is the part that's easy to miss. In rope_mode = shifted the RoPE shift is applied to the model, so you must connect the model through this node and use the output - feed the original model to your sampler and the conditioning and the model will disagree about where things are. In the default canvas mode the model passes through untouched. The chain people run:

BFS H3 Duet Conditioning ─ positive ─► BasicGuider / SamplerCustomAdvanced
                          ─ latent  ─► (same sampler)
                          ─ panel_info ─► BFS H3 Side Panel Crop (feed it the sampled latent)
                          ─ model ─► sampler (shifted mode)

BFS H3 Side Panel Crop takes panel_info plus either the sampled latent or decoded images, so crop the latent before VAE Decode - same picture, less work.

The prompt, and the VLM

You get three tiers, in order of who wins: your own text in prompt; otherwise a connected VLM; otherwise a draft.

vlm is a normal CLIPLoader running a Qwen3-VL text encoder (the same encoder family H3 uses). Connect it and the node asks it to write a full duet prompt for task + instruction while looking at the clip and the references - genuinely useful, because the draft path can't see anything and therefore produces generic text. The prompt it writes is a six-section REF2VA prompt, and the result comes out on the prompt output so you can copy the good ones into your own prompt box and stop paying for the VLM.

task picks the shape of the request (character swap, style, setting, appearance, lighting/weather), and instruction is the one useful sentence about what changes - "a 1990s anime cel style", "an elderly woman with grey hair". For a straight character swap you can leave instruction empty, since the person comes from the references.

The prompt-writing rules are the same as the all-in-one node and they're the difference between a good result and a mushy one: the panel has no tag, it is invisible to the text encoder, so refer to it by position ("the LEFT half is the kept footage") or drop {layout} in and let the node fill it for the current side and size. Never describe the panel's performer, wardrobe or room - not even to say what it isn't. Anything you leave unspecified is copied from the panel, which is the whole point.

Inputs worth knowing about

  • clip, vae, panel are required; panel is the clip you pin (one image per frame for a clip, or a single still for a static panel).
  • width / height / length - output geometry; length 0 follows the panel clip, and frames snap to 17k+5.
  • panel_noise - 0 copies the panel exactly. 0.1–0.2 gives the model room for big changes.
  • position, size, fit, gap, hold - where the strip goes, how big, how the clip is fitted into it (contain keeps the whole frame with grey around it; cover crops), the grey separator width, and whether the panel is held for the whole clip or just its first latent frame.
  • rope_mode / rope_gap - canvas or shifted. In shifted mode keep rope_gap tiny; a large gap against the video's width makes H3 draw its own split-screen.
  • keep_mask + keep_video - the inpainting door. With a mask, the video area starts from keep_video (or the panel clip) and only the masked region is regenerated, so background and lighting stay pixel-exact. This is how you swap a person without the room drifting.
  • Optional guide is an aligned latent guide in the video area, and ref_images grows as you connect reference pictures - they become the <Picture n> slots your prompt refers to.## Install

Manager → search ComfyUI-BFSNodes, or:

cd ComfyUI/custom_nodes
git clone https://github.com/alisson-anjos/ComfyUI-BFSNodes
cd ComfyUI-BFSNodes && pip install -r requirements.txt

Then restart. You need an H3 build of ComfyUI and the H3 model + VAE; the pack's other dependencies (insightface==0.7.3, onnxruntime, scenedetect, librosa, opencv-python) come along whether or not you use them.

If something's off

If BFS H3 Duet Conditioning isn't in your node list at all while BFS H3 Side Panel is, your ComfyUI predates the comfy_api.latest node interface - the pack registers these two through it and skips them quietly when the import fails. If you're in shifted mode and the frame looks like a genuine split screen with a seam, you almost certainly didn't route the model output into your sampler, or rope_gap is too large. And if the VLM writes something bland, that's usually the encoder: the pack's own docs warn that some smaller or modified Qwen3-VL builds return empty answers to certain wordings.

CategoryBFS/MiniMax H3

Inputs (31)

NameTypeDefaultDescription
clipCLIPThe MiniMax H3 text encoder (Qwen3-VL 32B).
vaeVAE—
panelIMAGEThe clip pinned beside the video (copied in sync), or a picture.
taskCOMBOcharacter swapWhat to do. With a VLM connected it writes the prompt for this task.
instructionSTRINGWhat changes, in seen words: 'a 1990s anime cel style', 'a sunny beach at sunset', 'an elderly woman with short grey hair', or for custom anything you want done. Empty is fine for character swap (the person comes from the pictures).
promptSTRINGYour own prompt (wins over the VLM and the draft). How to write a duet prompt (TSC, newer strategy): H3 has no idea what is pinned and what is noised, it reads one description of one video. Describe the finished split screen as plain footage: never write 'kept footage', 'kept exactly', 'generated', 'preserved', 'in sync with', 'seamlessly'. The pinned clip gets no tag and no retention line: name its region by where it is plus what is seen there, in one clause ('the top quarter shows a man dancing in a small bedroom'; {panel_region} / {video_region} give the region names for the current layout, {layout} the whole split-screen sentence). Write the other region as its own scene in a real place with concrete details. Spell out the motion beat by beat, with times for the big moments (cuts, strikes, jumps, a line), on the video's clock. The soundscape is the sounds of the new scene. Cuts: '[Shot 2] At MM:SS.mmm, both halves cut together to ...'.
widthINT44832–4096—
heightINT80032–4096—
lengthINT00–3600Frames (17k+5). 0 = the panel clip's length.
positionCOMBOleft4 options: top, left, right, bottom
sizeFLOAT1.000.1–1.5—
panel_noiseFLOAT0.000–10 pins the panel exactly; 0.1-0.2 for big changes.
rope_modeCOMBOcanvascanvas (one wide grid) or shifted (connect the model and use the model output).
fitCOMBOcontain3 options: contain, cover, stretch
gapINT00–8—
holdCOMBOall frames2 options: all frames, first latent frame
rope_gapFLOAT00–256—
ref_image_sizeCOMBOmatch2 options: match, max
vlm_max_tokensINT1024128–4096—
vlmoptCLIPOptional VLM (CLIPLoader with a Qwen3-VL text encoder). Empty prompt: it writes one from the task + instruction. Prompt with {...} fields: it fills them from the clip and the pictures. Prompt without fields: used as written.
modeloptMODELNeeded for rope_mode = shifted.
audio_vaeoptVAE—
guideoptIMAGEOptional aligned latent guide in the video area.
keep_maskoptMASKInpainting inside the duet: 1 = regenerate (e.g. the person, dilated), 0 = keep. The video area starts from keep_video (or the panel clip) and only the masked region is generated, so the background and its light stay pixel-exact.
keep_videooptIMAGEThe video kept outside the mask (default: the panel clip).
ref_imagesoptCOMFY_AUTOGROW_V3—
source_bluroptCOMBOoffBlur the source person in what the model sees (the panel / the guide): '<region> (<what stays sharp>)'. keep mouth: lips, teeth and tongue stay sharp; keep lips + tongue: the teeth are blurred too; mouth too: nothing stays sharp. masked area: the shot's own mask (elsewhere: the whole person). It reads the pose and motion but has no face to copy. The output is never blurred. Works with or instead of panel_static.
source_blur_segmenteroptCOMBOSAM 3.1What finds the face / head / hands / person and the mouth for source_blur. SAM 3.1: text prompts. Sapiens2: body-part classes in one pass, finer lips and teeth (downloads facebook/sapiens2-seg-0.4b, 1.6 GB, to models/sapiens2 the first time). 'keep lips + tongue' always uses Sapiens2 for the lips.
source_blur_effectoptCOMBOblurblur: no detail left. invert colours: keeps the shapes and the expression but not the skin tone or colours.
panel_staticoptCOMBOoffTSC's fix for swaps in the same world: cover the face (or the whole person) of the pinned clip in TV static, every frame (SAM 3), so the generated half cannot copy it and takes the identity from the reference. The output is the generated half only, so the static never shows.
settingoptIMAGEThe place picture (BFS Setting Picture: a frame of the clip with the person in static). It becomes the last <Picture n> and the prompt says it shows the setting (write {setting} to place its tag).

Outputs (6)

NameTypeDescription
positiveCONDITIONING—
latentLATENT—
modelMODEL—
panel_infoBFS_H3_PANEL—
promptSTRING—
canvas_previewIMAGE—