ffmpeg-skill 2.8.6 → 2.8.7

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/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.8.6`) | any release |
24
+ | `skill.version` | the npm / package.json version (`2.8.7`) | 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.8.6", "execution_mode": "local", "kind": "execution",
122
+ "skill": {"id": "ffmpeg-skill", "version": "2.8.7", "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.8.6",
3
+ "version": "2.8.7",
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",
@@ -8,11 +8,20 @@
8
8
  "video-editing",
9
9
  "video-processing",
10
10
  "audio",
11
+ "audio-processing",
12
+ "subtitles",
13
+ "captions",
14
+ "transcoding",
15
+ "loudness",
11
16
  "agent-skill",
17
+ "agent-skills",
12
18
  "claude-code",
19
+ "claude-skill",
13
20
  "cursor",
14
21
  "codex",
22
+ "ai-agents",
15
23
  "mcp",
24
+ "mcp-server",
16
25
  "skill"
17
26
  ],
18
27
  "license": "MIT",
package/scripts/check.py CHANGED
@@ -163,13 +163,25 @@ def main() -> int:
163
163
  else:
164
164
  tags = (v.get("color_primaries"), v.get("color_transfer"))
165
165
  untagged = not tags[0] and not tags[1]
166
- if v.get("bt2020_or_hdr") or tags == ("bt709", "bt709") or args.platform in ("podcast", "custom"):
166
+ # The matrix (color_space) is a tag too (#325): a player that honours it decodes with it,
167
+ # so a file whose only tag is a BT.601 matrix is BT.601, not "untagged".
168
+ matrix = v.get("color_space")
169
+ matrix_709 = matrix in (None, "bt709")
170
+ shown = (f"{matrix} matrix, primaries/transfer untagged" if untagged and matrix else
171
+ f"{tags[0]}/{tags[1]}" + ("" if matrix in (None, tags[0]) else f", {matrix} matrix"))
172
+ if v.get("bt2020_or_hdr") or (tags == ("bt709", "bt709") and matrix_709) or args.platform in ("podcast", "custom"):
167
173
  row("colour", "PASS", f"{tags[0]}/{tags[1]}" + (f" ({v.get('hdr_format')})" if v.get("bt2020_or_hdr") else ""), "bt709/bt709 tagged (or HDR)")
168
- elif untagged and (v.get("bit_depth") or 8) == 8:
174
+ elif untagged and matrix_709 and (v.get("bit_depth") or 8) == 8:
169
175
  # untagged 8-bit video is treated as BT.709 by every player and platform; nothing to fix
170
- row("colour", "PASS", "untagged (players assume bt709)", "bt709/bt709 tagged (or HDR)")
176
+ row("colour", "PASS", shown + " (players assume bt709)" if matrix else "untagged (players assume bt709)", "bt709/bt709 tagged (or HDR)")
177
+ elif matrix in ("smpte170m", "bt470bg"):
178
+ # The pixels are BT.601-encoded. Nothing here converts the matrix: --retag bt709 and
179
+ # every SDR re-encode (bt709_tag_args) only relabel it, which shifts the colours.
180
+ row("colour", "WARN", shown, "bt709/bt709 tagged (or HDR)",
181
+ "re-export from the master as BT.709; color.py --retag bt709 (like any re-encode here) only relabels the matrix: right only if the pixels really are BT.709",
182
+ reason="a player that honours the BT.601 tag and one that ignores it on HD video (decoding it as BT.709) show different colours")
171
183
  else:
172
- row("colour", "WARN", f"{tags[0]}/{tags[1]}", "bt709/bt709 tagged (or HDR)", "color.py --retag bt709 when the picture really is 709; color.py --to-sdr when it is HDR")
184
+ row("colour", "WARN", shown, "bt709/bt709 tagged (or HDR)", "color.py --retag bt709 when the picture really is 709; color.py --to-sdr when it is HDR")
173
185
  elif args.platform not in ("podcast", "custom"):
174
186
  row("video", "FAIL", "none", "video stream", "")
175
187