Sokhi (continued from #806): volume-numbered series ('B小町 …キャラクター ソングCD Vol.2' / 'Vol.2.5' / 'Vol.4' / 'Vol.4.5') got each other's art from both normal downloads and the retag tool. Two distinct holes, one principle: 1. The art picker's _album_matches validates by significant-token SUBSET — built to tolerate '(Deluxe)'/'- Remastered' suffixes. CJK strips out of the normalizer entirely, so Vol.4 → {b,tv,cd,vol,4}, a clean subset of Vol.4.5's {b,tv,cd,vol,4,5}: the wrong volume validated as "the same album with a suffix". Affected every fuzzy art source (iTunes, Deezer, AudioDB, Spotify) in downloads, retag, and the missing-art repair. 2. MusicBrainz match_release scores by string similarity — Vol.4 vs Vol.4.5 is 0.973, so the wrong volume could win the match outright, and its MBID then feeds Cover Art Archive with NO downstream validation (CAA is MBID-keyed, trusted by design). With Sokhi's MB metadata source this is the likely path in his logs (his release-group 404s push re-matching). The shared rule (core.text.title_match.numeric_tokens_differ): digit-bearing tokens must be IDENTICAL between the two titles. A number on one side only — volume, part, sequel, remaster year — is a different release, never a suffix. '1989' vs '1989 (Deluxe)' still matches (digits shared); 'Album' vs 'Album 2' now rejects (sequels!). Art picker rejects outright (falls through to next source / the download's own art — the designed cost of a false reject); MB matcher halves the candidate's confidence, landing it below the 70 gate while the exact-volume result is untouched. Tests: helper truth table, the exact reported pairs through _album_matches, and match_release end-to-end (wrong volume alone → no match beats a wrong MBID; exact volume beats near-identical wrong one despite lower MB score). 828 matching/metadata + 301 musicbrainz/retag/artwork tests pass.
65 lines
2.4 KiB
Python
65 lines
2.4 KiB
Python
"""Sokhi's wrong-cover-art report: 'Vol.4' must never match 'Vol.4.5'.
|
|
|
|
Two paths served wrong art for volume-numbered series:
|
|
- the art picker's _album_matches subset check (Vol.4's tokens are a subset
|
|
of Vol.4.5's once CJK/punctuation normalizes away) — covered in
|
|
tests/metadata/test_art_lookup.py
|
|
- MusicBrainz match_release: 0.97 string similarity let the wrong volume
|
|
win, and its MBID feeds CAA art with no downstream validation — covered
|
|
here, plus the shared helper itself.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
from core.text.title_match import numeric_tokens_differ
|
|
from core.musicbrainz_service import MusicBrainzService
|
|
|
|
VOL4 = "B小町 - TVアニメ「【推しの子】」キャラクターソングCD Vol.4"
|
|
VOL45 = "B小町 - TVアニメ「【推しの子】」キャラクターソングCD Vol.4.5"
|
|
|
|
|
|
def test_helper_volume_and_sequel_differ():
|
|
assert numeric_tokens_differ(VOL4, VOL45)
|
|
assert numeric_tokens_differ("Album", "Album 2")
|
|
assert numeric_tokens_differ("Now 99", "Now 100")
|
|
|
|
|
|
def test_helper_shared_or_no_digits_match():
|
|
assert not numeric_tokens_differ("1989", "1989 (Deluxe)")
|
|
assert not numeric_tokens_differ(VOL4, VOL4)
|
|
assert not numeric_tokens_differ("IGOR", "IGOR (Deluxe)")
|
|
assert not numeric_tokens_differ("", "")
|
|
|
|
|
|
def _service_with_results(results):
|
|
svc = MusicBrainzService.__new__(MusicBrainzService)
|
|
svc.mb_client = MagicMock()
|
|
svc.mb_client.search_release.return_value = results
|
|
svc._check_cache = lambda *a, **k: None
|
|
svc._save_to_cache = lambda *a, **k: None
|
|
return svc
|
|
|
|
|
|
def _result(title, score=100, mbid="mb-x"):
|
|
return {
|
|
'id': mbid, 'title': title, 'score': score,
|
|
'artist-credit': [{'artist': {'name': 'B小町'}}],
|
|
}
|
|
|
|
|
|
def test_match_release_rejects_wrong_volume():
|
|
"""Only the wrong volume is returned by search → no match at all is
|
|
better than caching Vol.4.5's MBID (CAA would serve its art unvalidated)."""
|
|
svc = _service_with_results([_result(VOL45, score=100, mbid='mb-wrong')])
|
|
assert svc.match_release(VOL4, 'B小町') is None
|
|
|
|
|
|
def test_match_release_prefers_exact_volume_over_near_identical():
|
|
svc = _service_with_results([
|
|
_result(VOL45, score=100, mbid='mb-wrong'),
|
|
_result(VOL4, score=90, mbid='mb-right'),
|
|
])
|
|
got = svc.match_release(VOL4, 'B小町')
|
|
assert got and got['mbid'] == 'mb-right'
|