agentsclimarketplace

Capcut editor

Skill duckyman-ai/agent-skills/skills/capcut-editor

πŸ¦† Ducky's agent skills collection

Install
npx -y skills add duckyman-ai/agent-skills --skill capcut-editor

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Read, analyze, and create CapCut desktop (macOS) video editing projects programmatically. Use this skill whenever the user mentions CapCut, video editing with CapCut, reading CapCut projects, creating CapCut-compatible drafts, adding video/audio/text tracks to CapCut, or wants to automate any CapCut desktop workflow. Also use when the user wants to batch-create video projects, insert media into CapCut, or manage CapCut project files.

SKILL.md

22.4 KB, as published. Nobody here has run it

CapCut Desktop Project Skill

Programmatically read, create, and modify CapCut desktop (macOS) project files.

Important Limitation

Only locally-created projects can be read/written. Projects downloaded from CapCut's template library have encrypted draft_info.json files (base64-encoded with proprietary ByteDance encryption) and cannot be modified. Always check by attempting json.loads() β€” if it fails, the project is encrypted.

Project Location

BASE = ~/Movies/CapCut/User Data/Projects/com.lveditor.draft/

Quick Reference

Read references/draft-schema.md for the complete JSON schema with field descriptions and time conversion rules.

Commands

List all projects

python3 -c "
import json, os
base = os.path.expanduser('~/Movies/CapCut/User Data/Projects/com.lveditor.draft')
with open(os.path.join(base, 'root_meta_info.json')) as f:
    data = json.load(f)
for d in data['all_draft_store']:
    name = d.get('draft_name', d['draft_fold_path'].split('/')[-1])
    dur = d.get('tm_duration', 0)
    secs = dur / 1000000 if dur else 0
    print(f'{name:30s}  {secs:>6.1f}s  {d[\"draft_fold_path\"]}')
"

Check if a project is readable (not encrypted)

python3 -c "
import json, os
base = os.path.expanduser('~/Movies/CapCut/User Data/Projects/com.lveditor.draft')
path = os.path.join(base, 'PROJECT_NAME', 'draft_info.json')
try:
    with open(path) as f: json.load(f)
    print('READABLE (plain JSON)')
except:
    print('ENCRYPTED (template project β€” cannot modify)')
"

Read project summary

python3 -c "
import json, os
base = os.path.expanduser('~/Movies/CapCut/User Data/Projects/com.lveditor.draft')
with open(os.path.join(base, 'PROJECT_NAME', 'draft_info.json')) as f:
    d = json.load(f)
print(f'ID: {d[\"id\"]}')
print(f'Duration: {d[\"duration\"]/1000000:.1f}s')
print(f'FPS: {d[\"fps\"]}')
print(f'Canvas: {d[\"canvas_config\"][\"width\"]}x{d[\"canvas_config\"][\"height\"]}')
tracks = d.get('tracks', [])
print(f'Tracks: {len(tracks)}')
for i, t in enumerate(tracks):
    segs = t.get('segments', [])
    print(f'  Track {i}: {t[\"type\"]} ({len(segs)} segments)')
mats = d.get('materials', {})
for k, v in mats.items():
    if isinstance(v, list) and len(v) > 0:
        print(f'  materials.{k}: {len(v)}')
"

Create a new project from scratch

python3 scripts/create-project.py <name> [--width 1920] [--height 1080] [--fps 30]

View and edit clips

# Show full timeline overview (tracks, segments, timing, transforms)
python3 scripts/edit-clips.py <project> --info

# Scale clips (1.0 = 100%, 1.5 = 150%)
python3 scripts/edit-clips.py <project> --scale 1.2 --clips 0 5 10
python3 scripts/edit-clips.py <project> --scale 1.1 --range 3-19

# Speed (0.5 = half speed, 2.0 = double)
python3 scripts/edit-clips.py <project> --speed 2.0 --clips 5
python3 scripts/edit-clips.py <project> --speed 0.5 --all

# Volume (0.0 = mute, 1.0 = normal, 2.0 = double)
python3 scripts/edit-clips.py <project> --volume 0 --range 0-2
python3 scripts/edit-clips.py <project> --mute --all

# Rotate/flip/opacity
python3 scripts/edit-clips.py <project> --rotate 90 --clips 2
python3 scripts/edit-clips.py <project> --flip-h --clips 3
python3 scripts/edit-clips.py <project> --opacity 0.8 --clips 0

# Trim clip to first N seconds
python3 scripts/edit-clips.py <project> --trim 3.5 --clips 0

# Split clip at position (seconds from clip start)
python3 scripts/edit-clips.py <project> --split 2.5 --clips 0

# Remove clips
python3 scripts/edit-clips.py <project> --remove --clips 10 11 12

# Move clip from index to index
python3 scripts/edit-clips.py <project> --move 5 --to 10

# Reset transforms
python3 scripts/edit-clips.py <project> --reset --clips 0 1 2

Edit audio tracks

# Audio tracks shown in --info output
python3 scripts/edit-clips.py <project> --info

# Volume (0.0=mute, 1.0=normal)
python3 scripts/edit-clips.py <project> --audio-track --volume 0.5 --all
python3 scripts/edit-clips.py <project> --audio-track --mute --clips 0

# Move audio segment to align with video (seconds from timeline start)
python3 scripts/edit-clips.py <project> --audio-track --position 10.5 --clips 0

# Select which audio track (0=first, 1=second, etc.)
python3 scripts/edit-clips.py <project> --audio-track --track-index 2 --volume 0.8 --all

# Speed up/slow down audio
python3 scripts/edit-clips.py <project> --audio-track --speed 1.5 --clips 0

# Trim audio to first N seconds
python3 scripts/edit-clips.py <project> --audio-track --trim 5.0 --clips 0

# Remove audio segment
python3 scripts/edit-clips.py <project> --audio-track --remove --clips 2

# Fade in/out (seconds)
python3 scripts/edit-clips.py <project> --audio-track --fade-in 0.5 --clips 0
python3 scripts/edit-clips.py <project> --audio-track --fade-out 1.0 --clips 0

Add video effects (zoom, blur, sparkle, etc.)

# List built-in effect shortcuts (20 effects)
python3 scripts/add-effect.py --list-effects

# Scan ALL cached effects (404+ downloaded effects)
python3 scripts/add-effect.py --scan-cache

# Add Zoom Lens to all clips
python3 scripts/add-effect.py <project> --effect zoom-lens --all

# Add Sparkle to specific clips
python3 scripts/add-effect.py <project> --effect sparkle --clips 0 5 10

# Add any cached effect by ID (from --scan-cache)
python3 scripts/add-effect.py <project> --effect-id 7399469087174233349 --all

# Add Blur to a range with custom parameters
python3 scripts/add-effect.py <project> --effect blur --range 3-19 --speed 0.02 --param-range 0.06

# Remove all effects
python3 scripts/add-effect.py <project> --clear

Add transitions between clips

# List available transitions
python3 scripts/add-transition.py --list

# Add transition between two adjacent clips
python3 scripts/add-transition.py <project> --between 19 20

# Add transitions between all consecutive clips
python3 scripts/add-transition.py <project> --all-gaps

# Add at specific gaps with custom duration
python3 scripts/add-transition.py <project> --gaps 2 5 10 --duration 0.5

# Remove all transitions
python3 scripts/add-transition.py <project> --clear

Add color grading (HSL)

# Show current color settings per clip
python3 scripts/add-color.py <project> --info

# Custom HSL values (hue: -180~180, sat: -100~100, light: -100~100)
python3 scripts/add-color.py <project> --hsl --hue 10 --saturation 20 --lightness -5 --clips 0 1 2
python3 scripts/add-color.py <project> --hsl --saturation 30 --all

# Presets
python3 scripts/add-color.py <project> --preset warm --all
python3 scripts/add-color.py <project> --preset vivid --range 0-10
python3 scripts/add-color.py <project> --preset moody --all

# Available presets: warm, cool, vivid, moody, vintage, fade
# Remove all color grading
python3 scripts/add-color.py <project> --clear

Add text overlays

# Single text
python3 scripts/add-text.py <project> --text "Hello" --start 0 --duration 5 --y -0.65 --size 14

# With a specific font
python3 scripts/add-text.py <project> --text "ΰΈͺΰΈ§ΰΈ±ΰΈͺΰΈ”ΰΈ΅" --font sarabun-bold --size 14
python3 scripts/add-text.py <project> --text "Hello" --font /full/path/to/font.ttf

# List available font aliases
python3 scripts/add-text.py --list-fonts

# From a JSON file
python3 scripts/add-text.py <project> --from-file texts.json

# Remove all text
python3 scripts/add-text.py <project> --clear

JSON file format (supports font field):

[
  {"text": "Opening title", "start": 0, "duration": 4.5, "y": -0.65, "size": 14, "font": "thai"},
  {"text": "Subtitle", "start": 5.4, "duration": 2, "y": 0.7, "size": 10, "font": "sarabun-bold"}
]

Built-in font aliases: default, en, th/thai, noto, noto-thai, capcut/capcut-bold/capcut-medium, sarabun/sarabun-bold/sarabun-light/sarabun-med/sarabun-thin, thsarabun/thsarabun-new, db-helv-med/db-helv-bold. Or pass any full path to a .ttf/.otf file.

Create project manually

The create-project.py script handles everything. Alternatively, create manually:

  1. Generate 3 separate UUIDs: project_json_id, timeline_id, draft_id
  2. Create the folder structure (22 files total β€” see below)
  3. CapCut MUST be closed β€” it overwrites root_meta_info.json on launch

Required file structure (22 files)

PROJECT_NAME/
β”œβ”€β”€ draft_info.json                          # Main project data
β”œβ”€β”€ draft_info.json.bak                      # Backup (identical to draft_info.json)
β”œβ”€β”€ draft_meta_info.json                     # Project metadata (plain JSON)
β”œβ”€β”€ draft_settings                           # INI format settings
β”œβ”€β”€ draft_agency_config.json                 # Resolution/agency config
β”œβ”€β”€ draft_biz_config.json                    # Empty file (0 bytes)
β”œβ”€β”€ performance_opt_info.json                # Performance optimization
β”œβ”€β”€ timeline_layout.json                     # UI layout with timeline UUID
β”œβ”€β”€ attachment_pc_common.json                # PC-specific attachments
β”œβ”€β”€ template-2.tmp                           # Full draft_info copy
β”œβ”€β”€ common_attachment/
β”‚   └── attachment_pc_timeline.json          # Timeline reference lines config
└── Timelines/
    β”œβ”€β”€ project.json                         # Timeline registry (separate id!)
    β”œβ”€β”€ project.json.bak                     # Backup
    └── {timeline_id}/                       # Named with timeline_id UUID
        β”œβ”€β”€ draft_info.json                  # Same content as root draft_info.json
        β”œβ”€β”€ draft_info.json.bak
        β”œβ”€β”€ attachment_editing.json          # Edit state
        β”œβ”€β”€ attachment_pc_common.json        # Same as root level
        β”œβ”€β”€ template.tmp                     # Draft info with canvas w/h = 0
        β”œβ”€β”€ template-2.tmp                   # Full draft_info copy
        └── common_attachment/
            β”œβ”€β”€ attachment_action_scene.json
            β”œβ”€β”€ attachment_gen_ai_info.json
            β”œβ”€β”€ attachment_pc_timeline.json
            └── attachment_script_video.json

UUID scheme

CapCut uses 3 separate UUIDs for each project:

UUIDUsed inNotes
project_json_idTimelines/project.json β†’ idSeparate from timeline
timeline_idTimelines/project.json β†’ main_timeline_id, draft_info.json β†’ id, timeline folder nameThe primary project UUID
draft_idroot_meta_info.json β†’ draft_id, draft_meta_info.json β†’ draft_idRegistry identifier

Key parameters

  • canvas_config.width/height β€” 1080Γ—1920 for 9:16 portrait, 1920Γ—1080 for 16:9 landscape
  • fps β€” 30.0 (standard)
  • create_time / update_time β€” microseconds (0 for blank project)
  • tm_draft_create / tm_draft_modified β€” microseconds (Unix timestamp Γ— 1,000,000)

Register in root_meta_info.json

When creating a project, add an entry to all_draft_store in root_meta_info.json:

import json, os, uuid, time

BASE = os.path.expanduser("~/Movies/CapCut/User Data/Projects/com.lveditor.draft")
dst = os.path.join(BASE, "PROJECT_NAME")
meta_path = os.path.join(BASE, "root_meta_info.json")

with open(meta_path) as f:
    meta = json.load(f)

meta["all_draft_store"].insert(0, {
    "cloud_draft_cover": False,
    "cloud_draft_sync": False,
    "draft_cloud_last_action_download": False,
    "draft_cloud_purchase_info": "",
    "draft_cloud_template_id": "",
    "draft_cloud_tutorial_info": "",
    "draft_cloud_videocut_purchase_info": "",
    "draft_cover": os.path.join(dst, "draft_cover.jpg"),
    "draft_fold_path": dst,
    "draft_id": str(uuid.uuid4()).upper(),
    "draft_is_ai_shorts": False,
    "draft_is_cloud_temp_draft": False,
    "draft_is_invisible": False,
    "draft_is_web_article_video": False,
    "draft_json_file": os.path.join(dst, "draft_info.json"),
    "draft_name": "PROJECT_NAME",
    "draft_new_version": "",
    "draft_root_path": BASE,
    "draft_timeline_materials_size": 4106,
    "draft_type": "",
    "draft_web_article_video_enter_from": "",
    "streaming_edit_draft_ready": True,
    "tm_draft_cloud_completed": "",
    "tm_draft_cloud_entry_id": -1,
    "tm_draft_cloud_modified": 0,
    "tm_draft_cloud_parent_entry_id": -1,
    "tm_draft_cloud_space_id": -1,
    "tm_draft_cloud_user_id": -1,
    "tm_draft_create": int(time.time() * 1_000_000),
    "tm_draft_modified": int(time.time() * 1_000_000),
    "tm_draft_removed": 0,
    "tm_duration": 0,
})

if isinstance(meta.get("draft_ids"), int):
    meta["draft_ids"] += 1

with open(meta_path, "w") as f:
    json.dump(meta, f, separators=(",", ":"))

Add a video clip to a project

  1. Generate UUIDs for: segment, video material, canvas material, speed material, placeholder_info, sound_channel_mapping, vocal_separation, material_color
  2. Add a video material to materials.videos with the video file path and metadata
  3. Add a canvas_color material to materials.canvases
  4. Add entries to materials.speeds, materials.placeholder_infos, materials.sound_channel_mappings, materials.vocal_separations, materials.material_colors
  5. Add a segment to the target video track with:
    • material_id β†’ video material UUID
    • extra_material_refs β†’ [speed_uuid, canvas_uuid, placeholder_uuid, sound_ch_uuid, vocal_sep_uuid, material_color_uuid]
    • target_timerange β†’ { start: cumulative position, duration: clip length in ΞΌs }
    • source_timerange β†’ { start: 0, duration: clip length in ΞΌs }
  6. Update duration at the project root level

Add an audio track

  1. Generate UUIDs for: segment, audio material, audio_fade, beats, speed, placeholder_info, sound_channel_mapping, vocal_separation
  2. Add audio material to materials.audios
  3. Add entries to materials.audio_fades, materials.beats, materials.speeds, materials.placeholder_infos, materials.sound_channel_mappings, materials.vocal_separations
  4. Either add a new track of type: "audio" or add segment to existing audio track
  5. Segment clip should be null for audio

Add a text overlay

  1. Generate UUID for: segment and text material
  2. Add text material to materials.texts with:
    • content β€” JSON-encoded string with text and styles array
    • font_size, text_color, alignment, line_max_width
  3. Add segment to a type: "text" track (create one if needed)
    • source_timerange should be null for text
    • clip.transform.y controls vertical position (e.g., 0.7 = lower third)

Text content field format

import json
content = json.dumps({
    "text": "Your text here",
    "styles": [{
        "fill": {
            "content": {
                "solid": {"color": [1.0, 1.0, 1.0]},
                "render_type": "solid"
            }
        },
        "range": [0, len("Your text here")],
        "strokes": [{
            "width": 0.06,
            "content": {
                "solid": {"color": [0, 0, 0]},
                "render_type": "solid"
            }
        }],
        "size": 15,
        "font": {
            "path": "/Applications/CapCut.app/Contents/Resources/Font/SystemFont/en.ttf",
            "id": ""
        }
    }]
})

Colors in styles are [R, G, B] normalized 0.0-1.0 (not hex).

Add video effects (zoom, blur, sparkle, etc.)

Video effects are placed on type: "effect" tracks and reference entries in materials.video_effects. Each segment on an effect track matches the timing of a video clip it applies to. Multiple effects can share one track via separate segments.

Structure:

  1. Create a video_effect material in materials.video_effects
  2. Create a segment on an effect track with target_timerange matching the video clip
  3. The segment's material_id points to the video_effect entry
import uuid, os

CACHE_BASE = "/Users/admin/Library/Containers/com.lemon.lvoverseas/Data/Movies/CapCut/User Data/Cache/effect"

def gen_uuid():
    return str(uuid.uuid4()).upper()

# Create a video effect material
effect_id = "7399465441057328389"  # Zoom Lens
effect_hash = "60a68556b7df52cc36d20d1f565b4569"
effect_path = os.path.join(CACHE_BASE, effect_id, effect_hash)

mat_id = gen_uuid()
video_effect = {
    "id": mat_id,
    "effect_id": effect_id,
    "resource_id": effect_id,
    "name": "Zoom Lens",
    "type": "video_effect",
    "path": effect_path,
    "adjust_params": [
        {"name": "effects_adjust_speed", "value": 0.01, "default_value": 0.33},
        {"name": "effects_adjust_range", "value": 0.07, "default_value": 0.3}
    ],
    "category_id": "27296",
    "category_name": "Trending",
    "apply_target_type": 2,
    "platform": "all",
    "source_platform": 1,
    "effect_type": 0,
    "is_ai_generate_effect": False,
    "is_third_party": False,
    "meta": "",
    "request_id": "",
    "resource_name": "",
    "sub_effects": [],
    "track_id": "",
}

# Add to materials
draft["materials"]["video_effects"].append(video_effect)

# Create effect segment (timing matches video clip)
effect_segment = {
    "id": gen_uuid(),
    "material_id": mat_id,
    "target_timerange": {"start": clip_start_us, "duration": clip_duration_us},
    "source_timerange": None,
    "render_timerange": None,
    "clip": None,
    "speed": 1.0,
    "volume": 1.0,
    "visible": True,
    "extra_material_refs": [],
    "render_index": 11001,
    "keyframe_refs": {},
    "common_keyframes": {},
    "group_id": "",
    "track_render_index": 2,
    "is_placeholder": False,
}

# Add to an existing effect track, or create one
effect_track = {
    "id": gen_uuid(),
    "type": "effect",
    "flag": 0,
    "attribute": 0,
    "name": "",
    "is_default_name": True,
    "segments": [effect_segment],
}
draft["tracks"].append(effect_track)

Key notes:

  • Effect segments have clip: null, source_timerange: null, extra_material_refs: []
  • Multiple effect segments for the same effect can go on a single effect track
  • render_index: 11001 and track_render_index: 2 are typical values (from observed projects)
  • Each effect needs its own video_effect material (separate UUID) even if using the same effect_id
  • Effect files must exist in the CapCut cache (user must have used the effect at least once)

Discover available effects from cache

# Scan all 404+ cached video effects
python3 scripts/add-effect.py --scan-cache

Or manually scan the cache:

python3 -c "
import os, json
CACHE = '/Users/admin/Library/Containers/com.lemon.lvoverseas/Data/Movies/CapCut/User Data/Cache/effect'
for eid in sorted(os.listdir(CACHE)):
    epath = os.path.join(CACHE, eid)
    if not os.path.isdir(epath): continue
    for h in os.listdir(epath):
        hpath = os.path.join(epath, h)
        config = os.path.join(hpath, 'config.json')
        if os.path.exists(config):
            try:
                with open(config) as f: data = json.load(f)
                name = data.get('name', '')
                if name: print(f'{eid:>20s}  {h}  {name}')
            except: pass
"

Get effect info from an existing project

If a user has applied an effect in CapCut, extract the full structure:

python3 -c "
import json, os
base = os.path.expanduser('~/Movies/CapCut/User Data/Projects/com.lveditor.draft')
with open(os.path.join(base, 'PROJECT_NAME', 'draft_info.json')) as f:
    d = json.load(f)
for ve in d['materials'].get('video_effects', []):
    print(f'  {ve[\"name\"]:30s}  effect_id={ve[\"effect_id\"]}')
    print(f'    adjust_params={ve.get(\"adjust_params\", [])}')
    print(f'    path={ve.get(\"path\", \"\")}')
"

Built-in effect reference

These effects are commonly cached after using CapCut. The effect_id is the identifier used in video_effect.effect_id and the cache directory name. The hash is the subdirectory inside the effect's cache folder.

Effecteffect_idhashadjust_params
Zoom Lens739946544105732838960a68556b7df52cc36d20d1f565b4569speed, range
Slow Zoom739946896194912589456b46f1ebc45c47730d3f7c2569200fcβ€”
Slight Zoom7399463624906984709c09004507723569a3e762494d4ffda7dβ€”
Full Zoom739947080875981542924749b428adbacfa9712b8a249912905β€”
Zoom Far67242263384183321678d97f1c1a60d9393c97ff4e9da0669aeβ€”
Blur73994649298304238132db7bf49d9349e308ef0f46c39b14abfβ€”
Blur Opening73994688863091622465dd4bf7e879fe7356e3e27e5105f5af1β€”
Soft Light7399467970071743749258b5bd7ba1fb94dce800bc496a30ed9β€”
Bokeh73994708838630883891c8442102d00628a4958e488251a75e7β€”
Dark Corner7399463239379209477ef7abad9671e2f3da7993b7673ece5fcβ€”
Sparkle7399469087174233349816803366dd866837e21380513b81e33β€”
Flashing Light7399472112223669510d7c42c303074967c0cad7c7a6adfe896β€”
Color Shift739947016020310758953c8584c8174f887b2802540dd28955bβ€”
Fog7399471802361105669bdda3043cfa04aa56d2806ada93367aeβ€”
Oil Paint11353735118b5e6a07a603581825a0fa8bb08e35β€”
Rainbow Bubble7399470727121947910ae2e32daa7af0fa8f4b61a0c5aacd196β€”
Petals Falling7399464130664500486a772c059e7fb8304292e7ebb870e8eb3β€”
Curtain Close739946849904468301305c17ac3298c0521cd91a720850a27deβ€”
Star Shift7399470054053547270f1c6583c2a7227b6ccf002863fdfdf65β€”

Note: Effect availability depends on what the user has downloaded/used in CapCut. Always verify the cache path exists before using an effect. If an effect isn't cached, ask the user to apply it once in CapCut, then close CapCut and re-read the project.

Time System

All time values in CapCut are microseconds (ΞΌs).

1 second = 1,000,000 ΞΌs
30fps frame β‰ˆ 33,333 ΞΌs

UUID Generation

import uuid
str(uuid.uuid4()).upper()
# Example: "595F710B-F0C1-44C6-AA60-525CF85705D5"

Safety

  • CapCut MUST be closed when creating/modifying projects β€” it overwrites files on launch
  • Always back up draft_info.json before modifying
  • Always back up root_meta_info.json before adding/removing projects
  • To check if CapCut is running: pgrep -fl "CapCut|lvoverseas|lemon"

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.