Two distinct systems power motion in a CapCut draft. They look similar but live in different fields and respond to different capcut-cli commands.
| System | What it does | Lives at | CLI command |
|---|---|---|---|
common_keyframes |
per-property motion: position, scale, rotation, alpha, colour adjustments | segment.common_keyframes[] |
keyframe, ken-burns.sh |
sticker_animation |
pre-built intro / outro / combo effects from CapCut's animation library | one entry in materials.material_animations[], referenced via extra_material_refs |
text-anim, image-anim |
For per-frame interpolation of a single property (position, scale, alpha, etc.):
One entry per property. Within an entry, keyframe_list is sorted by time_offset (microseconds from segment start, NOT from timeline start).
| CLI property | property_type |
Value range | Notes |
|---|---|---|---|
position_x |
KFTypePositionX |
-1 .. 1 normalized | 0 = horizontal centre |
position_y |
KFTypePositionY |
-1 .. 1 normalized | 0 = vertical centre |
rotation |
KFTypeRotation |
degrees clockwise | accepts "45deg" syntax |
scale_x |
KFTypeScaleX |
0 .. ∞ | 1 = source size |
scale_y |
KFTypeScaleY |
0 .. ∞ | |
uniform_scale |
KFTypeUniformScale |
0 .. ∞ | locks scale_x = scale_y |
alpha |
KFTypeAlpha |
0 .. 1 | accepts "50%" syntax |
saturation |
KFTypeSaturation |
-1 .. 1 | accepts "+0.5" syntax |
contrast |
KFTypeContrast |
-1 .. 1 | |
brightness |
KFTypeBrightness |
-1 .. 1 | |
volume |
KFTypeVolume |
0 .. 1 | audio segments only |
The keyframe CLI command appends to existing per-property lists on re-invocation and sorts by time_offset, so you can build up complex motion incrementally.
Per-keyframe interpolation curve to the NEXT keyframe in the list:
"Line"— linear (default)"Hold"— step (no interpolation)"Smooth"— ease in/out"Beizer"— custom bezier (additionalvalue_bezier_control_pointsfield)
capcut-cli keyframe writes "Line". Hand-editing is the only path to others today.
You cannot fade video/image segments via alpha keyframes. CapCut silently ignores KFTypeAlpha keyframes on video and text segments at render time, even though they're shown in the editor preview.
The correct way to fade in/out is the animation system (text-anim / image-anim with --intro fade-in / --outro fade-out slugs), which writes a sticker_animation entry instead. See below.
Motion keyframes (position, scale, rotation) DO render — Ken Burns zooms and slides work as expected via common_keyframes. The trap is alpha-only.
For drop-in animations from CapCut's library (fade-in, typewriter, pop-up, zoom-out, etc.), use the animation system. These live in materials.material_animations[] with type: "sticker_animation", despite the name applying to text and video segments too:
"materials": {
"material_animations": [
{
"id": "anim-uuid",
"type": "sticker_animation",
"animations": [
{
"id": "...",
"name": "Fade in",
"category_id": "", "category_name": "ruchang", // intro
"type": "in",
"resource_id": "...",
"effect_id": "...",
"material_type": "video", // or "text" / "sticker"
"platform": "cc",
"duration": 500000, // microseconds
"start": 0, // 0 for intros; auto-anchored for outros
"request_id": ""
}
// up to 3 entries per material: one intro, one outro, one combo
]
}
]
}The segment references this material via extra_material_refs:
"segment": {
"id": "...",
"extra_material_refs": [ "...speed...", "...placeholder...", "anim-uuid", ... ]
}CapCut allows ONE animation of each type (in, out, group) per segment, all packed into a single material_animations entry:
- Intro (
type: "in",category_name: "ruchang"): plays from segment start.start: 0. - Outro (
type: "out",category_name: "chuchang"): plays at segment end.capcut-cliauto-anchorsstart = target_duration - duration. - Combo (
type: "group",category_name: "zuhe"): plays through the entire segment (loop animations like pulse).
capcut-cli text-anim / image-anim guard against double-applying the same type — re-running with the same --intro slug overwrites; specifying neither leaves existing animations untouched.
The same animation system serves three segment types:
material_type: "video"for video / image segments (driven byimage-anim)material_type: "text"for text segments (driven bytext-anim)material_type: "sticker"for sticker segments (driven byimage-animsince the shape is identical)
Slugs are capcut-cli's human-readable handles for CapCut's library resources. The slug → resource_id / effect_id mapping lives in src/enums.json (extracted at build time from pyJianYingDraft). See 04-effects-filters-stickers.md for the namespace pattern (CapCut vs JianYing slugs differ).
Starter catalogue shipping in v0.3.0:
- Intros:
fade-in,flash-in,pulsing-zooms,scroll-up,stripe-merge,zoom-out - Outros:
fade-out,blur-out,smoke - Text intros:
fade-in,typewriter,pop-up,blur-text-in,zoom-in-text - Text outros:
fade-out,throw-out
Run capcut enums --image-intros -H (or --text-outros, etc.) for the full live catalogue including JianYing-namespace entries.
- Ken Burns (slow zoom + drift on a still): keyframes on
uniform_scale+position_x+position_y. Usescripts/ken-burns.shfrom the skill. - Fade in at clip start: animation system,
image-anim --intro fade-in(NOT alpha keyframe). - Fade out at clip end: animation system,
image-anim --outro fade-out. - Text reveals letter by letter: animation system,
text-anim --intro typewriter. - Subtle volume duck under a VO: keyframes on
volume. - Word-level caption highlight: NOT keyframes — use
text-rangeswhich writes multi-style entries inside the text material'scontentfield.
Both systems coexist. A video segment can have common_keyframes driving a Ken Burns motion AND a sticker_animation driving a fade-in intro. CapCut composites both at render time.