From 7cecda6b90dfcf0f6e09b3742bf14eabdc064772 Mon Sep 17 00:00:00 2001 From: FailedProxy Date: Fri, 8 May 2026 16:26:22 -0400 Subject: [PATCH] added lidar missing art checker --- Media/lidarr_missing_art.sh | 364 ++++++++++++++++++++++++++++++++++++ Notes_To-Do.md | 118 ++++++++++-- master_host1.conf | 2 +- 3 files changed, 463 insertions(+), 21 deletions(-) create mode 100644 Media/lidarr_missing_art.sh diff --git a/Media/lidarr_missing_art.sh b/Media/lidarr_missing_art.sh new file mode 100644 index 0000000..0c7cd6a --- /dev/null +++ b/Media/lidarr_missing_art.sh @@ -0,0 +1,364 @@ +#!/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 =====" \ No newline at end of file diff --git a/Notes_To-Do.md b/Notes_To-Do.md index 03224ed..9760487 100644 --- a/Notes_To-Do.md +++ b/Notes_To-Do.md @@ -3,26 +3,10 @@ source ~/.bashrc claude - - -arrs system error, can we delete movies from radar when dropped from tmdb, 99% of the time its future movies that get dropped -movie Silent Hill 2: The Movie (tmdbid 466226) was removed from TMDb - - - - - -later. -. fix failover strike list timing -. verify silent toggle switches back on good notifications - -. add to partnership, on offboard, remove all of containers that belonged to rmote, example remotes vaultwarden-jayred from my machine and leave my vaultwarden-Gmer4Lfe alone. and it does nothing to remote, thier side will hadle thier pc and remove my stuff from thier pc. - -. add updater to update containers while daily runs, along with a toggle to dissable in master. - -. add a script to check all docker containers and update any that still need it to run after the containers that get synced and updated. - -. add a script to find missing artist and album cover from fanart and itunes, or itunes as a fallback +we broke host ip look up or something +❌ [ERROR] Failed to resolve Tailscale IP for unRAID-Jayred365 +❌ [ERROR] Check: tailscale status | grep unRAID-Jayred365 +in tailscale manage consel, it shows unRAID-Jayred365, but in tailscale plugin it shows as @@ -38,3 +22,97 @@ Run 'docker run --help' for more information The command failed. had to use --mount type=bind,source=/mnt/ram-transcode,target=/ext-ram-transcode instead, for now, just to get it back online + + + + + + + +arrs system error, can we delete movies from radar when dropped from tmdb, 99% of the time its future movies that get dropped +movie Silent Hill 2: The Movie (tmdbid 466226) was removed from TMDb. same logic for sonarr. + + + + + +fail overhand back needs to happen in stages and in reverse...... so if i hit tier 3, i want it to only stop the services in tier 3 then rsync, then hand abck, then tier 2, shutdown containers rsync, and handback, same for 1.... this will allow emby to stay up during possible long writebacks. and ensure all data issynced back before emby it handed back. + + + + +later. +. fix failover strike list timing, maybe 30 seconds. them a t 90 seconds 3 stike triggers. just gotta test buffer. never had the strike system +. verify silent toggle switches back on good notifications + +. add to partnership, on offboard, remove all of containers that belonged to remote, example remotes vaultwarden-jayred from my machine and leave my vaultwarden-Gmer4Lfe alone. and it does nothing to remote, thier side will hadle thier pc and remove my stuff from thier pc. now i use folders in docker, and have a folder i put my buddies failover containers in. dont know if we can utilize that. + +. add updater to update containers while daily runs, along with a toggle to dissable in master. + +. add a script to check all docker containers and update any that still need it to run after the containers that get synced and updated. + +. add a script to find missing artist and album cover from fanart and itunes, or itunes as a fallback + + + + + +tailscale seperated, now each user has to share thier machines to the other. make sure tailscale ssh in the tailscale setting is turned off, we will be ssh through tailscale. each user will need to run , +Separate “automation key” (cleanest design) +Keep your main key secure +Create a dedicated rsync key with no passphrase +ssh-keygen -t ed25519 -f /root/.ssh/*_rsync_automation -N "" , example = gmer4lfe_rsync_automation +Copy it to remote: +ssh-copy-id -i /root/.ssh/*_rsync_automation.pub root@100.97.4.47 +that sets up passwordless syncs + +we should look into a rsync setup script. then tailscale users already running tailscale. share each others servers then each runs the script. keys are made and the remote side is gets ssh'd and copied to remote. and it should update master with the new key +could start a new folder called Initial_run. and eventualy maybe even an orch script. or set it up in the partner dcript under onboard + + + + we need to delete those keys from both servers on offboard. like it gets triggered. remote key removed then removed from local then, and new key is made next time we onboard. + even if we use a file to trck keys. less perfered, but maybe a good fallback. all host keys that get added, maybe with a specific *_rsync_automation "tag" gets added to the list and then deleted on offboard, but that would only handle a 2 pc setup, unles we can link hosts to keys when descovered. then if 5 pcs are in a group and host 3 leaves, the script can look it up and remove it from the local pc. + +cant use tailscale api, so + +i think we need to just make a block list so user cant get back into the system +then they are blocked machine level untill we accually get to tailscale, worst case u see it when you join with someone else + +What your block list actually does (in real terms) +Your system becomes: +✔ On exit +revoke keys / session access locally +add identifier to blocklist +✔ On future access attempts (your scripts) +check blocklist before allowing or trusting anything +refuse automation actions tied to that identity +So it functions as: +“Even if they reappear on the network, my server won’t trust them” +🔒 Why this works well with Tailscale’s model +Tailscale already handles: +transport security +identity authentication +device connectivity +Your layer adds: +policy after connection exists +human-defined lifecycle rules +✔ Script layer manages lifecycle only when told +✔ Exit triggers: +revoke keys +block future re-entry +clean state locally +No auto-provisioning. No implicit trust expansion. + + + + +WAY LATER +when a user offboards and there is more than 1 server left, one needs to become the owner, we could do this in multiple ways or a combination, strongest server and BANDWIDTH,whos contributing more.... maybe we just promt and ask them + + +rsync, look into cross sync. newest timestamp wins, so if a show 1 updates on server 1, show 2 on server 2, show 3 on server3, when rsync runc, each machine would run this, and add remove based on time stamp, and have arrs pull after sync + + + +look into an overall, setup script, pull as many vars as possible without user having to add. like docker names, and so on. \ No newline at end of file diff --git a/master_host1.conf b/master_host1.conf index 3d1582e..c89cc8a 100644 --- a/master_host1.conf +++ b/master_host1.conf @@ -72,7 +72,7 @@ # SSH key used for all server-to-server operations — rsync, failover container commands. # Must be in /root/.ssh/ and authorised in HOST2's /root/.ssh/authorized_keys. - HOST1_SSH_KEY="/root/.ssh/Gmer4Lfe-rsync-key" + HOST1_SSH_KEY="root/.ssh/gmer4lfe_rsync_automation" # ━━━ Emby ━━━ # Referenced by transcode_manager.sh, emby_session_report.sh, emby_database_repair.sh,