Changelog

main moves ahead of the last published npm/GitHub release; a dependent repo should pin a tagged version, not main. See README § Development, “Releasing”.

Unreleased

Fixed

Documentation

2.17.1

Automated release: version and notes generated from pull requests merged since 2.17.0.

Fixed

2.17.0

Automated release: version and notes generated from pull requests merged since 2.16.1.

Added

2.16.1

Automated release: version and notes generated from pull requests merged since 2.16.0.

Fixed

2.16.0

Automated release: version and notes generated from pull requests merged since 2.15.0.

Changed

2.15.0

Automated release: version and notes generated from pull requests merged since 2.14.0.

Changed

2.14.0

Automated release: version and notes generated from pull requests merged since 2.13.0.

Deprecated

2.13.0

Automated release: version and notes generated from pull requests merged since 2.12.1.

Added

2.12.1

Automated release: version and notes generated from pull requests merged since 2.12.0.

Fixed

Changed

2.12.0

Automated release: version and notes generated from pull requests merged since 2.11.0.

Added

Fixed

2.11.0

Automated release: version and notes generated from pull requests merged since 2.10.11.

Added

Changed

2.10.11

Automated release: version and notes generated from pull requests merged since 2.10.10.

Fixed

2.10.10

Automated release: version and notes generated from pull requests merged since 2.10.9.

Fixed

2.10.9

Automated release: version and notes generated from pull requests merged since 2.10.8.

Added

Deprecated

Fixed

Three failures on Ubuntu 26.04 (ubuntu-latest since 2026-10-21), whose apt FFmpeg is 8.0.1. Each is an FFmpeg bug in specific releases; the tools now behave the same on 5.1.1, 6.1.1, 7.0.2, 8.0.1 and 8.1.3, the builds they were run on.

2.10.8

Automated release: version and notes generated from pull requests merged since 2.10.7.

Added

2.10.7

Automated release: version and notes generated from pull requests merged since 2.10.6.

Fixed

2.10.6

Automated release: version and notes generated from pull requests merged since 2.10.5.

2.10.5

Automated release: version and notes generated from pull requests merged since 2.10.4.

Added

Fixed

Closes: #336, #339

2.10.4

Automated release: version and notes generated from pull requests merged since 2.10.3.

Fixed

2.10.3

Automated release: version and notes generated from pull requests merged since 2.10.2.

Fixed

2.10.2

Automated release: version and notes generated from pull requests merged since 2.10.1.

Fixed

2.10.1

Automated release: version and notes generated from pull requests merged since 2.10.0.

Fixed

2.10.0

Automated release: version and notes generated from pull requests merged since 2.9.0.

Added

2.9.0

Automated release: version and notes generated from pull requests merged since 2.8.7.

Added

2.8.7

Automated release: version and notes generated from pull requests merged since 2.8.6.

Fixed

2.8.6

Automated release: version and notes generated from pull requests merged since 2.8.5.

Added

2.8.5

Automated release: version and notes generated from pull requests merged since 2.8.4.

2.8.4

Automated release: version and notes generated from pull requests merged since 2.8.3.

Fixed

Changed

2.8.3

Automated release: version and notes generated from pull requests merged since 2.8.2.

Fixed

2.8.2

Automated release: version and notes generated from pull requests merged since 2.8.1.

2.8.1

Automated release: version and notes generated from pull requests merged since 2.8.0.

Fixed

2.8.0

Automated release: version and notes generated from pull requests merged since 2.7.0.

2.7.0

Automated release: version and notes generated from pull requests merged since 2.6.0.

2.6.0

Automated release: version and notes generated from pull requests merged since 2.5.1.

2.5.1

Automated release: version and notes generated from pull requests merged since 2.5.0.

2.5.0

Automated release: version and notes generated from pull requests merged since 2.4.2.

2.4.2

Automated release: version and notes generated from pull requests merged since 2.4.1.

2.4.1

Automated release: version and notes generated from pull requests merged since 2.4.0.

2.4.0

Automated release: version and notes generated from pull requests merged since 2.3.2.

2.3.2

Automated release: version and notes generated from pull requests merged since 2.3.1.

2.3.1

Automated release: version and notes generated from pull requests merged since 2.3.0.

2.3.0

Automated release: version and notes generated from pull requests merged since 2.2.5.

2.2.5

Automated release: version and notes generated from pull requests merged since 2.2.4.

2.2.4

Automated release: version and notes generated from pull requests merged since 2.2.3.

2.2.3

Automated release: version and notes generated from pull requests merged since 2.2.2.

2.2.2

Automated release: version and notes generated from pull requests merged since 2.2.1.

2.2.1

Automated release: version and notes generated from pull requests merged since 2.2.0.

2.2.0

Added

2.1.0

Added

Fixed

2.0.0

What 1.10.0 announced for 2.0 (contract --json’s deprecated list): four changes made, one withdrawn (result_v2, below). No new feature; tool count still 42. docs/contract.md “What 2.0 changed” is the migration table, and contract --json now lists the same under removed, with the version that deprecated each.

Removed

Changed

Also in this release

1.26.0

Automated release: version and notes generated from pull requests merged since 1.25.0.

1.25.0

Automated release: version and notes generated from pull requests merged since 1.24.1.

1.24.1

Automated release: version and notes generated from pull requests merged since 1.24.0.

Closes: #276

1.24.0

Automated release: version and notes generated from pull requests merged since 1.23.0.

1.23.0

Automated release: version and notes generated from pull requests merged since 1.22.0.

1.22.0

Automated release: version and notes generated from pull requests merged since 1.21.0.

1.21.0

Automated release: version and notes generated from pull requests merged since 1.20.0.

1.20.0

Automated release: version and notes generated from pull requests merged since 1.19.3.

1.19.3

Automated release: version and notes generated from pull requests merged since 1.19.2.

1.19.2

Automated release: version and notes generated from pull requests merged since 1.19.1.

1.19.1

Automated release: version and notes generated from pull requests merged since 1.19.0.

1.19.0

Automated release: version and notes generated from pull requests merged since 1.18.4.

1.18.4

Automated release: version and notes generated from pull requests merged since 1.18.3.

1.18.3

Automated release: version and notes generated from pull requests merged since 1.18.2.

1.18.2

Automated release: version and notes generated from pull requests merged since 1.18.1.

1.18.1

Automated release: version and notes generated from pull requests merged since 1.18.0.

1.18.0

Automated release: version and notes generated from pull requests merged since 1.17.3.

1.17.3

Automated release: version and notes generated from pull requests merged since 1.17.2.

1.17.2

Automated release: version and notes generated from pull requests merged since 1.17.1.

Fixed

1.17.1

Automated release: version and notes generated from pull requests merged since 1.17.0.

Fixed

1.17.0

Automated release: version and notes generated from pull requests merged since 1.16.1.

1.17.0 — throughput, plus the caption-size half of the eval-17 finding. The release workflow turns this heading into the version number.

Added

Changed

Fixed

1.16.1

Automated release: version and notes generated from pull requests merged since 1.16.0.

Fixed

1.16.0

Automated release: version and notes generated from pull requests merged since 1.15.1.

Added

Fixed

Changed

1.15.1

Automated release: version and notes generated from pull requests merged since 1.15.0.

Changed

1.15.0

Automated release: version and notes generated from pull requests merged since 1.14.0.

1.15.0

Added

Fixed

Known

Carried into the next release, all P2 from the 1.15.0 review:

Docs

1.14.0

Automated release: version and notes generated from pull requests merged since 1.13.0.

Added

Docs

1.13.0

Automated release: version and notes generated from pull requests merged since 1.12.0.

Added

Docs

1.12.0

Automated release: version and notes generated from pull requests merged since 1.11.1.

Added

Changed

Docs

1.11.1

Automated release: version and notes generated from pull requests merged since 1.11.0.

Changed

1.11.0

Automated release: version and notes generated from pull requests merged since 1.10.1.

Changed

Added

1.10.1

Automated release: version and notes generated from pull requests merged since 1.10.0.

Fixed

1.10.0

Automated release: version and notes generated from pull requests merged since 1.9.1.

Deprecated

Announced per docs/contract.md’s three-step policy (also in contract --json’s new top-level deprecated list and in docs/contract.md’s “What 2.0 changes”); everything below keeps working unchanged in 1.x and is removed in 2.0.0:

Other

1.9.1

Automated release: version and notes generated from pull requests merged since 1.9.0.

1.9.0

Automated release: version and notes generated from pull requests merged since 1.8.1.

1.8.1

Automated release: version and notes generated from pull requests merged since 1.8.0.

1.8.0

Automated release: version and notes generated from pull requests merged since 1.7.2.

1.7.2

Automated release: version and notes generated from pull requests merged since 1.7.1.

1.7.1

Automated release: version and notes generated from pull requests merged since 1.7.0.

1.7.0

Automated release: version and notes generated from pull requests merged since 1.6.0.

1.6.0

Automated release: version and notes generated from pull requests merged since 1.5.2.

1.5.2

Automated release: version and notes generated from pull requests merged since 1.5.1.

1.5.1

Automated release: version and notes generated from pull requests merged since 1.5.0.

1.5.0

Automated release: version and notes generated from pull requests merged since 1.4.15.

1.4.15

Automated release: version and notes generated from pull requests merged since 1.4.14.

1.4.14

Automated release: version and notes generated from pull requests merged since 1.4.13.

1.4.13

Automated release: version and notes generated from pull requests merged since 1.4.12.

1.4.12

Automated release: version and notes generated from pull requests merged since 1.4.11.

1.4.11

Automated release: version and notes generated from pull requests merged since 1.4.10.

1.4.10

Automated release: version and notes generated from pull requests merged since 1.4.9.

1.4.9

Automated release: version and notes generated from pull requests merged since 1.4.8.

1.4.8

Automated release: version and notes generated from pull requests merged since 1.4.7.

1.4.7

Automated release: version and notes generated from pull requests merged since 1.4.6.

1.4.6

Automated release: version and notes generated from pull requests merged since 1.4.5.

1.4.5

Automated release: version and notes generated from pull requests merged since 1.4.4.

1.4.4

Automated release: version and notes generated from pull requests merged since 1.4.3.

1.4.3

Automated release: version and notes generated from pull requests merged since 1.4.2.

1.4.2

Automated release: version and notes generated from pull requests merged since 1.4.1.

1.4.1

Automated release: version and notes generated from pull requests merged since 1.4.0.

1.4.0

Automated release: version and notes generated from pull requests merged since 1.3.1.

1.3.1

Automated release: version and notes generated from pull requests merged since 1.3.0.

1.3.0

Automated release: version and notes generated from pull requests merged since 1.2.0.

1.2.0

Automated release: version and notes generated from pull requests merged since 1.1.1.

1.1.1

Automated release: version and notes generated from pull requests merged since 1.1.0.

1.1.0

Automated release: version and notes generated from pull requests merged since 1.0.4.

1.0.4

Automated release: version and notes generated from pull requests merged since 1.0.3.

1.0.3

Automated release: version and notes generated from pull requests merged since 1.0.2.

1.0.2

Automated release: version and notes generated from pull requests merged since 1.0.1.

1.0.1

Automated release: version and notes generated from pull requests merged since 1.0.0.

1.0.0

Accidental major. 1.0.0, 1.0.1 and 1.0.2 contain no user-facing or compatibility change over 0.16.15 – they are three routine CI dependency bumps that a release-automation bug labelled major (see references/process-pitfalls.md, fixed in #145). They are left published because npm never lets a version number be reused; treat 1.0.x as 0.16.x under a different name.

Automated release: version and notes generated from pull requests merged since 0.16.15.

0.16.15

Automated release: version and notes generated from pull requests merged since 0.16.14.

0.16.14

Automated release: version and notes generated from pull requests merged since 0.16.13.

0.16.13

Automated release: version and notes generated from pull requests merged since 0.16.12.

0.16.12 — verify the fully-automated release pipeline end to end

No functional code changes. .github/workflows/release.yml was rewritten to auto-create the git tag, GitHub Release, and npm publish on every push to main that bumps package.json’s version, but the first real run of it (for 0.16.11) never got an npm publish to succeed: the NPM_TOKEN secret wasn’t valid yet, and once the tag existed the workflow’s own guard prevented a clean re-attempt under the same version. This bump exists solely to give the pipeline a fresh, untagged version to run against so the whole chain — tag, GitHub Release, and npm publish — can be verified working in one pass.

0.16.11 — fix a contract/reality mismatch on AAC, three batch.py/render.py reliability bugs, and a non-atomic installer

Continuing the same external audit report’s architecture/data-integrity/reliability findings:

0.16.10 — fix batch.py arbitrary script execution, and four fit/background/caption/freeze correctness bugs

0.16.9 — fix the MCP server crashing on a non-object JSON-RPC line

mcp/server.py’s stdio loop parsed each line with json.loads(), which accepts any valid JSON value, not just an object – a bare 42, null, true or [1,2,3] line parses without error. The very next check, "id" not in req, then raised an uncaught TypeError for a non-dict req (an int/bool/None isn’t iterable the way in needs). That check sat outside the try/except wrapping handle(), so the exception propagated out of the stdin loop and killed the entire stdio server process – not just that one malformed line, but every other in-flight and future tool call in the session along with it. Fixed by skipping any parsed JSON value that isn’t a dict before the "id" not in req check.

0.16.7 — fix a silently-dropped render.py fit height and a multicam.py drift-trim ordering bug

0.16.5 — fix silence.py breaking on audio-only WAV, unvalidated –fps, and a LUT-strength inversion in color.py

A deep line-by-line pass over the most-used editing tools:

0.16.4 — fix a silent false-PASS in check.py, an infinite loop in multicam.py, and a silent output collision in batch.py

Three unrelated bugs found in a wider audit past caption.py:

0.16.3 — fix two more caption.py delimiter bugs: ASS override injection, SRT blank-line split

Following on from 0.16.2’s --font fix, a closer look at caption.py found two more places where user-controlled cue text (from --text, an SRT file, or ASR transcription) flows raw into a delimited text format:

0.16.2 — fix caption.py’s –font not sanitised for ASS Style/force_style

An attack-surface audit of every call site that embeds user-controlled text into a filter/subtitle construct found that caption.py was the one script that never routed --font through a sanitiser before using it, unlike overlay.py/graphics.py/grid.py/look.py, which all call escape_drawtext() first. caption.py doesn’t build a drawtext= filter, though – it embeds --font into two different ASS constructs escape_drawtext() was never designed for: the comma-delimited [V4+ Styles] Style: line, and the comma-separated Key=Value list inside a -vf subtitles=...:force_style='...' option. A font name containing a comma shifted every field after it in the Style: line (size, colours, bold flag, alignment, margins); a comma or colon inside force_style’s FontName= broke the option-list/-vf parsing the same way.

Fixed with a new ass_font_name() helper in caption.py that drops , : \ ' and control characters from the font name outright – the same “no real font name needs this character, so don’t chase a per-context escape” call this codebase already made for escape_drawtext()’s '/%.

0.16.1 — fix two more escape_drawtext() gaps, and grid.py –pad’s audio

Adversarial testing (deliberately hostile filenames – very long, Unicode, shell metacharacters, quotes, semicolons, % specifiers, literal filter- graph syntax) found two more gaps in escape_drawtext(), the shared helper every drawtext=text=... call site uses (overlay.py --text, the --font/brand-font fallback, and 0.16.0’s grid.py labels):

Also fixed a real bug CodeRabbit’s review of 0.16.0 caught before it was acted on: grid.py --pad held each shorter cell’s video on its last frame out to the longest clip, but a --audio-from track shorter than that was mapped straight through with no padding at all – the release note’s “with silence” claim wasn’t true. Now --pad pads the selected audio track with apad/atrim to match, and the docstring/help text describe what --pad actually does (holds the last frame; does not add black video).

0.16.0 — add grid.py

New tool: composite --colsx--rows clips into one grid (e.g. a 4x2 wall of takes or angles), each cell letterboxed (not stretched) to a common --cell-width/--cell-height so mismatched aspect ratios and resolutions line up cleanly. --label auto (default) burns each clip’s own filename, extension stripped, into its cell’s bottom-right corner; --label none turns that off. No audio unless --audio-from picks one input’s track – mixing every clip’s audio together is rarely what a comparison grid needs, so this tool never does it silently. Runs only as long as the shortest clip by default; --pad instead holds each shorter clip’s last frame (with silence) out to the longest.

The per-cell label is filename-derived text reaching a drawtext=text=... option, the same injection class fixed in 0.15.3 – wrapped with the existing escape_drawtext() helper from the start, with a regression test that builds a clip literally named to look like a filter-graph breakout payload and confirms it renders as inert literal text (not a new filter).

0.15.3 — fix a real filter-graph injection via –font fallback

Found by the same adversarial pass that produced 0.15.2, this time auditing file-path/text escaping instead of colour flags. overlay.py --font and graphics.py’s brand-font fallback both accept a fontconfig family NAME (not a file path) and try to resolve it to a concrete file via default_font_file() (fc-match) first – but that resolution returns None whenever fc-match isn’t on PATH (true on some real systems, and always true on Windows, per default_font_file()’s own docstring). When it returns None, both tools fell back to font='{args.font}' with zero escaping, unlike the adjacent text='{escape_drawtext(args.text)}' one line above it in overlay.py. Since drawtext options are comma/colon-delimited, a font value like X',drawtext=text=OWNED doesn’t just fail to resolve a font – the comma ends the option (and the whole filter) early and starts an entirely new drawtext filter, which actually rendered. Confirmed this is a real, working injection (not theoretical): forced the fc-match-missing fallback path, ran overlay.py --font "X',drawtext=text=OWNED:fontcolor=yellow...", and the injected “OWNED” text was actually burnt into the output picture.

Fixed by wrapping both fallback values with the existing escape_drawtext() helper, matching the already-safe text= pattern next to it:

0.15.2 — fix a real filter-graph injection via colour flags

Found by adversarial testing: every flag that string-formats a colour straight into an ffmpeg filter graph accepted the value verbatim, with no validation. Since ffmpeg filter options are comma/colon-delimited, a value like black,drawtext=text=INJECTED doesn’t just set an odd colour – the comma ends the colour filter early and starts an entirely new one. Confirmed this is a real, working injection, not a theoretical one: rendered a frame with pad.py --color 'black,drawtext=text=INJECTED:fontcolor=white' and the injected text was actually burnt into the output picture.

Added validate_color() to _common.py (refuses anything that isn’t a named colour, 0xRRGGBB[AA], or #RRGGBB[AA], optionally with an @alpha suffix) and applied it to every colour-like flag that reaches a filter graph unescaped:

caption.py’s colour flags were already safe (routed through the existing color_hex()/ass_color() strict RRGGBB validators) and needed no change.

This is the same “no filter graph accepted from the caller” invariant every other typed flag in this codebase already holds to – colour flags were the one place a free-form string still reached a filter graph unescaped. New regression test proves the exploit across all 9 fixed call sites and that real colour values still work; full suite (183 tests) and the contract suite (67 tests) both pass.

0.15.1 — bug-check pass on the 39-tool set

Found and fixed while auditing the tools added across 0.13.0-0.15.0:

Found via a fresh worktree off main post-merge, adversarial testing of edge cases (zero/boundary values, multi-track inputs) rather than only the happy paths the original PRs’ tests covered. New regression tests for all four; full suite (181 tests) and the contract suite (67 tests) both pass.

0.15.0 — 5 more tools: straighten, freeze, pad, speedramp, loop

Five more mechanical, typed-flag FFmpeg capabilities:

Same conventions as every other tool here: every numeric flag range-checked before ffmpeg runs, no subject detection or judgement (straighten doesn’t measure the tilt, loop doesn’t smooth the seam, freeze doesn’t pick where to hold – the calling agent supplies all of that).

Registered in _contract.py’s TOOL_META/REENCODE_META (39 tools total, up from 34); 19 new regression tests in tests/test_all.py, including a pixel-level check that straighten.py --fit crop leaves no black corner.

0.14.0 — 5 new tools: cropdetect, deinterlace, denoise, redact, waveform

Five mechanical, typed-flag FFmpeg capabilities that had no wrapper yet:

All five follow this skill’s typed-flags-only convention: every numeric flag is range-checked against FFmpeg’s own real documented AVOptions before ffmpeg runs, and none introduces any subject detection or judgement – cropdetect measures existing bars, redact blurs the exact rectangle it’s given, neither decides what belongs in frame.

Registered in _contract.py’s TOOL_META/REENCODE_META (34 tools total, up from 29); 22 new regression tests in tests/test_all.py cover the functional path and validated ranges for each tool.

0.13.0 — add sphere.py: flat-viewport extraction from 360/spherical video

New tool wrapping FFmpeg’s v360 filter for the most common 360-video job: pointing a fixed, typed camera (--yaw/--pitch/--roll, --h-fov/--v-fov) at an equirectangular (or fisheye/cubemap/etc., via --input-projection) source and baking out an ordinary flat video. Consistent with this skill’s design boundary: it aims and extracts a viewport, it does not detect or track a subject — that decision stays with the calling agent. Registered in TOOL_META/ REENCODE_META, 29 tools total.

0.12.6 — stop shipping __pycache__ in the npm tarball

package.json’s files array scopes the tarball to bin/, scripts/, mcp/, references/, SKILL.md, README.md and LICENSE, but a files-scoped pack does not automatically respect .gitignore the way a plain git-tracked-files pack would — the 0.12.5 tarball on npm shipped every scripts/__pycache__/*.pyc a local interpreter had produced (interpreter-version-specific, harmless at runtime since Python regenerates them, but ~45% of the package’s unpacked size for nothing). Added an explicit .npmignore for __pycache__/, *.pyc, *.pyo, which npm honors even when files is set. Unpacked size drops from 913.7 kB / 64 files to 496.8 kB / 40 files.

0.12.5 — fix Windows drawtext crash (#100), and 4 smaller review findings

look.py, scenes.py --sheet, overlay.py --text and graphics.py could crash on real Windows FFmpeg builds (confirmed on winget’s gyan.dev 9.x): drawtext’s own fontconfig resolution dies with an access violation whenever it has to resolve a font by family name, with or without a valid fonts.conf — and doctor reported missing required: none, since -filters correctly lists drawtext as present; the crash only ever surfaced as a runtime failure, the exact thing the step-0 capability check exists to prevent.

Thanks to @willy92wins for the detailed repro in #100.

0.12.4 — stabilize.py gains --tripod and --crop (#96)

A follow-on audit of the same class of gap 0.12.3 closed in color.py --correct: scripts that wrap a real FFmpeg filter but only expose a subset of what that filter actually supports. stabilize.py wrapped vidstabdetect/vidstabtransform with only --shakiness/--smoothing/ --zoom, leaving two genuinely useful, real options unreachable:

Both are typed flags (--crop restricted to the filter’s own two real option names via choices), verified against ffmpeg -h filter=vidstabdetect/vidstabtransform’s real AVOptions before implementation, with new regression tests that actually run both flags end-to-end and check the output’s duration/resolution. (silence.py, audio.py, loudness.py, and fit.py’s minterpolate usage were also checked against their real filters’ full option sets and found either not applicable — silence.py doesn’t wrap silenceremove at all, it does its own silencedetect + range-trim — or already adequately covered.)

0.12.3 — color.py --correct gains gamma, lift/gain, levels and curves

Closes a long-standing, documented gap: the downstream color-grading-skill has carried GAMMA/LIFT/GAIN/LEVELS/CURVES in its own UNSUPPORTED_OPERATIONS because “ffmpeg-skill exposes no typed X filter in its public contract”. --correct gains five more typed flags, all folded into the same fixed filter chain the existing --exposure/--contrast/ --saturation/--temperature/--tint already build — no new mode, no filter string ever accepted from the caller:

0.12.2 — caption.py --mode mux no longer drops the input’s existing subtitle track(s)

Found while discussing a real use case (adding both an English and a Japanese soft subtitle track to a foreign video) and reproduced directly: chaining --mode mux once per language — the natural way to build a multi-language subtitle set — silently dropped every earlier language but the last, because the mode’s -map list never included the main input’s own existing subtitle stream(s), only the freshly-added one. Same class of bug fixed across fit.py/color.py/graphics.py/overlay.py in 0.12.1/#91 (deliberately scoped out of that pass since burn mode raises a real design question mux mode doesn’t have: mux mode explicitly promises “copies video/audio untouched, adds the SRT as a soft, toggleable subtitle stream”, so keeping what was already there has one obvious answer). Existing tracks are now mapped and stream-copied (-c:s:i copy per existing index) ahead of the new one, whose own -c:s/ -metadata:s:s:N now target its real index instead of always 0. Closes #93.

0.12.1 — Stream-preservation audit: subtitle/data streams no longer silently dropped by picture-only edits

Prompted by an external review pushing back that “feature-complete” for this project now means proving reliability, not adding tools. Audited all 28 tools against a real fixture carrying video + audio + subtitle + chapters, by actually running each tool and ffprobe-ing its output rather than reading the code and guessing (an ad hoc pass, not itself checked in as a test). The checked-in regression test below covers the four tools this pass actually changed, against a narrower existing fixture (video + audio + two subtitle tracks, no chapters).

0.12.0 — 2026-09-07 — Hardening pass: stream/input safety, contract-vs-implementation drift, SKILL.md/eval consistency

A hardening-focused release: no new tools, no new features. Everything here closes a gap between what the contract/docs/evals claimed and what the implementation actually did, or fixes a real runtime defect found by reproducing it first. contract_version is unchanged — every contract field addition here is additive.

0.11.0 — 2026-09-07 — Pixel crop, still-to-clip, rotate/flip/PiP/reverse/chromakey/stabilize/sequence/Ken Burns, fail-loudly output verification, capability map

Closes the video-editing-skill ADR-002/ADR-003 gap investigation (11 confirmed gaps): crop.py, insert.py (incl. Ken Burns), fit.py --rotate/--flip, overlay.py --video/--chromakey (video-on-video PiP, chroma key), reverse.py, stabilize.py, sequence.py, background.py, proxy.py, contract --json’s capability_map, and a repo-wide “fail loudly” pass making verify_output() the single success criterion for every writing tool. 28 tools total (was 21 at 0.10.0); README/package.json’s stale “21”/”22” tool-count strings are also corrected to the real count here — see #50 for making that count self-maintaining going forward.

0.10.0 — 2026-09-06 — FFmpeg 8+/Windows compatibility, per-tool doctor/contract usability, provenance and honesty fixes

0.9.2 — typed primary colour correction

0.9.1 — FFmpeg 8 capability detection; audio extraction, audio join, sample-accurate trims, typed dynamics

0.9.0 — machine-readable execution contract

ffmpeg-skill contract --json (python3 scripts/_contract.py --json) describes the skill for agent frameworks: contract_version 1.0 separate from the skill version; one ToolSpec per public script (ffmpeg-skill/<name>) with an input schema generated from its argparse parser, an output schema, role (analysis / analysis_and_execution / execution / verification), required and conditional ffmpeg capabilities, dry-run support, mutates_input: false, the verification tools to run afterwards, and whether a visual check is required. ffmpeg-skill doctor reports which capabilities the machine has. docs/contract.md explains the fields and how a planner consumes them.

0.8.5 — audio-only inputs, spelled out

0.8.4 — three repeats, independent grading

The 24-prompt evaluation was run three times (72 agent runs, evals/results/iteration-2-4.json) and every run was graded by a separate model (evals/results/iteration-2-4-independent-grades.json): routing 72/72, honest 72/72, user’s language 72/72, report format 71/72, visual check whenever the picture changed 24/24, mean quality 4.9 / 5. Script choices were identical across repeats; only defaults (crop vs pad, silence margin) varied.

0.8.3 — internals and triggering

0.8.2 — second agent-run evaluation

24 prompts (12 English edits, 8 Japanese edits, 4 that must be declined) run by independent agents against the 0.8.1 skill (evals/agent_prompts_24.json, evals/grade_runs_24.py, evals/results/iteration-2.json). Routing 24/24, honest refusals 4/4, Japanese reports 9/9, visual check whenever the picture changed 8/8, mean 6.5 commands per job.

0.8.1 — skill craft

The skill file itself, measured. Six realistic prompts were run by independent agents with the old and the restructured SKILL.md (evals/agent_prompts.json, evals/grade_runs.py, results in evals/results/).

0.8.0 — validation release

Measured instead of assumed. New tests/corpus.py pulls public real-device videos (GoPro HERO 4K 10-bit, DJI 4K60 no-audio, two iPhones incl. Dolby Vision 4K60, two Android screen recordings incl. 18 fps VFR and 120 fps, an HDR10 PQ test pattern, a 24p clip, and Blender’s Tears of Steel) and runs verify.py over them; tests/bench_*.py score algorithms against known ground truth.

0.7.0

0.6.0

0.5.0

0.4.1

Fixes found by running verify.py on a real iPhone clip (Dolby Vision 8.4 / HLG, 10-bit HEVC, 60 fps VFR, portrait rotation, extra metadata tracks):

0.4.0

0.3.0

0.2.0

0.1.0