fix(music): 系列专辑弱匹配要求分卷一致避免跨卷误配

- 资源带卷号(Ibiza Lounge Moments, Vol. 1)时候选卷号为其他分卷
  直接排除,修复 Vol.1/Vol.2 被误配到 Vol.3 条目;
  候选无卷号(条目本体不含分卷标记)时仍允许弱匹配
- 补充分卷一致性测试案例
This commit is contained in:
jxxghp
2026-08-11 11:22:57 +08:00
parent ae2b7810a6
commit 778d185c9d
2 changed files with 30 additions and 2 deletions

View File

@@ -194,12 +194,20 @@ class MusicBrainzModule(_ModuleBase):
# 系列专辑的卷号后缀(好歌茹芸, Vol. 3是发行分卷标记条目本体不含卷号
_VOLUME_SUFFIX_RE = re.compile(r",?\s*vol\.?\s*\d+$", re.IGNORECASE)
# 卷号提取:标题任意位置的 Vol. N 写法,系列专辑弱匹配时用于分卷一致性校验
_VOLUME_NUMBER_RE = re.compile(r"vol\.?\s*(\d+)", re.IGNORECASE)
@classmethod
def _strip_volume_suffix(cls, value: Optional[str]) -> str:
"""剔除标题尾部的卷号后缀,返回专辑本体名。"""
return cls._normalize_text(cls._VOLUME_SUFFIX_RE.sub("", str(value or "")))
@classmethod
def _volume_number(cls, value: Optional[str]) -> Optional[str]:
"""提取标题中的卷号Vol. 3 -> 3无卷号返回 None。"""
match = cls._VOLUME_NUMBER_RE.search(str(value or ""))
return match.group(1) if match else None
# 原声带资源标题的通用描述词尾部(条目本体是电影名);
# 尾部描述词保留时作为原声带形态标记参与弱匹配判定
_SOUNDTRACK_SUFFIX_RE = re.compile(
@@ -726,8 +734,10 @@ class MusicBrainzModule(_ModuleBase):
cls._search_title(meta.album or meta.title), meta.artists)
if not clean_title:
return None
# 去括号与卷号后缀后的本体名用于弱匹配(好歌茹芸, Vol. 3 -> 好歌茹芸)
# 去括号与卷号后缀后的本体名用于弱匹配(好歌茹芸, Vol. 3 -> 好歌茹芸)
# 资源带卷号时弱匹配要求候选卷号一致,避免 Ibiza Vol.1 误配 Vol.3
bare_title = cls._strip_volume_suffix(cls._strip_parenthetical(clean_title))
meta_volume = cls._volume_number(clean_title)
ranked: list[tuple[int, MusicInfo]] = []
for album in albums:
score = 0
@@ -738,7 +748,10 @@ class MusicBrainzModule(_ModuleBase):
for candidate_artist in album.artists
)
title_match = False
if cls._same_text(clean_title, album_title):
# 资源带卷号时候选卷号不一致(含其他分卷)直接排除,避免 Vol.1 误配 Vol.3
if meta_volume and cls._volume_number(album_title) not in (None, meta_volume):
pass
elif cls._same_text(clean_title, album_title):
score += 4
title_match = True
elif (

View File

@@ -697,6 +697,21 @@ def test_select_album_candidate_strips_volume_suffix():
assert matched.media_id == "album-1"
def test_select_album_candidate_rejects_wrong_volume():
"""资源带卷号时其他分卷候选不能被弱匹配采信。"""
meta = MetaMusic(title="Ibiza Lounge Moments, Vol. 1", artists=["Various Artists"], year=2022)
wrong_volume = MusicInfo(
source="musicbrainz",
music_type="album",
media_id="album-wrong",
title="Ibiza Lounge Moments, Vol. 3",
artists=["Various Artists"],
year=2023,
)
assert MusicBrainzModule._select_album_candidate(meta, [wrong_volume]) is None
def test_select_album_candidate_matches_contained_title():
"""条目带额外前缀完整包含资源主体名(原声带类)时应弱匹配命中。"""
meta = MetaMusic(