Nodes/ComfyUI-CustomNodePacks/Face/Pose Delta Editor (C2C)
ComfyUI Node

Face/Pose Delta Editor (C2C)

Nudge a face's gaze or pose across a Wan Animate shot without destroying the landmarks

By Code2Collapse·Created 6 months ago·Updated a day ago· 52
Face/Pose Delta Editor (C2C)
  • landmarks
  • landmarks_modified
  • info_json
keyframe_edits_json{ "keyframes": [ {"frame": 0, "deltas": {"61": [-0.02, 0.01]}}, {"frame": 30, "deltas": {"61": [-0.08, 0.04]}} ], "ease": "smooth_step", "extrapolate": "hold" }
anchor_modeintereye
left_eye_idx33
right_eye_idx263
external_anchors_json

Face/Pose Delta Editor (C2C) is the node for when you've got a landmark stream - typically MediaPipe FaceMesh landmarks from WanAnimatePreprocessV2's gaze/pose detector - and you want to edit the pose, not just read it. Its whole trick is that edits are expressed in anchor-relative space: instead of "move landmark 61 down 12 pixels" (which breaks as soon as the head turns), you say "shift the right eyebrow 0.05 units toward the nose," where one unit is the distance between the eyes. The edit then follows the face as it moves, rotates, and turns.

That distinction is the whole game. Wan Animate drives its animation off these landmark streams; raw pixel deltas applied to landmarks go out of whack the instant the head turns or the camera moves. A delta defined relative to the inter-ocular distance survives all of that. It's part of ComfyUI-CustomNodePacks, the ~72-node pack from Code2Collapse (Likhith-24, active on r/comfyui), and it sits in the Pose category because it plugs directly into the Wan Animate preprocessing chain.

How it works

  1. Compute a per-frame anchor. In intereye mode (default), the anchor is the midpoint of the two outer eye corners with scale = inter-ocular distance - robust under head turns. centroid mode falls back to the mean of all landmarks, useful for non-face landmark sets.
  2. Convert all landmarks to anchor-relative coordinates (rel = (px - c) / s).
  3. Apply your keyframed deltas in that relative space, easing between keyframes (smooth_step by default) and extrapolating beyond them (hold).
  4. Convert back to pixels per frame, so the modified stream stays in the same shape and ordering as the input.

The key input is keyframe_edits_json, a JSON blob listing which landmarks to move on which frames. The default shows the shape:

{
  "keyframes": [
    {"frame": 0,  "deltas": {"61": [-0.02, 0.01]}},
    {"frame": 30, "deltas": {"61": [-0.08, 0.04]}}
  ],
  "ease": "smooth_step",
  "extrapolate": "hold"
}

Deltas are per-landmark-index [dx, dy] in anchor-relative units - remember, 1.0 unit = one inter-ocular distance, so [-0.08, 0.04] is a small nudge, not a teleport. Landmark indices are MediaPipe FaceMesh indices; left_eye_idx (default 33) and right_eye_idx (default 263) define the inter-eye anchor. The optional external_anchors_json lets you override the computed anchors entirely ({"centers": [...], "scales": [...]}) if you have a better tracker upstream.

The inputs and outputs that matter

  • landmarks - the LANDMARKS stream in. Single-frame (N,2) is auto-promoted to T=1.
  • keyframe_edits_json - where you actually describe the edit. This is the one to learn.
  • anchor_mode - intereye (default, robust) or centroid (fallback).
  • landmarks_modified - the edited stream, same shape, ready to feed the same downstream you were feeding before.
  • info_json - per-frame anchor stats, keyframes used, deltas applied, weights. Read it once to check your blend before committing a long render.

Installing it

Ships in ComfyUI-CustomNodePacks. ComfyUI Manager → search "CustomNodePacks", or:

cd ComfyUI/custom_nodes
git clone https://github.com/Code2Collapse/ComfyUI-CustomNodePacks.git

Pure NumPy/torch math - no models, no downloads. The pack overall wants opencv-python>=4.7.0 and scipy>=1.10.0 for other nodes; install just those if missing (not the full requirements.txt, which can clobber ComfyUI's torch). Restart ComfyUI.

Common issues

  • Landmarks not found / wrong face - the indices must match your landmark source. Defaults assume MediaPipe FaceMesh (33/263); a different detector uses different indices.
  • Edits blow up when the head turns - that usually means your delta is too large in absolute terms or anchor_mode is wrong for the landmark set. If it's not a face set, switch to centroid.
  • intereye fell back to centroid - logged when the stream has too few landmarks for the eye indices; a sign your landmark source is incomplete.

The honest take: this node assumes you're already comfortable inside the Wan Animate landmark world - it's not a beginner's first node. But it's exactly the kind of surgical edit that's otherwise impossible: change the gaze on frame 30 without redrawing the whole shot.

CategoryMaskEditControl/Pose

Inputs (6)

NameTypeDefaultDescription
landmarksLANDMARKSPer-frame landmarks (T,N,2) — typically MediaPipe FaceMesh from WanAnimatePreprocessV2's gaze/pose detector. Single frame (N,2) is auto-promoted to T=1.
keyframe_edits_jsonSTRING{ "keyframes": [ {"frame": 0, "deltas": {"61": [-0.02, 0.01]}}, {"frame": 30, "deltas": {"61": [-0.08, 0.04]}} ], "ease": "smooth_step", "extrapolate": "hold" }JSON describing one or more keyframe edits in anchor-relative space (1.0 unit = inter-ocular distance).
anchor_modeCOMBOintereyeHow to compute the per-frame face anchor. 'intereye' is robust under head turns; 'centroid' is a fallback for non-face landmark sets.
left_eye_idxINT330–100000Landmark index of the outer-left eye corner. MediaPipe FaceMesh = 33.
right_eye_idxINT2630–100000Landmark index of the outer-right eye corner. MediaPipe FaceMesh = 263.
external_anchors_jsonoptSTRINGOptional override for per-frame anchors. JSON: {"centers": [[cx,cy], ...], "scales": [s, ...]}. If present, replaces the computed intereye/centroid anchors.

Outputs (2)

NameTypeDescription
landmarks_modifiedLANDMARKSModified landmarks (T, N, 2) — same shape and ordering as input.
info_jsonSTRINGJSON: per-frame anchor stats, keyframes used, deltas applied, weights at head/tail frames. Useful for debugging the blend.