refactor reduce_bitrate script for improved logging and functionality

add .gitignore for mp3 and bak files;
This commit is contained in:
2026-04-23 21:48:44 +03:30
parent 0c47186575
commit de9d5f0945
2 changed files with 159 additions and 58 deletions
+2
View File
@@ -0,0 +1,2 @@
*.mp3
*.bak
+157 -58
View File
@@ -1,81 +1,180 @@
#!/bin/bash #!/usr/bin/env bash
# Define the target bitrate
TARGET_BITRATE="192k" TARGET_BITRATE="192k"
ROOT_DIR="."
DRY_RUN=0
CLEANUP_BACKUPS=0
echo -e "\n\033[1;32mStarting Bitrate Reduction Script...\033[0m" GREEN="\033[1;32m"
echo -e "\033[1;34mTarget Bitrate: \033[0m${TARGET_BITRATE}" BLUE="\033[1;34m"
YELLOW="\033[1;33m"
RED="\033[1;31m"
DIM="\033[2m"
RESET="\033[0m"
# Function to process MP3 files safely usage() {
process_directory() { echo -e "${BLUE}Usage:${RESET} $0 [-n] [-d DIR] [-c]"
local DIR="$1" echo -e " -n Dry-run (only show files that would be converted)"
echo "Scanning Directory: $DIR" echo -e " -d DIR Working directory (default: current directory)"
echo -e " -c Remove .bak files after successful run"
echo -e " -h Show this help"
}
# Use find with -print0 + read -d '' → null-separated = safe with any filename log_step() { echo -e "\n${BLUE}▶${RESET} $*"; }
while IFS= read -r -d '' file; do log_info() { echo -e "${BLUE}ℹ${RESET} $*"; }
echo "Processing: $file" log_warn() { echo -e "${YELLOW}⚠${RESET} $*"; }
log_error() { echo -e "${RED}✖${RESET} $*"; }
log_success() { echo -e "${GREEN}✔${RESET} $*"; }
# Get current bitrate get_total_size_mb() {
METADATA=$(ffprobe -v error -select_streams a:0 -show_entries stream=bit_rate \ 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) -of default=noprint_wrappers=1:nokey=1 "$file" < /dev/null 2>/dev/null)
fi
if ! [[ "$METADATA" =~ ^[0-9]+$ ]]; then if [[ "$metadata" =~ ^[0-9]+$ ]]; then
METADATA=$(ffprobe -v error -show_entries format=bit_rate \ echo $((metadata / 1024))
-of default=noprint_wrappers=1:nokey=1 "$file" < /dev/null 2>/dev/null) return 0
fi fi
if ! [[ "$METADATA" =~ ^[0-9]+$ ]]; then return 1
echo -e "\033[1;31mError:\033[0m Could not read bit rate for $file. Skipping." }
continue
fi
CURRENT_BITRATE=$((METADATA / 1024)) process_file() {
echo -e "\033[1;32mCurrent Bitrate:\033[0m ${CURRENT_BITRATE} kbps" local file="$1"
local current_bitrate
local backup_file
local temp_file
if [ "$CURRENT_BITRATE" -le "${TARGET_BITRATE%k}" ]; then current_bitrate=$(get_bitrate_kbps "$file") || {
echo -e "\033[1;33mInfo:\033[0m Already at or below target. Skipping." log_warn "Could not read bitrate for: ${file}"
continue ((warn_count++))
fi return 0
}
# Create backup if [ "$current_bitrate" -le "${TARGET_BITRATE%k}" ]; then
BACKUP_FILE="${file%.mp3}.bak" log_info "Skip (already <= ${TARGET_BITRATE}): ${file}"
if [ ! -f "$BACKUP_FILE" ]; then ((skip_count++))
cp "$file" "$BACKUP_FILE" return 0
echo -e "\033[1;32mBackup created:\033[0m $BACKUP_FILE" fi
fi
# Convert safely using a temporary file with full path if [ "$DRY_RUN" -eq 1 ]; then
TEMP_FILE="${file%.mp3}_temp_$$.mp3" log_info "Would convert (${current_bitrate} kbps ➜ ${TARGET_BITRATE}): ${file}"
((dry_run_count++))
return 0
fi
if ffmpeg -nostdin -y -i "$file" -b:a "${TARGET_BITRATE}" -id3v2_version 3 "$TEMP_FILE" > /dev/null 2>&1; then backup_file="${file%.mp3}.bak"
mv "$TEMP_FILE" "$file" if [ ! -f "$backup_file" ]; then
echo -e "\033[1;32mProcessed \033[0m$file \033[1;32mto ${TARGET_BITRATE} ✅\033[0m" cp "$file" "$backup_file"
else log_success "Backup created: ${backup_file}"
echo -e "\033[1;31mError:\033[0m ffmpeg failed for $file" fi
rm -f "$TEMP_FILE"
fi
done < <(find "$DIR" -maxdepth 1 -type f -name "*.mp3" -print0) 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 ==================== # ==================== MAIN ====================
# Calculate total size before while getopts ":nd:ch" opt; do
TOTAL_SIZE_BEFORE=$(find . -name "*.mp3" -print0 | xargs -0 stat -c '%s' 2>/dev/null | \ case "$opt" in
awk '{sum += $1} END {printf "%.2f MB\n", sum/1024/1024}') n) DRY_RUN=1 ;;
echo -e "\n\033[1;32mTotal MP3 size before:\033[0m ${TOTAL_SIZE_BEFORE:-0.00 MB}" d) ROOT_DIR="$OPTARG" ;;
c) CLEANUP_BACKUPS=1 ;;
h)
usage
exit 0
;;
*)
usage
exit 1
;;
esac
done
# Process every directory (including .) if ! command -v ffprobe >/dev/null 2>&1 || ! command -v ffmpeg >/dev/null 2>&1; then
while IFS= read -r -d '' dir; do log_error "ffprobe/ffmpeg not found. Please install ffmpeg."
process_directory "$dir" exit 1
done < <(find . -type d -print0) fi
# Calculate total size after if [ ! -d "$ROOT_DIR" ]; then
TOTAL_SIZE_AFTER=$(find . -name "*.mp3" -print0 | xargs -0 stat -c '%s' 2>/dev/null | \ log_error "Directory not found: ${ROOT_DIR}"
awk '{sum += $1} END {printf "%.2f MB\n", sum/1024/1024}') exit 1
fi
echo -e "\n\033[1;32mTotal MP3 size after:\033[0m ${TOTAL_SIZE_AFTER:-0.00 MB}" log_step "Starting Bitrate Reduction"
echo -e "\n\033[1;32mBitrate Reduction Script Complete! 🎉\033[0m" 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
# Optional: remove all .bak files at the end total_before=$(get_total_size_mb "$ROOT_DIR")
# find . -name "*.bak" -type f -delete 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 🎉"