Signed-off-by: hax <hax@lainlounge.org>
This commit is contained in:
h@x 2025-07-07 01:18:35 +00:00
parent 930d282ccc
commit 0d3441e0e4

117
wave2m4a Normal file
View file

@ -0,0 +1,117 @@
#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# Title: wav2m4a
# Purpose: Comprehensive WAV to M4A auto-converter
# Version: 1.0.0
# Developer: h@x
# -----------------------------------------------------------------------------
set -euo pipefail
trap 'echo "✗ ERROR on line $LINENO (exit code $?): ${BASH_COMMAND}" >&2' ERR
IFS=$'\n\t'
# === Input & output directories ===
SRC_DIR="${1:-.}"
DST_DIR="${2:-./m4a_output}"
[[ -d "$SRC_DIR" ]] || {
echo "ERROR: Source directory '$SRC_DIR' not found." >&2
exit 1
}
mkdir -p "$DST_DIR"
# === FFmpeg capability check ===
if ! ffmpeg -hide_banner -muxers 2>/dev/null | grep -qw mov; then
echo "ERROR: FFmpeg build lacks the MP4/mov muxer required for .m4a outputs." >&2
exit 1
fi
# === Codec selection ===
declare -a CODEC_OPTS
if ffmpeg -hide_banner -encoders 2>/dev/null | grep -qw libfdk_aac; then
CODEC_OPTS=( -c:a libfdk_aac -profile:a aac_he_v2 -cutoff 18000 -b:a 256k )
echo "INFO: Using libfdk_aac HE-AAC v2 @256 kbps." >&2
else
CODEC_OPTS=( -c:a aac -b:a 256k )
echo "WARNING: Falling back to native AAC @256 kbps." >&2
fi
declare -a META_OPTS=(
-map_metadata 0
-id3v2_version 3
-write_id3v1 1
-metadata:s:a encoder="ffmpeg/${CODEC_OPTS[1]}"
)
declare -a FFMPEG_OPTS=( -hide_banner -loglevel error -nostdin -n )
# === Discover .wav files ===
mapfile -d '' files < <(find "$SRC_DIR" -type f -iname '*.wav' -print0)
TOTAL=${#files[@]}
(( TOTAL > 0 )) || {
echo "Nothing to do: no .wav files found under '$SRC_DIR'." >&2
exit 0
}
echo "FOUND: $TOTAL .wav file(s) to process." >&2
# === Progress-bar initialization ===
BAR_WIDTH=40
COUNT=0
LAST_PCT=-1
empty_bar=$(printf '%*s' "$BAR_WIDTH" '' | tr ' ' '.')
printf "Progress: [%-${BAR_WIDTH}s] 0%% (%d/%d)\n" \
"$empty_bar" 0 "$TOTAL" >&2
# === Conversion loop with ANSI overwrite ===
declare -a SKIPPED=()
declare -a CONVERTED=()
declare -A FAILED=()
for SRC_FILE in "${files[@]}"; do
(( COUNT += 1 ))
REL_PATH="${SRC_FILE#"$SRC_DIR"/}"
DST_FILE="$DST_DIR/${REL_PATH%.wav}.m4a"
mkdir -p "$(dirname "$DST_FILE")"
PCT=$(( COUNT * 100 / TOTAL ))
if (( PCT != LAST_PCT )); then
LAST_PCT=$PCT
filled=$(( COUNT * BAR_WIDTH / TOTAL ))
fbar=$(printf '%*s' "$filled" '' | tr ' ' '#')
ebar=$(printf '%*s' "$(( BAR_WIDTH - filled ))" '' | tr ' ' '.')
printf "\033[1A\rProgress: [%s%s] %3d%% (%d/%d)\n" \
"$fbar" "$ebar" "$PCT" "$COUNT" "$TOTAL" >&2
fi
if [[ -f "$DST_FILE" ]]; then
SKIPPED+=("$REL_PATH")
else
if ffmpeg "${FFMPEG_OPTS[@]}" -i "$SRC_FILE" \
"${CODEC_OPTS[@]}" -vn "${META_OPTS[@]}" -f ipod "$DST_FILE"; then
CONVERTED+=("$REL_PATH")
else
FAILED["$REL_PATH"]=1
fi
fi
done
# === Summary ===
echo >&2
printf "========== SUMMARY ==========\n" >&2
printf "Skipped (already present): %d\n" "${#SKIPPED[@]}" >&2
for f in "${SKIPPED[@]}"; do printf " • %s\n" "$f"; done >&2
echo >&2
printf "Converted: %d\n" "${#CONVERTED[@]}" >&2
for f in "${CONVERTED[@]}"; do printf " • %s\n" "$f"; done >&2
echo >&2
printf "Failed: %d\n" "${#FAILED[@]}" >&2
for f in "${!FAILED[@]}"; do printf " • %s\n" "$f"; done >&2
echo >&2
printf "Output directory: '%s'\n" "$DST_DIR" >&2
echo "=============================" >&2
(( ${#FAILED[@]} )) && exit 2 || exit 0