Files
scripts/reduce_bitrate.sh
T

204 lines
5.5 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
TARGET_BITRATE="192k"
ROOT_DIR="."
DRY_RUN=0
CLEANUP_BACKUPS=0
GREEN="\033[1;32m"
BLUE="\033[1;34m"
YELLOW="\033[1;33m"
RED="\033[1;31m"
DIM="\033[2m"
RESET="\033[0m"
usage() {
echo -e "${BLUE}Usage:${RESET} $0 [-n] [-d DIR] [-c]"
echo -e " -n Dry-run (only show files that would be converted)"
echo -e " -d DIR Working directory (default: current directory)"
echo -e " -c Remove .bak files after successful run"
echo -e " -h Show this help"
}
log_step() { echo -e "\n${BLUE}▶${RESET} $*"; }
log_info() { echo -e "${BLUE}ℹ${RESET} $*"; }
log_warn() { echo -e "${YELLOW}⚠${RESET} $*"; }
log_error() { echo -e "${RED}✖${RESET} $*"; }
log_success() { echo -e "${GREEN}✔${RESET} $*"; }
get_total_size_mb() {
local dir="$1"
find "$dir" -name "*.mp3" -print0 | xargs -0 stat -c '%s' 2>/dev/null | \
awk '{sum += $1} END {printf "%.2f MB\n", sum/1024/1024}'
}
get_bitrate_kbps() {
local file="$1"
local metadata
metadata=$(ffprobe -v error -select_streams a:0 -show_entries stream=bit_rate \
-of default=noprint_wrappers=1:nokey=1 "$file" < /dev/null 2>/dev/null)
if ! [[ "$metadata" =~ ^[0-9]+$ ]]; then
metadata=$(ffprobe -v error -show_entries format=bit_rate \
-of default=noprint_wrappers=1:nokey=1 "$file" < /dev/null 2>/dev/null)
fi
if [[ "$metadata" =~ ^[0-9]+$ ]]; then
echo $((metadata / 1024))
return 0
fi
return 1
}
restore_metadata() {
local source_file="$1"
local target_file="$2"
id3cp "$source_file" "$target_file" >/dev/null 2>&1
return $?
}
process_file() {
local file="$1"
local current_bitrate
local backup_file
local temp_file
current_bitrate=$(get_bitrate_kbps "$file") || {
log_warn "Could not read bitrate for: ${file}"
((warn_count++))
return 0
}
if [ "$current_bitrate" -le "${TARGET_BITRATE%k}" ]; then
log_info "Skip (already <= ${TARGET_BITRATE}): ${file}"
((skip_count++))
return 0
fi
if [ "$DRY_RUN" -eq 1 ]; then
log_info "Would convert (${current_bitrate} kbps ➜ ${TARGET_BITRATE}): ${file}"
((dry_run_count++))
return 0
fi
backup_file="${file%.mp3}.bak"
if [ ! -f "$backup_file" ]; then
cp "$file" "$backup_file"
log_success "Backup created: ${backup_file}"
fi
temp_file="${file%.mp3}_temp_$$.mp3"
if ffmpeg -nostdin -y -i "$file" -b:a "${TARGET_BITRATE}" -id3v2_version 3 "$temp_file" > /dev/null 2>&1; then
mv "$temp_file" "$file"
# Restore all metadata from backup file
if restore_metadata "$backup_file" "$file"; then
log_success "Converted: ${file} (${current_bitrate} kbps ➜ ${TARGET_BITRATE}) ✅"
((converted_count++))
else
log_error "Metadata restoration failed: ${file}"
# Revert to backup since metadata restoration failed
cp "$backup_file" "$file"
log_warn "Reverted to backup (conversion skipped due to metadata error)"
((error_count++))
fi
else
rm -f "$temp_file"
log_error "ffmpeg failed: ${file}"
((error_count++))
fi
}
# ==================== MAIN ====================
while getopts ":nd:ch" opt; do
case "$opt" in
n) DRY_RUN=1 ;;
d) ROOT_DIR="$OPTARG" ;;
c) CLEANUP_BACKUPS=1 ;;
h)
usage
exit 0
;;
*)
usage
exit 1
;;
esac
done
if ! command -v ffprobe >/dev/null 2>&1 || ! command -v ffmpeg >/dev/null 2>&1; then
log_error "ffprobe/ffmpeg not found. Please install ffmpeg."
exit 1
fi
if ! command -v id3cp >/dev/null 2>&1; then
log_error "id3cp not found. Please install id3lib (Arch: id3lib, Debian/Ubuntu: libid3-tools)."
exit 1
fi
if [ ! -d "$ROOT_DIR" ]; then
log_error "Directory not found: ${ROOT_DIR}"
exit 1
fi
log_step "Starting Bitrate Reduction"
log_info "Target bitrate: ${TARGET_BITRATE}"
log_info "Working directory: ${ROOT_DIR}"
if [ "$DRY_RUN" -eq 1 ]; then
log_info "Dry-run: enabled"
fi
if [ "$CLEANUP_BACKUPS" -eq 1 ]; then
log_info "Cleanup backups: enabled"
fi
total_before=$(get_total_size_mb "$ROOT_DIR")
log_info "Total MP3 size before: ${total_before:-0.00 MB}"
converted_count=0
skip_count=0
warn_count=0
error_count=0
dry_run_count=0
file_count=0
log_step "Scanning for MP3 files..."
while IFS= read -r -d '' file; do
((file_count++))
process_file "$file"
done < <(find "$ROOT_DIR" -type f -name "*.mp3" -print0)
if [ "$file_count" -eq 0 ]; then
log_warn "No MP3 files found in ${ROOT_DIR}"
fi
total_after=$(get_total_size_mb "$ROOT_DIR")
log_info "Total MP3 size after: ${total_after:-0.00 MB}"
log_step "Summary"
log_info "Files scanned: ${file_count}"
log_info "Converted: ${converted_count}"
log_info "Skipped: ${skip_count}"
log_info "Dry-run matches: ${dry_run_count}"
log_info "Warnings: ${warn_count}"
if [ "$error_count" -gt 0 ]; then
log_error "Errors: ${error_count}"
else
log_success "Errors: 0"
fi
if [ "$CLEANUP_BACKUPS" -eq 1 ] && [ "$DRY_RUN" -eq 0 ] && [ "$error_count" -eq 0 ]; then
log_step "Cleaning up backups"
find "$ROOT_DIR" -name "*.bak" -type f -delete
log_success "All .bak files removed"
elif [ "$CLEANUP_BACKUPS" -eq 1 ] && [ "$DRY_RUN" -eq 1 ]; then
log_warn "Cleanup skipped in dry-run mode"
elif [ "$CLEANUP_BACKUPS" -eq 1 ] && [ "$error_count" -gt 0 ]; then
log_warn "Cleanup skipped due to errors"
fi
log_step "Done 🎉"