ffmpeg-skill 3.2.0 → 3.2.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/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 (`2.0`) | a key is renamed, removed or changes meaning |
24
- | `skill.version` | the npm / package.json version (`3.2.0`) | any release |
24
+ | `skill.version` | the npm / package.json version (`3.2.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`
@@ -149,7 +149,7 @@ JSON would have been rewritten for that. The flat keys, typed per tool in `outpu
149
149
  "deprecated": [],
150
150
  "removed": [{"what": "...", "since": "1.10.0", "replacement": "...", "removed_in": "2.0.0", "where": "cli | json | mcp | behaviour"},
151
151
  {"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"}],
152
- "skill": {"id": "ffmpeg-skill", "version": "3.2.0", "execution_mode": "local", "kind": "execution",
152
+ "skill": {"id": "ffmpeg-skill", "version": "3.2.1", "execution_mode": "local", "kind": "execution",
153
153
  "entrypoints": {"cli": "...", "mcp": "...", "contract": "...", "doctor": "..."},
154
154
  "not_provided": ["AI reasoning", "decisions", "production plans", "project IR", "approvals", "network access", "transcription engine"]},
155
155
  "requirements": {"python": ">=3.10 (standard library only)", "ffmpeg": ">=6.1", "ffprobe": ">=6.1"},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ffmpeg-skill",
3
- "version": "3.2.0",
3
+ "version": "3.2.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",
@@ -1851,6 +1851,11 @@ audio.py INPUT [--voice [light|medium|strong] | --denoise [--denoise-strength 25
1851
1851
  [--effects FILE [--effects-volume -14]] [--stereo-widen 0..1]
1852
1852
  [--fade-in S] [--fade-out S] [--stereo | --mono | --downmix] [--replace FILE] [-o OUT]
1853
1853
  ```
1854
+ `--denoise` is `afftdn=nf=-N:tn=1`, where N is `--denoise-strength`, the noise
1855
+ floor in dB below full scale (20..60, default 25). It is the starting floor that
1856
+ `tn=1` then tracks, not an amount removed; ffmpeg's `nf` accepts -80..-20 only, so
1857
+ values under 20 are refused before ffmpeg runs.
1858
+
1854
1859
  `--voice` takes a strength; a bare `--voice` is `medium`, the chain it has always
1855
1860
  produced. The exact filter chains:
1856
1861
 
package/scripts/audio.py CHANGED
@@ -161,7 +161,7 @@ def main() -> int:
161
161
  ap.add_argument("-o", "--output", help="output file (default: <name>_audio.<ext>)")
162
162
  clean = ap.add_argument_group("clean-up")
163
163
  clean.add_argument("--denoise", action="store_true", help="FFT noise reduction (afftdn, adaptive)")
164
- clean.add_argument("--denoise-strength", type=float, default=25.0, help="noise floor in dB to remove, 10..60 (default 25)")
164
+ clean.add_argument("--denoise-strength", type=float, default=25.0, help="afftdn noise floor in dB below full scale, 20..60 (default 25; ffmpeg accepts nf -80..-20, and tn=1 then tracks the floor from this starting point)")
165
165
  clean.add_argument("--voice", nargs="?", const="medium", choices=["light", "medium", "strong"], default=None,
166
166
  help="speech preset (default medium when the flag is given bare): light = highpass 80 Hz + gentle compression; "
167
167
  "medium = highpass, de-esser, denoise, gentle compression; strong = medium plus a harder de-esser, a second "
@@ -305,8 +305,9 @@ def main() -> int:
305
305
  if args.voice:
306
306
  fx.append(VOICE_LEVELS[args.voice])
307
307
  elif args.denoise:
308
- if not 10 <= args.denoise_strength <= 60:
309
- die(f"--denoise-strength must be 10..60 (dB of noise floor to remove), got {args.denoise_strength:g}")
308
+ # afftdn's nf accepts -80..-20 only; 10..19.99 used to pass here and then fail inside ffmpeg.
309
+ if not 20 <= args.denoise_strength <= 60:
310
+ die(f"--denoise-strength must be 20..60 (afftdn noise floor in dB below full scale), got {args.denoise_strength:g}")
310
311
  fx.append(f"afftdn=nf=-{args.denoise_strength:g}:tn=1")
311
312
  if args.gain:
312
313
  fx.append(f"volume={args.gain:g}dB")