mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/jxxghp/MoviePilot.git
synced 2026-09-20 08:03:34 +08:00
fix(music): 修复音乐资源命名变体导致的漏搜 (#6706)
This commit is contained in:
@@ -288,6 +288,18 @@ _MUSIC_RESOURCE_CATALOG_TAIL_RE = re.compile(
|
||||
r"\s+-\s+\{[^{}\r\n]+\}(?:\s+-\s+[A-Za-z0-9][A-Za-z0-9@._-]*)?\s*$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
# 发行年份与独立规格尾段共同限定资源命名,不能仅凭无空格连字符猜测署名方向。
|
||||
_MUSIC_RESOURCE_RELEASE_RE = re.compile(
|
||||
r"^(?P<name>.+?)\s+(?P<year>(?:19|20)\d{2})\s+[-–—−-]+\s+(?P<spec>.+)$"
|
||||
)
|
||||
_MUSIC_RESOURCE_BRACKET_RE = re.compile(
|
||||
r"^\s*\[(?P<name>[^\[\]]+)\]\s*\[(?P<year>(?:19|20)\d{2})\](?P<tail>.*)$"
|
||||
)
|
||||
_MUSIC_RESOURCE_GROUP_RE = re.compile(
|
||||
r"^(?P<spec>.+?)(?:\s*[-–—−-]+\s*|\s+)"
|
||||
r"(?!(?:DELUXE|EXPANDED|SPECIAL|LIMITED|ANNIVERSARY|REMASTER(?:ED)?|RE-?RECORD(?:ED|ING))$)"
|
||||
r"(?P<group>[A-Z0-9][A-Z0-9@._-]{1,20})$"
|
||||
)
|
||||
# 年份及完整发行日期括号提供候选消歧线索,规格或目录号不作为日期。
|
||||
_MUSIC_YEAR_RE = re.compile(
|
||||
r"[\(\[(【]((?:19|20)\d{2})"
|
||||
@@ -750,10 +762,11 @@ class MetaMusic(MetaBase):
|
||||
def parse_resource(cls, title: str, subtitle: Optional[str] = None) -> "MetaMusic":
|
||||
"""合并资源标题与副标题的独立证据,不使用搜索目标补写作品身份。
|
||||
|
||||
标题解析仍复用 Python/Rust 公共入口;副标题只补缺失字段,保留标题中
|
||||
已有的署名。专辑、曲序、曲名的明确多段格式在资源层统一补充。
|
||||
带年份及规格证据的发行命名先统一归一,再复用 Python/Rust 公共入口;
|
||||
副标题只补缺失字段,保留标题中已有的署名。专辑、曲序、曲名的明确
|
||||
多段格式在资源层统一补充。
|
||||
"""
|
||||
meta = cls.parse_query(_MUSIC_RESOURCE_CATALOG_TAIL_RE.sub("", title))
|
||||
meta = cls.parse_query(cls._normalize_resource_release(title))
|
||||
meta.org_string = title
|
||||
meta.apply_audio_quality(title)
|
||||
subtitle = (subtitle or "").replace("丨", "|")
|
||||
@@ -812,6 +825,42 @@ class MetaMusic(MetaBase):
|
||||
meta.version = cls._resource_version(title, subtitle)
|
||||
return meta
|
||||
|
||||
@classmethod
|
||||
def _normalize_resource_release(cls, title: str) -> str:
|
||||
"""在 Python/Rust 解析前归一带年份及规格证据的站点发行命名。
|
||||
|
||||
方括号只展开首个作品段;独立规格尾段允许携带发布组。此类发行命名的
|
||||
中文紧凑署名采用「艺人-作品」,普通「作品-艺人」查询仍保留原有规则。
|
||||
"""
|
||||
text = _MUSIC_RESOURCE_CATALOG_TAIL_RE.sub("", title)
|
||||
bracket = _MUSIC_RESOURCE_BRACKET_RE.fullmatch(text)
|
||||
if bracket:
|
||||
name, year = bracket.group("name", "year")
|
||||
tail = bracket.group("tail")
|
||||
else:
|
||||
release = _MUSIC_RESOURCE_RELEASE_RE.fullmatch(text)
|
||||
if not release or not cls._is_resource_spec_tail(release.group("spec")):
|
||||
return text
|
||||
name, year = release.group("name", "year")
|
||||
tail = ""
|
||||
if not _MUSIC_ARTIST_TITLE_RE.match(name):
|
||||
artist, separator, work = name.partition("-")
|
||||
if separator and cls._contains_cjk(artist) and cls._contains_cjk(work):
|
||||
name = f"{artist.strip()} - {work.strip()}"
|
||||
# 保留尾部裸年份,让 Album/Single 等真实作品名不会提前被当作独立发行标签删除。
|
||||
return f"{name} {year}{tail}"
|
||||
|
||||
@staticmethod
|
||||
def _is_resource_spec_tail(value: str) -> bool:
|
||||
"""只清理完整规格及大写发布组尾标,录音版本和发行版本词必须保留。"""
|
||||
if _MUSIC_SPEC_SEGMENT_RE.fullmatch(value):
|
||||
return True
|
||||
group = _MUSIC_RESOURCE_GROUP_RE.fullmatch(value)
|
||||
return bool(
|
||||
group and _MUSIC_SPEC_SEGMENT_RE.fullmatch(group.group("spec"))
|
||||
and not _MUSIC_RECORDING_VERSION_RE.search(f"({group.group('group')})")
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _native_resource_title(
|
||||
cls, title: Optional[str], subtitle: str, secondary: "MetaMusic",
|
||||
|
||||
110
tests/test_music_resource_names.py
Normal file
110
tests/test_music_resource_names.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""音乐站点发行命名的解析和搜索匹配回归。"""
|
||||
|
||||
import pytest
|
||||
|
||||
from app.adapters.system import rust
|
||||
from app.chain.search import SearchChain
|
||||
from app.domain.context import MusicInfo, TorrentInfo
|
||||
from app.domain.meta import runtime
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.music import match_music_resource
|
||||
from app.schemas.types import MediaType
|
||||
|
||||
RESOURCE_TITLES = [
|
||||
"赵雷-署前街少年 2022 - WEB-DL - 24bit ALAC-HHWEB",
|
||||
"赵雷 - 署前街少年 2022 - FLAC TJUPT",
|
||||
"[赵雷-署前街少年][2022][Hi-Res 44 1kHz_24bit][FLAC]",
|
||||
"赵雷 - 署前街少年 2022 - WEB-DL - 24bit ALAC - HHWEB",
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(params=["python", "rust"])
|
||||
def music_engine(request, monkeypatch):
|
||||
"""分别验证纯 Python 和实际 Rust 解析器,恢复全局加速器配置。"""
|
||||
if request.param == "rust":
|
||||
assert rust.is_available(), "锁定环境应安装 moviepilot-rust"
|
||||
monkeypatch.setattr(rust, "is_enabled", lambda: True)
|
||||
|
||||
def reject_python_fallback(*_args, **_kwargs):
|
||||
raise AssertionError("Rust 资源名回归不能用 Python 回退冒充通过")
|
||||
|
||||
monkeypatch.setattr(MetaMusic, "_prepare_name_context", reject_python_fallback)
|
||||
monkeypatch.setattr(runtime, "_metainfo_accelerator", rust if request.param == "rust" else None)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("music_engine")
|
||||
@pytest.mark.parametrize("title", RESOURCE_TITLES)
|
||||
def test_album_release_names_match_without_target_assisted_parsing(title):
|
||||
"""真实命名变体应独立解析身份,并通过专辑搜索的严格匹配。"""
|
||||
meta = MetaMusic.parse_resource(title)
|
||||
assert (meta.title, meta.artists, meta.year) == ("署前街少年", ["赵雷"], 2022)
|
||||
assert meta.org_string == title
|
||||
assert meta.audio_format == ("ALAC" if "ALAC" in title else "FLAC")
|
||||
assert meta.audio_lossless is True
|
||||
target = MusicInfo(music_type="album", title="署前街少年", artists=["赵雷"], year="2022")
|
||||
assert match_music_resource(target, title).status == "exact"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("music_engine")
|
||||
def test_album_search_preserves_resources_from_different_sites():
|
||||
"""同一专辑的站点命名差异不能隐藏有折扣的候选。"""
|
||||
target = MusicInfo(music_type="album", title="署前街少年", artists=["赵雷"], year="2022")
|
||||
torrents = [
|
||||
TorrentInfo(title=title, category=MediaType.MUSIC.value, site_name=f"Site{index}",
|
||||
size=599533814, downloadvolumefactor=0.5 if index == 0 else 1,
|
||||
hit_and_run=index != 0)
|
||||
for index, title in enumerate(RESOURCE_TITLES)
|
||||
]
|
||||
contexts = SearchChain()._build_music_contexts(torrents, target, rule_groups=[])
|
||||
assert [context.torrent_info for context in contexts] == torrents
|
||||
assert contexts[0].torrent_info.downloadvolumefactor == 0.5
|
||||
assert contexts[0].torrent_info.hit_and_run is False
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("music_engine")
|
||||
@pytest.mark.parametrize("title,reason", [
|
||||
("赵雷-成都 2022 - FLAC TJUPT", "title_mismatch"),
|
||||
("其他艺人-署前街少年 2022 - FLAC TJUPT", "artist_unverified"),
|
||||
("赵雷-署前街少年 2021 - FLAC TJUPT", "year_mismatch"),
|
||||
("赵雷-署前街少年 (Live) 2022 - FLAC TJUPT", "version_mismatch"),
|
||||
("[赵雷-署前街少年 (Remix)][2022][FLAC]", "version_mismatch"),
|
||||
])
|
||||
def test_release_normalization_preserves_identity_constraints(title, reason):
|
||||
"""清理发行标记不能放宽作品、艺人、年份和版本的原有匹配约束。"""
|
||||
target = MusicInfo(music_type="album", title="署前街少年", artists=["赵雷"], year="2022")
|
||||
result = match_music_resource(target, title)
|
||||
assert result.status != "exact"
|
||||
assert result.reason == reason
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("music_engine")
|
||||
@pytest.mark.parametrize("title", [
|
||||
"为你盛开——许巍",
|
||||
"署前街少年-赵雷 FLAC",
|
||||
"Artist - Best Album FLAC",
|
||||
"Artist - Single Ladies FLAC",
|
||||
"Artist - Live At Montreux 1999 2022 FLAC",
|
||||
"Artist - Album 2022 - FLAC LIVE",
|
||||
"Artist - Album 2022 - FLAC DELUXE",
|
||||
"Artist - Album 2022 - FLAC REMASTERED",
|
||||
"Artist - Album 2022 - FLAC RE-RECORDED",
|
||||
"Artist - Album 2022 - FLAC Bonus Track",
|
||||
])
|
||||
def test_release_normalization_preserves_other_naming_styles(title):
|
||||
"""缺少明确发行尾链时沿用原解析规则,不能把自然语言尾段当发布组。"""
|
||||
assert MetaMusic._normalize_resource_release(title) == title
|
||||
resource = MetaMusic.parse_resource(title)
|
||||
query = MetaMusic.parse_query(title)
|
||||
assert (resource.title, resource.artists, resource.year) == (query.title, query.artists, query.year)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("music_engine")
|
||||
@pytest.mark.parametrize("title,artists", [
|
||||
("Artist - Album 2022 - FLAC", ["Artist"]),
|
||||
("Album 2022 - FLAC", []),
|
||||
("[Artist - Album][2022][FLAC]", ["Artist"]),
|
||||
])
|
||||
def test_release_names_without_group_or_cjk_credit(title, artists):
|
||||
"""规格不带发布组也应保留年份,且不能为缺少署名的资源补写艺术家。"""
|
||||
meta = MetaMusic.parse_resource(title)
|
||||
assert (meta.title, meta.artists, meta.year, meta.audio_format) == ("Album", artists, 2022, "FLAC")
|
||||
Reference in New Issue
Block a user