diff --git a/core/repair_jobs/orphan_file_detector.py b/core/repair_jobs/orphan_file_detector.py index 5f2de742..6a9e1559 100644 --- a/core/repair_jobs/orphan_file_detector.py +++ b/core/repair_jobs/orphan_file_detector.py @@ -46,6 +46,7 @@ class OrphanFileDetectorJob(RepairJob): # of depth 1-3 (filename, album/filename, artist/album/filename) which # covers all realistic path-prefix mismatches. known_suffixes = set() + known_titles = set() # (title_lower, artist_lower) for tag-based fallback conn = None try: conn = context.db._get_connection() @@ -57,6 +58,18 @@ class OrphanFileDetectorJob(RepairJob): for depth in range(1, min(4, len(parts) + 1)): suffix = '/'.join(parts[-depth:]).lower() known_suffixes.add(suffix) + + # Build title+artist set for tag-based fallback matching + cursor.execute(""" + SELECT t.title, ar.name FROM tracks t + LEFT JOIN artists ar ON ar.id = t.artist_id + WHERE t.title IS NOT NULL AND t.title != '' + """) + for row in cursor.fetchall(): + title = (row[0] or '').lower().strip() + artist = (row[1] or '').lower().strip() + if title: + known_titles.add((title, artist)) except Exception as e: logger.error("Error reading known file paths from DB: %s", e, exc_info=True) result.errors += 1 @@ -108,6 +121,20 @@ class OrphanFileDetectorJob(RepairJob): is_known = True break + # Fallback: read file tags and check if title+artist exists in DB + # Catches path mismatches where the file is tracked but under a different path + if not is_known and known_titles: + try: + from mutagen import File as MutagenFile + audio = MutagenFile(fpath, easy=True) + if audio: + file_title = ((audio.get('title') or [None])[0] or '').lower().strip() + file_artist = ((audio.get('artist') or [None])[0] or '').lower().strip() + if file_title and (file_title, file_artist) in known_titles: + is_known = True + except Exception: + pass + if not is_known: orphan_files.append(fpath)