add subscribe/files api

This commit is contained in:
jxxghp
2024-09-08 13:02:42 +08:00
parent 01505ceaa7
commit 34ae663d5a
13 changed files with 241 additions and 38 deletions

View File

@@ -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,

View File

@@ -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:
"""
发送消息

View File

@@ -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

View File

@@ -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):
"""
新增下载历史

View File

@@ -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):

View File

@@ -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:
"""

View File

@@ -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:
"""

View File

@@ -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]:
"""
发送列表类消息
"""

View File

@@ -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:

View File

@@ -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]:
"""
发送列表类消息
"""

View File

@@ -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:

View File

@@ -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]:
"""
发送媒体列表消息

View File

@@ -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]] = {}