Learn / Developer and release tools

How to batch-normalise audio loudness with ffmpeg

Measure LUFS and true peak, then normalise in two passes with ffmpeg loudnorm. Why +3 dB can break a -1 dBTP limit, and how to check every file.

By HiddenWay. Updated 17 October 2026. 6 min read.

If you deliver audio or video to clients, you will meet the same chore again and again: a folder of files at different levels that all need to match a target. Doing it by hand, file by file, is slow and easy to get wrong. ffmpeg, the free command-line tool, can measure and normalise loudness, and with a short script it can do a whole folder. This guide explains what the numbers mean, how the two-pass method works, and where the method can fail, with a worked example that shows why a simple gain increase is not always allowed.

Three measurements

Loudness work uses three numbers, defined in ITU-R Recommendation BS.1770, which sets out algorithms to measure audio programme loudness and true-peak audio level. The current version, BS.1770-5, was approved in November 2023.

  • Integrated loudness, in LUFS, is the loudness of the whole programme, weighted to match human hearing and averaged over its length.
  • Loudness range, in LU, describes how much the loudness varies through the programme.
  • True peak, in dBTP, estimates the highest level of the signal as a player will reconstruct it. It can be higher than the highest stored sample.

ffmpeg's loudnorm filter is described in its documentation as EBU R128 loudness normalisation. It can target integrated loudness, loudness range and maximum true peak, in single-pass or double-pass modes, and in dynamic or linear mode.

Targets are choices, not laws

EBU Recommendation R 128 is the European broadcast recommendation. Its figures, widely quoted, are a programme loudness of -23 LUFS and a maximum permitted true peak of -1 dBTP. We could not open the EBU page with our automated browser, because the site blocked it, so check these two figures in the current text before you rely on them.

Other places use other targets. The ffmpeg defaults, for instance, are an integrated loudness of -24.0 LUFS, a loudness range of 7.0 and a maximum true peak of -2.0 dBTP. Streaming services and podcast platforms publish their own numbers, and they change. The practical consequence is that a normalisation script should keep its targets in a settings file you can edit, and name them, such as "broadcast" or "podcast", so that you never type numbers from memory.

The two-pass method

The ffmpeg documentation says the filter supports single-pass modes for livestreams and files, and a double-pass mode for files. The double-pass method works like this.

  1. Measure. Run the filter once with output printed as JSON and the result thrown away. The filter reports the measured integrated loudness, loudness range, true peak and threshold of the input file.
  2. Apply. Run it again, giving those four measured values and your targets. If the conditions allow, the filter scales the whole file by one fixed amount, which the documentation calls linear normalisation.

In outline, the commands look like this:

  • Pass 1: ffmpeg -i input.wav -af loudnorm=I=-23:TP=-1:LRA=7:print_format=json -f null -
  • Pass 2: ffmpeg -i input.wav -af loudnorm=I=-23:TP=-1:LRA=7:measured_I=...:measured_LRA=...:measured_TP=...:measured_thresh=...:linear=true -ar 48000 output.wav

Replace the dots with the values from pass 1. Two details from the documentation are worth knowing. In dynamic mode, to detect true peaks accurately, ffmpeg upsamples the audio to 192 kHz, so set the output sample rate explicitly with -ar or the aresample filter. And for mono files meant to play on a stereo system, the dual_mono option compensates for a perceptual error in the measurement.

The condition that breaks linear mode

Here is the part that trips people up. The documentation says that linear normalisation needs all four measured values, and that the target loudness range should not be lower than the source loudness range and the change in integrated loudness should not push the true peak above the target. If any of these conditions fails, the filter reverts to dynamic mode.

Dynamic mode can change the character of the audio, because it adjusts the level as it goes. So a file that fails the check will not simply be turned up. It will be processed differently. That is not necessarily wrong, but you should know when it happens, and measure the result.

Worked example: when the gain does not fit

The measurements are invented. A file measures -26 LUFS with a true peak of -3 dBTP. Your target is -23 LUFS with a ceiling of -1 dBTP.

  • Loudness needs a gain of -23 - (-26) = +3 dB.
  • That would move the true peak from -3 to -3 + 3 = 0 dBTP.
  • The ceiling is -1 dBTP, so the result is 1 dB too high.

A gain of +3 dB meets the loudness but breaks the peak limit. The largest gain that respects the ceiling is -1 - (-3) = +2 dB, and that gives a loudness of -26 + 2 = -24 LUFS, 1 LU short of the target. You cannot have both with simple scaling. In practice you have three options: accept -24 LUFS, use a limiter or the dynamic mode and accept the changed sound, or ask for a source recording with more headroom. Note that gain of +2 dB multiplies the signal by about 1.26, since 10 to the power 2/20 is 1.26.

Whichever you choose, record it. A batch run that quietly falls back to dynamic mode for one file in twenty is a source of mysterious complaints later. Good tools print one line per file, with the measured values and what was done, and use an exit code that a script can act on.

What else a batch folder needs

Loudness is only one of the checks a delivery needs. In a typical batch you also want to:

  • inspect every file for duration, sample rate, channels and codec before you touch it;
  • detect clipping and clicks at the seam of a loop;
  • trim silence at the start and end, if the brief asks for it;
  • transcode to the delivery format with named presets;
  • check video for black frames, frozen frames and the delivery spec: size, frame rate, codec;
  • rename files from a template, with a dry run and an undo list;
  • record a manifest with a SHA-256 checksum of every file, so you can prove later what you sent.

Each of these is easy alone. The value of a batch tool is that it does them the same way every time, and says what it did.

Habits that prevent mistakes

  • Never overwrite the original. Write results to a new folder, and keep the source until the client has accepted the delivery.
  • Use a dry run first. Print the ffmpeg commands before you run them.
  • Measure the output, not just the input. The finished file is what the client hears.
  • Listen. A number cannot tell you that a normalised voice sounds wrong.
  • Keep targets in a file, with names, and keep the file with the project.

Tool that fits

Media Batch Lab (EUR 29, paid once) is a folder of 30 command-line Python scripts for repetitive audio and video delivery work: conversion, metadata, silence trimming and detection, EBU R128 loudness measurement and two-pass normalisation with editable named targets, clipping and loop-seam checks, preset transcodes, thumbnails, contact sheets, GIFs, trims, joins, black and frozen-frame detection, delivery-spec checks, template renaming with dry run and undo, CSV manifests with SHA-256, folder verification, a duplicate finder and a parallel ffmpeg runner. Each prints one result line per file, never overwrites unless you ask and can show its ffmpeg commands with --dry-run. It needs Python 3.10 or newer and your own ffmpeg and ffprobe, with no extra packages; ffmpeg is not included. It includes ten generated test files, 114 tests and a manual. Use it in your own and your clients' work; the kit may not be resold or shared.

What to do next

Media Batch Lab has 30 Python scripts for audio and video delivery: EBU R128 loudness measurement and two-pass normalisation, silence and clipping checks, transcodes, delivery-spec checks, renaming with undo and checksum manifests. You bring your own ffmpeg. €29 once.

See the Batch Lab

Questions people ask

Why does loudness normalisation need two passes?

The first pass measures the file: its integrated loudness, loudness range and true peak. The second pass uses those numbers to apply a fixed, linear gain, which keeps the dynamics of the original. Without the measurement, ffmpeg has to guess as it goes and works in dynamic mode, which can change the sound more. The ffmpeg documentation describes both a single-pass and a double-pass mode.

Does -23 LUFS apply everywhere?

No. The EBU R 128 recommendation targets -23 LUFS for broadcast programmes in Europe. Streaming services, podcasts, social platforms and film each publish their own targets, and ffmpeg's own default is -24 LUFS. Check the specification of the place you deliver to.

Can normalisation prevent all clipping?

No. Gain that raises loudness also raises the peak. If the file has little headroom, the loudness target and the true-peak limit can conflict, and you must choose between a quieter result, a limiter or a different target. Always measure the result, not just the plan.

Sources

  1. FFmpeg Filters Documentation, loudnorm: EBU R128 loudness normalisation (options I, LRA, TP, measured_*, linear, dual_mono)
  2. ITU-R Recommendation BS.1770-5 (11/2023), Algorithms to measure audio programme loudness and true-peak audio level
  3. EBU Recommendation R 128, Loudness normalisation and permitted maximum level of audio signals (figures cited from the standard; the EBU site blocked our automated browser, so check the current text)
  4. HiddenWay worked example: our own arithmetic, with invented measurements

We checked each source on 17 October 2026. If a fact here is out of date, tell us at hello@hiddenway.info.

Keep reading