From 44487f43437861261b13e1a39f7fdc163f1cbe12 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Tue, 11 Aug 2026 10:36:32 +0800 Subject: [PATCH] =?UTF-8?q?fix(music):=20=E5=8D=B7=E5=8F=B7=E5=90=8E?= =?UTF-8?q?=E7=BC=80=E5=89=A5=E7=A6=BB=E4=B8=8B=E6=B2=89=E5=88=B0=E5=80=99?= =?UTF-8?q?=E9=80=89=E6=AF=94=E5=AF=B9=E4=BF=AE=E5=A4=8D=E7=B3=BB=E5=88=97?= =?UTF-8?q?=E4=B8=93=E8=BE=91=E5=A4=B1=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 检索侧已剥离的 Vol. N 卷号后缀在候选挑选侧同样剥离, 「好歌茹芸, Vol. 3」本体名与条目「好歌, 茹芸」主标题弱匹配命中 - 检索词元切分补充 ASCII 逗号/分号,避免标点成为逐字检索词元 - 补充卷号弱匹配测试案例 --- app/modules/musicbrainz/__init__.py | 21 +++++++++++++++------ tests/test_musicbrainz_module.py | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/app/modules/musicbrainz/__init__.py b/app/modules/musicbrainz/__init__.py index 836ddf016..91cae6435 100644 --- a/app/modules/musicbrainz/__init__.py +++ b/app/modules/musicbrainz/__init__.py @@ -192,6 +192,14 @@ class MusicBrainzModule(_ModuleBase): text = str(value or "").strip() return cls._normalize_text(text.split(" ", 1)[0]) if text else "" + # 系列专辑的卷号后缀(好歌茹芸, Vol. 3)是发行分卷标记,条目本体不含卷号 + _VOLUME_SUFFIX_RE = re.compile(r",?\s*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 ""))) + # 演唱会资源的标题常带演出后缀(S.H.E十七音乐会),条目仅保留演出名本体 _PERFORMANCE_SUFFIX_RE = re.compile( r"\s*(?:音乐会|音樂會|演唱会|演唱會|巡回|巡演|Live|Tour)$", re.IGNORECASE) @@ -249,8 +257,7 @@ class MusicBrainzModule(_ModuleBase): artist = meta.artists[0] if meta.artists else None # 括号注释与 Vol. 卷号在条目中常以 disambiguation 形式存在,变体名兜底检索 bare_title = cls._strip_parenthetical(title) - bare_title = re.sub(r",?\s*vol\.?\s*\d+$", "", bare_title, flags=re.IGNORECASE) - bare_title = cls._normalize_text(bare_title) + bare_title = cls._strip_volume_suffix(bare_title) # 专辑名开头的艺术家署名前缀同样是命名习惯,用主体名检索 title = cls._strip_artist_prefix(title, meta.artists) bare_title = cls._strip_artist_prefix(bare_title, meta.artists) @@ -613,8 +620,9 @@ class MusicBrainzModule(_ModuleBase): # 曲名开头的艺术家署名前缀是命名习惯,用主体名比对 clean_title = cls._strip_artist_prefix(cls._search_title(meta.title), meta.artists) # 条目的影视 tie-in 注释多为全角括号,与资源半角注释无法精确相等, - # 去括号后的主体曲名一致视为弱匹配,且需艺术家同时命中才采信 - bare_title = cls._strip_parenthetical(clean_title) + # 去括号后的主体曲名一致视为弱匹配,且需艺术家同时命中才采信; + # 卷号后缀(Vol. 3)是发行分卷标记,条目本体不含卷号 + bare_title = cls._strip_volume_suffix(cls._strip_parenthetical(clean_title)) ranked: list[tuple[int, MusicInfo]] = [] for candidate in candidates: if normalized_source and (candidate.source or "").casefold() != normalized_source: @@ -692,7 +700,8 @@ class MusicBrainzModule(_ModuleBase): cls._search_title(meta.album or meta.title), meta.artists) if not clean_title: return None - bare_title = cls._strip_parenthetical(clean_title) + # 去括号与卷号后缀后的本体名用于弱匹配(好歌茹芸, Vol. 3 -> 好歌茹芸) + bare_title = cls._strip_volume_suffix(cls._strip_parenthetical(clean_title)) ranked: list[tuple[int, MusicInfo]] = [] for album in albums: score = 0 @@ -960,7 +969,7 @@ class MusicBrainzModule(_ModuleBase): r"[\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uAC00-\uD7AF]") # 检索词元切分:按空白、标点与括号拆分,保留 CJK 串与拉丁词(括号对逐字检索无意义) _QUERY_TOKEN_SPLIT_RE = re.compile( - r"[\s\-–—−-。,、;:!?·.…()()「」『』【】\[\]《》]+") + r"[\s\-–—−-。,、;:!?·.…()()「」『』【】\[\]《》,;]+") @classmethod def _query_phrase(cls, value: Optional[str]) -> Optional[str]: diff --git a/tests/test_musicbrainz_module.py b/tests/test_musicbrainz_module.py index 227492655..ab5eb669c 100644 --- a/tests/test_musicbrainz_module.py +++ b/tests/test_musicbrainz_module.py @@ -679,6 +679,24 @@ def test_select_album_candidate_matches_performance_suffix(): assert matched.media_id == "album-1" +def test_select_album_candidate_strips_volume_suffix(): + """系列专辑卷号后缀(Vol. 3)是发行分卷标记,本体名一致应弱匹配命中。""" + meta = MetaMusic(title="好歌茹芸, Vol. 3", artists=["许茹芸"], year=2011) + album = MusicInfo( + source="musicbrainz", + music_type="album", + media_id="album-1", + title="好歌, 茹芸: Valen Hsu Greatest Hits", + artists=["許茹芸"], + year=2011, + ) + + matched = MusicBrainzModule._select_album_candidate(meta, [album]) + + assert matched is not None + assert matched.media_id == "album-1" + + def test_select_album_candidate_matches_contained_title(): """条目带额外前缀完整包含资源主体名(原声带类)时应弱匹配命中。""" meta = MetaMusic(