Skip to main content

Offline RMS scanner for wav, mp3 and other formats using FFmpeg astats

Project description

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).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rms_scan-0.1.0.tar.gz (10.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rms_scan-0.1.0-py3-none-any.whl (9.8 kB view details)

Uploaded Python 3

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

Hashes for rms_scan-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b7c1b191379932ff541b522ac634971d77cbb0ecf6f0aa5d02f6499325f0a50d
MD5 37bc4d5a326ea06e3bf98b252816942b
BLAKE2b-256 43b964fd26ba91ab89bd022f034678031276aace1fe7ec3f6b81951b73770bf5

See more details on using hashes here.

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

Hashes for rms_scan-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8cbd69b971c7227b7c44ad1f27c4704a336cc859a011c48573e91010302686b4
MD5 5122b47825355d67dbe0ed276113be89
BLAKE2b-256 3ae8521cdf9da2c8964a989f61bbb7de54521513e650a1c0a19ff44a1a897d82

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page