github kajisho5/ffmpeg-skill v1.13.0

one hour ago

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

Added

  • audio.py --voice takes a strength: --voice light|medium|strong, with a bare --voice meaning medium — byte for byte the chain it has always produced, so existing calls and MCP requests are unchanged. light is highpass=f=80,acompressor=threshold=-18dB:ratio=2:attack=5:release=80:makeup=1 (rumble and level only, for a good room); medium is highpass=f=80,deesser=i=0.4,afftdn=nf=-25:tn=1,acompressor=threshold=-18dB:ratio=3:attack=5:release=80:makeup=2; strong is medium followed by deesser=i=0.6,acompressor=threshold=-24dB:ratio=4:attack=5:release=120:makeup=3,alimiter=limit=0.891251:level=disabled, for phone and laptop audio.
  • The ducking parameters are sayable: --duck-threshold DB (default −26.02 dBFS, the 0.05 linear the bed has used since 1.4, so the default filter string is unchanged), --duck-attack MS (20) and --duck-release MS (400) next to the existing --duck-amount. --json gains an audio block reporting the voice level, the stereo width, the effects bed and the duck settings the run actually used.
  • audio.py --stereo-widen 0..1 widens the stereo image (extrastereo=m=1+2*amount), applied after the channel layout is settled. It needs a real stereo source and says so: widening scales the side signal (L−R), which is exactly zero once a mono track has been duplicated to two identical channels, so a 1-channel input is refused (kind: input) whether or not --stereo is given, and more than two channels are refused unless --downmix folds them to stereo first — in which case the widening runs on the fold-down. Nothing is downmixed implicitly.
  • audio.py --effects FILE / --effects-volume DB mixes a third track (sound effects, atmos) into the bed. It is never ducked: effects are cut to the picture, so dipping them under speech would move them off their own frames.
  • loudness.py --json now reports the loudness range on both sides (measured.input_lra for the input, result.input_lra for the written file) and echoes the requested targets (lufs, tp, lra); --lra N is documented in references/scripts.md and the README.
  • check.py --platform podcast gains two informational rows: chapters (PASS when the container carries at least one marker, WARN none otherwise, with metadata.py --chapters as the fix) and channels (PASS for mono or stereo, WARN above — podcast players downmix 5.1 unpredictably). Neither can FAIL a delivery and neither appears for another platform.
  • render.py project keys: "audio": {"stems": {"dialogue": DB, "music": DB, "effects": DB}} names one level per element of the mix, mapping to --gain, --music-volume and --effects-volume (an explicit flag next to a stem wins; a stems effects level with no "audio": {"effects": "sfx.wav"} file is refused), and "chapters" — a chapters file path or an inline list of {"at": TIME, "title": STR} — runs metadata.py on the delivered file as the last stage before check, so the markers are in the file that ships. The stage plans like every other one: --dry-run/--plan prints the metadata.py command and lists chapters in stages, so the plan the user approves is the run. "audio": {"voice": "light"|"medium"|"strong"} picks the voice strength; true still means medium. Both new objects are validated against the project schema, so a misspelled stem or chapter key is refused by name.
  • The audio guards say what is missing instead of ignoring it: a duck parameter (--duck-threshold, --duck-attack, --duck-release, --duck-amount) without --duck, and --duck without --music, are refused (kind: input) the way a typed dynamics parameter without its switch already was. In a render.py project, an audio.stems.music level with no "music" file is refused like stems.effects without an "effects" file, and chapters entries (a missing at or title, a chapters file that does not exist) are validated before the first ffmpeg call rather than after the render has already delivered an unchaptered file.
  • tests/fixtures/mcp_tools.json regenerated: audio gains duck_threshold, duck_attack, duck_release, stereo_widen, effects and effects_volume and voice becomes a light|medium|strong enum. A client still sending the 1.12 boolean {"voice": true} keeps working: true is the bare flag, which is medium; docs/contract.md and audio.py --help say so.

Docs

  • Not shipped: a loudness.py --dialogue speech gate. loudnorm's EBU R128 integrated measurement already applies the −70 LUFS absolute and −10 LU relative gates, so gating the measurement on speech spans moved the result by at most 0.6 LU on every fixture (including one that is half digital silence) — inside check.py's own ±1 LU tolerance — for the cost of a second decode pass. references/gotchas.md#loudness-and-ambience records it.
  • docs/roadmap.md marks 1.13.0 done and records the decision not to add audio.py --chapters: metadata.py episode.mp4 --chapters chapters.txt already writes chapter markers with every stream copied, and a second spelling inside a tool that re-encodes the audio would be the worse one. render.py's chapters key is the project-level answer instead.
  • feat: the audio bed — voice levels, stereo widen, duck parameters, effects stem, loudness --dialogue, podcast rows, project chapters (#221)
  • evals: 18 prompts in th hi he ru el vi id tr it, 8 delivery prompts, 9 trigger queries, grader rules (#220)
  • docs: eval iteration 13 at 1.12.0 (50 prompts, nine languages) (#219)

What's Changed

  • docs: eval iteration 13 at 1.12.0 (50 prompts, nine languages) by @claude[bot] in #219
  • evals: prompts in th hi he ru el vi id tr it, delivery prompts, trigger queries by @claude[bot] in #220
  • feat: the audio bed — voice levels, stereo widen, duck parameters, effects stem, loudness --dialogue, podcast rows, project chapters by @claude[bot] in #221

Full Changelog: v1.12.0...v1.13.0

Don't miss a new ffmpeg-skill release

NewReleases is sending notifications on new releases.