diff --git a/wav-to-flac.sh b/wav-to-flac.sh new file mode 100755 index 0000000..a043ac6 --- /dev/null +++ b/wav-to-flac.sh @@ -0,0 +1,303 @@ +#!/bin/bash + +# Media Library WAV to FLAC Transcoding Script +# Processes multiple albums in a media library, converting all WAV files to FLAC + +# 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 _flac suffix)" + echo " -r, --replace Replace original files (use with caution)" + echo " -p, --preserve Preserve original files alongside new ones" + echo " -c, --compression N FLAC compression level 0-8 (default: 8)" + 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" + echo " $0 -c 5 /path/to/music/library" +} + +# Default options +OUTPUT_DIR="" +REPLACE_MODE=false +PRESERVE_MODE=false +TEST_MODE=false +COMPRESSION_LEVEL=8 +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 + ;; + -c|--compression) + COMPRESSION_LEVEL="$2" + if [[ ! "$COMPRESSION_LEVEL" =~ ^[0-8]$ ]]; then + echo "Error: Compression level must be 0-8" + exit 1 + fi + shift 2 + ;; + -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}_flac" +fi + +# Function to check if file needs transcoding +needs_transcoding() { + local file="$1" + + # Check if file is readable + if [ ! -r "$file" ]; then + echo " WARNING: File not readable: $file" + return 1 + fi + + # Get file format info + local format_info=$(ffprobe -v quiet -select_streams a:0 -show_entries stream=codec_name -of csv=p=0 "$file" 2>/dev/null) + + if [ -z "$format_info" ]; then + echo " WARNING: Could not determine format for: $(basename "$file")" + return 1 # Skip files we can't analyze + else + echo " Current format: $format_info" + return 0 # Needs transcoding + fi +} + +# Function to process a single WAV file +process_wav_file() { + local input_file="$1" + local relative_path="$2" + local output_file="" + + # Determine output file path + if [ "$REPLACE_MODE" = true ]; then + local dir=$(dirname "$input_file") + local filename=$(basename "$input_file" .wav) + output_file="$dir/${filename}.flac" + elif [ "$PRESERVE_MODE" = true ]; then + local dir=$(dirname "$input_file") + local filename=$(basename "$input_file" .wav) + output_file="$dir/${filename}.flac" + else + local output_relative_path="${relative_path%.wav}.flac" + output_file="$OUTPUT_DIR/$output_relative_path" + fi + + # Check if transcoding is needed + if ! needs_transcoding "$input_file"; then + echo " SKIP: $relative_path (cannot analyze)" + ((SKIPPED_COUNT++)) + return + fi + + # Check if output file already exists and is the same as input (prevent overwriting) + if [ "$output_file" = "$input_file" ]; then + echo " SKIP: $relative_path (output would overwrite input)" + ((SKIPPED_COUNT++)) + return + fi + + # Check if output file already exists + if [ -f "$output_file" ] && [ "$REPLACE_MODE" = false ]; then + echo " SKIP: $relative_path (output file already exists)" + ((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 + local temp_file="${output_file}.tmp.flac" + + echo " PROCESSING: $relative_path" + + # Transcode the file + echo " Command: ffmpeg -i \"$input_file\" -c:a flac -compression_level $COMPRESSION_LEVEL -y \"$temp_file\"" + + if ffmpeg -i "$input_file" -c:a flac -compression_level "$COMPRESSION_LEVEL" -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" + + # Remove original file if in replace mode + if [ "$REPLACE_MODE" = true ]; then + echo " Removing original: $(basename "$input_file")" + rm "$input_file" + fi + + ((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 WAV files in the album directory + local wav_files=() + while IFS= read -r -d '' file; do + wav_files+=("$file") + done < <(find "$album_dir" -name "*.wav" -type f -print0) + + if [ ${#wav_files[@]} -eq 0 ]; then + echo " No WAV files found in $album_name" + return + fi + + # Process each WAV file + for wav_file in "${wav_files[@]}"; do + local relative_path="${wav_file#$MEDIA_LIBRARY/}" + process_wav_file "$wav_file" "$relative_path" + done + + echo "" +} + +# Main processing +echo "Media Library WAV to FLAC Transcoder" +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 WAV files will be deleted after conversion)" +elif [ "$PRESERVE_MODE" = true ]; then + echo "Mode: PRESERVE (FLAC files created alongside originals)" +else + echo "Output: $OUTPUT_DIR" +fi + +echo "FLAC Compression Level: $COMPRESSION_LEVEL" +echo "" + +# Find all directories that contain WAV files (potential albums) +album_dirs=() +while IFS= read -r -d '' dir; do + if find "$dir" -maxdepth 1 -name "*.wav" -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 WAV files found in $MEDIA_LIBRARY" + exit 0 +fi + +echo "Found ${#album_dirs[@]} album(s) with WAV 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" +echo "Errors: $ERROR_COUNT files" + +if [ "$ERROR_COUNT" -gt 0 ]; then + exit 1 +fi \ No newline at end of file