fix(music): preserve symbol-only title matching

This commit is contained in:
jxxghp
2026-09-17 11:38:30 +08:00
parent 5f1931df48
commit 97d232af78
2 changed files with 51 additions and 6 deletions

View File

@@ -134,11 +134,23 @@ def music_base_title(value: Optional[str], *, preserve_editions: bool = False) -
return _BARE_VERSION_SUFFIX.sub(strip_version, _VERSION_SUFFIX.sub(strip_version, text)).strip()
def _music_title_key(value: Optional[str], *, preserve_editions: bool = False) -> str:
"""生成音乐标题比较键,纯符号名称在普通归一化为空时保留其符号身份。"""
base = music_base_title(
normalize("NFKC", str(value or "")),
preserve_editions=preserve_editions,
)
key = music_text_key(base)
if key:
return key
return re.sub(r"\s+", "", base).casefold()
def music_title_matches(music: MusicInfo, title: Optional[str], *, preserve_editions: bool = False) -> bool:
"""统一全半角后比较完整名称及别名,允许数据源保持既有发行版本边界。"""
expected = music_text_key(music_base_title(normalize("NFKC", str(title or "")), preserve_editions=preserve_editions))
"""统一全半角后比较完整名称及别名,纯符号名称也保留其身份。"""
expected = _music_title_key(title, preserve_editions=preserve_editions)
return bool(expected and any(
expected == music_text_key(music_base_title(normalize("NFKC", name), preserve_editions=preserve_editions))
expected == _music_title_key(name, preserve_editions=preserve_editions)
for name in music_titles(music)
))
@@ -229,7 +241,7 @@ def _resource_names(primary: MetaMusic, artists: list[str], *, album: bool = Fal
variants.append(parts[0])
for part in variants:
name = music_base_title(_TITLE_LABEL.sub("", part.strip()))
key = music_text_key(name)
key = _music_title_key(name)
if not key:
continue
names.append(key)
@@ -323,7 +335,7 @@ def match_music_resource(
else any(_contains_artist(content, artist) for artist in artists)
if not title_matched:
if music.music_type != MUSIC_ENTITY_ALBUM and artist_matched and any(
music_text_key(music_base_title(item)) in names
_music_title_key(item) in names
for item in music_titles(music, album=True)
):
return MusicMatch("album", "related_album")
@@ -338,7 +350,7 @@ def match_music_resource(
# 所属专辑只说明单曲归属,不能代替资源主标题证明整专范围。
primary_names = _resource_names(resource, artists)
if resource.track_number or (resource.album and not any(
music_text_key(music_base_title(item)) in primary_names for item in titles
_music_title_key(item) in primary_names for item in titles
)):
return MusicMatch("candidate", "partial_album")
if music.year and resource.year and str(music.year) != str(resource.year):

View File

@@ -156,6 +156,39 @@ def test_soundtrack_credit_does_not_change_recording_title(title):
assert music_title_matches(MusicInfo(title=expected), title)
@pytest.mark.parametrize("symbol", ["÷", "+", "=", "×", "−"])
def test_symbol_only_music_title_matches_itself_and_alias(symbol):
"""纯符号作品名不能因文本归一化为空而失去自身和别名匹配。"""
music = MusicInfo(title=symbol, title_aliases=["Readable Alias"])
assert music_title_matches(music, symbol)
assert music_title_matches(music, "Readable Alias")
assert not music_title_matches(music, "Different Title")
@pytest.mark.parametrize("symbol,alias,year", [
("÷", "Divide", 2017),
("+", "Plus", 2011),
("=", "Equals", 2021),
("×", "Multiply", 2017),
("−", "Subtract", 2023),
])
def test_symbol_only_album_matches_parsed_resource(symbol, alias, year):
"""纯符号专辑经资源解析后仍应按原名或可信别名精确命中。"""
music = MusicInfo(
music_type="album",
title=symbol,
album=symbol,
title_aliases=[alias],
album_aliases=[alias],
artists=["Ed Sheeran"],
year=year,
)
resource = f"Ed Sheeran - {symbol} - {year} - FLAC 分轨"
assert match_music_resource(music, resource).status == "exact"
def test_music_album_field_cannot_match_target_recording():
"""结构化资源中的所属专辑不能冒充同名目标单曲。"""
music = MusicInfo(title="Album", artists=["Artist"])