mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-03-20 03:57:30 +08:00
为TheMovieDb模块添加异步支持(part 1)
This commit is contained in:
@@ -689,6 +689,99 @@ class TheMovieDbModule(_ModuleBase):
|
||||
return [MediaInfo(tmdb_info=tmdbinfo) for tmdbinfo in infos]
|
||||
return []
|
||||
|
||||
# 异步方法
|
||||
async def async_search_medias(self, meta: MetaBase) -> Optional[List[MediaInfo]]:
|
||||
"""
|
||||
搜索媒体信息(异步版本)
|
||||
:param meta: 识别的元数据
|
||||
:reutrn: 媒体信息列表
|
||||
"""
|
||||
if settings.SEARCH_SOURCE and "themoviedb" not in settings.SEARCH_SOURCE:
|
||||
return None
|
||||
if not meta.name:
|
||||
return []
|
||||
if meta.type == MediaType.UNKNOWN and not meta.year:
|
||||
results = await self.tmdb.async_search_multiis(meta.name)
|
||||
else:
|
||||
if meta.type == MediaType.UNKNOWN:
|
||||
results = await self.tmdb.async_search_movies(meta.name, meta.year)
|
||||
results.extend(await self.tmdb.async_search_tvs(meta.name, meta.year))
|
||||
# 组合结果的情况下要排序
|
||||
results = sorted(
|
||||
results,
|
||||
key=lambda x: x.get("release_date") or x.get("first_air_date") or "0000-00-00",
|
||||
reverse=True
|
||||
)
|
||||
elif meta.type == MediaType.MOVIE:
|
||||
results = await self.tmdb.async_search_movies(meta.name, meta.year)
|
||||
else:
|
||||
results = await self.tmdb.async_search_tvs(meta.name, meta.year)
|
||||
# 将搜索词中的季写入标题中
|
||||
if results:
|
||||
medias = [MediaInfo(tmdb_info=info) for info in results]
|
||||
if meta.begin_season:
|
||||
# 小写数据转大写
|
||||
season_str = cn2an.an2cn(meta.begin_season, "low")
|
||||
for media in medias:
|
||||
if media.type == MediaType.TV:
|
||||
media.title = f"{media.title} 第{season_str}季"
|
||||
media.season = meta.begin_season
|
||||
return medias
|
||||
return []
|
||||
|
||||
async def async_tmdb_discover(self, mtype: MediaType, sort_by: str,
|
||||
with_genres: str,
|
||||
with_original_language: str,
|
||||
with_keywords: str,
|
||||
with_watch_providers: str,
|
||||
vote_average: float,
|
||||
vote_count: int,
|
||||
release_date: str,
|
||||
page: Optional[int] = 1) -> Optional[List[MediaInfo]]:
|
||||
"""
|
||||
TMDB发现功能(异步版本)
|
||||
:param mtype: 媒体类型
|
||||
:param sort_by: 排序方式
|
||||
:param with_genres: 类型
|
||||
:param with_original_language: 语言
|
||||
:param with_keywords: 关键字
|
||||
:param with_watch_providers: 提供商
|
||||
:param vote_average: 评分
|
||||
:param vote_count: 评分人数
|
||||
:param release_date: 发布日期
|
||||
:param page: 页码
|
||||
:return: 媒体信息列表
|
||||
"""
|
||||
if mtype == MediaType.MOVIE:
|
||||
infos = await self.tmdb.async_discover_movies({
|
||||
"sort_by": sort_by,
|
||||
"with_genres": with_genres,
|
||||
"with_original_language": with_original_language,
|
||||
"with_keywords": with_keywords,
|
||||
"with_watch_providers": with_watch_providers,
|
||||
"vote_average.gte": vote_average,
|
||||
"vote_count.gte": vote_count,
|
||||
"release_date.gte": release_date,
|
||||
"page": page
|
||||
})
|
||||
elif mtype == MediaType.TV:
|
||||
infos = await self.tmdb.async_discover_tvs({
|
||||
"sort_by": sort_by,
|
||||
"with_genres": with_genres,
|
||||
"with_original_language": with_original_language,
|
||||
"with_keywords": with_keywords,
|
||||
"with_watch_providers": with_watch_providers,
|
||||
"vote_average.gte": vote_average,
|
||||
"vote_count.gte": vote_count,
|
||||
"first_air_date.gte": release_date,
|
||||
"page": page
|
||||
})
|
||||
else:
|
||||
return []
|
||||
if infos:
|
||||
return [MediaInfo(tmdb_info=info) for info in infos]
|
||||
return []
|
||||
|
||||
def clear_cache(self):
|
||||
"""
|
||||
清除缓存
|
||||
|
||||
@@ -521,6 +521,7 @@ class TmdbApi:
|
||||
raise APIRateLimitException("触发TheDbMovie网站限流,获取媒体信息失败")
|
||||
if res.status_code != 200:
|
||||
return {}
|
||||
html = None
|
||||
html_text = res.text
|
||||
if not html_text:
|
||||
return {}
|
||||
@@ -560,13 +561,13 @@ class TmdbApi:
|
||||
logger.info("%s TMDB网站返回数据过多:%s" % (name, len(tmdb_links)))
|
||||
else:
|
||||
logger.info("%s TMDB网站未查询到媒体信息!" % name)
|
||||
return {}
|
||||
except Exception as err:
|
||||
logger.error(f"从TheDbMovie网站查询出错:{str(err)}")
|
||||
return {}
|
||||
finally:
|
||||
if html is not None:
|
||||
del html
|
||||
return {}
|
||||
|
||||
def get_info(self,
|
||||
mtype: MediaType,
|
||||
@@ -639,6 +640,7 @@ class TmdbApi:
|
||||
return None
|
||||
# dict[地区:分级]
|
||||
ratings = {}
|
||||
results = []
|
||||
if results := (tmdb_info.get("release_dates") or {}).get("results"):
|
||||
"""
|
||||
[
|
||||
@@ -1424,6 +1426,85 @@ class TmdbApi:
|
||||
"""
|
||||
self.tmdb.cache_clear()
|
||||
|
||||
# 异步方法
|
||||
async def async_search_multiis(self, title: str) -> List[dict]:
|
||||
"""
|
||||
同时查询模糊匹配的电影、电视剧TMDB信息(异步版本)
|
||||
"""
|
||||
if not title:
|
||||
return []
|
||||
ret_infos = []
|
||||
multis = await self.search.async_multi(term=title) or []
|
||||
for multi in multis:
|
||||
if multi.get("media_type") in ["movie", "tv"]:
|
||||
multi['media_type'] = MediaType.MOVIE if multi.get("media_type") == "movie" else MediaType.TV
|
||||
ret_infos.append(multi)
|
||||
return ret_infos
|
||||
|
||||
async def async_search_movies(self, title: str, year: str) -> List[dict]:
|
||||
"""
|
||||
查询模糊匹配的所有电影TMDB信息(异步版本)
|
||||
"""
|
||||
if not title:
|
||||
return []
|
||||
ret_infos = []
|
||||
if year:
|
||||
movies = await self.search.async_movies(term=title, year=year) or []
|
||||
else:
|
||||
movies = await self.search.async_movies(term=title) or []
|
||||
for movie in movies:
|
||||
if title in movie.get("title"):
|
||||
movie['media_type'] = MediaType.MOVIE
|
||||
ret_infos.append(movie)
|
||||
return ret_infos
|
||||
|
||||
async def async_search_tvs(self, title: str, year: str) -> List[dict]:
|
||||
"""
|
||||
查询模糊匹配的所有电视剧TMDB信息(异步版本)
|
||||
"""
|
||||
if not title:
|
||||
return []
|
||||
ret_infos = []
|
||||
if year:
|
||||
tvs = await self.search.async_tv_shows(term=title, release_year=year) or []
|
||||
else:
|
||||
tvs = await self.search.async_tv_shows(term=title) or []
|
||||
for tv in tvs:
|
||||
if title in tv.get("name"):
|
||||
tv['media_type'] = MediaType.TV
|
||||
ret_infos.append(tv)
|
||||
return ret_infos
|
||||
|
||||
async def async_discover_movies(self, params: dict) -> List[dict]:
|
||||
"""
|
||||
发现电影(异步版本)
|
||||
"""
|
||||
if not params:
|
||||
return []
|
||||
try:
|
||||
items = await self.discover.async_discover_movies(params_tuple=tuple(params.items())) or []
|
||||
for item in items:
|
||||
item['media_type'] = MediaType.MOVIE
|
||||
return items
|
||||
except Exception as e:
|
||||
logger.error(f"获取电影发现失败:{str(e)}")
|
||||
return []
|
||||
|
||||
async def async_discover_tvs(self, params: dict) -> List[dict]:
|
||||
"""
|
||||
发现电视剧(异步版本)
|
||||
"""
|
||||
if not params:
|
||||
return []
|
||||
try:
|
||||
items = await self.discover.async_discover_tv_shows(params_tuple=tuple(params.items())) or []
|
||||
for item in items:
|
||||
item['media_type'] = MediaType.TV
|
||||
return items
|
||||
except Exception as e:
|
||||
logger.error(f"获取电视剧发现失败:{str(e)}")
|
||||
return []
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
关闭连接
|
||||
|
||||
@@ -32,3 +32,24 @@ class Discover(TMDb):
|
||||
:return:
|
||||
"""
|
||||
return self._request_obj(self._urls["tv"], urlencode(params_tuple), key="results", call_cached=False)
|
||||
|
||||
@cached(maxsize=1, ttl=43200)
|
||||
async def async_discover_movies(self, params_tuple):
|
||||
"""
|
||||
Discover movies by different types of data like average rating, number of votes, genres and certifications.(异步版本)
|
||||
:param params_tuple: dict
|
||||
:return:
|
||||
"""
|
||||
params = dict(params_tuple)
|
||||
return await self._async_request_obj(self._urls["movies"], urlencode(params), key="results", call_cached=False)
|
||||
|
||||
@cached(maxsize=1, ttl=43200)
|
||||
async def async_discover_tv_shows(self, params_tuple):
|
||||
"""
|
||||
Discover TV shows by different types of data like average rating, number of votes, genres,
|
||||
the network they aired on and air dates.(异步版本)
|
||||
:param params_tuple: dict
|
||||
:return:
|
||||
"""
|
||||
return await self._async_request_obj(self._urls["tv"], urlencode(params_tuple), key="results",
|
||||
call_cached=False)
|
||||
|
||||
@@ -142,3 +142,71 @@ class Search(TMDb):
|
||||
params=params,
|
||||
key="results"
|
||||
)
|
||||
|
||||
async def async_multi(self, term, adult=None, region=None, page=1):
|
||||
"""
|
||||
Search multiple models in a single request.(异步版本)
|
||||
Multi search currently supports searching for movies, tv shows and people in a single request.
|
||||
:param term: str
|
||||
:param adult: bool
|
||||
:param region: str
|
||||
:param page: int
|
||||
:return:
|
||||
"""
|
||||
params = "query=%s&page=%s" % (quote(term), page)
|
||||
if adult is not None:
|
||||
params += "&include_adult=%s" % "true" if adult else "false"
|
||||
if region is not None:
|
||||
params += "®ion=%s" % quote(region)
|
||||
return await self._async_request_obj(
|
||||
self._urls["multi"],
|
||||
params=params,
|
||||
key="results"
|
||||
)
|
||||
|
||||
async def async_movies(self, term, adult=None, region=None, year=None, release_year=None, page=1):
|
||||
"""
|
||||
Search for movies.(异步版本)
|
||||
:param term: str
|
||||
:param adult: bool
|
||||
:param region: str
|
||||
:param year: int
|
||||
:param release_year: int
|
||||
:param page: int
|
||||
:return:
|
||||
"""
|
||||
params = "query=%s&page=%s" % (quote(term), page)
|
||||
if adult is not None:
|
||||
params += "&include_adult=%s" % "true" if adult else "false"
|
||||
if region is not None:
|
||||
params += "®ion=%s" % quote(region)
|
||||
if year is not None:
|
||||
params += "&year=%s" % year
|
||||
if release_year is not None:
|
||||
params += "&primary_release_year=%s" % release_year
|
||||
|
||||
return await self._async_request_obj(
|
||||
self._urls["movies"],
|
||||
params=params,
|
||||
key="results"
|
||||
)
|
||||
|
||||
async def async_tv_shows(self, term, adult=None, release_year=None, page=1):
|
||||
"""
|
||||
Search for a TV show.(异步版本)
|
||||
:param term: str
|
||||
:param adult: bool
|
||||
:param release_year: int
|
||||
:param page: int
|
||||
:return:
|
||||
"""
|
||||
params = "query=%s&page=%s" % (quote(term), page)
|
||||
if adult is not None:
|
||||
params += "&include_adult=%s" % "true" if adult else "false"
|
||||
if release_year is not None:
|
||||
params += "&first_air_date_year=%s" % release_year
|
||||
return await self._async_request_obj(
|
||||
self._urls["tv_shows"],
|
||||
params=params,
|
||||
key="results"
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ import requests.exceptions
|
||||
|
||||
from app.core.cache import cached
|
||||
from app.core.config import settings
|
||||
from app.utils.http import RequestUtils
|
||||
from app.utils.http import RequestUtils, AsyncRequestUtils
|
||||
from .exceptions import TMDbException
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -17,6 +17,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class TMDb(object):
|
||||
_req = None
|
||||
_async_req = None
|
||||
_session = None
|
||||
|
||||
def __init__(self, obj_cached=True, session=None, language=None):
|
||||
@@ -37,6 +38,8 @@ class TMDb(object):
|
||||
else:
|
||||
self._session = requests.Session()
|
||||
self._req = RequestUtils(session=self._session, proxies=self.proxies)
|
||||
# 初始化异步请求客户端
|
||||
self._async_req = AsyncRequestUtils(proxies=self.proxies)
|
||||
self._remaining = 40
|
||||
self._reset = None
|
||||
self._timeout = 15
|
||||
@@ -132,6 +135,14 @@ class TMDb(object):
|
||||
"""
|
||||
return self.request(method, url, data, json)
|
||||
|
||||
@cached(maxsize=settings.CONF.tmdb, ttl=settings.CONF.meta)
|
||||
async def async_cached_request(self, method, url, data, json,
|
||||
_ts=datetime.strftime(datetime.now(), '%Y%m%d')):
|
||||
"""
|
||||
缓存请求(异步版本)
|
||||
"""
|
||||
return await self.async_request(method, url, data, json)
|
||||
|
||||
def request(self, method, url, data, json):
|
||||
if method == "GET":
|
||||
req = self._req.get_res(url, params=data, json=json)
|
||||
@@ -141,6 +152,15 @@ class TMDb(object):
|
||||
raise TMDbException("无法连接TheMovieDb,请检查网络连接!")
|
||||
return req
|
||||
|
||||
async def async_request(self, method, url, data, json):
|
||||
if method == "GET":
|
||||
req = await self._async_req.get_res(url, params=data, json=json)
|
||||
else:
|
||||
req = await self._async_req.post_res(url, data=data, json=json)
|
||||
if req is None:
|
||||
raise TMDbException("无法连接TheMovieDb,请检查网络连接!")
|
||||
return req
|
||||
|
||||
def cache_clear(self):
|
||||
return self.cached_request.cache_clear()
|
||||
|
||||
@@ -209,6 +229,71 @@ class TMDb(object):
|
||||
return json_data.get(key)
|
||||
return json_data
|
||||
|
||||
async def _async_request_obj(self, action, params="", call_cached=True,
|
||||
method="GET", data=None, json=None, key=None):
|
||||
if self.api_key is None or self.api_key == "":
|
||||
raise TMDbException("TheMovieDb API Key 未设置!")
|
||||
|
||||
url = "https://%s/3%s?api_key=%s&%s&language=%s" % (
|
||||
self.domain,
|
||||
action,
|
||||
self.api_key,
|
||||
params,
|
||||
self.language,
|
||||
)
|
||||
|
||||
if self.cache and self.obj_cached and call_cached and method != "POST":
|
||||
req = await self.async_cached_request(method, url, data, json)
|
||||
else:
|
||||
req = await self.async_request(method, url, data, json)
|
||||
|
||||
if req is None:
|
||||
return None
|
||||
|
||||
headers = req.headers
|
||||
|
||||
if "X-RateLimit-Remaining" in headers:
|
||||
self._remaining = int(headers["X-RateLimit-Remaining"])
|
||||
|
||||
if "X-RateLimit-Reset" in headers:
|
||||
self._reset = int(headers["X-RateLimit-Reset"])
|
||||
|
||||
if self._remaining < 1:
|
||||
current_time = int(time.time())
|
||||
sleep_time = self._reset - current_time
|
||||
|
||||
if self.wait_on_rate_limit:
|
||||
logger.warning("达到请求频率限制,休眠:%d 秒..." % sleep_time)
|
||||
time.sleep(abs(sleep_time))
|
||||
return await self._async_request_obj(action, params, call_cached, method, data, json, key)
|
||||
else:
|
||||
raise TMDbException("达到请求频率限制,将在 %d 秒后重试..." % sleep_time)
|
||||
|
||||
json_data = req.json()
|
||||
|
||||
if "page" in json_data:
|
||||
self._page = json_data["page"]
|
||||
|
||||
if "total_results" in json_data:
|
||||
self._total_results = json_data["total_results"]
|
||||
|
||||
if "total_pages" in json_data:
|
||||
self._total_pages = json_data["total_pages"]
|
||||
|
||||
if self.debug:
|
||||
logger.info(json_data)
|
||||
logger.info(self.async_cached_request.cache_info())
|
||||
|
||||
if "errors" in json_data:
|
||||
raise TMDbException(json_data["errors"])
|
||||
|
||||
if "success" in json_data and json_data["success"] is False:
|
||||
raise TMDbException(json_data["status_message"])
|
||||
|
||||
if key:
|
||||
return json_data.get(key)
|
||||
return json_data
|
||||
|
||||
def close(self):
|
||||
if self._session:
|
||||
self._session.close()
|
||||
|
||||
Reference in New Issue
Block a user