mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 07:27:15 +08:00
feat(media): aggregate aliases from enabled metadata sources
This commit is contained in:
+154
-72
@@ -19,7 +19,7 @@ from app.domain.context import (
|
||||
MusicArtistInfo,
|
||||
MusicInfo,
|
||||
)
|
||||
from app.domain.media import is_music_media_source
|
||||
from app.domain.media import is_music_media_source, parse_media_source_selection
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.domain.metainfo import MetaInfo, MetaInfoPath
|
||||
@@ -530,48 +530,6 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
logger.warn(f"{metainfo.title} 未识别到媒体信息")
|
||||
return mediainfo
|
||||
|
||||
@staticmethod
|
||||
def _build_tmdb_supplement_meta(
|
||||
mediainfo: MediaInfo,
|
||||
metainfo: Optional[MetaBase] = None,
|
||||
) -> MetaBase:
|
||||
"""
|
||||
根据主识别结果构造 TMDB 辅助识别参数。
|
||||
|
||||
:param mediainfo: 主识别源返回的媒体信息
|
||||
:param metainfo: 原始标题解析信息
|
||||
:return: 不携带主识别源身份的 TMDB 查询参数
|
||||
"""
|
||||
title = mediainfo.title or getattr(metainfo, "name", None) or ""
|
||||
tmdb_meta = MetaInfo(title)
|
||||
if not tmdb_meta.cn_name and getattr(metainfo, "cn_name", None):
|
||||
tmdb_meta.cn_name = metainfo.cn_name
|
||||
if not tmdb_meta.en_name:
|
||||
tmdb_meta.en_name = mediainfo.en_title or (
|
||||
getattr(metainfo, "en_name", None)
|
||||
)
|
||||
tmdb_meta.type = mediainfo.type or (
|
||||
getattr(metainfo, "type", None) or MediaType.UNKNOWN
|
||||
)
|
||||
season = (
|
||||
mediainfo.season
|
||||
if mediainfo.season is not None
|
||||
else getattr(metainfo, "begin_season", None)
|
||||
)
|
||||
tmdb_meta.begin_season = season
|
||||
season_year = None
|
||||
if season is not None and mediainfo.season_years:
|
||||
season_year = (
|
||||
mediainfo.season_years.get(season)
|
||||
or mediainfo.season_years.get(str(season))
|
||||
)
|
||||
tmdb_meta.year = (
|
||||
season_year
|
||||
or mediainfo.year
|
||||
or getattr(metainfo, "year", None)
|
||||
)
|
||||
return tmdb_meta
|
||||
|
||||
@staticmethod
|
||||
def _merge_tmdb_auxiliary(
|
||||
mediainfo: MediaInfo,
|
||||
@@ -602,6 +560,154 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
setattr(mediainfo, field, getattr(tmdb_media, field, None))
|
||||
return mediainfo
|
||||
|
||||
@staticmethod
|
||||
def _build_tmdb_supplement_meta(
|
||||
mediainfo: MediaInfo,
|
||||
metainfo: Optional[MetaBase] = None,
|
||||
) -> MetaBase:
|
||||
"""构造兼容旧调用方的 TMDB 附加识别参数。"""
|
||||
title = mediainfo.title or getattr(metainfo, "name", None) or ""
|
||||
tmdb_meta = MetaInfo(title)
|
||||
tmdb_meta.en_name = mediainfo.en_title or getattr(metainfo, "en_name", None)
|
||||
tmdb_meta.type = mediainfo.type or getattr(metainfo, "type", None) or MediaType.UNKNOWN
|
||||
season = (
|
||||
mediainfo.season
|
||||
if mediainfo.season is not None
|
||||
else getattr(metainfo, "begin_season", None)
|
||||
)
|
||||
tmdb_meta.begin_season = season
|
||||
season_year = None
|
||||
if season is not None and mediainfo.season_years:
|
||||
season_year = (
|
||||
mediainfo.season_years.get(season)
|
||||
or mediainfo.season_years.get(str(season))
|
||||
)
|
||||
tmdb_meta.year = season_year or mediainfo.year or getattr(metainfo, "year", None)
|
||||
return tmdb_meta
|
||||
|
||||
@staticmethod
|
||||
def _media_alias_candidates(mediainfo: object) -> list[str]:
|
||||
"""按稳定字段顺序提取单个来源可参与搜索匹配的标题候选。"""
|
||||
candidates = [
|
||||
getattr(mediainfo, field, None)
|
||||
for field in (
|
||||
"title",
|
||||
"original_title",
|
||||
"en_title",
|
||||
"hk_title",
|
||||
"tw_title",
|
||||
"sg_title",
|
||||
)
|
||||
]
|
||||
candidates.extend(getattr(mediainfo, "names", None) or [])
|
||||
return [str(candidate).strip() for candidate in candidates if str(candidate or "").strip()]
|
||||
|
||||
@classmethod
|
||||
def _merge_media_auxiliary(
|
||||
cls,
|
||||
mediainfo: MediaInfo,
|
||||
auxiliary_medias: Iterable[object],
|
||||
selected_sources: Optional[MediaSourceSelection],
|
||||
) -> MediaInfo:
|
||||
"""合并多来源别名,并仅接受 TMDB 的兼容字段补充。"""
|
||||
aliases = cls._media_alias_candidates(mediainfo)
|
||||
seen_aliases = {" ".join(alias.casefold().split()) for alias in aliases}
|
||||
selected = (
|
||||
{selected_sources}
|
||||
if isinstance(selected_sources, MediaSource)
|
||||
else set(selected_sources or ())
|
||||
)
|
||||
for auxiliary in auxiliary_medias or []:
|
||||
auxiliary_source = normalize_media_source(
|
||||
getattr(auxiliary, "media_source", None)
|
||||
)
|
||||
if not auxiliary_source or (selected and auxiliary_source not in selected):
|
||||
continue
|
||||
for alias in cls._media_alias_candidates(auxiliary):
|
||||
normalized = " ".join(alias.casefold().split())
|
||||
if normalized in seen_aliases:
|
||||
continue
|
||||
aliases.append(alias)
|
||||
seen_aliases.add(normalized)
|
||||
if auxiliary_source == MediaSource.TMDB:
|
||||
cls._merge_tmdb_auxiliary(mediainfo, auxiliary)
|
||||
mediainfo.names = aliases
|
||||
return mediainfo
|
||||
|
||||
@staticmethod
|
||||
def _resolve_auxiliary_sources(
|
||||
media_source: Optional[MediaSourceSelection],
|
||||
) -> Optional[MediaSourceSelection]:
|
||||
"""解析请求级来源,未指定时使用用户配置的影视搜索来源集合。"""
|
||||
if media_source:
|
||||
return media_source
|
||||
configured = get_chain_runtime_config_snapshot().search_source
|
||||
try:
|
||||
return parse_media_source_selection(configured) or None
|
||||
except ValueError as err:
|
||||
logger.warning(f"媒体附加信息来源配置无效,跳过补充:{err}")
|
||||
return ()
|
||||
|
||||
def supplement_media_info(
|
||||
self,
|
||||
mediainfo: Optional[Union[MediaInfo, MusicInfo]],
|
||||
media_source: Optional[MediaSourceSelection] = None,
|
||||
metainfo: Optional[MetaBase] = None,
|
||||
) -> Optional[Union[MediaInfo, MusicInfo]]:
|
||||
"""按用户启用的数据源聚合影视别名和受控的 TMDB 附加字段。"""
|
||||
if (
|
||||
not mediainfo
|
||||
or isinstance(mediainfo, MusicInfo)
|
||||
or mediainfo.type == MediaType.MUSIC
|
||||
):
|
||||
return mediainfo
|
||||
selected_sources = self._resolve_auxiliary_sources(media_source)
|
||||
try:
|
||||
auxiliary_medias = self.run_module(
|
||||
"get_media_auxiliary_info",
|
||||
mediainfo=mediainfo,
|
||||
media_source=selected_sources,
|
||||
metainfo=metainfo,
|
||||
) or []
|
||||
except Exception as err:
|
||||
logger.warning(f"{mediainfo.title_year} 获取媒体附加信息失败:{err}")
|
||||
return mediainfo
|
||||
return self._merge_media_auxiliary(
|
||||
mediainfo,
|
||||
auxiliary_medias,
|
||||
selected_sources,
|
||||
)
|
||||
|
||||
async def async_supplement_media_info(
|
||||
self,
|
||||
mediainfo: Optional[Union[MediaInfo, MusicInfo]],
|
||||
media_source: Optional[MediaSourceSelection] = None,
|
||||
metainfo: Optional[MetaBase] = None,
|
||||
) -> Optional[Union[MediaInfo, MusicInfo]]:
|
||||
"""异步按用户启用的数据源聚合影视别名和受控 TMDB 字段。"""
|
||||
if (
|
||||
not mediainfo
|
||||
or isinstance(mediainfo, MusicInfo)
|
||||
or mediainfo.type == MediaType.MUSIC
|
||||
):
|
||||
return mediainfo
|
||||
selected_sources = self._resolve_auxiliary_sources(media_source)
|
||||
try:
|
||||
auxiliary_medias = await self.async_run_module(
|
||||
"async_get_media_auxiliary_info",
|
||||
mediainfo=mediainfo,
|
||||
media_source=selected_sources,
|
||||
metainfo=metainfo,
|
||||
) or []
|
||||
except Exception as err:
|
||||
logger.warning(f"{mediainfo.title_year} 异步获取媒体附加信息失败:{err}")
|
||||
return mediainfo
|
||||
return self._merge_media_auxiliary(
|
||||
mediainfo,
|
||||
auxiliary_medias,
|
||||
selected_sources,
|
||||
)
|
||||
|
||||
def supplement_tmdb_info(
|
||||
self,
|
||||
mediainfo: Optional[Union[MediaInfo, MusicInfo]],
|
||||
@@ -614,35 +720,11 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
:param metainfo: 原始标题解析信息
|
||||
:return: 已补充 TMDB 辅助字段的原媒体对象
|
||||
"""
|
||||
if not mediainfo:
|
||||
return None
|
||||
# 音乐原样返回:下面全是 TMDB 影视字段,MusicInfo 上根本没有。用 isinstance
|
||||
# 而不只看 type,一来静态检查能据此收窄(.type == 的比较收窄不了类型),二来
|
||||
# type 没被正确赋值的 MusicInfo 也挡得住,不至于到下一行才 AttributeError
|
||||
if isinstance(mediainfo, MusicInfo) or mediainfo.type == MediaType.MUSIC:
|
||||
return mediainfo
|
||||
if mediainfo.tmdb_id and mediainfo.tmdb_info and mediainfo.genre_ids:
|
||||
return mediainfo
|
||||
tmdb_meta = self._build_tmdb_supplement_meta(mediainfo, metainfo)
|
||||
# 按 source 路由走统一 dispatch:宿主识别模块对非自身来源都会快速返回 None,
|
||||
# 指定 TMDB 来源时只有 TheMovieDbModule 会应答,链层无需点名具体模块
|
||||
try:
|
||||
tmdb_media = self.run_module(
|
||||
"recognize_media",
|
||||
meta=tmdb_meta,
|
||||
mtype=mediainfo.type,
|
||||
media_source=MediaSource.TMDB,
|
||||
media_id=str(mediainfo.tmdb_id) if mediainfo.tmdb_id else None,
|
||||
episode_group=mediainfo.episode_group,
|
||||
cache=True,
|
||||
)
|
||||
except Exception as err:
|
||||
logger.warn(f"{mediainfo.title_year} 补充 TMDB 辅助信息失败:{err}")
|
||||
return mediainfo
|
||||
if not tmdb_media:
|
||||
logger.warn(f"{mediainfo.title_year} 未匹配到 TMDB 辅助信息")
|
||||
return mediainfo
|
||||
return self._merge_tmdb_auxiliary(mediainfo, tmdb_media)
|
||||
return self.supplement_media_info(
|
||||
mediainfo=mediainfo,
|
||||
media_source=MediaSource.TMDB,
|
||||
metainfo=metainfo,
|
||||
)
|
||||
|
||||
def _recognize_with_fallback_by_meta(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user