Oct 25, 2026
Edit Video Locally with ffmpeg-skill's 42 Agent Tools
ffmpeg-skill teaches Claude Code, Cursor, Codex or any MCP client a fixed probe → edit → check → verify workflow and ships 42 standard-library Python tools over your local FFmpeg — cuts, silence removal, captions, loudness, platform exports — with no cloud, no API keys, and a contact sheet the agent inspects before it says done.
An agent that "knows FFmpeg" still guesses — it assumes a frame rate, picks a codec the container cannot hold, re-encodes a file that only needed a stream copy, and reports done without opening the result. ffmpeg-skill, a 2k-star MIT project by kajisho5, replaces the guessing with 42 typed tools and a workflow that measures first and verifies last.
Why This Skill Matters
The skill file gives the agent a fixed workflow — probe, edit losslessly where possible, check, verify — and the tools do the work with local ffmpeg and ffprobe. Every job starts with probe.py, so decisions come from measured duration, fps, resolution, colour and audio layout rather than the file name. Nothing runs through a shell and no filter graph is accepted from the caller; each operation is a script with typed arguments. contract --json states, for every tool, what it takes, what it writes, which FFmpeg components it needs and how the result is verified, and the MCP surface is derived from that contract. After execution the output is probed, checked against the destination's spec and, when the picture changed, rendered as a contact sheet with look.py for the agent to look at.
The 42 tools split into analysis (probe, scenes, look), 24 editing tools from cut and join through silence, fit, stabilize, broll and grid, audio (audio, sync, loudness), picture (caption, overlay, graphics, color), delivery (export, proxy, check, report) and orchestration (render, batch, multicam, verify). All are Python 3.9 standard library, and all take --help, --dry-run, --json and --plan FILE.
Installation
npx ffmpeg-skill
npx ffmpeg-skill doctor
npx ffmpeg-skill contract --json | head -40
The first line installs for Claude Code into ~/.claude/skills/ffmpeg-skill; --cursor, --codex and --all target the other agents, and --project installs into ./.claude/skills/. As a Claude Code plugin, claude plugin install kajisho5/ffmpeg-skill needs no Node and updates with claude plugin update. Without Node, clone the repo and copy SKILL.md, scripts/, references/ and mcp/ into your agent's skills directory. Re-run npx ffmpeg-skill to refresh — copies are not updated automatically.
doctor checks that ffmpeg, ffprobe and every FFmpeg component the tools need are present. Read it carefully: an overall ok means nothing required by every tool is missing, but a plain Homebrew ffmpeg on macOS can be ok while caption.py specifically cannot run, because that formula lacks the subtitles filter — doctor --json's tools field gives the per-tool answer. The README's install table is brew install ffmpeg-full on macOS (keg-only, so put $(brew --prefix ffmpeg-full)/bin first on PATH), sudo apt install ffmpeg on Ubuntu and Debian, and winget install Gyan.FFmpeg on Windows. FFmpeg 5.0+ is required.
Real Workflow: An Interview Cut Down to a 60-Second Reel
- Install, run
doctor, then talk to your agent in plain language — the README's own example:
Take interview.mp4, keep 0:45–3:10 and 5:00–6:30, and make it exactly 60 seconds for Reels.
- The agent runs
probe.py, thencut.py --segments 0:45-3:10,5:00-6:30,fit.py --duration 60 --aspect 9:16 --fit crop,export.py --preset reels,check.py --platform reelsandlook.py. - It reports "final.mp4: 59.98 s, 1080×1920, 30 fps, AAC stereo" with the contact sheet it inspected. Reports carry one label —
Done:orFailed:— and a partial result isDone:with the shortfall named underNotes:. - The tools also run on their own from any shell:
S=~/.claude/skills/ffmpeg-skill/scripts
python3 $S/probe.py input.mp4 --compact
python3 $S/fit.py input.mp4 --duration 60 --aspect 9:16 --dry-run # print the plan, run nothing
python3 $S/export.py input.mp4 --preset reels --json # structured result with a probe of the output
- For a destination with its own UI, one command fills a shipped template:
python3 $S/render.py talk.mp4 --template tiktok --cues cues.txt
That is a 9:16 crop, captions popped word by word above TikTok's description bar and clear of its like column, −14 LUFS, the tiktok export preset, and a check.py --platform tiktok on the file it wrote. Templates ship for tiktok, reels, shorts, youtube-shorts, youtube, x, linkedin, facebook and podcast; --template all renders every destination from the same edit and writes a <name>_pack.md table of what each one produced.
Tips
- Lossless cuts snap to keyframes: a stream-copy cut can start up to one GOP earlier than asked, so
cut.pyre-encodes when the snap exceeds 0.5 s (--tolerancechanges the limit;--tolerance -1, plus--vfr-guard offon a variable-frame-rate source, keeps the file strictly lossless and lands the cut on the nearest earlier keyframe). - Variable frame rate — phone and screen recordings — is flagged by
probe.py, andcut.pyswitches to frame-accurate mode on it because copy-cuts on VFR land on the wrong frame. - Reframe first, caption second: text burned before a crop or resize ends up off-frame. Cropping 16:9 to 9:16 discards 70% of the width;
fit.py --fit cropcentres by default, so pass--crop-x/--crop-ytoward the subject or pad with--fit pad --pad-fill blur, and look at the contact sheet before deciding. - HDR stays HDR. Convert deliberately with
color.py --to-sdrbefore H.264 deliverables or LUT work; the platform export presets are SDR and warn on HDR input. - Loudness targets: −14 LUFS / −1 dBTP for YouTube and social platforms (the
loudness.pydefault),-I -16 --tp -1.5for podcasts,-I -23for broadcast.check.py file --platform podcastmeasures both LUFS and true peak. - No tool overwrites its input or silently replaces an existing output. Outputs are new files named
<input>_<operation>.<ext>unless told otherwise, and an existing path is refused until you pass--overwrite. - Audio is a first-class input: WAV, FLAC, MP3, M4A/AAC, OGG and Opus go through
probe,cut,join,silence,loudness,audio,syncandcheck --platform podcastwith the same commands as video. - Optional local transcription — Parakeet or a whisper, when installed — powers
caption.py --transcribeandsilence.py --filler --transcribe; both also take a transcript you already have, and both refuse with the install lines rather than guessing.
When Not to Use This
ffmpeg-skill is, in its README's framing, the hands, not the brain. It does not decide what to cut, whether a deliverable is approvable, what makes a highlight interesting, or what a caption should say — cue text is burned as written. Nothing in the repository detects a face, a product or a subject: redact.py blurs or interpolates a rectangle you supply, cropdetect.py --motion-centre reports where motion sat but picks no crop, and choosing what to keep in frame is the caller's job. A caller-supplied filter graph, -vf string or other ffmpeg option is not an argument any script accepts. The only GPU encode path a tool takes is Apple VideoToolbox on Apple Silicon, normally opted in with --hw; nvenc, QSV, VAAPI and AMF are reported by doctor --json but no tool encodes with them. And for a video rendered from React components the README points at Remotion as a complement — a file Remotion writes is an ordinary input here.
See the leaderboard for more skills.