feat(media): aggregate aliases from enabled metadata sources

This commit is contained in:
jxxghp
2026-08-26 13:45:03 +08:00
parent ac2a2b1129
commit e02418bace
19 changed files with 727 additions and 125 deletions
+79 -5
View File
@@ -1,5 +1,5 @@
from types import SimpleNamespace
from unittest.mock import Mock
from unittest.mock import Mock, patch
from app.chain.media import MediaChain
from app.domain.context import MediaInfo
@@ -23,11 +23,11 @@ class _FakeTmdbModule:
"""返回模块调度优先级。"""
return 0
def recognize_media(self, **kwargs):
"""与宿主识别模块一致:只应答 TMDB 来源的请求。"""
def get_media_auxiliary_info(self, **kwargs):
"""与宿主附加信息模块一致:只应答 TMDB 来源的请求。"""
if kwargs.get("media_source") != MediaSource.TMDB:
return None
return self.result
return []
return [self.result]
def _make_chain(tmdb_media: MediaInfo) -> MediaChain:
@@ -128,3 +128,77 @@ def test_tmdb_supplement_uses_current_season_year_and_keeps_season_zero() -> Non
assert tmdb_meta.begin_season == 0
assert tmdb_meta.year == "2024"
def test_multi_source_auxiliary_merges_aliases_but_only_tmdb_special_fields() -> None:
"""多来源只合并标题候选,分类、风格和外部 ID 必须由 TMDB 独占。"""
primary = MediaInfo(
media_source=MediaSource.Douban,
media_id="1",
type=MediaType.TV,
title="葬送的芙莉莲",
names=["Frieren"],
)
anilist = MediaInfo(
media_source=MediaSource.AniList,
media_id="154587",
type=MediaType.TV,
title="Sousou no Frieren",
names=["FRIEREN", "Frieren: Beyond Journey's End"],
category="动画冲突分类",
genre_ids=[99],
imdb_id="tt-conflict",
)
tmdb = MediaInfo(
tmdb_info={
"id": 209867,
"media_type": MediaType.TV,
"name": "Frieren: Beyond Journey's End",
"genre_ids": [16, 18],
"external_ids": {"imdb_id": "tt22248376", "tvdb_id": 424536},
}
)
tmdb.category = "日本动画"
result = MediaChain._merge_media_auxiliary(
primary,
[anilist, tmdb],
(MediaSource.AniList, MediaSource.TMDB),
)
assert result.names == [
"葬送的芙莉莲",
"Frieren",
"Sousou no Frieren",
"Frieren: Beyond Journey's End",
]
assert result.category == "日本动画"
assert result.genre_ids == [16, 18]
assert result.imdb_id == "tt22248376"
assert result.tvdb_id == 424536
def test_supplement_media_info_uses_configured_source_union() -> None:
"""未显式传来源时,Chain 应把用户的 SEARCH_SOURCE 多选完整传给 provider。"""
primary = MediaInfo(
media_source=MediaSource.Douban,
media_id="1",
type=MediaType.TV,
title="测试剧",
)
chain = object.__new__(MediaChain)
chain.run_module = Mock(return_value=[])
with patch(
"app.chain.media.get_chain_runtime_config_snapshot",
return_value=SimpleNamespace(search_source="douban,themoviedb,anilist"),
):
result = chain.supplement_media_info(primary)
assert result is primary
chain.run_module.assert_called_once_with(
"get_media_auxiliary_info",
mediainfo=primary,
media_source=(MediaSource.Douban, MediaSource.TMDB, MediaSource.AniList),
metainfo=None,
)