diff --git a/ffmpeg-flac-downsampling.sh b/ffmpeg-flac-downsampling.sh old mode 100644 new mode 100755 diff --git a/file-cleaning.sh b/file-cleaning.sh old mode 100644 new mode 100755 diff --git a/flac-opus-compressing.sh b/flac-opus-compressing.sh new file mode 100755 index 0000000..a4ccb9e --- /dev/null +++ b/flac-opus-compressing.sh @@ -0,0 +1,309 @@ +#!/bin/bash + +# Media Library FLAC to Opus Conversion Script +# Processes multiple albums in a media library, converting all FLAC files to Opus 448kbps + +# 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 + +# Check if ffmpeg has opus encoder support +if ! ffmpeg -encoders 2>/dev/null | grep -q "libopus"; then + echo "Error: ffmpeg does not have Opus encoder support (libopus)" + echo "Please install ffmpeg with Opus support" + exit 1 +fi + +# Function to display usage +show_usage() { + echo "Usage: $0 [OPTIONS] " + echo "" + echo "Options:" + echo " -o, --output DIR Output directory (default: creates _opus suffix)" + echo " -r, --replace Replace original files with Opus (use with caution)" + echo " -p, --preserve Create Opus files alongside original FLAC files" + echo " -t, --test Test mode - show what would be processed" + echo " -f, --force Force re-conversion of existing Opus files" + echo " -b, --bitrate RATE Opus bitrate in kbps (default: 448)" + 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 -b 384 /path/to/music/library" +} + +# Default options +OUTPUT_DIR="" +REPLACE_MODE=false +PRESERVE_MODE=false +TEST_MODE=false +FORCE_MODE=false +OPUS_BITRATE=448 +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 + ;; + -f|--force) + FORCE_MODE=true + shift + ;; + -b|--bitrate) + OPUS_BITRATE="$2" + shift 2 + ;; + -h|--help) + show_usage + exit 0 + ;; + -*) + echo "Unknown option: $1" + show_usage + exit 1 + ;; + *) + MEDIA_LIBRARY="$1" + shift + ;; + esac +done + +# Validate bitrate +if ! [[ "$OPUS_BITRATE" =~ ^[0-9]+$ ]] || [ "$OPUS_BITRATE" -lt 64 ] || [ "$OPUS_BITRATE" -gt 512 ]; then + echo "Error: Invalid bitrate '$OPUS_BITRATE'. Must be between 64 and 512 kbps" + exit 1 +fi + +# 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}_opus" +fi + +# Function to check if file needs conversion +needs_conversion() { + local input_file="$1" + local output_file="$2" + + # Check if input file is readable + if [ ! -r "$input_file" ]; then + echo " WARNING: File not readable: $input_file" + return 1 + fi + + # If force mode, always convert + if [ "$FORCE_MODE" = true ]; then + return 0 + fi + + # Check if output file already exists and is newer than input + if [ -f "$output_file" ] && [ "$output_file" -nt "$input_file" ]; then + return 1 # No conversion needed + fi + + return 0 # Needs conversion +} + +# 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 + # Replace .flac with .opus in same location + output_file="${input_file%.flac}.opus" + elif [ "$PRESERVE_MODE" = true ]; then + local dir=$(dirname "$input_file") + local filename=$(basename "$input_file" .flac) + output_file="$dir/${filename}.opus" + else + # Change extension from .flac to .opus + local opus_relative_path="${relative_path%.flac}.opus" + output_file="$OUTPUT_DIR/$opus_relative_path" + fi + + # Check if conversion is needed + if ! needs_conversion "$input_file" "$output_file"; then + echo " SKIP: $relative_path (Opus file exists and is newer)" + ((SKIPPED_COUNT++)) + return + fi + + if [ "$TEST_MODE" = true ]; then + echo " WOULD CONVERT: $relative_path → $(basename "$output_file") @ ${OPUS_BITRATE}kbps" + 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.opus" + + echo " CONVERTING: $relative_path → Opus ${OPUS_BITRATE}kbps" + + # Convert the file to Opus + echo " Command: ffmpeg -i \"$input_file\" -c:a libopus -b:a ${OPUS_BITRATE}k -vbr on -compression_level 10 -y \"$temp_file\"" + + if ffmpeg -i "$input_file" -c:a libopus -b:a "${OPUS_BITRATE}k" -vbr on -compression_level 10 -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++)) + + # If replace mode, remove original FLAC file + if [ "$REPLACE_MODE" = true ]; then + rm "$input_file" + echo " Removed original: $(basename "$input_file")" + fi + else + echo " ✗ ERROR: Failed to convert $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 to Opus Converter" +echo "====================================" +echo "Input: $MEDIA_LIBRARY" +echo "Bitrate: ${OPUS_BITRATE}kbps" + +if [ "$TEST_MODE" = true ]; then + echo "Mode: TEST MODE (no files will be modified)" +elif [ "$REPLACE_MODE" = true ]; then + echo "Mode: REPLACE (original FLAC files will be deleted after conversion)" +elif [ "$PRESERVE_MODE" = true ]; then + echo "Mode: PRESERVE (Opus files created alongside originals)" +else + echo "Output: $OUTPUT_DIR" +fi + +if [ "$FORCE_MODE" = true ]; then + echo "Force: Enabled (will re-convert existing Opus files)" +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 "Conversion Summary:" +echo "==================" +echo "Converted: $PROCESSED_COUNT files" +echo "Skipped: $SKIPPED_COUNT files (already converted or newer)" +echo "Errors: $ERROR_COUNT files" + +if [ "$ERROR_COUNT" -gt 0 ]; then + exit 1 +fi \ No newline at end of file