* Adopt uv for Python dependency management Replace manual pip/venv setup with `uv sync` (pyproject.toml + uv.lock), and switch all tool invocations across docs, skills, commands, and examples to `uv run`. Optional extras: --extra whisper, --extra modal. tools/requirements.txt is kept as a pip fallback. * FIX: bring uv migration current with main — post-June deps and docs Adds websocket-client (60db websocket transport) to core deps and a 'youtube' extra for the google-* packages (tools/youtube_upload.py), regenerates uv.lock, bumps pyproject version to 0.18.0, and converts the 51 'python3 tools/…' invocations added since the PR was opened (60db, YouTube, Kiro docs, publish command) to 'uv run'. Verified with uv sync --frozen --extra youtube; voiceover, sixtydb_tts, youtube_upload and migrate_to_kiro all run through the project environment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Asharib Ali <asharibarain4@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
quick-spot
A 15-second ad-style example built with moviepy instead of Remotion. Demonstrates the toolkit's "single-file build.py" pattern for short-form video.
Renders out of the box with zero external assets — solid colour backgrounds and PIL-rendered text only. Add per-scene voiceover and music when you're ready and the build picks them up automatically.
Quick start
# From the toolkit root (one-time — installs moviepy, Pillow, matplotlib
# alongside the rest of the toolkit's optional Python deps):
uv sync
# Then:
cd examples/quick-spot
uv run build.py
That's it. ~15 seconds later you have out.mp4 — a silent text-on-colour spot. Open it to verify the timing and layout look right.
If you get a
Missing dependencyerror, the script will tell you exactly which package is missing and whichpipcommand to run.
Why moviepy instead of Remotion?
Use moviepy for sub-30s, tightly-timed content where every frame matters and where text must be deterministic — trailers, promos, news lower thirds, social captions. The Remotion templates (sprint-review, product-demo) are designed for longer, design-system-driven work; moviepy is the right tool for ad-style spots.
See the moviepy skill (.claude/skills/moviepy/SKILL.md) for the full pattern guide and the genres where this approach shines.
What this example demonstrates
-
PIL → ImageClip text rendering. moviepy 2.x's
TextClip(method='label')clips letter ascenders and descenders. The fix is to render text to a transparent PNG via Pillow and load it as anImageClip. The cache key is the content hash so re-builds are free. Seerender_text_png()inbuild.py. -
Audio-anchored timeline. The comment block at the top of
build()is the source of truth. Every visual element has an absolutestart=referencing it. Drift is impossible because durations come from real audio (when present) rather than estimates. See CLAUDE.md → Video Timing → Audio-Anchored Timelines. -
Optional VO + ducked music.
build_audio()checks for per-scene mp3 files and mixes them only if all are present, otherwise renders silent. Music is ducked to 22% volume under VO at 115% — tuned defaults from production use. -
Solid-colour backgrounds with fades. No external video required. Swap these for
VideoFileClip("ltx_broll.mp4")if you generate b-roll withtools/ltx2.py.
Adding voiceover
# 1. Generate per-scene VO from the script (uses Qwen3-TTS by default; pass --provider elevenlabs for ElevenLabs)
uv run ../../tools/voiceover.py \
--script VOICEOVER-SCRIPT.md \
--scene-dir public/audio/scenes
# 2. Optional: generate background music
uv run ../../tools/music_gen.py \
--preset cta --duration 15 \
--output public/audio/music.mp3
# 3. Re-render — build.py picks up the new files automatically
uv run build.py
After the first audio render, run uv run ../../tools/sync_timing.py --voiceover-json against the generated audio to verify your scene-anchored timestamps still match the actual audio durations. If the audio is significantly longer/shorter than your timeline, edit the start= values and the comment block at the top of build.py.
Adding LTX-2 b-roll
Replace any color_bg(...) call with a video clip:
from moviepy import VideoFileClip
bg = (
VideoFileClip(str(HERE / "public" / "broll" / "scene1.mp4"))
.without_audio()
.subclipped(0, 4.0)
.resized(new_size=(W, H))
.with_start(0.0)
)
clips.append(bg)
# Optional: tinted overlay so the text stays readable over busy footage
clips.append(
ColorClip((W, H), color=NAVY_DARK)
.with_duration(4.0)
.with_start(0.0)
.with_opacity(0.55)
)
Generate b-roll with:
uv run ../../tools/ltx2.py \
--prompt "Dark moody abstract background, blue light streaks, cinematic" \
--num-frames 121 \
--output public/broll/scene1.mp4
See the ltx2 skill for prompting guidance.
File layout
quick-spot/
├── README.md # this file
├── VOICEOVER-SCRIPT.md # narration script (used by voiceover.py)
├── build.py # the entire video — runnable as-is
├── .text_cache/ # PIL text PNGs (auto-generated, gitignored)
├── public/
│ ├── audio/ # voiceover + music (gitignored, optional)
│ └── broll/ # LTX-2 generated b-roll (gitignored, optional)
└── out.mp4 # render output (gitignored)
Customising
- Colours — edit the constants near the top of
build.py(NAVY,CORAL_HEX, etc.) or pull them from abrands/profile. - Font — change
FONT_BOLDto any TTF on your system. The cache invalidates automatically when the font path changes (it's part of the hash key). - Duration — extend
DURATION, add scenes to the timeline comment block, then addtext_clip(...)calls referencing the new starts. - Aspect ratio — change
W, Hto(1080, 1920)for vertical (Reels/Shorts/TikTok). Allposition=("center", ...)values still work.
Related
- moviepy skill — full pattern reference with genre fits and gotchas
- ltx2 skill — generating b-roll and stylised character cameos
- acestep skill — generating background music with
music_gen.py - examples/data-viz-chart — sibling example showing matplotlib + moviepy for data-driven scenes