From 0e22fcfbb31eb98b1ec6b13f38fdbd07259227d3 Mon Sep 17 00:00:00 2001 From: jenstandstad Date: Tue, 17 Mar 2026 22:02:05 +0100 Subject: [PATCH] Adding fixes to alignment and also captain black! --- to_mp3.sh | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100755 to_mp3.sh diff --git a/to_mp3.sh b/to_mp3.sh new file mode 100755 index 0000000..e7724b5 --- /dev/null +++ b/to_mp3.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +# to_mp3.sh — Convert .m4a and .wav files to .mp3 +# +# Usage: +# ./to_mp3.sh [options] +# +# Options: +# --quality N VBR quality 0-9 (default: 2 ≈ 190kbps; lower = better) +# --bitrate N CBR bitrate e.g. 192k (overrides --quality) +# --replace Delete originals after successful conversion +# --dry-run Show what would be converted without doing anything +# +# Examples: +# ./to_mp3.sh recordings/ +# ./to_mp3.sh interview.m4a +# ./to_mp3.sh recordings/ --replace +# ./to_mp3.sh recordings/ --bitrate 128k + +set -euo pipefail + +QUALITY=2 +BITRATE="" +REPLACE=false +DRY_RUN=false +TARGET="" + +# Parse arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --quality) QUALITY="$2"; shift 2 ;; + --bitrate) BITRATE="$2"; shift 2 ;; + --replace) REPLACE=true; shift ;; + --dry-run) DRY_RUN=true; shift ;; + -*) echo "Unknown option: $1" >&2; exit 1 ;; + *) TARGET="$1"; shift ;; + esac +done + +if [[ -z "$TARGET" ]]; then + echo "Usage: $(basename "$0") [--quality N] [--bitrate N] [--replace] [--dry-run]" + exit 1 +fi + +if ! command -v ffmpeg &>/dev/null; then + echo "Error: ffmpeg not found." >&2 + exit 1 +fi + +# Collect files +files=() +if [[ -f "$TARGET" ]]; then + files=("$TARGET") +elif [[ -d "$TARGET" ]]; then + while IFS= read -r -d '' f; do + files+=("$f") + done < <(find "$TARGET" -maxdepth 1 -type f \( -iname "*.m4a" -o -iname "*.wav" \) -print0 | sort -z) +else + echo "Error: '$TARGET' is not a file or directory." >&2 + exit 1 +fi + +if [[ ${#files[@]} -eq 0 ]]; then + echo "No .m4a or .wav files found in: $TARGET" + exit 0 +fi + +# Build audio quality flags +if [[ -n "$BITRATE" ]]; then + audio_flags=(-b:a "$BITRATE") + quality_desc="CBR ${BITRATE}" +else + audio_flags=(-q:a "$QUALITY") + quality_desc="VBR quality ${QUALITY}" +fi + +echo "Converting ${#files[@]} file(s) to MP3 (${quality_desc})" +[[ "$REPLACE" == true ]] && echo " Originals will be deleted after conversion." +[[ "$DRY_RUN" == true ]] && echo " Dry-run mode — no files will be written." +echo "" + +converted=0 +skipped=0 +errors=0 + +for src in "${files[@]}"; do + out="${src%.*}.mp3" + + if [[ -f "$out" && "$DRY_RUN" == false ]]; then + echo " $(basename "$src"): output already exists, skipping" + ((skipped++)) || true + continue + fi + + size_mb=$(( $(stat -f%z "$src" 2>/dev/null || stat -c%s "$src") / 1048576 )) + printf " %-40s (%d MB)" "$(basename "$src")" "$size_mb" + + if [[ "$DRY_RUN" == true ]]; then + echo " [dry-run] → $(basename "$out")" + continue + fi + + if ffmpeg -i "$src" -vn "${audio_flags[@]}" -y "$out" -loglevel error; then + out_kb=$(( $(stat -f%z "$out" 2>/dev/null || stat -c%s "$out") / 1024 )) + echo " → $(basename "$out") (${out_kb} KB)" + ((converted++)) || true + if [[ "$REPLACE" == true ]]; then + rm "$src" + fi + else + echo " ERROR" + ((errors++)) || true + [[ -f "$out" ]] && rm "$out" + fi +done + +echo "" +echo "Done: ${converted} converted, ${skipped} skipped, ${errors} errors."