ffmpeg-skill 1.0.3 → 1.1.0
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 +14 -2
- package/package.json +1 -1
- package/references/process-pitfalls.md +55 -0
package/README.md
CHANGED
|
@@ -312,7 +312,7 @@ npx ffmpeg-skill doctor --json # available / missing / missing_optional / unkn
|
|
|
312
312
|
|
|
313
313
|
## FFmpeg compatibility
|
|
314
314
|
|
|
315
|
-
The tools need FFmpeg 5.0 or later. The capability parser has been run against the listings of these builds:
|
|
315
|
+
The tools need FFmpeg 5.0 or later and Python 3.9 or later (standard library only). What CI actually exercises on every pull request is FFmpeg 6.1 (Ubuntu), 8.x (macOS) and 9.x (Windows), all on Python 3.9, plus Ubuntu on Python 3.13 (the two ends of the supported range); 5.x and 7.x are expected to work from the filter/encoder names used but are not run ([#146](https://github.com/kajisho5/ffmpeg-skill/issues/146) tracks widening the matrix). The capability parser has been run against the listings of these builds:
|
|
316
316
|
|
|
317
317
|
| FFmpeg | `-filters` row layout | Source |
|
|
318
318
|
|---|---|---|
|
|
@@ -356,6 +356,14 @@ npx ffmpeg-skill --dir ./my-skills
|
|
|
356
356
|
npx ffmpeg-skill --uninstall # remove from the selected targets (--codex also clears the older ~/.codex/skills location)
|
|
357
357
|
```
|
|
358
358
|
|
|
359
|
+
As a Claude Code plugin (no Node needed, updates with `claude plugin update`):
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
claude plugin install kajisho5/ffmpeg-skill
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
The plugin namespaces the skill as `ffmpeg-skill:ffmpeg-skill`; the manifest is [.claude-plugin/plugin.json](.claude-plugin/plugin.json) and its version follows every release automatically.
|
|
366
|
+
|
|
359
367
|
Without Node: clone this repository and copy `SKILL.md`, `scripts/`, `references/` and `mcp/` into your agent's skills directory.
|
|
360
368
|
|
|
361
369
|
After installing:
|
|
@@ -381,6 +389,10 @@ FFmpeg itself:
|
|
|
381
389
|
|
|
382
390
|
`doctor`'s own introspection calls (`ffmpeg -filters`/`-encoders`/`-bsfs`/`-version`) time out after 10s and report `failed` rather than hanging forever — those are meant to be fast. Every tool's actual media-processing `ffmpeg` invocation (cut, fit, caption, ...) has no timeout: a legitimate `--accurate` re-encode of a long file can genuinely take a long time, so bounding it would risk killing real work. `-nostdin` is always passed, so a hung ffmpeg process waiting on stdin cannot happen; a caller that needs a hard ceiling on a specific job should apply its own external timeout/kill around that one invocation.
|
|
383
391
|
|
|
392
|
+
## Stability
|
|
393
|
+
|
|
394
|
+
1.x keeps every tool name, CLI argument, JSON output key and exit code working: nothing is removed or renamed, and nothing optional becomes required, until 2.0. The full list of what is promised and what is not, and the three-step deprecation policy, is in [docs/contract.md](docs/contract.md#stability-guarantee-1x). It is enforced by a test that pins every tool's argument names against a snapshot, so a breaking change fails CI instead of slipping into a patch.
|
|
395
|
+
|
|
384
396
|
## Development
|
|
385
397
|
|
|
386
398
|
```bash
|
|
@@ -395,7 +407,7 @@ CI (`.github/workflows/ci.yml`) runs on every pull request and on pushes to `mai
|
|
|
395
407
|
|
|
396
408
|
`tests/test_contract.py` runs on all three OSes, but a handful of its tests build a fake `ffmpeg` as a `#!/bin/sh` script on a PATH shim to force specific FFmpeg 6/7/8/9 fixture layouts through `doctor`'s parser — that technique isn't portable to Windows, so `test_dry_run_never_runs_ffmpeg_and_writes_nothing` and the whole `DoctorDetectionTests` class (fixture-driven layout parsing) are individually `skipIf`'d there and show as `skipped`, not silently absent, in the Windows job's log. Everything else — contract schema, `reencodes_*`, `doctor.tools`, MCP derivation, and every tool exercised through the contract, including `cut.py`'s provenance fields — runs against the real Windows `ffmpeg` on every PR. See [references/ci-platform-pitfalls.md](references/ci-platform-pitfalls.md) for this and other per-OS behaviour differences already diagnosed, before spending a CI cycle re-diagnosing a platform-only failure.
|
|
397
409
|
|
|
398
|
-
**Releasing** is fully automated end to end, including the version number itself — a PR doesn't need to touch `package.json`, `docs/contract.md`, or `CHANGELOG.md` at all. Once a PR merges to `main`, `.github/workflows/release.yml` takes it from there: if nobody bumped the version by hand, it
|
|
410
|
+
**Releasing** is fully automated end to end, including the version number itself — a PR doesn't need to touch `package.json`, `docs/contract.md`, or `CHANGELOG.md` at all. Once a PR merges to `main`, `.github/workflows/release.yml` takes it from there: if nobody bumped the version by hand, it resolves the next version ([.github/scripts/resolve_version.py](.github/scripts/resolve_version.py)) from the labels on every PR merged since the last tag (`minor`/`feature`/`enhancement` → minor, `fix`/`bug`/`patch` → patch, an unlabeled PR defaults to `patch`; a PR whose only labels are `chore`, `ci`, `docs` or `dependencies` is not releasable, so a merge that changes no shipped file releases nothing). Most PRs don't need a label added by hand: `release-drafter.yml`'s `autolabel` job applies one automatically from the PR's title/changed files (`Fix ...` → `fix`, `Add ...`/`feat ...` → `feature`, `docs`/`.md`/`.github/`/`build(deps)` changes → `chore`) as soon as it's opened — add a label yourself only to override that. **A major version is never chosen automatically**: no label rule produces `major`, and the workflow refuses to auto-bump across a major boundary even if someone applies that label — a real major release is a deliberate `package.json` bump in a PR, which the manual path below already handles. It bumps `package.json` and `docs/contract.md`, writes a `CHANGELOG.md` section listing those PRs (and any issues they closed), and pushes that commit to `main` itself. Either way — auto-bumped or hand-bumped in the PR — it then creates the `vX.Y.Z` tag, publishes a GitHub Release with notes extracted from `CHANGELOG.md`'s matching section, and publishes the package to npm. A PR that still wants to write its own version bump and `CHANGELOG.md` prose (e.g. to explain the "why" of a release by hand) can — the automation only fills in when nobody made that call already. A push to `main` with nothing new to release is a no-op. npm publishing needs an `NPM_TOKEN` repo secret (an npm access token with publish rights on this package) — without it the tag and GitHub Release still happen, only the npm step is skipped. A repo that depends on this one (an editing skill, an agent) should pin an `ffmpeg-skill` version by tag or npm version, not by tracking `main` — a merged-but-not-yet-released commit on `main` can be ahead of the last published npm version for the few minutes between merge and this workflow completing.
|
|
399
411
|
|
|
400
412
|
Contributing a change: see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
401
413
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ffmpeg-skill",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Agent Skill that gives coding agents (Claude Code, Cursor, Codex) a local video editor: 40 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",
|
|
@@ -103,3 +103,58 @@ shallow clone leaves no tag reachable to diff against, which would make the test
|
|
|
103
103
|
skip itself in CI, not fail). If this test ever needs to skip a genuinely changelog-less
|
|
104
104
|
closed issue (a pure process note, a duplicate, a revert of an unreleased change), name the
|
|
105
105
|
exemption in the test itself with a reason — don't just widen the regex or drop the check.
|
|
106
|
+
|
|
107
|
+
## Automation that can publish must not take its "major" cue from text it did not write
|
|
108
|
+
|
|
109
|
+
On 2026-09-11, three routine Dependabot merges (`actions/upload-artifact` 4→7,
|
|
110
|
+
`actions/setup-node` 4→7, `dependabot/fetch-metadata` 2→3) were published to npm as
|
|
111
|
+
**1.0.0, 1.0.1 and 1.0.2**. The release pipeline (`release.yml`, since #129) resolves the next
|
|
112
|
+
version from PR labels; an autolabeler rule added the same day applied `major` to any PR whose
|
|
113
|
+
*body* contained the literal breaking-change marker. Dependabot PR bodies quote the upstream
|
|
114
|
+
project's release notes verbatim, and upload-artifact's v5.0.0 notes contain exactly that
|
|
115
|
+
phrase — about *their* Node runtime, nothing to do with this package. One label, three
|
|
116
|
+
accidental majors, in nine minutes, with every job green.
|
|
117
|
+
|
|
118
|
+
Two things made it worse than one bad rule: every chore merge released at all (so the first
|
|
119
|
+
accident was followed by two more before anyone looked), and nothing in the pipeline treated
|
|
120
|
+
"the major number changed" as different from any other bump.
|
|
121
|
+
|
|
122
|
+
Fixes (this commit): no autolabeler rule produces `major` any more; `chore`/`ci`/`docs`/
|
|
123
|
+
`dependencies` PRs are excluded from version resolution so they release nothing; `release.yml`
|
|
124
|
+
refuses to auto-bump across a major boundary regardless of labels; and the release job is
|
|
125
|
+
serialised (`concurrency`) so back-to-back merges cannot race on the bump push.
|
|
126
|
+
|
|
127
|
+
The general rule: a pipeline that publishes must never derive an irreversible decision (a
|
|
128
|
+
major bump, a publish, a tag) from text it did not author — PR bodies, commit messages and
|
|
129
|
+
release notes are quotations as often as they are statements. Match on labels a person
|
|
130
|
+
applied, or on files changed, and make the irreversible step refuse anything surprising rather
|
|
131
|
+
than assume the surprise was intended. And after wiring any such automation, watch the first
|
|
132
|
+
few real runs' *results* (npm, tags) rather than their exit codes: the three runs here were
|
|
133
|
+
"success" by every check the job had.
|
|
134
|
+
|
|
135
|
+
## An action input that does not exist is a warning, not an error -- and "excluded from the notes" is not "no release"
|
|
136
|
+
|
|
137
|
+
The fix for the accidental majors above (#145) still released **1.0.4** for its own,
|
|
138
|
+
workflow-only merge. Two assumptions in `release.yml` were wrong and nothing checked either:
|
|
139
|
+
|
|
140
|
+
- `release-drafter/release-drafter@v6` was called with `dry-run: true` to "compute the next
|
|
141
|
+
version read-only". That action has no `dry-run` input. GitHub Actions logs
|
|
142
|
+
`Unexpected input(s) 'dry-run'` as a *warning* and runs the step anyway -- so every release
|
|
143
|
+
run had been rewriting the draft release live, and the "read-only" in the comment was fiction.
|
|
144
|
+
- `exclude-labels` in `release-drafter.yml` was expected to make a chore-only merge resolve to
|
|
145
|
+
the same version as the last tag. It only removes those PRs from the draft *notes*; the
|
|
146
|
+
version resolver still applies `default: patch` and reports last+patch. The workflow's "same
|
|
147
|
+
version → no-op" guard therefore never fired.
|
|
148
|
+
|
|
149
|
+
Both were visible in the first run's log and in the action's documented inputs, and both were
|
|
150
|
+
missed because the PR's test plan verified the YAML *parsed* and the config *contained* the
|
|
151
|
+
intended keys -- not that the action *did* what the comment claimed. Fixed by taking the
|
|
152
|
+
decision away from the action: `.github/scripts/resolve_version.py` reads the merged PRs'
|
|
153
|
+
labels through `gh api`, returns nothing when nothing is releasable, refuses `major`, and has
|
|
154
|
+
a unit test in `tests/test_contract.py` with fake label data for each rule.
|
|
155
|
+
|
|
156
|
+
The general rule, twice over now: when wiring a third-party action, read its `action.yml`
|
|
157
|
+
inputs (or `Unexpected input(s)` in the first log) before trusting a parameter, and treat
|
|
158
|
+
any step whose output decides an irreversible action as something to unit-test with fixed
|
|
159
|
+
inputs, not something to confirm by reading its YAML. And watch the first real run's
|
|
160
|
+
*effect* (tags, npm), which is how both incidents were actually noticed.
|