commit 8ae6274806a6cf1ccc80c62e4ae43b1bf1987184 Author: nnduc Date: Sat Jul 12 19:27:13 2025 +0700 add flac downsampling diff --git a/ffmpeg-flac-downsampling.sh b/ffmpeg-flac-downsampling.sh new file mode 100644 index 0000000..b2d63fa --- /dev/null +++ b/ffmpeg-flac-downsampling.sh @@ -0,0 +1,268 @@ +#!/bin/bash + +# Media Library FLAC Downsampling Script +# Processes multiple albums in a media library, downsampling all FLAC files to 44.1 kHz + +# Check if ffmpeg is installed +if ! command -v ffmpeg &> /dev/null; then + echo "Error: ffmpeg is not installed or not in PATH" + exit 1 +fi + +# Function to display usage +show_usage() { + echo "Usage: $0 [OPTIONS] " + echo "" + echo "Options:" + echo " -o, --output DIR Output directory (default: creates _44k suffix)" + echo " -r, --replace Replace original files (use with caution)" + echo " -p, --preserve Preserve original files alongside new ones" + echo " -t, --test Test mode - show what would be processed" + echo " -h, --help Show this help message" + echo "" + echo "Examples:" + echo " $0 /path/to/music/library" + echo " $0 -o /path/to/output /path/to/music/library" + echo " $0 --test /path/to/music/library" + echo " $0 --replace /path/to/music/library" +} + +# Default options +OUTPUT_DIR="" +REPLACE_MODE=false +PRESERVE_MODE=false +TEST_MODE=false +PROCESSED_COUNT=0 +SKIPPED_COUNT=0 +ERROR_COUNT=0 + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -o|--output) + OUTPUT_DIR="$2" + shift 2 + ;; + -r|--replace) + REPLACE_MODE=true + shift + ;; + -p|--preserve) + PRESERVE_MODE=true + shift + ;; + -t|--test) + TEST_MODE=true + shift + ;; + -h|--help) + show_usage + exit 0 + ;; + -*) + echo "Unknown option: $1" + show_usage + exit 1 + ;; + *) + MEDIA_LIBRARY="$1" + shift + ;; + esac +done + +# Check if media library path is provided +if [ -z "$MEDIA_LIBRARY" ]; then + echo "Error: Media library path is required" + show_usage + exit 1 +fi + +# Check if media library exists +if [ ! -d "$MEDIA_LIBRARY" ]; then + echo "Error: Media library directory '$MEDIA_LIBRARY' not found" + exit 1 +fi + +# Set output directory if not specified +if [ -z "$OUTPUT_DIR" ] && [ "$REPLACE_MODE" = false ]; then + OUTPUT_DIR="${MEDIA_LIBRARY}_44k" +fi + +# Function to check if file needs downsampling +needs_downsampling() { + local file="$1" + + # Check if file is readable + if [ ! -r "$file" ]; then + echo " WARNING: File not readable: $file" + return 1 + fi + + local sample_rate=$(ffprobe -v quiet -select_streams a:0 -show_entries stream=sample_rate -of csv=p=0 "$file" 2>/dev/null) + + if [ -z "$sample_rate" ]; then + echo " WARNING: Could not determine sample rate for: $(basename "$file")" + return 1 # Skip files we can't analyze + elif [ "$sample_rate" = "44100" ]; then + return 1 # No downsampling needed + else + echo " Current sample rate: ${sample_rate} Hz" + return 0 # Needs downsampling + fi +} + +# Function to process a single FLAC file +process_flac_file() { + local input_file="$1" + local relative_path="$2" + local output_file="" + + # Determine output file path + if [ "$REPLACE_MODE" = true ]; then + output_file="$input_file" + elif [ "$PRESERVE_MODE" = true ]; then + local dir=$(dirname "$input_file") + local filename=$(basename "$input_file" .flac) + output_file="$dir/${filename}_44k.flac" + else + output_file="$OUTPUT_DIR/$relative_path" + fi + + # Check if downsampling is needed + if ! needs_downsampling "$input_file"; then + echo " SKIP: $relative_path (already 44.1 kHz)" + ((SKIPPED_COUNT++)) + return + fi + + if [ "$TEST_MODE" = true ]; then + echo " WOULD PROCESS: $relative_path → $(basename "$output_file")" + return + fi + + # Create output directory if needed + local output_dir=$(dirname "$output_file") + if [ ! -d "$output_dir" ]; then + echo " Creating directory: $output_dir" + if ! mkdir -p "$output_dir"; then + echo " ✗ ERROR: Cannot create output directory: $output_dir" + ((ERROR_COUNT++)) + return + fi + fi + + # Verify input file exists and is readable + if [ ! -f "$input_file" ]; then + echo " ✗ ERROR: Input file not found: $input_file" + ((ERROR_COUNT++)) + return + fi + + if [ ! -r "$input_file" ]; then + echo " ✗ ERROR: Input file not readable: $input_file" + ((ERROR_COUNT++)) + return + fi + + # Create temporary file for safe processing (use .flac extension) + local temp_file="${output_file}.tmp.flac" + + echo " PROCESSING: $relative_path" + + # Downsample the file + echo " Command: ffmpeg -i \"$input_file\" -ar 44100 -c:a flac -compression_level 8 -y \"$temp_file\"" + + if ffmpeg -i "$input_file" -ar 44100 -c:a flac -compression_level 8 -y "$temp_file" 2>"${temp_file}.log"; then + # Move temp file to final location + mv "$temp_file" "$output_file" + echo " ✓ DONE: $(basename "$output_file")" + rm -f "${temp_file}.log" + ((PROCESSED_COUNT++)) + else + echo " ✗ ERROR: Failed to process $relative_path" + echo " Error log saved to: ${temp_file}.log" + if [ -f "${temp_file}.log" ]; then + echo " Last few lines of error:" + tail -n 5 "${temp_file}.log" | sed 's/^/ /' + fi + rm -f "$temp_file" + ((ERROR_COUNT++)) + fi +} + +# Function to process an album directory +process_album() { + local album_dir="$1" + local album_name=$(basename "$album_dir") + + echo "Processing album: $album_name" + + # Find all FLAC files in the album directory + local flac_files=() + while IFS= read -r -d '' file; do + flac_files+=("$file") + done < <(find "$album_dir" -name "*.flac" -type f -print0) + + if [ ${#flac_files[@]} -eq 0 ]; then + echo " No FLAC files found in $album_name" + return + fi + + # Process each FLAC file + for flac_file in "${flac_files[@]}"; do + local relative_path="${flac_file#$MEDIA_LIBRARY/}" + process_flac_file "$flac_file" "$relative_path" + done + + echo "" +} + +# Main processing +echo "Media Library FLAC Downsampler" +echo "===============================" +echo "Input: $MEDIA_LIBRARY" + +if [ "$TEST_MODE" = true ]; then + echo "Mode: TEST MODE (no files will be modified)" +elif [ "$REPLACE_MODE" = true ]; then + echo "Mode: REPLACE (original files will be overwritten)" +elif [ "$PRESERVE_MODE" = true ]; then + echo "Mode: PRESERVE (44k files created alongside originals)" +else + echo "Output: $OUTPUT_DIR" +fi + +echo "" + +# Find all directories that contain FLAC files (potential albums) +album_dirs=() +while IFS= read -r -d '' dir; do + if find "$dir" -maxdepth 1 -name "*.flac" -type f | grep -q .; then + album_dirs+=("$dir") + fi +done < <(find "$MEDIA_LIBRARY" -type d -print0) + +if [ ${#album_dirs[@]} -eq 0 ]; then + echo "No albums with FLAC files found in $MEDIA_LIBRARY" + exit 0 +fi + +echo "Found ${#album_dirs[@]} album(s) with FLAC files" +echo "" + +# Process each album +for album_dir in "${album_dirs[@]}"; do + process_album "$album_dir" +done + +# Summary +echo "Processing Summary:" +echo "==================" +echo "Processed: $PROCESSED_COUNT files" +echo "Skipped: $SKIPPED_COUNT files (already 44.1 kHz)" +echo "Errors: $ERROR_COUNT files" + +if [ "$ERROR_COUNT" -gt 0 ]; then + exit 1 +fi