Wan Keyframe Builder
Lock a pose, let Wan fill the gap
- image1
- image2
- image3
- image4
- image5
- image6
- image7
- image8
- images
- masks
- keyframes
Wan image-to-video is great at the between - the motion, the physics, the camera drift. What it's bad at is hitting specific poses you've already decided on. If you've hand-built a start-frame + mask timeline for WanVideoImageToVideoEncode before, you know it's a stack of PadImage, ImageConcat, and mask-strength nodes that always ends in an off-by-one and a re-render. This node is that whole stack in one box: feed it up to eight keyframe images, and it returns a full-length image timeline plus a matching strength-mask timeline, ready to drop straight into Kijai's WanVideoWrapper.
The idea is simple and it's the standard trick for Wan I2V: every frame you don't care about gets filled with mid-gray (0.5) so the model generates freely there, and each keyframe sits at a fixed position with a mask strength that says how locked that frame is. The output timeline is [T, H, W, C], the masks are [T, H, W], and they plug into WanVideoImageToVideoEncode → WanVideoSampler exactly like a single start image would. It doesn't call any API and needs no model files of its own - it's pure tensor assembly on top of ComfyUI's built-in IMAGE/MASK types.
The inputs that actually matter
image1–image8: your keyframes, oldest to newest.image1is required; the rest are optional. Any connected image becomes a keyframe.num_frames: total timeline length. Default 81, and the tooltip is right - Wan expects 4n+1, so think 81, 121, 161. Wander off that and you can get odd end-frame behavior.spacing_mode:evenauto-distributes your keyframes across the whole timeline (the node computesround(i * (T-1) / (N-1))), which is what you want 90% of the time.manualuses theframe_1–frame_8positions instead - but those only matter in manual mode, so leave them alone unless you're timing a beat precisely.first_strength/last_strength/middle_strength: the mask weights. First and last default to 1.0 (fully locked), middle to 0.8. The 0.8 default is a deliberate choice: it gives the model room to reinterpret the pose and animate smoothly between keyframes instead of snapping. Drop it toward 0.5 for looser guidance, keep it high if the model keeps wandering off your composition.
Outputs
Three, and they're named honestly. images is the full timeline with keyframes placed and gray fill everywhere else. masks is the strength map - 1.0 means the frame is locked, 0.5 gray means free generation, and the middle-keyframe positions carry your middle strength. keyframes is just the keyframe images stacked back into one batch [N, H, W, C], which you can wire anywhere you'd want the raw inputs (multi-reference encoders, preview, whatever). For the normal single-pass workflow you want images and masks going into WanVideoImageToVideoEncode.
Installing it
Via ComfyUI Manager, search "ComfyUI-WanKeyframeBuilder" and install. Or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/ckinpdx/ComfyUI-WanKeyframeBuilder
# restart ComfyUI
That's the whole install - there's no requirements.txt and nothing to download. This one lives in the category WanKeyframeBuilder, and there are only two nodes in the pack, so you can't miss it.
Where people get burned
The resolution check is the big one: all connected keyframes must match dimensions, and the node deliberately raises an error if they don't - the author's call, and a good one, because mismatched sizes mean your workflow is broken, not that the node is. Also remember the gray fill is intentional. If your output looks like it has "holes" where nothing's happening, that's the free-generation space working as designed. And if you're used to Hunyuan-style last-frame locking, recalibrate: with a single keyframe, only first_strength applies and it sits at frame 0 - a locked start, free everything after. The author (who also maintains a public ComfyUI workflows repo) built this to match the way Wan I2V actually conditions, and it shows.
Inputs (21)
| Name | Type | Default | Description |
|---|---|---|---|
| image1 | IMAGE | — | |
| num_frames | INT | 811–4096 | Total frames in the output timeline. Should be 4n+1 (e.g., 81, 121, 161) for Wan models. |
| spacing_mode | COMBO | Even: auto-distribute keyframes. Manual: use frame position inputs. | |
| first_strength | FLOAT | 1.000–1 | Mask strength for the FIRST keyframe. 1.0 = fully locked. |
| last_strength | FLOAT | 1.000–1 | Mask strength for the LAST keyframe. 1.0 = fully locked. |
| middle_strength | FLOAT | 0.800–1 | Mask strength for middle keyframes. 0.8 gives the model some freedom to interpret the pose. |
| frame_1 | INT | 00–4095 | Position for keyframe 1 (used in manual mode) |
| frame_2 | INT | 200–4095 | Position for keyframe 2 (used in manual mode) |
| frame_3 | INT | 400–4095 | Position for keyframe 3 (used in manual mode) |
| frame_4 | INT | 600–4095 | Position for keyframe 4 (used in manual mode) |
| frame_5 | INT | 800–4095 | Position for keyframe 5 (used in manual mode) |
| frame_6 | INT | 1000–4095 | Position for keyframe 6 (used in manual mode) |
| frame_7 | INT | 1200–4095 | Position for keyframe 7 (used in manual mode) |
| frame_8 | INT | 1400–4095 | Position for keyframe 8 (used in manual mode) |
| image2opt | IMAGE | — | |
| image3opt | IMAGE | — | |
| image4opt | IMAGE | — | |
| image5opt | IMAGE | — | |
| image6opt | IMAGE | — | |
| image7opt | IMAGE | — | |
| image8opt | IMAGE | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| images | IMAGE | — |
| masks | MASK | — |
| keyframes | IMAGE | — |