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"