mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-05 22:59:46 +08:00
613 lines
25 KiB
Python
613 lines
25 KiB
Python
"""
|
||
音乐搜索的名称边界、别名和人工候选回归测试。
|
||
"""
|
||
|
||
import copy
|
||
import pickle
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
from app.chain.download import DownloadChain
|
||
from app.chain.media import MediaChain
|
||
from app.chain.search import SearchChain
|
||
from app.domain.context import Context, MusicAlbumInfo, MusicInfo
|
||
from app.domain.meta.metamusic import MetaMusic
|
||
from app.domain.meta.runtime import get_metainfo_accelerator
|
||
from app.domain.metainfo import MetaInfo, MetaInfoPath
|
||
from app.domain.music import (
|
||
match_music_resource,
|
||
music_album_matches,
|
||
music_isrc_matches,
|
||
music_title_matches,
|
||
music_version_matches,
|
||
music_year_matches,
|
||
)
|
||
from app.schemas.music import MusicMeta
|
||
from app.schemas.types import MediaType
|
||
|
||
|
||
@pytest.mark.parametrize("title", ["U2 - One Tree Hill FLAC", "U2 - Someone FLAC", "U2 - One - Tree Hill FLAC"])
|
||
def test_music_match_rejects_other_titles(title):
|
||
"""
|
||
短曲名不能将较长的另一首作品判为同一单曲。
|
||
"""
|
||
assert match_music_resource(MusicInfo(title="One", artists=["U2"]), title).status == "rejected"
|
||
|
||
|
||
@pytest.mark.parametrize("artist,title,wrong_title", [
|
||
("Lee", "Leeway", "way"),
|
||
("Rise", "Surprise", "Surp"),
|
||
("Lee", "HappyLee", "Happy"),
|
||
("Élan", "Élansong", "song"),
|
||
("Мир", "Мирный", "ный"),
|
||
])
|
||
def test_artist_affix_cannot_cut_into_a_title_word(artist, title, wrong_title):
|
||
"""
|
||
署名恰好是单词的一部分时,不得据此构造另一首歌的名称。
|
||
"""
|
||
resource = f"{artist} - {title} FLAC"
|
||
assert match_music_resource(MusicInfo(title=title, artists=[artist]), resource).status == "exact"
|
||
assert match_music_resource(MusicInfo(title=wrong_title, artists=[artist]), resource).status == "rejected"
|
||
|
||
|
||
@pytest.mark.parametrize("title", ["Lee - Song", "Song - Lee", "LEE: Song"])
|
||
def test_artist_affix_preserves_explicit_signature_boundaries(title):
|
||
"""
|
||
已解析字段中的完整署名有分隔符时,仍可去掉署名参与匹配。
|
||
"""
|
||
target = MusicInfo(title="Song", artists=["Lee"])
|
||
meta = MetaMusic(title=title, artists=["Lee"])
|
||
assert match_music_resource(target, title, meta=meta).status == "exact"
|
||
|
||
|
||
def test_artist_affix_supports_existing_cjk_signature_style():
|
||
"""
|
||
中文连写及“的”字署名形式仍参与同一套资源名称比较。
|
||
"""
|
||
target = MusicInfo(title="爱情电影主题曲", artists=["许茹芸"])
|
||
for title in ("许茹芸爱情电影主题曲", "许茹芸的爱情电影主题曲"):
|
||
meta = MetaMusic(title=title, artists=["许茹芸"])
|
||
assert match_music_resource(target, title, meta=meta).status == "exact"
|
||
|
||
|
||
@pytest.mark.parametrize("artist", ["Jay Chou", "周杰倫"])
|
||
def test_music_match_accepts_source_artist_aliases(artist):
|
||
"""
|
||
同一艺术家来源别名和繁简署名均可命中。
|
||
"""
|
||
music = MusicInfo(title="晴天", artists=["周杰伦"], artist_aliases=["Jay Chou"])
|
||
assert match_music_resource(music, f"{artist} - 晴天 FLAC").status == "exact"
|
||
|
||
|
||
@pytest.mark.parametrize("artist", ["VA", "V.A.", "群星"])
|
||
def test_music_match_accepts_compilation_credit(artist):
|
||
"""
|
||
合辑通用艺术家署名不应造成漏搜。
|
||
"""
|
||
music = MusicInfo(music_type="album", title="Test Compilation", artists=["Various Artists"])
|
||
assert match_music_resource(music, f"{artist} - Test Compilation FLAC").status == "exact"
|
||
|
||
|
||
@pytest.mark.parametrize("title,reason", [
|
||
("Jay Chou - 晴天 FLAC", "artist_unverified"),
|
||
("周杰伦 - 晴天 (Live) FLAC", "version_mismatch"),
|
||
])
|
||
def test_music_match_uncertain_identity_requires_confirmation(title, reason):
|
||
"""
|
||
未核验艺名和不同录音版本只能交给用户确认。
|
||
"""
|
||
result = match_music_resource(MusicInfo(title="晴天", artists=["周杰伦"]), title)
|
||
assert (result.status, result.reason) == ("candidate", reason)
|
||
|
||
|
||
def test_music_match_distinguishes_missing_evidence_and_related_album():
|
||
"""
|
||
缺艺术家、缺分类和所属专辑均不得直接绑定成目标单曲。
|
||
"""
|
||
music = MusicInfo(title="Get Lucky", artists=["Daft Punk"], album="Random Access Memories")
|
||
assert match_music_resource(music, "Daft Punk - Random Access Memories FLAC").status == "album"
|
||
assert match_music_resource(music, "Daft Punk - Get Lucky FLAC", category=None).reason == "category_unknown"
|
||
assert match_music_resource(MusicInfo(title="晴天"), "周杰伦 - 晴天 FLAC").reason == "target_artist_missing"
|
||
|
||
|
||
def test_music_match_handles_accents_and_edition_suffix():
|
||
"""
|
||
变音符不改变身份,缺少明确的目标发行版本则必须人工确认。
|
||
"""
|
||
assert match_music_resource(MusicInfo(title="Halo", artists=["Beyoncé"]), "Beyonce - Halo FLAC").status == "exact"
|
||
album = MusicInfo(music_type="album", title="Test Album (Deluxe Edition)", artists=["Artist"])
|
||
assert match_music_resource(album, "Artist - Test Album FLAC").reason == "edition_unverified"
|
||
|
||
|
||
def test_resource_parser_merges_subtitle_without_target_information():
|
||
"""
|
||
副标题补全艺术家、专辑和音质,资源元数据不携带目标 ID。
|
||
"""
|
||
meta = MetaMusic.parse_resource("晴天 FLAC", "演唱:周杰伦;专辑:叶惠美;24bit 96kHz")
|
||
assert meta.title == "晴天"
|
||
assert meta.artists == ["周杰伦"]
|
||
assert meta.album == "叶惠美"
|
||
assert meta.bit_depth == 24
|
||
assert meta.media_id is None
|
||
|
||
|
||
def test_resource_parser_reads_unlabelled_album_description():
|
||
"""
|
||
描述末尾的无冒号专辑字段也应保留作品证据。
|
||
"""
|
||
description = "Apple Music ALAC 宋冬野 2026 06 29 专辑 再想想"
|
||
meta = MetaMusic.parse_resource(
|
||
"Song DongYe 2026 AppleLossless Take Another Moment", description
|
||
)
|
||
assert (meta.title, meta.album, meta.artists, meta.year) == (
|
||
"AppleLossless Take Another Moment", "再想想", ["Song DongYe"], 2026
|
||
)
|
||
|
||
|
||
def test_album_match_uses_unlabelled_description_title_as_candidate():
|
||
"""
|
||
外文资源的中文专辑描述可形成候选,但未核验艺术家不能自动命中。
|
||
"""
|
||
title = "Song DongYe 2026 AppleLossless Take Another Moment"
|
||
description = "Apple Music ALAC 宋冬野 2026 06 29 专辑 再想想"
|
||
target = MusicInfo(music_type="album", title="再想想", artists=["宋冬野"], year=2026)
|
||
result = match_music_resource(target, title, description)
|
||
assert (result.status, result.reason) == ("candidate", "artist_unverified")
|
||
|
||
|
||
def test_resource_parser_preserves_title_evidence_and_parses_track_segments():
|
||
"""
|
||
冲突副标题不能覆盖标题艺术家,明确的曲序段则可用于区分专辑和单曲。
|
||
"""
|
||
meta = MetaMusic.parse_resource("Artist - Album - 01 - Song [FLAC]", "演唱:Other Artist")
|
||
assert (meta.artists, meta.album, meta.track_number, meta.title) == (["Artist"], "Album", 1, "Song")
|
||
live = MetaMusic.parse_resource("U2 - One (Live) FLAC")
|
||
assert live.version == "Live"
|
||
|
||
|
||
def test_resource_parser_keeps_bracketed_title():
|
||
"""
|
||
纯展示括号内的作品名不能与规格标签一同丢弃。
|
||
"""
|
||
meta = MetaMusic.parse_resource("【永遠・是朋友】24bit/96kHz", "專輯藝人:周華健;無損音樂")
|
||
assert meta.title and "永遠" in meta.title
|
||
assert meta.artists == ["周華健"]
|
||
|
||
|
||
@pytest.mark.parametrize("title", [
|
||
'Macavity (From The Motion Picture Soundtrack "Cats")',
|
||
'Beautiful Ghosts (From The Motion Picture "Cats")',
|
||
"Beautiful Ghosts《猫》原声插曲",
|
||
])
|
||
def test_soundtrack_credit_does_not_change_recording_title(title):
|
||
"""
|
||
影视来源说明不是歌名本体,不能让正确的 MusicBrainz 录音候选被拒绝。
|
||
"""
|
||
expected = "Macavity" if title.startswith("Macavity") else "Beautiful Ghosts"
|
||
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"])
|
||
assert match_music_resource(music, "Artist - Album - 01 - Other Song FLAC").status == "rejected"
|
||
wanted = MusicInfo(title="Wanted Song", album="Album", artists=["Artist"])
|
||
assert match_music_resource(wanted, "Artist - Album - 01 - Other Song FLAC").status == "rejected"
|
||
|
||
|
||
@pytest.mark.parametrize("title", [
|
||
"周杰伦 - 晴天 FLAC",
|
||
"周杰伦 - 03 - 晴天 FLAC",
|
||
"周杰伦 - 03 - 叶惠美 FLAC",
|
||
])
|
||
def test_album_match_cannot_promote_track_from_subtitle(title):
|
||
"""
|
||
即使没有曲序,单曲所属专辑字段也不能证明资源是整张专辑。
|
||
"""
|
||
album = MusicInfo(music_type="album", title="叶惠美", artists=["周杰伦"])
|
||
match = match_music_resource(album, title, "专辑:叶惠美")
|
||
assert (match.status, match.reason) == ("candidate", "partial_album")
|
||
|
||
|
||
@pytest.mark.parametrize("title", ["周杰伦 - 叶惠美 FLAC", "周杰伦《叶惠美》FLAC", "Jay Chou - Ye Hui Mei FLAC"])
|
||
def test_album_match_keeps_primary_album_names_and_aliases(title):
|
||
"""
|
||
整专主标题及可信别名仍可精确命中,不因副标题补充专辑字段而降级。
|
||
"""
|
||
album = MusicInfo(music_type="album", title="叶惠美", title_aliases=["Ye Hui Mei"],
|
||
artists=["周杰伦"], artist_aliases=["Jay Chou"])
|
||
assert match_music_resource(album, title, "专辑:叶惠美").status == "exact"
|
||
|
||
|
||
@pytest.mark.parametrize("subtitle", ["版本:Live", "錄音版本:Live", "Version: Live", "[Live]", "演唱:周杰伦;(Live)"])
|
||
def test_resource_subtitle_version_is_matching_evidence(subtitle):
|
||
"""
|
||
明确的副标题版本只可匹配同一版本,不能自动绑定为普通录音。
|
||
"""
|
||
title = "周杰伦 - 晴天 FLAC"
|
||
meta = MetaMusic.parse_resource(title, subtitle)
|
||
assert meta.version == "Live"
|
||
ordinary = MusicInfo(title="晴天", artists=["周杰伦"])
|
||
assert match_music_resource(ordinary, title, subtitle).reason == "version_mismatch"
|
||
live = MusicInfo(title="晴天 (Live)", artists=["周杰伦"])
|
||
assert match_music_resource(live, title, subtitle).status == "exact"
|
||
|
||
|
||
def test_resource_title_version_has_priority_over_subtitle():
|
||
"""
|
||
标题版本已有明确证据时,冲突副标题不能覆盖;艺人字段也不是版本声明。
|
||
"""
|
||
assert MetaMusic.parse_resource("U2 - One (Live) FLAC", "版本:Remix").version == "Live"
|
||
assert MetaMusic.parse_resource("Song FLAC", "艺术家:Live").version is None
|
||
|
||
|
||
@pytest.mark.parametrize("expected,actual,matched", [
|
||
("Live 1999", "Live 2000", False),
|
||
("Live 1999-01-02", "Live 1999.01.03", False),
|
||
("Live 1999-01-02", "Live 1999/1/2", True),
|
||
("Live 1999", "Live 1999-01-02", True),
|
||
("Live", "Live 1999", True),
|
||
("Live 1999", "Live", True),
|
||
("mix 1999", "mix 2000", False),
|
||
("Live 1999-02-31", "Live 1999-03-01", True),
|
||
("Live 1999-01-02", "Live 1999-01-02", True),
|
||
("Live 1999年1月2日", "Live 1999-01-03", False),
|
||
("Live 1999-2001", "Live 2000", True),
|
||
("Live 1999-01-01 to 1999-01-03", "Live 1999-01-02", True),
|
||
])
|
||
def test_music_version_checks_only_explicit_conflicting_dates(expected, actual, matched):
|
||
"""
|
||
同类录音的明确日期或年份冲突仍须排除;缺失、部分日期或非法日期不能凭空补全。
|
||
"""
|
||
target = MusicInfo(title="1999", artists=["Artist"], version=expected)
|
||
meta = MetaMusic(title="1999", artists=["Artist"], version=actual)
|
||
assert music_version_matches(target, meta) is matched
|
||
|
||
|
||
def test_taylors_version_is_distinct_from_original_recording():
|
||
"""
|
||
Taylor's Version 是重新录制,不得与原版仅凭同名和同艺人互换。
|
||
"""
|
||
rerecorded = MusicInfo(
|
||
title="Back to December (Taylor's Version)",
|
||
artists=["Taylor Swift"],
|
||
)
|
||
original = MetaMusic(title="Back To December", artists=["Taylor Swift"])
|
||
same_version = MetaMusic(
|
||
title="Back To December (Taylor’s Version)",
|
||
artists=["Taylor Swift"],
|
||
)
|
||
|
||
assert music_version_matches(rerecorded, original) is False
|
||
assert music_version_matches(rerecorded, same_version) is True
|
||
|
||
|
||
def test_music_release_evidence_rejects_wrong_album_and_year():
|
||
"""
|
||
同名同艺人的录音仍须服从文件标签中的专辑本体和发行年份。
|
||
"""
|
||
info = MusicInfo(
|
||
title="Sparks Fly",
|
||
artists=["Taylor Swift"],
|
||
album="Speak Now",
|
||
year=2025,
|
||
)
|
||
meta = MetaMusic(
|
||
title="Sparks Fly",
|
||
artists=["Taylor Swift"],
|
||
album="Speak Now (Target Exclusive Deluxe Edition 2CD).CD1",
|
||
year=2010,
|
||
)
|
||
|
||
assert music_album_matches(info, meta.album) is True
|
||
assert music_year_matches(info, meta) is False
|
||
|
||
|
||
def test_music_resource_rejects_different_dated_live_recording():
|
||
"""
|
||
版本日期可来自标题括号,不将两场同名现场录音自动绑定成同一作品。
|
||
"""
|
||
target = MusicInfo(title="Song (Live 2001-05-02)", artists=["Artist"])
|
||
assert match_music_resource(target, "Artist - Song (Live 2001-05-03) FLAC").reason == "version_mismatch"
|
||
|
||
|
||
@pytest.mark.parametrize("code", [None, "", "Unknown", "N/A", "0", "---", "USABC260001", "UŚABC2600001"])
|
||
def test_invalid_isrc_cannot_be_strong_identity(code):
|
||
"""
|
||
空值、占位值与格式错误不能因为文本相同而绕过名称和版本匹配。
|
||
"""
|
||
assert music_isrc_matches(MusicInfo(isrc=code), MetaMusic(isrc=code)) is False
|
||
|
||
|
||
@pytest.mark.parametrize("code", ["USABC2600001", "us-abc-26-00001", "ISRC US-ABC-26-00001"])
|
||
def test_valid_isrc_accepts_standard_display_format(code):
|
||
"""
|
||
标准代码、大小写和用于展示的前缀分隔格式应指向同一录音身份。
|
||
"""
|
||
assert music_isrc_matches(MusicInfo(isrc="USABC2600001"), MetaMusic(isrc=code)) is True
|
||
|
||
|
||
@pytest.mark.parametrize("mtype,suffix", [(MediaType.MUSIC, " FLAC"), (None, ".flac")])
|
||
@pytest.mark.parametrize("global_words", [False, True])
|
||
def test_music_metainfo_applies_shared_recognition_words(monkeypatch, mtype, suffix, global_words):
|
||
"""
|
||
音乐名称复用影视识别词语法,音频后缀与显式类型入口行为一致。
|
||
"""
|
||
words = ["错误曲名 => 晴天"]
|
||
if global_words:
|
||
monkeypatch.setattr("app.domain.meta.words.get_custom_words", lambda: words)
|
||
meta = MetaInfo(f"周杰伦 - 错误曲名{suffix}", "版本:Live", mtype=mtype,
|
||
custom_words=None if global_words else words)
|
||
assert (meta.title, meta.artists, meta.version) == ("晴天", ["周杰伦"], "Live")
|
||
assert meta.audio_format == "FLAC"
|
||
assert meta.apply_words == words
|
||
assert MetaMusic.from_dict(meta.to_dict()).apply_words == words
|
||
assert MusicMeta.model_validate(meta.to_dict()).apply_words == words
|
||
|
||
|
||
def test_legacy_music_metainfo_keeps_empty_recognition_words():
|
||
"""
|
||
旧音乐缓存缺少识别词记录时仍能恢复,且实例之间不共享可变列表。
|
||
"""
|
||
old = MetaMusic.from_dict({"title": "晴天"})
|
||
assert old.apply_words == []
|
||
changed = MetaMusic(title="晴天")
|
||
changed.apply_words = ["错误曲名 => 晴天"]
|
||
assert old.apply_words == []
|
||
|
||
|
||
@pytest.mark.parametrize("suffix", ["mp2", "tta", "flac"])
|
||
def test_music_recognition_words_keep_original_file_format(suffix):
|
||
"""
|
||
识别词可能删掉文件后缀,但不能丢失或覆盖文件原本声明的音频格式。
|
||
"""
|
||
meta = MetaInfo(f"周杰伦 - 错误曲名.{suffix}", custom_words=[f"错误曲名\\.{suffix} => 晴天"])
|
||
assert meta.title == "晴天"
|
||
assert meta.audio_format == suffix.upper()
|
||
|
||
|
||
def test_music_artist_in_subtitle_cannot_override_conflicting_title_credit():
|
||
"""
|
||
另一位艺人的同名歌曲不能借副标题出现目标艺人而成为精确命中。
|
||
"""
|
||
music = MusicInfo(title="One", artists=["U2"])
|
||
assert match_music_resource(music, "Metallica - One FLAC", "Related artist: U2").reason == "artist_unverified"
|
||
|
||
|
||
def test_artist_name_is_not_a_recording_version():
|
||
"""
|
||
艺人名称含 Live 时,不能把署名误当作现场录音标记。
|
||
"""
|
||
music = MusicInfo(title="Song", artists=["Live"])
|
||
assert match_music_resource(music, "Live - Song FLAC").status == "exact"
|
||
|
||
|
||
def test_compilation_album_credit_does_not_prove_track_artist():
|
||
"""
|
||
单曲已经有明确艺人时,整专的合辑署名不构成同一录音的身份依据。
|
||
"""
|
||
music = MusicInfo(title="Song", artists=["Artist"], album_artist="Various Artists")
|
||
assert match_music_resource(music, "VA - Song FLAC").status == "candidate"
|
||
|
||
|
||
def test_distinct_album_artist_is_not_a_recording_artist_alias():
|
||
"""
|
||
演唱者与专辑艺术家分属不同实体时,不把专辑署名视为演唱者别名。
|
||
"""
|
||
music = MusicInfo(title="Song", artists=["Performer"], album_artist="Album Artist")
|
||
assert match_music_resource(music, "Album Artist - Song FLAC").status == "candidate"
|
||
|
||
|
||
def test_simplification_does_not_merge_recording_and_album_artist_aliases():
|
||
"""
|
||
文本展示转换不能把另一位专辑艺术家混入录音艺术家别名。
|
||
"""
|
||
original = MusicInfo(title="晴天", artists=["周杰倫"], album_artist="周華健")
|
||
simplified = MediaChain._simplify_recognized_music_info(original)
|
||
assert simplified.artist_aliases == ["周杰倫"]
|
||
|
||
|
||
@pytest.mark.parametrize("artist", ["AC/DC", "Earth, Wind & Fire"])
|
||
def test_compound_artist_credit_preserves_name_punctuation(artist):
|
||
"""
|
||
已知完整艺名含分隔符时,不能因资源解析拆段而误判为另一位艺人。
|
||
"""
|
||
music = MusicInfo(title="Song", artists=[artist])
|
||
assert match_music_resource(music, f"{artist} - Song FLAC").status == "exact"
|
||
|
||
|
||
@pytest.mark.parametrize("python_only", [False, True])
|
||
def test_resource_evidence_matches_python_and_native_parser_paths(monkeypatch, python_only):
|
||
"""
|
||
资源级补充规则必须同时适用于 Python 回退和真实 Rust 标题解析。
|
||
"""
|
||
if python_only:
|
||
monkeypatch.setattr("app.domain.meta.metamusic.get_metainfo_accelerator", lambda: None)
|
||
else:
|
||
accelerator = get_metainfo_accelerator()
|
||
if not accelerator or not accelerator.parse_metamusic("Artist - Song FLAC"):
|
||
pytest.skip("当前环境没有可用的 Rust 音乐解析器")
|
||
meta = MetaMusic.parse_resource("Artist - Album - 01 - Song FLAC", "24bit 96kHz")
|
||
assert (meta.title, meta.album, meta.track_number) == ("Song", "Album", 1)
|
||
assert meta.artists == ["Artist"]
|
||
assert (meta.bit_depth, meta.sample_rate) == (24, 96000)
|
||
bracketed = MetaMusic.parse_resource("【永遠・是朋友】24bit/96kHz", "專輯藝人:周華健")
|
||
assert bracketed.title and "永遠" in bracketed.title
|
||
assert bracketed.artists == ["周華健"]
|
||
live = MetaInfo("周杰伦 - 错误曲名.flac", "版本:Live", mtype=MediaType.MUSIC,
|
||
custom_words=["错误曲名 => 晴天"])
|
||
assert (live.title, live.version, live.audio_format) == ("晴天", "Live", "FLAC")
|
||
assert live.apply_words == ["错误曲名 => 晴天"]
|
||
album = MusicInfo(music_type="album", title="叶惠美", artists=["周杰伦"])
|
||
assert match_music_resource(album, "周杰伦 - 晴天 FLAC", "专辑:叶惠美").reason == "partial_album"
|
||
query = MetaMusic.parse_query("周杰伦 - 晴天 [Live] FLAC")
|
||
assert query.version == "Live"
|
||
filename = MetaInfoPath(Path("/music/周杰伦/叶惠美/晴天 [Live].flac"))
|
||
assert filename.version == "Live"
|
||
|
||
|
||
@pytest.mark.parametrize("factory", [MusicInfo, MusicAlbumInfo])
|
||
def test_old_music_cache_restores_alias_defaults(factory):
|
||
"""
|
||
旧 pickle 没有新增字段时应恢复空列表,继续支持标准序列化和专辑投影。
|
||
"""
|
||
info = factory(title="Album", artists=["Artist"])
|
||
for field in ("title_aliases", "artist_aliases", "album_aliases"):
|
||
info.__dict__.pop(field, None)
|
||
restored = pickle.loads(pickle.dumps(info))
|
||
assert restored.to_dict()["title_aliases"] == []
|
||
if isinstance(restored, MusicAlbumInfo):
|
||
assert restored.to_music_info().artist_aliases == []
|
||
|
||
|
||
def test_clearing_music_context_does_not_mutate_source_snapshot():
|
||
"""
|
||
共用结果裁剪只清理副本,不能清空模块缓存或调用方仍持有的原始响应。
|
||
"""
|
||
original = MusicInfo(title="Album", raw_data={"id": "source"})
|
||
duplicate = copy.copy(original)
|
||
duplicate.clear()
|
||
assert duplicate.raw_data == {}
|
||
assert original.raw_data == {"id": "source"}
|
||
|
||
|
||
def test_music_simplification_preserves_original_search_names():
|
||
"""
|
||
展示繁转简后必须仍能按原始标题与艺术家检索。
|
||
"""
|
||
original = MusicInfo(music_type="album", title="永遠是朋友", album="永遠是朋友", artists=["周華健"])
|
||
simplified = MediaChain._simplify_recognized_music_info(original)
|
||
keywords = SearchChain.music_site_keywords(simplified)
|
||
assert keywords[:2] == ["永远是朋友", "永遠是朋友"]
|
||
assert "周華健" in simplified.artist_aliases
|
||
assert MusicInfo.from_dict(simplified.to_dict()).title_aliases == ["永遠是朋友"]
|
||
|
||
|
||
def test_music_simplification_prefers_chinese_artist_alias(monkeypatch):
|
||
"""
|
||
开启音乐简体转换时,整理名称应优先使用同一艺人的中文别名。
|
||
"""
|
||
original = MusicInfo(
|
||
title="舞孃",
|
||
artists=["Jolin Tsai"],
|
||
artist_aliases=["Jolin Tsai", "蔡依林"],
|
||
)
|
||
monkeypatch.setattr("app.runtime.config.settings.MUSIC_METADATA_TO_SIMPLIFIED", True)
|
||
|
||
simplified = MediaChain._simplify_recognized_music_info(original)
|
||
|
||
assert simplified.artists == ["蔡依林"]
|
||
assert "Jolin Tsai" in simplified.artist_aliases
|
||
assert original.artists == ["Jolin Tsai"]
|
||
|
||
|
||
def test_music_simplification_keeps_artist_name_when_disabled(monkeypatch):
|
||
"""
|
||
关闭音乐简体转换时,不得因中文别名改变整理使用的艺术家名称。
|
||
"""
|
||
original = MusicInfo(
|
||
title="舞孃",
|
||
artists=["Jolin Tsai"],
|
||
artist_aliases=["Jolin Tsai", "蔡依林"],
|
||
)
|
||
monkeypatch.setattr("app.runtime.config.settings.MUSIC_METADATA_TO_SIMPLIFIED", False)
|
||
|
||
result = MediaChain._simplify_recognized_music_info(original)
|
||
|
||
assert result is original
|
||
assert result.artists == ["Jolin Tsai"]
|
||
|
||
|
||
def test_music_simplification_keeps_multi_artist_aliases_isolated(monkeypatch):
|
||
"""
|
||
多位艺术家同时整理时,中文别名必须按艺术家身份分别匹配。
|
||
"""
|
||
original = MusicInfo(
|
||
title="合作曲",
|
||
artists=["Jolin Tsai", "Jay Chou"],
|
||
artist_ids=["artist-1", "artist-2"],
|
||
artist_aliases=["Jolin Tsai", "蔡依林", "Jay Chou", "周杰伦"],
|
||
raw_data={
|
||
"artist-credit": [
|
||
{
|
||
"name": "Jolin Tsai",
|
||
"artist": {
|
||
"id": "artist-1",
|
||
"name": "Jolin Tsai",
|
||
"aliases": [{"name": "蔡依林"}],
|
||
},
|
||
},
|
||
{
|
||
"name": "Jay Chou",
|
||
"artist": {
|
||
"id": "artist-2",
|
||
"name": "Jay Chou",
|
||
"aliases": [{"name": "周杰伦"}],
|
||
},
|
||
},
|
||
],
|
||
},
|
||
)
|
||
monkeypatch.setattr("app.runtime.config.settings.MUSIC_METADATA_TO_SIMPLIFIED", True)
|
||
|
||
simplified = MediaChain._simplify_recognized_music_info(original)
|
||
|
||
assert simplified.artists == ["蔡依林", "周杰伦"]
|
||
|
||
|
||
def test_music_album_alias_is_used_for_search():
|
||
"""
|
||
匹配接受的作品别名也必须进入实际站点查询阶梯。
|
||
"""
|
||
music = MusicInfo(music_type="album", title="Ye Hui Mei", title_aliases=["叶惠美"], artists=["Jay Chou"])
|
||
assert "叶惠美" in SearchChain.music_site_keywords(music)
|
||
|
||
|
||
def test_automatic_batch_download_excludes_manual_music_candidates():
|
||
"""
|
||
人工候选即使被传入批量入口,也不得自动交给下载器。
|
||
"""
|
||
chain = object.__new__(DownloadChain)
|
||
assert chain._execute_batch_download([Context(match_status="candidate")]) == ([], None)
|
||
assert chain._execute_batch_download([Context(match_status="candidate", match_reason="related_album")]) == ([], None)
|