Nodes/Comfyui-zhenzhen/zhenzhen-animate-motion-transfer
ComfyUI Node

zhenzhen-animate-motion-transfer

Motion transfer without building a pose stack

By T8mars·Created about a year ago·Updated 3 days ago· 749
zhenzhen-animate-motion-transfer
  • input_image
  • input_video
  • api_config
  • video
  • video_url
  • task_id
  • response
◄image_url►
◄video_url►
◄resolution720p►
◄ratioadaptive►
◄custom_ratio16:9►
◄frame_rate30►
◄max_frames0►
◄skip_frames0►
◄pose_methodvitpose►
◄normal_modetrue►
◄neck_correctionfalse►
◄pose_strength1.00►
◄camera_motionfalse►
◄camera_strength1.00►
◄mask_modefalse►
◄expression_strength0.80►
◄chest_motion_strength0.20►
◄skip_errorfalse►
◄seed0►

You have a character image. You have a video of somebody dancing. You want the first one doing the second one's dance. Locally that's a Wan Animate / SCAIL-style pipeline plus pose checkpoints plus the 4n+1 frame-length trap; through this node it's two inputs and a poll loop on someone else's GPU.

zhenzhen-animate-motion-transfer (the pack calls it T8Zhenzhen_Animate_Motion_Transfer_LowPrice) is a thin ComfyUI wrapper around the animate-motion-transfer model on Zhenzhen's low-price API channel. It runs no model locally. It uploads your image and your video, hands the server a JSON payload, waits, and drops an MP4 back in as a VIDEO.

Why you'd reach for it

Motion transfer is the one video job where the pose skeleton argument still matters. SCAIL-2's whole pitch was removing the stick figure - squash a driving performance into a skeleton and you throw away depth and contact, exactly what makes a character look like it occupies the wrong space. This node sits on the older, safer side: it exposes a pose extractor choice (vitpose, sdpose, wuwupose), so the server does keypoint extraction and retargeting for you. You get the skeleton workflow without owning it - no pose models, no preprocessing chain, no tiling hacks. You pay per call instead.

What actually happens under the hood

Grep the class in qwen21_animate_low_price_nodes.py and the order is plain. It checks that exactly one character image and one motion video arrived, then uploads whatever is local - the IMAGE as PNG bytes, the VIDEO encoded with a MIME type picked from its container (mp4, mov, avi, mkv). Then it builds:

{"model": "animate-motion-transfer", "images": [image_url], "metadata": {...}}

and submits through the pack's compatibility video endpoint, polls with a progress bar, downloads the result MP4 and decodes it to ComfyUI's VIDEO type. One detail worth knowing: several metadata keys are only sent when they differ from the default (max_frames, pose_strength, camera_motion, expression_strength and friends). Leaving a widget alone means the API's own default applies - not necessarily the number you see in the box.

The inputs that matter

  • Exactly one image, one way. image_url (a string URL) or input_image (an IMAGE). The tooltips say "Leave empty for input_image," and the validator enforces it brutally: both filled, or neither, gets you Provide exactly one character image: image_url or input_image. Same rules for the video: video_url or input_video.
  • resolution - 480p, 720p, 1080p, default 720p. There's a hard rule baked in: at 1080p, max_frames can't exceed frame_rate × 10. Ask for more frames than that and it refuses rather than silently truncating.
  • ratio - default adaptive, which follows your source. Eight fixed ratios plus custom, which reads custom_ratio as W:H.
  • pose_method - leave it on vitpose unless the output looks wrong; wuwupose and sdpose are the alternates, and swapping extractors is the cheapest A/B test when the retarget looks mushy.
  • The three strength dials - pose_strength (1.0), expression_strength (0.8), chest_motion_strength (0.2). This is the node's personality; back expression_strength off if your character's face is fighting the driving actor's.

Outputs are a video (wire it to Save Video or a combine node), video_url, task_id, and response (the full JSON). The task_id is your receipt: if ComfyUI times out, the author's advice is to open that task on the site's async-task list and download it there, because a client timeout often isn't a failed job.

Install

Nothing model-shaped to download - this is a network node, and the "dependency" is an API key from Zhenzhen's shop (domestic api.seedance.nz, overseas ai.t8star.org).

cd ComfyUI/custom_nodes
git clone https://github.com/T8mars/Comfyui-zhenzhen
# restart ComfyUI; Manager can also search "Comfyui-zhenzhen"

requirements.txt is a fat but ordinary list (aiohttp, requests, Pillow, numpy and friends).

Where people get burned

The Settings node is mandatory-ish. api_config comes from the pack's Settings node (ZHENZHEN_SEEDANCE2_CONFIG). An empty key there gives you Connected Seedance 2.0 Low Price Settings has an empty api_key. - the node falls back to SEEDANCE_API_KEY / SEEDANCE_BASE_URL env vars, and if those are missing too, it refuses. The pack ships an ApiKey button in its own JS extensions, which is the fastest way to find where to paste it.

The overseas channel needs a real network path. The README is blunt: it's an overseas server, some users can't reach it at all (443 errors), and the fix is a proper VPN in TUN mode - its words, not mine - plus turning off aggressive local firewalls. Downloads are already hardened: result fetches get a 120-second floor on their timeouts, and a dead HTTP_PROXY/HTTPS_PROXY variable that kills the CDN connection triggers a retry through a session that ignores environment proxies.

Widget drift after updating. New controls get appended to the end of the list so old widgets_values stay valid, but the README has had to say "right-click the node → refresh/fix, or re-add it" more than once after a reshuffle. If a value looks like it belongs to a different field, rebuild the node.

And one setting: seed is ComfyUI cache bookkeeping only - never sent to the API. Leave it fixed with unchanged inputs and you get the cached result, not a fresh render.

Categoryzhenzhen/Seedance2 Low Price

Inputs (22)

NameTypeDefaultDescription
image_urlSTRINGLeave empty for input_image.
video_urlSTRINGLeave empty for input_video.
resolutionCOMBO720p3 options: 480p, 720p, 1080p
ratioCOMBOadaptive10 options: adaptive, 1:1, 2:3, 3:2, 3:4, 4:3, +4
custom_ratioSTRING16:9—
frame_rateINT301–999999—
max_framesINT00–9999990 uses the API default.
skip_framesINT00–999999—
pose_methodCOMBOvitpose3 options: vitpose, sdpose, wuwupose
normal_modeBOOLEANtrue—
neck_correctionBOOLEANfalse—
pose_strengthFLOAT1.00—
camera_motionBOOLEANfalse—
camera_strengthFLOAT1.00—
mask_modeBOOLEANfalse—
expression_strengthFLOAT0.80—
chest_motion_strengthFLOAT0.20—
input_imageoptIMAGE—
input_videooptVIDEO—
api_configoptZHENZHEN_SEEDANCE2_CONFIG—
skip_erroroptBOOLEANfalse—
seedoptINT00–18446744073709550000ComfyUI cache seed only; not sent to the API.

Outputs (4)

NameTypeDescription
videoVIDEO—
video_urlSTRING—
task_idSTRING—
responseSTRING—