From 293490387498093b38ae5889154d82e04e718c21 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Tue, 23 Jun 2026 15:58:36 -0700 Subject: [PATCH] #916: align missing-track title match with the Reorganize matcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reporter's image 3 shows Reorganize maps all 62 multi-disc tracks correctly ('62 unchanged') — it matches by title, proving the titles DO align on this album. My first normalizer DELETED bracket content, so 'X - Main Theme' (file) vs 'X (Main Theme)' (canonical) would mismatch. Reorganize treats brackets as separators (keeps the words); now _normTitleForMatch does the same — drop only the (feat. Y) credit, turn every other separator into whitespace. Verified: dash<->bracket, curly<->straight apostrophe, special<->regular hyphen, and feat all normalize equal; distinct titles stay distinct. --- webui/static/library.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/webui/static/library.js b/webui/static/library.js index 502ea93e..ca9cfa35 100644 --- a/webui/static/library.js +++ b/webui/static/library.js @@ -3964,13 +3964,19 @@ async function ensureEnhancedAlbumCanonicalTracks(album) { } } -// Loose title key for owned<->canonical matching. Lowercase, drop bracketed -// annotations ((feat. X), [Explicit]) and punctuation so editions line up. +// Loose title key for owned<->canonical matching. Mirrors the Reorganize +// matcher (core.library_reorganize._normalize_title), which already maps these +// same multi-disc tracks correctly: drop only the featured-artist credit, then +// treat every other separator (brackets, dashes, slashes, punctuation) as +// whitespace — so "X (Main Theme)" and "X - Main Theme" collapse to the same key +// while "(feat. Y)" is removed. Keeping bracket CONTENT (not deleting it) is what +// makes editions line up. function _normTitleForMatch(value) { return String(value || '') .toLowerCase() - .replace(/\([^)]*\)|\[[^\]]*\]/g, ' ') - .replace(/[^a-z0-9]+/g, ' ') + .replace(/[([]\s*(?:feat|ft|featuring)\b[^)\]]*[)\]]/g, ' ') // (feat. Y) / [ft Y] + .replace(/\s+(?:feat|ft|featuring)\b\.?\s.*$/g, ' ') // trailing feat. Y … + .replace(/[^a-z0-9]+/g, ' ') // all other separators -> space (KEEP content) .trim(); }