Purpose
Apply one of three zoom motion types to a clip or still image — the zoom-punch transition and camera-emphasis moves described in CREATOR_GRAMMAR.md §3 and §5:
--type |
What it does | When to use |
|---|---|---|
zoom-punch |
Instant stepped tighter crop at a given timestamp — the clip plays at normal scale, then snaps to a tighter crop at --at |
Emphasis on a word or beat; replaces a cut to a tighter angle |
push-in |
Gradual scale-up from --from-scale to --to-scale over the whole clip |
Premium feel, slow approach; slow-burn reveal |
ken-burns |
Slow pan + zoom via ffmpeg zoompan — works on stills or clips |
Product still → motion, b-roll slow move |
Inputs
--source(required) — path to source video or image file.--type(required) — one ofzoom-punch,push-in,ken-burns.--from-scale(optional, default1.0) — starting scale multiplier (1.0 = no zoom).--to-scale(optional, default1.12) — ending/peak scale multiplier.--at(optional, default0) — timestamp in seconds forzoom-punchsnap (ignored for other types).--focal(optional) — focal point asx,yin 0–1 normalized coordinates (default0.5,0.5= center). Controls which part of the frame stays centered during zoom.--output(required) — output video path (.mp4).--fps(optional, default30) — output frame rate.--crf(optional, default18) — x264 quality.--duration(optional) — force output duration in seconds (required when source is an image).
Workflow
- Verify
ffmpegandffprobeare on PATH; verify source file exists. - Probe source: get width, height, duration (if video). For images, use
--duration(default 4s). - Compute crop/scale parameters from
--focal,--from-scale,--to-scale, source dimensions. - Build ffmpeg filtergraph:
zoom-punch: usetrim+setpts+scaleto produce two sub-clips (before/at--atatfrom-scale, after--atatto-scale), then concat.push-in: usescale=w=iw*<to_scale>:h=ih*<to_scale>+crop=iw/<to_scale>:ih/<to_scale>:...withzoompan-style lerp oroverlayapproach — implemented as a frame-accuratezoompanwithz=expression ramping fromfrom-scaletoto-scale.ken-burns: use ffmpegzoompan=z='zoom+0.0015':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d=<frames>:s=<WxH>:fps=<fps>.
- Run ffmpeg with
-c:v libx264 -crf <crf> -preset fast -pix_fmt yuv420p; include audio passthrough when source has audio. - Write
manifest.jsonto same directory as output.
Run:
python3 skills/atoms/editing/add-zoom-cuts/scripts/zoom_cuts.py \
--source /path/to/clip.mp4 \
--type push-in \
--to-scale 1.15 \
--output /path/to/clip-zoom.mp4
python3 skills/atoms/editing/add-zoom-cuts/scripts/zoom_cuts.py \
--source /path/to/clip.mp4 \
--type zoom-punch \
--at 2.5 \
--to-scale 1.20 \
--output /path/to/clip-punched.mp4
Output
<output>.mp4— re-encoded video with zoom motion applied.manifest.json(same directory as output) — includes:skill_name,run_id,input_path,output_files,provider,model_or_tool,created_at,duration_seconds,status,warnings,errorszoom_type,from_scale,to_scale,focal_x,focal_y,at_seconds(for zoom-punch)source_duration_seconds,output_duration_seconds
Quality Checks
- Output file exists and is non-zero size.
ffprobeconfirms output is playable H.264/yuv420p.- Output duration matches source duration (±0.2s for video sources).
- For
zoom-punch: visible scale step at--attimestamp when scrubbed frame-by-frame. - For
push-in: smooth continuous zoom fromfrom-scaletoto-scale. - For
ken-burns: slow pan + zoom motion visible on playback; no black borders (crop covers overscan). manifest.jsonrecords zoom parameters.
Failure Modes
ffmpeg/ffprobenot installed — install viabrew install ffmpeg.- Source missing or unreadable — script exits with clear error.
- Invalid
--type— script exits with usage error listing valid values. --atpast end of video (zoom-punch) — treated as applying zoom from start; warning recorded.--to-scale < --from-scale— script exits with error.--to-scale> 2.0 — allowed but warns; extreme zooms may introduce visible pixelation.- Image source without
--duration— defaults to 4s; if that is wrong, pass--duration. - Source has no audio — output produced without audio stream; not an error.