added lidar missing art checker

This commit is contained in:
2026-05-08 16:26:22 -04:00
parent 35707a4191
commit 7cecda6b90
3 changed files with 463 additions and 21 deletions
+364
View File
@@ -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 ====="
+98 -20
View File
@@ -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 wont trust them”
🔒 Why this works well with Tailscales 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.
+1 -1
View File
@@ -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,