ffmpeg-skill 1.18.2 → 1.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/docs/contract.md +23 -11
- package/mcp/server.py +19 -6
- package/package.json +1 -1
- package/scripts/_contract.py +9 -0
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ npx ffmpeg-skill
|
|
|
41
41
|
|
|
42
42
|
Left half is the input, right half is what the command produced. **[All 53 before/after demos, with the exact command under each one →](docs/demos.md)** — all of it generated from synthetic footage by `python3 demos/build.py`, so you can rebuild every frame of it yourself.
|
|
43
43
|
|
|
44
|
-
`ffmpeg-skill` is an [Agent Skill](https://docs.anthropic.com/en/docs/agents-and-tools/agent-skills) for Claude Code, Cursor, Codex and any agent that reads `SKILL.md`. It teaches the agent a fixed workflow (probe → edit losslessly where possible → check → verify) and ships **42 tools** that do the actual work with `ffmpeg` / `ffprobe`: cut, join, silence removal, fit to duration and aspect, captions and karaoke, overlays and motion graphics, HDR → SDR and LUTs, audio clean-up and typed dynamics, sync with drift correction, multicam, loudness, delivery checks, whole-edit project rendering, batch folders. Every tool is also an MCP tool, and the whole set is described by a machine-readable contract.
|
|
44
|
+
`ffmpeg-skill` is an [Agent Skill](https://docs.anthropic.com/en/docs/agents-and-tools/agent-skills) for Claude Code, Cursor, Codex and any agent that reads `SKILL.md`. It teaches the agent a fixed workflow (probe → edit losslessly where possible → check → verify) and ships **42 tools** that do the actual work with `ffmpeg` / `ffprobe`: cut, join, silence removal, fit to duration and aspect, captions and karaoke, overlays and motion graphics, HDR → SDR and LUTs, audio clean-up and typed dynamics, sync with drift correction, multicam, loudness, delivery checks, whole-edit project rendering, batch folders. Every tool is also callable as an MCP tool (`tools/list` advertises a core 12 by default to keep client context small, with `FFMPEG_SKILL_MCP_FULL=1` listing all 42; every tool is reachable by name through `tools/call` either way), and the whole set is described by a machine-readable contract.
|
|
45
45
|
|
|
46
46
|
If `ffmpeg` and `python3` are on your PATH, it works: offline, on footage you would rather not upload.
|
|
47
47
|
|
|
@@ -341,9 +341,9 @@ Next to the tool list the document carries a top-level `deprecated` list (1.10):
|
|
|
341
341
|
|
|
342
342
|
On Windows, `python3` is only on PATH if Python was installed from the Microsoft Store; a python.org install exposes `python` (or the `py` launcher) instead — if your MCP client reports the server failed to start, change `"command"` above to `"python"` (or the full path from `where python`).
|
|
343
343
|
|
|
344
|
-
`mcp/server.py` is a stdio JSON-RPC transport with no tool table of its own. `tools/list` is derived from the contract at start-up
|
|
344
|
+
`mcp/server.py` is a stdio JSON-RPC transport with no tool table of its own. `tools/list` is derived from the contract at start-up, in contract order, with `inputSchema` translated from each tool's `input_schema`. By default it lists only the core 12 (`render`, `look`, `caption`, `export`, `check`, `fit`, `cut`, `audio`, `loudness`, `graphics`, `silence`, `probe`) so a client doesn't pay context for 30 schemas it rarely calls directly; set `FFMPEG_SKILL_MCP_FULL=1` to list all 42. Every tool, listed or not, is callable through `tools/call`, which maps structured arguments to argv and runs the named script; a raw `argv` form is accepted for compatibility and marked non-canonical. `python3 mcp/server.py --list` prints the tools; `--call probe '{"inputs": ["a.mp4"]}'` runs one from the shell.
|
|
345
345
|
|
|
346
|
-
`FFMPEG_SKILL_MCP_LEAN=1` in the server's environment drops `json` and `progress` from every `inputSchema`: they are transport flags the server sets itself, not tool arguments, and 2.0 drops them unconditionally. It is opt-in,
|
|
346
|
+
`FFMPEG_SKILL_MCP_LEAN=1` in the server's environment drops `json` and `progress` from every `inputSchema`: they are transport flags the server sets itself, not tool arguments, and 2.0 drops them unconditionally. It is opt-in, independent of `FFMPEG_SKILL_MCP_FULL`, and the default `tools/list` stays byte-identical (aside from the core-12 filter) to the CLI surface the contract promises.
|
|
347
347
|
|
|
348
348
|
### Capability detection
|
|
349
349
|
|
package/docs/contract.md
CHANGED
|
@@ -21,7 +21,7 @@ The contract is derived from the code that runs, not maintained beside it:
|
|
|
21
21
|
| Field | Meaning | Changes when |
|
|
22
22
|
|---|---|---|
|
|
23
23
|
| `contract_version` | shape of this document (`1.0`) | a key is renamed, removed or changes meaning |
|
|
24
|
-
| `skill.version` | the npm / package.json version (`1.18.
|
|
24
|
+
| `skill.version` | the npm / package.json version (`1.18.3`) | any release |
|
|
25
25
|
|
|
26
26
|
A release that adds a tool or a flag keeps `contract_version`; a breaking change to the
|
|
27
27
|
ToolSpec shape bumps it. Consumers pin on `contract_version` and read `skill.version`
|
|
@@ -88,19 +88,19 @@ spelling keeps working until 2.0.
|
|
|
88
88
|
|
|
89
89
|
| What 2.0 removes | Since | Replacement | To be ready today |
|
|
90
90
|
|---|---|---|---|
|
|
91
|
-
| The per-tool v1 success keys next to `result_v2` (`output`, `probe`, `commands`, `verified`, `verification` and each tool's own keys at the top level) | 1.18.
|
|
92
|
-
| `--crf` as an alias of `--quality` on every re-encoding tool that takes `--quality` (`export.py` keeps `--crf`: its preset chooses the encoder) | 1.18.
|
|
93
|
-
| `json` and `progress` in the MCP `inputSchema` | 1.18.
|
|
94
|
-
| `hdr` meaning "BT.2020 primaries *or* a PQ/HLG transfer" in `probe` | 1.18.
|
|
95
|
-
| Overwriting an existing output with only a warning | 1.18.
|
|
91
|
+
| The per-tool v1 success keys next to `result_v2` (`output`, `probe`, `commands`, `verified`, `verification` and each tool's own keys at the top level) | 1.18.3 | `result_v2`, promoted to the top level in 2.0 | Run with `FFMPEG_SKILL_RESULT_V2=1` and read `result_v2` (`metrics`, `notes`, `details`) instead of the top-level keys |
|
|
92
|
+
| `--crf` as an alias of `--quality` on every re-encoding tool that takes `--quality` (`export.py` keeps `--crf`: its preset chooses the encoder) | 1.18.3 | `--quality N` (the same CRF scale, codec-neutral) | Pass `--quality`; `--crf` warns on stderr and is marked in `--help` |
|
|
93
|
+
| `json` and `progress` in the MCP `inputSchema` | 1.18.3 | nothing: the transport sets them itself | Stop sending them from an MCP client; run the server with `FFMPEG_SKILL_MCP_LEAN=1` to see the 2.0 schema |
|
|
94
|
+
| `hdr` meaning "BT.2020 primaries *or* a PQ/HLG transfer" in `probe` | 1.18.3 | `hdr_signal` (true only for PQ / HLG / Dolby Vision); in 2.0 `hdr` takes that meaning | Key on `hdr_signal` for "is this a real HDR signal" and on `hdr_format` for the `BT.2020 SDR` case |
|
|
95
|
+
| Overwriting an existing output with only a warning | 1.18.3 | `--overwrite` as explicit consent (refused without it from 2.0) | Set `FFMPEG_SKILL_NO_OVERWRITE=1` (the recommended agent setting) and pass `--overwrite` where a replacement is intended |
|
|
96
96
|
|
|
97
97
|
## Skill
|
|
98
98
|
|
|
99
99
|
```json
|
|
100
100
|
{
|
|
101
101
|
"contract_version": "1.0",
|
|
102
|
-
"deprecated": [{"what": "...", "since": "1.18.
|
|
103
|
-
"skill": {"id": "ffmpeg-skill", "version": "1.18.
|
|
102
|
+
"deprecated": [{"what": "...", "since": "1.18.3", "replacement": "...", "removed_in": "2.0.0", "where": "cli | json | mcp | behaviour"}],
|
|
103
|
+
"skill": {"id": "ffmpeg-skill", "version": "1.18.3", "execution_mode": "local", "kind": "execution",
|
|
104
104
|
"entrypoints": {"cli": "...", "mcp": "...", "contract": "...", "doctor": "..."},
|
|
105
105
|
"not_provided": ["AI reasoning", "decisions", "production plans", "project IR", "approvals", "network access", "transcription engine"]},
|
|
106
106
|
"requirements": {"python": ">=3.9 (standard library only)", "ffmpeg": ">=5.0", "ffprobe": ">=5.0"},
|
|
@@ -128,7 +128,7 @@ One entry per tool under `tools`, sorted by id. Tool ids are stable:
|
|
|
128
128
|
| `output_schema` | what `--json` prints on stdout |
|
|
129
129
|
| `supports_dry_run`, `dry_run` | whether `--dry-run` plans without running ffmpeg or writing files |
|
|
130
130
|
| `supports_json` | whether `--json` exists |
|
|
131
|
-
| `supports_json_brief` | whether `--json-brief` exists (1.18.
|
|
131
|
+
| `supports_json_brief` | whether `--json-brief` exists (1.18.3): the same success document with `probe` replaced by a compact `summary` (`duration_s`, `width`, `height`, `fps`, `vcodec`, `acodec`, `channels`, and `lufs` when the tool measured one), `commands` replaced by the number of commands run, and the per-step `verification` list dropped (its verdict stays in `verified`). Tool-specific keys are unchanged, `--json`'s own output is unchanged, and a failure prints the same failure document either way |
|
|
132
132
|
| `mutates_input` | always `false`: no tool overwrites its input |
|
|
133
133
|
| `produces_artifact` | writes a file (media, PNG, HTML, EDL) |
|
|
134
134
|
| `verification` | `{required, tools}`: which tools to run on the output afterwards |
|
|
@@ -450,7 +450,7 @@ Per-tool keys added in 1.17, all additive:
|
|
|
450
450
|
| `jobs`, `jobs_requested`, `wall_seconds`, `item_seconds_total`, `timed_out` | `batch.py` | the parallelism actually applied and the number asked for, the batch's wall clock, the sum of the per-item times (so the speed-up can be quoted), and whether the shared timeout budget ran out. A timed-out item carries `"skipped": "timeout"` in its result row |
|
|
451
451
|
| `cache` | `render.py --cache` | `{dir, ffmpeg, hits, misses, saved_seconds, entries}`, plus `would_hit` under `--dry-run`. The ffmpeg build banner, the skill version, the contract version, the forwarded flags (`--fast`, `--codec`, …) and the output's extension are all part of every key, so a cache is never reused across any of them — a `--fast` draft is never served to a run that did not ask for one |
|
|
452
452
|
|
|
453
|
-
Per-tool keys added in 1.18.
|
|
453
|
+
Per-tool keys added in 1.18.3, all additive:
|
|
454
454
|
|
|
455
455
|
| key | tool | what it holds |
|
|
456
456
|
|---|---|---|
|
|
@@ -458,7 +458,7 @@ Per-tool keys added in 1.18.2, all additive:
|
|
|
458
458
|
| `text_unchanged` | `caption.py` | a sibling inside the `caption` block, **burn mode only** (`--mode mux` never touches the text and omits the key): `true` when the drawn text equals the cues that were handed in — nothing transcribed, no cue dropped, no cue **split** across two consecutive cues and no glyph stripped (`--emoji none`). Wrapping, line breaks and timing do not count: the words are the same. This tool never rewrites, shortens or translates a cue, so the key is a statement of what happened, not a judgement of the text |
|
|
459
459
|
|
|
460
460
|
|
|
461
|
-
Per-tool keys added in 1.18.
|
|
461
|
+
Per-tool keys added in 1.18.3, all additive:
|
|
462
462
|
|
|
463
463
|
| key | tool | what it holds |
|
|
464
464
|
|---|---|---|
|
|
@@ -578,6 +578,18 @@ for good (see "What 2.0 changes"). The flag is opt-in and changes nothing else:
|
|
|
578
578
|
pins -- descriptions may change between releases (the `--crf` deprecation mark did) -- so a lean
|
|
579
579
|
client and a default client see the same tools with the same names.
|
|
580
580
|
|
|
581
|
+
### Default `tools/list` surface: core 12, opt-in for all 42
|
|
582
|
+
|
|
583
|
+
By default `tools/list` returns only the core dozen in `_contract.MCP_CORE_TOOLS` (`render`,
|
|
584
|
+
`look`, `caption`, `export`, `check`, `fit`, `cut`, `audio`, `loudness`, `graphics`, `silence`,
|
|
585
|
+
`probe`) rather than all 42, so a session doesn't pay context for 30 schemas it is unlikely to ever call
|
|
586
|
+
directly -- the set is the tools eval iterations 17-20's ground-truth `expect` lists actually name
|
|
587
|
+
most often across the corpus in `evals/agent_prompts*.json`. Every tool -- including the other 30
|
|
588
|
+
-- is still callable by name through `tools/call` regardless of what `tools/list` advertised; the
|
|
589
|
+
contract (`ffmpeg-skill contract --json`) still describes all 42 unconditionally. Set
|
|
590
|
+
`FFMPEG_SKILL_MCP_FULL=1` (anything but "" or `0`) in the server's environment to make `tools/list`
|
|
591
|
+
return all 42, as every version before 1.18.3 did.
|
|
592
|
+
|
|
581
593
|
## Consuming the contract from an agent
|
|
582
594
|
|
|
583
595
|
A planning agent (for example video-production-agent's SkillRegistry) can:
|
package/mcp/server.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""ffmpeg-skill as an MCP server (stdio, JSON-RPC 2.0) — standard library only.
|
|
3
3
|
|
|
4
|
-
Every public script in ../scripts becomes a tool
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
Every public script in ../scripts becomes a callable tool, but tools/list defaults to
|
|
5
|
+
advertising only the core 12 (_contract.MCP_CORE_TOOLS) to keep the schema dump small;
|
|
6
|
+
set FFMPEG_SKILL_MCP_FULL=1 to list all 42. Every tool is still callable by name via
|
|
7
|
+
tools/call either way. Tool names, order, inputSchema and the structured-argument mapping
|
|
8
|
+
all come from the contract (scripts/_contract.py); this file is only the transport.
|
|
9
|
+
Arguments are passed as a flat object (keys = argparse dests) or, for CLI compatibility,
|
|
10
|
+
as an argv list. Results are the script's --json output.
|
|
8
11
|
|
|
9
12
|
Run:
|
|
10
13
|
python3 mcp/server.py # stdio transport
|
|
@@ -126,8 +129,18 @@ def call_tool(name: str, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
126
129
|
|
|
127
130
|
|
|
128
131
|
def tool_list() -> List[Dict[str, Any]]:
|
|
129
|
-
"""tools/list: one entry per ToolSpec, in the contract's order, inputSchema derived from ToolSpec.input_schema.
|
|
130
|
-
|
|
132
|
+
"""tools/list: one entry per ToolSpec, in the contract's order, inputSchema derived from ToolSpec.input_schema.
|
|
133
|
+
|
|
134
|
+
Default surface is the core 12 (_contract.MCP_CORE_TOOLS, roadmap P1-7): the other 30 stay
|
|
135
|
+
reachable by name through tools/call regardless. FFMPEG_SKILL_MCP_FULL=1 (anything but "" or
|
|
136
|
+
"0") lists all 42, unchanged from pre-1.18.3 behaviour.
|
|
137
|
+
"""
|
|
138
|
+
all_specs = specs()
|
|
139
|
+
if os.environ.get("FFMPEG_SKILL_MCP_FULL", "") not in ("", "0"):
|
|
140
|
+
names = all_specs.keys()
|
|
141
|
+
else:
|
|
142
|
+
names = (n for n in all_specs if n in _contract.MCP_CORE_TOOLS)
|
|
143
|
+
return [_contract.mcp_tool(all_specs[name]) for name in names]
|
|
131
144
|
|
|
132
145
|
|
|
133
146
|
def handle(req: Dict[str, Any]) -> Dict[str, Any]:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ffmpeg-skill",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.3",
|
|
4
4
|
"description": "Agent Skill that gives coding agents (Claude Code, Cursor, Codex) a local video editor: 42 FFmpeg tools with a machine-readable contract, contract-derived MCP server, FFmpeg capability detection, probe-first / verify-last workflow. Cut, join, silence removal, fit, captions and karaoke, overlays, motion graphics, HDR to SDR, LUTs, audio clean-up and typed dynamics, sync with drift correction, multicam, loudness, delivery checks, project rendering, batch. No API keys, no cloud, no dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ffmpeg",
|
package/scripts/_contract.py
CHANGED
|
@@ -1075,6 +1075,15 @@ MCP_JSON_EXEMPT = ("look", "probe")
|
|
|
1075
1075
|
# itself, not tool arguments. Off by default so tools/list stays byte-identical to the CLI surface
|
|
1076
1076
|
# the contract promises; 2.0 drops them unconditionally.
|
|
1077
1077
|
MCP_LEAN_DROP = ("json", "progress")
|
|
1078
|
+
# default MCP tools/list surface (roadmap P1-7, shipped 1.18.3): the 42-tool schema dump costs
|
|
1079
|
+
# every MCP session context whether it needs it or not. These 12 are the tools eval iterations
|
|
1080
|
+
# 17-20's ground-truth `expect` lists actually name most often across the 118 agent prompts in
|
|
1081
|
+
# evals/agent_prompts*.json (render/look/caption/export/check each >=10 hits; fit/cut/audio/
|
|
1082
|
+
# loudness/graphics/silence/probe each >=6) -- not a hand-picked "starter" set. `tools/list`
|
|
1083
|
+
# returns only these unless FFMPEG_SKILL_MCP_FULL=1 (anything but "" or "0") asks for all 42; the
|
|
1084
|
+
# other 30 stay reachable by name through tools/call either way, and `contract --json` always
|
|
1085
|
+
# describes all 42.
|
|
1086
|
+
MCP_CORE_TOOLS = ("render", "look", "caption", "export", "check", "fit", "cut", "audio", "loudness", "graphics", "silence", "probe")
|
|
1078
1087
|
MCP_STRUCTURED_NOTE = ("Structured arguments: keys are the input_schema property names (argparse dests), positionals "
|
|
1079
1088
|
"are passed by name, output -> -o. Or argv: the raw CLI list (non-canonical; all other keys are then ignored). "
|
|
1080
1089
|
"Media paths must be absolute.")
|