From a803e8399c035d549f3072eeb4328de3b360a792 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Sun, 29 Mar 2026 20:40:27 -0700 Subject: [PATCH] Add Cover Art Archive as high-resolution album art source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Album art embedding now tries Cover Art Archive first (using the MusicBrainz release ID from source ID embedding) before falling back to Spotify/iTunes/Deezer URLs. CAA provides original-quality artwork, often 1200x1200 or higher vs Spotify's 640x640 max. Reordered _embed_source_ids to run before _embed_album_art_metadata so the MusicBrainz release ID is available for the CAA lookup. Also fixed hardcoded 640x640 FLAC picture dimensions — now detects actual size from image bytes. Falls back to existing behavior if CAA fails or no release ID exists. --- web_server.py | 92 +++++++++++++++++++++++++++++++++++------- webui/static/helper.js | 1 + 2 files changed, 79 insertions(+), 14 deletions(-) diff --git a/web_server.py b/web_server.py index e2616d50..a6163a3e 100644 --- a/web_server.py +++ b/web_server.py @@ -15786,13 +15786,15 @@ def _enhance_file_metadata(file_path: str, context: dict, artist: dict, album_in if metadata.get('disc_number'): audio_file['disk'] = [(metadata['disc_number'], 0)] + # ── Embed source IDs (Spotify, MusicBrainz, etc.) on the same object ── + # Runs before album art so MusicBrainz release ID is available for + # Cover Art Archive high-resolution lookup. + _embed_source_ids(audio_file, metadata) + # ── Embed album art on the same object ── if config_manager.get('metadata_enhancement.embed_album_art', True): _embed_album_art_metadata(audio_file, metadata) - # ── Embed source IDs (Spotify, MusicBrainz, etc.) on the same object ── - _embed_source_ids(audio_file, metadata) - # ── Embed audio quality tag ── quality = context.get('_audio_quality', '') if quality and config_manager.get('metadata_enhancement.tags.quality_tag', True) is not False: @@ -16014,17 +16016,64 @@ def _extract_spotify_metadata(context: dict, artist: dict, album_info: dict) -> return metadata -def _embed_album_art_metadata(audio_file, metadata: dict): - """Downloads and embeds high-quality Spotify album art into the file.""" +def _get_image_dimensions(data: bytes): + """Extract width/height from JPEG or PNG image data without PIL.""" try: - art_url = metadata.get('album_art_url') - if not art_url: - print("🎨 No album art URL available for embedding.") - return + if data[:8] == b'\x89PNG\r\n\x1a\n': + # PNG: width and height at bytes 16-23 + import struct + w, h = struct.unpack('>II', data[16:24]) + return w, h + if data[:2] == b'\xff\xd8': + # JPEG: scan for SOF0/SOF2 marker + import struct + i = 2 + while i < len(data) - 9: + if data[i] != 0xFF: + break + marker = data[i + 1] + if marker in (0xC0, 0xC2): + h, w = struct.unpack('>HH', data[i + 5:i + 9]) + return w, h + length = struct.unpack('>H', data[i + 2:i + 4])[0] + i += 2 + length + except Exception: + pass + return None, None - with urllib.request.urlopen(art_url, timeout=10) as response: - image_data = response.read() - mime_type = response.info().get_content_type() + +def _embed_album_art_metadata(audio_file, metadata: dict): + """Downloads and embeds album art — tries Cover Art Archive (full resolution) + first if MusicBrainz release ID is available, falls back to Spotify/iTunes URL.""" + try: + image_data = None + mime_type = None + + # Try Cover Art Archive first (often 1200x1200+, original quality) + release_mbid = metadata.get('musicbrainz_release_id') + if release_mbid: + try: + caa_url = f"https://coverartarchive.org/release/{release_mbid}/front" + req = urllib.request.Request(caa_url, headers={'Accept': 'image/*'}) + with urllib.request.urlopen(req, timeout=10) as response: + image_data = response.read() + mime_type = response.info().get_content_type() or 'image/jpeg' + if image_data and len(image_data) > 1000: + print(f"🎨 Cover art from Cover Art Archive ({len(image_data) // 1024}KB)") + else: + image_data = None # Too small, likely an error page + except Exception: + image_data = None # Fall through to Spotify/iTunes URL + + # Fallback to Spotify/iTunes/Deezer URL (typically 640x640) + if not image_data: + art_url = metadata.get('album_art_url') + if not art_url: + print("🎨 No album art URL available for embedding.") + return + with urllib.request.urlopen(art_url, timeout=10) as response: + image_data = response.read() + mime_type = response.info().get_content_type() if not image_data: print("❌ Failed to download album art data.") @@ -16039,8 +16088,10 @@ def _embed_album_art_metadata(audio_file, metadata: dict): picture.data = image_data picture.type = 3 picture.mime = mime_type - picture.width = 640 - picture.height = 640 + # Detect actual dimensions from image data + _img_w, _img_h = _get_image_dimensions(image_data) + picture.width = _img_w or 640 + picture.height = _img_h or 640 picture.depth = 24 audio_file.add_picture(picture) # MP4/M4A @@ -16209,6 +16260,10 @@ def _embed_source_ids(audio_file, metadata: dict): if yr.isdigit(): release_year = yr + # Store release MBID in metadata for downstream use (e.g. Cover Art Archive) + if _rc_mbid: + metadata['musicbrainz_release_id'] = _rc_mbid + # Write release year to file tags if not already present if release_year and 'ORIGINALDATE' not in id_tags: id_tags['ORIGINALDATE'] = release_year @@ -19009,6 +19064,15 @@ def get_version_info(): "title": "What's New in SoulSync", "subtitle": f"Version {SOULSYNC_VERSION} — Latest Changes", "sections": [ + { + "title": "🎨 High-Resolution Cover Art from Cover Art Archive", + "description": "Album art now sourced from Cover Art Archive when available — often 1200x1200+ original quality", + "features": [ + "• Tries Cover Art Archive first using MusicBrainz release ID (full resolution)", + "• Falls back to Spotify/iTunes/Deezer URL (640x640) if CAA unavailable", + "• Source ID embedding now runs before art embedding to make release ID available" + ] + }, { "title": "🎵 Embedded Lyrics in Audio Files", "description": "Lyrics are now embedded directly in audio file tags alongside the .lrc sidecar file", diff --git a/webui/static/helper.js b/webui/static/helper.js index c1f073da..cb153ff5 100644 --- a/webui/static/helper.js +++ b/webui/static/helper.js @@ -3403,6 +3403,7 @@ function closeHelperSearch() { const WHATS_NEW = { '2.1': [ // Newest features first + { title: 'High-Res Cover Art', desc: 'Album art now sourced from Cover Art Archive (1200x1200+) when MusicBrainz release ID is available' }, { title: 'Embedded Lyrics', desc: 'Lyrics now embedded directly in audio file tags — Navidrome, Jellyfin, and Plex can display them' }, { title: 'Fix AcoustID False Positives', desc: 'High-confidence fingerprints no longer quarantine correct files when titles are in different languages' }, { title: 'Fix Junk Tags Surviving', desc: 'Soulseek source tags are now wiped to disk immediately — no more album fragmentation from partial metadata' },