Generate short, assemble long
A video diffusion model trained on a ~5 second window will happily accept
length=1449 and spend 97 minutes returning a clip that is technically 60
seconds and effectively a photograph. The image stays clean. It is the
motion that dies.
Measured on Wan 2.2 TI2V-5B, same prompt, same seed, only length changed.
"Breath depth" is peak body displacement inside a 3-second window:
| length | breath depth |
|---|---|
| 181 frames (7.5 s) | 1.29 |
| 481 frames (20 s) | ~0.8 — 41% of mean motion retained |
| 1449 frames (60 s) | 0.31 |
For scale, a prompt deliberately asking for a motionless subject scored 0.58. The 60-second generation was stiller than asking for stillness. The model distributes the described movement across whatever duration you request, so per-frame motion shrinks as length grows.
Generate at or below the trained window. Assemble the rest.
Palindrome, not loop
Play the clip forward, then backward, and repeat. For any oscillatory subject — breathing, water, foliage — the reversal is undetectable, and the turnaround is a real frame sequence rather than a cut, so there is nothing to blend.
# trim the model's startup settle (first ~8 frames are noise resolving)
ffmpeg -i in.mp4 -vf "select=gte(n\,8),setpts=PTS-STARTPTS" -r 24 fwd.mp4
# reverse, dropping BOTH duplicated endpoints or the turnaround stutters
ffmpeg -i fwd.mp4 -vf "reverse,select=between(n\,1\,171),setpts=PTS-STARTPTS" -r 24 rev.mp4
# alternate in ONE concat list, then trim to target
printf "file 'fwd.mp4'\nfile 'rev.mp4'\n%.0s" {1..7} > list.txt
ffmpeg -f concat -safe 0 -i list.txt -frames:v 14400 out.mp4Motion survives exactly: 0.224 mean in the loop against 0.221 in the source. Joins measure 1.4x median motion where a real breath peaks at 13x — invisible.
Two traps:
-stream_loopis not frame-accurate. Looping a pre-built palindrome file left a hitch every cycle (2.7x median). Alternating the segments inside a single concat list dropped it to 1.4x.- A palindrome only works on symmetric motion. A directional gesture — a paw flexing, a head turning — visibly un-happens on the reverse. Check what your motion actually is before mirroring it.
Chaining dies at eight hops
Feeding each segment's last frame forward as the next segment's start_image
produces genuinely non-repeating footage. It also produces autoregressive error
feedback: the model slightly amplifies a spatial frequency, reads its own
output, and amplifies it again.
Edge energy and ringing, relative to the first segment:
| segment | 3 | 6 | 9 | 12 | 15 | 18 | 20 |
|---|---|---|---|---|---|---|---|
| edge | 107% | 124% | 150% | 179% | 198% | 218% | 220% |
| halo | 111% | 142% | 181% | 217% | 233% | 254% | 252% |
By segment 18 the texture is dense concentric ripples with colour fringing. It saturates near 220% — a resonance settling at its equilibrium amplitude, which happens to be ruinous. Useful to about 8-10 hops, then stop.
Zoom (0.5%/segment) and saturation (2%/segment, compounding) also drift, but
those are correctable in post. The resonance is not.
Two fixes that do not work
Recorded because both look obviously correct and cost a day between them.
Spectral matching plus blur. Match the seed frame's radial frequency profile to the reference, then blur until edge energy returns to baseline. At σ 0.6 all three metrics read ~100%. The ripples were still plainly visible. Damping a coherent ripple's amplitude satisfies a scalar statistic without removing the ripple. The numbers said fixed; the pixels said otherwise.
Lossy damping. An earlier chain that stayed clean had pulled its seed frames out of H.264 video; the failing one used lossless PNG. Plausible theory: compression was accidentally damping the feedback. Reintroduced deliberately as a JPEG round-trip at q85 — 112 / 136 / 161 / 174% at segments 3 / 6 / 9 / 12, identical to undamped within noise. The earlier chain was not stable because of compression. It was stable because it was only eight segments long, and it was measured through H.264 frames that smooth exactly this artifact.
Measuring periodic motion
- Use FFT, not autocorrelation. Autocorrelation locked onto a harmonic and
reported 12.3 cycles/min for a signal the FFT correctly read as 25.3.
Method: per-pixel temporal std → top 5% varying pixels → mean per frame →
quadratic detrend → Hanning →
rfft→ peak in the plausible band. - Never measure rate on an assembled loop. The loop's own cycle dominates the spectrum. A finished palindrome returned 16.7/min for footage whose forward segments each measured 25-27. Measure one un-looped segment.
- Scalar metrics are not a quality check. Every failure above passed at least one metric. Look at the pixels, at full resolution — a downscaled contact sheet hid a two-headed animal that was obvious at 1:1.
Prompt asymmetry
Positive prompts add objects to the scene; negatives remove concepts. They are not interchangeable, and the failures are literal:
| written in positive | produced |
|---|---|
whiskers twitching |
a twitching, restless animal |
motionless, does not move |
a frozen still image |
locked-off tripod shot |
an actual tripod, in frame |
Say what should move in the positive. Put suppression — including equipment
nouns, and frozen, still image, lifeless — only in the negative.
The general shape
Every attempt to generate long directly produced a worse result than assembling short clips, and cost far more. The single most expensive render in the exercise, at 97 minutes, was also the least usable output. Eight independently generated 7.5-second clips, palindromed and crossfaded, gave ten minutes with no degradation for about 90 minutes of total compute.