From 031e595ff7689757d1b47317e07705ead1de8810 Mon Sep 17 00:00:00 2001 From: dschlueter Date: Wed, 29 Apr 2026 08:44:04 +0200 Subject: [PATCH] Add Discogs as cover source fallback after MusicBrainz MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _discogs_cover_url(): searches Discogs database/search API by artist+album, returns primary image URL; uses DISCOGS_TOKEN if set, else anonymous - download_discogs_cover(): downloads and saves as folder.jpg (PNG→JPEG via PIL) - resolve_cover() priority: local → MusicBrainz → Discogs - music_enricher: pass artist/album to resolve_cover() for Discogs lookup Co-Authored-By: Claude Sonnet 4.6 --- cover_handler.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++ music_enricher.py | 2 ++ 2 files changed, 64 insertions(+) diff --git a/cover_handler.py b/cover_handler.py index 1a10322..8ebdce7 100755 --- a/cover_handler.py +++ b/cover_handler.py @@ -221,10 +221,67 @@ def embed_cover(audio_path: Path, cover_path: Path) -> bool: return False +def _discogs_cover_url(artist: Optional[str], album: Optional[str]) -> Optional[str]: + """Sucht auf Discogs nach artist+album und gibt die primäre Image-URL zurück.""" + if not HAS_REQUESTS or not artist or not album: + return None + import os + token = os.getenv("DISCOGS_TOKEN", "") + headers = {"User-Agent": "MusicMetadataEnricher/1.0"} + if token: + headers["Authorization"] = f"Discogs token={token}" + try: + r = requests.get( + "https://api.discogs.com/database/search", + params={"artist": artist, "release_title": album, "type": "release", "per_page": 3}, + headers=headers, + timeout=10, + ) + if r.status_code != 200: + return None + results = r.json().get("results", []) + for result in results: + cover = result.get("cover_image") or result.get("thumb") + if cover and "spacer" not in cover: + return cover + except Exception as e: + print(f" ⚠️ Discogs-Suchfehler: {e}", file=sys.stderr) + return None + + +def download_discogs_cover(artist: Optional[str], album: Optional[str], dest_dir: Path) -> Optional[Path]: + url = _discogs_cover_url(artist, album) + if not url: + return None + dest = dest_dir / "folder.jpg" + try: + r = requests.get(url, timeout=15, headers={"User-Agent": "MusicMetadataEnricher/1.0"}) + if r.status_code != 200: + return None + ct = r.headers.get("content-type", "") + if ("png" in ct or url.lower().endswith(".png")) and HAS_PIL: + import io + with Image.open(io.BytesIO(r.content)) as img: + buf = io.BytesIO() + img.convert("RGB").save(buf, format="JPEG", quality=92) + dest.write_bytes(buf.getvalue()) + else: + dest.write_bytes(r.content) + if _image_ok(dest): + return dest + dest.unlink(missing_ok=True) + except Exception as e: + print(f" ⚠️ Discogs-Cover-Fehler: {e}", file=sys.stderr) + dest.unlink(missing_ok=True) + return None + + def resolve_cover( image_files: List[Path], release_mbid: Optional[str], album_dir: Path, + artist: Optional[str] = None, + album: Optional[str] = None, ) -> tuple[Optional[Path], Optional[str]]: """Returns (cover_path, source_label).""" local = find_local_cover(image_files) @@ -237,4 +294,9 @@ def resolve_cover( if downloaded: return downloaded, "musicbrainz" + if artist or album: + downloaded = download_discogs_cover(artist, album, album_dir) + if downloaded: + return downloaded, "discogs" + return None, None diff --git a/music_enricher.py b/music_enricher.py index 4b980b1..c99af9e 100755 --- a/music_enricher.py +++ b/music_enricher.py @@ -148,6 +148,8 @@ def process_album( hints.cover_images, proposal.mbid, album_dir, + artist=proposal.albumartist, + album=proposal.album, ) if cover_path and not args.no_cover: proposal.cover_path = cover_path