#!/bin/bash # ============================================================================================== # ================================= Music Artwork Fetcher ====================================== # ============================================================================================== # # SAFE DESIGN # - READS from Lidarr only # - NEVER modifies tags # - NEVER renames media # - NEVER writes to Lidarr # - ONLY writes missing artwork files # # ARTWORK # # Album Folder: # cover.jpg # cdart.png # back.jpg # # Artist Folder: # folder.jpg # fanart.jpg # logo.png # banner.jpg # # SOURCES # # Album Covers: # fanart.tv # iTunes fallback # # Artist Art: # fanart.tv # Deezer fallback # Last.fm fallback # ================================= CONFIG ===================================================== LIDARR_URL="http://localhost:8686" LIDARR_API_KEY="b2977e71ef074bc0a0529d9fcce3b2dc" FANART_API_KEY="Yd7147a43b692df0b364b94dc47efb81" LASTFM_API_KEY="be6dc169c33ae263e690c30d18b7491d" LOG="/tmp/artfetch.log" MIN_SIZE=10000 MAX_PARALLEL=4 RETRIES=2 SLEEP_BETWEEN=0.2 # ================================= FLAGS ====================================================== DRY_RUN=0 if [[ "$1" == "--dry-run" ]]; then DRY_RUN=1 echo "Running in DRY-RUN mode" fi # ================================= LOGGING ==================================================== log() { echo "$(date '+%F %T') | $1" >> "$LOG" } # ================================= HELPERS ==================================================== curl_json() { curl -s \ --connect-timeout 5 \ --max-time 20 \ "$1" } job_count() { jobs -rp | wc -l } wait_for_slot() { while (( $(job_count) >= MAX_PARALLEL )); do sleep 0.2 done } # ================================= DOWNLOAD =================================================== download_if_valid() { local url="$1" local dest="$2" [[ -z "$url" || "$url" == "null" ]] && return 1 # never overwrite existing [[ -f "$dest" ]] && return 0 if [[ "$DRY_RUN" -eq 1 ]]; then log "[DRY] $dest" return 0 fi for ((i=0; i<=RETRIES; i++)); do tmp="${dest}.tmp" curl -s \ --connect-timeout 5 \ --max-time 20 \ -L \ -o "$tmp" \ "$url" size=$(stat -c%s "$tmp" 2>/dev/null) if [[ "$size" -gt "$MIN_SIZE" ]]; then mv "$tmp" "$dest" log "[OK] $dest" return 0 fi rm -f "$tmp" sleep 1 done log "[FAIL] $dest" return 1 } # ================================= DEEZER ===================================================== deezer_artist_image() { local artist="$1" query=$(printf "%s" "$artist" | sed 's/ /+/g') curl_json \ "https://api.deezer.com/search/artist?q=$query" | jq -r '.data[0].picture_xl // empty' } # ================================= LASTFM ===================================================== lastfm_artist_image() { local artist="$1" encoded=$(printf "%s" "$artist" | sed 's/ /%20/g') curl_json \ "https://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist=$encoded&api_key=$LASTFM_API_KEY&format=json" | jq -r '.artist.image[-1]["#text"] // empty' } # ============================================================================================== # ====================================== ALBUMS ================================================= # ============================================================================================== echo "===== PROCESSING ALBUMS =====" albums=$(curl_json \ "$LIDARR_URL/api/v1/album?apikey=$LIDARR_API_KEY") [[ -z "$albums" || "$albums" == "null" ]] && { echo "ERROR: Lidarr album API failed" exit 1 } total_albums=$(echo "$albums" | jq '. | length') album_count=0 echo "$albums" | jq -c '.[]?' | while read -r album; do ((album_count++)) path=$(echo "$album" | jq -r '.path') mbid=$(echo "$album" | jq -r '.foreignAlbumId') artist=$(echo "$album" | jq -r '.artist.artistName') name=$(echo "$album" | jq -r '.title') [[ ! -d "$path" ]] && continue printf '[ALBUM %s/%s] %s - %s\n' \ "$album_count" "$total_albums" "$artist" "$name" # skip complete albums if [[ -f "$path/cover.jpg" && -f "$path/cdart.png" && -f "$path/back.jpg" ]]; then echo " -> complete" continue fi wait_for_slot ( JSON="" if [[ -n "$mbid" && "$mbid" != "null" ]]; then JSON=$(curl_json \ "http://webservice.fanart.tv/v3/music/albums/$mbid?api_key=$FANART_API_KEY") sleep "$SLEEP_BETWEEN" fi # ================= COVER ================= if [[ ! -f "$path/cover.jpg" ]]; then IMG=$(echo "$JSON" | jq -r '.[].albumcover[0].url // empty') if ! download_if_valid "$IMG" "$path/cover.jpg"; then query=$(printf "%s %s" "$artist" "$name" | sed 's/ /+/g') itunes=$(curl_json \ "https://itunes.apple.com/search?term=$query&entity=album&limit=1" | jq -r '.results[0].artworkUrl100 // empty' | sed 's/100x100/600x600/') download_if_valid "$itunes" "$path/cover.jpg" fi fi # ================= CDART ================= if [[ ! -f "$path/cdart.png" ]]; then IMG=$(echo "$JSON" | jq -r '.[].cdart[0].url // empty') download_if_valid "$IMG" "$path/cdart.png" fi # ================= BACK ================= if [[ ! -f "$path/back.jpg" ]]; then IMG=$(echo "$JSON" | jq -r '.[].albumback[0].url // empty') download_if_valid "$IMG" "$path/back.jpg" fi ) & done wait # ============================================================================================== # ====================================== ARTISTS ================================================ # ============================================================================================== echo echo "===== PROCESSING ARTISTS =====" artists=$(curl_json \ "$LIDARR_URL/api/v1/artist?apikey=$LIDARR_API_KEY") [[ -z "$artists" || "$artists" == "null" ]] && { echo "ERROR: Lidarr artist API failed" exit 1 } total_artists=$(echo "$artists" | jq '. | length') artist_count=0 echo "$artists" | jq -c '.[]?' | while read -r artist; do ((artist_count++)) path=$(echo "$artist" | jq -r '.path') mbid=$(echo "$artist" | jq -r '.foreignArtistId') name=$(echo "$artist" | jq -r '.artistName') [[ ! -d "$path" ]] && continue [[ -z "$mbid" || "$mbid" == "null" ]] && continue printf '[ARTIST %s/%s] %s\n' \ "$artist_count" "$total_artists" "$name" # skip complete artists if [[ -f "$path/folder.jpg" && -f "$path/fanart.jpg" && -f "$path/logo.png" && -f "$path/banner.jpg" ]]; then echo " -> complete" continue fi wait_for_slot ( JSON=$(curl_json \ "http://webservice.fanart.tv/v3/music/$mbid?api_key=$FANART_API_KEY") sleep "$SLEEP_BETWEEN" # ================= folder.jpg ================= if [[ ! -f "$path/folder.jpg" ]]; then IMG=$(echo "$JSON" | jq -r '.artistthumb[0].url // empty') if ! download_if_valid "$IMG" "$path/folder.jpg"; then IMG=$(deezer_artist_image "$name") if ! download_if_valid "$IMG" "$path/folder.jpg"; then IMG=$(lastfm_artist_image "$name") download_if_valid "$IMG" "$path/folder.jpg" fi fi fi # ================= fanart.jpg ================= if [[ ! -f "$path/fanart.jpg" ]]; then IMG=$(echo "$JSON" | jq -r '.artistbackground[0].url // empty') if ! download_if_valid "$IMG" "$path/fanart.jpg"; then IMG=$(deezer_artist_image "$name") download_if_valid "$IMG" "$path/fanart.jpg" fi fi # ================= logo.png ================= if [[ ! -f "$path/logo.png" ]]; then IMG=$(echo "$JSON" | jq -r '.hdmusiclogo[0].url // empty') download_if_valid "$IMG" "$path/logo.png" fi # ================= banner.jpg ================= if [[ ! -f "$path/banner.jpg" ]]; then IMG=$(echo "$JSON" | jq -r '.musicbanner[0].url // empty') download_if_valid "$IMG" "$path/banner.jpg" fi ) & done wait echo echo "===== COMPLETE =====" log "===== COMPLETE ====="