Wan Animate 2: Motion Transfer
Animate a still character using a driving video with Wan Animate 2, transferring motion directly from video frames without any pose extraction or skeleton preprocessing. The output preserves the reference character's identity while generating a fresh background and camera movement from your text prompt. Ideal for motion transfer, action mimicry, and character animation replacement projects.
Inside the workflow
A read-only preview of the source template. Open it in Cloud to explore the nodes and make changes.
Loading the graph
Open full-size workflow preview- Output
- Video
- Author
- ComfyUI
Call this model from your code
The request below uses the same prepared graph and settings as the Playground. Each submission can start a paid run; do not automatically retry a submission whose outcome is unknown.
What you need
- Runs on
- Comfy Cloud
- Key
- COMFY_API_KEY
- Your files
- Uploaded by the code from their urls
Needs a paid Cloud plan and credits.
# Python 3.10+: pip install comfy-sdk==0.4.0
import os
from comfy_sdk import Comfy
api_key = os.environ.get("COMFY_API_KEY", "").strip()
if not api_key: raise RuntimeError("Set COMFY_API_KEY")
os.environ.setdefault("COMFY_BASE_URL", "https://cloud.comfy.org")
client = Comfy(api_key=api_key)
workflow = client.workflows.from_json({
"189": {
"class_type": "LoadImage",
"inputs": {
"image": "UPLOADED_image_FILENAME"
},
"_meta": {
"title": "Load Image (Reference Image)"
}
},
"240": {
"class_type": "LoadVideo",
"inputs": {
"file": "UPLOADED_video_FILENAME"
},
"_meta": {
"title": "Load Video (Pose Video)"
}
},
"245": {
"class_type": "CreateVideo",
"inputs": {
"audio": [
"288",
1
],
"bit_depth": 8,
"codec": "none",
"color_space": "sRGB",
"fps": [
"288",
2
],
"images": [
"289",
0
]
},
"_meta": {
"title": "Create Video"
}
},
"246": {
"class_type": "SaveVideo",
"inputs": {
"filename_prefix": "video/ComfyUI",
"format": "auto",
"format.codec": "auto",
"video": [
"245",
0
]
},
"_meta": {
"title": "Save Video"
}
},
"288": {
"class_type": "GetVideoComponents",
"inputs": {
"video": [
"240",
0
]
},
"_meta": {
"title": "Get Video Components"
}
},
"289": {
"class_type": "BatchImagesNode",
"inputs": {
"images.image0": [
"261:299",
0
]
},
"_meta": {
"title": "Batch Images"
}
},
"292": {
"class_type": "SaveVideo",
"inputs": {
"filename_prefix": "video/ComfyUI",
"format": "auto",
"format.codec": "auto",
"video": [
"291:80",
0
]
},
"_meta": {
"title": "Save Video"
}
},
"536": {
"class_type": "ComfyMathExpression",
"inputs": {
"expression": "a / 81",
"values.a": [
"537",
2
]
},
"_meta": {
"title": "Math Expression"
}
},
"537": {
"class_type": "GetImageSize",
"inputs": {
"image": [
"288",
0
]
},
"_meta": {
"title": "Get Image Size"
}
},
"538": {
"class_type": "PreviewAny",
"inputs": {
"source": [
"536",
0
]
},
"_meta": {
"title": "Preview as Text"
}
},
"261:11": {
"class_type": "LoraLoaderModelOnly",
"inputs": {
"lora_name": "lightx2v_I2V_14B_480p_cfg_step_distill_rank64_bf16.safetensors",
"model": [
"261:239",
0
],
"strength_model": 1
},
"_meta": {
"title": "Load LoRA"
}
},
"261:18": {
"class_type": "BasicScheduler",
"inputs": {
"denoise": 1,
"model": [
"261:224",
0
],
"scheduler": "simple",
"steps": 6
},
"_meta": {
"title": "BasicScheduler"
}
},
"261:19": {
"class_type": "SamplerCustom",
"inputs": {
"add_noise": True,
"cfg": 1,
"latent_image": [
"261:247",
2
],
"model": [
"261:95",
0
],
"negative": [
"261:247",
1
],
"noise_seed": 951677846216619,
"positive": [
"261:247",
0
],
"sampler": [
"261:27",
0
],
"sigmas": [
"261:18",
0
]
},
"_meta": {
"title": "SamplerCustom"
}
},
"261:220": {
"class_type": "CLIPVisionEncode",
"inputs": {
"clip_vision": [
"261:75",
0
],
"crop": "none",
"image": [
"261:236",
0
]
},
"_meta": {
"title": "CLIP Vision Encode"
}
},
"261:222": {
"class_type": "CLIPTextEncode",
"inputs": {
"clip": [
"261:9",
0
],
"text": "An elderly mystic doing an energetic contemporary street dance with rhythmic steps, wide arm swings, a spin and a crouch, background stationary"
},
"_meta": {
"title": "CLIP Text Encode (Prompt)"
}
},
"261:223": {
"class_type": "TrimVideoLatent",
"inputs": {
"samples": [
"261:19",
0
],
"trim_amount": [
"261:247",
3
]
},
"_meta": {
"title": "Trim Video Latent"
}
},
"261:224": {
"class_type": "WanAnimate2Cache",
"inputs": {
"device": "gpu",
"dtype": "int8",
"model": [
"261:258",
0
]
},
"_meta": {
"title": "WanAnimate2Cache"
}
},
"261:236": {
"class_type": "ImageFromBatch",
"inputs": {
"batch_index": 0,
"image": [
"261:243",
0
],
"length": 1
},
"_meta": {
"title": "Get Image from Batch"
}
},
"261:239": {
"class_type": "UNETLoader",
"inputs": {
"unet_name": "wan_animate_2_int8_convrot.safetensors",
"weight_dtype": "default"
},
"_meta": {
"title": "Load Diffusion Model"
}
},
"261:241": {
"class_type": "GetVideoComponents",
"inputs": {
"video": [
"240",
0
]
},
"_meta": {
"title": "Get Video Components"
}
},
"261:243": {
"class_type": "ResizeImageMaskNode",
"inputs": {
"input": [
"261:241",
0
],
"resize_type": "scale dimensions",
"resize_type.crop": "center",
"resize_type.height": 480,
"resize_type.width": 832,
"scale_method": "area"
},
"_meta": {
"title": "Resize Image/Mask"
}
},
"261:244": {
"class_type": "ResizeImageMaskNode",
"inputs": {
"input": [
"189",
0
],
"resize_type": "scale dimensions",
"resize_type.crop": "center",
"resize_type.height": [
"261:256",
1
],
"resize_type.width": [
"261:256",
0
],
"scale_method": "area"
},
"_meta": {
"title": "Resize Image/Mask"
}
},
"261:247": {
"class_type": "WanAnimate2ToVideo",
"inputs": {
"batch_size": 1,
"clip_vision_output": [
"261:76",
0
],
"clip_vision_output_pose": [
"261:220",
0
],
"height": [
"261:256",
1
],
"length": 81,
"negative": [
"261:4",
0
],
"pose_end_percent": 1,
"pose_start_percent": 0,
"pose_strength": 1,
"pose_video": [
"261:243",
0
],
"positive": [
"261:3",
0
],
"positive_pose": [
"261:222",
0
],
"reference_image": [
"261:244",
0
],
"reference_image_strength": 1,
"vae": [
"261:7",
0
],
"video_frame_offset": 0,
"width": [
"261:256",
0
]
},
"_meta": {
"title": "WanAnimate2ToVideo"
}
},
"261:256": {
"class_type": "GetImageSize",
"inputs": {
"image": [
"261:243",
0
]
},
"_meta": {
"title": "Get Image Size"
}
},
"261:257": {
"class_type": "ContextWindowsManual",
"inputs": {
"causal_window_fix": True,
"closed_loop": False,
"cond_retain_index_list": "0",
"context_length": 21,
"context_overlap": 8,
"context_schedule": "standard_static",
"context_stride": 1,
"dim": 2,
"freenoise": True,
"fuse_method": "pyramid",
"latent_retain_index_list": "",
"model": [
"261:11",
0
],
"split_conds_to_windows": False
},
"_meta": {
"title": "Context Windows (Manual)"
}
},
"261:258": {
"class_type": "ComfySwitchNode",
"inputs": {
"on_false": [
"261:11",
0
],
"on_true": [
"261:257",
0
],
"switch": False
},
"_meta": {
"title": "If/Else Switch"
}
},
"261:27": {
"class_type": "KSamplerSelect",
"inputs": {
"sampler_name": "lcm"
},
"_meta": {
"title": "KSamplerSelect"
}
},
"261:297": {
"class_type": "PrimitiveBoolean",
"inputs": {
"value": False
},
"_meta": {
"title": "Boolean"
}
},
"261:298": {
"class_type": "ImageFromBatch",
"inputs": {
"batch_index": 1,
"image": [
"261:6",
0
],
"length": 4096
},
"_meta": {
"title": "Get Image from Batch"
}
},
"261:299": {
"class_type": "ComfySwitchNode",
"inputs": {
"on_false": [
"261:6",
0
],
"on_true": [
"261:298",
0
],
"switch": [
"261:297",
0
]
},
"_meta": {
"title": "If/Else Switch"
}
},
"261:3": {
"class_type": "CLIPTextEncode",
"inputs": {
"clip": [
"261:9",
0
],
"text": "Character appearance description: A tall, sinewy elderly mystic with deeply creased pale slate-grey skin, intense amber eyes and a long braided steel-grey beard falling over his collarbones. A crown of polished white stag antlers bound with sinew sits on his head. A glossy black raven-feather mantle covers his shoulders over a bare, scarred torso. He wears a heavy pleated crimson wool kilt with a herringbone weave, a belt of carved ivory plaques and tarnished silver rings, and dark sealskin bindings wrapped around his lower legs; his feet are bare.\nBackground description: A plain, dark charcoal backdrop with soft drifting mist and cold blue directional light, no decorations."
},
"_meta": {
"title": "CLIP Text Encode (Positive Prompt)"
}
},
"261:4": {
"class_type": "CLIPTextEncode",
"inputs": {
"clip": [
"261:9",
0
],
"text": "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
},
"_meta": {
"title": "CLIP Text Encode (Negative Prompt)"
}
},
"261:6": {
"class_type": "VAEDecode",
"inputs": {
"samples": [
"261:223",
0
],
"vae": [
"261:7",
0
]
},
"_meta": {
"title": "VAE Decode"
}
},
"261:7": {
"class_type": "VAELoader",
"inputs": {
"vae_name": "Wan2_1_VAE_bf16.safetensors"
},
"_meta": {
"title": "Load VAE"
}
},
"261:75": {
"class_type": "CLIPVisionLoader",
"inputs": {
"clip_name": "clip_vision_h.safetensors"
},
"_meta": {
"title": "Load CLIP Vision"
}
},
"261:76": {
"class_type": "CLIPVisionEncode",
"inputs": {
"clip_vision": [
"261:75",
0
],
"crop": "none",
"image": [
"261:244",
0
]
},
"_meta": {
"title": "CLIP Vision Encode"
}
},
"261:9": {
"class_type": "CLIPLoader",
"inputs": {
"clip_name": "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"device": "default",
"type": "wan"
},
"_meta": {
"title": "Load CLIP"
}
},
"261:95": {
"class_type": "ModelSamplingSD3",
"inputs": {
"model": [
"261:224",
0
],
"shift": 5
},
"_meta": {
"title": "ModelSamplingSD3"
}
},
"291:290": {
"class_type": "ComfyMathExpression",
"inputs": {
"expression": "a & ~1",
"values.a": [
"291:90",
0
]
},
"_meta": {
"title": "Math Expression"
}
},
"291:416": {
"class_type": "GetImageSize",
"inputs": {
"image": [
"291:77",
0
]
},
"_meta": {
"title": "Get Image Size"
}
},
"291:417": {
"class_type": "ImageFromBatch",
"inputs": {
"batch_index": 0,
"image": [
"291:77",
0
],
"length": [
"291:572",
1
]
},
"_meta": {
"title": "Get Image from Batch"
}
},
"291:571": {
"class_type": "GetImageSize",
"inputs": {
"image": [
"291:78",
0
]
},
"_meta": {
"title": "Get Image Size"
}
},
"291:572": {
"class_type": "ComfyMathExpression",
"inputs": {
"expression": "min(a,b)",
"values.a": [
"291:416",
2
],
"values.b": [
"291:571",
2
]
},
"_meta": {
"title": "Math Expression"
}
},
"291:573": {
"class_type": "ImageFromBatch",
"inputs": {
"batch_index": 0,
"image": [
"291:78",
0
],
"length": [
"291:572",
1
]
},
"_meta": {
"title": "Get Image from Batch"
}
},
"291:77": {
"class_type": "GetVideoComponents",
"inputs": {
"video": [
"246",
0
]
},
"_meta": {
"title": "Get Video Components"
}
},
"291:78": {
"class_type": "GetVideoComponents",
"inputs": {
"video": [
"240",
0
]
},
"_meta": {
"title": "Get Video Components"
}
},
"291:79": {
"class_type": "ImageStitch",
"inputs": {
"direction": "right",
"image1": [
"291:417",
0
],
"image2": [
"291:573",
0
],
"match_image_size": True,
"spacing_color": "white",
"spacing_width": 0
},
"_meta": {
"title": "Stitch Images"
}
},
"291:80": {
"class_type": "CreateVideo",
"inputs": {
"audio": [
"291:77",
1
],
"bit_depth": 8,
"codec": "none",
"color_space": "sRGB",
"fps": [
"291:77",
2
],
"images": [
"291:97",
0
]
},
"_meta": {
"title": "Create Video"
}
},
"291:90": {
"class_type": "GetImageSize",
"inputs": {
"image": [
"291:79",
0
]
},
"_meta": {
"title": "Get Image Size"
}
},
"291:96": {
"class_type": "ComfyMathExpression",
"inputs": {
"expression": "a & ~1",
"values.a": [
"291:90",
1
]
},
"_meta": {
"title": "Math Expression"
}
},
"291:97": {
"class_type": "ResizeImageMaskNode",
"inputs": {
"input": [
"291:79",
0
],
"resize_type": "scale dimensions",
"resize_type.crop": "center",
"resize_type.height": [
"291:96",
1
],
"resize_type.width": [
"291:290",
1
],
"scale_method": "area"
},
"_meta": {
"title": "Resize Image/Mask"
}
}
})
workflow.set_input("189", "image", client.assets.from_url("https://comfy.org/catalog-media/daad57c5-e3b2-44b3-a5e6-7b9ccd2801af/8aa22c70-41c6-4a3a-ae58-d675bb83f3fe"))
workflow.set_input("240", "file", client.assets.from_url("https://comfy.org/catalog-media/7efe8b53-ab50-4c05-b6f5-17cf11809cd4/09024a32-55f2-4c41-8582-e1497516ae1d"))
# run() also passes api_key to the partner nodes in this workflow.
job = client.run(workflow, api_key=api_key)
for output in job.get_outputs("246"):
output.to_file(output.name)
for output in job.get_outputs("292"):
output.to_file(output.name)Try an example
About this workflow
This ComfyUI workflow demonstrates end-to-end motion transfer using Wan-Animate-2 (by Wan-Video/Alibaba): a single reference image (your character) is animated to follow the motion in a driving video, without any pose keypoint or skeleton extraction. The core of the graph is the "Motion Transfer (Wan Animate 2)" subgraph powered by custom Wan-Animate-2 nodes (UUIDs: 11706f8a-428d-4ef9-b24f-863f651c1b0b, 79280513-53b2-4417-93f8-f3e116bb6736, b798a87e-23ec-483d-ab9b-6af5b298d0ad). These nodes accept your reference image, the per-frame motion signal from the driving video, and a text prompt that guides background look and camera feel.
Because motion is learned directly from frames, setup is fast and robust—no OpenPose or preprocessing passes.
LoadImage provides the character identity, while LoadVideo and GetVideoComponents extract the driving frames and fps. BatchImagesNode replicates the reference image so each video frame gets its own paired identity input. GetImageSize and ComfyMathExpression are used to keep resolution and aspect ratio consistent between inputs and outputs. PreviewAny lets you inspect intermediate frames, and CreateVideo + SaveVideo assemble the generated frames back into a video using the original fps.
For longer clips, you manually duplicate the Motion Transfer subgraph (native looping support is pending upstream) and connect it in sequence so the pipeline can process the entire driving video.
Questions & answers
No. Wan-Animate-2 performs direct motion transfer from the driving video frames. The workflow does not require keypoints or pose preprocessing.
Use a clean, well-lit reference image with minimal occlusion and a simple background. Keep the aspect ratio consistent, and avoid prompts that describe a different character or facial features that conflict with your reference.
GetVideoComponents provides fps for the driving clip—pass that to CreateVideo so playback matches the source timing. To retime, adjust the fps in CreateVideo (higher fps = faster playback, lower fps = slower).
Native loop support is still under review. For now, duplicate the "Motion Transfer (Wan Animate 2)" subgraph: enable the bypassed copy (Ctrl+B), ensure its prompt and size match the first, connect its image output to BatchImagesNode, then copy-paste (Ctrl+C, Ctrl+Shift+V) enough segments to cover the full driving video.