Describe an edit once. Open it in Final Cut, Resolve, Premiere, Kdenlive, Shotcut, Blender or CapCut.
How it works Β· Editors Β· Features Β· AI agent Β· Quick start Β· API Β· Open in editor Β· Voiceover Β· Testing Β· FAQ Β· Credits
tl = Timeline("demo")
tl.add_clip("interview.mp4", dur=8, src=12, fade_in=0.5)
tl.add_clip("broll.mp4", dur=5, crossfade=1)
tl.add_audio("music.mp3", volume=0.4, fade_out=2)
tl.add_title("Opening scene", start=1, dur=3, fade=0.5)
export(tl, "fcp", "out") # out/demo.fcpxmlflowchart LR
A[Your footage<br/>+ what you want] --> B[You, a script,<br/>or an AI agent]
B --> C[(Timeline<br/>plain JSON)]
C --> D1[Final Cut]
C --> D2[Resolve]
C --> D3[Premiere]
C --> D4[Kdenlive / Shotcut]
C --> D5[Blender]
C --> D6[CapCut]
D1 & D2 & D3 & D4 & D5 & D6 --> E[Open, polish, render]
Your footage is never touched. The project file only says which part of which file plays where. You or your agent decide the cuts. This project writes the project file.
| Editor | Output | Tested |
|---|---|---|
| Final Cut Pro | .fcpxml |
π‘ |
| DaVinci Resolve | .fcpxml + import script |
π‘ |
| Premiere Pro | .xml + .srt |
π‘ |
| Kdenlive / Shotcut | .mlt |
π’ |
| Blender | .py |
π’ |
| CapCut | draft folder | π‘ |
π’ rendered and measured Β π‘ file checked, not opened in the app. iMovie and Filmora can't import a timeline, so they're not supported.
mindmap
root((AI Editor))
Video
Trim and sequence
Overlay tracks
Still images
Speed
Opacity
Fades
Crossfade
Zoom keyframes
Audio
Multiple lanes
Volume
Fades
Voiceover
Text
Titles
Colour, size, position
SRT export
Project
Resolution and fps
Save / load JSON
CLI and Python
Every editor supports every feature. A test fails if one doesn't.
| Feature | Final Cut | Resolve | Premiere | Kdenlive / Shotcut | Blender | CapCut |
|---|---|---|---|---|---|---|
| Trim, sequence, tracks | β | β | β | β | β | β |
| Volume, speed, opacity | β | β | β | β | β | β |
| Fades, crossfade | β | β | β | β | β | β ΒΉ |
| Titles | β | β | β | β | β | β |
| Zoom keyframes | β | β | β | β | β | β |
| Still images | β | β | β | β | β | β |
ΒΉ Pictures only. For audio, use separate tracks with fade_in/fade_out.
Not included: effects, filters, colour grading, other transitions (each editor has its own), font choice and text animation, and reading existing projects. Media is referenced by absolute path, so moving it means re-linking in the editor.
Not a chat app, but any coding agent (Claude Code, Codex, Cursor) can drive it, because an edit is just a few lines of Python or a JSON file.
sequenceDiagram
actor You
participant Agent as AI agent
participant AE as AI Editor
participant Ed as Your editor
You->>Agent: "30 s highlight, title, music, export for Premiere"
Agent->>Agent: look at footage (ffprobe, frames, transcript)
Agent->>AE: write Timeline
AE->>Ed: project file
You->>Ed: review, adjust, render
The agent can only cut on what it can see or hear. Give it a transcript or notes for best results.
Try saying
"Cut
~/footage/trip.mp4to 30 seconds with a title, and export for Premiere." "Addsong.mp3quietly under the clips and dissolve between them." "Zoom in on each click at 3.2 s, 7.8 s and 12.5 s, then export for Resolve."
Not built yet: a ready-made agent instruction file, and helpers for transcription and scene detection.
Needs: Python 3.10+, ffmpeg (brew install ffmpeg). The editor is only needed to open the result.
git clone https://github.com/zandy700/AI-Editor.git && cd AI-Editor
python3 -m venv .venv && source .venv/bin/activate
pip install -e . # core
pip install -e '.[narrate,capcut]' # optional: voiceover, CapCutMake two test clips, then export:
ffmpeg -f lavfi -i testsrc=duration=10:size=1280x720:rate=30 -f lavfi -i sine=duration=10 -shortest -pix_fmt yuv420p a.mp4
ffmpeg -f lavfi -i smptebars=duration=10:size=1280x720:rate=30 -f lavfi -i sine=frequency=330:duration=10 -shortest -pix_fmt yuv420p b.mp4from editor import Timeline
from editor.export import export
tl = Timeline("demo")
tl.add_clip("a.mp4", dur=4, src=2, fade_in=0.5)
tl.add_clip("b.mp4", dur=4, crossfade=1)
tl.add_title("Hello", start=1, dur=2, fade=0.5)
export(tl, "fcp", "out") # or premiere, resolve, kdenlive, shotcut, blender, capcutCommand line, from a saved timeline:
python -m editor export timeline.json fcp premiere -o out
python -m editor export timeline.json all -o outTimeline(name="Edit", width=1920, height=1080, fps=30). All times are in seconds.
track 1 (overlay) βββββ watermark ββββββββββββββββββββββ
track 0 (main) βββ a.mp4 βββββββ²β±ββββ b.mp4 βββββββββ
β crossfade overlap
title [ Hello ]
audio 0 βͺβͺβͺβͺβͺβͺβͺβͺβͺβͺ music.mp3 βͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺβͺ
time 0s ββββββββββββββββββββββββββββββββββββββββββββββββΆ
| Method | Does |
|---|---|
add_clip(path, dur, src, start, track, **opts) |
Video or still image |
add_audio(path, dur, src, start, track, **opts) |
Audio on a lane |
add_title(text, start, dur, **opts) |
On-screen text |
save(path) / Timeline.load(path) |
JSON round trip |
srt() |
Titles as subtitles |
Defaults that keep scripts short: start is the end of the track, so repeated add_clip calls chain; dur is the rest of the file (stills need dur); track=0 is the main video.
| Clip option | Default | Meaning |
|---|---|---|
src |
0 | Trim: in-point in the file |
volume |
1.0 | Sound level |
speed |
1.0 | 2.0 = twice as fast. dur is the length on the timeline, so a 2Γ clip with dur=2 uses 4 s of the file |
opacity |
1.0 | Picture transparency |
fade_in / fade_out |
0 | Seconds. Picture fades from/to black (transparent on overlays) and sound from/to silence |
crossfade |
0 | Seconds. Overlaps the previous clip on its track and dissolves across the overlap. start is set for you (previous end β crossfade), so the timeline gets shorter. Main video track only |
zoom |
[] | [(seconds_into_clip, scale), β¦], 1.0 = no zoom. Usually set by apply_click_zoom |
track |
0 | Video: 0 = main, 1+ = overlays on top. Audio: lane number |
| Title option | Default | Meaning |
|---|---|---|
y |
-0.8 | -1 bottom to 1 top (default is subtitle position) |
size |
60 | Pixels at 1080p |
color |
#ffffff |
Hex |
fade |
0 | Fade in/out seconds |
Click zoom for screen recordings:
from editor.zoom import apply_click_zoom
rec = tl.add_clip("screen.mp4")
apply_click_zoom(rec, [3.2, 7.8, 12.5], scale=1.5) # times in seconds from clip start, or a JSON file of timesscale=1.5 zoom factor, ramp=0.25 seconds to zoom in/out, hold=0.6 seconds to stay zoomed.
JSON (write it from any language, or have an LLM produce it). tl.save("timeline.json") and Timeline.load(...) round-trip the whole edit. Use absolute paths. start is required on every clip in JSON.
{
"name": "demo", "width": 1920, "height": 1080, "fps": 30,
"clips": [
{"path": "/abs/path/a.mp4", "start": 0, "dur": 4, "src": 2, "kind": "video", "track": 0,
"volume": 1.0, "speed": 1.0, "opacity": 1.0, "fade_in": 0.5, "fade_out": 0.0,
"crossfade": 0.0, "zoom": []},
{"path": "/abs/path/music.mp3", "start": 0, "dur": 8, "src": 0, "kind": "audio", "track": 0,
"volume": 0.5, "speed": 1.0, "opacity": 1.0, "fade_in": 1.0, "fade_out": 2.0,
"crossfade": 0.0, "zoom": []}
],
"titles": [
{"text": "Hello", "start": 1, "dur": 2, "y": -0.8, "size": 60, "fade": 0.5, "color": "#ffffff"}
]
}| Editor | Files | How |
|---|---|---|
| Final Cut | Name.fcpxml |
File β Import β XML. A new event named after the timeline appears |
| Resolve | Name.fcpxml, Name.resolve.py |
File β Import β Timeline, or run the script (below) |
| Premiere | Name.premiere.xml, Name.srt |
File β Import. If you have titles, import the .srt and drag it on as captions, in case Premiere drops the XML titles |
| Shotcut | Name.mlt |
File β Open File |
| Kdenlive | Name.mlt |
File β Open. If it refuses, open in Shotcut or render with melt |
| Blender | Name.blender.py |
See below |
| CapCut | folder Name/ |
Export into CapCut's drafts folder, restart CapCut |
Resolve script, with Resolve running:
python3 out/Name.resolve.py # import the timeline
python3 out/Name.resolve.py --render ~/Movies # import, then queue and run a renderEnable DaVinci Resolve β Preferences β System β General β External scripting using: Local. Some versions limit scripting to Studio. Importing the .fcpxml by hand always works.
Blender (5.x tested):
blender -b --python out/Name.blender.py # build only
blender -b --python out/Name.blender.py -- --render video.mp4 # build and rendermacOS executable: /Applications/Blender.app/Contents/MacOS/Blender. Run without -b to edit by hand in the Video Editing workspace.
CapCut (pip install -e '.[capcut]'). Pass the full drafts path:
export(tl, "capcut", "/Users/you/Movies/CapCut/User Data/Projects/com.lveditor.draft") # macOS
export(tl, "capcut", r"C:\Users\you\AppData\Local\CapCut\User Data\Projects\com.lveditor.draft") # WindowsThe location differs between versions, and newer versions may encrypt drafts and refuse them.
No editor at all: render the MLT file with melt (brew install mlt):
melt out/Name.mlt -consumer avformat:video.mp4 vcodec=libx264 acodec=aacfrom editor.narrate import narrate
narrate(tl, "Welcome back. Today we cut a short film.")
narrate(tl, "θΏζ―δΈδΈͺδΈζε₯εγ", start=12) # Chinese picks the Chinese voiceEach sentence is spoken, placed on the timeline, and gets a subtitle of the same length.
| Option | Default |
|---|---|
voice |
en-US-AriaNeural |
zh_voice |
zh-CN-XiaoxiaoNeural |
start |
after existing audio |
gap |
0.1 s between sentences |
out_dir |
temp folder. Pass one you keep, the exported project points at those mp3s |
Needs pip install -e '.[narrate]' and internet. It uses edge-tts, which calls Microsoft's read-aloud service. That service is unofficial and could change or stop, so don't build anything critical on it.
| Target | Checked by |
|---|---|
| π’ Kdenlive / Shotcut | Rendered with melt; colours, timing, titles, audio levels measured |
| π’ Blender | Rendered headless in Blender 5.2; same measurements |
| π‘ Final Cut, Resolve, Premiere, CapCut | Valid file, references resolve, times frame-aligned. Never opened in the real app |
Not opened in the real apps means some details are unconfirmed: Final Cut's title template ID and timeMap, Premiere's effect IDs and text generator. Resolve may ignore speed changes, keyframed fades or title styling. If an export looks wrong, open an issue with the editor, version, screenshot and your timeline .json.
pip install -e '.[narrate,capcut]'
for t in tests/test_*.py; do python "$t"; doneeditor/
timeline.py model + media probing
narrate.py voiceover + subtitles
zoom.py click zoom
export/ fcpxml, resolve, premiere, mlt, blender, capcut
tests/ one file per exporter, plus features.py (shared full-feature timeline)
Add an editor: write editor/export/<name>.py with SUPPORTS = set(FEATURES) and export(timeline, out_dir), register it in editor/export/__init__.py, add a test.
- Can't see the project? Import the file as shown in Open in your editor. For CapCut, restart the app.
- Footage missing? Paths are absolute. Re-link if you moved it.
- Looks wrong? Open an issue (see Testing).
This project was inspired by jianying-editor-skill, an AI skill that builds video edits for JianYing from plain-language requests. Its idea of letting an AI agent write the timeline is what AI Editor carries over to other editors. No code was copied. Thank you to the people who built it:
![]() luoluoluo22 Project author / Maintainer |
![]() twodogegg macOS compatibility / ffprobe fallback / unit tests |
![]() shaozheliu Missing-media fix / self-contained assets |
![]() Maxinsomnia macOS 5.9+ execution support |
The optional CapCut export uses pyJianYingDraft by GuanYixuan (Apache-2.0), installed as a separate dependency.



