From 39d6410b36867353db0cd398bfb929be264bac2d Mon Sep 17 00:00:00 2001 From: thsrite Date: Mon, 26 Aug 2024 13:18:05 +0800 Subject: [PATCH] =?UTF-8?q?fix=20Emby=E5=BC=B9=E5=B9=95=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=20v1.1=20=E8=A7=A3=E6=9E=90Emby=E6=97=A5=E5=BF=97=EF=BC=8C?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E5=B7=B2=E9=85=8D=E7=BD=AE=E5=BC=B9=E5=B9=95?= =?UTF-8?q?=E6=BA=90=E6=98=AF=E5=90=A6=E5=85=A8=E9=83=A8=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- package.json | 3 +- plugins/embydanmu/__init__.py | 231 +++++++++++++++++++++++++++------- 3 files changed, 186 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index dfa8983..f720d39 100644 --- a/README.md +++ b/README.md @@ -52,4 +52,4 @@ MoviePilot三方插件市场:https://github.com/thsrite/MoviePilot-Plugins/ - 影视将映订阅 v1.1 - Emby视频类型检查 v1.0 - Emby有声书整理 v1.1 -- Emby弹幕下载 v1.0 \ No newline at end of file +- Emby弹幕下载 v1.1 \ No newline at end of file diff --git a/package.json b/package.json index a88d49b..949f1e8 100644 --- a/package.json +++ b/package.json @@ -748,11 +748,12 @@ "name": "Emby弹幕下载", "description": "通知Emby Danmu插件下载弹幕。", "labels": "Emby,媒体库", - "version": "1.0", + "version": "1.1", "icon": "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/danmu.png", "author": "thsrite", "level": 1, "history": { + "v1.1": "解析Emby日志,判断已配置弹幕源是否全部匹配失败。", "v1.0": "通知Emby Danmu插件下载弹幕。" } } diff --git a/plugins/embydanmu/__init__.py b/plugins/embydanmu/__init__.py index a658331..68b4506 100644 --- a/plugins/embydanmu/__init__.py +++ b/plugins/embydanmu/__init__.py @@ -1,4 +1,5 @@ import json +import re import time from pathlib import Path from typing import List, Tuple, Dict, Any @@ -20,7 +21,7 @@ class EmbyDanmu(_PluginBase): # 插件图标 plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/danmu.png" # 插件版本 - plugin_version = "1.0" + plugin_version = "1.1" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -72,6 +73,15 @@ class EmbyDanmu(_PluginBase): userid=event.event_data.get("user")) return + # 检查插件是否正确配置 + danmu_source = self.__get_danmu_source() + if not danmu_source: + logger.error(f"未配置弹幕源") + self.post_message(channel=event.event_data.get("channel"), + title=f"Emby未正确配置弹幕源", + userid=event.event_data.get("user")) + return + library_name = args_list[0] library_item_name = args_list[1] library_item_season = None @@ -156,18 +166,24 @@ class EmbyDanmu(_PluginBase): logger.info( f"已通知弹幕插件获取 {library_name} {library_item_name} 季度:{season_id}的弹幕") season_item_cnt, danmu_cnt = self.__check_danmu_exists(season_id) - if season_item_cnt == danmu_cnt: - logger.info( - f"{library_name} {library_item_name} 字幕文件已全部下载完成:{danmu_cnt}") + if season_item_cnt == 0: + logger.error(f"Emby已配置弹幕源全部匹配弹幕失败") self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {library_item_name} 字幕文件已全部下载完成:{danmu_cnt}", + title=f"Emby已配置弹幕源全部匹配弹幕失败", userid=event.event_data.get("user")) else: - logger.error( - f"{library_name} {library_item_name} 字幕文件未全部下载完成:{danmu_cnt}/{season_item_cnt}") - self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {library_item_name} 字幕文件未全部下载完成:{danmu_cnt}/{season_item_cnt}", - userid=event.event_data.get("user")) + if season_item_cnt == danmu_cnt: + logger.info( + f"{library_name} {library_item_name} 字幕文件已全部下载完成:{danmu_cnt}") + self.post_message(channel=event.event_data.get("channel"), + title=f"{library_name} {library_item_name} 字幕文件已全部下载完成:{danmu_cnt}", + userid=event.event_data.get("user")) + else: + logger.error( + f"{library_name} {library_item_name} 字幕文件未全部下载完成:{season_item_cnt}/{danmu_cnt}") + self.post_message(channel=event.event_data.get("channel"), + title=f"{library_name} {library_item_name} 字幕文件未全部下载完成:{season_item_cnt}/{danmu_cnt}", + userid=event.event_data.get("user")) else: logger.error( f"通知弹幕插件获取 {library_name} {library_item_name} 季度:{season_id}的弹幕失败") @@ -186,18 +202,24 @@ class EmbyDanmu(_PluginBase): logger.info( f"已通知弹幕插件获取 {library_name} {library_item_name} 季度:{season_id}的弹幕") season_item_cnt, danmu_cnt = self.__check_danmu_exists(season_id) - if season_item_cnt == danmu_cnt: - logger.info( - f"{library_item_name} 字幕文件已全部下载完成:{danmu_cnt}") + if season_item_cnt == 0: + logger.error(f"Emby已配置弹幕源全部匹配弹幕失败") self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {library_item_name} 字幕文件已全部下载完成:{danmu_cnt}", + title=f"Emby已配置弹幕源全部匹配弹幕失败", userid=event.event_data.get("user")) else: - logger.error( - f"{library_item_name} 字幕文件未全部下载完成:{danmu_cnt}/{season_item_cnt}") - self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {library_item_name} 字幕文件未全部下载完成:{danmu_cnt}/{season_item_cnt}", - userid=event.event_data.get("user")) + if season_item_cnt == danmu_cnt: + logger.info( + f"{library_item_name} 字幕文件已全部下载完成:{danmu_cnt}") + self.post_message(channel=event.event_data.get("channel"), + title=f"{library_name} {library_item_name} 字幕文件已全部下载完成:{danmu_cnt}", + userid=event.event_data.get("user")) + else: + logger.error( + f"{library_item_name} 字幕文件未全部下载完成:{season_item_cnt}/{danmu_cnt}") + self.post_message(channel=event.event_data.get("channel"), + title=f"{library_name} {library_item_name} 字幕文件未全部下载完成:{season_item_cnt}/{danmu_cnt}", + userid=event.event_data.get("user")) else: logger.error( f"通知弹幕插件获取 {library_name} {library_item_name} 季度:{library_item_season}的弹幕失败") @@ -213,18 +235,24 @@ class EmbyDanmu(_PluginBase): logger.info( f"已通知弹幕插件获取 {library_name} {library_item_name} 季度:{season_id}的弹幕") season_item_cnt, danmu_cnt = self.__check_danmu_exists(season_id) - if season_item_cnt == danmu_cnt: - logger.info( - f"{library_item_name} 字幕文件已全部下载完成:{danmu_cnt}") + if season_item_cnt == 0: + logger.error(f"Emby已配置弹幕源全部匹配弹幕失败") self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {library_item_name} 字幕文件已全部下载完成:{danmu_cnt}", + title=f"Emby已配置弹幕源全部匹配弹幕失败", userid=event.event_data.get("user")) else: - logger.error( - f"{library_item_name} 字幕文件未全部下载完成:{danmu_cnt}/{season_item_cnt}") - self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {library_item_name} 字幕文件未全部下载完成:{danmu_cnt}/{season_item_cnt}", - userid=event.event_data.get("user")) + if season_item_cnt == danmu_cnt: + logger.info( + f"{library_item_name} 字幕文件已全部下载完成:{danmu_cnt}") + self.post_message(channel=event.event_data.get("channel"), + title=f"{library_name} {library_item_name} 字幕文件已全部下载完成:{danmu_cnt}", + userid=event.event_data.get("user")) + else: + logger.error( + f"{library_item_name} 字幕文件未全部下载完成:{danmu_cnt}/{season_item_cnt}") + self.post_message(channel=event.event_data.get("channel"), + title=f"{library_name} {library_item_name} 字幕文件未全部下载完成:{danmu_cnt}/{season_item_cnt}", + userid=event.event_data.get("user")) else: logger.error( f"通知弹幕插件获取 {library_name} {library_item_name} 季度:{season_id}的弹幕失败") @@ -249,19 +277,24 @@ class EmbyDanmu(_PluginBase): logger.info( f"已通知弹幕插件获取 {library_name} {item.get('Name')} {movie_id} 的弹幕") # 获取媒体详情 - movie_info = self.__get_item_info(movie_id) - - movie_path = movie_info.get("Path") - parent_path = Path(movie_path).parent + item_info = self.__get_item_info(movie_id) + item_path = item_info.get("Path") + parent_path = Path(item_path).parent logger.info(f"开始检查路径 {parent_path} 下是是否有字幕文件") # 检查是否有字幕文件 - danmu_path_pattern = Path(movie_path).stem + "*.xml" + danmu_path_pattern = Path(item_path).stem + "*.xml" retry_cnt = 3 while len(list(parent_path.glob(danmu_path_pattern))) == 0 and retry_cnt > 0: - retry_cnt -= 1 - logger.warn( - f"{parent_path} 下未找到字幕文件:{danmu_path_pattern},等待60秒后重试 ({retry_cnt}次)") - time.sleep(60) + # 解析日志判断是否全部失败 + if self.__check_all_failed_by_log(item_name=item_info.get("Name"), + item_year=item_info.get("ProductionYear")): + logger.error(f"解析日志判断已配置弹幕源全部匹配弹幕失败") + retry_cnt = -1 + else: + retry_cnt -= 1 + logger.warn( + f"{parent_path} 下未找到字幕文件:{danmu_path_pattern},等待60秒后重试 ({retry_cnt}次)") + time.sleep(60) if len(list(parent_path.glob(danmu_path_pattern))) >= 1: logger.info(f"{parent_path} 下已找到字幕文件:{danmu_path_pattern}") @@ -358,7 +391,7 @@ class EmbyDanmu(_PluginBase): 通知Danmu插件获取弹幕 """ if not self._EMBY_HOST or not self._EMBY_APIKEY: - return [] + return False req_url = f"%sapi/danmu/%s?option=Refresh&api_key=%s" % ( self._EMBY_HOST, item_id, self._EMBY_APIKEY) try: @@ -392,7 +425,8 @@ class EmbyDanmu(_PluginBase): 检查媒体是否有弹幕 """ season_items = self.__get_items(season_id) - item_path = self.__get_item_info(season_items[0].get("Id")).get("Path") + item_info = self.__get_item_info(season_items[0].get("Id")) + item_path = item_info.get("Path") parent_path = Path(item_path).parent logger.info(f"开始检查路径 {parent_path} 下是是否有字幕文件") # 检查是否有字幕文件 @@ -401,18 +435,119 @@ class EmbyDanmu(_PluginBase): retry_cnt = len(season_items) _downloaded_danmu_files = [] while len(_downloaded_danmu_files) < len(season_items) and retry_cnt > 0: - danmu_files = list(parent_path.glob(danmu_path_pattern)) - for danmu_file in danmu_files: - if danmu_file.name not in _downloaded_danmu_files: - _downloaded_danmu_files.append(danmu_file.name) - logger.info(f"已下载字幕文件:{danmu_file.name}") - retry_cnt -= 1 - logger.warn( - f"{parent_path} 下字幕文件:{danmu_path_pattern} 未下载完成,等待60秒后重试 ({retry_cnt}次)") - time.sleep(60) + # 解析日志判断是否全部失败 + if self.__check_all_failed_by_log(item_name=item_info.get("SeriesName"), + item_year=item_info.get("ProductionYear")): + logger.error(f"解析日志判断已配置弹幕源全部匹配弹幕失败") + retry_cnt = -1 + else: + danmu_files = list(parent_path.glob(danmu_path_pattern)) + for danmu_file in danmu_files: + if danmu_file.name not in _downloaded_danmu_files: + _downloaded_danmu_files.append(danmu_file.name) + logger.info(f"已下载字幕文件:{danmu_file.name}") + retry_cnt -= 1 + logger.warn( + f"{parent_path} 下字幕文件:{danmu_path_pattern} 未下载完成,等待60秒后重试 ({retry_cnt}次)") + time.sleep(60) return len(_downloaded_danmu_files), len(season_items) + def __get_plugins(self) -> list: + """ + 获取插件列表 + """ + if not self._EMBY_HOST or not self._EMBY_APIKEY: + return [] + req_url = f"%semby/web/configurationpages?PageType=PluginConfiguration&EnableInMainMenu=true&UserId=%s&api_key=%s" % ( + self._EMBY_HOST, self._EMBY_USER, self._EMBY_APIKEY) + with RequestUtils().get_res(req_url) as res: + if res: + return res.json() + else: + logger.info(f"获取插件列表失败,无法连接Emby!") + return [] + + def __get_plugin_info(self, plugin_id) -> dict: + """ + 获取插件详情 + """ + if not self._EMBY_HOST or not self._EMBY_APIKEY: + return {} + req_url = f"%semby/Plugins/%s/Configuration?api_key=%s" % ( + self._EMBY_HOST, plugin_id, self._EMBY_APIKEY) + with RequestUtils().get_res(req_url) as res: + if res: + return res.json() + else: + logger.info(f"获取插件详情失败,无法连接Emby!") + return {} + + def __get_danmu_source(self) -> list: + """ + 获取弹幕源 + """ + # 获取插件列表 + list_plugins = self.__get_plugins() + if not list_plugins: + return [] + + # 获取弹幕配置插件 + plugin_id = None + for plugin in list_plugins: + if plugin.get("Name") == "danmu": + plugin_id = plugin.get("PluginId") + break + + if not plugin_id: + logger.error("弹幕配置插件未安装") + return [] + + # 获取弹幕源 + plugin_info = self.__get_plugin_info(plugin_id) + if not plugin_info: + return [] + + scrapers = plugin_info.get("Scrapers", []) + if not scrapers: + return [] + + return [scraper.get("Name") for scraper in scrapers if scraper.get("Enable") == True] + + def __get_emby_log(self) -> str: + """ + 获取emby日志 + """ + if not self._EMBY_HOST or not self._EMBY_APIKEY: + return "" + req_url = f"%sSystem/Logs/embyserver.txt?api_key=%s" % ( + self._EMBY_HOST, self._EMBY_APIKEY) + with RequestUtils().get_res(req_url) as res: + if res: + return res.text + else: + logger.info(f"获取插件详情失败,无法连接Emby!") + return "" + + def __check_all_failed_by_log(self, item_name, item_year) -> bool: + """ + 解析emby日志 + """ + danmu_source = self.__get_danmu_source() + emby_log = self.__get_emby_log() + if not emby_log: + return False + + # 正则解析删除的媒体信息 + all_matched = True + for source in danmu_source: + pattern = fr'\[{source}\]匹配失败:{item_name} \({item_year}\)' + matches = re.findall(pattern, emby_log) + if not matches: + all_matched = False + break + return all_matched + @staticmethod def get_command() -> List[Dict[str, Any]]: return [