Install
$ agentstack add skill-duckyman-ai-agent-skills-capcut-editor ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
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 [--width 1920] [--height 1080] [--fps 30]
View and edit clips
# Show full timeline overview (tracks, segments, timing, transforms)
python3 scripts/edit-clips.py --info
# Scale clips (1.0 = 100%, 1.5 = 150%)
python3 scripts/edit-clips.py --scale 1.2 --clips 0 5 10
python3 scripts/edit-clips.py --scale 1.1 --range 3-19
# Speed (0.5 = half speed, 2.0 = double)
python3 scripts/edit-clips.py --speed 2.0 --clips 5
python3 scripts/edit-clips.py --speed 0.5 --all
# Volume (0.0 = mute, 1.0 = normal, 2.0 = double)
python3 scripts/edit-clips.py --volume 0 --range 0-2
python3 scripts/edit-clips.py --mute --all
# Rotate/flip/opacity
python3 scripts/edit-clips.py --rotate 90 --clips 2
python3 scripts/edit-clips.py --flip-h --clips 3
python3 scripts/edit-clips.py --opacity 0.8 --clips 0
# Trim clip to first N seconds
python3 scripts/edit-clips.py --trim 3.5 --clips 0
# Split clip at position (seconds from clip start)
python3 scripts/edit-clips.py --split 2.5 --clips 0
# Remove clips
python3 scripts/edit-clips.py --remove --clips 10 11 12
# Move clip from index to index
python3 scripts/edit-clips.py --move 5 --to 10
# Reset transforms
python3 scripts/edit-clips.py --reset --clips 0 1 2
Edit audio tracks
# Audio tracks shown in --info output
python3 scripts/edit-clips.py --info
# Volume (0.0=mute, 1.0=normal)
python3 scripts/edit-clips.py --audio-track --volume 0.5 --all
python3 scripts/edit-clips.py --audio-track --mute --clips 0
# Move audio segment to align with video (seconds from timeline start)
python3 scripts/edit-clips.py --audio-track --position 10.5 --clips 0
# Select which audio track (0=first, 1=second, etc.)
python3 scripts/edit-clips.py --audio-track --track-index 2 --volume 0.8 --all
# Speed up/slow down audio
python3 scripts/edit-clips.py --audio-track --speed 1.5 --clips 0
# Trim audio to first N seconds
python3 scripts/edit-clips.py --audio-track --trim 5.0 --clips 0
# Remove audio segment
python3 scripts/edit-clips.py --audio-track --remove --clips 2
# Fade in/out (seconds)
python3 scripts/edit-clips.py --audio-track --fade-in 0.5 --clips 0
python3 scripts/edit-clips.py --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 --effect zoom-lens --all
# Add Sparkle to specific clips
python3 scripts/add-effect.py --effect sparkle --clips 0 5 10
# Add any cached effect by ID (from --scan-cache)
python3 scripts/add-effect.py --effect-id 7399469087174233349 --all
# Add Blur to a range with custom parameters
python3 scripts/add-effect.py --effect blur --range 3-19 --speed 0.02 --param-range 0.06
# Remove all effects
python3 scripts/add-effect.py --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 --between 19 20
# Add transitions between all consecutive clips
python3 scripts/add-transition.py --all-gaps
# Add at specific gaps with custom duration
python3 scripts/add-transition.py --gaps 2 5 10 --duration 0.5
# Remove all transitions
python3 scripts/add-transition.py --clear
Add color grading (HSL)
# Show current color settings per clip
python3 scripts/add-color.py --info
# Custom HSL values (hue: -180~180, sat: -100~100, light: -100~100)
python3 scripts/add-color.py --hsl --hue 10 --saturation 20 --lightness -5 --clips 0 1 2
python3 scripts/add-color.py --hsl --saturation 30 --all
# Presets
python3 scripts/add-color.py --preset warm --all
python3 scripts/add-color.py --preset vivid --range 0-10
python3 scripts/add-color.py --preset moody --all
# Available presets: warm, cool, vivid, moody, vintage, fade
# Remove all color grading
python3 scripts/add-color.py --clear
Add text overlays
# Single text
python3 scripts/add-text.py --text "Hello" --start 0 --duration 5 --y -0.65 --size 14
# With a specific font
python3 scripts/add-text.py --text "สวัสดี" --font sarabun-bold --size 14
python3 scripts/add-text.py --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 --from-file texts.json
# Remove all text
python3 scripts/add-text.py --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:
- Generate 3 separate UUIDs:
project_json_id,timeline_id,draft_id - Create the folder structure (22 files total — see below)
- CapCut MUST be closed — it overwrites
root_meta_info.jsonon 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:
| UUID | Used in | Notes | |------|---------|-------| | project_json_id | Timelines/project.json → id | Separate from timeline | | timeline_id | Timelines/project.json → main_timeline_id, draft_info.json → id, timeline folder name | The primary project UUID | | draft_id | root_meta_info.json → draft_id, draft_meta_info.json → draft_id | Registry identifier |
Key parameters
canvas_config.width/height— 1080×1920 for 9:16 portrait, 1920×1080 for 16:9 landscapefps— 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 rootmetainfo.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
- Generate UUIDs for: segment, video material, canvas material, speed material,
placeholderinfo, soundchannelmapping, vocalseparation, material_color
- Add a
videomaterial tomaterials.videoswith the video file path and metadata - Add a
canvas_colormaterial tomaterials.canvases - Add entries to
materials.speeds,materials.placeholder_infos,
materials.sound_channel_mappings, materials.vocal_separations, materials.material_colors
- Add a segment to the target video track with:
material_id→ video material UUIDextra_material_refs→ [speeduuid, canvasuuid, placeholderuuid, soundch_uuid,
vocalsepuuid, materialcoloruuid]
target_timerange→ { start: cumulative position, duration: clip length in μs }source_timerange→ { start: 0, duration: clip length in μs }
- Update
durationat the project root level
Add an audio track
- Generate UUIDs for: segment, audio material, audio_fade, beats, speed,
placeholderinfo, soundchannelmapping, vocalseparation
- Add
audiomaterial tomaterials.audios - Add entries to
materials.audio_fades,materials.beats,materials.speeds,
materials.placeholder_infos, materials.sound_channel_mappings, materials.vocal_separations
- Either add a new track of
type: "audio"or add segment to existing audio track - Segment
clipshould benullfor audio
Add a text overlay
- Generate UUID for: segment and text material
- Add text material to
materials.textswith:
content— JSON-encoded string withtextandstylesarrayfont_size,text_color,alignment,line_max_width
- Add segment to a
type: "text"track (create one if needed)
source_timerangeshould benullfor textclip.transform.ycontrols 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:
- Create a video_effect material in
materials.video_effects - Create a segment on an
effecttrack withtarget_timerangematching the video clip - The segment's
material_idpoints 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},
{"n
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [duckyman-ai](https://github.com/duckyman-ai)
- **Source:** [duckyman-ai/agent-skills](https://github.com/duckyman-ai/agent-skills)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.