wolf's report: Spotify shows 'Calma - Remix', Find & Add searches that literal string, but the library stores the track as just 'Calma' (only the 3:58 duration marks it the remix). The literal LIKE '%calma - remix%' misses, so it fell to the OR-fuzzy fallback which floods on the common word 'remix' (20 unrelated '... remix' hits). Dropping '- Remix' (searching 'Calma') finds it instantly. Fix: search_tracks (and api_search_tracks) now retry on the BASE title — the part before Spotify's ' - ' version separator — BEFORE the OR-fuzzy flood. So 'Calma - Remix' resolves to 'Calma' (or 'Calma (Remix)') and the noise fallback is never reached when the base matches. New core.text.title_match.base_title_before_dash (splits the first spaced ' - '; leaves bare hyphens like 'Up-Tight' alone). Tests: pure helper (3) + real-DB integration reproducing the Calma case, the parenthesized-remix variant, plain-title-unaffected, and no-flood (4). 64 search/match tests green.
88 lines
3.6 KiB
Python
88 lines
3.6 KiB
Python
"""Find & Add: Spotify "Title - Qualifier" must find the base-titled library track.
|
|
|
|
wolf's report: Spotify shows "Calma - Remix", Find & Add searches that literal
|
|
string, the library stores the track as just "Calma" (only the duration marks it
|
|
as the remix) → the literal search misses and the OR-fuzzy fallback floods 20
|
|
unrelated "... remix" hits. Dropping "- Remix" (searching "Calma") finds it.
|
|
|
|
Fix: search_tracks retries on the base title (before Spotify's " - " separator)
|
|
before the OR-fuzzy flood.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from core.text.title_match import base_title_before_dash
|
|
from database.music_database import MusicDatabase
|
|
|
|
|
|
# --- pure helper -----------------------------------------------------------
|
|
|
|
def test_base_title_before_dash_strips_spotify_version_suffix():
|
|
assert base_title_before_dash('Calma - Remix') == 'Calma'
|
|
assert base_title_before_dash('Closer - Radio Edit') == 'Closer'
|
|
assert base_title_before_dash('Crocodile Rock - Remastered 2014') == 'Crocodile Rock'
|
|
|
|
|
|
def test_base_title_before_dash_leaves_plain_titles_alone():
|
|
assert base_title_before_dash('Tom Sawyer') == 'Tom Sawyer'
|
|
assert base_title_before_dash('21st Century Schizoid Man') == '21st Century Schizoid Man'
|
|
assert base_title_before_dash('Up-Tight') == 'Up-Tight' # bare hyphen, not a separator
|
|
assert base_title_before_dash('') == ''
|
|
|
|
|
|
def test_base_title_before_dash_splits_first_separator_only():
|
|
assert base_title_before_dash('A - B - C') == 'A'
|
|
|
|
|
|
# --- integration: the real search path -------------------------------------
|
|
|
|
@pytest.fixture
|
|
def db(tmp_path):
|
|
return MusicDatabase(str(tmp_path / "music.db"))
|
|
|
|
|
|
def _insert(db, tid, title, artist_id, artist_name):
|
|
with db._get_connection() as conn:
|
|
conn.execute("INSERT OR IGNORE INTO artists (id, name) VALUES (?, ?)", (artist_id, artist_name))
|
|
conn.execute("INSERT OR IGNORE INTO albums (id, title, artist_id) VALUES (?, ?, ?)",
|
|
(artist_id, "Alb", artist_id))
|
|
conn.execute(
|
|
"INSERT INTO tracks (id, album_id, artist_id, title, track_number, duration, file_path) "
|
|
"VALUES (?, ?, ?, ?, 1, 238, ?)",
|
|
(tid, artist_id, artist_id, title, f"/m/{tid}.flac"),
|
|
)
|
|
conn.commit()
|
|
|
|
|
|
def test_spotify_dash_remix_finds_base_titled_track(db):
|
|
# Library stores the remix as just "Calma" (the wolf case).
|
|
_insert(db, 1, "Calma", 1, "Pedro Capó")
|
|
results = db.search_tracks(title="Calma - Remix", rank_artist="Pedro Capó")
|
|
titles = [t.title for t in results]
|
|
assert "Calma" in titles, "base-title fallback should find 'Calma' for 'Calma - Remix'"
|
|
|
|
|
|
def test_spotify_dash_remix_finds_parenthesized_remix(db):
|
|
# …and still matches when the library DID label it "(Remix)".
|
|
_insert(db, 1, "Calma (Remix)", 1, "Pedro Capó")
|
|
results = db.search_tracks(title="Calma - Remix", rank_artist="Pedro Capó")
|
|
assert any("Calma" in t.title for t in results)
|
|
|
|
|
|
def test_plain_title_unaffected_uses_basic_search(db):
|
|
_insert(db, 1, "Tom Sawyer", 1, "Rush")
|
|
results = db.search_tracks(title="Tom Sawyer")
|
|
assert [t.title for t in results] == ["Tom Sawyer"]
|
|
|
|
|
|
def test_dash_query_does_not_flood_when_base_matches(db):
|
|
# The base-title retry must short-circuit BEFORE the OR-fuzzy flood, so an
|
|
# unrelated "... Remix" track doesn't drown the real one.
|
|
_insert(db, 1, "Calma", 1, "Pedro Capó")
|
|
_insert(db, 2, "Some Other Song (KAIZ Remix)", 2, "Someone Else")
|
|
results = db.search_tracks(title="Calma - Remix", rank_artist="Pedro Capó")
|
|
titles = [t.title for t in results]
|
|
assert "Calma" in titles
|
|
assert "Some Other Song (KAIZ Remix)" not in titles
|