mix-master
Canonical short-form-ad audio mix in one FFmpeg pass. VO loudnorm + 3.0× per-clip + 2.0× mix, music 0.13 base + apad+afade, sidechain compress 20:1 @ 0.01, climax line +20%, optional video-to-music duration sync. Replaces the reactive multi-round tuning that cost v03 6+ passes.
Description awaits a fresh source check.
Try your first task
A source-grounded starter task has not been verified for this skill yet.
SKILL.md
name: mix-masterdescription: Canonical short-form-ad audio mix in one FFmpeg pass. VO loudnorm + 3.0× per-clip + 2.0× mix, music 0.13 base + apad+afade, sidechain compress 20:1 @ 0.01, climax line +20%, optional video-to-music duration sync. Replaces the reactive multi-round tuning that cost v03 6+ passes.status: supersededsuperseded_by: audio-mix@1.0.1, sound-layer@1.0.4Superseded: the video kit now does this with the audio-mix part, version 1.0.1 and the sound-layer part, version 1.0.4, in the parts folder of this repository. Its mix mode is the audio-mix step (voice, a ducked bed and timed effects) and its finish mode is the sound layer, which levels every video to -14 LUFS with the true peak at or below -1 dBTP. This atom stays, unchanged in behaviour, for skills outside the kit until they move; its scripts still run.
mix-master
Purpose
The v03 Ironman LEARNINGS describe 6+ tuning rounds because no default mix protocol existed: round 1 music too loud, round 2 VO too quiet, round 3 Whisper mis-transcribed, round 4 sidechain ratio finally right, round 5 music ended too early, round 6 climax sat at the same loudness as the rest.
This atom encodes the protocol that landed and ships it as the default. Operators tune by flag; they don't redesign the chain.
Inputs
--video <path>— video file with no audio (or whose audio gets dropped) (required)--vo <path,path,...>— comma-separated list of per-scene VO mp3s in scene order (required)--vo-starts <ms,ms,...>— start time in milliseconds for each VO clip on the timeline (required, same length as--vo)--music <path>— music bed file (required)--sfx <path,path,...>— optional comma-separated SFX list--sfx-starts <ms,ms,...>— optional SFX start times--output <path>— output mp4 (required)--total-duration <s>— target total duration in seconds (required — video AND music are sync-fit to this)--vo-boost-line <N>— 1-indexed VO clip that is the climax; gets +20% additional volume (default unset)--vo-volume <float>— per-clip VO volume multiplier (default3.0)--vo-mix-volume <float>— extra mix-bus volume on VO (default2.0)--music-volume <float>— base music volume (default0.13)--music-swell-volume <float>— peak music volume during apad/swell (default0.21)--sidechain-ratio <float>— sidechain ratio, capped at 20 by FFmpeg (default20)--sidechain-threshold <float>— sidechain threshold (default0.01)--target-i <lufs>— VO loudnorm integrated target (default-16)--target-tp <dbtp>— VO loudnorm true-peak (default-1.5)--target-lra <lu>— VO loudnorm LRA (default11)
Workflow
- Validate inputs —
--voand--vo-startsmust be same length;--sfxand--sfx-startsmust be same length; ffmpeg + ffprobe must be on PATH. - Probe music duration (
ffprobe). If music shorter than--total-duration, appendapad=pad_dur=2.0then trim back to target. If music longer, trim withafade=outover last 1.5s. - Build a
filter_complex_scriptfile (long chains exceed shell arg limits):- Per-VO:
[N:a]loudnorm=I=...:TP=...:LRA=...,adelay=<start>|<start>,volume=<vo_volume>[voN]; - Climax VO (if
--vo-boost-line N): volume becomesvo_volume * 1.2. - Mix all VO:
amix=inputs=K:duration=longest,volume=<vo_mix_volume>[vo_pre]; - Split VO for sidechain key:
[vo_pre]asplit=2[vo_final][vo_sc]; - Music base:
apad=pad_dur=2.0,atrim=0:<TOTAL>,afade=t=out:st=<TOTAL-1.5>:d=1.5,volume=<music_volume>[music_base]; - Sidechain:
[music_base][vo_sc]sidechaincompress=threshold=<thresh>:ratio=<ratio>:attack=20:release=600[music_ducked]; - SFX (optional): per-SFX
adelay+volumethen mix into[sfx_bus]. - Final:
amix=inputs=<2 or 3>:duration=longest[a_out].
- Per-VO:
- Run a single ffmpeg invocation that consumes video, all VO, music, all SFX, applies the filter chain, and outputs the mixed mp4 (video re-encoded only if duration trim needed; otherwise
-c:v copy). - Run a verification probe: ffprobe loudness summary on
[a_out], writemanifest.jsonandverification.md.
Output
<output>— mixed mp4 with VO + music + SFX.manifest.json— chain parameters, vo timing, climax line, integrated LUFS after.verification.md— pass/fail report (integrated LUFS within ±0.5 of expected target, no clipping).
Quality Checks
- Output integrated LUFS within ±0.5 of
<target-i>(post-protocol; should be approximately -14 social-ready after the boosts). - True-peak strictly below
<target-tp>(no clipping). - Output duration equals
<total-duration>± 100ms. - Music tail has
afade=out— last 100ms RMS < -30 dB. - If
--vo-boost-linewas set, the corresponding clip'svolume=value isvo_volume * 1.2.
Failure Modes
- Sidechain ratio > 20 — FFmpeg errors "Result too large." Cap at 20 (the protocol default).
- VO and starts arrays mismatched — fail fast at validation.
- Music too short and
apadinsufficient — emit a warning; video may end before music. Operator should re-source music or shorten--total-duration. - VO clip > 5KB but actually error JSON — out of scope here;
create-clipsPhase 4 catches it. - Re-encoding video unnecessarily — if total duration matches input video duration, use
-c:v copyto preserve quality.
Example
scripts/mix.sh \
--video acme/ads/video-01/edits/master-no-audio.mp4 \
--vo "acme/ads/video-01/audio/vo-scene-01.mp3,...,acme/ads/video-01/audio/vo-scene-14.mp3" \
--vo-starts "0,2000,3500,...,28000" \
--music acme/ads/video-01/audio/music-cue-01.wav \
--output acme/ads/video-01/edits/master-final.mp4 \
--total-duration 30.0 \
--vo-boost-line 14
Consumed by: video-orchestrator/edit-video Phase 3–5, auto-fix-from-review-notes dispatcher (when vo-intelligibility or music-vo-balance polish-notes items reference mix-master).