mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-28 03:27:31 +08:00
feat:协程搜索 part1
This commit is contained in:
+55
-47
@@ -16,23 +16,23 @@ router = APIRouter()
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/last", summary="查询搜索结果", response_model=List[schemas.Context])
|
@router.get("/last", summary="查询搜索结果", response_model=List[schemas.Context])
|
||||||
def search_latest(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
async def search_latest(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||||
"""
|
"""
|
||||||
查询搜索结果
|
查询搜索结果
|
||||||
"""
|
"""
|
||||||
torrents = SearchChain().last_search_results()
|
torrents = await SearchChain().async_last_search_results()
|
||||||
return [torrent.to_dict() for torrent in torrents]
|
return [torrent.to_dict() for torrent in torrents]
|
||||||
|
|
||||||
|
|
||||||
@router.get("/media/{mediaid}", summary="精确搜索资源", response_model=schemas.Response)
|
@router.get("/media/{mediaid}", summary="精确搜索资源", response_model=schemas.Response)
|
||||||
def search_by_id(mediaid: str,
|
async def search_by_id(mediaid: str,
|
||||||
mtype: Optional[str] = None,
|
mtype: Optional[str] = None,
|
||||||
area: Optional[str] = "title",
|
area: Optional[str] = "title",
|
||||||
title: Optional[str] = None,
|
title: Optional[str] = None,
|
||||||
year: Optional[str] = None,
|
year: Optional[str] = None,
|
||||||
season: Optional[str] = None,
|
season: Optional[str] = None,
|
||||||
sites: Optional[str] = None,
|
sites: Optional[str] = None,
|
||||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||||
"""
|
"""
|
||||||
根据TMDBID/豆瓣ID精确搜索站点资源 tmdb:/douban:/bangumi:
|
根据TMDBID/豆瓣ID精确搜索站点资源 tmdb:/douban:/bangumi:
|
||||||
"""
|
"""
|
||||||
@@ -49,55 +49,59 @@ def search_by_id(mediaid: str,
|
|||||||
else:
|
else:
|
||||||
site_list = None
|
site_list = None
|
||||||
torrents = None
|
torrents = None
|
||||||
|
media_chain = MediaChain()
|
||||||
|
search_chain = SearchChain()
|
||||||
# 根据前缀识别媒体ID
|
# 根据前缀识别媒体ID
|
||||||
if mediaid.startswith("tmdb:"):
|
if mediaid.startswith("tmdb:"):
|
||||||
tmdbid = int(mediaid.replace("tmdb:", ""))
|
tmdbid = int(mediaid.replace("tmdb:", ""))
|
||||||
if settings.RECOGNIZE_SOURCE == "douban":
|
if settings.RECOGNIZE_SOURCE == "douban":
|
||||||
# 通过TMDBID识别豆瓣ID
|
# 通过TMDBID识别豆瓣ID
|
||||||
doubaninfo = MediaChain().get_doubaninfo_by_tmdbid(tmdbid=tmdbid, mtype=media_type)
|
doubaninfo = await media_chain.async_get_doubaninfo_by_tmdbid(tmdbid=tmdbid, mtype=media_type)
|
||||||
if doubaninfo:
|
if doubaninfo:
|
||||||
torrents = SearchChain().search_by_id(doubanid=doubaninfo.get("id"),
|
torrents = await search_chain.async_search_by_id(doubanid=doubaninfo.get("id"),
|
||||||
mtype=media_type, area=area, season=media_season,
|
mtype=media_type, area=area, season=media_season,
|
||||||
sites=site_list, cache_local=True)
|
sites=site_list, cache_local=True)
|
||||||
else:
|
else:
|
||||||
return schemas.Response(success=False, message="未识别到豆瓣媒体信息")
|
return schemas.Response(success=False, message="未识别到豆瓣媒体信息")
|
||||||
else:
|
else:
|
||||||
torrents = SearchChain().search_by_id(tmdbid=tmdbid, mtype=media_type, area=area, season=media_season,
|
torrents = await search_chain.async_search_by_id(tmdbid=tmdbid, mtype=media_type, area=area,
|
||||||
sites=site_list, cache_local=True)
|
season=media_season,
|
||||||
|
sites=site_list, cache_local=True)
|
||||||
elif mediaid.startswith("douban:"):
|
elif mediaid.startswith("douban:"):
|
||||||
doubanid = mediaid.replace("douban:", "")
|
doubanid = mediaid.replace("douban:", "")
|
||||||
if settings.RECOGNIZE_SOURCE == "themoviedb":
|
if settings.RECOGNIZE_SOURCE == "themoviedb":
|
||||||
# 通过豆瓣ID识别TMDBID
|
# 通过豆瓣ID识别TMDBID
|
||||||
tmdbinfo = MediaChain().get_tmdbinfo_by_doubanid(doubanid=doubanid, mtype=media_type)
|
tmdbinfo = await media_chain.async_get_tmdbinfo_by_doubanid(doubanid=doubanid, mtype=media_type)
|
||||||
if tmdbinfo:
|
if tmdbinfo:
|
||||||
if tmdbinfo.get('season') and not media_season:
|
if tmdbinfo.get('season') and not media_season:
|
||||||
media_season = tmdbinfo.get('season')
|
media_season = tmdbinfo.get('season')
|
||||||
torrents = SearchChain().search_by_id(tmdbid=tmdbinfo.get("id"),
|
torrents = await search_chain.async_search_by_id(tmdbid=tmdbinfo.get("id"),
|
||||||
mtype=media_type, area=area, season=media_season,
|
mtype=media_type, area=area, season=media_season,
|
||||||
sites=site_list, cache_local=True)
|
sites=site_list, cache_local=True)
|
||||||
else:
|
else:
|
||||||
return schemas.Response(success=False, message="未识别到TMDB媒体信息")
|
return schemas.Response(success=False, message="未识别到TMDB媒体信息")
|
||||||
else:
|
else:
|
||||||
torrents = SearchChain().search_by_id(doubanid=doubanid, mtype=media_type, area=area, season=media_season,
|
torrents = await search_chain.async_search_by_id(doubanid=doubanid, mtype=media_type, area=area,
|
||||||
sites=site_list, cache_local=True)
|
season=media_season,
|
||||||
|
sites=site_list, cache_local=True)
|
||||||
elif mediaid.startswith("bangumi:"):
|
elif mediaid.startswith("bangumi:"):
|
||||||
bangumiid = int(mediaid.replace("bangumi:", ""))
|
bangumiid = int(mediaid.replace("bangumi:", ""))
|
||||||
if settings.RECOGNIZE_SOURCE == "themoviedb":
|
if settings.RECOGNIZE_SOURCE == "themoviedb":
|
||||||
# 通过BangumiID识别TMDBID
|
# 通过BangumiID识别TMDBID
|
||||||
tmdbinfo = MediaChain().get_tmdbinfo_by_bangumiid(bangumiid=bangumiid)
|
tmdbinfo = await media_chain.async_get_tmdbinfo_by_bangumiid(bangumiid=bangumiid)
|
||||||
if tmdbinfo:
|
if tmdbinfo:
|
||||||
torrents = SearchChain().search_by_id(tmdbid=tmdbinfo.get("id"),
|
torrents = await search_chain.async_search_by_id(tmdbid=tmdbinfo.get("id"),
|
||||||
mtype=media_type, area=area, season=media_season,
|
mtype=media_type, area=area, season=media_season,
|
||||||
sites=site_list, cache_local=True)
|
sites=site_list, cache_local=True)
|
||||||
else:
|
else:
|
||||||
return schemas.Response(success=False, message="未识别到TMDB媒体信息")
|
return schemas.Response(success=False, message="未识别到TMDB媒体信息")
|
||||||
else:
|
else:
|
||||||
# 通过BangumiID识别豆瓣ID
|
# 通过BangumiID识别豆瓣ID
|
||||||
doubaninfo = MediaChain().get_doubaninfo_by_bangumiid(bangumiid=bangumiid)
|
doubaninfo = await media_chain.async_get_doubaninfo_by_bangumiid(bangumiid=bangumiid)
|
||||||
if doubaninfo:
|
if doubaninfo:
|
||||||
torrents = SearchChain().search_by_id(doubanid=doubaninfo.get("id"),
|
torrents = await search_chain.async_search_by_id(doubanid=doubaninfo.get("id"),
|
||||||
mtype=media_type, area=area, season=media_season,
|
mtype=media_type, area=area, season=media_season,
|
||||||
sites=site_list, cache_local=True)
|
sites=site_list, cache_local=True)
|
||||||
else:
|
else:
|
||||||
return schemas.Response(success=False, message="未识别到豆瓣媒体信息")
|
return schemas.Response(success=False, message="未识别到豆瓣媒体信息")
|
||||||
else:
|
else:
|
||||||
@@ -106,18 +110,18 @@ def search_by_id(mediaid: str,
|
|||||||
mediaid=mediaid,
|
mediaid=mediaid,
|
||||||
convert_type=settings.RECOGNIZE_SOURCE
|
convert_type=settings.RECOGNIZE_SOURCE
|
||||||
)
|
)
|
||||||
event = eventmanager.send_event(ChainEventType.MediaRecognizeConvert, event_data)
|
event = await eventmanager.async_send_event(ChainEventType.MediaRecognizeConvert, event_data)
|
||||||
# 使用事件返回的上下文数据
|
# 使用事件返回的上下文数据
|
||||||
if event and event.event_data:
|
if event and event.event_data:
|
||||||
event_data: MediaRecognizeConvertEventData = event.event_data
|
event_data: MediaRecognizeConvertEventData = event.event_data
|
||||||
if event_data.media_dict:
|
if event_data.media_dict:
|
||||||
search_id = event_data.media_dict.get("id")
|
search_id = event_data.media_dict.get("id")
|
||||||
if event_data.convert_type == "themoviedb":
|
if event_data.convert_type == "themoviedb":
|
||||||
torrents = SearchChain().search_by_id(tmdbid=search_id, mtype=media_type, area=area,
|
torrents = await search_chain.async_search_by_id(tmdbid=search_id, mtype=media_type, area=area,
|
||||||
season=media_season, cache_local=True)
|
season=media_season, cache_local=True)
|
||||||
elif event_data.convert_type == "douban":
|
elif event_data.convert_type == "douban":
|
||||||
torrents = SearchChain().search_by_id(doubanid=search_id, mtype=media_type, area=area,
|
torrents = await search_chain.async_search_by_id(doubanid=search_id, mtype=media_type, area=area,
|
||||||
season=media_season, cache_local=True)
|
season=media_season, cache_local=True)
|
||||||
else:
|
else:
|
||||||
if not title:
|
if not title:
|
||||||
return schemas.Response(success=False, message="未知的媒体ID")
|
return schemas.Response(success=False, message="未知的媒体ID")
|
||||||
@@ -130,14 +134,16 @@ def search_by_id(mediaid: str,
|
|||||||
if media_season:
|
if media_season:
|
||||||
meta.type = MediaType.TV
|
meta.type = MediaType.TV
|
||||||
meta.begin_season = media_season
|
meta.begin_season = media_season
|
||||||
mediainfo = MediaChain().recognize_media(meta=meta)
|
mediainfo = await media_chain.async_recognize_media(meta=meta)
|
||||||
if mediainfo:
|
if mediainfo:
|
||||||
if settings.RECOGNIZE_SOURCE == "themoviedb":
|
if settings.RECOGNIZE_SOURCE == "themoviedb":
|
||||||
torrents = SearchChain().search_by_id(tmdbid=mediainfo.tmdb_id, mtype=media_type, area=area,
|
torrents = await search_chain.async_search_by_id(tmdbid=mediainfo.tmdb_id, mtype=media_type,
|
||||||
season=media_season, cache_local=True)
|
area=area,
|
||||||
|
season=media_season, cache_local=True)
|
||||||
else:
|
else:
|
||||||
torrents = SearchChain().search_by_id(doubanid=mediainfo.douban_id, mtype=media_type, area=area,
|
torrents = await search_chain.async_search_by_id(doubanid=mediainfo.douban_id, mtype=media_type,
|
||||||
season=media_season, cache_local=True)
|
area=area,
|
||||||
|
season=media_season, cache_local=True)
|
||||||
# 返回搜索结果
|
# 返回搜索结果
|
||||||
if not torrents:
|
if not torrents:
|
||||||
return schemas.Response(success=False, message="未搜索到任何资源")
|
return schemas.Response(success=False, message="未搜索到任何资源")
|
||||||
@@ -146,16 +152,18 @@ def search_by_id(mediaid: str,
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/title", summary="模糊搜索资源", response_model=schemas.Response)
|
@router.get("/title", summary="模糊搜索资源", response_model=schemas.Response)
|
||||||
def search_by_title(keyword: Optional[str] = None,
|
async def search_by_title(keyword: Optional[str] = None,
|
||||||
page: Optional[int] = 0,
|
page: Optional[int] = 0,
|
||||||
sites: Optional[str] = None,
|
sites: Optional[str] = None,
|
||||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||||
"""
|
"""
|
||||||
根据名称模糊搜索站点资源,支持分页,关键词为空是返回首页资源
|
根据名称模糊搜索站点资源,支持分页,关键词为空是返回首页资源
|
||||||
"""
|
"""
|
||||||
torrents = SearchChain().search_by_title(title=keyword, page=page,
|
torrents = await SearchChain().async_search_by_title(
|
||||||
sites=[int(site) for site in sites.split(",") if site] if sites else None,
|
title=keyword, page=page,
|
||||||
cache_local=True)
|
sites=[int(site) for site in sites.split(",") if site] if sites else None,
|
||||||
|
cache_local=True
|
||||||
|
)
|
||||||
if not torrents:
|
if not torrents:
|
||||||
return schemas.Response(success=False, message="未搜索到任何资源")
|
return schemas.Response(success=False, message="未搜索到任何资源")
|
||||||
return schemas.Response(success=True, data=[torrent.to_dict() for torrent in torrents])
|
return schemas.Response(success=True, data=[torrent.to_dict() for torrent in torrents])
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from collections.abc import Callable
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Any, Tuple, List, Set, Union, Dict
|
from typing import Optional, Any, Tuple, List, Set, Union, Dict
|
||||||
|
|
||||||
|
import aiofiles
|
||||||
from qbittorrentapi import TorrentFilesList
|
from qbittorrentapi import TorrentFilesList
|
||||||
from transmission_rpc import File
|
from transmission_rpc import File
|
||||||
|
|
||||||
@@ -59,6 +60,32 @@ class ChainBase(metaclass=ABCMeta):
|
|||||||
logger.error(f"加载缓存 {filename} 出错:{str(err)}")
|
logger.error(f"加载缓存 {filename} 出错:{str(err)}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def async_load_cache(filename: str) -> Any:
|
||||||
|
"""
|
||||||
|
异步从本地加载缓存
|
||||||
|
"""
|
||||||
|
cache_path = settings.TEMP_PATH / filename
|
||||||
|
if cache_path.exists():
|
||||||
|
try:
|
||||||
|
async with aiofiles.open(cache_path, 'rb') as f:
|
||||||
|
content = await f.read()
|
||||||
|
return pickle.loads(content)
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"加载缓存 {filename} 出错:{str(err)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def async_save_cache(cache: Any, filename: str) -> None:
|
||||||
|
"""
|
||||||
|
异步保存缓存到本地
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
async with aiofiles.open(settings.TEMP_PATH / filename, 'wb') as f:
|
||||||
|
await f.write(pickle.dumps(cache))
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"保存缓存 {filename} 出错:{str(err)}")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def save_cache(cache: Any, filename: str) -> None:
|
def save_cache(cache: Any, filename: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
+185
-43
@@ -230,17 +230,15 @@ class MediaChain(ChainBase):
|
|||||||
meta_names = list(dict.fromkeys([k for k in [meta_org.name,
|
meta_names = list(dict.fromkeys([k for k in [meta_org.name,
|
||||||
meta.cn_name,
|
meta.cn_name,
|
||||||
meta.en_name] if k]))
|
meta.en_name] if k]))
|
||||||
for name in meta_names:
|
tmdbinfo = self._match_tmdb_with_names(
|
||||||
tmdbinfo = self.match_tmdbinfo(
|
meta_names=meta_names,
|
||||||
name=name,
|
year=meta.year,
|
||||||
year=meta.year,
|
mtype=mtype or meta.type,
|
||||||
mtype=mtype or meta.type,
|
season=meta.begin_season
|
||||||
season=meta.begin_season
|
)
|
||||||
)
|
if tmdbinfo:
|
||||||
if tmdbinfo:
|
# 合季季后返回
|
||||||
# 合季季后返回
|
tmdbinfo['season'] = meta.begin_season
|
||||||
tmdbinfo['season'] = meta.begin_season
|
|
||||||
break
|
|
||||||
return tmdbinfo
|
return tmdbinfo
|
||||||
|
|
||||||
def get_tmdbinfo_by_bangumiid(self, bangumiid: int) -> Optional[dict]:
|
def get_tmdbinfo_by_bangumiid(self, bangumiid: int) -> Optional[dict]:
|
||||||
@@ -256,23 +254,17 @@ class MediaChain(ChainBase):
|
|||||||
else:
|
else:
|
||||||
meta_cn = meta = MetaInfo(title=bangumiinfo.get("name"))
|
meta_cn = meta = MetaInfo(title=bangumiinfo.get("name"))
|
||||||
# 年份
|
# 年份
|
||||||
release_date = bangumiinfo.get("date") or bangumiinfo.get("air_date")
|
year = self._extract_year_from_bangumi(bangumiinfo)
|
||||||
if release_date:
|
|
||||||
year = release_date[:4]
|
|
||||||
else:
|
|
||||||
year = None
|
|
||||||
# 识别TMDB媒体信息
|
# 识别TMDB媒体信息
|
||||||
meta_names = list(dict.fromkeys([k for k in [meta_cn.name,
|
meta_names = list(dict.fromkeys([k for k in [meta_cn.name,
|
||||||
meta.name] if k]))
|
meta.name] if k]))
|
||||||
for name in meta_names:
|
tmdbinfo = self._match_tmdb_with_names(
|
||||||
tmdbinfo = self.match_tmdbinfo(
|
meta_names=meta_names,
|
||||||
name=name,
|
year=year,
|
||||||
year=year,
|
mtype=MediaType.TV,
|
||||||
mtype=MediaType.TV,
|
season=meta.begin_season
|
||||||
season=meta.begin_season
|
)
|
||||||
)
|
return tmdbinfo
|
||||||
if tmdbinfo:
|
|
||||||
return tmdbinfo
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_doubaninfo_by_tmdbid(self, tmdbid: int,
|
def get_doubaninfo_by_tmdbid(self, tmdbid: int,
|
||||||
@@ -285,19 +277,7 @@ class MediaChain(ChainBase):
|
|||||||
# 名称
|
# 名称
|
||||||
name = tmdbinfo.get("title") or tmdbinfo.get("name")
|
name = tmdbinfo.get("title") or tmdbinfo.get("name")
|
||||||
# 年份
|
# 年份
|
||||||
year = None
|
year = self._extract_year_from_tmdb(tmdbinfo, season)
|
||||||
if tmdbinfo.get('release_date'):
|
|
||||||
year = tmdbinfo['release_date'][:4]
|
|
||||||
elif tmdbinfo.get('seasons') and season:
|
|
||||||
for seainfo in tmdbinfo['seasons']:
|
|
||||||
# 季
|
|
||||||
season_number = seainfo.get("season_number")
|
|
||||||
if not season_number:
|
|
||||||
continue
|
|
||||||
air_date = seainfo.get("air_date")
|
|
||||||
if air_date and season_number == season:
|
|
||||||
year = air_date[:4]
|
|
||||||
break
|
|
||||||
# IMDBID
|
# IMDBID
|
||||||
imdbid = tmdbinfo.get("external_ids", {}).get("imdb_id")
|
imdbid = tmdbinfo.get("external_ids", {}).get("imdb_id")
|
||||||
return self.match_doubaninfo(
|
return self.match_doubaninfo(
|
||||||
@@ -320,11 +300,7 @@ class MediaChain(ChainBase):
|
|||||||
else:
|
else:
|
||||||
meta = MetaInfo(title=bangumiinfo.get("name"))
|
meta = MetaInfo(title=bangumiinfo.get("name"))
|
||||||
# 年份
|
# 年份
|
||||||
release_date = bangumiinfo.get("date") or bangumiinfo.get("air_date")
|
year = self._extract_year_from_bangumi(bangumiinfo)
|
||||||
if release_date:
|
|
||||||
year = release_date[:4]
|
|
||||||
else:
|
|
||||||
year = None
|
|
||||||
# 使用名称识别豆瓣媒体信息
|
# 使用名称识别豆瓣媒体信息
|
||||||
return self.match_doubaninfo(
|
return self.match_doubaninfo(
|
||||||
name=meta.name,
|
name=meta.name,
|
||||||
@@ -889,3 +865,169 @@ class MediaChain(ChainBase):
|
|||||||
logger.info(f"{content} 搜索到 {len(medias)} 条相关媒体信息")
|
logger.info(f"{content} 搜索到 {len(medias)} 条相关媒体信息")
|
||||||
# 识别的元数据,媒体信息列表
|
# 识别的元数据,媒体信息列表
|
||||||
return meta, medias
|
return meta, medias
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_year_from_bangumi(bangumiinfo: dict) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
从Bangumi信息中提取年份
|
||||||
|
"""
|
||||||
|
release_date = bangumiinfo.get("date") or bangumiinfo.get("air_date")
|
||||||
|
if release_date:
|
||||||
|
return release_date[:4]
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_year_from_tmdb(tmdbinfo: dict, season: Optional[int] = None) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
从TMDB信息中提取年份
|
||||||
|
"""
|
||||||
|
year = None
|
||||||
|
if tmdbinfo.get('release_date'):
|
||||||
|
year = tmdbinfo['release_date'][:4]
|
||||||
|
elif tmdbinfo.get('seasons') and season:
|
||||||
|
for seainfo in tmdbinfo['seasons']:
|
||||||
|
season_number = seainfo.get("season_number")
|
||||||
|
if not season_number:
|
||||||
|
continue
|
||||||
|
air_date = seainfo.get("air_date")
|
||||||
|
if air_date and season_number == season:
|
||||||
|
year = air_date[:4]
|
||||||
|
break
|
||||||
|
return year
|
||||||
|
|
||||||
|
def _match_tmdb_with_names(self, meta_names: list, year: Optional[str],
|
||||||
|
mtype: MediaType, season: Optional[int] = None) -> Optional[dict]:
|
||||||
|
"""
|
||||||
|
使用名称列表匹配TMDB信息
|
||||||
|
"""
|
||||||
|
for name in meta_names:
|
||||||
|
tmdbinfo = self.match_tmdbinfo(
|
||||||
|
name=name,
|
||||||
|
year=year,
|
||||||
|
mtype=mtype,
|
||||||
|
season=season
|
||||||
|
)
|
||||||
|
if tmdbinfo:
|
||||||
|
return tmdbinfo
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _async_match_tmdb_with_names(self, meta_names: list, year: Optional[str],
|
||||||
|
mtype: MediaType, season: Optional[int] = None) -> Optional[dict]:
|
||||||
|
"""
|
||||||
|
使用名称列表匹配TMDB信息(异步版本)
|
||||||
|
"""
|
||||||
|
for name in meta_names:
|
||||||
|
tmdbinfo = await self.async_match_tmdbinfo(
|
||||||
|
name=name,
|
||||||
|
year=year,
|
||||||
|
mtype=mtype,
|
||||||
|
season=season
|
||||||
|
)
|
||||||
|
if tmdbinfo:
|
||||||
|
return tmdbinfo
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def async_get_tmdbinfo_by_doubanid(self, doubanid: str, mtype: MediaType = None) -> Optional[dict]:
|
||||||
|
"""
|
||||||
|
根据豆瓣ID获取TMDB信息(异步版本)
|
||||||
|
"""
|
||||||
|
tmdbinfo = None
|
||||||
|
doubaninfo = await self.async_douban_info(doubanid=doubanid, mtype=mtype)
|
||||||
|
if doubaninfo:
|
||||||
|
# 优先使用原标题匹配
|
||||||
|
if doubaninfo.get("original_title"):
|
||||||
|
meta = MetaInfo(title=doubaninfo.get("title"))
|
||||||
|
meta_org = MetaInfo(title=doubaninfo.get("original_title"))
|
||||||
|
else:
|
||||||
|
meta_org = meta = MetaInfo(title=doubaninfo.get("title"))
|
||||||
|
# 年份
|
||||||
|
if doubaninfo.get("year"):
|
||||||
|
meta.year = doubaninfo.get("year")
|
||||||
|
# 处理类型
|
||||||
|
if isinstance(doubaninfo.get('media_type'), MediaType):
|
||||||
|
meta.type = doubaninfo.get('media_type')
|
||||||
|
else:
|
||||||
|
meta.type = MediaType.MOVIE if doubaninfo.get("type") == "movie" else MediaType.TV
|
||||||
|
# 匹配TMDB信息
|
||||||
|
meta_names = list(dict.fromkeys([k for k in [meta_org.name,
|
||||||
|
meta.cn_name,
|
||||||
|
meta.en_name] if k]))
|
||||||
|
tmdbinfo = await self._async_match_tmdb_with_names(
|
||||||
|
meta_names=meta_names,
|
||||||
|
year=meta.year,
|
||||||
|
mtype=mtype or meta.type,
|
||||||
|
season=meta.begin_season
|
||||||
|
)
|
||||||
|
if tmdbinfo:
|
||||||
|
# 合季季后返回
|
||||||
|
tmdbinfo['season'] = meta.begin_season
|
||||||
|
return tmdbinfo
|
||||||
|
|
||||||
|
async def async_get_tmdbinfo_by_bangumiid(self, bangumiid: int) -> Optional[dict]:
|
||||||
|
"""
|
||||||
|
根据BangumiID获取TMDB信息(异步版本)
|
||||||
|
"""
|
||||||
|
bangumiinfo = await self.async_bangumi_info(bangumiid=bangumiid)
|
||||||
|
if bangumiinfo:
|
||||||
|
# 优先使用原标题匹配
|
||||||
|
if bangumiinfo.get("name_cn"):
|
||||||
|
meta = MetaInfo(title=bangumiinfo.get("name"))
|
||||||
|
meta_cn = MetaInfo(title=bangumiinfo.get("name_cn"))
|
||||||
|
else:
|
||||||
|
meta_cn = meta = MetaInfo(title=bangumiinfo.get("name"))
|
||||||
|
# 年份
|
||||||
|
year = self._extract_year_from_bangumi(bangumiinfo)
|
||||||
|
# 识别TMDB媒体信息
|
||||||
|
meta_names = list(dict.fromkeys([k for k in [meta_cn.name,
|
||||||
|
meta.name] if k]))
|
||||||
|
tmdbinfo = await self._async_match_tmdb_with_names(
|
||||||
|
meta_names=meta_names,
|
||||||
|
year=year,
|
||||||
|
mtype=MediaType.TV,
|
||||||
|
season=meta.begin_season
|
||||||
|
)
|
||||||
|
return tmdbinfo
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def async_get_doubaninfo_by_tmdbid(self, tmdbid: int, mtype: MediaType = None,
|
||||||
|
season: Optional[int] = None) -> Optional[dict]:
|
||||||
|
"""
|
||||||
|
根据TMDBID获取豆瓣信息(异步版本)
|
||||||
|
"""
|
||||||
|
tmdbinfo = await self.async_tmdb_info(tmdbid=tmdbid, mtype=mtype)
|
||||||
|
if tmdbinfo:
|
||||||
|
# 名称
|
||||||
|
name = tmdbinfo.get("title") or tmdbinfo.get("name")
|
||||||
|
# 年份
|
||||||
|
year = self._extract_year_from_tmdb(tmdbinfo, season)
|
||||||
|
# IMDBID
|
||||||
|
imdbid = tmdbinfo.get("external_ids", {}).get("imdb_id")
|
||||||
|
return await self.async_match_doubaninfo(
|
||||||
|
name=name,
|
||||||
|
year=year,
|
||||||
|
mtype=mtype,
|
||||||
|
imdbid=imdbid
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def async_get_doubaninfo_by_bangumiid(self, bangumiid: int) -> Optional[dict]:
|
||||||
|
"""
|
||||||
|
根据BangumiID获取豆瓣信息(异步版本)
|
||||||
|
"""
|
||||||
|
bangumiinfo = await self.async_bangumi_info(bangumiid=bangumiid)
|
||||||
|
if bangumiinfo:
|
||||||
|
# 优先使用中文标题匹配
|
||||||
|
if bangumiinfo.get("name_cn"):
|
||||||
|
meta = MetaInfo(title=bangumiinfo.get("name_cn"))
|
||||||
|
else:
|
||||||
|
meta = MetaInfo(title=bangumiinfo.get("name"))
|
||||||
|
# 年份
|
||||||
|
year = self._extract_year_from_bangumi(bangumiinfo)
|
||||||
|
# 使用名称识别豆瓣媒体信息
|
||||||
|
return await self.async_match_doubaninfo(
|
||||||
|
name=meta.name,
|
||||||
|
year=year,
|
||||||
|
mtype=MediaType.TV,
|
||||||
|
season=meta.begin_season
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|||||||
@@ -97,6 +97,77 @@ class SearchChain(ChainBase):
|
|||||||
logger.error(f'加载搜索结果失败:{str(e)} - {traceback.format_exc()}')
|
logger.error(f'加载搜索结果失败:{str(e)} - {traceback.format_exc()}')
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
async def async_last_search_results(self) -> List[Context]:
|
||||||
|
"""
|
||||||
|
异步获取上次搜索结果
|
||||||
|
"""
|
||||||
|
# 读取本地文件缓存
|
||||||
|
content = await self.async_load_cache(self.__result_temp_file)
|
||||||
|
if not content:
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
return pickle.loads(content)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'加载搜索结果失败:{str(e)} - {traceback.format_exc()}')
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def async_search_by_id(self, tmdbid: Optional[int] = None, doubanid: Optional[str] = None,
|
||||||
|
mtype: MediaType = None, area: Optional[str] = "title", season: Optional[int] = None,
|
||||||
|
sites: List[int] = None, cache_local: bool = False) -> List[Context]:
|
||||||
|
"""
|
||||||
|
根据TMDBID/豆瓣ID异步搜索资源,精确匹配,不过滤本地存在的资源
|
||||||
|
:param tmdbid: TMDB ID
|
||||||
|
:param doubanid: 豆瓣 ID
|
||||||
|
:param mtype: 媒体,电影 or 电视剧
|
||||||
|
:param area: 搜索范围,title or imdbid
|
||||||
|
:param season: 季数
|
||||||
|
:param sites: 站点ID列表
|
||||||
|
:param cache_local: 是否缓存到本地
|
||||||
|
"""
|
||||||
|
mediainfo = await self.async_recognize_media(tmdbid=tmdbid, doubanid=doubanid, mtype=mtype)
|
||||||
|
if not mediainfo:
|
||||||
|
logger.error(f'{tmdbid} 媒体信息识别失败!')
|
||||||
|
return []
|
||||||
|
no_exists = None
|
||||||
|
if season:
|
||||||
|
no_exists = {
|
||||||
|
tmdbid or doubanid: {
|
||||||
|
season: NotExistMediaInfo(episodes=[])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# TODO async
|
||||||
|
results = self.process(mediainfo=mediainfo, sites=sites, area=area, no_exists=no_exists)
|
||||||
|
# 保存到本地文件
|
||||||
|
if cache_local:
|
||||||
|
await self.async_save_cache(pickle.dumps(results), self.__result_temp_file)
|
||||||
|
return results
|
||||||
|
|
||||||
|
async def async_search_by_title(self, title: str, page: Optional[int] = 0,
|
||||||
|
sites: List[int] = None, cache_local: Optional[bool] = False) -> List[Context]:
|
||||||
|
"""
|
||||||
|
根据标题异步搜索资源,不识别不过滤,直接返回站点内容
|
||||||
|
:param title: 标题,为空时返回所有站点首页内容
|
||||||
|
:param page: 页码
|
||||||
|
:param sites: 站点ID列表
|
||||||
|
:param cache_local: 是否缓存到本地
|
||||||
|
"""
|
||||||
|
if title:
|
||||||
|
logger.info(f'开始搜索资源,关键词:{title} ...')
|
||||||
|
else:
|
||||||
|
logger.info(f'开始浏览资源,站点:{sites} ...')
|
||||||
|
# 搜索 TODO async
|
||||||
|
torrents = self.__search_all_sites(keywords=[title], sites=sites, page=page) or []
|
||||||
|
if not torrents:
|
||||||
|
logger.warn(f'{title} 未搜索到资源')
|
||||||
|
return []
|
||||||
|
# 组装上下文
|
||||||
|
contexts = [Context(meta_info=MetaInfo(title=torrent.title, subtitle=torrent.description),
|
||||||
|
torrent_info=torrent) for torrent in torrents]
|
||||||
|
# 保存到本地文件
|
||||||
|
if cache_local:
|
||||||
|
await self.async_save_cache(pickle.dumps(contexts), self.__result_temp_file)
|
||||||
|
return contexts
|
||||||
|
|
||||||
def process(self, mediainfo: MediaInfo,
|
def process(self, mediainfo: MediaInfo,
|
||||||
keyword: Optional[str] = None,
|
keyword: Optional[str] = None,
|
||||||
no_exists: Dict[int, Dict[int, NotExistMediaInfo]] = None,
|
no_exists: Dict[int, Dict[int, NotExistMediaInfo]] = None,
|
||||||
|
|||||||
Reference in New Issue
Block a user