feat(cli): add normalize-audio to match one clip's loudness to another - #3306
Draft
miguel-heygen wants to merge 1 commit into
Draft
feat(cli): add normalize-audio to match one clip's loudness to another#3306miguel-heygen wants to merge 1 commit into
miguel-heygen wants to merge 1 commit into
Conversation
miguel-heygen
force-pushed
the
cli-normalize-audio
branch
from
August 17, 2026 20:56
084108d to
26e67d2
Compare
Measures two authored `<audio>` clips with FFmpeg's integrated EBU R128 loudness and writes the target's matching `data-volume`, leaving the reference untouched. The measurement is bounded to the window the composition actually plays. `data-end` bounds a clip's timeline window just as `data-duration` does, and `-ss`/`-t` belong before `-i`: after it they bound the OUTPUT, and with `-f null` there is none, so ebur128 keeps integrating past the clip. On a fixture whose played window is -61.8 LUFS inside a file that measures -27.9 whole, either mistake reports a loudness the composition never plays and "corrects" an already-matched clip by tens of dB. Two EBU R128 passes run between reading the composition and writing it, each bounded only by a two-minute timeout, and the skill docs tell agents to keep Studio open meanwhile — so the attribute patch is re-applied to a fresh read and written through a temp file and a rename. Under `--json` the failures are documents too: an agent doing `JSON.parse(stdout)` on a bare error line throws. A pair needing more than the +12 dB ceiling has a source-file problem rather than a mixer one — mixer gain raises the noise floor with the signal — so the refusal names the remedy.
miguel-heygen
force-pushed
the
cli-normalize-audio
branch
from
August 17, 2026 20:57
26e67d2 to
63ffe8d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Measures two authored
<audio>clips with FFmpeg's integrated EBU R128 loudness and writes the target's matchingdata-volume, leaving the reference untouched. Dry run by default;--writepersists.Measuring the window the composition actually plays
Two ways to get this wrong, both of which produce a plausible-but-wrong gain:
data-endbounds a clip's timeline window just asdata-durationdoes. The parser, the runtime and the render mixer all honour it.-ss/-tbelong before-i. After it they bound the output, and with-f nullthere is no output worth bounding — ffmpeg keeps feeding the filter graph andebur128integrates audio the clip never plays.On a fixture whose played window is −61.8 LUFS inside a file that measures −27.9 LUFS whole, either mistake reports the wrong number and
--write"corrects" an already-matched clip by tens of dB.Writing safely
Two EBU R128 passes run between reading the composition and writing it, each bounded only by a two-minute timeout, and the skill docs tell agents to keep Studio open on the project meanwhile. The attribute patch is re-applied to a fresh read and written through a temp file and a rename, so a concurrent Studio edit is not reverted and a crash cannot leave a half-written composition.
Agent contract
Under
--jsonthe failures are documents too — an agent doingJSON.parse(stdout)on a bare error line throws. The command is registered in the curated--helplisting so an agent enumerating capabilities can discover it.A pair needing more than the +12 dB authoring ceiling has a source-file problem rather than a mixer one — mixer gain raises the noise floor with the signal — so the refusal names the remedy instead of just declining.
Stack
Based on
u1-audio-gain-core, whose shared gain module it imports. Retarget tomainbefore merging. Independent ofu2-studio-gain-surface; the two may land in either order.