ffmpeg-skill 2.15.0 → 2.16.1

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
@@ -71,6 +71,7 @@ If `ffmpeg` and `python3` are on your PATH, it works: offline, on footage you wo
71
71
  | Cut / join / measure / export a file right now | **this repo** (`ffmpeg-skill`), standalone |
72
72
  | Decide cut points, approve a deliverable, plan a whole edit | [`video-production-agent`](https://github.com/kajisho5/video-production-agent) / [`AI-video-production-OS`](https://github.com/kajisho5/AI-video-production-OS) |
73
73
  | Build a typed editing graph across a workspace, without writing raw `ffmpeg` | [`video-editing-skill`](https://github.com/kajisho5/video-editing-skill) / [`audio-production-skill`](https://github.com/kajisho5/audio-production-skill) |
74
+ | Put Remotion motion graphics (animated captions, silence jump cuts, audio-peak motion) on a real recording | [`remotion-ffmpeg-skill`](https://github.com/kajisho5/remotion-ffmpeg-skill): its compositions take their length, fps, frame, silences, scene cuts, audio peaks and beats from this repo's `probe.py`, `silence.py` and `scenes.py`. It verifies its render with `check.py` and `look.py`, composites a graphics layer with `overlay.py` (`--place` for its `--sparse` mode), and delivers through `render.py --template` |
74
75
 
75
76
  Other repos in the ecosystem — [`media-analysis-skill`](https://github.com/kajisho5/media-analysis-skill), [`transcription-skill`](https://github.com/kajisho5/transcription-skill), [`subtitle-skill`](https://github.com/kajisho5/subtitle-skill), [`thumbnail-skill`](https://github.com/kajisho5/thumbnail-skill), [`color-grading-skill`](https://github.com/kajisho5/color-grading-skill), [`motion-graphics-skill`](https://github.com/kajisho5/motion-graphics-skill), [`qc-skill`](https://github.com/kajisho5/qc-skill) — read this repo's `contract --json`, its tools' `--json` output and `doctor`, the same way any agent framework would; this repo does not call into any of them. The dependency runs one way.
76
77
 
@@ -400,7 +401,7 @@ Each operation is a script whose flags are its argparse parser, and that parser
400
401
 
401
402
  ### How does ffmpeg-skill compare with Remotion?
402
403
 
403
- 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.
404
+ 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`). [`remotion-ffmpeg-skill`](https://github.com/kajisho5/remotion-ffmpeg-skill) joins the two. It measures the footage with this repo, draws the graphics in Remotion from those measurements, and checks and delivers the result with this repo again. Install both (`npx ffmpeg-skill`, then `npx remotion-ffmpeg-skill`). As with the other skills, the dependency runs one way: it calls these tools, and nothing here calls it. HyperFrames renders a video from HTML and is the same kind of neighbour: a file it writes is an ordinary input too.
404
405
 
405
406
  ### Does it validate the filter graph before running?
406
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.15.0`) | any release |
24
+ | `skill.version` | the npm / package.json version (`2.16.1`) | 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`
@@ -131,7 +131,7 @@ JSON would have been rewritten for that. The flat keys, typed per tool in `outpu
131
131
  "deprecated": [{"what": "running on Python 3.9 (the requirements.python floor)", "since": "2.10.9", "replacement": "Python 3.10 or later; ...", "removed_in": "3.0.0", "where": "behaviour"},
132
132
  {"what": "running on FFmpeg 5.x (the requirements.ffmpeg floor)", "since": "2.14.0", "replacement": "FFmpeg 6.1 or later; ...", "removed_in": "3.0.0", "where": "behaviour"}],
133
133
  "removed": [{"what": "...", "since": "1.10.0", "replacement": "...", "removed_in": "2.0.0", "where": "cli | json | mcp | behaviour"}],
134
- "skill": {"id": "ffmpeg-skill", "version": "2.15.0", "execution_mode": "local", "kind": "execution",
134
+ "skill": {"id": "ffmpeg-skill", "version": "2.16.1", "execution_mode": "local", "kind": "execution",
135
135
  "entrypoints": {"cli": "...", "mcp": "...", "contract": "...", "doctor": "..."},
136
136
  "not_provided": ["AI reasoning", "decisions", "production plans", "project IR", "approvals", "network access", "transcription engine"]},
137
137
  "requirements": {"python": ">=3.9 (standard library only; 3.9 deprecated since 2.10.9, removed in 3.0.0; 3.10+ recommended)", "ffmpeg": ">=5.0 (5.x deprecated since 2.14.0, removed in 3.0.0; 6.1+ recommended)", "ffprobe": ">=5.0"},
@@ -643,6 +643,7 @@ Per-tool keys added in 1.17, all additive:
643
643
  | `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 |
644
644
  | `timeline` | `render.py --export-timeline FILE` | `{format, rate, duration, frames, clips, transition, transition_frames, music, markers, notes, not_exported}`: the written timeline's own numbers (`rate` is exact, `"30000/1001"` for 29.97) and what the project asked for that an editor timeline cannot carry. Nothing is encoded (`commands: []`); `verified` means the file was written and reads back as its format (`verification` step `parse`) -- not that an editor opened it |
645
645
  | `cache` | `render.py --cache` | `{dir, ffmpeg, hits, misses, miss_reasons, saved_seconds, entries}`, plus `would_hit` under `--dry-run`. `miss_reasons` (unreleased, additive): `[{stage, reason, ...}]`, `reason` one of `not_cached`, `sidecar_missing`, `artifact_missing`, `sidecar_corrupt`, `artifact_truncated`, `no_output_path`. The ffmpeg build banner, the skill version, the contract version, the forwarded flags (`--fast`, `--codec`, …), the GPU setting (`FFMPEG_SKILL_HW` and whether `--hw`/`--no-hw` made it explicit) 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 |
646
+ | `intermediate_preset` | `render.py` | The x264/x265 preset the stages before an encoding export ran at: `--intermediate-preset`, default `superfast` since 2.16 (`veryfast` in 2.15). `null` when no stage received it: no encoding export was reached, `--fast`, `--stop-after` before the export, a `copy` export, or nothing but the export encoded. Present on every render result, early stops included |
646
647
 
647
648
  Per-tool keys added in 1.17.1, all additive:
648
649
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ffmpeg-skill",
3
- "version": "2.15.0",
3
+ "version": "2.16.1",
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",
@@ -717,15 +717,16 @@ render.py project.json [--fast] [--dry-run] [--stop-after STAGE] [--work DIR --k
717
717
  render.py project.json --cache DIR [--from STAGE] # reuse identical stages from a previous run
718
718
  render.py plan.json [--audit-log FILE] # execute a plan written by <tool> --plan plan.json
719
719
  render.py project.json --export-timeline edit.fcpxml|edit.edl|edit.otio # the cut for an editor, nothing rendered
720
- render.py project.json --intermediate-preset medium # the stages' own encoder preset instead of veryfast
720
+ render.py project.json --intermediate-preset medium # the stages' own encoder preset instead of superfast
721
721
  ```
722
- `--intermediate-preset` (2.15, default `veryfast`): when the project has an export stage that
722
+ `--intermediate-preset` (2.15; default `superfast` since 2.16, `veryfast` in 2.15): when the project has an export stage that
723
723
  re-encodes (any preset but `copy`) and the run goes that far (no `--stop-after` before it), the
724
724
  stages before it that encode with x264 or x265 (clips, fit, join, silence, captions, graphics,
725
725
  overlays, audiogram; x265 on the HDR path) encode with this preset instead of `medium`. export.py re-encodes their output and its own
726
726
  preset sets the delivered quality, so the earlier `medium` passes cost time the file does not
727
727
  keep: a 30 s 1080p reels render took 41.7 s instead of 51.8 s on FFmpeg 8.0.1 (SSIM 0.997
728
- between the two deliveries). `medium` restores the stages' default; `--fast` still overrides
728
+ between the two deliveries); `superfast` (2.16) took it to 30 s from veryfast's 33 s on 8.0.1, the
729
+ same time on 6.1, at the same quality. `medium` restores the stages' default; `--fast` still overrides
729
730
  everything; a stage given its own `--preset` keeps it. The result names it as
730
731
  `intermediate_preset` (`null` when not applied).
731
732
  `--export-timeline FILE` (2.1) writes the project's cut as an editor timeline instead of
@@ -576,6 +576,9 @@ def output_schema(name: str, meta: Dict[str, Any], hw: bool = False) -> Dict[str
576
576
  elif name == "render":
577
577
  extra = {"stages": {"type": "array", "items": {"type": "string"}}, "check": {"type": ["object", "null"], "description": "check.py's document for a \"check\" stage with a platform, \"content\": true, or both; when check.py printed none, a failure document with error.reason check_output_unparsed"},
578
578
  "cache": {"type": ["object", "null"], "description": "--cache: {dir, ffmpeg, hits, misses, miss_reasons: [{stage, reason: not_cached|sidecar_missing|artifact_missing|sidecar_corrupt|artifact_truncated|no_output_path, ...}], saved_seconds, entries, would_hit (--dry-run)}"},
579
+ "caption": {"type": ["object", "null"], "description": "the caption stage's own block, forwarded verbatim from caption.py (cue-layout counts and fit-size keys); null without a caption stage"},
580
+ "snap": {"type": ["object", "null"], "description": "a project's \"snap\": cut.py's snap block for the first snapped clip, with every clip's in clips; null when nothing was snapped"},
581
+ "intermediate_preset": {"type": ["string", "null"], "description": "2.15: the x264/x265 preset the stages before an encoding export ran at (--intermediate-preset, default superfast since 2.16); null when no stage received it (no encoding export reached, --fast, --stop-after before the export, a copy export, or nothing but the export encoded)"},
579
582
  "notes": {"type": "array", "items": {"type": "string"}, "description": "each stage's own notes, prefixed with its tool (\"export: loudness ... is outside x's ...\")"},
580
583
  "plan": {"type": "string", "description": "when the argument was a plan.json (written by <tool> --plan): its path"},
581
584
  "plan_sha256": {"type": ["string", "null"], "description": "plan execution: sha256 of the plan file as executed"},
package/scripts/render.py CHANGED
@@ -640,7 +640,11 @@ CACHE: Dict[str, Any] = _fresh_cache()
640
640
  # re-encodes their output, so their own `medium` pass bought nothing the delivery keeps. Measured
641
641
  # on a 30 s 1080p reels render (4 cores): fit 18 s and captions 27 s of a 70 s run at `medium`;
642
642
  # `veryfast` at the same CRF encodes in about half the time (SSIM 0.997 against `medium`) and
643
- # the export, still `medium`, sets the delivered quality. `--intermediate-preset medium` restores it.
643
+ # the export, still `medium`, sets the delivered quality. 2.16: `superfast` (the default since): on
644
+ # FFmpeg 8.0.1 the same render took 30 s instead of 33 s with veryfast, SSIM against a lossless
645
+ # reference 0.9868 vs 0.9863 (the export decides what is kept); on 6.1 the two took the same time.
646
+ # `ultrafast` was faster still (26 s) but measurably worse (0.9814). `--intermediate-preset medium`
647
+ # restores the stages' own default.
644
648
  _X264_STAGES = frozenset({"cut.py", "fit.py", "join.py", "silence.py", "caption.py", "graphics.py", "overlay.py", "waveform.py"})
645
649
  INTERMEDIATE: Dict[str, Any] = {"preset": None, "applied": False}
646
650
 
@@ -1066,8 +1070,8 @@ def main() -> int:
1066
1070
  help="append the audit records of this run (a refused plan, a lock taken over, ...) to FILE as JSON lines; "
1067
1071
  "default for a refused plan: <plan>.audit.jsonl next to it. A record that cannot be written is a "
1068
1072
  "warning, never a failure")
1069
- ap.add_argument("--intermediate-preset", default="veryfast", choices=X264_PRESETS,
1070
- help="x264/x265 preset of the stages before an encoding export (default veryfast: the export "
1073
+ ap.add_argument("--intermediate-preset", default="superfast", choices=X264_PRESETS,
1074
+ help="x264/x265 preset of the stages before an encoding export (default superfast: the export "
1071
1075
  "re-encodes them, and its own preset sets the delivered quality); medium = the stages' own default. "
1072
1076
  "Not used without an export stage, with --stop-after before it, with --fast, or for a copy export")
1073
1077
  ap.add_argument("--stop-after", choices=["clips", "join", "silence", "fit", "captions", "graphics", "overlays", "audio", "loudness", "export"], help="stop after this stage (for iterating)")