mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-06 16:07:01 +08:00
@@ -62,7 +62,9 @@ class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]):
|
|||||||
server.reconnect()
|
server.reconnect()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
pass
|
for server in self.get_instances().values():
|
||||||
|
if server.is_authenticated():
|
||||||
|
server.disconnect()
|
||||||
|
|
||||||
def test(self) -> Optional[Tuple[bool, str]]:
|
def test(self) -> Optional[Tuple[bool, str]]:
|
||||||
"""
|
"""
|
||||||
@@ -73,7 +75,7 @@ class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]):
|
|||||||
for name, server in self.get_instances().items():
|
for name, server in self.get_instances().items():
|
||||||
if not server.is_configured():
|
if not server.is_configured():
|
||||||
return False, f"飞牛影视配置不完整:{name}"
|
return False, f"飞牛影视配置不完整:{name}"
|
||||||
if server.is_inactive() and server.reconnect() != True:
|
if server.is_inactive() and not server.reconnect():
|
||||||
return False, f"无法连接飞牛影视:{name}"
|
return False, f"无法连接飞牛影视:{name}"
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
|
|||||||
+107
-46
@@ -4,7 +4,7 @@ import random
|
|||||||
import time
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional, Union, List
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
@@ -19,27 +19,27 @@ class User:
|
|||||||
|
|
||||||
|
|
||||||
class Category(Enum):
|
class Category(Enum):
|
||||||
Movie = "Movie"
|
MOVIE = "Movie"
|
||||||
TV = "TV"
|
TV = "TV"
|
||||||
Mix = "Mix"
|
MIX = "Mix"
|
||||||
Others = "Others"
|
OTHERS = "Others"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _missing_(cls, value):
|
def _missing_(cls, value):
|
||||||
return cls.Others
|
return cls.OTHERS
|
||||||
|
|
||||||
|
|
||||||
class Type(Enum):
|
class Type(Enum):
|
||||||
Movie = "Movie"
|
MOVIE = "Movie"
|
||||||
TV = "TV"
|
TV = "TV"
|
||||||
Season = "Season"
|
SEASON = "Season"
|
||||||
Episode = "Episode"
|
EPISODE = "Episode"
|
||||||
Video = "Video"
|
VIDEO = "Video"
|
||||||
Directory = "Directory"
|
DIRECTORY = "Directory"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _missing_(cls, value):
|
def _missing_(cls, value):
|
||||||
return cls.Video
|
return cls.VIDEO
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -60,6 +60,13 @@ class MediaDbSummary:
|
|||||||
total: int = 0
|
total: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Version:
|
||||||
|
# 飞牛影视版本
|
||||||
|
frontend: Optional[str] = None
|
||||||
|
backend: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Item:
|
class Item:
|
||||||
guid: str
|
guid: str
|
||||||
@@ -103,6 +110,7 @@ class Api:
|
|||||||
"_apikey",
|
"_apikey",
|
||||||
"_api_path",
|
"_api_path",
|
||||||
"_request_utils",
|
"_request_utils",
|
||||||
|
"_version",
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -117,13 +125,34 @@ class Api:
|
|||||||
def apikey(self) -> str:
|
def apikey(self) -> str:
|
||||||
return self._apikey
|
return self._apikey
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self) -> Optional[Version]:
|
||||||
|
return self._version
|
||||||
|
|
||||||
def __init__(self, host: str, apikey: str):
|
def __init__(self, host: str, apikey: str):
|
||||||
self._api_path = "/v/api/v1"
|
"""
|
||||||
|
:param host: 飞牛服务端地址,如http://127.0.0.1:5666/v
|
||||||
|
"""
|
||||||
|
self._api_path = "/api/v1"
|
||||||
self._host = host.rstrip("/")
|
self._host = host.rstrip("/")
|
||||||
self._apikey = apikey
|
self._apikey = apikey
|
||||||
self._token = None
|
self._token: Optional[str] = None
|
||||||
|
self._version: Optional[Version] = None
|
||||||
self._request_utils = RequestUtils(session=requests.Session())
|
self._request_utils = RequestUtils(session=requests.Session())
|
||||||
|
|
||||||
|
def sys_version(self) -> Optional[Version]:
|
||||||
|
"""
|
||||||
|
飞牛影视版本号
|
||||||
|
"""
|
||||||
|
if (res := self.__request_api("/sys/version")) and res.success:
|
||||||
|
if res.data:
|
||||||
|
self._version = Version(
|
||||||
|
frontend=res.data.get("version"),
|
||||||
|
backend=res.data.get("mediasrvVersion"),
|
||||||
|
)
|
||||||
|
return self._version
|
||||||
|
return None
|
||||||
|
|
||||||
def login(self, username, password) -> Optional[str]:
|
def login(self, username, password) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
登录飞牛影视
|
登录飞牛影视
|
||||||
@@ -131,14 +160,14 @@ class Api:
|
|||||||
:return: 成功返回token 否则返回None
|
:return: 成功返回token 否则返回None
|
||||||
"""
|
"""
|
||||||
if (
|
if (
|
||||||
res := self.__request_api(
|
res := self.__request_api(
|
||||||
"/login",
|
"/login",
|
||||||
data={
|
data={
|
||||||
"username": username,
|
"username": username,
|
||||||
"password": password,
|
"password": password,
|
||||||
"app_name": "trimemedia-web",
|
"app_name": "trimemedia-web",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
) and res.success:
|
) and res.success:
|
||||||
self._token = res.data.get("token")
|
self._token = res.data.get("token")
|
||||||
return self._token
|
return self._token
|
||||||
@@ -250,7 +279,7 @@ class Api:
|
|||||||
扫描指定媒体库
|
扫描指定媒体库
|
||||||
"""
|
"""
|
||||||
if (
|
if (
|
||||||
res := self.__request_api(f"/mdb/scan/{mdb.guid}", data={})
|
res := self.__request_api(f"/mdb/scan/{mdb.guid}", data={})
|
||||||
) and res.success:
|
) and res.success:
|
||||||
if res.data:
|
if res.data:
|
||||||
return True
|
return True
|
||||||
@@ -272,22 +301,22 @@ class Api:
|
|||||||
return item
|
return item
|
||||||
|
|
||||||
def item_list(
|
def item_list(
|
||||||
self,
|
self,
|
||||||
guid: Optional[str] = None,
|
guid: Optional[str] = None,
|
||||||
type=None,
|
types=None,
|
||||||
exclude_grouped_video=True,
|
exclude_grouped_video=True,
|
||||||
page=1,
|
page=1,
|
||||||
page_size=22,
|
page_size=22,
|
||||||
sort_by="create_time",
|
sort_by="create_time",
|
||||||
sort="DESC",
|
sort="DESC",
|
||||||
) -> Optional[list[Item]]:
|
) -> Optional[list[Item]]:
|
||||||
"""
|
"""
|
||||||
媒体列表
|
媒体列表
|
||||||
"""
|
"""
|
||||||
if type is None:
|
if types is None:
|
||||||
type = [Type.Movie, Type.TV, Type.Directory, Type.Video]
|
types = [Type.MOVIE, Type.TV, Type.DIRECTORY, Type.VIDEO]
|
||||||
post = {
|
post = {
|
||||||
"tags": {"type": type} if type else {},
|
"tags": {"type": types} if types else {},
|
||||||
"sort_type": sort,
|
"sort_type": sort,
|
||||||
"sort_column": sort_by,
|
"sort_column": sort_by,
|
||||||
"page": page,
|
"page": page,
|
||||||
@@ -307,25 +336,48 @@ class Api:
|
|||||||
搜索影片、演员
|
搜索影片、演员
|
||||||
"""
|
"""
|
||||||
if (
|
if (
|
||||||
res := self.__request_api("/search/list", params={"q": keywords})
|
res := self.__request_api("/search/list", params={"q": keywords})
|
||||||
) and res.success:
|
) and res.success:
|
||||||
return [self.__build_item(info) for info in res.data]
|
return [self.__build_item(info) for info in res.data]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def item(self, guid: str) -> Optional[Item]:
|
def item(self, guid: str) -> Optional[Item]:
|
||||||
""" """
|
"""
|
||||||
|
查询媒体详情
|
||||||
|
"""
|
||||||
if (res := self.__request_api(f"/item/{guid}")) and res.success:
|
if (res := self.__request_api(f"/item/{guid}")) and res.success:
|
||||||
return self.__build_item(res.data)
|
return self.__build_item(res.data)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def del_item(self, guid: str, delete_file: bool) -> bool:
|
||||||
|
"""
|
||||||
|
删除媒体
|
||||||
|
|
||||||
|
:param delete_file: True删除媒体文件,False仅从媒体库移除
|
||||||
|
"""
|
||||||
|
if (
|
||||||
|
res := self.__request_api(
|
||||||
|
f"/item/{guid}",
|
||||||
|
method="delete",
|
||||||
|
data={"delete_file": 1 if delete_file else 0, "media_guids": []},
|
||||||
|
)
|
||||||
|
) and res.success:
|
||||||
|
if res.data:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def season_list(self, tv_guid: str) -> Optional[list[Item]]:
|
def season_list(self, tv_guid: str) -> Optional[list[Item]]:
|
||||||
""" """
|
"""
|
||||||
|
查询季列表
|
||||||
|
"""
|
||||||
if (res := self.__request_api(f"/season/list/{tv_guid}")) and res.success:
|
if (res := self.__request_api(f"/season/list/{tv_guid}")) and res.success:
|
||||||
return [self.__build_item(info) for info in res.data]
|
return [self.__build_item(info) for info in res.data]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def episode_list(self, season_guid: str) -> Optional[list[Item]]:
|
def episode_list(self, season_guid: str) -> Optional[list[Item]]:
|
||||||
""" """
|
"""
|
||||||
|
查询剧集列表
|
||||||
|
"""
|
||||||
if (res := self.__request_api(f"/episode/list/{season_guid}")) and res.success:
|
if (res := self.__request_api(f"/episode/list/{season_guid}")) and res.success:
|
||||||
return [self.__build_item(info) for info in res.data]
|
return [self.__build_item(info) for info in res.data]
|
||||||
return None
|
return None
|
||||||
@@ -338,12 +390,12 @@ class Api:
|
|||||||
return [self.__build_item(info) for info in res.data]
|
return [self.__build_item(info) for info in res.data]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __get_authx(self, api_path, body: Optional[str]):
|
def __get_authx(self, api_path: str, body: Optional[str]):
|
||||||
"""
|
"""
|
||||||
计算消息签名
|
计算消息签名
|
||||||
"""
|
"""
|
||||||
if api_path[0] != "/":
|
if not api_path.startswith("/v"):
|
||||||
api_path = "/" + api_path
|
api_path = "/v" + api_path
|
||||||
nonce = str(random.randint(100000, 999999))
|
nonce = str(random.randint(100000, 999999))
|
||||||
ts = str(int(time.time() * 1000))
|
ts = str(int(time.time() * 1000))
|
||||||
md5 = hashlib.md5()
|
md5 = hashlib.md5()
|
||||||
@@ -366,10 +418,17 @@ class Api:
|
|||||||
return f"nonce={nonce}×tamp={ts}&sign={sign}"
|
return f"nonce={nonce}×tamp={ts}&sign={sign}"
|
||||||
|
|
||||||
def __request_api(
|
def __request_api(
|
||||||
self, api: str, method: str = None, params: dict = None, data: dict = None
|
self,
|
||||||
|
api: str,
|
||||||
|
method: Optional[str] = None,
|
||||||
|
params: Optional[dict] = None,
|
||||||
|
data: Optional[dict] = None,
|
||||||
|
suppress_log=False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
请求飞牛影视API
|
请求飞牛影视API
|
||||||
|
|
||||||
|
:param suppress_log: 是否禁止日志
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -397,7 +456,7 @@ class Api:
|
|||||||
url = self._host + api_path
|
url = self._host + api_path
|
||||||
if method is None:
|
if method is None:
|
||||||
method = "get" if data is None else "post"
|
method = "get" if data is None else "post"
|
||||||
if method == "post":
|
if method != "get":
|
||||||
json_body = (
|
json_body = (
|
||||||
json.dumps(data, allow_nan=False, cls=JsonEncoder) if data else ""
|
json.dumps(data, allow_nan=False, cls=JsonEncoder) if data else ""
|
||||||
)
|
)
|
||||||
@@ -422,11 +481,13 @@ class Api:
|
|||||||
resp = res.json()
|
resp = res.json()
|
||||||
msg = resp.get("msg")
|
msg = resp.get("msg")
|
||||||
if code := int(resp.get("code", -1)):
|
if code := int(resp.get("code", -1)):
|
||||||
logger.error(f"请求接口 {api_path} 失败,错误码:{code} {msg}")
|
if not suppress_log:
|
||||||
|
logger.error(f"请求接口 {url} 失败,错误码:{code} {msg}")
|
||||||
return Result(code, msg)
|
return Result(code, msg)
|
||||||
return Result(0, msg, resp.get("data"))
|
return Result(0, msg, resp.get("data"))
|
||||||
else:
|
elif not suppress_log:
|
||||||
logger.error(f"请求接口 {api_path} 失败")
|
logger.error(f"请求接口 {url} 失败")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"请求接口 {api_path} 异常:" + str(e))
|
if not suppress_log:
|
||||||
|
logger.error(f"请求接口 {url} 异常:" + str(e))
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -26,21 +26,58 @@ class TrimeMedia:
|
|||||||
username: Optional[str] = None,
|
username: Optional[str] = None,
|
||||||
password: Optional[str] = None,
|
password: Optional[str] = None,
|
||||||
play_host: Optional[str] = None,
|
play_host: Optional[str] = None,
|
||||||
sync_libraries: list = None,
|
sync_libraries: Optional[list] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
if not host or not username or not password:
|
if not host or not username or not password:
|
||||||
logger.error("飞牛影视配置不完整!!")
|
logger.error("飞牛影视配置不完整!!")
|
||||||
return
|
return
|
||||||
host = UrlUtils.standardize_base_url(host).rstrip("/")
|
|
||||||
if play_host:
|
|
||||||
self._playhost = UrlUtils.standardize_base_url(play_host).rstrip("/")
|
|
||||||
self._username = username
|
self._username = username
|
||||||
self._password = password
|
self._password = password
|
||||||
self._sync_libraries = sync_libraries or []
|
self._sync_libraries = sync_libraries or []
|
||||||
self._api = fnapi.Api(host, apikey="16CCEB3D-AB42-077D-36A1-F355324E4237")
|
|
||||||
|
if (api := self.__create_api(host)) is None:
|
||||||
|
logger.error(f"请检查服务端地址 {host}")
|
||||||
|
return
|
||||||
|
self._api = api
|
||||||
|
if play_api := self.__create_api(play_host):
|
||||||
|
self._playhost = play_api.host
|
||||||
|
elif play_host:
|
||||||
|
logger.warning(f"请检查外网播放地址 {play_host}")
|
||||||
|
|
||||||
self.reconnect()
|
self.reconnect()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api(self) -> Optional[fnapi.Api]:
|
||||||
|
"""
|
||||||
|
获得飞牛API
|
||||||
|
"""
|
||||||
|
return self._api
|
||||||
|
|
||||||
|
def __create_api(self, host: Optional[str]) -> Optional[fnapi.Api]:
|
||||||
|
"""
|
||||||
|
创建一个飞牛API
|
||||||
|
|
||||||
|
:param host: 服务端地址
|
||||||
|
:return: 如果地址无效、不可访问则返回None
|
||||||
|
"""
|
||||||
|
if not host:
|
||||||
|
return None
|
||||||
|
api_key = "16CCEB3D-AB42-077D-36A1-F355324E4237"
|
||||||
|
host = UrlUtils.standardize_base_url(host).rstrip("/")
|
||||||
|
|
||||||
|
if not host.endswith("/v"):
|
||||||
|
# 尝试补上结尾的/v 测试能否正常访问
|
||||||
|
api = fnapi.Api(host + "/v", api_key)
|
||||||
|
if api.sys_version():
|
||||||
|
return api
|
||||||
|
# 测试用户配置的地址
|
||||||
|
api = fnapi.Api(host, api_key)
|
||||||
|
return api if api.sys_version() else None
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
def is_configured(self) -> bool:
|
def is_configured(self) -> bool:
|
||||||
return self._api is not None
|
return self._api is not None
|
||||||
|
|
||||||
@@ -62,14 +99,27 @@ class TrimeMedia:
|
|||||||
"""
|
"""
|
||||||
if not self.is_configured():
|
if not self.is_configured():
|
||||||
return False
|
return False
|
||||||
|
if (fnver := self._api.sys_version()) is None:
|
||||||
|
return False
|
||||||
|
# 版本号:0.8.36, 服务版本:0.8.19
|
||||||
|
logger.debug(f"版本号:{fnver.frontend}, 服务版本:{fnver.backend}")
|
||||||
if self._api.login(self._username, self._password) is None:
|
if self._api.login(self._username, self._password) is None:
|
||||||
return False
|
return False
|
||||||
self._userinfo = self._api.user_info()
|
self._userinfo = self._api.user_info()
|
||||||
if self._userinfo is None:
|
if self._userinfo is None:
|
||||||
return False
|
return False
|
||||||
logger.debug(f"{self._userinfo.username} 成功登录飞牛影视")
|
logger.debug(f"{self._username} 成功登录飞牛影视")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
"""
|
||||||
|
断开与飞牛的连接
|
||||||
|
"""
|
||||||
|
if self.is_authenticated():
|
||||||
|
self._api.logout()
|
||||||
|
self._userinfo = None
|
||||||
|
logger.debug(f"{self._username} 已断开飞牛影视")
|
||||||
|
|
||||||
def get_librarys(
|
def get_librarys(
|
||||||
self, hidden: Optional[bool] = False
|
self, hidden: Optional[bool] = False
|
||||||
) -> List[schemas.MediaServerLibrary]:
|
) -> List[schemas.MediaServerLibrary]:
|
||||||
@@ -87,11 +137,11 @@ class TrimeMedia:
|
|||||||
for library in self._libraries.values():
|
for library in self._libraries.values():
|
||||||
if hidden and self.__is_library_blocked(library.guid):
|
if hidden and self.__is_library_blocked(library.guid):
|
||||||
continue
|
continue
|
||||||
if library.category == fnapi.Category.Movie:
|
if library.category == fnapi.Category.MOVIE:
|
||||||
library_type = MediaType.MOVIE.value
|
library_type = MediaType.MOVIE.value
|
||||||
elif library.category == fnapi.Category.TV:
|
elif library.category == fnapi.Category.TV:
|
||||||
library_type = MediaType.TV.value
|
library_type = MediaType.TV.value
|
||||||
elif library.category == fnapi.Category.Others:
|
elif library.category == fnapi.Category.OTHERS:
|
||||||
# 忽略这个库
|
# 忽略这个库
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
@@ -107,7 +157,7 @@ class TrimeMedia:
|
|||||||
f"{self._api.host}{img_path}?w=256"
|
f"{self._api.host}{img_path}?w=256"
|
||||||
for img_path in library.posters or []
|
for img_path in library.posters or []
|
||||||
],
|
],
|
||||||
link=f"{self._playhost or self._api.host}/v/library/{library.guid}",
|
link=f"{self._playhost or self._api.host}/library/{library.guid}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return libraries
|
return libraries
|
||||||
@@ -170,7 +220,7 @@ class TrimeMedia:
|
|||||||
movies = []
|
movies = []
|
||||||
items = self._api.search_list(keywords=title) or []
|
items = self._api.search_list(keywords=title) or []
|
||||||
for item in items:
|
for item in items:
|
||||||
if item.type != fnapi.Type.Movie:
|
if item.type != fnapi.Type.MOVIE:
|
||||||
continue
|
continue
|
||||||
if (
|
if (
|
||||||
(not tmdb_id or tmdb_id == item.tmdb_id)
|
(not tmdb_id or tmdb_id == item.tmdb_id)
|
||||||
@@ -280,7 +330,7 @@ class TrimeMedia:
|
|||||||
lib = self.__match_library_by_path(item.target_path)
|
lib = self.__match_library_by_path(item.target_path)
|
||||||
if lib is None:
|
if lib is None:
|
||||||
# 如果有匹配失败的,刷新整个库
|
# 如果有匹配失败的,刷新整个库
|
||||||
return self._api.mdb_scanall()
|
return self.refresh_root_library()
|
||||||
# 媒体库去重
|
# 媒体库去重
|
||||||
libraries.add(lib.guid)
|
libraries.add(lib.guid)
|
||||||
|
|
||||||
@@ -290,7 +340,7 @@ class TrimeMedia:
|
|||||||
logger.info(f"刷新媒体库:{lib.name}")
|
logger.info(f"刷新媒体库:{lib.name}")
|
||||||
if not self._api.mdb_scan(lib):
|
if not self._api.mdb_scan(lib):
|
||||||
# 如果失败,刷新整个库
|
# 如果失败,刷新整个库
|
||||||
return self._api.mdb_scanall()
|
return self.refresh_root_library()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __match_library_by_path(self, path: Path) -> Optional[fnapi.MediaDb]:
|
def __match_library_by_path(self, path: Path) -> Optional[fnapi.MediaDb]:
|
||||||
@@ -336,7 +386,7 @@ class TrimeMedia:
|
|||||||
if item.watched:
|
if item.watched:
|
||||||
user_state.played = True
|
user_state.played = True
|
||||||
if item.duration and item.ts is not None:
|
if item.duration and item.ts is not None:
|
||||||
user_state.percentage = item.ts / item.duration
|
user_state.percentage = item.ts / item.duration * 100
|
||||||
user_state.resume = True
|
user_state.resume = True
|
||||||
if item.type is None:
|
if item.type is None:
|
||||||
item_type = None
|
item_type = None
|
||||||
@@ -361,40 +411,37 @@ class TrimeMedia:
|
|||||||
"""
|
"""
|
||||||
拼装播放链接
|
拼装播放链接
|
||||||
"""
|
"""
|
||||||
if item.type == fnapi.Type.Episode:
|
if item.type == fnapi.Type.EPISODE:
|
||||||
return f"{host}/v/tv/episode/{item.guid}"
|
return f"{host}/tv/episode/{item.guid}"
|
||||||
elif item.type == fnapi.Type.Season:
|
elif item.type == fnapi.Type.SEASON:
|
||||||
return f"{host}/v/tv/season/{item.guid}"
|
return f"{host}/tv/season/{item.guid}"
|
||||||
elif item.type == fnapi.Type.Movie:
|
elif item.type == fnapi.Type.MOVIE:
|
||||||
return f"{host}/v/movie/{item.guid}"
|
return f"{host}/movie/{item.guid}"
|
||||||
elif item.type == fnapi.Type.TV:
|
elif item.type == fnapi.Type.TV:
|
||||||
return f"{host}/v/tv/{item.guid}"
|
return f"{host}/tv/{item.guid}"
|
||||||
else:
|
else:
|
||||||
# 其它类型走通用页面,由飞牛来判断
|
# 其它类型走通用页面,由飞牛来判断
|
||||||
return f"{host}/v/other/{item.guid}"
|
return f"{host}/other/{item.guid}"
|
||||||
|
|
||||||
def __build_media_server_play_item(
|
def __build_media_server_play_item(
|
||||||
self, item: fnapi.Item
|
self, item: fnapi.Item
|
||||||
) -> schemas.MediaServerPlayItem:
|
) -> schemas.MediaServerPlayItem:
|
||||||
"""
|
if item.type == fnapi.Type.EPISODE:
|
||||||
:params use_backdrop: 是否优先使用Backdrop类型的图片
|
|
||||||
"""
|
|
||||||
if item.type == fnapi.Type.Episode:
|
|
||||||
title = item.tv_title
|
title = item.tv_title
|
||||||
subtitle = f"S{item.season_number}:{item.episode_number} - {item.title}"
|
subtitle = f"S{item.season_number}:{item.episode_number} - {item.title}"
|
||||||
else:
|
else:
|
||||||
title = item.title
|
title = item.title
|
||||||
subtitle = "电影" if item.type == fnapi.Type.Movie else "视频"
|
subtitle = "电影" if item.type == fnapi.Type.MOVIE else "视频"
|
||||||
type = (
|
types = (
|
||||||
MediaType.MOVIE.value
|
MediaType.MOVIE.value
|
||||||
if item.type in [fnapi.Type.Movie, fnapi.Type.Video]
|
if item.type in [fnapi.Type.MOVIE, fnapi.Type.VIDEO]
|
||||||
else MediaType.TV.value
|
else MediaType.TV.value
|
||||||
)
|
)
|
||||||
return schemas.MediaServerPlayItem(
|
return schemas.MediaServerPlayItem(
|
||||||
id=item.guid,
|
id=item.guid,
|
||||||
title=title,
|
title=title,
|
||||||
subtitle=subtitle,
|
subtitle=subtitle,
|
||||||
type=type,
|
type=types,
|
||||||
image=f"{self._api.host}{item.poster}",
|
image=f"{self._api.host}{item.poster}",
|
||||||
link=self.__build_play_url(self._playhost or self._api.host, item),
|
link=self.__build_play_url(self._playhost or self._api.host, item),
|
||||||
percent=(
|
percent=(
|
||||||
@@ -421,22 +468,22 @@ class TrimeMedia:
|
|||||||
"""
|
"""
|
||||||
if not self.is_authenticated():
|
if not self.is_authenticated():
|
||||||
return None
|
return None
|
||||||
if (SIZE := limit) is None:
|
if (page_size := limit) is None:
|
||||||
SIZE = -1
|
page_size = -1
|
||||||
items = (
|
items = (
|
||||||
self._api.item_list(
|
self._api.item_list(
|
||||||
guid=parent,
|
guid=parent,
|
||||||
page=start_index + 1,
|
page=start_index + 1,
|
||||||
page_size=SIZE,
|
page_size=page_size,
|
||||||
type=[fnapi.Type.Movie, fnapi.Type.TV, fnapi.Type.Directory],
|
types=[fnapi.Type.MOVIE, fnapi.Type.TV, fnapi.Type.DIRECTORY],
|
||||||
)
|
)
|
||||||
or []
|
or []
|
||||||
)
|
)
|
||||||
for item in items:
|
for item in items:
|
||||||
if item.type == fnapi.Type.Directory:
|
if item.type == fnapi.Type.DIRECTORY:
|
||||||
for items in self.get_items(parent=item.guid):
|
for items in self.get_items(parent=item.guid):
|
||||||
yield items
|
yield items
|
||||||
elif item.type in [fnapi.Type.Movie, fnapi.Type.TV]:
|
elif item.type in [fnapi.Type.MOVIE, fnapi.Type.TV]:
|
||||||
yield self.__build_media_server_item(item)
|
yield self.__build_media_server_item(item)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -482,7 +529,7 @@ class TrimeMedia:
|
|||||||
self._api.item_list(
|
self._api.item_list(
|
||||||
page=1,
|
page=1,
|
||||||
page_size=max(100, num * 5),
|
page_size=max(100, num * 5),
|
||||||
type=[fnapi.Type.Movie, fnapi.Type.TV],
|
types=[fnapi.Type.MOVIE, fnapi.Type.TV],
|
||||||
)
|
)
|
||||||
or []
|
or []
|
||||||
)
|
)
|
||||||
@@ -505,7 +552,7 @@ class TrimeMedia:
|
|||||||
self._api.item_list(
|
self._api.item_list(
|
||||||
page=1,
|
page=1,
|
||||||
page_size=max(100, num * 5),
|
page_size=max(100, num * 5),
|
||||||
type=[fnapi.Type.Movie, fnapi.Type.TV],
|
types=[fnapi.Type.MOVIE, fnapi.Type.TV],
|
||||||
)
|
)
|
||||||
or []
|
or []
|
||||||
)
|
)
|
||||||
@@ -534,7 +581,7 @@ class TrimeMedia:
|
|||||||
|
|
||||||
def __is_library_blocked(self, library_guid: str):
|
def __is_library_blocked(self, library_guid: str):
|
||||||
if library := self._libraries.get(library_guid):
|
if library := self._libraries.get(library_guid):
|
||||||
if library.category == fnapi.Category.Others:
|
if library.category == fnapi.Category.OTHERS:
|
||||||
# 忽略这个库
|
# 忽略这个库
|
||||||
return True
|
return True
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user