Files
scripts/reduce_bitrate.sh
T
2026-04-23 21:48:44 +03:30

181 lines
4.8 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
}
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"
log_success "Converted: ${file} (${current_bitrate} kbps ➜ ${TARGET_BITRATE}) ✅"
((converted_count++))
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 [ ! -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 🎉"