ffmpeg-skill 2.10.7 → 2.10.8

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 CHANGED
@@ -395,9 +395,13 @@ The short list for humans. The agent-facing version, with the reasoning, is the
395
395
 
396
396
  Each operation is a script whose flags are its argparse parser, and that parser is what `contract --json` and the MCP `inputSchema` are built from. CI fails the build when those drift from the scripts. A caller-supplied filter graph, `-vf` string, or other ffmpeg option is not an argument any script accepts. `doctor` reads `ffmpeg -filters` (and `-encoders`, `-bsfs`) and reports a filter a tool needs before a job runs into it. MCP does accept an `argv` array, and that array is the named script's own CLI flags, still bound to that script: the script's flags, never an ffmpeg command line and never a shell.
397
397
 
398
- ### How does this differ from Remotion or HyperFrames? Can they be used together?
398
+ ### How does ffmpeg-skill compare with Remotion?
399
399
 
400
- Remotion renders a video from React components. HyperFrames renders one from HTML. This repository does neither, and it does not call either of them. It edits a media file you already have, on the machine, with ffmpeg and ffprobe, then probes the file it wrote. A render from either tool is an ordinary input: `probe.py`, `cut.py`, `fit.py`, `export.py` and `check.py` take the file the same way they take any other clip.
400
+ ffmpeg-skill edits footage you already have, on this machine, with FFmpeg tools whose flags are a machine-readable contract (`contract --json`). Remotion renders a video from React components. They complement each other: this repository does not call Remotion, and a file Remotion writes is an ordinary input (`probe.py`, `cut.py`, `fit.py`, `export.py`, `check.py`). HyperFrames renders a video from HTML and is the same kind of neighbour: a file it writes is an ordinary input too.
401
+
402
+ ### Does it validate the filter graph before running?
403
+
404
+ No. `--dry-run` prints the ffmpeg command a writing tool would run and does not start ffmpeg, so ffmpeg never parses that filter graph. ffprobe still runs when the tool probes an input that exists. Unless `--overwrite` is passed, an existing output is refused (`kind: input`) before ffmpeg. An output that is the same file as an input is refused either way. No media file is written, and `verified` is false; `--plan FILE` does write the plan JSON. A path that is not on disk yet is not measured: the probe is a stub (0×0, duration 0), so a number taken from it is not a measurement. The measuring tools (`probe`, `check`, `sync`, `multicam`, `scenes`, `cropdetect`, `report`, `silence`, `loudness`, `stabilize`) still run their measurements; only the artifact is skipped, and `silence` with `--filler --transcribe` also runs the speech engine, which may download a model. `verify` accepts `--dry-run` and ignores it: its steps run. `--plan FILE` is that dry run written down — the planned argv, the command lines, and a fingerprint of each existing input (size, plus a sha256 of the first and last 8 MiB). `render.py` and `verify.py` refuse `--plan`. `render.py FILE` runs a plan only when those fingerprints still match, by running the tool again with the planned argv, not by executing the recorded ffmpeg lines; `--dry-run` on that run only prints them. `doctor` reports whether the filters, encoders and bitstream filters the contract names are in this build (`ffmpeg -filters`, `-encoders`, `-bsfs`) and which fonts are installed. A name in that listing is not a check of the graph this command would build, and a listed GPU encoder is not a successful encode.
401
405
 
402
406
  ### Can it remove burned-in subtitles or logos?
403
407
 
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 (`2.10.7`) | any release |
24
+ | `skill.version` | the npm / package.json version (`2.10.8`) | 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`
@@ -119,7 +119,7 @@ JSON would have been rewritten for that. The flat keys, typed per tool in `outpu
119
119
  "contract_version": "1.0",
120
120
  "deprecated": [],
121
121
  "removed": [{"what": "...", "since": "1.10.0", "replacement": "...", "removed_in": "2.0.0", "where": "cli | json | mcp | behaviour"}],
122
- "skill": {"id": "ffmpeg-skill", "version": "2.10.7", "execution_mode": "local", "kind": "execution",
122
+ "skill": {"id": "ffmpeg-skill", "version": "2.10.8", "execution_mode": "local", "kind": "execution",
123
123
  "entrypoints": {"cli": "...", "mcp": "...", "contract": "...", "doctor": "..."},
124
124
  "not_provided": ["AI reasoning", "decisions", "production plans", "project IR", "approvals", "network access", "transcription engine"]},
125
125
  "requirements": {"python": ">=3.9 (standard library only)", "ffmpeg": ">=5.0", "ffprobe": ">=5.0"},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ffmpeg-skill",
3
- "version": "2.10.7",
3
+ "version": "2.10.8",
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",
@@ -1465,7 +1465,20 @@ def mcp_tool(spec: Dict[str, Any]) -> Dict[str, Any]:
1465
1465
  descriptions verbatim (the same ~250 characters repeated 42 times in every tools/list dump);
1466
1466
  it now goes once into the server's `initialize` response (`instructions`) instead, so a tool's
1467
1467
  own description stays its own one-line sentence."""
1468
- return {"name": spec["name"], "description": spec["description"], "inputSchema": mcp_input_schema(spec)}
1468
+ return {"name": spec["name"], "description": spec["description"], "inputSchema": mcp_input_schema(spec),
1469
+ "annotations": mcp_annotations(spec)}
1470
+
1471
+
1472
+ def mcp_annotations(spec: Dict[str, Any]) -> Dict[str, Any]:
1473
+ """MCP ToolAnnotations (spec 2025-06-18) derived from the ToolSpec flags. Hints only: clients
1474
+ must not treat them as a security boundary. Every tool runs locally (no open world); none edits
1475
+ its input in place; a tool that writes nothing is read-only."""
1476
+ writes = bool(spec.get("produces_artifact")) or bool(spec.get("mutates_input"))
1477
+ return {"title": spec["name"],
1478
+ "readOnlyHint": not writes,
1479
+ "destructiveHint": bool(spec.get("mutates_input")),
1480
+ "idempotentHint": spec.get("idempotency_hint") in ("bit_exact", "content_equivalent", "cached"),
1481
+ "openWorldHint": False}
1469
1482
 
1470
1483
 
1471
1484
  def mcp_tools(detect: bool = False) -> List[Dict[str, Any]]: