fix(quality-scanner): resolve relative library paths before probing

Diagnostics revealed the real cause: the tracks table stores file_path
RELATIVE to the library root (e.g. "Asketa/Another Side/01-01 - Another
Side.flac"), so probing the raw path failed for the entire library — every
track came back unprobeable and was left unflagged ("20/20 could not be
probed").

The scanner now resolves each path via _resolve_library_file_path (checks
transfer/download/library dirs, same helper the rest of the app uses) before
probing, falling back to docker_resolve_path. Injected via deps for testability.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
dev 2026-06-14 23:30:46 +02:00
parent 9af31c3706
commit ff061324ba
3 changed files with 45 additions and 8 deletions

View file

@ -77,6 +77,10 @@ class QualityScannerDeps:
# so the scan checks against the SAME ranked-target core as the download
# quality guard — not just the file extension. Injected for testability.
probe_audio_quality: Callable = None
# Resolve a DB-stored (often RELATIVE, e.g. "Artist/Album/Track.flac") path
# to an absolute on-disk file by checking the transfer/download/library
# dirs. Without this the probe opens a relative path and fails.
resolve_library_file_path: Callable = None
def _extract_lookup_value(value: Any, *names: str, default: Any = None) -> Any:
@ -438,14 +442,21 @@ def run_quality_scanner(scope='watchlist', profile_id=1, deps: QualityScannerDep
# Probe the REAL audio quality (bit depth / sample rate / bitrate)
# and check it against the ranked targets — same core as the
# download guard. The library path may be a Windows/host path that
# needs Docker resolution before mutagen can open it.
resolved_path = file_path
try:
from core.imports.paths import docker_resolve_path
resolved_path = docker_resolve_path(file_path) if file_path else file_path
except Exception:
resolved_path = file_path
# download guard. The DB stores paths RELATIVE to the library root
# (e.g. "Artist/Album/Track.flac"), so resolve to an absolute
# on-disk path first or mutagen can't open the file.
resolved_path = None
if deps.resolve_library_file_path:
try:
resolved_path = deps.resolve_library_file_path(file_path)
except Exception:
resolved_path = None
if not resolved_path:
try:
from core.imports.paths import docker_resolve_path
resolved_path = docker_resolve_path(file_path) if file_path else file_path
except Exception:
resolved_path = file_path
aq = deps.probe_audio_quality(resolved_path) if deps.probe_audio_quality else None
if aq is not None:

View file

@ -175,6 +175,7 @@ def _build_deps(
get_quality_tier_from_extension=lambda fp: quality_tier_result,
add_activity_item=lambda *a, **kw: None,
probe_audio_quality=probe_fn or _default_probe,
resolve_library_file_path=lambda fp: fp, # identity — tests use direct paths
)
return deps
@ -263,6 +264,30 @@ def test_high_quality_tracks_skipped(mock_db_and_wishlist):
assert state['low_quality'] == 0
def test_scanner_resolves_relative_path_before_probing(mock_db_and_wishlist):
"""DB stores RELATIVE paths (Artist/Album/Track.flac); the scanner must
resolve them to absolute via resolve_library_file_path before probing,
otherwise mutagen can't open the file and the whole library passes."""
from core.quality.model import AudioQuality
db, _ = mock_db_and_wishlist
db._watchlist_artists = [_WatchlistArtist('A')]
db._tracks = [_track_row(file_path='Artist/Album/Track.flac')]
state = {}
probed = []
def _probe(fp):
probed.append(fp)
return AudioQuality('flac', bit_depth=16, sample_rate=44100)
deps = _build_deps(state=state, probe_fn=_probe)
deps.resolve_library_file_path = lambda fp: '/music/' + fp
qs.run_quality_scanner('watchlist', 1, deps)
assert probed == ['/music/Artist/Album/Track.flac']
def test_low_quality_tracks_attempted(mock_db_and_wishlist):
"""Low-quality tracks (tier_num > min) trigger a metadata search."""
db, _ = mock_db_and_wishlist

View file

@ -17662,6 +17662,7 @@ def _build_quality_scanner_deps():
get_quality_tier_from_extension=_get_quality_tier_from_extension,
add_activity_item=add_activity_item,
probe_audio_quality=_probe_audio_quality,
resolve_library_file_path=_resolve_library_file_path,
)