mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-04-05 03:38:36 +08:00
add subscribe/files api
This commit is contained in:
@@ -398,6 +398,20 @@ def user_subscribes(
|
||||
return Subscribe.list_by_username(db, username)
|
||||
|
||||
|
||||
@router.get("/files/{subscribe_id}", summary="订阅相关文件信息", response_model=List[schemas.SubscrbieInfo])
|
||||
def subscribe_files(
|
||||
subscribe_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
订阅相关文件信息
|
||||
"""
|
||||
subscribe = Subscribe.get(db, subscribe_id)
|
||||
if subscribe:
|
||||
return SubscribeChain().subscribe_files_info(subscribe)
|
||||
return schemas.SubscrbieInfo()
|
||||
|
||||
|
||||
@router.get("/{subscribe_id}", summary="订阅详情", response_model=schemas.Subscribe)
|
||||
def read_subscribe(
|
||||
subscribe_id: int,
|
||||
|
||||
@@ -459,6 +459,14 @@ class ChainBase(metaclass=ABCMeta):
|
||||
"""
|
||||
return self.run_module("media_exists", mediainfo=mediainfo, itemid=itemid)
|
||||
|
||||
def media_files(self, mediainfo: MediaInfo) -> Optional[List[FileItem]]:
|
||||
"""
|
||||
获取媒体文件清单
|
||||
:param mediainfo: 识别的媒体信息
|
||||
:return: 媒体文件列表
|
||||
"""
|
||||
return self.run_module("media_files", mediainfo=mediainfo)
|
||||
|
||||
def post_message(self, message: Notification) -> None:
|
||||
"""
|
||||
发送消息
|
||||
|
||||
@@ -9,12 +9,14 @@ from app.chain import ChainBase
|
||||
from app.chain.download import DownloadChain
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.search import SearchChain
|
||||
from app.chain.tmdb import TmdbChain
|
||||
from app.chain.torrents import TorrentsChain
|
||||
from app.core.config import settings
|
||||
from app.core.context import TorrentInfo, Context, MediaInfo
|
||||
from app.core.event import eventmanager, Event, EventManager
|
||||
from app.core.meta import MetaBase
|
||||
from app.core.metainfo import MetaInfo
|
||||
from app.db.downloadhistory_oper import DownloadHistoryOper
|
||||
from app.db.models.subscribe import Subscribe
|
||||
from app.db.site_oper import SiteOper
|
||||
from app.db.subscribe_oper import SubscribeOper
|
||||
@@ -24,7 +26,7 @@ from app.helper.message import MessageHelper
|
||||
from app.helper.subscribe import SubscribeHelper
|
||||
from app.helper.torrent import TorrentHelper
|
||||
from app.log import logger
|
||||
from app.schemas import NotExistMediaInfo, Notification
|
||||
from app.schemas import NotExistMediaInfo, Notification, SubscrbieInfo, SubscribeEpisodeInfo
|
||||
from app.schemas.types import MediaType, SystemConfigKey, MessageChannel, NotificationType, EventType
|
||||
|
||||
|
||||
@@ -36,12 +38,14 @@ class SubscribeChain(ChainBase):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.downloadchain = DownloadChain()
|
||||
self.downloadhis = DownloadHistoryOper()
|
||||
self.searchchain = SearchChain()
|
||||
self.subscribeoper = SubscribeOper()
|
||||
self.subscribehistoryoper = SubscribeHistoryOper()
|
||||
self.subscribehelper = SubscribeHelper()
|
||||
self.torrentschain = TorrentsChain()
|
||||
self.mediachain = MediaChain()
|
||||
self.tmdbchain = TmdbChain()
|
||||
self.message = MessageHelper()
|
||||
self.systemconfig = SystemConfigOper()
|
||||
self.torrenthelper = TorrentHelper()
|
||||
@@ -983,7 +987,8 @@ class SubscribeChain(ChainBase):
|
||||
begin_season: int,
|
||||
total_episode: int,
|
||||
start_episode: int,
|
||||
downloaded_episodes: List[int] = None):
|
||||
downloaded_episodes: List[int] = None
|
||||
) -> Dict[Union[int, str], Dict[int, NotExistMediaInfo]]:
|
||||
"""
|
||||
根据订阅开始集数和总集数,结合TMDB信息计算当前订阅的缺失集数
|
||||
:param subscribe_name: 订阅名称
|
||||
@@ -1118,7 +1123,7 @@ class SubscribeChain(ChainBase):
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def __get_default_subscribe_config(mtype: MediaType, default_config_key: str):
|
||||
def __get_default_subscribe_config(mtype: MediaType, default_config_key: str) -> Optional[str]:
|
||||
"""
|
||||
获取默认订阅配置
|
||||
"""
|
||||
@@ -1155,3 +1160,110 @@ class SubscribeChain(ChainBase):
|
||||
"min_seeders": default_rule.get("min_seeders"),
|
||||
"min_seeders_time": default_rule.get("min_seeders_time"),
|
||||
}
|
||||
|
||||
def subscribe_files_info(self, subscribe: Subscribe) -> Optional[SubscrbieInfo]:
|
||||
"""
|
||||
订阅相关的下载和文件信息
|
||||
"""
|
||||
if not subscribe:
|
||||
return
|
||||
|
||||
# 返回订阅数据
|
||||
subscribe_info = SubscrbieInfo(
|
||||
id=subscribe.id,
|
||||
name=subscribe.name,
|
||||
year=subscribe.year,
|
||||
type=subscribe.type,
|
||||
tmdbid=subscribe.tmdbid,
|
||||
doubanid=subscribe.doubanid,
|
||||
season=subscribe.season,
|
||||
poster=subscribe.poster,
|
||||
backdrop=subscribe.backdrop,
|
||||
vote=subscribe.vote,
|
||||
description=subscribe.description,
|
||||
episodes_info={}
|
||||
)
|
||||
|
||||
# 所有集的数据
|
||||
episodes_info = {}
|
||||
if subscribe.tmdbid and subscribe.type == MediaType.TV.value:
|
||||
# 查询TMDB中的集信息
|
||||
tmdb_episodes = self.tmdbchain.tmdb_episodes(
|
||||
tmdbid=subscribe.tmdb_id,
|
||||
season=subscribe.season
|
||||
)
|
||||
if tmdb_episodes:
|
||||
for episode in tmdb_episodes:
|
||||
episode_info = SubscribeEpisodeInfo()
|
||||
episodes_info.title = episode.name
|
||||
episodes_info.description = episode.overview
|
||||
episodes_info.backdrop = f"https://{settings.TMDB_IMAGE_DOMAIN}/t/p/w500${episode.still_path}"
|
||||
episodes_info[episode.episode_number] = episode_info
|
||||
elif subscribe.type == MediaType.TV.value:
|
||||
# 根据开始结束集计算集信息
|
||||
for i in range(subscribe.start_episode or 1, subscribe.total_episode + 1):
|
||||
episode_info = SubscribeEpisodeInfo()
|
||||
episode_info.title = f'第 {i} 集'
|
||||
episodes_info[i] = episode_info
|
||||
else:
|
||||
# 电影
|
||||
episode_info = SubscribeEpisodeInfo()
|
||||
episode_info.title = subscribe.name
|
||||
episodes_info[0] = episode_info
|
||||
|
||||
# 所有下载记录
|
||||
download_his = self.downloadhis.get_by_mediaid(tmdbid=subscribe.tmdbid, doubanid=subscribe.doubanid)
|
||||
if download_his:
|
||||
for his in download_his:
|
||||
# 种子链接
|
||||
torrent_url = f"【{his.torrent_site}】{his.torrent_name}"
|
||||
# 查询下载文件
|
||||
files = self.downloadhis.get_files_by_hash(his.hash)
|
||||
if files:
|
||||
for file in files:
|
||||
# 识别文件名
|
||||
file_meta = MetaInfo(file.filepath)
|
||||
if subscribe.type == MediaType.TV.value:
|
||||
episode_number = file_meta.begin_episode
|
||||
if episode_number and episodes_info.get(episode_number):
|
||||
episodes_info[episode_number].download_file = file.fullpath
|
||||
episodes_info[episode_number].torrent = torrent_url
|
||||
else:
|
||||
episodes_info[0].download_file = file.fullpath
|
||||
episodes_info[0].torrent = torrent_url
|
||||
|
||||
# 生成元数据
|
||||
meta = MetaInfo(subscribe.name)
|
||||
meta.year = subscribe.year
|
||||
meta.begin_season = subscribe.season or None
|
||||
try:
|
||||
meta.type = MediaType(subscribe.type)
|
||||
except ValueError:
|
||||
logger.error(f'订阅 {subscribe.name} 类型错误:{subscribe.type}')
|
||||
return subscribe_info
|
||||
# 识别媒体信息
|
||||
mediainfo: MediaInfo = self.recognize_media(meta=meta, mtype=meta.type,
|
||||
tmdbid=subscribe.tmdbid,
|
||||
doubanid=subscribe.doubanid,
|
||||
cache=False)
|
||||
if not mediainfo:
|
||||
logger.warn(
|
||||
f'未识别到媒体信息,标题:{subscribe.name},tmdbid:{subscribe.tmdbid},doubanid:{subscribe.doubanid}')
|
||||
return subscribe_info
|
||||
|
||||
# 所有媒体库文件记录
|
||||
library_fileitems = self.media_files(mediainfo)
|
||||
if library_fileitems:
|
||||
for fileitem in library_fileitems:
|
||||
# 识别文件名
|
||||
file_meta = MetaInfo(fileitem.path)
|
||||
if subscribe.type == MediaType.TV.value:
|
||||
episode_number = file_meta.begin_episode
|
||||
if episode_number and episodes_info.get(episode_number):
|
||||
episodes_info[episode_number].library_file = fileitem.path
|
||||
else:
|
||||
episodes_info[0].library_file = fileitem.path
|
||||
|
||||
# 更新订阅信息
|
||||
subscribe_info.episodes_info = episodes_info
|
||||
return subscribe_info
|
||||
|
||||
@@ -23,6 +23,14 @@ class DownloadHistoryOper(DbOper):
|
||||
"""
|
||||
return DownloadHistory.get_by_hash(self._db, download_hash)
|
||||
|
||||
def get_by_mediaid(self, tmdbid: int, doubanid: str) -> List[DownloadHistory]:
|
||||
"""
|
||||
按媒体ID查询下载记录
|
||||
:param tmdbid: tmdbid
|
||||
:param doubanid: doubanid
|
||||
"""
|
||||
return DownloadHistory.get_by_mediaid(self._db, tmdbid=tmdbid, doubanid=doubanid)
|
||||
|
||||
def add(self, **kwargs):
|
||||
"""
|
||||
新增下载历史
|
||||
|
||||
@@ -53,6 +53,12 @@ class DownloadHistory(Base):
|
||||
def get_by_hash(db: Session, download_hash: str):
|
||||
return db.query(DownloadHistory).filter(DownloadHistory.download_hash == download_hash).first()
|
||||
|
||||
@staticmethod
|
||||
@db_query
|
||||
def get_by_mediaid(db: Session, tmdbid: int, doubanid: str):
|
||||
return db.query(DownloadHistory).filter(DownloadHistory.tmdbid == tmdbid,
|
||||
DownloadHistory.doubanid == doubanid).all()
|
||||
|
||||
@staticmethod
|
||||
@db_query
|
||||
def list_by_page(db: Session, page: int = 1, count: int = 30):
|
||||
|
||||
@@ -1043,12 +1043,12 @@ class FileManagerModule(_ModuleBase):
|
||||
else:
|
||||
return Path(render_str)
|
||||
|
||||
def media_exists(self, mediainfo: MediaInfo, **kwargs) -> Optional[ExistMediaInfo]:
|
||||
def media_files(self, mediainfo: MediaInfo) -> List[FileItem]:
|
||||
"""
|
||||
判断媒体文件是否存在于文件系统(网盘或本地文件),只支持标准媒体库结构
|
||||
:param mediainfo: 识别的媒体信息
|
||||
:return: 如不存在返回None,存在时返回信息,包括每季已存在所有集{type: movie/tv, seasons: {season: [episodes]}}
|
||||
获取对应媒体的媒体库文件列表
|
||||
:param mediainfo: 媒体信息
|
||||
"""
|
||||
ret_fileitems = []
|
||||
# 检查本地媒体库
|
||||
dest_dirs = DirectoryHelper().get_library_dirs()
|
||||
# 检查每一个媒体库目录
|
||||
@@ -1059,8 +1059,6 @@ class FileManagerModule(_ModuleBase):
|
||||
continue
|
||||
# 媒体分类路径
|
||||
dir_path = self.__get_dest_dir(mediainfo=mediainfo, target_dir=dest_dir)
|
||||
if not storage_oper.get_item(dir_path):
|
||||
continue
|
||||
# 重命名格式
|
||||
rename_format = settings.TV_RENAME_FORMAT \
|
||||
if mediainfo.type == MediaType.TV else settings.MOVIE_RENAME_FORMAT
|
||||
@@ -1079,30 +1077,45 @@ class FileManagerModule(_ModuleBase):
|
||||
if not media_path.exists():
|
||||
continue
|
||||
# 检索媒体文件
|
||||
media_files = SystemUtils.list_files(directory=media_path, extensions=settings.RMT_MEDIAEXT)
|
||||
if not media_files:
|
||||
fileitem = storage_oper.get_item(media_path)
|
||||
if not fileitem:
|
||||
continue
|
||||
if mediainfo.type == MediaType.MOVIE:
|
||||
# 电影存在任何文件为存在
|
||||
logger.info(f"{mediainfo.title_year} 在本地文件系统中找到了")
|
||||
return ExistMediaInfo(type=MediaType.MOVIE)
|
||||
else:
|
||||
# 电视剧检索集数
|
||||
seasons: Dict[int, list] = {}
|
||||
for media_file in media_files:
|
||||
file_meta = MetaInfo(media_file.stem)
|
||||
season_index = file_meta.begin_season or 1
|
||||
episode_index = file_meta.begin_episode
|
||||
if not episode_index:
|
||||
continue
|
||||
if season_index not in seasons:
|
||||
seasons[season_index] = []
|
||||
media_files = storage_oper.list(fileitem)
|
||||
if media_files:
|
||||
ret_fileitems.extend(media_files)
|
||||
return ret_fileitems
|
||||
|
||||
def media_exists(self, mediainfo: MediaInfo, **kwargs) -> Optional[ExistMediaInfo]:
|
||||
"""
|
||||
判断媒体文件是否存在于文件系统(网盘或本地文件),只支持标准媒体库结构
|
||||
:param mediainfo: 识别的媒体信息
|
||||
:return: 如不存在返回None,存在时返回信息,包括每季已存在所有集{type: movie/tv, seasons: {season: [episodes]}}
|
||||
"""
|
||||
# 检查媒体库
|
||||
fileitems = self.media_files(mediainfo)
|
||||
if not fileitems:
|
||||
return None
|
||||
|
||||
if mediainfo.type == MediaType.MOVIE:
|
||||
# 电影存在任何文件为存在
|
||||
logger.info(f"{mediainfo.title_year} 在本地文件系统中找到了")
|
||||
return ExistMediaInfo(type=MediaType.MOVIE)
|
||||
else:
|
||||
# 电视剧检索集数
|
||||
seasons: Dict[int, list] = {}
|
||||
for fileitem in fileitems:
|
||||
file_meta = MetaInfo(fileitem.basename)
|
||||
season_index = file_meta.begin_season or 1
|
||||
episode_index = file_meta.begin_episode
|
||||
if not episode_index:
|
||||
continue
|
||||
if season_index not in seasons:
|
||||
seasons[season_index] = []
|
||||
if episode_index not in seasons[season_index]:
|
||||
seasons[season_index].append(episode_index)
|
||||
# 返回剧集情况
|
||||
logger.info(f"{mediainfo.title_year} 在本地文件系统中找到了这些季集:{seasons}")
|
||||
return ExistMediaInfo(type=MediaType.TV, seasons=seasons)
|
||||
# 不存在
|
||||
return None
|
||||
# 返回剧集情况
|
||||
logger.info(f"{mediainfo.title_year} 在本地文件系统中找到了这些季集:{seasons}")
|
||||
return ExistMediaInfo(type=MediaType.TV, seasons=seasons)
|
||||
|
||||
def __delete_version_files(self, target_storage: str, path: Path) -> bool:
|
||||
"""
|
||||
|
||||
@@ -248,7 +248,7 @@ class SlackModule(_ModuleBase, _MessageBase):
|
||||
continue
|
||||
client: Slack = self.get_client(conf.name)
|
||||
if client:
|
||||
client.send_meidas_msg(title=message.title, medias=medias, userid=message.userid)
|
||||
client.send_medias_msg(title=message.title, medias=medias, userid=message.userid)
|
||||
|
||||
def post_torrents_message(self, message: Notification, torrents: List[Context]) -> None:
|
||||
"""
|
||||
|
||||
@@ -168,7 +168,7 @@ class Slack:
|
||||
logger.error(f"Slack消息发送失败: {msg_e}")
|
||||
return False, str(msg_e)
|
||||
|
||||
def send_meidas_msg(self, medias: List[MediaInfo], userid: str = "", title: str = "") -> Optional[bool]:
|
||||
def send_medias_msg(self, medias: List[MediaInfo], userid: str = "", title: str = "") -> Optional[bool]:
|
||||
"""
|
||||
发送列表类消息
|
||||
"""
|
||||
|
||||
@@ -117,7 +117,7 @@ class SynologyChatModule(_ModuleBase, _MessageBase):
|
||||
continue
|
||||
client: SynologyChat = self.get_client(conf.name)
|
||||
if client:
|
||||
client.send_meidas_msg(title=message.title, medias=medias,
|
||||
client.send_medias_msg(title=message.title, medias=medias,
|
||||
userid=message.userid)
|
||||
|
||||
def post_torrents_message(self, message: Notification, torrents: List[Context]) -> None:
|
||||
|
||||
@@ -90,7 +90,7 @@ class SynologyChat:
|
||||
logger.error(f"SynologyChat发送消息错误:{str(msg_e)}")
|
||||
return False
|
||||
|
||||
def send_meidas_msg(self, medias: List[MediaInfo], userid: str = "", title: str = "") -> Optional[bool]:
|
||||
def send_medias_msg(self, medias: List[MediaInfo], userid: str = "", title: str = "") -> Optional[bool]:
|
||||
"""
|
||||
发送列表类消息
|
||||
"""
|
||||
|
||||
@@ -163,7 +163,7 @@ class TelegramModule(_ModuleBase, _MessageBase):
|
||||
continue
|
||||
client: Telegram = self.get_client(conf.name)
|
||||
if client:
|
||||
client.send_meidas_msg(title=message.title, medias=medias,
|
||||
client.send_medias_msg(title=message.title, medias=medias,
|
||||
userid=message.userid, link=message.link)
|
||||
|
||||
def post_torrents_message(self, message: Notification, torrents: List[Context]) -> None:
|
||||
|
||||
@@ -114,7 +114,7 @@ class Telegram:
|
||||
logger.error(f"发送消息失败:{msg_e}")
|
||||
return False
|
||||
|
||||
def send_meidas_msg(self, medias: List[MediaInfo], userid: str = "",
|
||||
def send_medias_msg(self, medias: List[MediaInfo], userid: str = "",
|
||||
title: str = "", link: str = None) -> Optional[bool]:
|
||||
"""
|
||||
发送媒体列表消息
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import json
|
||||
from typing import Optional, List
|
||||
from typing import Optional, List, Dict
|
||||
|
||||
from pydantic import BaseModel, validator
|
||||
|
||||
@@ -79,3 +79,45 @@ class Subscribe(BaseModel):
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class SubscribeEpisodeInfo(BaseModel):
|
||||
# 种子地址
|
||||
torrent: Optional[str] = None
|
||||
# 下载文件路程
|
||||
download_file: Optional[str] = None
|
||||
# 媒体库文件路径
|
||||
library_file: Optional[str] = None
|
||||
# 标题
|
||||
title: Optional[str] = None
|
||||
# 描述
|
||||
description: Optional[str] = None
|
||||
# 背景图
|
||||
backdrop: Optional[str] = None
|
||||
|
||||
|
||||
class SubscrbieInfo(BaseModel):
|
||||
# 订阅ID
|
||||
id: Optional[int] = None
|
||||
# 订阅名称
|
||||
name: Optional[str] = None
|
||||
# 订阅年份
|
||||
year: Optional[str] = None
|
||||
# 订阅类型 电影/电视剧
|
||||
type: Optional[str] = None
|
||||
# 媒体ID
|
||||
tmdbid: Optional[int] = None
|
||||
doubanid: Optional[str] = None
|
||||
bangumiid: Optional[int] = None
|
||||
# 季号
|
||||
season: Optional[int] = None
|
||||
# 海报
|
||||
poster: Optional[str] = None
|
||||
# 背景图
|
||||
backdrop: Optional[str] = None
|
||||
# 评分
|
||||
vote: Optional[int] = 0
|
||||
# 描述
|
||||
description: Optional[str] = None
|
||||
# 集信息 {集号: {download: 文件路径,library: 文件路径, backdrop: url, title: 标题, description: 描述}}
|
||||
episodes_info: Optional[Dict[int, SubscribeEpisodeInfo]] = {}
|
||||
|
||||
Reference in New Issue
Block a user