Offline RMS scanner for wav, mp3 and other formats using FFmpeg astats
Project description
rms-scan
rms-scan is a small offline CLI that analyzes a media file with FFmpeg astats and reports:
- Overall/average RMS:
lavfi.astats.Overall.RMS_level(dBFS) - Highest RMS window:
lavfi.astats.Overall.RMS_peak(dBFS) - Pass/fail against a configurable RMS window (default:
[-23, -18]dBFS)
It also prints per-channel RMS details when available.
Why / use cases
- Broadcast/podcast loudness spot-checks (quick RMS window compliance checks)
- Batch validation of large media libraries
- CI gating (fail builds when audio is consistently too quiet/loud)
Requirements
- Python 3.10+
- FFmpeg + FFprobe in
PATH(or pass explicit paths via--ffmpeg/--ffprobe)
Install FFmpeg:
- macOS (Homebrew):
brew install ffmpeg - Debian/Ubuntu:
sudo apt install ffmpeg
Install
From project root:
pip install .
After install, the console script is available as:
rms-scan --help
You can also run directly:
python3 rms_scan.py --help
Usage
rms-scan <path-to-audio-or-video-file>
Options:
--minminimum RMS threshold in dBFS (default:-23)--maxmaximum RMS threshold in dBFS (default:-18)--jsonmachine-readable output--verboseprint raw astats lines being parsed--ffmpeg <path>override ffmpeg binary--ffprobe <path>override ffprobe binary
Example (human output)
$ rms-scan program.wav
File: program.wav
Duration: 3728.41 s
Audio: 48000 Hz, 2 ch, stereo
Overall RMS_level: -21.34 dBFS
Overall RMS_peak: -18.62 dBFS
Spec window: [-23.0, -18.0] dBFS
Result: PASS
Details:
Channel 1 RMS_level: -21.20 dBFS
Channel 1 RMS_peak: -18.55 dBFS
Channel 2 RMS_level: -21.48 dBFS
Channel 2 RMS_peak: -18.70 dBFS
Example (FAIL + suggested gain)
$ rms-scan quiet_mix.mp3
File: quiet_mix.mp3
Duration: 912.03 s
Audio: 44100 Hz, 2 ch, stereo
Overall RMS_level: -24.10 dBFS
Overall RMS_peak: -20.80 dBFS
Spec window: [-23.0, -18.0] dBFS
Result: FAIL
Suggested gain change: +3.6 dB
Details:
Channel 1 RMS_level: -24.05 dBFS
Channel 1 RMS_peak: -20.77 dBFS
Channel 2 RMS_level: -24.15 dBFS
Channel 2 RMS_peak: -20.84 dBFS
Suggested gain change targets midpoint -20.5 dBFS using:
target - measured_rms_level
Example (--json)
{
"channel_layout": "stereo",
"channels": 2,
"details": {
"per_channel": {
"1": {
"RMS_level_dbfs": -21.2,
"RMS_peak_dbfs": -18.55
},
"2": {
"RMS_level_dbfs": -21.48,
"RMS_peak_dbfs": -18.7
}
}
},
"duration_seconds": 3728.41,
"file": "program.wav",
"overall": {
"RMS_level": {
"last_dbfs": -21.34,
"max_observed_dbfs": -21.34,
"selected_dbfs": -21.34
},
"RMS_peak": {
"last_dbfs": -18.62,
"max_observed_dbfs": -18.62,
"selected_dbfs": -18.62
}
},
"pass": true,
"range": {
"max_dbfs": -18.0,
"min_dbfs": -23.0
},
"sample_rate_hz": 48000,
"suggested_gain_change_db": -0.84,
"target_midpoint_dbfs": -20.5
}
Exit Codes
0: PASS (Overall RMS_level in range)1: FAIL (outside range)2: ffmpeg/ffprobe missing3: astats parse failure4: invalid input file
FFmpeg Analysis Method
The tool uses FFprobe first for metadata and then analyzes audio without rendering output:
- disables non-audio streams:
-vn -sn -dn - null muxer output:
-f null - - astats filter with metadata keys:
- preferred:
astats=metadata=1:reset=0:measure_overall=RMS_level+RMS_peak:measure_perchannel=RMS_level+RMS_peak,ametadata=print - fallback for older builds:
astats=metadata=1:reset=0,ametadata=print
- preferred:
This processes as fast as decode/filter speed allows (typically faster-than-real-time on modern machines).
Troubleshooting
Error: Unable to find ffmpeg/ffprobe
- Install FFmpeg package (
brew install ffmpegorsudo apt install ffmpeg) - Or pass binary paths with
--ffmpegand--ffprobe
Parse failure (exit code 3)
If lavfi.astats.Overall.RMS_level / RMS_peak are not found:
- Re-run with
--verboseto inspect parsed lines - Check filter availability:
ffmpeg -filters | grep astatsffmpeg -filters | grep ametadata
- Try a newer FFmpeg build (some builds vary in filter/metadata behavior)
Input fails validation (exit code 4)
- Verify file exists and contains an audio stream
- Confirm ffprobe can read it:
ffprobe -v error -show_streams -show_format <file>
Development
python -m venv .venv
source .venv/bin/activate
python -m pip install -U pip
python -m pip install -e ".[dev]"
python -m unittest discover -s tests -p "test_*.py"
python -m build
python -m twine check dist/*
Release
- Update
pyproject.tomlversion andCHANGELOG.md. - Build and check:
python -m buildpython -m twine check dist/*
- Create and push a tag like
v0.1.0. The GitHub Actionsrelease.ymlworkflow publishes to PyPI via Trusted Publishing (OIDC).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rms_scan-0.1.0.tar.gz.
File metadata
- Download URL: rms_scan-0.1.0.tar.gz
- Upload date:
- Size: 10.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b7c1b191379932ff541b522ac634971d77cbb0ecf6f0aa5d02f6499325f0a50d
|
|
| MD5 |
37bc4d5a326ea06e3bf98b252816942b
|
|
| BLAKE2b-256 |
43b964fd26ba91ab89bd022f034678031276aace1fe7ec3f6b81951b73770bf5
|
File details
Details for the file rms_scan-0.1.0-py3-none-any.whl.
File metadata
- Download URL: rms_scan-0.1.0-py3-none-any.whl
- Upload date:
- Size: 9.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8cbd69b971c7227b7c44ad1f27c4704a336cc859a011c48573e91010302686b4
|
|
| MD5 |
5122b47825355d67dbe0ed276113be89
|
|
| BLAKE2b-256 |
3ae8521cdf9da2c8964a989f61bbb7de54521513e650a1c0a19ff44a1a897d82
|