How to Parse FFmpeg ebur128 Realtime Output

This article provides a practical guide on how to capture and parse the real-time loudness measurement output of the FFmpeg ebur128 filter. It covers the command-line configurations required to stream EBU R128 data, explains how to handle FFmpeg’s console output, and provides a Python implementation to extract momentary, short-term, and integrated loudness values programmatically in real-time.

Method 1: Parsing FFmpeg Stderr Output

By default, the ebur128 filter writes its real-time analysis to the standard error (stderr) stream. To force FFmpeg to process the input in real-time, you must use the -re flag (for live input emulation) and set the realtime=1 parameter on the filter.

The FFmpeg Command

ffmpeg -re -i input.mp3 -filter_complex ebur128=realtime=1 -f null -

This command outputs lines to stderr at regular intervals that look like this:

[Parsed_ebur128_0 @ 0x55b27b99e840] t: 0.1       M: -21.4 S: -24.0 I: -22.5 LRA:   0.0
[Parsed_ebur128_0 @ 0x55b27b99e840] t: 0.2       M: -20.8 S: -24.0 I: -22.5 LRA:   0.0

Python Script to Parse Stderr

To parse this output programmatically, you can run FFmpeg as a subprocess, read stderr line-by-line, and extract the values using regular expressions.

import subprocess
import re

command = [
    'ffmpeg',
    '-re',
    '-i', 'input.mp3',
    '-filter_complex', 'ebur128=realtime=1',
    '-f', 'null',
    '-'
]

# Match the output line from ebur128
# Example: t: 0.1       M: -21.4 S: -24.0 I: -22.5 LRA:   0.0
pattern = re.compile(
    r"t:\s*(?P<t>[\d\.]+)\s+M:\s*(?P<M>[\d\.-]+)\s+S:\s*(?P<S>[\d\.-]+)\s+I:\s*(?P<I>[\d\.-]+)"
)

process = subprocess.Popen(command, stderr=subprocess.PIPE, stdout=subprocess.DEVNULL, text=True)

try:
    while True:
        line = process.stderr.readline()
        if not line:
            break
        
        match = pattern.search(line)
        if match:
            data = match.groupdict()
            time = float(data['t'])
            momentary = float(data['M'])
            short_term = float(data['S'])
            integrated = float(data['I'])
            
            print(f"Time: {time:0.1f}s | Momentary: {momentary} LUFS | Short-term: {short_term} LUFS | Integrated: {integrated} LUFS")
finally:
    process.terminate()

Method 2: Structured Output using FFprobe and Metadata

If you want to avoid parsing unstructured text logs, you can use ebur128 with the metadata=1 parameter. This injects the loudness values directly into the audio frame metadata. You can then use ffprobe to output these values in a structured CSV format to standard output (stdout).

The FFprobe Command

ffprobe -v error -f lavfi -i amovie=input.mp3,ebur128=metadata=1 \
  -show_entries frame=pkt_pts_time:frame_tags=lavfi.r128.M,lavfi.r128.S,lavfi.r128.I \
  -of csv=p=0

This command outputs a clean, comma-separated stream directly to stdout:

0.100000,-21.4,-24.0,-22.5
0.200000,-20.8,-24.0,-22.5

Each row corresponds to a frame and contains: [Timestamp],[Momentary Loudness],[Short-term Loudness],[Integrated Loudness]

Python Script to Parse FFprobe CSV Output

Since the output of this method is standard CSV, parsing it in real-time is highly reliable and does not require complex regex.

import subprocess
import csv

command = [
    'ffprobe',
    '-v', 'error',
    '-f', 'lavfi',
    '-i', 'amovie=input.mp3,ebur128=metadata=1',
    '-show_entries', 'frame=pkt_pts_time:frame_tags=lavfi.r128.M,lavfi.r128.S,lavfi.r128.I',
    '-of', 'csv=p=0'
]

process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True)

try:
    reader = csv.reader(process.stdout)
    for row in reader:
        if len(row) >= 4:
            timestamp = float(row[0])
            momentary = float(row[1])
            short_term = float(row[2])
            integrated = float(row[3])
            
            print(f"Timestamp: {timestamp:.2f} | M: {momentary} | S: {short_term} | I: {integrated}")
finally:
    process.terminate()