From 89f843d223635907894cfa45a16723721f616cbe Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Thu, 11 Jun 2026 10:46:36 -0700 Subject: [PATCH] #851: normalize '/' ':' '_' to spaces so slash-titles match underscore sources MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The candidate matcher rejected valid downloads of titles with a '/' or ':' (e.g. Sawano's "You See Big Girl / T:T") because the unified normalize() removed those chars ("t:t" -> "tt") while keeping the '_' that source filenames substitute for them ("T_T" -> "t_t") — the asymmetry tanked the similarity score. Now '/ \ : _' all map to spaces before the strip, so "/ T:T" and "_ T_T" both normalize to "t t". Verified on the real library: similarity for the Sawano pair 0.927 -> 1.000; only 348/40786 strings (0.85%, all containing those separators) change; worst-case joined-variant match (e.g. "12:05" vs "1205") stays 0.889, well above the 0.70 title threshold — no match regressions. Fixes the matching half of #851. --- core/matching/audio_verification.py | 5 +++++ tests/matching/test_normalize_slash_851.py | 24 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 tests/matching/test_normalize_slash_851.py diff --git a/core/matching/audio_verification.py b/core/matching/audio_verification.py index 06b90949..a347ce73 100644 --- a/core/matching/audio_verification.py +++ b/core/matching/audio_verification.py @@ -67,6 +67,11 @@ def normalize(text: str) -> str: '', s, flags=re.IGNORECASE, ) s = re.sub(r'\s*-\s*from\s+.+$', '', s, flags=re.IGNORECASE) + # Path/separator punctuation -> space so a title keeps matching a source + # filename that substituted '_' for an illegal '/' or ':' (#851): the on-disk + # "You See Big Girl _ T_T" must normalize the same as "You See Big Girl / T:T". + # Done before the strip below so they become word boundaries, not joins. + s = re.sub(r'[\\/:_]+', ' ', s) # Drop remaining punctuation but keep word chars (incl. CJK) + spaces. s = re.sub(r'[^\w\s]', '', s) s = re.sub(r'\s+', ' ', s).strip() diff --git a/tests/matching/test_normalize_slash_851.py b/tests/matching/test_normalize_slash_851.py new file mode 100644 index 00000000..9b4b3c8c --- /dev/null +++ b/tests/matching/test_normalize_slash_851.py @@ -0,0 +1,24 @@ +"""#851: a '/' or ':' in a title must normalize the same as a source filename +that substituted '_' for them, so the candidate matcher stops rejecting valid +downloads (e.g. Sawano's "You See Big Girl / T:T" vs on-disk "..._ T_T").""" + +from __future__ import annotations + +from core.matching.audio_verification import normalize, similarity + + +def test_slash_colon_title_matches_underscore_source(): + assert normalize("You See Big Girl / T:T") == normalize("You See Big Girl _ T_T") + assert similarity("You See Big Girl / T:T", "You See Big Girl _ T_T") == 1.0 + + +def test_separators_become_word_boundaries(): + assert normalize("Re:Zero") == "re zero" + assert normalize("AC/DC") == "ac dc" + assert normalize("T_T") == "t t" + + +def test_joined_variant_stays_above_title_threshold(): + # Spacing a separator must not drop a joined-variant match below 0.70. + assert similarity("AC/DC", "ACDC") >= 0.70 + assert similarity("12:05", "1205") >= 0.70