Upgrade artwork to highest available resolution for Spotify and iTunes

Spotify album art: replace the 4-char size segment after '0000ab67616d'
with '82c1' to request the original uploaded master (up to 2000px+).
Applied via _upgrade_spotify_image_url() in Track, Artist, and Album
dataclass constructors and as a catch-all in _download_cover_art.
Scoped to the ab67616d album art prefix only — artist images use a
different prefix (ab676161) where the trick does not apply.

iTunes/Apple Music: replace '100x100bb' with '3000x3000bb' in all
artworkUrl100 replacements across Track, Artist, Album, and the
get_album images arrays. Also applied as a catch-all in _download_cover_art.

Deezer already uses cover_xl at its maximum — no changes needed there.
pull/295/head
Broque Thomas 1 month ago
parent f851a67d6b
commit 20c8bff85c

@ -83,8 +83,8 @@ class Track:
# Extract album image (highest quality)
album_image_url = None
if 'artworkUrl100' in track_data:
# Replace 100x100 with 600x600 for higher quality
album_image_url = track_data['artworkUrl100'].replace('100x100bb', '600x600bb')
# Replace 100x100 with 3000x3000 for highest available quality
album_image_url = track_data['artworkUrl100'].replace('100x100bb', '3000x3000bb')
# Get artist name(s) - prefer clean name from ID lookup if available
if clean_artist_name:
@ -136,7 +136,7 @@ class Artist:
# iTunes artist search doesn't reliably return images
image_url = None
if 'artworkUrl100' in artist_data:
image_url = artist_data['artworkUrl100'].replace('100x100bb', '600x600bb')
image_url = artist_data['artworkUrl100'].replace('100x100bb', '3000x3000bb')
# Build external URLs
external_urls = {}
@ -173,8 +173,8 @@ class Album:
# Get highest quality artwork
image_url = None
if album_data.get('artworkUrl100'):
image_url = album_data['artworkUrl100'].replace('100x100bb', '600x600bb')
image_url = album_data['artworkUrl100'].replace('100x100bb', '3000x3000bb')
# Build external URLs
external_urls = {}
if 'collectionViewUrl' in album_data:
@ -633,12 +633,12 @@ class iTunesClient:
# Reconstruct Spotify-compatible format from cached raw iTunes data
image_url = None
if cached.get('artworkUrl100'):
image_url = cached['artworkUrl100'].replace('100x100bb', '600x600bb')
image_url = cached['artworkUrl100'].replace('100x100bb', '3000x3000bb')
images = []
if image_url:
images = [
{'url': image_url, 'height': 600, 'width': 600},
{'url': image_url, 'height': 3000, 'width': 3000},
{'url': cached['artworkUrl100'].replace('100x100bb', '300x300bb'), 'height': 300, 'width': 300},
{'url': cached['artworkUrl100'], 'height': 100, 'width': 100}
]
@ -681,13 +681,13 @@ class iTunesClient:
# Normalize to Spotify-compatible format
image_url = None
if album_data.get('artworkUrl100'):
image_url = album_data['artworkUrl100'].replace('100x100bb', '600x600bb')
image_url = album_data['artworkUrl100'].replace('100x100bb', '3000x3000bb')
# Build images array like Spotify (multiple sizes)
images = []
if image_url:
images = [
{'url': image_url, 'height': 600, 'width': 600},
{'url': image_url, 'height': 3000, 'width': 3000},
{'url': album_data['artworkUrl100'].replace('100x100bb', '300x300bb'), 'height': 300, 'width': 300},
{'url': album_data['artworkUrl100'], 'height': 100, 'width': 100}
]

@ -1,6 +1,7 @@
import spotipy
from spotipy.oauth2 import SpotifyOAuth, SpotifyClientCredentials
from typing import Dict, List, Optional, Any
import re
import time
import threading
from functools import wraps
@ -11,6 +12,21 @@ from core.metadata_cache import get_metadata_cache
logger = get_logger("spotify_client")
def _upgrade_spotify_image_url(url: str) -> str:
"""Upgrade a Spotify CDN image URL to the highest available resolution.
Album art URLs use the prefix 'ab67616d' and encode size as the 4-char hex
segment following '0000' (e.g. 'b273' = 640x640, '1e02' = 300x300).
Replacing it with '82c1' requests the original uploaded master (up to 2000px+).
This only applies to album art (ab67616d prefix). Artist images use a different
prefix (ab676161) and the 82c1 trick does not work for them those are left
as-is since Spotify already returns them sorted largest-first.
"""
if not url or 'i.scdn.co' not in url:
return url
return re.sub(r'(/image/ab67616d)0000[0-9a-f]{4}', r'\g<1>000082c1', url)
# Global rate limiting variables
_last_api_call_time = 0
_api_call_lock = threading.Lock()
@ -352,7 +368,7 @@ class Track:
if 'album' in track_data and 'images' in track_data['album']:
images = track_data['album']['images']
if images:
album_image_url = images[0]['url']
album_image_url = _upgrade_spotify_image_url(images[0]['url'])
return cls(
id=track_data['id'],
@ -384,7 +400,7 @@ class Artist:
# Get the largest image URL if available
image_url = None
if artist_data.get('images') and len(artist_data['images']) > 0:
image_url = artist_data['images'][0]['url']
image_url = _upgrade_spotify_image_url(artist_data['images'][0]['url'])
return cls(
id=artist_data['id'],
@ -413,7 +429,7 @@ class Album:
# Get the largest image URL if available
image_url = None
if album_data.get('images') and len(album_data['images']) > 0:
image_url = album_data['images'][0]['url']
image_url = _upgrade_spotify_image_url(album_data['images'][0]['url'])
return cls(
id=album_data['id'],

@ -19127,7 +19127,7 @@ def _download_cover_art(album_info: dict, target_dir: str, context: dict = None)
print(f"CAA upgrade failed — keeping existing cover.jpg")
return
# Fallback to Spotify/iTunes/Deezer URL (typically 640x640)
# Fallback to Spotify/iTunes/Deezer URL
if not image_data:
art_url = album_info.get('album_image_url')
# If album_info lacks the URL, try the context's spotify_album
@ -19141,6 +19141,13 @@ def _download_cover_art(album_info: dict, target_dir: str, context: dict = None)
art_url = images[0].get('url') if isinstance(images[0], dict) else None
if art_url:
print(f"Using cover art URL from spotify_album context")
# Upgrade to highest available resolution before fetching
if art_url and 'i.scdn.co' in art_url:
from core.spotify_client import _upgrade_spotify_image_url
art_url = _upgrade_spotify_image_url(art_url)
elif art_url and 'mzstatic.com' in art_url:
import re as _re
art_url = _re.sub(r'\d+x\d+bb', '3000x3000bb', art_url)
if not art_url:
print("No cover art URL available for download.")
return

Loading…
Cancel
Save