From de9d5f0945917eb09406d94eaa2bbf9773a8d137 Mon Sep 17 00:00:00 2001 From: Amir Aref Date: Thu, 23 Apr 2026 21:48:44 +0330 Subject: [PATCH] refactor reduce_bitrate script for improved logging and functionality add .gitignore for mp3 and bak files; --- .gitignore | 2 + reduce_bitrate.sh | 215 +++++++++++++++++++++++++++++++++------------- 2 files changed, 159 insertions(+), 58 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7333585 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.mp3 +*.bak \ No newline at end of file diff --git a/reduce_bitrate.sh b/reduce_bitrate.sh index 4b4aab9..8e53b17 100755 --- a/reduce_bitrate.sh +++ b/reduce_bitrate.sh @@ -1,81 +1,180 @@ -#!/bin/bash +#!/usr/bin/env bash -# Define the target bitrate TARGET_BITRATE="192k" +ROOT_DIR="." +DRY_RUN=0 +CLEANUP_BACKUPS=0 -echo -e "\n\033[1;32mStarting Bitrate Reduction Script...\033[0m" -echo -e "\033[1;34mTarget Bitrate: \033[0m${TARGET_BITRATE}" +GREEN="\033[1;32m" +BLUE="\033[1;34m" +YELLOW="\033[1;33m" +RED="\033[1;31m" +DIM="\033[2m" +RESET="\033[0m" -# Function to process MP3 files safely -process_directory() { - local DIR="$1" - echo "Scanning Directory: $DIR" +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" +} - # Use find with -print0 + read -d '' → null-separated = safe with any filename - while IFS= read -r -d '' file; do - echo "Processing: $file" +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 current bitrate - METADATA=$(ffprobe -v error -select_streams a:0 -show_entries stream=bit_rate \ +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 - 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 - if ! [[ "$METADATA" =~ ^[0-9]+$ ]]; then - echo -e "\033[1;31mError:\033[0m Could not read bit rate for $file. Skipping." - continue - fi + return 1 +} - CURRENT_BITRATE=$((METADATA / 1024)) - echo -e "\033[1;32mCurrent Bitrate:\033[0m ${CURRENT_BITRATE} kbps" +process_file() { + local file="$1" + local current_bitrate + local backup_file + local temp_file - if [ "$CURRENT_BITRATE" -le "${TARGET_BITRATE%k}" ]; then - echo -e "\033[1;33mInfo:\033[0m Already at or below target. Skipping." - continue - fi + current_bitrate=$(get_bitrate_kbps "$file") || { + log_warn "Could not read bitrate for: ${file}" + ((warn_count++)) + return 0 + } - # Create backup - BACKUP_FILE="${file%.mp3}.bak" - if [ ! -f "$BACKUP_FILE" ]; then - cp "$file" "$BACKUP_FILE" - echo -e "\033[1;32mBackup created:\033[0m $BACKUP_FILE" - fi + if [ "$current_bitrate" -le "${TARGET_BITRATE%k}" ]; then + log_info "Skip (already <= ${TARGET_BITRATE}): ${file}" + ((skip_count++)) + return 0 + fi - # Convert safely using a temporary file with full path - TEMP_FILE="${file%.mp3}_temp_$$.mp3" + if [ "$DRY_RUN" -eq 1 ]; then + 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 - mv "$TEMP_FILE" "$file" - echo -e "\033[1;32mProcessed \033[0m$file \033[1;32mto ${TARGET_BITRATE} ✅\033[0m" - else - echo -e "\033[1;31mError:\033[0m ffmpeg failed for $file" - rm -f "$TEMP_FILE" - fi + backup_file="${file%.mp3}.bak" + if [ ! -f "$backup_file" ]; then + cp "$file" "$backup_file" + log_success "Backup created: ${backup_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 ==================== -# Calculate total size before -TOTAL_SIZE_BEFORE=$(find . -name "*.mp3" -print0 | xargs -0 stat -c '%s' 2>/dev/null | \ - awk '{sum += $1} END {printf "%.2f MB\n", sum/1024/1024}') -echo -e "\n\033[1;32mTotal MP3 size before:\033[0m ${TOTAL_SIZE_BEFORE:-0.00 MB}" +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 -# Process every directory (including .) -while IFS= read -r -d '' dir; do - process_directory "$dir" -done < <(find . -type d -print0) +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 -# Calculate total size after -TOTAL_SIZE_AFTER=$(find . -name "*.mp3" -print0 | xargs -0 stat -c '%s' 2>/dev/null | \ - awk '{sum += $1} END {printf "%.2f MB\n", sum/1024/1024}') +if [ ! -d "$ROOT_DIR" ]; then + log_error "Directory not found: ${ROOT_DIR}" + exit 1 +fi -echo -e "\n\033[1;32mTotal MP3 size after:\033[0m ${TOTAL_SIZE_AFTER:-0.00 MB}" -echo -e "\n\033[1;32mBitrate Reduction Script Complete! 🎉\033[0m" +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 -# Optional: remove all .bak files at the end -# find . -name "*.bak" -type f -delete +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 🎉"