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
- t: Time in seconds.
- M: Momentary loudness (LUFS).
- S: Short-term loudness (LUFS).
- I: Integrated loudness (LUFS).
- LRA: Loudness range.
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=0This 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()