From 79c907cfcbc41ceb6b911b67541a5bca05fb7a52 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Thu, 4 Jun 2026 14:50:40 -0700 Subject: [PATCH] Cover Art Filler: honor the configured cover-art sources too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consistency follow-up: the Filler picked art via metadata source-priority + its own prefer_source knob, ignoring metadata_enhancement.album_art_order — the explicit cover-art source list that post-process embed and the Library Re-tag job now use. So 'cover art sources' meant two different things. Prefer select_preferred_art_url (the configured order) first; fall back to the existing prefer_source / source-priority loop when no order is configured (the default) — non-breaking, existing behavior unchanged for those users. Help text updated. Test: configured order wins + skips the source loop. --- core/repair_jobs/missing_cover_art.py | 35 +++++++++++++++++++++------ tests/test_missing_cover_art.py | 31 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/core/repair_jobs/missing_cover_art.py b/core/repair_jobs/missing_cover_art.py index e6690981..c4ae85b9 100644 --- a/core/repair_jobs/missing_cover_art.py +++ b/core/repair_jobs/missing_cover_art.py @@ -46,9 +46,10 @@ class MissingCoverArtJob(RepairJob): description = 'Finds albums missing artwork and locates art from metadata sources' help_text = ( 'Scans your library for albums that have no cover art stored in the database. ' - 'For each missing cover, it searches the configured metadata sources using the ' - 'album name and artist to find matching artwork. If Prefer Source is set, that ' - 'source is tried first; otherwise the primary metadata source is used.\n\n' + 'For each missing cover, it searches for matching artwork using the album name ' + 'and artist. If you have configured cover-art sources (Settings > metadata ' + 'enhancement art order), those are used first; otherwise it falls back to ' + 'Prefer Source (if set) or the primary metadata source.\n\n' 'When artwork is found, a finding is created with the image URL so you can review ' 'and apply it. The job does not download or embed artwork automatically.\n\n' 'Settings:\n' @@ -66,6 +67,10 @@ class MissingCoverArtJob(RepairJob): settings = self._get_settings(context) primary_source = get_primary_source() source_priority = get_source_priority(primary_source) + # User's configured cover-art source order (caa/deezer/itunes/spotify/ + # audiodb). Empty by default → falls back to the source-priority loop. + art_order = (context.config_manager.get('metadata_enhancement.album_art_order') + if context.config_manager else None) prefer_source = settings.get('prefer_source') if prefer_source and prefer_source != primary_source and prefer_source in source_priority: source_priority.remove(prefer_source) @@ -170,11 +175,27 @@ class MissingCoverArtJob(RepairJob): artwork_url = None + # Honor the user's configured cover-art sources first (the same + # metadata_enhancement.album_art_order the post-process embed and the + # Library Re-tag job use) so "cover art sources" means one thing + # app-wide. Non-breaking: returns None when no order is configured + # (the default), so we fall back to the prefer_source / metadata + # source-priority loop below — unchanged behavior for existing users. + try: + from core.metadata.art_lookup import select_preferred_art_url + artwork_url = select_preferred_art_url( + artist_name, title, + {'musicbrainz_release_id': None}, art_order, + ) + except Exception as e: + logger.debug("preferred cover-art lookup failed for album %s: %s", album_id, e) + # Try source-specific IDs first, then title/artist search, in priority order. - for source in source_priority: - artwork_url = self._try_source(source, source_album_ids.get(source), title, artist_name) - if artwork_url: - break + if not artwork_url: + for source in source_priority: + artwork_url = self._try_source(source, source_album_ids.get(source), title, artist_name) + if artwork_url: + break if artwork_url: if context.report_progress: diff --git a/tests/test_missing_cover_art.py b/tests/test_missing_cover_art.py index 6c21fe93..2994ad33 100644 --- a/tests/test_missing_cover_art.py +++ b/tests/test_missing_cover_art.py @@ -162,6 +162,37 @@ def test_missing_cover_art_prefers_explicit_source_over_primary(monkeypatch): assert context.findings[0]['details']['found_artwork_url'] == 'https://img/spotify-direct' +def test_missing_cover_art_uses_configured_art_sources(monkeypatch): + """When cover-art sources are configured (album_art_order), the Filler pulls + art from them and skips the metadata source-priority loop — same 'cover art + sources' notion the Re-tag job and post-process embed honor.""" + conn = _make_db((1, 'Album', 1, '', 'sp-album', 'it-album', 'dz-album', 'dg-album', 'hy-album')) + settings = { + 'repair.jobs.missing_cover_art.settings': {}, + 'metadata_enhancement.album_art_order': ['itunes', 'deezer'], + } + findings = [] + context = SimpleNamespace( + db=SimpleNamespace(_get_connection=lambda: conn), + config_manager=SimpleNamespace(get=lambda key, default=None: settings.get(key, default)), + check_stop=lambda: False, wait_if_paused=lambda: False, + update_progress=lambda *a, **k: None, report_progress=lambda *a, **k: None, + create_finding=lambda **kw: (findings.append(kw) or True), + findings=findings, + ) + monkeypatch.setattr(mca, 'get_primary_source', lambda: 'spotify') + consulted = [] + monkeypatch.setattr(mca, 'get_client_for_source', lambda s: consulted.append(s) or _FakeClient()) + monkeypatch.setattr('core.metadata.art_lookup.select_preferred_art_url', + lambda artist, album, meta, order, **k: 'https://configured/art.jpg') + + result = mca.MissingCoverArtJob().scan(context) + + assert result.findings_created == 1 + assert findings[0]['details']['found_artwork_url'] == 'https://configured/art.jpg' + assert consulted == [] # source-priority loop skipped when configured art wins + + def test_missing_cover_art_uses_primary_when_prefer_unset(monkeypatch): conn = _make_db((1, 'Album', 1, '', None, None, None, None, None)) context = _make_context(conn)