Audio compression and leveling bash script

We’ve got a bunch of poems recorded in a mixture of audio file formats by different people. The goal was to get them all compressed in the same format with the same settings and make an attempt at leveling the audio.

This bash script was created with the help of Claude so I can run it against all the files in a folder at once. It appends _c to the file name to indicate it’s been processed.

You might need to install ffmpeg. I use brew to do this.

On a Mac, to make this work, put the audio-compressor.sh file in /usr/local/bin/.

Then make it executable with chmod +x /usr/local/bin/audio-compressor.sh

Now you can run it against folder with audio-compressor.sh ~/Desktop/audio-poetry/2.0_Witnessing

Now is there any purpose in me writing this post since the AI did a chunk of the work and anyone can use AI? I think so.

First, I’ll forget this if I don’t document it and there’s a decent chance I’ll do something similar again someday or I might point someone else at this.

Secondly, I find people often just don’t quite have an idea that a thing could be done or the path to figuring it out, even with AI, might not have to be quite so arduous. Putting workflows and ideas out there can’t hurt. Maybe this post saves someone some tokens, avoids some energy use, or lets someone avoid AI entirely (by proxy anyway).

#!/bin/bash

# audio-compressor.sh
# Normalizes and compresses audio files to 128kbps mp3 for web.
# Uses two-pass EBU R128 loudness normalization (-16 LUFS) so all
# tracks play at a consistent perceived volume.
# Usage: audio-compressor.sh /path/to/folder

set -u

TARGET_BITRATE=128   # kbps
TARGET_LUFS=-16      # integrated loudness target
TARGET_TP=-1.5       # true peak ceiling (dBTP)
TARGET_LRA=11        # loudness range

# --- Validate input ---

if [ $# -eq 0 ]; then
  echo "Usage: ./audio-compressor.sh /path/to/folder"
  exit 1
fi

FOLDER="$1"

if [ ! -d "$FOLDER" ]; then
  echo "Error: '$FOLDER' is not a directory."
  exit 1
fi

# --- Check for ffmpeg ---

if ! command -v ffmpeg &> /dev/null || ! command -v ffprobe &> /dev/null; then
  echo "Error: ffmpeg and ffprobe are required."
  echo "Install with: brew install ffmpeg"
  exit 1
fi

# --- Process files ---

EXTENSIONS="mp3 wav flac aac m4a ogg wma aiff aif"
CONVERTED=0
SKIPPED=0
FAILED=0

for EXT in $EXTENSIONS; do
  for FILE in "$FOLDER"/*."$EXT" "$FOLDER"/*."$(echo "$EXT" | tr '[:lower:]' '[:upper:]')"; do
    [ -f "$FILE" ] || continue

    BASENAME="$(basename "$FILE")"
    NAME="${BASENAME%.*}"

    # Skip files that are already a _c variant
    if [[ "$NAME" == *_c ]]; then
      continue
    fi

    OUTPUT="$FOLDER/${NAME}_c.mp3"

    # Skip if compressed version already exists
    if [ -f "$OUTPUT" ]; then
      echo "SKIP (exists): ${NAME}_c.mp3 already exists"
      SKIPPED=$((SKIPPED + 1))
      continue
    fi

    echo -n "PROCESSING: $BASENAME ... "

    # --- Pass 1: Measure loudness ---
    MEASURE=$(ffmpeg -i "$FILE" \
      -af "loudnorm=I=${TARGET_LUFS}:TP=${TARGET_TP}:LRA=${TARGET_LRA}:print_format=json" \
      -f null - 2>&1 | tail -20)

    # Parse measured values from JSON output
    MEASURED_I=$(echo "$MEASURE" | grep '"input_i"' | sed 's/[^-0-9.]//g' || echo "")
    MEASURED_TP=$(echo "$MEASURE" | grep '"input_tp"' | sed 's/[^-0-9.]//g' || echo "")
    MEASURED_LRA=$(echo "$MEASURE" | grep '"input_lra"' | sed 's/[^-0-9.]//g' || echo "")
    MEASURED_THRESH=$(echo "$MEASURE" | grep '"input_thresh"' | sed 's/[^-0-9.]//g' || echo "")
    TARGET_OFFSET=$(echo "$MEASURE" | grep '"target_offset"' | sed 's/[^-0-9.]//g' || echo "")

    if [ -z "$MEASURED_I" ] || [ -z "$MEASURED_TP" ]; then
      echo "FAILED (could not measure loudness)"
      FAILED=$((FAILED + 1))
      continue
    fi

    # --- Pass 2: Normalize and compress ---
    # If measured_I is positive (out of range for two-pass), fall back to single-pass
    USE_TWO_PASS=true
    case "$MEASURED_I" in
      -*) ;;  # negative, two-pass is fine
      *)  USE_TWO_PASS=false ;;  # positive or zero, use single-pass
    esac

    if $USE_TWO_PASS; then
      if ffmpeg -y -i "$FILE" \
        -af "loudnorm=I=${TARGET_LUFS}:TP=${TARGET_TP}:LRA=${TARGET_LRA}:measured_I=${MEASURED_I}:measured_TP=${MEASURED_TP}:measured_LRA=${MEASURED_LRA}:measured_thresh=${MEASURED_THRESH}:offset=${TARGET_OFFSET}:linear=true" \
        -codec:a libmp3lame -b:a ${TARGET_BITRATE}k -ar 44100 -ac 2 \
        "$OUTPUT" -loglevel error 2>&1; then
        echo "done (was ${MEASURED_I} LUFS -> ${TARGET_LUFS} LUFS)"
        CONVERTED=$((CONVERTED + 1))
      else
        echo "FAILED"
        FAILED=$((FAILED + 1))
      fi
    else
      echo -n "(hot audio, single-pass) ... "
      if ffmpeg -y -i "$FILE" \
        -af "loudnorm=I=${TARGET_LUFS}:TP=${TARGET_TP}:LRA=${TARGET_LRA}" \
        -codec:a libmp3lame -b:a ${TARGET_BITRATE}k -ar 44100 -ac 2 \
        "$OUTPUT" -loglevel error 2>&1; then
        echo "done (was ${MEASURED_I} LUFS -> ${TARGET_LUFS} LUFS)"
        CONVERTED=$((CONVERTED + 1))
      else
        echo "FAILED"
        FAILED=$((FAILED + 1))
      fi
    fi
  done
done

echo ""
echo "--- Summary ---"
echo "Processed: $CONVERTED"
echo "Skipped:   $SKIPPED"
echo "Failed:    $FAILED"

Leave a Reply