mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-05-13 07:26:45 +00:00
fix: align media recognition fallback and shared reporting
Route title and path lookups through the fallback-aware entrypoints so auxiliary matches can reuse pre-assist keywords without forcing image fetches in lightweight flows. Also reduce noisy agent shutdown logging during cleanup.
This commit is contained in:
@@ -6,7 +6,8 @@ from typing import List, Optional, Type
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.agent.tools.base import MoviePilotTool, ToolChain
|
||||
from app.agent.tools.base import MoviePilotTool
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.search import SearchChain
|
||||
from app.chain.download import DownloadChain
|
||||
from app.core.config import settings
|
||||
@@ -275,7 +276,10 @@ class AddDownloadTool(MoviePilotTool):
|
||||
meta_info = MetaInfo(title=torrent_title, subtitle=torrent_description)
|
||||
media_info = cached_context.media_info if cached_context.media_info else None
|
||||
if not media_info:
|
||||
media_info = await ToolChain().async_recognize_media(meta=meta_info)
|
||||
media_info = await MediaChain().async_recognize_by_meta(
|
||||
meta_info,
|
||||
obtain_images=False,
|
||||
)
|
||||
if not media_info:
|
||||
failed_messages.append(f"{torrent_input} 无法识别媒体信息")
|
||||
continue
|
||||
|
||||
@@ -60,7 +60,10 @@ class RecognizeMediaTool(MoviePilotTool):
|
||||
"message": "文件路径不能为空"
|
||||
}, ensure_ascii=False)
|
||||
|
||||
context = await media_chain.async_recognize_by_path(path)
|
||||
context = await media_chain.async_recognize_by_path(
|
||||
path,
|
||||
obtain_images=False,
|
||||
)
|
||||
if context:
|
||||
return self._format_context_result(context, "文件")
|
||||
else:
|
||||
@@ -73,7 +76,10 @@ class RecognizeMediaTool(MoviePilotTool):
|
||||
elif title:
|
||||
# 种子标题识别
|
||||
metainfo = MetaInfo(title, subtitle)
|
||||
mediainfo = await media_chain.async_recognize_by_meta(metainfo)
|
||||
mediainfo = await media_chain.async_recognize_by_meta(
|
||||
metainfo,
|
||||
obtain_images=False,
|
||||
)
|
||||
if mediainfo:
|
||||
context = Context(meta_info=metainfo, media_info=mediainfo)
|
||||
return self._format_context_result(context, "种子")
|
||||
|
||||
@@ -76,12 +76,17 @@ def add(
|
||||
# 元数据
|
||||
metainfo = MetaInfo(title=torrent_in.title, subtitle=torrent_in.description)
|
||||
# 媒体信息
|
||||
mediainfo = MediaChain().select_recognize_source(
|
||||
log_name=torrent_in.title,
|
||||
log_context=torrent_in.title,
|
||||
native_fn=lambda: MediaChain().recognize_media(meta=metainfo, tmdbid=tmdbid, doubanid=doubanid),
|
||||
plugin_fn=lambda: MediaChain().recognize_help(title=torrent_in.title, org_meta=metainfo)
|
||||
)
|
||||
if tmdbid or doubanid:
|
||||
mediainfo = MediaChain().recognize_media(
|
||||
meta=metainfo,
|
||||
tmdbid=tmdbid,
|
||||
doubanid=doubanid,
|
||||
)
|
||||
else:
|
||||
mediainfo = MediaChain().recognize_by_meta(
|
||||
metainfo,
|
||||
obtain_images=False,
|
||||
)
|
||||
if not mediainfo:
|
||||
return schemas.Response(success=False, message="无法识别媒体信息")
|
||||
# 种子信息
|
||||
|
||||
@@ -202,7 +202,11 @@ async def seasons(mediaid: Optional[str] = None,
|
||||
meta = MetaInfo(title)
|
||||
if year:
|
||||
meta.year = year
|
||||
mediainfo = await MediaChain().async_recognize_media(meta, mtype=MediaType.TV)
|
||||
meta.type = MediaType.TV
|
||||
mediainfo = await MediaChain().async_recognize_by_meta(
|
||||
meta,
|
||||
obtain_images=False,
|
||||
)
|
||||
if mediainfo:
|
||||
if settings.RECOGNIZE_SOURCE == "themoviedb":
|
||||
seasons_info = await TmdbChain().async_tmdb_seasons(tmdbid=mediainfo.tmdb_id)
|
||||
@@ -261,7 +265,10 @@ async def detail(mediaid: str, type_name: str, title: Optional[str] = None, year
|
||||
meta.year = year
|
||||
if mtype:
|
||||
meta.type = mtype
|
||||
mediainfo = await mediachain.async_recognize_media(meta=meta)
|
||||
mediainfo = await mediachain.async_recognize_by_meta(
|
||||
meta,
|
||||
obtain_images=False,
|
||||
)
|
||||
# 识别
|
||||
if mediainfo:
|
||||
await mediachain.async_obtain_images(mediainfo)
|
||||
|
||||
@@ -255,7 +255,10 @@ async def search_by_id_stream(request: Request,
|
||||
if media_season:
|
||||
meta.type = MediaType.TV
|
||||
meta.begin_season = media_season
|
||||
mediainfo = await media_chain.async_recognize_media(meta=meta)
|
||||
mediainfo = await media_chain.async_recognize_by_meta(
|
||||
meta,
|
||||
obtain_images=False,
|
||||
)
|
||||
if mediainfo:
|
||||
if settings.RECOGNIZE_SOURCE == "themoviedb":
|
||||
torrents = search_chain.async_search_by_id_stream(tmdbid=mediainfo.tmdb_id,
|
||||
@@ -388,7 +391,10 @@ async def search_by_id(mediaid: str,
|
||||
if media_season:
|
||||
meta.type = MediaType.TV
|
||||
meta.begin_season = media_season
|
||||
mediainfo = await media_chain.async_recognize_media(meta=meta)
|
||||
mediainfo = await media_chain.async_recognize_by_meta(
|
||||
meta,
|
||||
obtain_images=False,
|
||||
)
|
||||
if mediainfo:
|
||||
if settings.RECOGNIZE_SOURCE == "themoviedb":
|
||||
torrents = await search_chain.async_search_by_id(tmdbid=mediainfo.tmdb_id, mtype=media_type,
|
||||
|
||||
@@ -6,6 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException
|
||||
from starlette.responses import FileResponse, Response
|
||||
|
||||
from app import schemas
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.storage import StorageChain
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.core.config import settings
|
||||
@@ -199,7 +200,10 @@ def rename(fileitem: schemas.FileItem,
|
||||
continue
|
||||
sub_path = Path(f"{fileitem.path}{sub_file.name}")
|
||||
meta = MetaInfoPath(sub_path)
|
||||
mediainfo = transferchain.recognize_media(meta)
|
||||
mediainfo = MediaChain().recognize_by_meta(
|
||||
meta,
|
||||
obtain_images=False,
|
||||
)
|
||||
if not mediainfo:
|
||||
progress.end()
|
||||
return schemas.Response(success=False, message=f"{sub_path.name} 未识别到媒体信息")
|
||||
|
||||
@@ -13,6 +13,7 @@ from fastapi import APIRouter, Body, Depends, HTTPException, Header, Request, Re
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from app import schemas
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.mediaserver import MediaServerChain
|
||||
from app.chain.search import SearchChain
|
||||
from app.chain.system import SystemChain
|
||||
@@ -785,7 +786,10 @@ def ruletest(
|
||||
)
|
||||
|
||||
# 根据标题查询媒体信息
|
||||
media_info = SearchChain().recognize_media(MetaInfo(title=title, subtitle=subtitle))
|
||||
media_info = MediaChain().recognize_by_meta(
|
||||
MetaInfo(title=title, subtitle=subtitle),
|
||||
obtain_images=False,
|
||||
)
|
||||
if not media_info:
|
||||
return schemas.Response(success=False, message="未识别到媒体信息!")
|
||||
|
||||
|
||||
@@ -31,7 +31,10 @@ def query_name(path: str, filetype: str,
|
||||
:param _: Token校验
|
||||
"""
|
||||
meta = MetaInfoPath(Path(path))
|
||||
mediainfo = MediaChain().recognize_media(meta)
|
||||
mediainfo = MediaChain().recognize_by_meta(
|
||||
meta,
|
||||
obtain_images=False,
|
||||
)
|
||||
if not mediainfo:
|
||||
return schemas.Response(success=False, message="未识别到媒体信息")
|
||||
new_path = TransferChain().recommend_name(meta=meta, mediainfo=mediainfo)
|
||||
|
||||
@@ -548,8 +548,12 @@ def arr_series_lookup(term: str, _: Annotated[str, Depends(verify_apikey)], db:
|
||||
seas = list(range(1, int(sea_num) + 1))
|
||||
|
||||
# 根据TVDB查询媒体信息
|
||||
mediainfo = MediaChain().recognize_media(meta=MetaInfo(tvdbinfo.get('name')),
|
||||
mtype=MediaType.TV)
|
||||
meta = MetaInfo(tvdbinfo.get('name'))
|
||||
meta.type = MediaType.TV
|
||||
mediainfo = MediaChain().recognize_by_meta(
|
||||
meta,
|
||||
obtain_images=False,
|
||||
)
|
||||
if not mediainfo:
|
||||
continue
|
||||
# 查询是否存在
|
||||
|
||||
@@ -465,10 +465,12 @@ class ChainBase(metaclass=ABCMeta):
|
||||
bangumiid: Optional[int] = None,
|
||||
episode_group: Optional[str] = None,
|
||||
cache: bool = True,
|
||||
share_meta: MetaBase = None,
|
||||
) -> Optional[MediaInfo]:
|
||||
"""
|
||||
识别媒体信息,不含Fanart图片
|
||||
:param meta: 识别的元数据
|
||||
:param share_meta: 共享识别查询/上报使用的原始元数据
|
||||
:param mtype: 识别的媒体类型,与tmdbid配套
|
||||
:param tmdbid: tmdbid
|
||||
:param doubanid: 豆瓣ID
|
||||
@@ -488,6 +490,7 @@ class ChainBase(metaclass=ABCMeta):
|
||||
bangumiid = None
|
||||
elif not mtype and meta and meta.type in [MediaType.TV, MediaType.MOVIE]:
|
||||
mtype = meta.type
|
||||
share_query_meta = share_meta or meta
|
||||
share_helper = MediaRecognizeShareHelper()
|
||||
with fresh(not cache):
|
||||
mediainfo = self.run_module(
|
||||
@@ -502,12 +505,22 @@ class ChainBase(metaclass=ABCMeta):
|
||||
)
|
||||
if mediainfo:
|
||||
if not mediainfo.recognize_cache_hit:
|
||||
share_helper.report(meta=meta, mediainfo=mediainfo)
|
||||
share_helper.report(
|
||||
meta=meta,
|
||||
mediainfo=mediainfo,
|
||||
keyword_meta=share_query_meta,
|
||||
)
|
||||
return mediainfo
|
||||
|
||||
if self._can_use_media_recognize_share(meta, tmdbid, doubanid, bangumiid):
|
||||
if self._can_use_media_recognize_share(
|
||||
share_query_meta, tmdbid, doubanid, bangumiid
|
||||
):
|
||||
shared_cache_meta = self._snapshot_recognize_cache_meta(meta)
|
||||
shared_item = share_helper.query(meta=meta, mtype=mtype)
|
||||
shared_item = share_helper.query(
|
||||
meta=meta,
|
||||
mtype=mtype,
|
||||
keyword_meta=share_query_meta,
|
||||
)
|
||||
shared_params = share_helper.to_recognize_params(shared_item)
|
||||
if shared_params:
|
||||
with fresh(not cache):
|
||||
@@ -535,10 +548,12 @@ class ChainBase(metaclass=ABCMeta):
|
||||
bangumiid: Optional[int] = None,
|
||||
episode_group: Optional[str] = None,
|
||||
cache: bool = True,
|
||||
share_meta: MetaBase = None,
|
||||
) -> Optional[MediaInfo]:
|
||||
"""
|
||||
识别媒体信息,不含Fanart图片(异步版本)
|
||||
:param meta: 识别的元数据
|
||||
:param share_meta: 共享识别查询/上报使用的原始元数据
|
||||
:param mtype: 识别的媒体类型,与tmdbid配套
|
||||
:param tmdbid: tmdbid
|
||||
:param doubanid: 豆瓣ID
|
||||
@@ -558,6 +573,7 @@ class ChainBase(metaclass=ABCMeta):
|
||||
bangumiid = None
|
||||
elif not mtype and meta and meta.type in [MediaType.TV, MediaType.MOVIE]:
|
||||
mtype = meta.type
|
||||
share_query_meta = share_meta or meta
|
||||
share_helper = MediaRecognizeShareHelper()
|
||||
async with async_fresh(not cache):
|
||||
mediainfo = await self.async_run_module(
|
||||
@@ -572,12 +588,22 @@ class ChainBase(metaclass=ABCMeta):
|
||||
)
|
||||
if mediainfo:
|
||||
if not mediainfo.recognize_cache_hit:
|
||||
await share_helper.async_report(meta=meta, mediainfo=mediainfo)
|
||||
await share_helper.async_report(
|
||||
meta=meta,
|
||||
mediainfo=mediainfo,
|
||||
keyword_meta=share_query_meta,
|
||||
)
|
||||
return mediainfo
|
||||
|
||||
if self._can_use_media_recognize_share(meta, tmdbid, doubanid, bangumiid):
|
||||
if self._can_use_media_recognize_share(
|
||||
share_query_meta, tmdbid, doubanid, bangumiid
|
||||
):
|
||||
shared_cache_meta = self._snapshot_recognize_cache_meta(meta)
|
||||
shared_item = await share_helper.async_query(meta=meta, mtype=mtype)
|
||||
shared_item = await share_helper.async_query(
|
||||
meta=meta,
|
||||
mtype=mtype,
|
||||
keyword_meta=share_query_meta,
|
||||
)
|
||||
shared_params = share_helper.to_recognize_params(shared_item)
|
||||
if shared_params:
|
||||
async with async_fresh(not cache):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
from threading import Lock
|
||||
@@ -447,39 +448,83 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
||||
return mediainfo
|
||||
|
||||
def recognize_by_meta(
|
||||
self, metainfo: MetaBase, episode_group: Optional[str] = None
|
||||
self,
|
||||
metainfo: MetaBase,
|
||||
episode_group: Optional[str] = None,
|
||||
obtain_images: bool = True,
|
||||
) -> Optional[MediaInfo]:
|
||||
"""
|
||||
根据主副标题识别媒体信息
|
||||
"""
|
||||
mediainfo = self._recognize_with_fallback_by_meta(
|
||||
metainfo=metainfo,
|
||||
episode_group=episode_group,
|
||||
obtain_images=obtain_images,
|
||||
)
|
||||
if not mediainfo:
|
||||
logger.warn(f"{metainfo.title} 未识别到媒体信息")
|
||||
return mediainfo
|
||||
|
||||
def _recognize_with_fallback_by_meta(
|
||||
self,
|
||||
metainfo: MetaBase,
|
||||
episode_group: Optional[str] = None,
|
||||
obtain_images: bool = False,
|
||||
) -> Optional[MediaInfo]:
|
||||
"""
|
||||
根据标题识别媒体信息,必要时回退到辅助识别。
|
||||
"""
|
||||
if not metainfo:
|
||||
return None
|
||||
title = metainfo.title
|
||||
share_meta = deepcopy(metainfo)
|
||||
|
||||
def native_recognize() -> Optional[MediaInfo]:
|
||||
return self.recognize_media(
|
||||
meta=metainfo,
|
||||
share_meta=share_meta,
|
||||
episode_group=episode_group,
|
||||
)
|
||||
|
||||
def plugin_recognize() -> Optional[MediaInfo]:
|
||||
return self.recognize_help(
|
||||
title=title,
|
||||
org_meta=metainfo,
|
||||
share_meta=share_meta,
|
||||
episode_group=episode_group,
|
||||
)
|
||||
|
||||
# 按 config 中设置的识别顺序识别
|
||||
mediainfo = self.select_recognize_source(
|
||||
log_name=title,
|
||||
log_context=title,
|
||||
native_fn=lambda: self.recognize_media(
|
||||
meta=metainfo, episode_group=episode_group
|
||||
),
|
||||
plugin_fn=lambda: self.recognize_help(title=title, org_meta=metainfo),
|
||||
native_fn=native_recognize,
|
||||
plugin_fn=plugin_recognize,
|
||||
)
|
||||
if not mediainfo:
|
||||
logger.warn(f"{title} 未识别到媒体信息")
|
||||
return None
|
||||
# 识别成功
|
||||
logger.info(
|
||||
f"{title} 识别到媒体信息:{mediainfo.type.value} {mediainfo.title_year}"
|
||||
)
|
||||
# 更新媒体图片
|
||||
self.obtain_images(mediainfo=mediainfo)
|
||||
# 返回上下文
|
||||
if obtain_images:
|
||||
self.obtain_images(mediainfo=mediainfo)
|
||||
return mediainfo
|
||||
|
||||
def recognize_help(self, title: str, org_meta: MetaBase) -> Optional[MediaInfo]:
|
||||
def recognize_help(
|
||||
self,
|
||||
title: str,
|
||||
org_meta: MetaBase,
|
||||
share_meta: MetaBase = None,
|
||||
episode_group: Optional[str] = None,
|
||||
) -> Optional[MediaInfo]:
|
||||
"""
|
||||
请求辅助识别,返回媒体信息
|
||||
|
||||
:param title: 标题
|
||||
:param org_meta: 原始元数据
|
||||
:param share_meta: 共享识别查询/上报使用的原始元数据
|
||||
:param episode_group: 剧集组
|
||||
"""
|
||||
# 发送请求事件,等待结果
|
||||
result: Event = eventmanager.send_event(
|
||||
@@ -521,10 +566,17 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
||||
if org_meta.begin_season is not None or org_meta.begin_episode is not None:
|
||||
org_meta.type = MediaType.TV
|
||||
# 重新识别
|
||||
return self.recognize_media(meta=org_meta)
|
||||
return self.recognize_media(
|
||||
meta=org_meta,
|
||||
share_meta=share_meta,
|
||||
episode_group=episode_group,
|
||||
)
|
||||
|
||||
def recognize_by_path(
|
||||
self, path: str, episode_group: Optional[str] = None
|
||||
self,
|
||||
path: str,
|
||||
episode_group: Optional[str] = None,
|
||||
obtain_images: bool = True,
|
||||
) -> Optional[Context]:
|
||||
"""
|
||||
根据文件路径识别媒体信息
|
||||
@@ -533,23 +585,14 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
||||
file_path = Path(path)
|
||||
# 元数据
|
||||
file_meta = MetaInfoPath(file_path)
|
||||
# 按 config 中设置的识别顺序识别
|
||||
mediainfo = self.select_recognize_source(
|
||||
log_name=file_path.name,
|
||||
log_context=path,
|
||||
native_fn=lambda: self.recognize_media(
|
||||
meta=file_meta, episode_group=episode_group
|
||||
),
|
||||
plugin_fn=lambda: self.recognize_help(title=path, org_meta=file_meta),
|
||||
mediainfo = self._recognize_with_fallback_by_meta(
|
||||
metainfo=file_meta,
|
||||
episode_group=episode_group,
|
||||
obtain_images=obtain_images,
|
||||
)
|
||||
if not mediainfo:
|
||||
logger.warn(f"{path} 未识别到媒体信息")
|
||||
return Context(meta_info=file_meta)
|
||||
logger.info(
|
||||
f"{path} 识别到媒体信息:{mediainfo.type.value} {mediainfo.title_year}"
|
||||
)
|
||||
# 更新媒体图片
|
||||
self.obtain_images(mediainfo=mediainfo)
|
||||
# 返回上下文
|
||||
return Context(meta_info=file_meta, media_info=mediainfo)
|
||||
|
||||
@@ -1340,21 +1383,51 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
||||
return mediainfo
|
||||
|
||||
async def async_recognize_by_meta(
|
||||
self, metainfo: MetaBase, episode_group: Optional[str] = None
|
||||
self,
|
||||
metainfo: MetaBase,
|
||||
episode_group: Optional[str] = None,
|
||||
obtain_images: bool = True,
|
||||
) -> Optional[MediaInfo]:
|
||||
"""
|
||||
根据主副标题识别媒体信息(异步版本)
|
||||
"""
|
||||
title = metainfo.title
|
||||
mediainfo = await self._async_recognize_with_fallback_by_meta(
|
||||
metainfo=metainfo,
|
||||
episode_group=episode_group,
|
||||
obtain_images=obtain_images,
|
||||
)
|
||||
if not mediainfo:
|
||||
logger.warn(f"{metainfo.title} 未识别到媒体信息")
|
||||
return mediainfo
|
||||
|
||||
async def _async_recognize_with_fallback_by_meta(
|
||||
self,
|
||||
metainfo: MetaBase,
|
||||
episode_group: Optional[str] = None,
|
||||
obtain_images: bool = False,
|
||||
) -> Optional[MediaInfo]:
|
||||
"""
|
||||
异步根据标题识别媒体信息,必要时回退到辅助识别。
|
||||
"""
|
||||
if not metainfo:
|
||||
return None
|
||||
title = metainfo.title
|
||||
share_meta = deepcopy(metainfo)
|
||||
|
||||
# 定义识别函数
|
||||
async def native_recognize():
|
||||
return await self.async_recognize_media(
|
||||
meta=metainfo, episode_group=episode_group
|
||||
meta=metainfo,
|
||||
share_meta=share_meta,
|
||||
episode_group=episode_group,
|
||||
)
|
||||
|
||||
async def plugin_recognize():
|
||||
return await self.async_recognize_help(title=title, org_meta=metainfo)
|
||||
return await self.async_recognize_help(
|
||||
title=title,
|
||||
org_meta=metainfo,
|
||||
share_meta=share_meta,
|
||||
episode_group=episode_group,
|
||||
)
|
||||
|
||||
# 按 config 中设置的识别顺序识别
|
||||
mediainfo = await self.async_select_recognize_source(
|
||||
@@ -1364,25 +1437,28 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
||||
plugin_fn=plugin_recognize,
|
||||
)
|
||||
if not mediainfo:
|
||||
logger.warn(f"{title} 未识别到媒体信息")
|
||||
return None
|
||||
# 识别成功
|
||||
logger.info(
|
||||
f"{title} 识别到媒体信息:{mediainfo.type.value} {mediainfo.title_year}"
|
||||
)
|
||||
# 更新媒体图片
|
||||
await self.async_obtain_images(mediainfo=mediainfo)
|
||||
# 返回上下文
|
||||
if obtain_images:
|
||||
await self.async_obtain_images(mediainfo=mediainfo)
|
||||
return mediainfo
|
||||
|
||||
async def async_recognize_help(
|
||||
self, title: str, org_meta: MetaBase
|
||||
self,
|
||||
title: str,
|
||||
org_meta: MetaBase,
|
||||
share_meta: MetaBase = None,
|
||||
episode_group: Optional[str] = None,
|
||||
) -> Optional[MediaInfo]:
|
||||
"""
|
||||
请求辅助识别,返回媒体信息(异步版本)
|
||||
|
||||
:param title: 标题
|
||||
:param org_meta: 原始元数据
|
||||
:param share_meta: 共享识别查询/上报使用的原始元数据
|
||||
:param episode_group: 剧集组
|
||||
"""
|
||||
# 发送请求事件,等待结果
|
||||
result: Event = await eventmanager.async_send_event(
|
||||
@@ -1424,10 +1500,17 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
||||
if org_meta.begin_season or org_meta.begin_episode:
|
||||
org_meta.type = MediaType.TV
|
||||
# 重新识别
|
||||
return await self.async_recognize_media(meta=org_meta)
|
||||
return await self.async_recognize_media(
|
||||
meta=org_meta,
|
||||
share_meta=share_meta,
|
||||
episode_group=episode_group,
|
||||
)
|
||||
|
||||
async def async_recognize_by_path(
|
||||
self, path: str, episode_group: Optional[str] = None
|
||||
self,
|
||||
path: str,
|
||||
episode_group: Optional[str] = None,
|
||||
obtain_images: bool = True,
|
||||
) -> Optional[Context]:
|
||||
"""
|
||||
根据文件路径识别媒体信息(异步版本)
|
||||
@@ -1436,31 +1519,14 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
||||
file_path = Path(path)
|
||||
# 元数据
|
||||
file_meta = MetaInfoPath(file_path)
|
||||
|
||||
# 定义识别函数
|
||||
async def native_recognize():
|
||||
return await self.async_recognize_media(
|
||||
meta=file_meta, episode_group=episode_group
|
||||
)
|
||||
|
||||
async def plugin_recognize():
|
||||
return await self.async_recognize_help(title=path, org_meta=file_meta)
|
||||
|
||||
# 按 config 中设置的识别顺序识别
|
||||
mediainfo = await self.async_select_recognize_source(
|
||||
log_name=file_path.name,
|
||||
log_context=path,
|
||||
native_fn=native_recognize,
|
||||
plugin_fn=plugin_recognize,
|
||||
mediainfo = await self._async_recognize_with_fallback_by_meta(
|
||||
metainfo=file_meta,
|
||||
episode_group=episode_group,
|
||||
obtain_images=obtain_images,
|
||||
)
|
||||
if not mediainfo:
|
||||
logger.warn(f"{path} 未识别到媒体信息")
|
||||
return Context(meta_info=file_meta)
|
||||
logger.info(
|
||||
f"{path} 识别到媒体信息:{mediainfo.type.value} {mediainfo.title_year}"
|
||||
)
|
||||
# 更新媒体图片
|
||||
await self.async_obtain_images(mediainfo=mediainfo)
|
||||
# 返回上下文
|
||||
return Context(meta_info=file_meta, media_info=mediainfo)
|
||||
|
||||
|
||||
@@ -194,7 +194,11 @@ class SubscribeChain(ChainBase):
|
||||
|
||||
# 使用名称识别兜底
|
||||
if not mediainfo:
|
||||
mediainfo = self.recognize_media(meta=metainfo, episode_group=episode_group)
|
||||
mediainfo = MediaChain().recognize_by_meta(
|
||||
metainfo,
|
||||
episode_group=episode_group,
|
||||
obtain_images=False,
|
||||
)
|
||||
|
||||
# 识别失败
|
||||
if not mediainfo:
|
||||
@@ -371,7 +375,11 @@ class SubscribeChain(ChainBase):
|
||||
|
||||
# 使用名称识别兜底
|
||||
if not mediainfo:
|
||||
mediainfo = await self.async_recognize_media(meta=metainfo, episode_group=episode_group)
|
||||
mediainfo = await MediaChain().async_recognize_by_meta(
|
||||
metainfo,
|
||||
episode_group=episode_group,
|
||||
obtain_images=False,
|
||||
)
|
||||
|
||||
# 识别失败
|
||||
if not mediainfo:
|
||||
@@ -827,7 +835,10 @@ class SubscribeChain(ChainBase):
|
||||
and not context.media_info.douban_id)) and context.media_recognize_fail_count < 3:
|
||||
logger.debug(
|
||||
f'尝试重新识别种子:{context.torrent_info.title},当前失败次数:{context.media_recognize_fail_count}/3')
|
||||
re_mediainfo = self.recognize_media(meta=context.meta_info)
|
||||
re_mediainfo = MediaChain().recognize_by_meta(
|
||||
context.meta_info,
|
||||
obtain_images=False,
|
||||
)
|
||||
if re_mediainfo:
|
||||
# 清理多余信息
|
||||
re_mediainfo.clear()
|
||||
@@ -939,8 +950,11 @@ class SubscribeChain(ChainBase):
|
||||
# 更新元数据缓存
|
||||
_context.meta_info = torrent_meta
|
||||
# 重新识别媒体信息
|
||||
torrent_mediainfo = self.recognize_media(meta=torrent_meta,
|
||||
episode_group=subscribe.episode_group)
|
||||
torrent_mediainfo = MediaChain().recognize_by_meta(
|
||||
torrent_meta,
|
||||
episode_group=subscribe.episode_group,
|
||||
obtain_images=False,
|
||||
)
|
||||
if torrent_mediainfo:
|
||||
# 清理多余信息
|
||||
torrent_mediainfo.clear()
|
||||
|
||||
@@ -278,7 +278,10 @@ class TorrentsChain(ChainBase):
|
||||
and torrent.category == MediaType.TV.value:
|
||||
meta.type = MediaType.TV
|
||||
# 识别媒体信息
|
||||
mediainfo: MediaInfo = MediaChain().recognize_by_meta(meta)
|
||||
mediainfo: MediaInfo = MediaChain().recognize_by_meta(
|
||||
meta,
|
||||
obtain_images=False,
|
||||
)
|
||||
if not mediainfo:
|
||||
logger.warn(f'{torrent.title} 未识别到媒体信息')
|
||||
# 存储空的媒体信息
|
||||
|
||||
@@ -1289,7 +1289,10 @@ class TransferChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
||||
mediainfo.category = download_history.media_category
|
||||
else:
|
||||
# 识别媒体信息
|
||||
mediainfo = MediaChain().recognize_by_meta(task.meta)
|
||||
mediainfo = MediaChain().recognize_by_meta(
|
||||
task.meta,
|
||||
obtain_images=False,
|
||||
)
|
||||
|
||||
# 更新媒体图片
|
||||
if mediainfo:
|
||||
@@ -2269,9 +2272,12 @@ class TransferChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
|
||||
# 更新媒体图片
|
||||
self.obtain_images(mediainfo=mediainfo)
|
||||
else:
|
||||
mediainfo = MediaChain().recognize_by_path(
|
||||
str(src_path), episode_group=history.episode_group
|
||||
recognize_context = MediaChain().recognize_by_path(
|
||||
str(src_path),
|
||||
episode_group=history.episode_group,
|
||||
obtain_images=False,
|
||||
)
|
||||
mediainfo = recognize_context.media_info if recognize_context else None
|
||||
if not mediainfo:
|
||||
return False, f"未识别到媒体信息,类型:{mtype.value},id:{mediaid}"
|
||||
# 重新执行整理
|
||||
|
||||
@@ -114,12 +114,15 @@ class MediaRecognizeShareHelper(metaclass=WeakSingleton):
|
||||
|
||||
@classmethod
|
||||
def _build_query_params(
|
||||
cls, meta: Optional[MetaBase], mtype: Optional[MediaType] = None
|
||||
cls,
|
||||
meta: Optional[MetaBase],
|
||||
mtype: Optional[MediaType] = None,
|
||||
keyword_meta: Optional[MetaBase] = None,
|
||||
) -> Optional[dict]:
|
||||
"""
|
||||
组装共享识别查询参数
|
||||
"""
|
||||
keyword = cls._extract_keyword(meta)
|
||||
keyword = cls._extract_keyword(keyword_meta or meta)
|
||||
if not keyword:
|
||||
return None
|
||||
|
||||
@@ -137,7 +140,10 @@ class MediaRecognizeShareHelper(metaclass=WeakSingleton):
|
||||
|
||||
@classmethod
|
||||
def _build_report_payload(
|
||||
cls, meta: Optional[MetaBase], mediainfo: Optional[MediaInfo]
|
||||
cls,
|
||||
meta: Optional[MetaBase],
|
||||
mediainfo: Optional[MediaInfo],
|
||||
keyword_meta: Optional[MetaBase] = None,
|
||||
) -> Optional[dict]:
|
||||
"""
|
||||
组装共享识别上报载荷
|
||||
@@ -145,7 +151,7 @@ class MediaRecognizeShareHelper(metaclass=WeakSingleton):
|
||||
if not meta or not mediainfo:
|
||||
return None
|
||||
|
||||
keyword = cls._extract_keyword(meta)
|
||||
keyword = cls._extract_keyword(keyword_meta or meta)
|
||||
media_type = cls._extract_media_type(meta=meta, mediainfo=mediainfo)
|
||||
if not keyword or not media_type:
|
||||
return None
|
||||
@@ -197,7 +203,12 @@ class MediaRecognizeShareHelper(metaclass=WeakSingleton):
|
||||
"""
|
||||
return bool(settings.MEDIA_RECOGNIZE_SHARE)
|
||||
|
||||
def query(self, meta: Optional[MetaBase], mtype: Optional[MediaType] = None) -> Optional[dict]:
|
||||
def query(
|
||||
self,
|
||||
meta: Optional[MetaBase],
|
||||
mtype: Optional[MediaType] = None,
|
||||
keyword_meta: Optional[MetaBase] = None,
|
||||
) -> Optional[dict]:
|
||||
"""
|
||||
查询共享识别结果
|
||||
"""
|
||||
@@ -205,7 +216,11 @@ class MediaRecognizeShareHelper(metaclass=WeakSingleton):
|
||||
return None
|
||||
|
||||
api_url = self._build_api_url()
|
||||
params = self._build_query_params(meta=meta, mtype=mtype)
|
||||
params = self._build_query_params(
|
||||
meta=meta,
|
||||
mtype=mtype,
|
||||
keyword_meta=keyword_meta,
|
||||
)
|
||||
if not api_url or not params:
|
||||
return None
|
||||
|
||||
@@ -236,7 +251,10 @@ class MediaRecognizeShareHelper(metaclass=WeakSingleton):
|
||||
return item
|
||||
|
||||
async def async_query(
|
||||
self, meta: Optional[MetaBase], mtype: Optional[MediaType] = None
|
||||
self,
|
||||
meta: Optional[MetaBase],
|
||||
mtype: Optional[MediaType] = None,
|
||||
keyword_meta: Optional[MetaBase] = None,
|
||||
) -> Optional[dict]:
|
||||
"""
|
||||
异步查询共享识别结果
|
||||
@@ -245,7 +263,11 @@ class MediaRecognizeShareHelper(metaclass=WeakSingleton):
|
||||
return None
|
||||
|
||||
api_url = self._build_api_url()
|
||||
params = self._build_query_params(meta=meta, mtype=mtype)
|
||||
params = self._build_query_params(
|
||||
meta=meta,
|
||||
mtype=mtype,
|
||||
keyword_meta=keyword_meta,
|
||||
)
|
||||
if not api_url or not params:
|
||||
return None
|
||||
|
||||
@@ -275,7 +297,12 @@ class MediaRecognizeShareHelper(metaclass=WeakSingleton):
|
||||
logger.info(f"共享媒体识别命中:{params.get('keyword')} - {item}")
|
||||
return item
|
||||
|
||||
def report(self, meta: Optional[MetaBase], mediainfo: Optional[MediaInfo]) -> bool:
|
||||
def report(
|
||||
self,
|
||||
meta: Optional[MetaBase],
|
||||
mediainfo: Optional[MediaInfo],
|
||||
keyword_meta: Optional[MetaBase] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
上报共享识别结果
|
||||
"""
|
||||
@@ -283,7 +310,11 @@ class MediaRecognizeShareHelper(metaclass=WeakSingleton):
|
||||
return False
|
||||
|
||||
api_url = self._build_api_url()
|
||||
payload = self._build_report_payload(meta=meta, mediainfo=mediainfo)
|
||||
payload = self._build_report_payload(
|
||||
meta=meta,
|
||||
mediainfo=mediainfo,
|
||||
keyword_meta=keyword_meta,
|
||||
)
|
||||
if not api_url or not payload:
|
||||
return False
|
||||
|
||||
@@ -309,7 +340,10 @@ class MediaRecognizeShareHelper(metaclass=WeakSingleton):
|
||||
return result.get("code") == 0
|
||||
|
||||
async def async_report(
|
||||
self, meta: Optional[MetaBase], mediainfo: Optional[MediaInfo]
|
||||
self,
|
||||
meta: Optional[MetaBase],
|
||||
mediainfo: Optional[MediaInfo],
|
||||
keyword_meta: Optional[MetaBase] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
异步上报共享识别结果
|
||||
@@ -318,7 +352,11 @@ class MediaRecognizeShareHelper(metaclass=WeakSingleton):
|
||||
return False
|
||||
|
||||
api_url = self._build_api_url()
|
||||
payload = self._build_report_payload(meta=meta, mediainfo=mediainfo)
|
||||
payload = self._build_report_payload(
|
||||
meta=meta,
|
||||
mediainfo=mediainfo,
|
||||
keyword_meta=keyword_meta,
|
||||
)
|
||||
if not api_url or not payload:
|
||||
return False
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ class AgentInitializer:
|
||||
logger.info("AI智能体管理器已关闭")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"关闭AI智能体管理器时发生错误: {e}")
|
||||
logger.debug(f"关闭AI智能体管理器时发生错误: {e}")
|
||||
|
||||
|
||||
# 全局AI智能体初始化器实例
|
||||
|
||||
@@ -67,7 +67,10 @@ class AddDownloadAction(BaseAction):
|
||||
if not t.meta_info:
|
||||
t.meta_info = MetaInfo(title=t.torrent_info.title, subtitle=t.torrent_info.description)
|
||||
if not t.media_info:
|
||||
t.media_info = MediaChain().recognize_media(meta=t.meta_info)
|
||||
t.media_info = MediaChain().recognize_by_meta(
|
||||
t.meta_info,
|
||||
obtain_images=False,
|
||||
)
|
||||
if not t.media_info:
|
||||
self._has_error = True
|
||||
logger.warning(f"{t.torrent_info.title} 未识别到媒体信息,无法下载")
|
||||
|
||||
@@ -2,7 +2,8 @@ from typing import Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from app.workflow.actions import BaseAction, ActionChain
|
||||
from app.workflow.actions import BaseAction
|
||||
from app.chain.media import MediaChain
|
||||
from app.core.config import settings, global_vars
|
||||
from app.core.context import Context
|
||||
from app.core.metainfo import MetaInfo
|
||||
@@ -98,7 +99,10 @@ class FetchRssAction(BaseAction):
|
||||
meta = MetaInfo(title=torrentinfo.title, subtitle=torrentinfo.description)
|
||||
mediainfo = None
|
||||
if params.match_media:
|
||||
mediainfo = ActionChain().recognize_media(meta)
|
||||
mediainfo = MediaChain().recognize_by_meta(
|
||||
meta,
|
||||
obtain_images=False,
|
||||
)
|
||||
if not mediainfo:
|
||||
logger.warning(f"{torrentinfo.title} 未识别到媒体信息")
|
||||
continue
|
||||
|
||||
@@ -72,7 +72,10 @@ class FetchTorrentsAction(BaseAction):
|
||||
continue
|
||||
# 识别媒体信息
|
||||
if params.match_media:
|
||||
torrent.media_info = searchchain.recognize_media(torrent.meta_info)
|
||||
torrent.media_info = searchchain.recognize_by_meta(
|
||||
torrent.meta_info,
|
||||
obtain_images=False,
|
||||
)
|
||||
if not torrent.media_info:
|
||||
logger.warning(f"{torrent.torrent_info.title} 未识别到媒体信息")
|
||||
continue
|
||||
|
||||
@@ -65,7 +65,10 @@ class ScrapeFileAction(BaseAction):
|
||||
continue
|
||||
meta = MetaInfoPath(Path(fileitem.path))
|
||||
mediachain = MediaChain()
|
||||
mediainfo = mediachain.recognize_media(meta)
|
||||
mediainfo = mediachain.recognize_by_meta(
|
||||
meta,
|
||||
obtain_images=False,
|
||||
)
|
||||
if not mediainfo:
|
||||
_failed_count += 1
|
||||
logger.info(f"{fileitem.path} 未识别到媒体信息,无法刮削")
|
||||
|
||||
@@ -13,6 +13,8 @@ sys.modules.setdefault("psutil", ModuleType("psutil"))
|
||||
from app.chain import ChainBase
|
||||
from app.core.context import MediaInfo
|
||||
from app.core.meta import MetaBase
|
||||
from app.core.metainfo import MetaInfo
|
||||
from app.chain.media import MediaChain
|
||||
from app.helper.recognize import MediaRecognizeShareHelper
|
||||
from app.schemas.types import MediaType
|
||||
|
||||
@@ -21,6 +23,7 @@ class TestMediaRecognizeShare(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.chain = ChainBase()
|
||||
cls.media_chain = MediaChain()
|
||||
|
||||
@staticmethod
|
||||
def _build_meta(name: str, media_type: MediaType = MediaType.UNKNOWN) -> MetaBase:
|
||||
@@ -204,6 +207,114 @@ class TestMediaRecognizeShare(unittest.TestCase):
|
||||
self.assertEqual(query_params["keyword"], "未应用识别词的名称")
|
||||
self.assertEqual(report_payload["keyword"], "未应用识别词的名称")
|
||||
|
||||
def test_query_and_report_can_use_distinct_keyword_meta(self):
|
||||
"""
|
||||
共享识别应允许用原始关键字上报,同时保留辅助识别后的年份/季信息。
|
||||
"""
|
||||
helper = MediaRecognizeShareHelper()
|
||||
meta = self._build_meta("辅助识别后的名称", MediaType.TV)
|
||||
meta.year = "2024"
|
||||
meta.begin_season = 2
|
||||
|
||||
keyword_meta = self._build_meta("辅助识别前的名称", MediaType.UNKNOWN)
|
||||
keyword_meta.original_name = "辅助识别前的名称"
|
||||
|
||||
mediainfo = MediaInfo(
|
||||
title="测试剧集",
|
||||
year="2024",
|
||||
tmdb_id=401,
|
||||
type=MediaType.TV,
|
||||
season=2,
|
||||
)
|
||||
|
||||
query_params = helper._build_query_params(
|
||||
meta=meta,
|
||||
mtype=None,
|
||||
keyword_meta=keyword_meta,
|
||||
)
|
||||
report_payload = helper._build_report_payload(
|
||||
meta=meta,
|
||||
mediainfo=mediainfo,
|
||||
keyword_meta=keyword_meta,
|
||||
)
|
||||
|
||||
self.assertEqual(query_params["keyword"], "辅助识别前的名称")
|
||||
self.assertEqual(query_params["year"], "2024")
|
||||
self.assertEqual(query_params["season"], 2)
|
||||
self.assertEqual(report_payload["keyword"], "辅助识别前的名称")
|
||||
self.assertEqual(report_payload["year"], "2024")
|
||||
self.assertEqual(report_payload["season"], 2)
|
||||
|
||||
def test_report_shared_result_with_distinct_keyword_meta(self):
|
||||
"""
|
||||
辅助识别成功后应按辅助前名称上报共享结果。
|
||||
"""
|
||||
meta = self._build_meta("辅助识别后的名称", MediaType.TV)
|
||||
meta.year = "2024"
|
||||
meta.begin_season = 1
|
||||
share_meta = self._build_meta("辅助识别前的名称", MediaType.UNKNOWN)
|
||||
share_meta.original_name = "辅助识别前的名称"
|
||||
mediainfo = MediaInfo(title="测试剧集", year="2024", tmdb_id=402, type=MediaType.TV)
|
||||
|
||||
with patch.object(self.chain, "run_module", return_value=mediainfo), patch(
|
||||
"app.chain.MediaRecognizeShareHelper.report",
|
||||
return_value=True,
|
||||
) as report_mock:
|
||||
result = self.chain.recognize_media(meta=meta, share_meta=share_meta, cache=False)
|
||||
|
||||
self.assertIs(result, mediainfo)
|
||||
report_mock.assert_called_once_with(
|
||||
meta=meta,
|
||||
mediainfo=mediainfo,
|
||||
keyword_meta=share_meta,
|
||||
)
|
||||
|
||||
def test_query_shared_result_with_distinct_keyword_meta(self):
|
||||
"""
|
||||
本地识别失败后应按辅助前名称回查共享结果。
|
||||
"""
|
||||
meta = self._build_meta("辅助识别后的名称", MediaType.TV)
|
||||
meta.year = "2024"
|
||||
share_meta = self._build_meta("辅助识别前的名称", MediaType.UNKNOWN)
|
||||
share_meta.original_name = "辅助识别前的名称"
|
||||
shared_media = MediaInfo(title="测试剧集", year="2024", tmdb_id=403, type=MediaType.TV)
|
||||
|
||||
with patch.object(
|
||||
self.chain,
|
||||
"run_module",
|
||||
side_effect=[None, shared_media],
|
||||
), patch(
|
||||
"app.chain.MediaRecognizeShareHelper.query",
|
||||
return_value={"type": "tv", "tmdbid": 403, "season": 1},
|
||||
) as query_mock, patch(
|
||||
"app.chain.MediaRecognizeShareHelper.to_recognize_params",
|
||||
return_value={
|
||||
"mtype": MediaType.TV,
|
||||
"tmdbid": 403,
|
||||
"doubanid": None,
|
||||
"bangumiid": None,
|
||||
"season": 1,
|
||||
},
|
||||
), patch(
|
||||
"app.chain.MediaRecognizeShareHelper.report",
|
||||
return_value=False,
|
||||
), patch.object(
|
||||
self.chain,
|
||||
"_update_local_recognize_cache",
|
||||
):
|
||||
result = self.chain.recognize_media(
|
||||
meta=meta,
|
||||
share_meta=share_meta,
|
||||
cache=False,
|
||||
)
|
||||
|
||||
self.assertIs(result, shared_media)
|
||||
query_mock.assert_called_once_with(
|
||||
meta=meta,
|
||||
mtype=None,
|
||||
keyword_meta=share_meta,
|
||||
)
|
||||
|
||||
def test_skip_report_when_local_recognize_hits_cache(self):
|
||||
"""
|
||||
本地识别命中缓存时不应上报共享识别
|
||||
@@ -255,6 +366,80 @@ class TestMediaRecognizeShare(unittest.TestCase):
|
||||
report_mock.assert_not_awaited()
|
||||
query_mock.assert_not_awaited()
|
||||
|
||||
def test_recognize_by_meta_can_skip_obtain_images(self):
|
||||
"""
|
||||
标题识别可显式关闭图片拉取。
|
||||
"""
|
||||
meta = MetaInfo("测试电影")
|
||||
mediainfo = MediaInfo(title="测试电影", year="2024", tmdb_id=404, type=MediaType.MOVIE)
|
||||
|
||||
with patch.object(
|
||||
self.media_chain,
|
||||
"recognize_media",
|
||||
return_value=mediainfo,
|
||||
) as recognize_mock, patch.object(
|
||||
self.media_chain,
|
||||
"obtain_images",
|
||||
) as obtain_images_mock:
|
||||
result = self.media_chain.recognize_by_meta(meta, obtain_images=False)
|
||||
|
||||
self.assertIs(result, mediainfo)
|
||||
recognize_mock.assert_called_once()
|
||||
obtain_images_mock.assert_not_called()
|
||||
|
||||
def test_recognize_by_meta_reports_with_original_keyword_after_plugin_help(self):
|
||||
"""
|
||||
辅助识别后应继续使用辅助前关键字进行共享上报。
|
||||
"""
|
||||
meta = MetaInfo("辅助前名称")
|
||||
plugin_media = MediaInfo(title="辅助后名称", year="2024", tmdb_id=405, type=MediaType.TV)
|
||||
|
||||
with patch.object(
|
||||
self.media_chain,
|
||||
"select_recognize_source",
|
||||
side_effect=lambda **kwargs: kwargs["plugin_fn"](),
|
||||
), patch.object(
|
||||
self.media_chain,
|
||||
"recognize_help",
|
||||
return_value=plugin_media,
|
||||
) as recognize_help_mock, patch.object(
|
||||
self.media_chain,
|
||||
"obtain_images",
|
||||
):
|
||||
result = self.media_chain.recognize_by_meta(meta, obtain_images=False)
|
||||
|
||||
self.assertIs(result, plugin_media)
|
||||
self.assertEqual(recognize_help_mock.call_args.kwargs["share_meta"].name, "辅助前名称")
|
||||
|
||||
def test_async_recognize_by_meta_can_skip_obtain_images(self):
|
||||
"""
|
||||
异步标题识别可显式关闭图片拉取。
|
||||
"""
|
||||
meta = MetaInfo("测试异步电影")
|
||||
mediainfo = MediaInfo(title="测试异步电影", year="2025", tmdb_id=406, type=MediaType.MOVIE)
|
||||
|
||||
async def runner():
|
||||
with patch.object(
|
||||
self.media_chain,
|
||||
"async_recognize_media",
|
||||
AsyncMock(return_value=mediainfo),
|
||||
) as recognize_mock, patch.object(
|
||||
self.media_chain,
|
||||
"async_obtain_images",
|
||||
AsyncMock(),
|
||||
) as obtain_images_mock:
|
||||
result = await self.media_chain.async_recognize_by_meta(
|
||||
meta,
|
||||
obtain_images=False,
|
||||
)
|
||||
return result, recognize_mock, obtain_images_mock
|
||||
|
||||
result, recognize_mock, obtain_images_mock = asyncio.run(runner())
|
||||
|
||||
self.assertIs(result, mediainfo)
|
||||
recognize_mock.assert_awaited_once()
|
||||
obtain_images_mock.assert_not_awaited()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user