| name | add-music-track |
| description | mix a music track under a silent video using ffmpeg. normalizes loudness to roughly -14 LUFS, applies a short fade in and out, and trims or loops the music to match video duration. use after a silent video master and a chosen music file are both ready. |
add-music-track
Purpose
Take a silent (or near-silent) video master and a music file, mix them with sane defaults, and write a final video with audible, well-balanced music. Loudness is normalized so the output sits at roughly -14 LUFS (platform-friendly for social and YouTube). Fades in and out are applied automatically, and music duration is matched to video duration by trimming or looping with a crossfade.
Inputs
--video <path> to a silent video (any common container).
--music <path> to a music file (mp3 or wav). Must be a real music track from find-music, create-programmatic-music-bed, or user-supplied with a documented source. Synthesized tones (ffmpeg -f lavfi -i sine=..., white noise, oscillator output) are rejected by default.
--out <path> for the mixed output.
- Optional
--target-lufs <float> (default -14).
- Optional
--fade-in <seconds> and --fade-out <seconds> (defaults: 0.5 and 1.0).
- Optional
--duck-vo <path> to a voiceover track to mix on top with side-chain ducking.
- Optional
--allow-placeholder-tone flag (off by default) for explicit smoke-test runs where the upstream user has acknowledged the music is a placeholder.
Workflow
- Verify music is real. Probe the music file with
ffprobe. Reject if the metadata or filename suggests a tone (lavfi, sine, tone, placeholder, synth), or if a spectral check shows a sustained pure-tone profile. Reject also if the run's caller manifest records music_source: placeholder. The run is blocked unless --allow-placeholder-tone is set.
- Probe video duration with
ffprobe and music duration.
- If music is shorter than video, loop with a crossfade so the join is seamless. If longer, trim with a fade-out at the cut.
- Apply loudnorm to bring music to the target LUFS. Apply fade-in at the start and fade-out near the end of the video.
- If
--duck-vo is provided, mix the VO at -16 LUFS and side-chain compress the music when VO is active.
- Run a single ffmpeg invocation that combines the silent video stream with the processed music (and optional VO) and writes the output with H.264 video and AAC audio.
- Write
manifest.json with input paths, durations, target LUFS, music provenance (real | placeholder-tone-allowed), and the final output path. Write verification.md.
Output
- A muxed video file at the requested output path.
manifest.json and verification.md.
Quality Checks
- Output duration matches the input video duration exactly.
- Output has an audible audio stream (probe confirms a non-silent track).
- Music provenance is recorded in the manifest and is not
placeholder for production runs.
- Measured loudness is within ±1 LUFS of the target.
- No clipping in peaks (true peak below -1 dBTP).
Failure Modes
- ffmpeg or ffprobe not installed — surface the error and mark blocked.
- Music file is corrupt or unreadable — surface the error.
- Music file is a synthesized tone or placeholder — mark
blocked and direct the caller to re-run find-music. Do not silently mix a tone under a delivery video.
- Video already has an audio stream — by default, replace it; surface the replacement in the manifest.
- Loudness target cannot be hit because the music is too quiet — re-encode at higher gain and re-measure.