Files
MoviePilot/tests/test_metamusic_samples.py
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

155 lines
6.4 KiB
Python

"""
真实站点主副标题的离线音乐识别回归,不包含站点凭据或网络访问。
"""
import json
from pathlib import Path
import Pinyin2Hanzi
import pytest
from app.adapters.system import rust
from app.domain.meta import metamusic, runtime
from app.domain.meta.metamusic import MetaMusic
from app.domain.metainfo import MetaInfo
from app.schemas.types import MediaType
SAMPLES = json.loads(
(Path(__file__).parent / "fixtures" / "music_metainfo_samples.json").read_text(encoding="utf-8")
)["samples"]
@pytest.mark.parametrize("engine", ["python", "rust"])
@pytest.mark.parametrize("sample", SAMPLES, ids=lambda sample: sample["id"])
def test_real_music_titles_and_subtitles(sample, engine, monkeypatch):
"""
两条真实 MetaInfo 路径须提取相同的名称证据,不能用 Python 回退冒充 Rust。
"""
if engine == "rust":
if not rust.is_available():
pytest.skip("moviepilot_rust 扩展未安装")
monkeypatch.setattr(rust, "is_enabled", lambda: True)
def reject_python_fallback(*_args, **_kwargs):
"""
Rust 样本解析一旦回退 Python 就显式失败。
"""
raise AssertionError("真实音乐样本不应从 Rust 回退 Python")
monkeypatch.setattr(MetaMusic, "_prepare_name_context", reject_python_fallback)
monkeypatch.setattr(runtime, "_metainfo_accelerator", rust if engine == "rust" else None)
meta = MetaInfo(sample["title"], sample["subtitle"], mtype=MediaType.MUSIC)
assert meta.type == MediaType.MUSIC
assert meta.org_string == sample["title"]
for field, expected in sample["expected"].items():
assert getattr(meta, field) == expected, field
@pytest.mark.parametrize("engine", ["python", "rust"])
@pytest.mark.parametrize("title, expected", [
("Artist - Best Album FLAC", "Best Album"),
("Artist - Single Ladies FLAC", "Single Ladies"),
("Artist - Live At Montreux 1999 2022 FLAC", "Live At Montreux 1999 2022"),
])
def test_release_cleanup_preserves_natural_titles(title, expected, engine, monkeypatch):
"""
发行类型和年份规则不能删除作品名中的自然单词或连续年份。
"""
if engine == "rust" and not rust.is_available():
pytest.skip("moviepilot_rust 扩展未安装")
monkeypatch.setattr(rust, "is_enabled", lambda: True)
monkeypatch.setattr(runtime, "_metainfo_accelerator", rust if engine == "rust" else None)
meta = MetaInfo(title, mtype=MediaType.MUSIC)
assert meta.title == expected
assert meta.album is None
@pytest.mark.parametrize("engine", ["python", "rust"])
@pytest.mark.parametrize("subtitle", [
"歌手:周杰伦 - 专辑:叶惠美 | FLAC | [2003]",
"歌手:周杰伦;专辑:叶惠美;[2003] FLAC",
"歌手:周杰伦,专辑:叶惠美 | [2003] FLAC",
])
def test_subtitle_labels_keep_album_and_year_boundaries(subtitle, engine, monkeypatch):
"""
明确字段可补全缺失信息,平台分隔与后续字段不能污染署名或专辑名。
"""
if engine == "rust" and not rust.is_available():
pytest.skip("moviepilot_rust 扩展未安装")
monkeypatch.setattr(rust, "is_enabled", lambda: True)
monkeypatch.setattr(runtime, "_metainfo_accelerator", rust if engine == "rust" else None)
meta = MetaInfo("晴天", subtitle, mtype=MediaType.MUSIC)
assert (meta.artists, meta.title, meta.album, meta.year) == (["周杰伦"], "晴天", "叶惠美", 2003)
@pytest.mark.parametrize("engine", ["python", "rust"])
@pytest.mark.parametrize("suffix, artists", [
("(featuring Guest)", ["Artist", "Guest"]),
("(feat. Artist & Guest)", ["Artist", "Guest"]),
("(feature presentation)", ["Artist"]),
])
def test_featured_credits_require_explicit_marker(suffix, artists, engine, monkeypatch):
"""
仅提取明确的客串署名,保留原始曲名,去重且不误读普通注释。
"""
if engine == "rust" and not rust.is_available():
pytest.skip("moviepilot_rust 扩展未安装")
monkeypatch.setattr(rust, "is_enabled", lambda: True)
monkeypatch.setattr(runtime, "_metainfo_accelerator", rust if engine == "rust" else None)
meta = MetaInfo(f"Artist - Song {suffix} FLAC", mtype=MediaType.MUSIC)
assert meta.artists == artists
assert meta.title == f"Song {suffix}"
@pytest.mark.parametrize("engine", ["python", "rust"])
@pytest.mark.parametrize("title, subtitle, expected", [
("Shan.Ge.Liao.Zai.2023.FLAC", "音乐专辑 | 山歌廖哉 - 歌手:刀郎", "山歌廖哉"),
("Yisa Yu 2019 SanShiErLi FLAC", "郁可唯 - 三十而慄 2019 - FLAC 分軌", "三十而慄"),
("Shan Ge Liao Zai FLAC", "曲名:山高水长", "Shan Ge Liao Zai"),
("Artist - White Light 1971 FLAC", "歌手:歌手;曲名:白光", "White Light"),
("山歌廖哉 FLAC", "曲名:其他专辑", "山歌廖哉"),
])
def test_pinyin_title_prefers_verified_native_subtitle(title, subtitle, expected, engine, monkeypatch):
"""
中文原文须与拼音逐字对应;同字数无关文案及正常外文名不能触发替换。
"""
if engine == "rust" and not rust.is_available():
pytest.skip("moviepilot_rust 扩展未安装")
monkeypatch.setattr(rust, "is_enabled", lambda: True)
monkeypatch.setattr(runtime, "_metainfo_accelerator", rust if engine == "rust" else None)
meta = MetaInfo(title, subtitle, mtype=MediaType.MUSIC)
assert meta.title == expected
assert meta.org_string == title
def test_pinyin_check_loads_only_syllable_table(monkeypatch):
"""
拼音校验只读音节候选表,不创建会常驻约 50MB 概率表的 DefaultHmmParams。
"""
def reject_hmm_params(*_args, **_kwargs):
"""
完整 HMM 参数一旦被创建就显式失败。
"""
raise AssertionError("拼音校验不应载入 HMM 概率表")
monkeypatch.setattr(Pinyin2Hanzi, "DefaultHmmParams", reject_hmm_params)
# 模块级 from-import 会绕过上面的替换,一并拦截。
monkeypatch.setattr(metamusic, "DefaultHmmParams", reject_hmm_params, raising=False)
monkeypatch.setattr(runtime, "_metainfo_accelerator", None)
metamusic._music_pinyin_states.cache_clear()
meta = MetaInfo("Shan.Ge.Liao.Zai.2023.FLAC", "音乐专辑 | 山歌廖哉 - 歌手:刀郎", mtype=MediaType.MUSIC)
assert meta.title == "山歌廖哉"
assert metamusic._music_pinyin_states.cache_info().currsize == 1