From dadc489b08a7d714e9531e84c5bb44303892b8f0 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Thu, 19 Mar 2026 22:01:27 -0700 Subject: [PATCH] Add tag-based fallback to orphan detector to prevent false positives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When suffix path matching fails, reads file tags (title+artist) and checks against DB tracks. Prevents false orphan detection from path mismatches in Docker where DB paths differ from filesystem paths. Only runs for files that fail suffix matching — zero overhead in normal cases. --- core/repair_jobs/orphan_file_detector.py | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) 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)