From d9e2e129b0fee34e0cd44e7462fa529a7dcd8dba Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Thu, 9 Apr 2026 18:46:49 -0700 Subject: [PATCH] Allow selecting which duplicate to keep in duplicate detector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Duplicate finding detail now shows each version as clickable — user can choose which to keep instead of relying on auto-selection. Added track_number as tiebreaker in auto-pick (higher track number wins over 01, catching leftover duplicates from the playlist sync track number bug). Track number displayed in the detail view for clarity. --- core/repair_worker.py | 17 +++++++++++++---- webui/static/script.js | 39 +++++++++++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/core/repair_worker.py b/core/repair_worker.py index 0d2fc573..2b75f763 100644 --- a/core/repair_worker.py +++ b/core/repair_worker.py @@ -1174,14 +1174,23 @@ class RepairWorker: conn.close() def _fix_duplicates(self, entity_type, entity_id, file_path, details): - """Keep the best quality duplicate and remove the rest from the database.""" + """Keep the selected or best quality duplicate and remove the rest from the database.""" tracks = details.get('tracks', []) if len(tracks) < 2: return {'success': False, 'error': 'Not enough duplicate info to determine best copy'} - # Pick best: highest bitrate, then longest duration - best = max(tracks, key=lambda t: (t.get('bitrate', 0) or 0, t.get('duration', 0) or 0)) - best_id = best.get('track_id') or best.get('id') + # If user specified which track to keep, use that + keep_id = details.get('_fix_action') + if keep_id: + best = next((t for t in tracks if str(t.get('track_id') or t.get('id')) == str(keep_id)), None) + if not best: + return {'success': False, 'error': f'Selected track ID {keep_id} not found in duplicates'} + best_id = keep_id + else: + # Auto-pick: highest bitrate, then longest duration, then highest track number (correct > 01) + best = max(tracks, key=lambda t: (t.get('bitrate', 0) or 0, t.get('duration', 0) or 0, t.get('track_number', 0) or 0)) + best_id = best.get('track_id') or best.get('id') + if not best_id: return {'success': False, 'error': 'Could not determine best track ID'} diff --git a/webui/static/script.js b/webui/static/script.js index c016253d..cf3a08b9 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -62757,23 +62757,27 @@ function _renderFindingDetail(f) { case 'duplicate_tracks': if (!d.tracks || !d.tracks.length) return _gridRows([['Count', d.count || '?']]); - // Determine best copy (same logic as backend: highest bitrate, then duration) + // Determine best copy (same logic as backend: highest bitrate, then duration, then track number) const bestDup = d.tracks.reduce((best, t) => { const bBr = best.bitrate || 0, tBr = t.bitrate || 0; const bDur = best.duration || 0, tDur = t.duration || 0; - return (tBr > bBr || (tBr === bBr && tDur > bDur)) ? t : best; + const bTn = best.track_number || 0, tTn = t.track_number || 0; + return (tBr > bBr || (tBr === bBr && tDur > bDur) || (tBr === bBr && tDur === bDur && tTn > bTn)) ? t : best; }, d.tracks[0]); + const findingId = f.id; return media + `