moviepy for Video Production
moviepy is the toolkit's go-to library for putting deterministic text on top of AI-generated video and for building short, single-file Python video projects without a Remotion toolchain.
The deeper principle is trustworthy text: any genre where text has to be readable, accurate, and consistent (legally, editorially, or commercially) is a genre where AI-rendered in-frame text is unacceptable and a moviepy overlay step is the natural fix. Names must be spelled right. Prices must be exact. Source attributions must be pixel-perfect. AI generation models cannot guarantee any of that.
When to use moviepy vs. Remotion
Two runnable references for everything in this skill live in examples/:
examples/quick-spot/build.py— 15-second ad-style spot. Audio-anchored timeline, text overlay, optional VO + ducked music. Renders silent out of the box with zero external assets.examples/data-viz-chart/build.py— animated time-series chart with deterministic title and source attribution. Demonstrates the matplotlib (data) + moviepy (trustworthy text) split.
Both run with uv run build.py and produce a real out.mp4 immediately. Read them alongside this skill — every pattern below is shown working there.
Dependencies. moviepy, Pillow, and matplotlib are declared in the root pyproject.toml and installed with the toolkit's one-line Python setup: uv sync. If you hit Missing dependency when running an example, run that command from the repo root — the examples' build.py files will tell you the same thing in their error message and exit cleanly rather than printing a bare traceback.
The main use case: text on AI-generated video
Both LTX-2 and SadTalker output bare visuals:
- LTX-2 cannot reliably render readable text (the model hallucinates letterforms — see the ltx2 skill's "Bad Prompts").
- SadTalker outputs a talking head with no captions, labels, lower thirds, or context.
The fix is to generate the visual cleanly, then composite text over it deterministically with moviepy. This is the canonical pattern in this toolkit:
Common shapes this takes:
Genres where this shines
The "AI-visual + deterministic text overlay" pattern is the natural production pipeline for several styles of video. If the request matches one of these, reach for moviepy by default:
Lesser-but-real fits: music videos (lyric overlays), reaction videos (source attribution), sports recaps (score overlays), real-estate tours (price / sqft), conference talks (speaker + session plate).
For full SRT-driven subtitling (long-form, time-coded, multilingual) moviepy is workable but not ideal — reach for ffmpeg with subtitles filter or a dedicated subtitle tool. moviepy is best for hand-placed overlays, not bulk caption tracks.
Text rendering — use PIL, not TextClip
Critical gotcha: moviepy 2.x's TextClip(method='label') has a tight-bbox bug that clips letter ascenders and descenders (the tops of capitals, the tails of g/p/y). On Apple Silicon you'll see characters with sliced edges and not realise what's wrong for hours.
The workaround: render text to a transparent PNG via PIL, then load it as an ImageClip. Cache the result by content hash so re-builds are free.
The full helper (with kwargs for bold, position, fades, and cleaner ergonomics) is in examples/quick-spot/build.py — copy it rather than re-implementing.
Audio-anchored timeline pattern
For ad-style edits where every frame matters, generate per-scene VO first and anchor every visual to known absolute timestamps. This eliminates timing drift entirely. See CLAUDE.md → Video Timing → Audio-Anchored Timelines for the full pattern. The short version:
Common recipes
Text on a single AI-generated clip
Lower third over a SadTalker talking head
Tinted overlay for text contrast over busy footage
LTX-2 b-roll is often too visually busy for legible text. Drop a semi-transparent navy layer between the video and the text:
Side-by-side composite
Mix per-scene VO with ducked music
Gotchas
- moviepy 2.x renamed methods. Use
subclipped(notsubclip),with_duration/with_start/with_position(notset_durationetc.),with_effects([...])instead of.fadein()/.fadeout(). Many tutorials online still show 1.x syntax — be skeptical. TextClip(method='label')clips ascenders/descenders. Always use the PIL workaround above.OffthreadVideois Remotion-only. moviepy usesVideoFileClip. Don't mix the two.- Resizing requires Pillow ≥ 10.0 for the LANCZOS resample. If you see
ANTIALIASerrors, upgrade Pillow. ColorCliptakes RGB tuples, not hex strings. Use(20, 24, 38), not"#141826".- Audio in
VideoFileClipis loaded by default. Call.without_audio()if you only want the visual — composing with audio you don't want will cause silent VO drops inCompositeAudioClip. - Always set
size=(W, H)onCompositeVideoClip. Without it, output dimensions follow the first clip, which can be smaller than your target.
When to reach for what
References
- Runnable example — short ad-style spot:
examples/quick-spot/build.py - Runnable example — data-viz with text overlay:
examples/data-viz-chart/build.py - Audio-anchored timelines:
CLAUDE.md → Video Timing → Audio-Anchored Timelines - Related skills:
ltx2,ffmpeg,remotion

