feat 支持多媒体服务器同时使用

This commit is contained in:
jxxghp 2023-09-23 09:20:51 +08:00
parent 78dab04c96
commit fd9eef2089
12 changed files with 159 additions and 96 deletions

View File

@ -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`设置项:

View File

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

View File

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

View File

@ -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]]:
""" """
媒体数量统计 媒体数量统计
""" """

View File

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

View File

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

View File

@ -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: 字典解析为消息时需要包含titletextimage :return: 字典解析为消息时需要包含titletextimage
""" """
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 []

View File

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

View File

@ -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: 字典解析为消息时需要包含titletextimage :return: 字典解析为消息时需要包含titletextimage
""" """
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 []

View File

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

View File

@ -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: 字典解析为消息时需要包含titletextimage :return: 字典解析为消息时需要包含titletextimage
""" """
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 []

View File

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