Prepare a driving clip for Wan Animate 2 with ffprobe and ffmpeg: trim, crop, re-check

●1 ●1 ●4
calendar_today ago • schedule2 min read

Character animation from a still image has a simple contract: one image says what the character looks like, one video says how it moves. Most failed attempts are not about the model at all. The driving clip is too long, the aspect ratio does not match the character, or the file breaks a size rule the upload form will reject anyway.

This post walks through a small Python script that checks both inputs against the published rules and prints the exact ffmpeg commands that fix what it finds. Everything below was run locally on a synthetic character image and an ffmpeg test pattern. No generation job was submitted.

Table of input rules for the character image and the driving video
Diagram made for this article from the rules on the page.

The rules worth encoding

The Wan Animate 2 page lists the input requirements in its upload panel and step-by-step section:

  • Character image: one JPG, JPEG or PNG, up to 10 MB, both sides greater than 340 px, aspect ratio between 2:5 and 5:2.
  • Driving video: one MP4 or MOV, up to 100 MB, both sides greater than 340 px, same aspect range.
  • Duration depends on orientation: following the video's orientation accepts 3–30 second clips; following the image's orientation accepts 3–10 seconds.
  • Other controls: background from the image or the video, 720p or 1080p output, and an optional prompt of up to 2,500 characters.

The orientation rule is the one people miss. It changes the allowed clip length, so the checker takes it as an argument instead of assuming one value.

Two boxes comparing follow video and follow image
Diagram made for this article.

The script

It needs Pillow and ffprobe/ffmpeg on your PATH. Save it as animate_prep.py:

#!/usr/bin/env python3
"""animate_prep.py - check a character image + driving video against the
input rules published on the Wan Animate 2 page, and print ffmpeg fixes.

Rules (edit when the page changes):
  image: JPG/JPEG/PNG, <= 10 MB, both sides > 340 px, aspect 2:5..5:2
  video: MP4/MOV, <= 100 MB, both sides > 340 px, aspect 2:5..5:2
         3-30 s when orientation follows the video, 3-10 s when it follows the image
Needs Pillow and ffprobe/ffmpeg on PATH. Nothing is uploaded.
"""
import argparse, json, os, subprocess, sys
from PIL import Image

MB = 1024 * 1024
IMG_EXT, VID_EXT = {".jpg", ".jpeg", ".png"}, {".mp4", ".mov"}
MIN_SIDE, AR_MIN, AR_MAX = 340, 2 / 5, 5 / 2
DUR = {"video": (3.0, 30.0), "image": (3.0, 10.0)}

def probe(path):
    out = subprocess.run(["ffprobe", "-v", "error", "-select_streams", "v:0",
                          "-show_entries", "stream=width,height:format=duration",
                          "-of", "json", path], capture_output=True, text=True, check=True).stdout
    j = json.loads(out)
    s = j["streams"][0]
    return s["width"], s["height"], float(j["format"]["duration"])

def check_dims(tag, w, h, errs):
    if min(w, h) <= MIN_SIDE:
        errs.append(f"{tag}: {w}x{h} - both sides must be > {MIN_SIDE} px")
    if not AR_MIN <= w / h <= AR_MAX:
        errs.append(f"{tag}: aspect {w / h:.2f} outside 2:5..5:2")

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("image"); ap.add_argument("video")
    ap.add_argument("--orientation", choices=["video", "image"], default="image")
    ap.add_argument("--start", type=float, default=0.0, help="where the useful motion begins (s)")
    a = ap.parse_args()
    errs, fixes = [], []

    ext = os.path.splitext(a.image)[1].lower()
    if ext not in IMG_EXT: errs.append(f"image: {ext} not in {sorted(IMG_EXT)}")
    if os.path.getsize(a.image) > 10 * MB: errs.append("image: larger than 10 MB")
    with Image.open(a.image) as im: iw, ih = im.size
    check_dims("image", iw, ih, errs)

    ext = os.path.splitext(a.video)[1].lower()
    if ext not in VID_EXT: errs.append(f"video: {ext} not in {sorted(VID_EXT)}")
    if os.path.getsize(a.video) > 100 * MB: errs.append("video: larger than 100 MB")
    vw, vh, dur = probe(a.video)
    check_dims("video", vw, vh, errs)
    lo, hi = DUR[a.orientation]
    if not lo <= dur <= hi:
        errs.append(f"video: {dur:.1f} s outside {lo:.0f}-{hi:.0f} s for orientation={a.orientation}")
        keep = min(hi, max(lo, dur - a.start))
        fixes.append(f"ffmpeg -y -ss {a.start:g} -i {a.video} -t {keep:g} -c:v libx264 -an trimmed.mp4")

    # match the driving clip's framing to the character image (centre crop)
    ia, va = iw / ih, vw / vh
    if abs(ia - va) > 0.05:
        if va > ia:  # video too wide -> crop width
            cw, ch = int(vh * ia) // 2 * 2, vh
        else:        # video too tall -> crop height
            cw, ch = vw, int(vw / ia) // 2 * 2
        src = "trimmed.mp4" if fixes else a.video
        fixes.append(f"ffmpeg -y -i {src} -vf crop={cw}:{ch} -c:v libx264 -an framed.mp4"
                     f"   # {vw}x{vh} -> {cw}x{ch}, aspect {ia:.2f} like the image")

    print(f"image {iw}x{ih} aspect {ia:.2f} | video {vw}x{vh} aspect {va:.2f} {dur:.1f} s | orientation={a.orientation}")
    for e in errs: print("FAIL", e)
    for f in fixes: print("FIX ", f)
    if not errs and not fixes: print("PASS ready for upload")
    sys.exit(1 if errs else 0)

if __name__ == "__main__":
    main()

Three things it does that a plain size check does not:

  1. Reads real dimensions and duration with ffprobe. File names and container metadata lie; the stream does not.
  2. Prints fixes instead of applying them. You decide where the useful motion starts (--start), and you can paste the command or tweak it.
  3. Matches the clip to the character, not just to the rules. A 16:9 clip technically passes the 2:5–5:2 range, but if your character image is a 2:3 portrait, the page itself advises matching the framing and body position of the image and the motion reference. The script suggests a centre crop to the image's aspect ratio when the two differ by more than 0.05.

Run 1: a 14-second landscape clip

The test inputs are a flat 768×1152 character drawing and a 14-second 1920×1080 testsrc2 clip, with the useful motion assumed to start at 2 seconds. With orientation following the image, the clip is too long, and its aspect ratio is far from the portrait character.

Terminal output of the first run with one failure and two fixes
Real output from a run on 2026-10-26.

The exit code is 1, so this can gate a script or a Makefile target.

Apply the fixes

The two printed commands trim to 10 seconds from the 2-second mark, then crop the centre 720×1080 region so the clip has the same 0.67 aspect ratio as the character image.

Frames before and after the crop next to the character image
Frames extracted with ffmpeg from the test clip during the same session.

A centre crop is only a default. If the performer stands off-centre, use crop=w:h:x:y with an explicit x offset, or reframe in an editor. What matters is that the body sits in the frame roughly where the character's body sits in the image.

Run 2: re-check

Terminal output showing the fixes applied and PASS
Real output from a run on 2026-10-26.

The re-encoded clip is 720×1080, exactly 10.0 seconds, and inside every published rule. That is the point to open the workspace, not before.

What the script cannot check

The page's advice on consistency is mostly visual: use a single clearly visible subject, keep the face, shoulders and torso visible, reduce occlusion and rapid cuts, and avoid large pose changes. None of that is measurable with ffprobe. Treat the script as the mechanical gate and still watch the clip once:

  • Does the performer stay in frame for the whole clip?
  • Are there hard cuts? A continuous take is what the page asks for.
  • Is the starting pose close to the character image's pose?

It also cannot know the credit cost. The page says the quote uses the verified reference-video duration and the selected resolution, and the generator shows the required credits before submission. Trimming to the motion you need is therefore also the cheapest thing you can do.

Upload panel of the Wan Animate 2 page with the published input specs
Screenshot captured 2026-10-26 (Asia/Shanghai).

Small extensions

  • Add -vf scale=-2:1080 after the crop if a source is below 1080 px tall and you plan to request 1080p output; do not upscale tiny clips and expect detail.
  • Strip audio (-an) as shown unless you need it elsewhere; the driving clip supplies movement and timing.
  • Keep the constants at the top of the file and update them when the page changes. Published limits drift, and a checker with stale numbers is worse than none.
🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelski - Mar 19

A Textile Mill's Dyehouse Ramp Curve: Get One Step Wrong and the Sheet Belongs to the Master

aiomniu - Oct 18

Dashboard Operasional Armada Rental Mobil dengan Python + FastAPI

Masbadar - Mar 12

The Sovereign Vault — A Comprehensive Guide to Protocol-Driven AI

Ken W. Algerverified - Jun 4

FFmpeg Beginner Guide

Divyansh1552005 - Oct 22, 2025
chevron_left
154 Points • 6 Badges
2Posts
0Comments
GPT Image 2.5 creative workflow notes.

Related Jobs

Commenters (This Week)

2 comments
1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!