Skip to content

Instantly share code, notes, and snippets.

@gustavomdsantos
Last active February 19, 2026 00:39
Show Gist options
  • Select an option

  • Save gustavomdsantos/491f2efd88212772ae2c411ea70a5b98 to your computer and use it in GitHub Desktop.

Select an option

Save gustavomdsantos/491f2efd88212772ae2c411ea70a5b98 to your computer and use it in GitHub Desktop.
A simple file LUFS meter using FFmpeg, and inspired by rsgain and iZotope RX Waveform Statistics.
#!/usr/bin/env bash
# ==================================================================
# LUFS Meter
# Analyzes audio files and extracts loudness metrics based on the
# ITU-R BS.1770-4 (EBU R128) standard.
#
# AUTHOR:
# gustavomdsantos@pm.me
# ==================================================================
show_help() {
cat << EOF
Usage: $(basename "$0") [OPTIONS] FILE...
Analyze audio files and report loudness statistics.
Options:
-h, --help Show this help message and exit
Metrics:
Integrated Overall loudness of the file (LUFS).
Short-Term Max Maximum loudness of the file (LUFS).
SIR Short-to-Integrated Ratio, measures the
dynamic lift above the average loudness (LU).
Perceived Loudness A linear scale based on human perception (%).
True-Peak The maximum absolute level of the signal (dBTP).
Peak The maximum sample value (dBFS).
Requires: ffmpeg, ffprobe, awk
EOF
}
# Process options
case "$1" in
-h|--help)
show_help
exit 0
;;
"")
show_help
exit 1
;;
esac
for cmd in ffmpeg ffprobe awk; do
if ! command -v "$cmd" &> /dev/null; then
echo "Error: Tool '$cmd' not found. Please install it to continue."
exit 1
fi
done
if [ "$#" -eq 0 ]; then
echo "Usage: $0 file1 [file2 ...]"
exit 1
fi
for file in "$@"; do
# Check if file exists to prevent errors
if [ ! -f "$file" ]; then
echo "Error: File \"$file\" not found."
continue
fi
echo -ne "Analyzing: \"$(basename "$file")\"...\r"
output=""
sample_peak=""
perceived_loudness=""
# Using ffprobe to check file and its audio channels
channels=$(ffprobe -v error -select_streams a:0 -show_entries stream=channels -of default=noprint_wrappers=1:nokey=1 "$file" 2>/dev/null)
# Check if file is a valid audio file
if [ $? -ne 0 ] || [ -z "$channels" ]; then
echo -ne "\033[K"
echo "Error: File: \"$file\" is invalid."
continue
fi
# Adjust filter for dual mono/stereo to ensure proper measurement
if [ "$channels" -eq 1 ]; then
audio_filter="pan=stereo|c0=c0|c1=c0,ebur128=peak=true,astats=measure_overall=1:reset=0"
else
audio_filter="ebur128=peak=true,astats=measure_overall=1:reset=0"
fi
# Run ffmpeg analysis
log=$(ffmpeg -hide_banner -nostats -i "$file" -filter_complex "[0:a]$audio_filter" -f null - 2>&1)
# Extract values using standard text tools
integrated=$(echo "$log" | grep "I:" | tail -n1 | sed -n 's/.*I:\s*\([-0-9.]*\).*/\1/p' | tr -d ' ')
short_term=$(echo "$log" | grep "S:" | sed -n 's/.*S:\s*\([-0-9.]*\).*/\1/p' | sort -n | tail -n1 | tr -d ' ')
true_peak=$(echo "$log" | grep -A2 "True peak:" | grep "Peak:" | sed -n 's/.*Peak:\s*\([-0-9.]*\).*/\1/p' | tr -d ' ')
peak=$(echo "$log" | grep -i "Peak level dB" | head -n1 | sed -E 's/.*: (.*)/\1/' | tr -d ' ')
# SIR (Short-to-Integrated Ratio), inspired by the PAPR (Peak-to-Average Power Ratio).
# Formula: SIR = Short-Term Max - Integrated
if [[ -n "$integrated" && -n "$short_term" ]]; then
sir=$(LC_NUMERIC=C awk -v s="$short_term" -v i="$integrated" 'BEGIN { printf "%.1f", s - i }')
fi
# Perceived Loudness (PL), a simple linear interpolation of LUFS-I scale.
# Formula: PL = (IL + 17) * (100 / 11)
# Eg.: -17 LUFS = 0% | -11.5 LUFS = 50% | -6 LUFS = 100%
if [[ -n "$integrated" ]]; then
pl=$(LC_NUMERIC=C awk -v i="$integrated" 'BEGIN { printf "%.1f", (i + 17) * 100 / 11 }')
fi
if [[ -n "$peak" && "$peak" != "inf" ]]; then
sample_peak=$(LC_NUMERIC=C awk -v p="$peak" 'BEGIN { printf "%.1f", p }')
fi
echo -ne "\033[K"
output="Track: \"$(basename "$file")\"\n\n"
output+=" Integrated: ${integrated:-N/A} LUFS\n"
output+=" Short-Term Max: ${short_term:-N/A} LUFS\n"
output+=" SIR: ${sir:-N/A} LU\n"
output+=" Perceived Loudness: ${pl:-N/A} %\n"
output+=" True-Peak: ${true_peak:-N/A} dBTP\n"
output+=" Peak: ${sample_peak:-N/A} dBFS"
echo -e "\n$output\n"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment