kdenlive
README.md
# kdenlive-editing-mcp
Scripted video editing with **Kdenlive 26.08.1 / MLT 7.41.0**, driven from
Python, with an MCP server on top. Built because a `.kdenlive` file is an
undocumented internal serialisation and the toolchain around it fails silently —
wrong frame rate, missing audio, clips in the wrong place — rather than with an
error.
Pointed at a folder of footage, it writes a project you can open and start
cutting, then proves the result is right by rendering frames back out of the
finished timeline and comparing them against the source.
> Verified against Kdenlive **26.08.1**, MLT **7.41.0**, document version
> **1.1**. The project format is not a public API and changes between releases;
> treat that as a version pin.
## Layout
| path | what |
|---|---|
| `bin/melt-run` | runs melt on a project with the silent traps guarded, and real exit codes |
| `bin/kdenlive-mcp` | starts the MCP server on stdio |
| `lib/kdenlivelib.py` | probe media, transcode to CFR, build the project XML |
| `lib/verify.py` | render frames back out of a timeline and compare them to their sources |
| `lib/mcp_server.py` | the MCP tools |
| `scripts/stringout.py` | worked example: N seconds of every clip in a folder, on one timeline |
| `CLAUDE.md` | the verified format notes and the traps, with measurements |
## Usage
```sh
scripts/stringout.py raw-videos/ -s 1.5 -o assembly.kdenlive
scripts/stringout.py raw-videos/ --transcode proxies/ --verify
```
A stringout is every take in order, trimmed to a glance — a starting point to
cut against rather than a finished edit. Clips land on V1 with their audio on
A1, grouped so they move together; the bin keeps every clip at full length, so
any slice can be pulled longer by hand afterwards.
From Python:
```python
from kdenlivelib import Profile, Project
from verify import verify_project
p = Project(Profile.hd1080p60())
for clip in clips:
p.append(clip, seconds=1.5)
p.save("assembly.kdenlive")
for r in verify_project("assembly.kdenlive"):
print(r.name, r.verdict) # match / MISMATCH
```
## MCP server
`bin/kdenlive-mcp` speaks stdio. Point a client at it:
```json
{"mcpServers": {"kdenlive": {"command": "/path/to/kdenlive-editing-mcp/bin/kdenlive-mcp"}}}
```
| tool | what |
|---|---|
| `probe_media` | duration, geometry, frame rate, rotation, audio, VFR |
| `scan_folder` | inventory a folder, flagging VFR clips and byte-identical duplicates |
| `transcode_to_cfr` | constant-frame-rate proxy, rotation preserved |
| `build_stringout` | assemble a timeline from files or folders |
| `verify_project` | render each clip's midpoint back out and compare to source |
| `check_audio` | measure the timeline's audio level over a window |
| `render_project` | render to a video file, frame rate handled safely |
The tools are coarse on purpose. The useful unit of work is a whole project
file; an agent placing clips one call at a time would get the frame arithmetic
wrong in exactly the ways `CLAUDE.md` describes, and nothing would report it.
## Why the wrapper
- Passing `width=`/`height=` to an MLT consumer **silently resets the output
frame rate to 25**. The render succeeds with the right number of frames,
stamped at the wrong rate, so the file reports the wrong duration and plays at
the wrong speed.
- Kdenlive is a Flatpak, and a Flatpak has its **own private `/tmp`**. Give melt
an output path there and it writes nothing and **exits 0**.
- A failing render still needs to look like a failure to the shell.
`CLAUDE.md` has the rest — memory that scales with clip count rather than
timeline length, why a near-lossless proxy is slower to edit with than the phone
original, and why the obvious VFR test misses real VFR footage. Each item was
measured, not assumed.
## Requirements
Kdenlive 26.08.1 as a Flatpak (`org.kde.kdenlive`, which supplies `melt`) or a
host MLT, plus ffmpeg/ffprobe and Python 3.10+. The MCP server needs `mcp`
(`pip install -e .`). Use absolute paths: the sandbox's working directory is not
yours.
## Note on metadata
Phone video carries creation timestamps and frequently GPS coordinates, and
`to_cfr()` does not strip them — an edit proxy is meant to match its source.
Projects also embed absolute paths into your home directory. `.gitignore` keeps
media and `.kdenlive` files out of the repo for that reason; strip metadata
before publishing renders.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues