Skip to main content

rms-scan

CI PyPI

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:

  • --min minimum RMS threshold in dBFS (default: -23)
  • --max maximum RMS threshold in dBFS (default: -18)
  • --json machine-readable output
  • --verbose print 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 missing
  • 3: astats parse failure
  • 4: 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

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 ffmpeg or sudo apt install ffmpeg)
  • Or pass binary paths with --ffmpeg and --ffprobe

Parse failure (exit code 3)

If lavfi.astats.Overall.RMS_level / RMS_peak are not found:

  • Re-run with --verbose to inspect parsed lines
  • Check filter availability:
    • ffmpeg -filters | grep astats
    • ffmpeg -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

  1. Update pyproject.toml version and CHANGELOG.md.
  2. Build and check:
    • python -m build
    • python -m twine check dist/*
  3. Create and push a tag like v0.1.0. The GitHub Actions release.yml workflow publishes to PyPI via Trusted Publishing (OIDC).

Metadata

Release files for rms-scan 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rms-scan 0.1.0
File Size Uploaded
rms_scan-0.1.0.tar.gz 10.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for rms-scan 0.1.0
File Interpreter ABI Platform
rms_scan-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 19.9 kB

Release files / rms_scan-0.1.0.tar.gz

Download URL rms_scan-0.1.0.tar.gz
Size 10.0 kB
Tags Source
SHA-256 checksum
How to use checksums
b7c1b191379932ff541b522ac634971d77cbb0ecf6f0aa5d02f6499325f0a50d
BLAKE2b-256 checksum
How to use checksums
43b964fd26ba91ab89bd022f034678031276aace1fe7ec3f6b81951b73770bf5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.8

Release files / rms_scan-0.1.0-py3-none-any.whl

Download URL rms_scan-0.1.0-py3-none-any.whl
Size 9.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8cbd69b971c7227b7c44ad1f27c4704a336cc859a011c48573e91010302686b4
BLAKE2b-256 checksum
How to use checksums
3ae8521cdf9da2c8964a989f61bbb7de54521513e650a1c0a19ff44a1a897d82
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.8

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page