feat 支持多媒体服务器同时使用
This commit is contained in:
parent
78dab04c96
commit
fd9eef2089
@ -132,7 +132,7 @@ docker pull jxxghp/moviepilot:latest
|
|||||||
|
|
||||||
- **DOWNLOADER_MONITOR:** 下载器监控,`true`/`false`,默认为`true`,开启后下载完成时才会自动整理入库
|
- **DOWNLOADER_MONITOR:** 下载器监控,`true`/`false`,默认为`true`,开启后下载完成时才会自动整理入库
|
||||||
|
|
||||||
- **MEDIASERVER:** 媒体服务器,支持`emby`/`jellyfin`/`plex`,同时还需要配置对应媒体服务器的环境变量,非对应媒体服务器的变量可删除,推荐使用`emby`
|
- **MEDIASERVER:** 媒体服务器,支持`emby`/`jellyfin`/`plex`,同时开启多个使用`,`分隔。还需要配置对应媒体服务器的环境变量,非对应媒体服务器的变量可删除,推荐使用`emby`
|
||||||
|
|
||||||
- `emby`设置项:
|
- `emby`设置项:
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, List
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
from requests import Session
|
from requests import Session
|
||||||
@ -24,14 +24,16 @@ def statistic(db: Session = Depends(get_db),
|
|||||||
"""
|
"""
|
||||||
查询媒体数量统计信息
|
查询媒体数量统计信息
|
||||||
"""
|
"""
|
||||||
media_statistic = DashboardChain(db).media_statistic()
|
media_statistics: Optional[List[schemas.Statistic]] = DashboardChain(db).media_statistic()
|
||||||
if media_statistic:
|
if media_statistics:
|
||||||
return schemas.Statistic(
|
# 汇总各媒体库统计信息
|
||||||
movie_count=media_statistic.movie_count,
|
ret_statistic = schemas.Statistic()
|
||||||
tv_count=media_statistic.tv_count,
|
for media_statistic in media_statistics:
|
||||||
episode_count=media_statistic.episode_count,
|
ret_statistic.movie_count += media_statistic.movie_count
|
||||||
user_count=media_statistic.user_count
|
ret_statistic.tv_count += media_statistic.tv_count
|
||||||
)
|
ret_statistic.episode_count += media_statistic.episode_count
|
||||||
|
ret_statistic.user_count += media_statistic.user_count
|
||||||
|
return ret_statistic
|
||||||
else:
|
else:
|
||||||
return schemas.Statistic()
|
return schemas.Statistic()
|
||||||
|
|
||||||
|
@ -336,14 +336,14 @@ class ChainBase(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
return self.run_module("media_exists", mediainfo=mediainfo, itemid=itemid)
|
return self.run_module("media_exists", mediainfo=mediainfo, itemid=itemid)
|
||||||
|
|
||||||
def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> Optional[bool]:
|
def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
刷新媒体库
|
刷新媒体库
|
||||||
:param mediainfo: 识别的媒体信息
|
:param mediainfo: 识别的媒体信息
|
||||||
:param file_path: 文件路径
|
:param file_path: 文件路径
|
||||||
:return: 成功或失败
|
:return: 成功或失败
|
||||||
"""
|
"""
|
||||||
return self.run_module("refresh_mediaserver", mediainfo=mediainfo, file_path=file_path)
|
self.run_module("refresh_mediaserver", mediainfo=mediainfo, file_path=file_path)
|
||||||
|
|
||||||
def post_message(self, message: Notification) -> None:
|
def post_message(self, message: Notification) -> None:
|
||||||
"""
|
"""
|
||||||
@ -391,22 +391,22 @@ class ChainBase(metaclass=ABCMeta):
|
|||||||
:param mediainfo: 识别的媒体信息
|
:param mediainfo: 识别的媒体信息
|
||||||
:return: 成功或失败
|
:return: 成功或失败
|
||||||
"""
|
"""
|
||||||
return self.run_module("scrape_metadata", path=path, mediainfo=mediainfo)
|
self.run_module("scrape_metadata", path=path, mediainfo=mediainfo)
|
||||||
|
|
||||||
def register_commands(self, commands: Dict[str, dict]) -> None:
|
def register_commands(self, commands: Dict[str, dict]) -> None:
|
||||||
"""
|
"""
|
||||||
注册菜单命令
|
注册菜单命令
|
||||||
"""
|
"""
|
||||||
return self.run_module("register_commands", commands=commands)
|
self.run_module("register_commands", commands=commands)
|
||||||
|
|
||||||
def scheduler_job(self) -> None:
|
def scheduler_job(self) -> None:
|
||||||
"""
|
"""
|
||||||
定时任务,每10分钟调用一次,模块实现该接口以实现定时服务
|
定时任务,每10分钟调用一次,模块实现该接口以实现定时服务
|
||||||
"""
|
"""
|
||||||
return self.run_module("scheduler_job")
|
self.run_module("scheduler_job")
|
||||||
|
|
||||||
def clear_cache(self) -> None:
|
def clear_cache(self) -> None:
|
||||||
"""
|
"""
|
||||||
清理缓存,模块实现该接口响应清理缓存事件
|
清理缓存,模块实现该接口响应清理缓存事件
|
||||||
"""
|
"""
|
||||||
return self.run_module("clear_cache")
|
self.run_module("clear_cache")
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
from app.chain import ChainBase
|
from app.chain import ChainBase
|
||||||
|
|
||||||
@ -6,7 +8,7 @@ class DashboardChain(ChainBase):
|
|||||||
"""
|
"""
|
||||||
各类仪表板统计处理链
|
各类仪表板统计处理链
|
||||||
"""
|
"""
|
||||||
def media_statistic(self) -> schemas.Statistic:
|
def media_statistic(self) -> Optional[List[schemas.Statistic]]:
|
||||||
"""
|
"""
|
||||||
媒体数量统计
|
媒体数量统计
|
||||||
"""
|
"""
|
||||||
|
@ -23,23 +23,23 @@ class MediaServerChain(ChainBase):
|
|||||||
def __init__(self, db: Session = None):
|
def __init__(self, db: Session = None):
|
||||||
super().__init__(db)
|
super().__init__(db)
|
||||||
|
|
||||||
def librarys(self) -> List[schemas.MediaServerLibrary]:
|
def librarys(self, server: str) -> List[schemas.MediaServerLibrary]:
|
||||||
"""
|
"""
|
||||||
获取媒体服务器所有媒体库
|
获取媒体服务器所有媒体库
|
||||||
"""
|
"""
|
||||||
return self.run_module("mediaserver_librarys")
|
return self.run_module("mediaserver_librarys", server=server)
|
||||||
|
|
||||||
def items(self, library_id: Union[str, int]) -> Generator:
|
def items(self, server: str, library_id: Union[str, int]) -> Generator:
|
||||||
"""
|
"""
|
||||||
获取媒体服务器所有项目
|
获取媒体服务器所有项目
|
||||||
"""
|
"""
|
||||||
return self.run_module("mediaserver_items", library_id=library_id)
|
return self.run_module("mediaserver_items", server=server, library_id=library_id)
|
||||||
|
|
||||||
def episodes(self, item_id: Union[str, int]) -> List[schemas.MediaServerSeasonInfo]:
|
def episodes(self, server: str, item_id: Union[str, int]) -> List[schemas.MediaServerSeasonInfo]:
|
||||||
"""
|
"""
|
||||||
获取媒体服务器剧集信息
|
获取媒体服务器剧集信息
|
||||||
"""
|
"""
|
||||||
return self.run_module("mediaserver_tv_episodes", item_id=item_id)
|
return self.run_module("mediaserver_tv_episodes", server=server, item_id=item_id)
|
||||||
|
|
||||||
def remote_sync(self, channel: MessageChannel, userid: Union[int, str]):
|
def remote_sync(self, channel: MessageChannel, userid: Union[int, str]):
|
||||||
"""
|
"""
|
||||||
@ -59,7 +59,6 @@ class MediaServerChain(ChainBase):
|
|||||||
# 媒体服务器同步使用独立的会话
|
# 媒体服务器同步使用独立的会话
|
||||||
_db = SessionFactory()
|
_db = SessionFactory()
|
||||||
_dbOper = MediaServerOper(_db)
|
_dbOper = MediaServerOper(_db)
|
||||||
logger.info("开始同步媒体库数据 ...")
|
|
||||||
# 汇总统计
|
# 汇总统计
|
||||||
total_count = 0
|
total_count = 0
|
||||||
# 清空登记薄
|
# 清空登记薄
|
||||||
@ -67,35 +66,42 @@ class MediaServerChain(ChainBase):
|
|||||||
# 同步黑名单
|
# 同步黑名单
|
||||||
sync_blacklist = settings.MEDIASERVER_SYNC_BLACKLIST.split(
|
sync_blacklist = settings.MEDIASERVER_SYNC_BLACKLIST.split(
|
||||||
",") if settings.MEDIASERVER_SYNC_BLACKLIST else []
|
",") if settings.MEDIASERVER_SYNC_BLACKLIST else []
|
||||||
for library in self.librarys():
|
# 设置的媒体服务器
|
||||||
if library.name in sync_blacklist:
|
if not settings.MEDIASERVER:
|
||||||
|
return
|
||||||
|
mediaservers = settings.MEDIASERVER.split(",")
|
||||||
|
# 遍历媒体服务器
|
||||||
|
for mediaserver in mediaservers:
|
||||||
|
logger.info(f"开始同步媒体库 {mediaserver} 的数据 ...")
|
||||||
|
for library in self.librarys(mediaserver):
|
||||||
# 同步黑名单 跳过
|
# 同步黑名单 跳过
|
||||||
continue
|
if library.name in sync_blacklist:
|
||||||
logger.info(f"正在同步媒体库 {library.name} ...")
|
|
||||||
library_count = 0
|
|
||||||
for item in self.items(library.id):
|
|
||||||
if not item:
|
|
||||||
continue
|
continue
|
||||||
if not item.item_id:
|
logger.info(f"正在同步 {mediaserver} 媒体库 {library.name} ...")
|
||||||
continue
|
library_count = 0
|
||||||
# 计数
|
for item in self.items(mediaserver, library.id):
|
||||||
library_count += 1
|
if not item:
|
||||||
seasoninfo = {}
|
continue
|
||||||
# 类型
|
if not item.item_id:
|
||||||
item_type = "电视剧" if item.item_type in ['Series', 'show'] else "电影"
|
continue
|
||||||
if item_type == "电视剧":
|
# 计数
|
||||||
# 查询剧集信息
|
library_count += 1
|
||||||
espisodes_info = self.episodes(item.item_id) or []
|
seasoninfo = {}
|
||||||
for episode in espisodes_info:
|
# 类型
|
||||||
seasoninfo[episode.season] = episode.episodes
|
item_type = "电视剧" if item.item_type in ['Series', 'show'] else "电影"
|
||||||
# 插入数据
|
if item_type == "电视剧":
|
||||||
item_dict = item.dict()
|
# 查询剧集信息
|
||||||
item_dict['seasoninfo'] = json.dumps(seasoninfo)
|
espisodes_info = self.episodes(mediaserver, item.item_id) or []
|
||||||
item_dict['item_type'] = item_type
|
for episode in espisodes_info:
|
||||||
_dbOper.add(**item_dict)
|
seasoninfo[episode.season] = episode.episodes
|
||||||
logger.info(f"媒体库 {library.name} 同步完成,共同步数量:{library_count}")
|
# 插入数据
|
||||||
# 总数累加
|
item_dict = item.dict()
|
||||||
total_count += library_count
|
item_dict['seasoninfo'] = json.dumps(seasoninfo)
|
||||||
|
item_dict['item_type'] = item_type
|
||||||
|
_dbOper.add(**item_dict)
|
||||||
|
logger.info(f"{mediaserver} 媒体库 {library.name} 同步完成,共同步数量:{library_count}")
|
||||||
|
# 总数累加
|
||||||
|
total_count += library_count
|
||||||
# 关闭数据库连接
|
# 关闭数据库连接
|
||||||
if _db:
|
if _db:
|
||||||
_db.close()
|
_db.close()
|
||||||
|
@ -76,7 +76,7 @@ class Settings(BaseSettings):
|
|||||||
AUTH_SITE: str = ""
|
AUTH_SITE: str = ""
|
||||||
# 交互搜索自动下载用户ID,使用,分割
|
# 交互搜索自动下载用户ID,使用,分割
|
||||||
AUTO_DOWNLOAD_USER: str = None
|
AUTO_DOWNLOAD_USER: str = None
|
||||||
# 消息通知渠道 telegram/wechat/slack
|
# 消息通知渠道 telegram/wechat/slack,多个通知渠道用,分隔
|
||||||
MESSAGER: str = "telegram"
|
MESSAGER: str = "telegram"
|
||||||
# WeChat企业ID
|
# WeChat企业ID
|
||||||
WECHAT_CORPID: str = None
|
WECHAT_CORPID: str = None
|
||||||
@ -142,7 +142,7 @@ class Settings(BaseSettings):
|
|||||||
DOWNLOAD_CATEGORY: bool = False
|
DOWNLOAD_CATEGORY: bool = False
|
||||||
# 下载站点字幕
|
# 下载站点字幕
|
||||||
DOWNLOAD_SUBTITLE: bool = True
|
DOWNLOAD_SUBTITLE: bool = True
|
||||||
# 媒体服务器 emby/jellyfin/plex
|
# 媒体服务器 emby/jellyfin/plex,多个媒体服务器,分割
|
||||||
MEDIASERVER: str = "emby"
|
MEDIASERVER: str = "emby"
|
||||||
# 入库刷新媒体库
|
# 入库刷新媒体库
|
||||||
REFRESH_MEDIASERVER: bool = True
|
REFRESH_MEDIASERVER: bool = True
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import json
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Tuple, Union, Any, List, Generator
|
from typing import Optional, Tuple, Union, Any, List, Generator
|
||||||
|
|
||||||
@ -41,7 +40,7 @@ class EmbyModule(_ModuleBase):
|
|||||||
# Emby认证
|
# Emby认证
|
||||||
return self.emby.authenticate(name, password)
|
return self.emby.authenticate(name, password)
|
||||||
|
|
||||||
def webhook_parser(self, body: Any, form: Any, args: Any) -> WebhookEventInfo:
|
def webhook_parser(self, body: Any, form: Any, args: Any) -> Optional[WebhookEventInfo]:
|
||||||
"""
|
"""
|
||||||
解析Webhook报文体
|
解析Webhook报文体
|
||||||
:param body: 请求体
|
:param body: 请求体
|
||||||
@ -49,11 +48,7 @@ class EmbyModule(_ModuleBase):
|
|||||||
:param args: 请求参数
|
:param args: 请求参数
|
||||||
:return: 字典,解析为消息时需要包含:title、text、image
|
:return: 字典,解析为消息时需要包含:title、text、image
|
||||||
"""
|
"""
|
||||||
if form and form.get("data"):
|
return self.emby.get_webhook_message(form, args)
|
||||||
result = form.get("data")
|
|
||||||
else:
|
|
||||||
result = json.dumps(dict(args))
|
|
||||||
return self.emby.get_webhook_message(result)
|
|
||||||
|
|
||||||
def media_exists(self, mediainfo: MediaInfo, itemid: str = None) -> Optional[ExistMediaInfo]:
|
def media_exists(self, mediainfo: MediaInfo, itemid: str = None) -> Optional[ExistMediaInfo]:
|
||||||
"""
|
"""
|
||||||
@ -87,7 +82,7 @@ class EmbyModule(_ModuleBase):
|
|||||||
logger.info(f"{mediainfo.title_year} 媒体库中已存在:{tvs}")
|
logger.info(f"{mediainfo.title_year} 媒体库中已存在:{tvs}")
|
||||||
return ExistMediaInfo(type=MediaType.TV, seasons=tvs)
|
return ExistMediaInfo(type=MediaType.TV, seasons=tvs)
|
||||||
|
|
||||||
def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> Optional[bool]:
|
def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
刷新媒体库
|
刷新媒体库
|
||||||
:param mediainfo: 识别的媒体信息
|
:param mediainfo: 识别的媒体信息
|
||||||
@ -103,25 +98,27 @@ class EmbyModule(_ModuleBase):
|
|||||||
target_path=file_path
|
target_path=file_path
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
return self.emby.refresh_library_by_items(items)
|
self.emby.refresh_library_by_items(items)
|
||||||
|
|
||||||
def media_statistic(self) -> schemas.Statistic:
|
def media_statistic(self) -> List[schemas.Statistic]:
|
||||||
"""
|
"""
|
||||||
媒体数量统计
|
媒体数量统计
|
||||||
"""
|
"""
|
||||||
media_statistic = self.emby.get_medias_count()
|
media_statistic = self.emby.get_medias_count()
|
||||||
user_count = self.emby.get_user_count()
|
user_count = self.emby.get_user_count()
|
||||||
return schemas.Statistic(
|
return [schemas.Statistic(
|
||||||
movie_count=media_statistic.get("MovieCount") or 0,
|
movie_count=media_statistic.get("MovieCount") or 0,
|
||||||
tv_count=media_statistic.get("SeriesCount") or 0,
|
tv_count=media_statistic.get("SeriesCount") or 0,
|
||||||
episode_count=media_statistic.get("EpisodeCount") or 0,
|
episode_count=media_statistic.get("EpisodeCount") or 0,
|
||||||
user_count=user_count or 0
|
user_count=user_count or 0
|
||||||
)
|
)]
|
||||||
|
|
||||||
def mediaserver_librarys(self) -> List[schemas.MediaServerLibrary]:
|
def mediaserver_librarys(self, server: str) -> Optional[List[schemas.MediaServerLibrary]]:
|
||||||
"""
|
"""
|
||||||
媒体库列表
|
媒体库列表
|
||||||
"""
|
"""
|
||||||
|
if server != "emby":
|
||||||
|
return None
|
||||||
librarys = self.emby.get_librarys()
|
librarys = self.emby.get_librarys()
|
||||||
if not librarys:
|
if not librarys:
|
||||||
return []
|
return []
|
||||||
@ -133,10 +130,12 @@ class EmbyModule(_ModuleBase):
|
|||||||
path=library.get("path")
|
path=library.get("path")
|
||||||
) for library in librarys]
|
) for library in librarys]
|
||||||
|
|
||||||
def mediaserver_items(self, library_id: str) -> Generator:
|
def mediaserver_items(self, server: str, library_id: str) -> Optional[Generator]:
|
||||||
"""
|
"""
|
||||||
媒体库项目列表
|
媒体库项目列表
|
||||||
"""
|
"""
|
||||||
|
if server != "emby":
|
||||||
|
return None
|
||||||
items = self.emby.get_items(library_id)
|
items = self.emby.get_items(library_id)
|
||||||
for item in items:
|
for item in items:
|
||||||
yield schemas.MediaServerItem(
|
yield schemas.MediaServerItem(
|
||||||
@ -153,10 +152,13 @@ class EmbyModule(_ModuleBase):
|
|||||||
path=item.get("path"),
|
path=item.get("path"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def mediaserver_tv_episodes(self, item_id: Union[str, int]) -> List[schemas.MediaServerSeasonInfo]:
|
def mediaserver_tv_episodes(self, server: str,
|
||||||
|
item_id: Union[str, int]) -> Optional[List[schemas.MediaServerSeasonInfo]]:
|
||||||
"""
|
"""
|
||||||
获取剧集信息
|
获取剧集信息
|
||||||
"""
|
"""
|
||||||
|
if server != "emby":
|
||||||
|
return None
|
||||||
seasoninfo = self.emby.get_tv_episodes(item_id=item_id)
|
seasoninfo = self.emby.get_tv_episodes(item_id=item_id)
|
||||||
if not seasoninfo:
|
if not seasoninfo:
|
||||||
return []
|
return []
|
||||||
|
@ -545,7 +545,7 @@ class Emby(metaclass=Singleton):
|
|||||||
logger.error(f"连接Users/Items出错:" + str(e))
|
logger.error(f"连接Users/Items出错:" + str(e))
|
||||||
yield {}
|
yield {}
|
||||||
|
|
||||||
def get_webhook_message(self, message_str: str) -> WebhookEventInfo:
|
def get_webhook_message(self, form: any, args: dict) -> Optional[WebhookEventInfo]:
|
||||||
"""
|
"""
|
||||||
解析Emby Webhook报文
|
解析Emby Webhook报文
|
||||||
电影:
|
电影:
|
||||||
@ -783,9 +783,22 @@ class Emby(metaclass=Singleton):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
message = json.loads(message_str)
|
if not form and not args:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
if form and form.get("data"):
|
||||||
|
result = form.get("data")
|
||||||
|
else:
|
||||||
|
result = json.dumps(dict(args))
|
||||||
|
message = json.loads(result)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"解析emby webhook报文出错:" + str(e))
|
||||||
|
return None
|
||||||
|
eventType = message.get('Event')
|
||||||
|
if not eventType:
|
||||||
|
return None
|
||||||
logger.info(f"接收到emby webhook:{message}")
|
logger.info(f"接收到emby webhook:{message}")
|
||||||
eventItem = WebhookEventInfo(event=message.get('Event', ''), channel="emby")
|
eventItem = WebhookEventInfo(event=eventType, channel="emby")
|
||||||
if message.get('Item'):
|
if message.get('Item'):
|
||||||
if message.get('Item', {}).get('Type') == 'Episode':
|
if message.get('Item', {}).get('Type') == 'Episode':
|
||||||
eventItem.item_type = "TV"
|
eventItem.item_type = "TV"
|
||||||
|
@ -41,7 +41,7 @@ class JellyfinModule(_ModuleBase):
|
|||||||
# Jellyfin认证
|
# Jellyfin认证
|
||||||
return self.jellyfin.authenticate(name, password)
|
return self.jellyfin.authenticate(name, password)
|
||||||
|
|
||||||
def webhook_parser(self, body: Any, form: Any, args: Any) -> WebhookEventInfo:
|
def webhook_parser(self, body: Any, form: Any, args: Any) -> Optional[WebhookEventInfo]:
|
||||||
"""
|
"""
|
||||||
解析Webhook报文体
|
解析Webhook报文体
|
||||||
:param body: 请求体
|
:param body: 请求体
|
||||||
@ -49,7 +49,7 @@ class JellyfinModule(_ModuleBase):
|
|||||||
:param args: 请求参数
|
:param args: 请求参数
|
||||||
:return: 字典,解析为消息时需要包含:title、text、image
|
:return: 字典,解析为消息时需要包含:title、text、image
|
||||||
"""
|
"""
|
||||||
return self.jellyfin.get_webhook_message(json.loads(body))
|
return self.jellyfin.get_webhook_message(body)
|
||||||
|
|
||||||
def media_exists(self, mediainfo: MediaInfo, itemid: str = None) -> Optional[ExistMediaInfo]:
|
def media_exists(self, mediainfo: MediaInfo, itemid: str = None) -> Optional[ExistMediaInfo]:
|
||||||
"""
|
"""
|
||||||
@ -83,32 +83,34 @@ class JellyfinModule(_ModuleBase):
|
|||||||
logger.info(f"{mediainfo.title_year} 媒体库中已存在:{tvs}")
|
logger.info(f"{mediainfo.title_year} 媒体库中已存在:{tvs}")
|
||||||
return ExistMediaInfo(type=MediaType.TV, seasons=tvs)
|
return ExistMediaInfo(type=MediaType.TV, seasons=tvs)
|
||||||
|
|
||||||
def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> Optional[bool]:
|
def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
刷新媒体库
|
刷新媒体库
|
||||||
:param mediainfo: 识别的媒体信息
|
:param mediainfo: 识别的媒体信息
|
||||||
:param file_path: 文件路径
|
:param file_path: 文件路径
|
||||||
:return: 成功或失败
|
:return: 成功或失败
|
||||||
"""
|
"""
|
||||||
return self.jellyfin.refresh_root_library()
|
self.jellyfin.refresh_root_library()
|
||||||
|
|
||||||
def media_statistic(self) -> schemas.Statistic:
|
def media_statistic(self) -> List[schemas.Statistic]:
|
||||||
"""
|
"""
|
||||||
媒体数量统计
|
媒体数量统计
|
||||||
"""
|
"""
|
||||||
media_statistic = self.jellyfin.get_medias_count()
|
media_statistic = self.jellyfin.get_medias_count()
|
||||||
user_count = self.jellyfin.get_user_count()
|
user_count = self.jellyfin.get_user_count()
|
||||||
return schemas.Statistic(
|
return [schemas.Statistic(
|
||||||
movie_count=media_statistic.get("MovieCount") or 0,
|
movie_count=media_statistic.get("MovieCount") or 0,
|
||||||
tv_count=media_statistic.get("SeriesCount") or 0,
|
tv_count=media_statistic.get("SeriesCount") or 0,
|
||||||
episode_count=media_statistic.get("EpisodeCount") or 0,
|
episode_count=media_statistic.get("EpisodeCount") or 0,
|
||||||
user_count=user_count or 0
|
user_count=user_count or 0
|
||||||
)
|
)]
|
||||||
|
|
||||||
def mediaserver_librarys(self) -> List[schemas.MediaServerLibrary]:
|
def mediaserver_librarys(self, server: str) -> Optional[List[schemas.MediaServerLibrary]]:
|
||||||
"""
|
"""
|
||||||
媒体库列表
|
媒体库列表
|
||||||
"""
|
"""
|
||||||
|
if server != "jellyfin":
|
||||||
|
return None
|
||||||
librarys = self.jellyfin.get_librarys()
|
librarys = self.jellyfin.get_librarys()
|
||||||
if not librarys:
|
if not librarys:
|
||||||
return []
|
return []
|
||||||
@ -120,10 +122,12 @@ class JellyfinModule(_ModuleBase):
|
|||||||
path=library.get("path")
|
path=library.get("path")
|
||||||
) for library in librarys]
|
) for library in librarys]
|
||||||
|
|
||||||
def mediaserver_items(self, library_id: str) -> Generator:
|
def mediaserver_items(self, server: str, library_id: str) -> Optional[Generator]:
|
||||||
"""
|
"""
|
||||||
媒体库项目列表
|
媒体库项目列表
|
||||||
"""
|
"""
|
||||||
|
if server != "jellyfin":
|
||||||
|
return None
|
||||||
items = self.jellyfin.get_items(library_id)
|
items = self.jellyfin.get_items(library_id)
|
||||||
for item in items:
|
for item in items:
|
||||||
yield schemas.MediaServerItem(
|
yield schemas.MediaServerItem(
|
||||||
@ -140,10 +144,13 @@ class JellyfinModule(_ModuleBase):
|
|||||||
path=item.get("path"),
|
path=item.get("path"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def mediaserver_tv_episodes(self, item_id: Union[str, int]) -> List[schemas.MediaServerSeasonInfo]:
|
def mediaserver_tv_episodes(self, server: str,
|
||||||
|
item_id: Union[str, int]) -> Optional[List[schemas.MediaServerSeasonInfo]]:
|
||||||
"""
|
"""
|
||||||
获取剧集信息
|
获取剧集信息
|
||||||
"""
|
"""
|
||||||
|
if server != "jellyfin":
|
||||||
|
return None
|
||||||
seasoninfo = self.jellyfin.get_tv_episodes(item_id=item_id)
|
seasoninfo = self.jellyfin.get_tv_episodes(item_id=item_id)
|
||||||
if not seasoninfo:
|
if not seasoninfo:
|
||||||
return []
|
return []
|
||||||
|
@ -387,7 +387,7 @@ class Jellyfin(metaclass=Singleton):
|
|||||||
logger.error(f"连接Library/Refresh出错:" + str(e))
|
logger.error(f"连接Library/Refresh出错:" + str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_webhook_message(self, message: dict) -> WebhookEventInfo:
|
def get_webhook_message(self, body: any) -> Optional[WebhookEventInfo]:
|
||||||
"""
|
"""
|
||||||
解析Jellyfin报文
|
解析Jellyfin报文
|
||||||
{
|
{
|
||||||
@ -450,9 +450,21 @@ class Jellyfin(metaclass=Singleton):
|
|||||||
"UserId": "9783d2432b0d40a8a716b6aa46xxxxx"
|
"UserId": "9783d2432b0d40a8a716b6aa46xxxxx"
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
if not body:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
message = json.loads(body)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"解析Jellyfin Webhook报文出错:" + str(e))
|
||||||
|
return None
|
||||||
|
if not message:
|
||||||
|
return None
|
||||||
logger.info(f"接收到jellyfin webhook:{message}")
|
logger.info(f"接收到jellyfin webhook:{message}")
|
||||||
|
eventType = message.get('NotificationType')
|
||||||
|
if not eventType:
|
||||||
|
return None
|
||||||
eventItem = WebhookEventInfo(
|
eventItem = WebhookEventInfo(
|
||||||
event=message.get('NotificationType', ''),
|
event=eventType,
|
||||||
channel="jellyfin"
|
channel="jellyfin"
|
||||||
)
|
)
|
||||||
eventItem.item_id = message.get('ItemId')
|
eventItem.item_id = message.get('ItemId')
|
||||||
|
@ -31,7 +31,7 @@ class PlexModule(_ModuleBase):
|
|||||||
if not self.plex.is_inactive():
|
if not self.plex.is_inactive():
|
||||||
self.plex = Plex()
|
self.plex = Plex()
|
||||||
|
|
||||||
def webhook_parser(self, body: Any, form: Any, args: Any) -> WebhookEventInfo:
|
def webhook_parser(self, body: Any, form: Any, args: Any) -> Optional[WebhookEventInfo]:
|
||||||
"""
|
"""
|
||||||
解析Webhook报文体
|
解析Webhook报文体
|
||||||
:param body: 请求体
|
:param body: 请求体
|
||||||
@ -39,7 +39,7 @@ class PlexModule(_ModuleBase):
|
|||||||
:param args: 请求参数
|
:param args: 请求参数
|
||||||
:return: 字典,解析为消息时需要包含:title、text、image
|
:return: 字典,解析为消息时需要包含:title、text、image
|
||||||
"""
|
"""
|
||||||
return self.plex.get_webhook_message(form.get("payload"))
|
return self.plex.get_webhook_message(form)
|
||||||
|
|
||||||
def media_exists(self, mediainfo: MediaInfo, itemid: str = None) -> Optional[ExistMediaInfo]:
|
def media_exists(self, mediainfo: MediaInfo, itemid: str = None) -> Optional[ExistMediaInfo]:
|
||||||
"""
|
"""
|
||||||
@ -77,7 +77,7 @@ class PlexModule(_ModuleBase):
|
|||||||
logger.info(f"{mediainfo.title_year} 媒体库中已存在:{tvs}")
|
logger.info(f"{mediainfo.title_year} 媒体库中已存在:{tvs}")
|
||||||
return ExistMediaInfo(type=MediaType.TV, seasons=tvs)
|
return ExistMediaInfo(type=MediaType.TV, seasons=tvs)
|
||||||
|
|
||||||
def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> Optional[bool]:
|
def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
刷新媒体库
|
刷新媒体库
|
||||||
:param mediainfo: 识别的媒体信息
|
:param mediainfo: 识别的媒体信息
|
||||||
@ -93,24 +93,26 @@ class PlexModule(_ModuleBase):
|
|||||||
target_path=file_path
|
target_path=file_path
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
return self.plex.refresh_library_by_items(items)
|
self.plex.refresh_library_by_items(items)
|
||||||
|
|
||||||
def media_statistic(self) -> schemas.Statistic:
|
def media_statistic(self) -> List[schemas.Statistic]:
|
||||||
"""
|
"""
|
||||||
媒体数量统计
|
媒体数量统计
|
||||||
"""
|
"""
|
||||||
media_statistic = self.plex.get_medias_count()
|
media_statistic = self.plex.get_medias_count()
|
||||||
return schemas.Statistic(
|
return [schemas.Statistic(
|
||||||
movie_count=media_statistic.get("MovieCount") or 0,
|
movie_count=media_statistic.get("MovieCount") or 0,
|
||||||
tv_count=media_statistic.get("SeriesCount") or 0,
|
tv_count=media_statistic.get("SeriesCount") or 0,
|
||||||
episode_count=media_statistic.get("EpisodeCount") or 0,
|
episode_count=media_statistic.get("EpisodeCount") or 0,
|
||||||
user_count=1
|
user_count=1
|
||||||
)
|
)]
|
||||||
|
|
||||||
def mediaserver_librarys(self) -> List[schemas.MediaServerLibrary]:
|
def mediaserver_librarys(self, server: str) -> Optional[List[schemas.MediaServerLibrary]]:
|
||||||
"""
|
"""
|
||||||
媒体库列表
|
媒体库列表
|
||||||
"""
|
"""
|
||||||
|
if server != "plex":
|
||||||
|
return None
|
||||||
librarys = self.plex.get_librarys()
|
librarys = self.plex.get_librarys()
|
||||||
if not librarys:
|
if not librarys:
|
||||||
return []
|
return []
|
||||||
@ -122,10 +124,12 @@ class PlexModule(_ModuleBase):
|
|||||||
path=library.get("path")
|
path=library.get("path")
|
||||||
) for library in librarys]
|
) for library in librarys]
|
||||||
|
|
||||||
def mediaserver_items(self, library_id: str) -> Generator:
|
def mediaserver_items(self, server: str, library_id: str) -> Optional[Generator]:
|
||||||
"""
|
"""
|
||||||
媒体库项目列表
|
媒体库项目列表
|
||||||
"""
|
"""
|
||||||
|
if server != "plex":
|
||||||
|
return None
|
||||||
items = self.plex.get_items(library_id)
|
items = self.plex.get_items(library_id)
|
||||||
for item in items:
|
for item in items:
|
||||||
yield schemas.MediaServerItem(
|
yield schemas.MediaServerItem(
|
||||||
@ -142,10 +146,13 @@ class PlexModule(_ModuleBase):
|
|||||||
path=item.get("path"),
|
path=item.get("path"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def mediaserver_tv_episodes(self, item_id: Union[str, int]) -> List[schemas.MediaServerSeasonInfo]:
|
def mediaserver_tv_episodes(self, server: str,
|
||||||
|
item_id: Union[str, int]) -> Optional[List[schemas.MediaServerSeasonInfo]]:
|
||||||
"""
|
"""
|
||||||
获取剧集信息
|
获取剧集信息
|
||||||
"""
|
"""
|
||||||
|
if server != "plex":
|
||||||
|
return None
|
||||||
seasoninfo = self.plex.get_tv_episodes(item_id=item_id)
|
seasoninfo = self.plex.get_tv_episodes(item_id=item_id)
|
||||||
if not seasoninfo:
|
if not seasoninfo:
|
||||||
return []
|
return []
|
||||||
|
@ -353,7 +353,7 @@ class Plex(metaclass=Singleton):
|
|||||||
logger.error(f"获取媒体库列表出错:{err}")
|
logger.error(f"获取媒体库列表出错:{err}")
|
||||||
yield {}
|
yield {}
|
||||||
|
|
||||||
def get_webhook_message(self, message_str: str) -> WebhookEventInfo:
|
def get_webhook_message(self, form: any) -> Optional[WebhookEventInfo]:
|
||||||
"""
|
"""
|
||||||
解析Plex报文
|
解析Plex报文
|
||||||
eventItem 字段的含义
|
eventItem 字段的含义
|
||||||
@ -457,9 +457,21 @@ class Plex(metaclass=Singleton):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
message = json.loads(message_str)
|
if not form:
|
||||||
|
return None
|
||||||
|
payload = form.get("payload")
|
||||||
|
if not payload:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
message = json.loads(payload)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"解析plex webhook出错:{str(e)}")
|
||||||
|
return None
|
||||||
|
eventType = message.get('event')
|
||||||
|
if not eventType:
|
||||||
|
return None
|
||||||
logger.info(f"接收到plex webhook:{message}")
|
logger.info(f"接收到plex webhook:{message}")
|
||||||
eventItem = WebhookEventInfo(event=message.get('event', ''), channel="plex")
|
eventItem = WebhookEventInfo(event=eventType, channel="plex")
|
||||||
if message.get('Metadata'):
|
if message.get('Metadata'):
|
||||||
if message.get('Metadata', {}).get('type') == 'episode':
|
if message.get('Metadata', {}).get('type') == 'episode':
|
||||||
eventItem.item_type = "TV"
|
eventItem.item_type = "TV"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user