Cover Art Filler: resolve the rep path before the disk-art check (flags-every-album)

The scan checked album_has_art_on_disk() on the RAW DB track path, while the
apply (_fix_missing_cover_art) resolves the path first. On any path-mapped
setup (docker mounts, a Plex/SoulSync path mismatch) the raw path isn't found,
disk art reads as "missing", and EVERY album gets flagged — then the apply
resolves the path, finds the art already there, and reports "already present".
Scan and apply disagreed purely because only the apply resolved paths.

Now the scan resolves the representative path the same way (resolve_library_
file_path, same transfer/download/config inputs the retag job uses) before
checking disk art. Unresolvable → treated as no-local-file (not claimed
disk-missing) so we never false-flag a file we simply can't reach.

Tests: disk check runs on the resolved path (thumb+art → not flagged);
unresolvable path → not flagged + art never checked on None.
This commit is contained in:
BoulderBadgeDad 2026-06-08 09:52:17 -07:00
parent 442ced3dbf
commit 8277385051
2 changed files with 62 additions and 1 deletions

View file

@ -4,6 +4,7 @@ import os
import re
from core.metadata.art_apply import album_has_art_on_disk
from core.library.path_resolver import resolve_library_file_path
from core.metadata_service import get_client_for_source, get_primary_source, get_source_priority
from core.repair_jobs import register_job
from core.repair_jobs.base import JobContext, JobResult, RepairJob
@ -137,6 +138,8 @@ class MissingCoverArtJob(RepairJob):
context.update_progress(0, total)
logger.info("Found %d albums missing cover art", total)
download_folder = (context.config_manager.get('soulseek.download_path', '')
if context.config_manager else None)
if context.report_progress:
context.report_progress(phase=f'Searching artwork for {total} albums...', total=total)
@ -160,7 +163,20 @@ class MissingCoverArtJob(RepairJob):
# Art can be missing in the DB (no thumb_url) and/or on disk (no
# embedded art and no cover.jpg). Skip albums that already have both.
db_missing = not (str(album_thumb).strip() if album_thumb else '')
disk_missing = bool(rep_path) and not album_has_art_on_disk(rep_path)
# Resolve the representative path the SAME way the apply does
# (_fix_missing_cover_art) before checking disk art. Checking the raw
# DB path would fail on any path-mapped setup (docker mounts, a
# Plex/SoulSync path mismatch) — the file isn't found, album art
# reads as "missing", and EVERY album gets flagged while the apply
# (which resolves) then finds the art already present. Unresolvable →
# treat as no-local-file (don't claim disk-missing).
resolved_rep = resolve_library_file_path(
rep_path,
transfer_folder=getattr(context, 'transfer_folder', None),
download_folder=download_folder,
config_manager=context.config_manager,
) if rep_path else None
disk_missing = bool(resolved_rep) and not album_has_art_on_disk(resolved_rep)
if not db_missing and not disk_missing:
result.skipped += 1
continue

View file

@ -281,3 +281,48 @@ def test_result_matches_unit():
# no artist on result → require exact title
assert m({'title': 'Album'}, 'Album', 'Artist')
assert not m({'title': 'Album Deluxe'}, 'Album', 'Artist')
# ── disk-art check must run on the RESOLVED path (flags-every-album bug) ──
def _add_track(conn, path):
conn.execute(
"INSERT INTO tracks (id, album_id, file_path, disc_number, track_number) "
"VALUES (1, 1, ?, 1, 1)", (path,))
conn.commit()
def test_scan_checks_disk_art_on_resolved_path(monkeypatch):
# Album already has a DB thumb (db not missing) and a track whose DB path
# only resolves via mapping. The disk-art check must run on the RESOLVED
# path — checking the raw path would fail on path-mapped setups and flag
# the whole library while the apply (which resolves) finds art present.
conn = _make_db((1, 'Album', 1, 'https://has/thumb', None, None, None, None, None))
_add_track(conn, '/plex/raw/song.flac')
context = _make_context(conn)
checked = {}
monkeypatch.setattr(mca, 'resolve_library_file_path',
lambda raw, **k: '/resolved/song.flac' if raw == '/plex/raw/song.flac' else None)
monkeypatch.setattr(mca, 'album_has_art_on_disk',
lambda p: checked.update(path=p) or True)
result = mca.MissingCoverArtJob().scan(context)
assert checked.get('path') == '/resolved/song.flac' # resolved, not raw
assert result.findings_created == 0 # thumb + disk art → not flagged
def test_scan_unresolvable_path_not_flagged_disk_missing(monkeypatch):
# An unreachable file (resolve → None) must NOT be claimed as "missing disk
# art" — we can't know, so don't false-flag. (Album has a thumb already.)
conn = _make_db((1, 'Album', 1, 'https://has/thumb', None, None, None, None, None))
_add_track(conn, '/gone/song.flac')
context = _make_context(conn)
monkeypatch.setattr(mca, 'resolve_library_file_path', lambda raw, **k: None)
called = []
monkeypatch.setattr(mca, 'album_has_art_on_disk', lambda p: called.append(p) or False)
result = mca.MissingCoverArtJob().scan(context)
assert result.findings_created == 0 # thumb present, disk unknown → not flagged
assert called == [] # never checked art on a None path