CapCut's GUI lets you drag a zoom keyframe onto a clip, but doing it for 60+ shots in a 12-minute video is a wrist injury. This recipe writes the keyframes directly into draft_info.json for one segment at a time, and you call it from a loop or a script.
Chinese translation pending — see
README.zh-CN.mdat the repo root.
A complete zoom (the motion finishes by the cut), ScaleX interpolated linearly across the segment's full duration, position and rotation locked.
Two directions:
| Direction | Scale | Use it for |
|---|---|---|
in |
1.0 → 1.2 | dwell shots — emotional close-ups, ruins, single-image payoff beats |
out |
1.5 → 1.0 | cinematic reveals — opens the frame at the start of an act |
The zoom-out has bigger amplitude on purpose. A reveal needs more space to travel than a push-in.
- A CapCut project that already has at least one video segment (image or footage).
- Python 3.9+. No external deps.
# zoom in on a payoff still at 9:49
python3 scripts/apply-zoom.py \
"$HOME/Movies/CapCut/User Data/Projects/com.lveditor.draft/MyProject" \
--at "9:49" --direction in
# zoom out on the act-opening wide
python3 scripts/apply-zoom.py \
"$HOME/Movies/CapCut/User Data/Projects/com.lveditor.draft/MyProject" \
--material-name "act01-opening-wide.jpg" --direction outThree ways to identify the target segment — any one is enough:
--segment <id>— exact segment id fromcapcut infoordraft_info.json--at "M:SS"— any timestamp inside the slot (the script picks the segment that contains it)--material-name <substring>— case-insensitive match on the material's filename
Then close + reopen CapCut to load the new keyframes.
For a slot of duration D seconds, it sets:
time_offset is in microseconds. The 1_000_000 is what bites if you write this by hand — milliseconds break silently because the keyframe just sits at frame 0.
- Don't combine with the unfinished-pan recipe on the same segment. Pick one motion.
- Don't apply zoom-in to fast-cut hooks (under ~2s). The motion is invisible at that length.
- Don't use zoom-out as a default — it's a reveal. Most shots want zoom-in or no motion.
A .before-zoom backup of draft_info.json is written next to the original. Pass --no-backup to skip.