feat: expand metadata sources and media server sync (#6129)

This commit is contained in:
jxxghp
2026-07-21 11:34:18 +08:00
parent 1d708870c9
commit e3f80af74f
43 changed files with 2581 additions and 152 deletions

View File

@@ -0,0 +1,311 @@
from typing import List, Optional, Tuple, Union
from app.core.config import settings
from app.core.context import MediaInfo
from app.core.meta import MetaBase
from app.helper.scraper import MediaScraperHelper
from app.log import logger
from app.modules import _ModuleBase
from app.modules.anilist.anilist import AniListApi
from app.schemas.types import MediaRecognizeType, MediaType, ModuleType
class AniListModule(_ModuleBase):
"""
AniList 动画媒体识别与刮削模块
"""
CONFIG_WATCH = {"PROXY_HOST"}
anilist_api: AniListApi = None
scraper: MediaScraperHelper = None
def init_module(self) -> None:
"""初始化 AniList 客户端与通用刮削器"""
self.anilist_api = AniListApi()
self.scraper = MediaScraperHelper()
def init_setting(self) -> Tuple[str, Union[str, bool]]:
"""AniList 模块无需独立开关"""
return None
def stop(self) -> None:
"""关闭 AniList 模块"""
return None
def test(self) -> Tuple[bool, str]:
"""测试 AniList GraphQL API 连通性"""
result = self.anilist_api.search("Cowboy Bebop", count=1)
return (True, "") if result else (False, "AniList网络连接失败")
@staticmethod
def get_name() -> str:
"""获取模块名称"""
return "AniList"
@staticmethod
def get_type() -> ModuleType:
"""获取模块类型"""
return ModuleType.MediaRecognize
@staticmethod
def get_subtype() -> MediaRecognizeType:
"""获取模块子类型"""
return MediaRecognizeType.AniList
@staticmethod
def get_priority() -> int:
"""获取模块优先级"""
return 4
@staticmethod
def _source_enabled(source: Optional[str]) -> bool:
"""
判断本次识别是否指定 AniList。
:param source: 请求级识别数据源
:return: 是否启用 AniList 识别
"""
return (source or settings.RECOGNIZE_SOURCE) == "anilist"
@staticmethod
def _media_type(info: dict) -> MediaType:
"""
将 AniList 发布格式转换为系统媒体类型。
:param info: AniList 媒体信息
:return: 系统媒体类型
"""
return MediaType.MOVIE if info.get("format") == "MOVIE" else MediaType.TV
@classmethod
def _matches_meta(cls, meta: MetaBase, info: dict) -> bool:
"""
判断 AniList 候选项是否符合标题解析出的类型与年份。
:param meta: 标题解析元数据
:param info: AniList 候选项
:return: 是否符合筛选条件
"""
if meta.type in {MediaType.MOVIE, MediaType.TV} and cls._media_type(info) != meta.type:
return False
year = info.get("startDate", {}).get("year") or info.get("seasonYear")
return not meta.year or not year or str(year) == str(meta.year)
@staticmethod
def _enrich_people(info: dict) -> dict:
"""
将 AniList 人物连接转换为统一媒体信息所需的演职员结构。
:param info: AniList 媒体详情
:return: 补充演员和导演后的媒体详情
"""
enriched = dict(info)
actors = []
for edge in info.get("characters", {}).get("edges") or []:
character = edge.get("node") or {}
voice_actors = edge.get("voiceActors") or []
actor = voice_actors[0] if voice_actors else {}
actor_name = actor.get("name", {}).get("full")
if not actor_name:
continue
actors.append(
{
"name": actor_name,
"character": character.get("name", {}).get("full")
or character.get("name", {}).get("native"),
"avatar": {"large": actor.get("image", {}).get("large")},
"url": actor.get("siteUrl"),
}
)
enriched["actors"] = actors
directors = []
for edge in info.get("staff", {}).get("edges") or []:
role = edge.get("role") or ""
if "Director" not in role:
continue
staff = edge.get("node") or {}
directors.append(
{
"name": staff.get("name", {}).get("full"),
"job": role,
"avatar": {"large": staff.get("image", {}).get("large")},
"url": staff.get("siteUrl"),
}
)
enriched["directors"] = directors
return enriched
def recognize_media(
self,
meta: MetaBase = None,
anilistid: Optional[int] = None,
source: Optional[str] = None,
**kwargs,
) -> Optional[MediaInfo]:
"""
按 AniList ID 或标题识别动画媒体信息。
:param meta: 标题解析元数据
:param anilistid: AniList 媒体 ID
:param source: 请求级识别数据源
:return: 统一媒体信息
"""
if not anilistid and (not meta or not self._source_enabled(source)):
return None
info = self.anilist_api.detail(anilistid) if anilistid else self._match_by_meta(meta)
if not info:
return None
mediainfo = MediaInfo(anilist_info=self._enrich_people(info))
if meta and meta.begin_season is not None:
mediainfo.season = meta.begin_season
logger.info(
f"{anilistid or meta.name} AniList识别结果{mediainfo.type.value} "
f"{mediainfo.title_year}"
)
return mediainfo
async def async_recognize_media(
self,
meta: MetaBase = None,
anilistid: Optional[int] = None,
source: Optional[str] = None,
**kwargs,
) -> Optional[MediaInfo]:
"""
异步按 AniList ID 或标题识别动画媒体信息。
:param meta: 标题解析元数据
:param anilistid: AniList 媒体 ID
:param source: 请求级识别数据源
:return: 统一媒体信息
"""
if not anilistid and (not meta or not self._source_enabled(source)):
return None
info = (
await self.anilist_api.async_detail(anilistid)
if anilistid
else await self._async_match_by_meta(meta)
)
if not info:
return None
mediainfo = MediaInfo(anilist_info=self._enrich_people(info))
if meta and meta.begin_season is not None:
mediainfo.season = meta.begin_season
logger.info(
f"{anilistid or meta.name} AniList识别结果{mediainfo.type.value} "
f"{mediainfo.title_year}"
)
return mediainfo
def _match_by_meta(self, meta: MetaBase) -> Optional[dict]:
"""
同步搜索并筛选最符合标题解析结果的 AniList 条目。
:param meta: 标题解析元数据
:return: AniList 媒体详情
"""
for info in self.anilist_api.search(meta.name):
if self._matches_meta(meta, info):
return info
return None
async def _async_match_by_meta(self, meta: MetaBase) -> Optional[dict]:
"""
异步搜索并筛选最符合标题解析结果的 AniList 条目。
:param meta: 标题解析元数据
:return: AniList 媒体详情
"""
for info in await self.anilist_api.async_search(meta.name):
if self._matches_meta(meta, info):
return info
return None
def search_medias(
self, meta: MetaBase, source: Optional[str] = None
) -> Optional[List[MediaInfo]]:
"""
搜索 AniList 动画媒体信息。
:param meta: 标题解析元数据
:param source: 请求级搜索数据源
:return: 统一媒体信息列表
"""
if source and source != "anilist":
return None
if not source and settings.SEARCH_SOURCE and "anilist" not in settings.SEARCH_SOURCE:
return None
if not meta or not meta.name:
return []
return [
MediaInfo(anilist_info=self._enrich_people(info))
for info in self.anilist_api.search(meta.name)
if self._matches_meta(meta, info)
]
async def async_search_medias(
self, meta: MetaBase, source: Optional[str] = None
) -> Optional[List[MediaInfo]]:
"""
异步搜索 AniList 动画媒体信息。
:param meta: 标题解析元数据
:param source: 请求级搜索数据源
:return: 统一媒体信息列表
"""
if source and source != "anilist":
return None
if not source and settings.SEARCH_SOURCE and "anilist" not in settings.SEARCH_SOURCE:
return None
if not meta or not meta.name:
return []
return [
MediaInfo(anilist_info=self._enrich_people(info))
for info in await self.anilist_api.async_search(meta.name)
if self._matches_meta(meta, info)
]
def metadata_nfo(
self,
mediainfo: MediaInfo,
season: Optional[int] = None,
episode: Optional[int] = None,
**kwargs,
) -> Optional[str]:
"""
生成 AniList 来源的 NFO 内容。
:param mediainfo: 统一媒体信息
:param season: 季号
:param episode: 集号
:return: NFO XML 文本
"""
scrape_source = mediainfo.scrape_source or settings.SCRAP_SOURCE
if scrape_source != "anilist":
return None
return self.scraper.get_metadata_nfo(mediainfo, season=season, episode=episode)
def metadata_img(
self,
mediainfo: MediaInfo,
season: Optional[int] = None,
episode: Optional[int] = None,
) -> Optional[dict]:
"""
获取 AniList 来源的刮削图片清单。
:param mediainfo: 统一媒体信息
:param season: 季号
:param episode: 集号
:return: 图片文件名与下载地址映射
"""
scrape_source = mediainfo.scrape_source or settings.SCRAP_SOURCE
if scrape_source != "anilist":
return None
return self.scraper.get_metadata_img(mediainfo, season=season, episode=episode)
def clear_cache(self) -> None:
"""清理 AniList 接口缓存"""
self.anilist_api.clear_cache()

View File

@@ -0,0 +1,185 @@
from typing import Optional
from app.core.cache import cached
from app.core.config import settings
from app.log import logger
from app.utils.http import AsyncRequestUtils, RequestUtils
class AniListApi:
"""
AniList GraphQL API 客户端
"""
_base_url = "https://graphql.anilist.co"
_media_fields = """
id
idMal
title { romaji english native }
format
status
description(asHtml: false)
startDate { year month day }
endDate { year month day }
seasonYear
episodes
duration
countryOfOrigin
coverImage { extraLarge large }
bannerImage
genres
synonyms
averageScore
popularity
isAdult
siteUrl
studios(isMain: true) { nodes { name } }
staff(perPage: 25, sort: [RELEVANCE]) {
edges { role node { name { full } image { large } siteUrl } }
}
characters(perPage: 25, sort: [ROLE]) {
edges {
role
node { name { full native } image { large } siteUrl }
voiceActors(language: JAPANESE, sort: [RELEVANCE]) {
name { full }
image { large }
siteUrl
}
}
}
externalLinks { site url type }
"""
def __init__(self) -> None:
"""初始化同步与异步请求客户端"""
headers = {
"User-Agent": settings.NORMAL_USER_AGENT,
"Accept": "application/json",
"Content-Type": "application/json",
}
self._request = RequestUtils(
proxies=settings.PROXY,
headers=headers,
)
self._async_request = AsyncRequestUtils(
proxies=settings.PROXY,
headers=headers,
)
@staticmethod
def _extract_response(response) -> Optional[dict]:
"""
提取 GraphQL 响应数据并统一处理上游错误。
:param response: HTTP 响应对象
:return: GraphQL data 字段
"""
if response is None or response.status_code != 200:
return None
try:
result = response.json()
except Exception as err:
logger.error(f"解析 AniList 响应失败:{str(err)}")
return None
if result.get("errors"):
logger.warning(f"AniList 接口返回错误:{result.get('errors')}")
return None
return result.get("data")
def _invoke(self, query: str, variables: dict) -> Optional[dict]:
"""
执行同步 GraphQL 请求。
:param query: GraphQL 查询
:param variables: 查询变量
:return: GraphQL data 字段
"""
response = self._request.post_res(
self._base_url,
json={"query": query, "variables": variables},
)
return self._extract_response(response)
async def _async_invoke(self, query: str, variables: dict) -> Optional[dict]:
"""
执行异步 GraphQL 请求。
:param query: GraphQL 查询
:param variables: 查询变量
:return: GraphQL data 字段
"""
response = await self._async_request.post_res(
self._base_url,
json={"query": query, "variables": variables},
)
return self._extract_response(response)
@cached(maxsize=settings.CONF.anilist, ttl=settings.CONF.meta, shared_key="get")
def detail(self, anilist_id: int) -> Optional[dict]:
"""
根据 AniList ID 获取动画详情。
:param anilist_id: AniList 媒体 ID
:return: AniList 媒体详情
"""
query = f"query ($id: Int!) {{ Media(id: $id, type: ANIME) {{ {self._media_fields} }} }}"
result = self._invoke(query, {"id": anilist_id})
return result.get("Media") if result else None
@cached(maxsize=settings.CONF.anilist, ttl=settings.CONF.meta, shared_key="get")
async def async_detail(self, anilist_id: int) -> Optional[dict]:
"""
异步根据 AniList ID 获取动画详情。
:param anilist_id: AniList 媒体 ID
:return: AniList 媒体详情
"""
query = f"query ($id: Int!) {{ Media(id: $id, type: ANIME) {{ {self._media_fields} }} }}"
result = await self._async_invoke(query, {"id": anilist_id})
return result.get("Media") if result else None
@cached(maxsize=settings.CONF.anilist, ttl=settings.CONF.meta, shared_key="get")
def search(self, name: str, count: int = 20) -> list[dict]:
"""
按标题搜索 AniList 动画。
:param name: 动画标题
:param count: 返回条数
:return: AniList 媒体列表
"""
query = f"""
query ($search: String!, $count: Int!) {{
Page(page: 1, perPage: $count) {{
media(search: $search, type: ANIME, sort: SEARCH_MATCH) {{ {self._media_fields} }}
}}
}}
"""
result = self._invoke(query, {"search": name, "count": count})
return result.get("Page", {}).get("media") or [] if result else []
@cached(maxsize=settings.CONF.anilist, ttl=settings.CONF.meta, shared_key="get")
async def async_search(self, name: str, count: int = 20) -> list[dict]:
"""
异步按标题搜索 AniList 动画。
:param name: 动画标题
:param count: 返回条数
:return: AniList 媒体列表
"""
query = f"""
query ($search: String!, $count: Int!) {{
Page(page: 1, perPage: $count) {{
media(search: $search, type: ANIME, sort: SEARCH_MATCH) {{ {self._media_fields} }}
}}
}}
"""
result = await self._async_invoke(query, {"search": name, "count": count})
return result.get("Page", {}).get("media") or [] if result else []
def clear_cache(self) -> None:
"""清理 AniList 详情与搜索缓存"""
self.detail.cache_clear()
self.async_detail.cache_clear()
self.search.cache_clear()
self.async_search.cache_clear()

View File

@@ -4,10 +4,11 @@ from app import schemas
from app.core.config import settings
from app.core.context import MediaInfo
from app.core.meta import MetaBase
from app.helper.scraper import MediaScraperHelper
from app.log import logger
from app.modules import _ModuleBase
from app.modules.bangumi.bangumi import BangumiApi
from app.schemas.types import ModuleType, MediaRecognizeType
from app.schemas.types import MediaRecognizeType, MediaType, ModuleType
from app.utils.http import RequestUtils
@@ -18,12 +19,14 @@ class BangumiModule(_ModuleBase):
CONFIG_WATCH = {"PROXY_HOST"}
bangumiapi: BangumiApi = None
scraper: MediaScraperHelper = None
def init_module(self) -> None:
"""
初始化Bangumi客户端
"""
self.bangumiapi = BangumiApi()
self.scraper = MediaScraperHelper()
def stop(self) -> None:
"""
@@ -44,7 +47,8 @@ class BangumiModule(_ModuleBase):
return False, "Bangumi网络连接失败"
def init_setting(self) -> Tuple[str, Union[str, bool]]:
pass
"""Bangumi模块无需独立开关"""
return None
@staticmethod
def get_name() -> str:
@@ -74,59 +78,133 @@ class BangumiModule(_ModuleBase):
"""
return 3
def recognize_media(self, bangumiid: int = None,
**kwargs) -> Optional[MediaInfo]:
def recognize_media(
self,
meta: MetaBase = None,
bangumiid: int = None,
source: Optional[str] = None,
**kwargs,
) -> Optional[MediaInfo]:
"""
识别媒体信息
:param meta: 识别的元数据
:param bangumiid: 识别的Bangumi ID
:param source: 请求级识别数据源
:return: 识别的媒体信息,包括剧集信息
"""
if not bangumiid:
if not bangumiid and (
not meta or (source or settings.RECOGNIZE_SOURCE) != "bangumi"
):
return None
# 直接查询详情
info = self.bangumi_info(bangumiid=bangumiid)
info = (
self.bangumi_info(bangumiid=bangumiid)
if bangumiid
else self._match_by_meta(meta)
)
if info:
# 赋值TMDB信息并返回
info["actors"] = self.bangumiapi.credits(info.get("id"))
mediainfo = MediaInfo(bangumi_info=info)
logger.info(f"{bangumiid} Bangumi识别结果{mediainfo.type.value} "
if meta and meta.begin_season is not None:
mediainfo.season = meta.begin_season
logger.info(f"{bangumiid or meta.name} Bangumi识别结果{mediainfo.type.value} "
f"{mediainfo.title_year}")
return mediainfo
else:
logger.info(f"{bangumiid} 未匹配到Bangumi媒体信息")
logger.info(f"{bangumiid or meta.name} 未匹配到Bangumi媒体信息")
return None
async def async_recognize_media(self, bangumiid: int = None,
**kwargs) -> Optional[MediaInfo]:
async def async_recognize_media(
self,
meta: MetaBase = None,
bangumiid: int = None,
source: Optional[str] = None,
**kwargs,
) -> Optional[MediaInfo]:
"""
识别媒体信息(异步版本)
:param meta: 识别的元数据
:param bangumiid: 识别的Bangumi ID
:param source: 请求级识别数据源
:return: 识别的媒体信息,包括剧集信息
"""
if not bangumiid:
if not bangumiid and (
not meta or (source or settings.RECOGNIZE_SOURCE) != "bangumi"
):
return None
# 直接查询详情
info = await self.async_bangumi_info(bangumiid=bangumiid)
info = (
await self.async_bangumi_info(bangumiid=bangumiid)
if bangumiid
else await self._async_match_by_meta(meta)
)
if info:
# 赋值TMDB信息并返回
info["actors"] = await self.bangumiapi.async_credits(info.get("id"))
mediainfo = MediaInfo(bangumi_info=info)
logger.info(f"{bangumiid} Bangumi识别结果{mediainfo.type.value} "
if meta and meta.begin_season is not None:
mediainfo.season = meta.begin_season
logger.info(f"{bangumiid or meta.name} Bangumi识别结果{mediainfo.type.value} "
f"{mediainfo.title_year}")
return mediainfo
else:
logger.info(f"{bangumiid} 未匹配到Bangumi媒体信息")
logger.info(f"{bangumiid or meta.name} 未匹配到Bangumi媒体信息")
return None
def search_medias(self, meta: MetaBase) -> Optional[List[MediaInfo]]:
@staticmethod
def _matches_meta(meta: MetaBase, info: dict) -> bool:
"""
判断Bangumi候选项是否符合标题解析出的类型与年份。
:param meta: 标题解析元数据
:param info: Bangumi候选项详情
:return: 是否符合筛选条件
"""
if (
meta.type in {MediaType.MOVIE, MediaType.TV}
and MediaInfo.get_bangumi_media_type(info) != meta.type
):
return False
release_date = info.get("date") or info.get("air_date") or ""
return not meta.year or not release_date or release_date[:4] == str(meta.year)
def _match_by_meta(self, meta: MetaBase) -> Optional[dict]:
"""
搜索并获取最符合标题解析结果的Bangumi详情。
:param meta: 标题解析元数据
:return: Bangumi媒体详情
"""
for item in (self.bangumiapi.search(meta.name) or [])[:10]:
info = self.bangumiapi.detail(item.get("id")) if item.get("id") else None
if info and self._matches_meta(meta, info):
return info
return None
async def _async_match_by_meta(self, meta: MetaBase) -> Optional[dict]:
"""
异步搜索并获取最符合标题解析结果的Bangumi详情。
:param meta: 标题解析元数据
:return: Bangumi媒体详情
"""
for item in (await self.bangumiapi.async_search(meta.name) or [])[:10]:
info = await self.bangumiapi.async_detail(item.get("id")) if item.get("id") else None
if info and self._matches_meta(meta, info):
return info
return None
def search_medias(
self, meta: MetaBase, source: Optional[str] = None
) -> Optional[List[MediaInfo]]:
"""
搜索媒体信息
:param meta: 识别的元数据
:reutrn: 媒体信息
:param source: 请求级搜索数据源
:return: 媒体信息
"""
if settings.SEARCH_SOURCE and "bangumi" not in settings.SEARCH_SOURCE:
if source and source != "bangumi":
return None
if not source and settings.SEARCH_SOURCE and "bangumi" not in settings.SEARCH_SOURCE:
return None
if not meta.name:
return []
@@ -137,13 +215,18 @@ class BangumiModule(_ModuleBase):
or meta.name.lower() in str(info.get("name_cn")).lower()]
return []
async def async_search_medias(self, meta: MetaBase) -> Optional[List[MediaInfo]]:
async def async_search_medias(
self, meta: MetaBase, source: Optional[str] = None
) -> Optional[List[MediaInfo]]:
"""
搜索媒体信息(异步版本)
:param meta: 识别的元数据
:reutrn: 媒体信息
:param source: 请求级搜索数据源
:return: 媒体信息
"""
if settings.SEARCH_SOURCE and "bangumi" not in settings.SEARCH_SOURCE:
if source and source != "bangumi":
return None
if not source and settings.SEARCH_SOURCE and "bangumi" not in settings.SEARCH_SOURCE:
return None
if not meta.name:
return []
@@ -176,6 +259,45 @@ class BangumiModule(_ModuleBase):
logger.info(f"开始获取Bangumi信息{bangumiid} ...")
return await self.bangumiapi.async_detail(bangumiid)
def metadata_nfo(
self,
mediainfo: MediaInfo,
season: Optional[int] = None,
episode: Optional[int] = None,
**kwargs,
) -> Optional[str]:
"""
生成Bangumi来源的NFO内容。
:param mediainfo: 统一媒体信息
:param season: 季号
:param episode: 集号
:return: NFO XML文本
"""
scrape_source = mediainfo.scrape_source or settings.SCRAP_SOURCE
if scrape_source != "bangumi":
return None
return self.scraper.get_metadata_nfo(mediainfo, season=season, episode=episode)
def metadata_img(
self,
mediainfo: MediaInfo,
season: Optional[int] = None,
episode: Optional[int] = None,
) -> Optional[dict]:
"""
获取Bangumi来源的刮削图片清单。
:param mediainfo: 统一媒体信息
:param season: 季号
:param episode: 集号
:return: 图片文件名与下载地址映射
"""
scrape_source = mediainfo.scrape_source or settings.SCRAP_SOURCE
if scrape_source != "bangumi":
return None
return self.scraper.get_metadata_img(mediainfo, season=season, episode=episode)
def bangumi_calendar(self) -> Optional[List[MediaInfo]]:
"""
获取Bangumi每日放送
@@ -319,7 +441,7 @@ class BangumiModule(_ModuleBase):
return [MediaInfo(bangumi_info=info) for info in infos]
return []
def clear_cache(self):
def clear_cache(self) -> None:
"""
清除缓存
"""

View File

@@ -127,8 +127,11 @@ class DoubanModule(_ModuleBase):
if not doubanid and not meta:
return None
if meta and not doubanid \
and settings.RECOGNIZE_SOURCE != "douban":
if (
meta
and not doubanid
and (kwargs.get("source") or settings.RECOGNIZE_SOURCE) != "douban"
):
return None
if not meta:
@@ -227,8 +230,11 @@ class DoubanModule(_ModuleBase):
if not doubanid and not meta:
return None
if meta and not doubanid \
and settings.RECOGNIZE_SOURCE != "douban":
if (
meta
and not doubanid
and (kwargs.get("source") or settings.RECOGNIZE_SOURCE) != "douban"
):
return None
if not meta:
@@ -927,13 +933,18 @@ class DoubanModule(_ModuleBase):
return [MediaInfo(douban_info=info) for info in infos.get("subject_collection_items")]
return []
def search_medias(self, meta: MetaBase) -> Optional[List[MediaInfo]]:
def search_medias(
self, meta: MetaBase, source: Optional[str] = None
) -> Optional[List[MediaInfo]]:
"""
搜索媒体信息
:param meta: 识别的元数据
:reutrn: 媒体信息
:param source: 请求级搜索数据源
:return: 媒体信息
"""
if settings.SEARCH_SOURCE and "douban" not in settings.SEARCH_SOURCE:
if source and source != "douban":
return None
if not source and settings.SEARCH_SOURCE and "douban" not in settings.SEARCH_SOURCE:
return None
if not meta.name:
return []
@@ -943,13 +954,18 @@ class DoubanModule(_ModuleBase):
# 返回数据
return self._build_search_medias_result(meta, result.get("items"))
async def async_search_medias(self, meta: MetaBase) -> Optional[List[MediaInfo]]:
async def async_search_medias(
self, meta: MetaBase, source: Optional[str] = None
) -> Optional[List[MediaInfo]]:
"""
搜索媒体信息(异步版本)
:param meta: 识别的元数据
:reutrn: 媒体信息
:param source: 请求级搜索数据源
:return: 媒体信息
"""
if settings.SEARCH_SOURCE and "douban" not in settings.SEARCH_SOURCE:
if source and source != "douban":
return None
if not source and settings.SEARCH_SOURCE and "douban" not in settings.SEARCH_SOURCE:
return None
if not meta.name:
return []
@@ -1147,7 +1163,7 @@ class DoubanModule(_ModuleBase):
:param mediainfo: 媒体信息
:param season: 季号
"""
if settings.SCRAP_SOURCE != "douban":
if (mediainfo.scrape_source or settings.SCRAP_SOURCE) != "douban":
return None
return self.scraper.get_metadata_nfo(mediainfo=mediainfo, season=season)
@@ -1158,7 +1174,7 @@ class DoubanModule(_ModuleBase):
:param season: 季号
:param episode: 集号
"""
if settings.SCRAP_SOURCE != "douban":
if (mediainfo.scrape_source or settings.SCRAP_SOURCE) != "douban":
return None
return self.scraper.get_metadata_img(mediainfo=mediainfo, season=season, episode=episode)
@@ -1169,7 +1185,7 @@ class DoubanModule(_ModuleBase):
:param mediainfo: 媒体信息
:return: None 表示不处理MediaInfo 表示继续处理
"""
if settings.RECOGNIZE_SOURCE != "douban":
if mediainfo.source != "douban" and settings.RECOGNIZE_SOURCE != "douban":
return None
if not mediainfo.douban_id:
return None

View File

@@ -92,14 +92,23 @@ class TheMovieDbModule(_ModuleBase):
pass
@staticmethod
def _validate_recognize_params(meta: MetaBase, tmdbid: Optional[int]) -> bool:
def _validate_recognize_params(
meta: MetaBase,
tmdbid: Optional[int],
source: Optional[str] = None,
) -> bool:
"""
验证识别参数
:param meta: 标题解析元数据
:param tmdbid: TMDB ID
:param source: 请求级识别数据源
:return: 参数是否可用于TMDB识别
"""
if not tmdbid and not meta:
return False
if meta and not tmdbid and settings.RECOGNIZE_SOURCE != "themoviedb":
if meta and not tmdbid and (source or settings.RECOGNIZE_SOURCE) != "themoviedb":
return False
if meta and not meta.name and not tmdbid:
@@ -467,7 +476,7 @@ class TheMovieDbModule(_ModuleBase):
:return: 识别的媒体信息,包括剧集信息
"""
# 验证参数
if not self._validate_recognize_params(meta, tmdbid):
if not self._validate_recognize_params(meta, tmdbid, kwargs.get("source")):
return None
if not meta:
@@ -553,7 +562,7 @@ class TheMovieDbModule(_ModuleBase):
:return: 识别的媒体信息,包括剧集信息
"""
# 验证参数
if not self._validate_recognize_params(meta, tmdbid):
if not self._validate_recognize_params(meta, tmdbid, kwargs.get("source")):
return None
if not meta:
@@ -726,13 +735,18 @@ class TheMovieDbModule(_ModuleBase):
MediaType.TV.value: list(self.category.tv_categorys)
}
def search_medias(self, meta: MetaBase) -> Optional[List[MediaInfo]]:
def search_medias(
self, meta: MetaBase, source: Optional[str] = None
) -> Optional[List[MediaInfo]]:
"""
搜索媒体信息
:param meta: 识别的元数据
:reutrn: 媒体信息列表
:param source: 请求级搜索数据源
:return: 媒体信息列表
"""
if settings.SEARCH_SOURCE and "themoviedb" not in settings.SEARCH_SOURCE:
if source and source != "themoviedb":
return None
if not source and settings.SEARCH_SOURCE and "themoviedb" not in settings.SEARCH_SOURCE:
return None
if not meta.name:
return []
@@ -822,7 +836,7 @@ class TheMovieDbModule(_ModuleBase):
:param season: 季号
:param episode: 集号
"""
if settings.SCRAP_SOURCE != "themoviedb":
if (mediainfo.scrape_source or settings.SCRAP_SOURCE) != "themoviedb":
return None
return self.scraper.get_metadata_nfo(meta=meta, mediainfo=mediainfo, season=season, episode=episode)
@@ -834,7 +848,7 @@ class TheMovieDbModule(_ModuleBase):
:param season: 季号
:param episode: 集号
"""
if settings.SCRAP_SOURCE != "themoviedb":
if (mediainfo.scrape_source or settings.SCRAP_SOURCE) != "themoviedb":
return None
return self.scraper.get_metadata_img(mediainfo=mediainfo, season=season, episode=episode)
@@ -955,7 +969,7 @@ class TheMovieDbModule(_ModuleBase):
:param mediainfo: 媒体信息
:return: None 表示不处理MediaInfo 表示继续处理
"""
if settings.RECOGNIZE_SOURCE != "themoviedb":
if mediainfo.source != "themoviedb" and settings.RECOGNIZE_SOURCE != "themoviedb":
return None
if not mediainfo.tmdb_id:
return mediainfo
@@ -1181,13 +1195,18 @@ class TheMovieDbModule(_ModuleBase):
return []
# 异步方法
async def async_search_medias(self, meta: MetaBase) -> Optional[List[MediaInfo]]:
async def async_search_medias(
self, meta: MetaBase, source: Optional[str] = None
) -> Optional[List[MediaInfo]]:
"""
搜索媒体信息(异步版本)
:param meta: 识别的元数据
:reutrn: 媒体信息列表
:param source: 请求级搜索数据源
:return: 媒体信息列表
"""
if settings.SEARCH_SOURCE and "themoviedb" not in settings.SEARCH_SOURCE:
if source and source != "themoviedb":
return None
if not source and settings.SEARCH_SOURCE and "themoviedb" not in settings.SEARCH_SOURCE:
return None
if not meta.name:
return []