#!/bin/bash # Import, transcode, and organize music from slskd downloads # Usage: ./import_music.sh [--dry-run] [--keep-flac] INGEST_DIR="/disks/Plex/Music/_ingest" LIBRARY_DIR="/disks/Plex/Music" GROUP="mediaserver" BITRATE="128k" DRY_RUN=false KEEP_FLAC=false for arg in "$@"; do case $arg in --dry-run) DRY_RUN=true ;; --keep-flac) KEEP_FLAC=true ;; esac done echo "=== Music Import Started ===" $DRY_RUN && echo "DRY RUN MODE" # Skip these directories shopt -s extglob cd "$INGEST_DIR" || exit 1 for album_dir in */; do [[ ! -d "$album_dir" ]] && continue dir_name="${album_dir%/}" # Skip special dirs [[ "$dir_name" == "incomplete" ]] && continue [[ "$dir_name" == "downloads" ]] && continue # Count audio files audio_count=$(ls -1 "$album_dir"*.flac "$album_dir"*.mp3 "$album_dir"*.opus 2>/dev/null | wc -l) [[ $audio_count -lt 2 ]] && continue echo "" echo "Processing: $dir_name ($audio_count files)" # Get first audio file for metadata first_file=$(ls "$album_dir"*.flac "$album_dir"*.mp3 2>/dev/null | head -1) [[ -z "$first_file" ]] && continue # Extract metadata artist=$(ffprobe -v quiet -show_entries format_tags=artist -of default=noprint_wrappers=1:nokey=1 "$first_file" 2>/dev/null) album_artist=$(ffprobe -v quiet -show_entries format_tags=album_artist -of default=noprint_wrappers=1:nokey=1 "$first_file" 2>/dev/null) album=$(ffprobe -v quiet -show_entries format_tags=album -of default=noprint_wrappers=1:nokey=1 "$first_file" 2>/dev/null) # Prefer album_artist over artist [[ -n "$album_artist" ]] && artist="$album_artist" # Clean for filesystem artist=$(echo "$artist" | tr -d '<>:"/\\|?*' | sed 's/\.$//') album=$(echo "$album" | tr -d '<>:"/\\|?*' | sed 's/\.$//') if [[ -z "$artist" ]] || [[ -z "$album" ]]; then echo " ⚠ Missing metadata, skipping" continue fi dest_dir="$LIBRARY_DIR/$artist/$album" echo " → $artist / $album" if $DRY_RUN; then echo " [DRY RUN] Would transcode to $dest_dir" continue fi mkdir -p "$dest_dir" # Transcode FLACs for flac in "$album_dir"*.flac "$album_dir"*.FLAC; do [[ ! -f "$flac" ]] && continue base=$(basename "$flac") # Remove .flac extension properly name="${base%.[Ff][Ll][Aa][Cc]}" opus_out="$dest_dir/${name}.opus" echo " Transcoding: $name" ffmpeg -hide_banner -loglevel error -i "$flac" \ -c:a libopus -b:a "$BITRATE" -vbr on \ -map_metadata 0 -y "$opus_out" [[ -f "$opus_out" ]] && chgrp "$GROUP" "$opus_out" 2>/dev/null done # Remove any FLACs from destination (we only want Opus there) for flac in "$dest_dir"/*.flac "$dest_dir"/*.FLAC; do [[ -f "$flac" ]] && rm "$flac" done # Copy non-FLAC audio for audio in "$album_dir"*.mp3 "$album_dir"*.m4a "$album_dir"*.ogg; do [[ -f "$audio" ]] && cp "$audio" "$dest_dir/" done # Copy artwork for art in "$album_dir"*.jpg "$album_dir"*.png "$album_dir"cover.* "$album_dir"folder.*; do [[ -f "$art" ]] && cp "$art" "$dest_dir/" done # Fix permissions chgrp -R "$GROUP" "$dest_dir" 2>/dev/null chmod -R g+rw "$dest_dir" 2>/dev/null # Run beets for tagging echo " Running beets..." beet import -q "$dest_dir" 2>/dev/null if [[ $? -eq 0 ]]; then echo " ✓ Beets tagging complete" else echo " ⚠ Beets tagging skipped or failed (check manually)" fi # Verify new_count=$(ls -1 "$dest_dir"/*.opus "$dest_dir"/*.mp3 2>/dev/null | wc -l) if [[ $new_count -ge 3 ]]; then echo " ✓ Imported $new_count files" rm -rf "$INGEST_DIR/$dir_name" echo " ✓ Cleaned source" else echo " ✗ Verification failed" fi done echo "" echo "=== Done ==="