mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-07 16:36:53 +08:00
fix: map Bangumi movie subjects correctly (#6155)
This commit is contained in:
+10
-4
@@ -845,7 +845,7 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
|||||||
tmdbinfo = self._match_tmdb_with_names(
|
tmdbinfo = self._match_tmdb_with_names(
|
||||||
meta_names=meta_names,
|
meta_names=meta_names,
|
||||||
year=year,
|
year=year,
|
||||||
mtype=MediaType.TV,
|
mtype=MediaInfo.get_bangumi_media_type(bangumiinfo),
|
||||||
season=meta.begin_season,
|
season=meta.begin_season,
|
||||||
)
|
)
|
||||||
return tmdbinfo
|
return tmdbinfo
|
||||||
@@ -885,7 +885,10 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
|||||||
year = self._extract_year_from_bangumi(bangumiinfo)
|
year = self._extract_year_from_bangumi(bangumiinfo)
|
||||||
# 使用名称识别豆瓣媒体信息
|
# 使用名称识别豆瓣媒体信息
|
||||||
return self.match_doubaninfo(
|
return self.match_doubaninfo(
|
||||||
name=meta.name, year=year, mtype=MediaType.TV, season=meta.begin_season
|
name=meta.name,
|
||||||
|
year=year,
|
||||||
|
mtype=MediaInfo.get_bangumi_media_type(bangumiinfo),
|
||||||
|
season=meta.begin_season,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -1861,7 +1864,7 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
|||||||
tmdbinfo = await self._async_match_tmdb_with_names(
|
tmdbinfo = await self._async_match_tmdb_with_names(
|
||||||
meta_names=meta_names,
|
meta_names=meta_names,
|
||||||
year=year,
|
year=year,
|
||||||
mtype=MediaType.TV,
|
mtype=MediaInfo.get_bangumi_media_type(bangumiinfo),
|
||||||
season=meta.begin_season,
|
season=meta.begin_season,
|
||||||
)
|
)
|
||||||
return tmdbinfo
|
return tmdbinfo
|
||||||
@@ -1901,6 +1904,9 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
|||||||
year = self._extract_year_from_bangumi(bangumiinfo)
|
year = self._extract_year_from_bangumi(bangumiinfo)
|
||||||
# 使用名称识别豆瓣媒体信息
|
# 使用名称识别豆瓣媒体信息
|
||||||
return await self.async_match_doubaninfo(
|
return await self.async_match_doubaninfo(
|
||||||
name=meta.name, year=year, mtype=MediaType.TV, season=meta.begin_season
|
name=meta.name,
|
||||||
|
year=year,
|
||||||
|
mtype=MediaInfo.get_bangumi_media_type(bangumiinfo),
|
||||||
|
season=meta.begin_season,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|||||||
+21
-2
@@ -9,6 +9,8 @@ from app.core.metainfo import MetaInfo
|
|||||||
from app.schemas.types import MediaType
|
from app.schemas.types import MediaType
|
||||||
from app.utils.string import StringUtils
|
from app.utils.string import StringUtils
|
||||||
|
|
||||||
|
BANGUMI_MOVIE_PLATFORMS = frozenset({"movie", "电影", "剧场版"})
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TorrentInfo:
|
class TorrentInfo:
|
||||||
@@ -243,6 +245,10 @@ class SubtitleInfo:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MediaInfo:
|
class MediaInfo:
|
||||||
|
"""
|
||||||
|
统一媒体信息,负责聚合各元数据源的标准字段
|
||||||
|
"""
|
||||||
|
|
||||||
# 内部标记:是否命中本地识别缓存,不参与序列化
|
# 内部标记:是否命中本地识别缓存,不参与序列化
|
||||||
recognize_cache_hit = False
|
recognize_cache_hit = False
|
||||||
# 来源:themoviedb、douban、bangumi
|
# 来源:themoviedb、douban、bangumi
|
||||||
@@ -721,7 +727,20 @@ class MediaInfo:
|
|||||||
elif type(current_value) is type(value):
|
elif type(current_value) is type(value):
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
def set_bangumi_info(self, info: dict):
|
@staticmethod
|
||||||
|
def get_bangumi_media_type(info: dict) -> MediaType:
|
||||||
|
"""
|
||||||
|
根据Bangumi媒介平台获取标准媒体类型,未知平台兼容回退为电视剧
|
||||||
|
|
||||||
|
:param info: Bangumi条目信息
|
||||||
|
:return: 标准媒体类型
|
||||||
|
"""
|
||||||
|
platform = str(info.get("platform") or "").strip().casefold()
|
||||||
|
if platform in BANGUMI_MOVIE_PLATFORMS:
|
||||||
|
return MediaType.MOVIE
|
||||||
|
return MediaType.TV
|
||||||
|
|
||||||
|
def set_bangumi_info(self, info: dict) -> None:
|
||||||
"""
|
"""
|
||||||
初始化Bangumi信息
|
初始化Bangumi信息
|
||||||
"""
|
"""
|
||||||
@@ -735,7 +754,7 @@ class MediaInfo:
|
|||||||
self.bangumi_id = info.get("id")
|
self.bangumi_id = info.get("id")
|
||||||
# 类型
|
# 类型
|
||||||
if not self.type:
|
if not self.type:
|
||||||
self.type = MediaType.TV
|
self.type = self.get_bangumi_media_type(info)
|
||||||
# 标题
|
# 标题
|
||||||
if not self.title:
|
if not self.title:
|
||||||
self.title = info.get("name_cn") or info.get("name")
|
self.title = info.get("name_cn") or info.get("name")
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import asyncio
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from app.chain.media import MediaChain
|
||||||
|
from app.core.context import MediaInfo
|
||||||
|
from app.schemas.types import MediaType
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("platform", "expected_type"),
|
||||||
|
[
|
||||||
|
("剧场版", MediaType.MOVIE),
|
||||||
|
("Movie", MediaType.MOVIE),
|
||||||
|
("电影", MediaType.MOVIE),
|
||||||
|
("TV", MediaType.TV),
|
||||||
|
("WEB", MediaType.TV),
|
||||||
|
("OVA", MediaType.TV),
|
||||||
|
("未知", MediaType.TV),
|
||||||
|
(None, MediaType.TV),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_bangumi_platform_maps_to_media_type(
|
||||||
|
platform: Optional[str], expected_type: MediaType
|
||||||
|
) -> None:
|
||||||
|
"""Bangumi媒介平台应映射为正确的标准媒体类型。"""
|
||||||
|
media_info = MediaInfo(bangumi_info={"id": 1, "name": "测试条目", "platform": platform})
|
||||||
|
|
||||||
|
assert media_info.type == expected_type
|
||||||
|
|
||||||
|
|
||||||
|
def test_bangumi_media_type_does_not_override_explicit_type() -> None:
|
||||||
|
"""显式媒体类型应优先于Bangumi媒介平台。"""
|
||||||
|
media_info = MediaInfo(
|
||||||
|
type=MediaType.TV,
|
||||||
|
bangumi_info={"id": 1, "name": "测试条目", "platform": "剧场版"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert media_info.type == MediaType.TV
|
||||||
|
|
||||||
|
|
||||||
|
class _SyncBangumiMediaChain:
|
||||||
|
"""同步Bangumi跨数据源转换测试桩。"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""初始化调用参数记录。"""
|
||||||
|
self.tmdb_mtype = None
|
||||||
|
self.douban_mtype = None
|
||||||
|
|
||||||
|
def bangumi_info(self, bangumiid: int) -> dict:
|
||||||
|
"""返回剧场版Bangumi条目信息。"""
|
||||||
|
return {
|
||||||
|
"id": bangumiid,
|
||||||
|
"name": "Movie Test",
|
||||||
|
"name_cn": "电影测试",
|
||||||
|
"date": "2026-01-01",
|
||||||
|
"platform": "剧场版",
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_year_from_bangumi(bangumi_info: dict) -> str:
|
||||||
|
"""返回测试条目的年份。"""
|
||||||
|
return bangumi_info["date"][:4]
|
||||||
|
|
||||||
|
def _match_tmdb_with_names(self, **kwargs) -> dict:
|
||||||
|
"""记录TMDB匹配使用的媒体类型。"""
|
||||||
|
self.tmdb_mtype = kwargs["mtype"]
|
||||||
|
return {"id": 100}
|
||||||
|
|
||||||
|
def match_doubaninfo(self, **kwargs) -> dict:
|
||||||
|
"""记录豆瓣匹配使用的媒体类型。"""
|
||||||
|
self.douban_mtype = kwargs["mtype"]
|
||||||
|
return {"id": "200"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_bangumi_movie_conversion_uses_movie_type() -> None:
|
||||||
|
"""Bangumi剧场版转TMDB和豆瓣时均应按电影匹配。"""
|
||||||
|
chain = _SyncBangumiMediaChain()
|
||||||
|
|
||||||
|
tmdb_info = MediaChain.get_tmdbinfo_by_bangumiid(chain, 1)
|
||||||
|
douban_info = MediaChain.get_doubaninfo_by_bangumiid(chain, 1)
|
||||||
|
|
||||||
|
assert tmdb_info == {"id": 100}
|
||||||
|
assert douban_info == {"id": "200"}
|
||||||
|
assert chain.tmdb_mtype == MediaType.MOVIE
|
||||||
|
assert chain.douban_mtype == MediaType.MOVIE
|
||||||
|
|
||||||
|
|
||||||
|
class _AsyncBangumiMediaChain:
|
||||||
|
"""异步Bangumi跨数据源转换测试桩。"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""初始化调用参数记录。"""
|
||||||
|
self.tmdb_mtype = None
|
||||||
|
self.douban_mtype = None
|
||||||
|
|
||||||
|
async def async_bangumi_info(self, bangumiid: int) -> dict:
|
||||||
|
"""返回剧场版Bangumi条目信息。"""
|
||||||
|
return {
|
||||||
|
"id": bangumiid,
|
||||||
|
"name": "Movie Test",
|
||||||
|
"name_cn": "电影测试",
|
||||||
|
"date": "2026-01-01",
|
||||||
|
"platform": "Movie",
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_year_from_bangumi(bangumi_info: dict) -> str:
|
||||||
|
"""返回测试条目的年份。"""
|
||||||
|
return bangumi_info["date"][:4]
|
||||||
|
|
||||||
|
async def _async_match_tmdb_with_names(self, **kwargs) -> dict:
|
||||||
|
"""记录异步TMDB匹配使用的媒体类型。"""
|
||||||
|
self.tmdb_mtype = kwargs["mtype"]
|
||||||
|
return {"id": 100}
|
||||||
|
|
||||||
|
async def async_match_doubaninfo(self, **kwargs) -> dict:
|
||||||
|
"""记录异步豆瓣匹配使用的媒体类型。"""
|
||||||
|
self.douban_mtype = kwargs["mtype"]
|
||||||
|
return {"id": "200"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_async_bangumi_movie_conversion_uses_movie_type() -> None:
|
||||||
|
"""异步Bangumi电影转换应向TMDB和豆瓣传递电影类型。"""
|
||||||
|
chain = _AsyncBangumiMediaChain()
|
||||||
|
|
||||||
|
tmdb_info = asyncio.run(MediaChain.async_get_tmdbinfo_by_bangumiid(chain, 1))
|
||||||
|
douban_info = asyncio.run(MediaChain.async_get_doubaninfo_by_bangumiid(chain, 1))
|
||||||
|
|
||||||
|
assert tmdb_info == {"id": 100}
|
||||||
|
assert douban_info == {"id": "200"}
|
||||||
|
assert chain.tmdb_mtype == MediaType.MOVIE
|
||||||
|
assert chain.douban_mtype == MediaType.MOVIE
|
||||||
Reference in New Issue
Block a user