Turning up an AI promo’s audio by ear is hard to reproduce. Build a small measurement workflow instead: analyze the source, use those measurements for a second-pass render, then measure the saved output. Keep listening review separate from the numbers.

This guide supplies a runnable FFmpeg drill, a reusable local-file normalization function and explicit failure checks. The worked example uses an authored synthetic tone, not a voice clone, customer recording or assessment of AI speech quality. It establishes that the measurement/render/re-measure path works in the tested environment.

A delivery contract you can inspect

Orbit Desk is a fictional business preparing an AI-assisted promo. For this exercise its audio handoff is a 48 kHz PCM WAV, with an integrated-loudness target of -16 LUFS, a maximum true-peak target of -1.5 dBTP and a loudness-range setting of 11 LU. Those are chosen example settings, not a claim about any platform’s required specification. Replace them with the actual delivery contract before using the function on a production asset.

Integrated loudness describes the program-level measurement; true peak is a separate peak measurement. The test does not require the peak to reach the ceiling. In the fixture below, it stays well below it. A loudness-range setting is also not a promise that the output will have exactly that measured range.

Executed check Observed result
Source integrated loudness -28.04 LUFS
Saved WAV integrated loudness -16.03 LUFS
Saved WAV true peak -8.88 dBTP
Reported second-pass mode linear
Saved audio format Mono, 48 kHz, 16-bit PCM, 24 seconds
Measurements from the executed synthetic fixture on FFmpeg 8.1.1. This table is technical evidence, not a listening-quality score.

What the two passes do

The versioned filter manual’s loudnorm section documents both single- and double-pass operation. The first pass collects source measurements. The render passes measured integrated loudness, true peak, range and threshold back to the filter, along with the offset. The output sample rate is explicitly set because dynamic mode may upsample internally.

Requesting linear mode does not guarantee it. The n8.1.1 implementation checks the supplied measurements and eligibility before entering that mode. The drill records the reported normalization type. It then runs a new measurement on the saved WAV and reads that measurement’s input values; it does not substitute a predicted filter output for inspection of the delivered file.

Run the drill without touching a business recording

Prerequisites: Python 3.14.3 and an already installed FFmpeg executable on PATH with loudnorm and PCM WAV support. The executed environment was Windows with FFmpeg 8.1.1-full_build-www.gyan.dev. No tools were installed or upgraded, no API was called and no paid service was used. FFmpeg is a compiled dependency, not a Python package bundled by this script.

Save the following as loudness_drill.py in a disposable directory. The demo requires a new child directory and refuses an existing one. It creates synthetic 440 Hz tones at three levels, a silence fixture and an invalid-media fixture. It never plays audio. Do not mistake the tone for an actual AI voiceover test.

"""Two-pass loudness drill; local trusted media only, no playback."""
from pathlib import Path
import hashlib
import json
import math
import shutil
import struct
import subprocess
import sys
import wave

TARGET = 'loudnorm=I=-16:TP=-1.5:LRA=11'


def run(args, logs):
    executable = shutil.which('ffmpeg')
    if not executable:
        raise RuntimeError('FFmpeg must already be installed and on PATH')
    result = subprocess.run([executable, '-nostdin', *map(str, args)],
                            capture_output=True, text=True, timeout=90)
    logs.append({'args': list(map(str, args)), 'returncode': result.returncode,
                 'stdout': result.stdout, 'stderr': result.stderr})
    if result.returncode:
        raise RuntimeError('FFmpeg failed:\n' + result.stderr)
    return result


def stats(stderr):
    start = stderr.rfind('{')
    if start < 0:
        raise ValueError('Missing loudnorm JSON')
    result, _ = json.JSONDecoder().raw_decode(stderr[start:])
    for key in ('input_i', 'input_tp', 'input_lra', 'input_thresh', 'target_offset'):
        if not math.isfinite(float(result[key])):
            raise ValueError('Non-finite loudness: silence or unusable measurement')
    return result


def measure(source, logs):
    result = run(['-hide_banner', '-i', Path(source).resolve(), '-map', '0:a:0',
                  '-af', TARGET + ':print_format=json', '-f', 'null', '-'], logs)
    return stats(result.stderr)


def normalize(source, destination, logs):
    source, destination = Path(source).resolve(), Path(destination).resolve()
    if not source.is_file():
        raise ValueError('Source must be an existing local file')
    if destination.exists():
        raise FileExistsError('Choose a new output path')
    if destination.suffix.lower() != '.wav':
        raise ValueError('This example produces PCM WAV only')
    before = measure(source, logs)
    options = ':'.join([
        TARGET, 'measured_I=' + before['input_i'],
        'measured_TP=' + before['input_tp'], 'measured_LRA=' + before['input_lra'],
        'measured_thresh=' + before['input_thresh'],
        'offset=' + before['target_offset'], 'linear=true', 'print_format=json'])
    second = run(['-hide_banner', '-n', '-i', source, '-map', '0:a:0', '-af', options,
                  '-ar', '48000', '-c:a', 'pcm_s16le', destination], logs)
    applied = stats(second.stderr)
    after = measure(destination, logs)
    return {'before_i': float(before['input_i']), 'after_i': float(after['input_i']),
            'after_tp': float(after['input_tp']),
            'normalization_type': applied['normalization_type']}


def fixture(path, silent=False):
    rate = 48000
    data = bytearray()
    for n in range(rate * 24):
        t = n / rate
        amplitude = (0.03, 0.09, 0.05)[min(int(t // 8), 2)]
        fade = min(1.0, t / .02, (24 - t) / .02)
        sample = 0 if silent else int(32767 * amplitude * fade * math.sin(2 * math.pi * 440 * t))
        data.extend(struct.pack('<h', sample))
    with wave.open(str(path), 'wb') as audio:
        audio.setparams((1, 2, rate, 0, 'NONE', 'not compressed'))
        audio.writeframes(data)


def demo(folder):
    if not __debug__:
        raise RuntimeError('Run without Python optimization for drill assertions')
    folder = Path(folder)
    folder.mkdir()  # Refuses an existing directory, including previous drill results.
    logs = []
    version = run(['-version'], logs).stdout.splitlines()[0]
    source, output = folder/'tone.wav', folder/'normalized.wav'
    fixture(source)
    result = normalize(source, output, logs)
    assert abs(result['after_i'] - (-16)) <= .3, result
    assert result['after_tp'] <= -1.4, result
    checks = ['saved WAV meets example loudness/peak tolerances']
    with wave.open(str(output), 'rb') as audio:
        assert (audio.getnchannels(), audio.getsampwidth(), audio.getframerate()) == (1, 2, 48000)
        assert abs(audio.getnframes()/audio.getframerate() - 24) < .01
    checks.append('saved WAV format and duration verified')
    original_hash = hashlib.sha256(output.read_bytes()).hexdigest()
    try:
        normalize(source, output, logs)
    except FileExistsError:
        assert hashlib.sha256(output.read_bytes()).hexdigest() == original_hash
    else:
        raise AssertionError('Existing output was not rejected')
    checks.append('existing output rejected and bytes preserved')
    silence = folder/'silence.wav'
    fixture(silence, silent=True)
    try:
        normalize(silence, folder/'silence-output.wav', logs)
    except ValueError as exc:
        assert 'Non-finite' in str(exc)
        assert not (folder/'silence-output.wav').exists()
    else:
        raise AssertionError('Silence was not rejected')
    checks.append('silence rejected before render')
    invalid = folder/'invalid.wav'
    invalid.write_bytes(b'not a media file')
    try:
        normalize(invalid, folder/'invalid-output.wav', logs)
    except RuntimeError:
        assert logs[-1]['returncode'] != 0
        assert not (folder/'invalid-output.wav').exists()
    else:
        raise AssertionError('Invalid media was not rejected')
    checks.append('invalid media decode rejected')
    summary = {'passed': True, 'measurements': result, 'checks': checks}
    (folder/'evidence.json').write_text(json.dumps(
        {**summary, 'python': sys.version, 'ffmpeg': version, 'logs': logs}, indent=2), encoding='utf-8')
    return summary


if __name__ == '__main__':
    if len(sys.argv) == 3 and sys.argv[1] == '--demo':
        print(json.dumps(demo(sys.argv[2]), indent=2))
    elif len(sys.argv) == 3:
        logs = []
        print(json.dumps(normalize(sys.argv[1], sys.argv[2], logs), indent=2))
    else:
        raise SystemExit('Usage: python loudness_drill.py --demo NEW_DIRECTORY or INPUT NEW.wav')

Run this command. It leaves the WAV files and full FFmpeg command/stderr evidence in demo-output. The five named checks must pass. Do not run Python with optimization enabled; the demo refuses it so its assertions cannot disappear.

python -X utf8 loudness_drill.py --demo demo-output

The executed summary was:

{
  "passed": true,
  "measurements": {
    "before_i": -28.04,
    "after_i": -16.03,
    "after_tp": -8.88,
    "normalization_type": "linear"
  },
  "checks": [
    "saved WAV meets example loudness/peak tolerances",
    "saved WAV format and duration verified",
    "existing output rejected and bytes preserved",
    "silence rejected before render",
    "invalid media decode rejected"
  ]
}

Adapt the function for the real handoff

The script also accepts two positional paths: an existing local input and a new output whose extension is .wav. That path goes through the same normalize function used by the drill. Work on a copy and review the returned measurements. The example selects the first audio stream, does not request a channel conversion and writes 16-bit PCM at 48 kHz. It is not a video remuxer, stem mixer or batch publisher.

Use trusted local media. A failed render can leave a partial output, so inspect that file and choose a fresh destination before retrying. The source remains untouched, and FFmpeg receives -n to refuse overwriting a destination even after the Python precheck. The demo verifies that an existing output’s bytes remain identical. Silence is rejected when its measurement is non-finite; invalid media causes a real decode failure. These checks do not constitute a sandbox for hostile media or a parser guarantee for every FFmpeg version.

Keep the exact source and filter settings between passes. After final mixing, compression, video muxing or re-encoding, measure the actual delivery file again. Stereo, mono and dual-mono playback assumptions need a deliberate choice; the fixture is mono and uses the filter’s default dual_mono=false. It does not establish the right channel policy for your distribution target.

Listen to the finished result before release: intelligibility, clicks, noise, pacing, music balance and whether the voice suits the business are outside this automated check. Matching a loudness number cannot repair clipped source audio or establish voice/asset rights. For the surrounding creative handoff, use the campaign release review and the video delivery checks.

Versions, evidence and limits

The five checks executed against generated local fixtures, and the final article’s extracted code was reproduced in a separate disposable directory. Public code is compared with independent preserved hashes and run again after publication. The original saved output passed a chosen tolerance of 0.3 LU around -16 LUFS and a true-peak check of at most -1.4 dBTP; these are drill assertions, not certification limits.

No speech, music mix, AI model, creative application host, platform upload or customer integration was tested. The measurements come from FFmpeg itself, not an independent calibrated meter. The synthetic fixture establishes local pipeline behavior, not perceived audio improvement, commercial compliance or compatibility with every codec/build.

The FFmpeg license overview explains that licensing depends on build options. The installed build reports –enable-gpl and –enable-version3; this guide does not redistribute its binary. Keep the actual build’s notices and terms with any deployment review.

Jason Ead publishes AlgoThoughts AI News and connects practical engineering with My Biz Heroes: websites, automation, workflows, AI tools and training. Explore Jason’s work. The octopus DJ cover is generated conceptual artwork; the interior table reports measured fixture results.