From 30e628a4675766f1105ebfb4e9dc597b3859ebe5 Mon Sep 17 00:00:00 2001 From: thsrite Date: Thu, 17 Oct 2024 10:10:17 +0800 Subject: [PATCH] fix v2 Emby instance fetch --- package.v2.json | 21 +- plugins.v2/embyactorsync/__init__.py | 183 +++---- plugins.v2/embyaudiobook/__init__.py | 131 +++-- plugins.v2/embycollectionsort/__init__.py | 194 ++++---- plugins.v2/embydanmu/__init__.py | 579 ++++++++++++---------- plugins.v2/embyextendtype/__init__.py | 89 ++-- plugins.v2/embymetatag/__init__.py | 189 ++++--- plugins.v2/embyreporter/__init__.py | 140 ++++-- 8 files changed, 848 insertions(+), 678 deletions(-) diff --git a/package.v2.json b/package.v2.json index 89fbb50..b983685 100644 --- a/package.v2.json +++ b/package.v2.json @@ -57,11 +57,12 @@ "name": "Emby媒体标签", "description": "自动给媒体库媒体添加标签。", "labels": "Emby", - "version": "1.2.1", + "version": "1.3", "icon": "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/tag.png", "author": "thsrite", "level": 1, "history": { + "v1.3": "适配v2多媒体服务器", "v1.2": "支持指定特殊媒体名称添加标签", "v1.1": "添加远程交互命令", "v1.0": "自动给媒体库媒体添加标签" @@ -71,11 +72,12 @@ "name": "Emby观影报告", "description": "推送Emby观影报告,需Emby安装Playback Report 插件。", "labels": "Emby", - "version": "1.8.1", + "version": "1.9", "icon": "Pydiocells_A.png", "author": "thsrite", "level": 1, "history": { + "v1.9": "适配v2多媒体服务器", "v1.8": "推送微信增加时间戳,防止使用缓存", "v1.7": "分块发送,兼容微信推送", "v1.6": "fix #67", @@ -118,11 +120,12 @@ "name": "Emby弹幕下载", "description": "通知Emby Danmu插件下载弹幕。", "labels": "Emby,媒体库", - "version": "1.2.1", + "version": "1.3", "icon": "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/danmu.png", "author": "thsrite", "level": 1, "history": { + "v1.3": "适配v2多媒体服务器", "v1.2": "检查本地弹幕文件是否已存在,避免重复下载。", "v1.1": "解析Emby日志,判断已配置弹幕源是否全部匹配失败。", "v1.0": "通知Emby Danmu插件下载弹幕。" @@ -132,11 +135,12 @@ "name": "Emby视频类型检查", "description": "定期检查Emby媒体库中是否包含指定的视频类型,发送通知。", "labels": "Emby,媒体库", - "version": "1.0.1", + "version": "1.1", "icon": "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/extendtype.png", "author": "thsrite", "level": 1, "history": { + "v1.1": "适配v2多媒体服务器", "v1.0": "定期检查Emby媒体库中是否包含指定的视频类型,发送通知。" } }, @@ -144,11 +148,12 @@ "name": "Emby有声书整理", "description": "还在为Emby有声书整理烦恼吗?入库存在很多单集?", "labels": "Emby,媒体库", - "version": "1.1.1", + "version": "1.2", "icon": "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/audiobook.png", "author": "thsrite", "level": 1, "history": { + "v1.2": "适配v2多媒体服务器", "v1.1": "整理完锁定,防止数据被刷新", "v1.0": "还在为Emby有声书整理烦恼吗?入库存在很多单集。" } @@ -157,11 +162,12 @@ "name": "Emby合集媒体排序", "description": "Emby保留按照加入时间倒序的前提下,把合集中的媒体按照发布日期排序,修改加入时间已到达顺序排列的目的。", "labels": "媒体库", - "version": "1.1.1", + "version": "1.2", "icon": "Element_A.png", "author": "thsrite", "level": 1, "history": { + "v1.2": "适配v2多媒体服务器", "v1.1": "优化处理逻辑", "v1.0": "保留按照加入时间倒序的前提下,把合集中的媒体放一块,不用到处找。" } @@ -170,11 +176,12 @@ "name": "Emby剧集演员同步", "description": "同步剧演员信息到集演员信息。", "labels": "Emby,媒体库", - "version": "1.3.1", + "version": "1.4", "icon": "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/embyactorsync.png", "author": "thsrite", "level": 1, "history": { + "v1.4": "适配v2多媒体服务器", "v1.3": "剧集优先使用季演员。", "v1.2": "交互命令返回处理完成信息。", "v1.1": "支持交互命令手动同步单个剧集 /as 媒体库名 剧集名。", diff --git a/plugins.v2/embyactorsync/__init__.py b/plugins.v2/embyactorsync/__init__.py index 44d98ec..b8a68fd 100644 --- a/plugins.v2/embyactorsync/__init__.py +++ b/plugins.v2/embyactorsync/__init__.py @@ -11,7 +11,6 @@ from app.core.event import eventmanager, Event from app.helper.mediaserver import MediaServerHelper from app.log import logger from app.plugins import _PluginBase -from app.modules.emby import Emby from app.schemas.types import EventType, MediaType from app.utils.http import RequestUtils @@ -24,7 +23,7 @@ class EmbyActorSync(_PluginBase): # 插件图标 plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/embyactorsync.png" # 插件版本 - plugin_version = "1.3.1" + plugin_version = "1.4" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -38,7 +37,8 @@ class EmbyActorSync(_PluginBase): _onlyonce = False _enabled = False - _librarys = None + _mediaservers = None + mediaserver_helper = None _EMBY_HOST = None _EMBY_USER = None @@ -51,20 +51,7 @@ class EmbyActorSync(_PluginBase): if config: self._enabled = config.get("enabled") self._onlyonce = config.get("onlyonce") - self._librarys = config.get("librarys") or [] - - emby_server = self.mediaserver_helper.get_service(name="Emby") - if not emby_server: - logger.error("未配置Emby媒体服务器") - return - - self._EMBY_USER = emby_server.instance.get_user() - self._EMBY_HOST = emby_server.config.get("host") - self._EMBY_APIKEY = emby_server.config.get("apikey") - if not self._EMBY_HOST.endswith("/"): - self._EMBY_HOST += "/" - if not self._EMBY_HOST.startswith("http"): - self._EMBY_HOST = "http://" + self._EMBY_HOST + self._mediaservers = config.get("mediaservers") or [] # 加载模块 if self._onlyonce: @@ -98,7 +85,7 @@ class EmbyActorSync(_PluginBase): { "enabled": self._enabled, "onlyonce": self._onlyonce, - "librarys": self._librarys, + "mediaservers": self._mediaservers, } ) @@ -130,72 +117,85 @@ class EmbyActorSync(_PluginBase): """ Emby剧集演员同步 """ - # 获取媒体库信息 - librarys = Emby().get_librarys() + emby_servers = self.mediaserver_helper.get_services(name_filters=self._mediaservers, type_filter="emby") + if not emby_servers: + logger.error("未配置Emby媒体服务器") + return - # 匹配需要的媒体库 - for library in librarys: - if library.type != MediaType.TV.value: - continue - if self._librarys and library.name not in self._librarys: - continue - if library_name and library.name != library_name: - continue + for emby_name, emby_server in emby_servers.items(): + logger.info(f"开始处理媒体服务器 {emby_name}") + self._EMBY_USER = emby_server.instance.get_user() + self._EMBY_APIKEY = emby_server.config.config.get("apikey") + self._EMBY_HOST = emby_server.config.config.get("host") + if not self._EMBY_HOST.endswith("/"): + self._EMBY_HOST += "/" + if not self._EMBY_HOST.startswith("http"): + self._EMBY_HOST = "http://" + self._EMBY_HOST - # 获取媒体库媒体列表 - library_items = self.__get_items(library.id) - if not library_items: - logger.error(f"获取媒体库:{library.name}的媒体列表失败") - continue + # 获取媒体库信息 + librarys = emby_server.instance.get_librarys() - logger.info(f"开始同步媒体库:{library.name},ID:{library.id}") + # 匹配需要的媒体库 + for library in librarys: + if library.type != MediaType.TV.value: + continue + if library_name and library.name != library_name: + continue - # 遍历媒体列表,获取媒体的ID和名称 - for item in library_items: - if media_name: - # 电影弹幕 - matches = re.findall(r'^(.+?)\s\(\d{4}\)$', item.get("Name").strip()) - if (not matches and media_name != item.get("Name")) or (matches and str(matches[0]) != media_name): - continue + # 获取媒体库媒体列表 + library_items = self.__get_items(library.id) + if not library_items: + logger.error(f"获取媒体库:{library.name}的媒体列表失败") + continue - logger.info(f"开始同步媒体:{item.get('Name')},ID:{item.get('Id')}") - item_info = self.__get_item_info(item.get("Id")) - seasons = self.__get_items(item.get("Id")) - for season in seasons: - season_info = self.__get_item_info(season.get("Id")) - peoples = season_info.get("People") or item_info.get("People") - season_items = self.__get_items(season.get("Id")) - for season_item in season_items: - retry = 0 - while retry < 3: - season_item_info = self.__get_item_info(season_item.get("Id")) - try: - if season_item_info.get("People") == peoples: - logger.warn( - f"媒体:{item.get('Name')} {season_item_info.get('SeasonName')} {season_item_info.get('IndexNumber')} {season_item_info.get('Name')} 演员信息已更新") - retry = 3 - continue - season_item_info.update({ - "People": peoples - }) - season_item_info["LockedFields"].append("Cast") - flag = self.__update_item_info(season_item.get("Id"), season_item_info) - logger.info( - f"更新媒体:{item.get('Name')} {season_item_info.get('SeasonName')} {season_item_info.get('IndexNumber')} {season_item_info.get('Name')} 成功:{flag}") - if flag: - retry = 3 - time.sleep(0.5) - else: + logger.info(f"开始同步媒体库:{library.name},ID:{library.id}") + + # 遍历媒体列表,获取媒体的ID和名称 + for item in library_items: + if media_name: + # 电影弹幕 + matches = re.findall(r'^(.+?)\s\(\d{4}\)$', item.get("Name").strip()) + if (not matches and media_name != item.get("Name")) or (matches and str(matches[0]) != media_name): + continue + + logger.info(f"开始同步媒体:{item.get('Name')},ID:{item.get('Id')}") + item_info = self.__get_item_info(item.get("Id")) + seasons = self.__get_items(item.get("Id")) + for season in seasons: + season_info = self.__get_item_info(season.get("Id")) + peoples = season_info.get("People") or item_info.get("People") + season_items = self.__get_items(season.get("Id")) + for season_item in season_items: + retry = 0 + while retry < 3: + season_item_info = self.__get_item_info(season_item.get("Id")) + try: + if season_item_info.get("People") == peoples: + logger.warn( + f"媒体:{item.get('Name')} {season_item_info.get('SeasonName')} {season_item_info.get('IndexNumber')} {season_item_info.get('Name')} 演员信息已更新") + retry = 3 + continue + season_item_info.update({ + "People": peoples + }) + season_item_info["LockedFields"].append("Cast") + flag = self.__update_item_info(season_item.get("Id"), season_item_info) + logger.info( + f"更新媒体:{item.get('Name')} {season_item_info.get('SeasonName')} {season_item_info.get('IndexNumber')} {season_item_info.get('Name')} 成功:{flag}") + if flag: + retry = 3 + time.sleep(0.5) + else: + retry += 1 + except Exception as e: retry += 1 - except Exception as e: - retry += 1 - logger.error( - f"更新媒体:{item.get('Name')} {season_item_info.get('SeasonName')} {season_item_info.get('IndexNumber')} {season_item_info.get('Name')} 信息出错:{e} 开始重试...{retry} / 3") - if event: - self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {media_name} 同步完成", - userid=event.event_data.get("user")) - logger.info(f"Emby剧集演员同步完成") + logger.error( + f"更新媒体:{item.get('Name')} {season_item_info.get('SeasonName')} {season_item_info.get('IndexNumber')} {season_item_info.get('Name')} 信息出错:{e} 开始重试...{retry} / 3") + if event: + self.post_message(channel=event.event_data.get("channel"), + title=f"{library_name} {media_name} 同步完成", + userid=event.event_data.get("user")) + logger.info(f"{emby_name} 剧集演员同步完成") def __update_item_info(self, item_id, data): headers = { @@ -256,9 +256,6 @@ class EmbyActorSync(_PluginBase): """ 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 """ - librarys = Emby().get_librarys() - library_options = [{'title': library.name, 'value': library.name} for library in librarys if - library.type == MediaType.TV.value] return [ { "component": "VForm", @@ -301,26 +298,30 @@ class EmbyActorSync(_PluginBase): ] }, { - "component": "VRow", - "content": [ + 'component': 'VRow', + 'content': [ { 'component': 'VCol', 'props': { - 'cols': 12, - 'md': 4 + 'cols': 12 }, 'content': [ { 'component': 'VSelect', 'props': { - 'model': 'librarys', - 'label': '媒体库', - 'items': library_options + 'multiple': True, + 'chips': True, + 'clearable': True, + 'model': 'mediaservers', + 'label': '媒体服务器', + 'items': [{"title": config.name, "value": config.name} + for config in self.mediaserver_helper.get_configs().values() if + config.type == "emby"] } } ] - }, - ], + } + ] }, { 'component': 'VRow', @@ -348,7 +349,7 @@ class EmbyActorSync(_PluginBase): ], { "enabled": False, "onlyonce": False, - "librarys": [], + "mediaservers": [], } def get_page(self) -> List[dict]: diff --git a/plugins.v2/embyaudiobook/__init__.py b/plugins.v2/embyaudiobook/__init__.py index 8bc470f..4e6d686 100644 --- a/plugins.v2/embyaudiobook/__init__.py +++ b/plugins.v2/embyaudiobook/__init__.py @@ -28,7 +28,7 @@ class EmbyAudioBook(_PluginBase): # 插件图标 plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/audiobook.png" # 插件版本 - plugin_version = "1.1.1" + plugin_version = "1.2" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -49,6 +49,7 @@ class EmbyAudioBook(_PluginBase): _cron = None _library_id = None _msgtype = None + _mediaservers = None mediaserver_helper = None _EMBY_HOST = None @@ -70,20 +71,7 @@ class EmbyAudioBook(_PluginBase): self._notify = config.get("notify") self._rename = config.get("rename") self._msgtype = config.get("msgtype") - - emby_server = self.mediaserver_helper.get_service(name="Emby") - if not emby_server: - logger.error("未配置Emby媒体服务器") - return - - self._EMBY_USER = emby_server.instance.get_user() - self._EMBY_HOST = emby_server.config.get("host") - self._EMBY_APIKEY = emby_server.config.get("apikey") - - if not self._EMBY_HOST.endswith("/"): - self._EMBY_HOST += "/" - if not self._EMBY_HOST.startswith("http"): - self._EMBY_HOST = "http://" + self._EMBY_HOST + self._mediaservers = config.get("mediaservers") or [] # 停止现有任务 self.stop_service() @@ -132,6 +120,7 @@ class EmbyAudioBook(_PluginBase): "cron": self._cron, "notify": self._notify, "msgtype": self._msgtype, + "mediaservers": self._mediaservers, }) def check(self): @@ -139,48 +128,63 @@ class EmbyAudioBook(_PluginBase): logger.error("请设置有声书文件夹ID!") return - # 获取所有有声书 - items = self.__get_items(parent_id=int(self._library_id)) - if not items: - logger.error(f"获取媒体库 {self._library_id} 有声书列表失败!") + emby_servers = self.mediaserver_helper.get_services(name_filters=self._mediaservers, type_filter="emby") + if not emby_servers: + logger.error("未配置Emby媒体服务器") return - # 检查有声书是否需要整理 - for item in items: - book_items = self.__get_items(item.get("Id")) - if not book_items: - logger.error(f"获取 {item.get('Name')} {item.get('Id')} 有声书失败!") + for emby_name, emby_server in emby_servers.items(): + logger.info(f"开始处理媒体服务器 {emby_name}") + self._EMBY_USER = emby_server.instance.get_user() + self._EMBY_APIKEY = emby_server.config.config.get("apikey") + self._EMBY_HOST = emby_server.config.config.get("host") + if not self._EMBY_HOST.endswith("/"): + self._EMBY_HOST += "/" + if not self._EMBY_HOST.startswith("http"): + self._EMBY_HOST = "http://" + self._EMBY_HOST + + # 获取所有有声书 + items = self.__get_items(parent_id=int(self._library_id)) + if not items: + logger.error(f"获取媒体库 {self._library_id} 有声书列表失败!") return # 检查有声书是否需要整理 - __need_zl = False - for book_item in book_items: - if not book_item.get("AlbumId"): - __need_zl = True - break + for item in items: + book_items = self.__get_items(item.get("Id")) + if not book_items: + logger.error(f"获取 {item.get('Name')} {item.get('Id')} 有声书失败!") + return - # 需要整理的提示需要整理 - if __need_zl: - logger.info(f"有声书 {item.get('Name')} 需要整理,共 {len(book_items)} 集") - # self.__zl(items, -1) - # 发送通知 - if self._notify: - mtype = NotificationType.Manual - if self._msgtype: - mtype = NotificationType.__getitem__(str(self._msgtype)) or NotificationType.Manual - self.post_message(title="Emby有声书整理", - mtype=mtype, - text=f"有声书 {item.get('Name')} 需要整理,共 {len(book_items)} 集") - else: - # 不需要整理的锁定 - other_book_info = self.__get_item_info(item.get("Id")) - other_book_info.update({ - "LockData": True, - }) - self.__update_item_info(item.get("Id"), other_book_info) - logger.info(f"有声书 {item.get('Name')} 不需要整理,已锁定") + # 检查有声书是否需要整理 + __need_zl = False + for book_item in book_items: + if not book_item.get("AlbumId"): + __need_zl = True + break - logger.info("Emby有声书整理服务执行完毕") + # 需要整理的提示需要整理 + if __need_zl: + logger.info(f"有声书 {item.get('Name')} 需要整理,共 {len(book_items)} 集") + # self.__zl(items, -1) + # 发送通知 + if self._notify: + mtype = NotificationType.Manual + if self._msgtype: + mtype = NotificationType.__getitem__(str(self._msgtype)) or NotificationType.Manual + self.post_message(title="Emby有声书整理", + mtype=mtype, + text=f"有声书 {item.get('Name')} 需要整理,共 {len(book_items)} 集") + else: + # 不需要整理的锁定 + other_book_info = self.__get_item_info(item.get("Id")) + other_book_info.update({ + "LockData": True, + }) + self.__update_item_info(item.get("Id"), other_book_info) + logger.info(f"有声书 {item.get('Name')} 不需要整理,已锁定") + + logger.info(f"{emby_name} 有声书整理服务执行完毕") @eventmanager.register(EventType.PluginAction) def audiobook(self, event: Event = None): @@ -573,6 +577,32 @@ class EmbyAudioBook(_PluginBase): } ] }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12 + }, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'multiple': True, + 'chips': True, + 'clearable': True, + 'model': 'mediaservers', + 'label': '媒体服务器', + 'items': [{"title": config.name, "value": config.name} + for config in self.mediaserver_helper.get_configs().values() if + config.type == "emby"] + } + } + ] + } + ] + }, { 'component': 'VRow', 'content': [ @@ -604,6 +634,7 @@ class EmbyAudioBook(_PluginBase): "cron": "", "msgtype": "", "library_id": "", + "mediaservers": [], } def get_page(self) -> List[dict]: diff --git a/plugins.v2/embycollectionsort/__init__.py b/plugins.v2/embycollectionsort/__init__.py index baa7598..ba587b5 100644 --- a/plugins.v2/embycollectionsort/__init__.py +++ b/plugins.v2/embycollectionsort/__init__.py @@ -27,7 +27,7 @@ class EmbyCollectionSort(_PluginBase): # 插件图标 plugin_icon = "Element_A.png" # 插件版本 - plugin_version = "1.1.1" + plugin_version = "1.2" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -45,6 +45,8 @@ class EmbyCollectionSort(_PluginBase): _cron = None _sort_type = None _collection_library_id = None + _mediaservers = None + mediaserver_helper = None _EMBY_HOST = None _EMBY_USER = None @@ -62,20 +64,8 @@ class EmbyCollectionSort(_PluginBase): self._cron = config.get("cron") self._sort_type = config.get("sort_type") or "asc" self._collection_library_id = config.get("collection_library_id") + self._mediaservers = config.get("mediaservers") or [] - emby_server = self.mediaserver_helper.get_service(name="Emby") - if not emby_server: - logger.error("未配置Emby媒体服务器") - return - - self._EMBY_USER = emby_server.instance.get_user() - self._EMBY_HOST = emby_server.config.get("host") - self._EMBY_APIKEY = emby_server.config.get("apikey") - if not self._EMBY_HOST.endswith("/"): - self._EMBY_HOST += "/" - if not self._EMBY_HOST.startswith("http"): - self._EMBY_HOST = "http://" + self._EMBY_HOST - # 加载模块 if self._enabled or self._onlyonce: # 定时服务 @@ -121,6 +111,7 @@ class EmbyCollectionSort(_PluginBase): "enabled": self._enabled, "sort_type": self._sort_type, "collection_library_id": self._collection_library_id, + "mediaservers": self._mediaservers, } ) @@ -128,93 +119,104 @@ class EmbyCollectionSort(_PluginBase): """ 更改合集媒体入库时间 """ - if "emby" not in settings.MEDIASERVER: - logger.error("未配置Emby媒体服务器") - return - if not self._collection_library_id: logger.error("未配置合集所在媒体库") return - # 获取合集列表 - collections = self.__get_items(self._collection_library_id) - handle_times = [] + emby_servers = self.mediaserver_helper.get_services(name_filters=self._mediaservers, type_filter="emby") + if not emby_servers: + logger.error("未配置Emby媒体服务器") + return - for collection in collections: - logger.info(f"开始处理合集: {collection.get('Name')} {collection.get('Id')}") - items = self.__get_items(collection.get("Id")) - item_dict = [] - for item in items: - item_info = self.__get_item_info(item.get("Id")) - item_dict.append({"Name": item.get("Name"), "Id": item.get("Id"), "item_info": item_info}) + for emby_name, emby_server in emby_servers.items(): + logger.info(f"开始处理媒体服务器 {emby_name}") + self._EMBY_USER = emby_server.instance.get_user() + self._EMBY_APIKEY = emby_server.config.config.get("apikey") + self._EMBY_HOST = emby_server.config.config.get("host") + if not self._EMBY_HOST.endswith("/"): + self._EMBY_HOST += "/" + if not self._EMBY_HOST.startswith("http"): + self._EMBY_HOST = "http://" + self._EMBY_HOST - # 按照发布时间排序 - sorted_items = sorted(item_dict, key=lambda x: x.get("item_info").get("PremiereDate"), - reverse=self._sort_type == "降序") - # 初始化时间 - current_time = datetime.strptime(sorted_items[0]["item_info"]["DateCreated"], "%Y-%m-%dT%H:%M:%S.%f0Z") + # 获取合集列表 + collections = self.__get_items(self._collection_library_id) + handle_times = [] - # 更新每个 item 的 DateCreated,规则为 - updated_items = [] + for collection in collections: + logger.info(f"开始处理合集: {collection.get('Name')} {collection.get('Id')}") + items = self.__get_items(collection.get("Id")) + item_dict = [] + for item in items: + item_info = self.__get_item_info(item.get("Id")) + item_dict.append({"Name": item.get("Name"), "Id": item.get("Id"), "item_info": item_info}) - while sorted_items: - sub_update_items = [] + # 按照发布时间排序 + sorted_items = sorted(item_dict, key=lambda x: x.get("item_info").get("PremiereDate"), + reverse=self._sort_type == "降序") + # 初始化时间 + current_time = datetime.strptime(sorted_items[0]["item_info"]["DateCreated"], "%Y-%m-%dT%H:%M:%S.%f0Z") + + # 更新每个 item 的 DateCreated,规则为 + updated_items = [] + + while sorted_items: + sub_update_items = [] + + for item in sorted_items: + with lock: + new_date_created = current_time.strftime("%Y-%m-%dT%H:%M:%S.%f0Z") + # 时间相同,跳过 + if str(new_date_created) == str(item['item_info']['DateCreated']): + logger.debug( + f"合集媒体: {item.get('Name')} 原入库时间 {item['item_info']['DateCreated']} 新入库时间 {new_date_created} 时间相同,跳过") + handle_times.append(str(current_time)) + sub_update_items.append(str(current_time)) + # 时间减一秒,用于下一个 item 的更新 + current_time -= timedelta(seconds=1) + continue + + if str(current_time) in handle_times: + logger.warn( + f"合集媒体: {item.get('Name')} {current_time} 时间已被占用,开始增加 {len(sorted_items) + 1} 秒,重新尝试处理") + # 处理完成的 items 从列表中移除 + handle_times = [str(_time) for _time in handle_times if _time not in sub_update_items] + # 如果时间已被占用,增加 len(sorted_items) + 1 秒 + current_time += timedelta(seconds=len(sorted_items) + 1) + # 重置已处理的 items 列表和 handle_times 集合 + updated_items.clear() + # 时间已被占用,跳出 for 循环 + break - for item in sorted_items: - with lock: - new_date_created = current_time.strftime("%Y-%m-%dT%H:%M:%S.%f0Z") - # 时间相同,跳过 - if str(new_date_created) == str(item['item_info']['DateCreated']): logger.debug( - f"合集媒体: {item.get('Name')} 原入库时间 {item['item_info']['DateCreated']} 新入库时间 {new_date_created} 时间相同,跳过") + f"合集媒体: {item.get('Name')} 原入库时间 {item['item_info']['DateCreated']} 新入库时间 {new_date_created}") + item["item_info"]["DateCreated"] = new_date_created + updated_items.append(item["item_info"]) handle_times.append(str(current_time)) sub_update_items.append(str(current_time)) # 时间减一秒,用于下一个 item 的更新 current_time -= timedelta(seconds=1) - continue + else: + # 所有 item 处理完成,跳出 while 循环 + break + time.sleep(1) - if str(current_time) in handle_times: - logger.warn( - f"合集媒体: {item.get('Name')} {current_time} 时间已被占用,开始增加 {len(sorted_items) + 1} 秒,重新尝试处理") - # 处理完成的 items 从列表中移除 - handle_times = [str(_time) for _time in handle_times if _time not in sub_update_items] - # 如果时间已被占用,增加 len(sorted_items) + 1 秒 - current_time += timedelta(seconds=len(sorted_items) + 1) - # 重置已处理的 items 列表和 handle_times 集合 - updated_items.clear() - # 时间已被占用,跳出 for 循环 - break + if not updated_items: + logger.warn(f"合集: {collection.get('Name')} {collection.get('Id')} 无需更新入库时间") + continue - logger.debug( - f"合集媒体: {item.get('Name')} 原入库时间 {item['item_info']['DateCreated']} 新入库时间 {new_date_created}") - item["item_info"]["DateCreated"] = new_date_created - updated_items.append(item["item_info"]) - handle_times.append(str(current_time)) - sub_update_items.append(str(current_time)) - # 时间减一秒,用于下一个 item 的更新 - current_time -= timedelta(seconds=1) - else: - # 所有 item 处理完成,跳出 while 循环 - break - time.sleep(1) + logger.debug(f"获取合集排序后最新的入库时间: {current_time}") - if not updated_items: - logger.warn(f"合集: {collection.get('Name')} {collection.get('Id')} 无需更新入库时间") - continue + # 更新入库时间 + for item_info in updated_items: + update_flag = self.__update_item_info(item_info.get("Id"), item_info) + if update_flag: + logger.info(f"{item_info.get('Name')} 更新入库时间到{item_info.get('DateCreated')}成功") + else: + logger.error(f"{item_info.get('Name')} 更新入库时间到{item_info.get('DateCreated')}失败") - logger.debug(f"获取合集排序后最新的入库时间: {current_time}") + logger.info(f"合集处理完成: {collection.get('Name')} {collection.get('Id')}") - # 更新入库时间 - for item_info in updated_items: - update_flag = self.__update_item_info(item_info.get("Id"), item_info) - if update_flag: - logger.info(f"{item_info.get('Name')} 更新入库时间到{item_info.get('DateCreated')}成功") - else: - logger.error(f"{item_info.get('Name')} 更新入库时间到{item_info.get('DateCreated')}失败") - - logger.info(f"合集处理完成: {collection.get('Name')} {collection.get('Id')}") - - logger.info(f"更新Emby合集媒体排序完成") + logger.info(f"更新 {emby_name} 合集媒体排序完成") @eventmanager.register(EventType.PluginAction) def remote_sync(self, event: Event): @@ -378,7 +380,32 @@ class EmbyCollectionSort(_PluginBase): }, ], }, - + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12 + }, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'multiple': True, + 'chips': True, + 'clearable': True, + 'model': 'mediaservers', + 'label': '媒体服务器', + 'items': [{"title": config.name, "value": config.name} + for config in self.mediaserver_helper.get_configs().values() if + config.type == "emby"] + } + } + ] + } + ] + }, { 'component': 'VRow', 'content': [ @@ -408,6 +435,7 @@ class EmbyCollectionSort(_PluginBase): "sort_type": "降序", "cron": "5 1 * * *", "collection_library_id": "", + "mediaservers": [], } def get_page(self) -> List[dict]: diff --git a/plugins.v2/embydanmu/__init__.py b/plugins.v2/embydanmu/__init__.py index 2cac05e..2c492e5 100644 --- a/plugins.v2/embydanmu/__init__.py +++ b/plugins.v2/embydanmu/__init__.py @@ -20,7 +20,7 @@ class EmbyDanmu(_PluginBase): # 插件图标 plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/danmu.png" # 插件版本 - plugin_version = "1.2.1" + plugin_version = "1.3" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -36,6 +36,7 @@ class EmbyDanmu(_PluginBase): _enabled = False _library_task = {} _danmu_source = [] + _mediaservers = None mediaserver_helper = None _EMBY_HOST = None @@ -49,19 +50,7 @@ class EmbyDanmu(_PluginBase): # 读取配置 if config: self._enabled = config.get("enabled") - - emby_server = self.mediaserver_helper.get_service(name="Emby") - if not emby_server: - logger.error("未配置Emby媒体服务器") - return - - self._EMBY_USER = emby_server.instance.get_user() - self._EMBY_HOST = emby_server.config.get("host") - self._EMBY_APIKEY = emby_server.config.get("apikey") - if not self._EMBY_HOST.endswith("/"): - self._EMBY_HOST += "/" - if not self._EMBY_HOST.startswith("http"): - self._EMBY_HOST = "http://" + self._EMBY_HOST + self._mediaservers = config.get("mediaservers") or [] self._danmu_source = self.__get_danmu_source() @@ -95,145 +84,210 @@ class EmbyDanmu(_PluginBase): userid=event.event_data.get("user")) return - library_name = args_list[0] - library_item_name = args_list[1] - library_item_season = None - if len(args_list) == 3: - library_item_season = int(args_list[2]) - logger.info( - f"开始下载弹幕文件:{library_name} - {library_item_name} {f'(季{library_item_season})' if library_item_season else ''}") - - # 获取媒体库信息 - librarys = self.__get_librarys() - - library_id = None - library_options = None - library_type = None - # 匹配需要的媒体库 - for library in librarys: - if library.get("Name") == library_name: - logger.info(f"找到媒体库:{library_name},ID:{library.get('Id')}") - library_type = library.get("CollectionType") - library_id = library.get("Id") - library_options = library.get("LibraryOptions") - break - - if not library_id or not library_options: - logger.error(f"未找到媒体库:{library_name}") - self.post_message(channel=event.event_data.get("channel"), - title=f"未找到媒体库:{library_name}", - userid=event.event_data.get("user")) + emby_servers = self.mediaserver_helper.get_services(name_filters=self._mediaservers, type_filter="emby") + if not emby_servers: + logger.error("未配置Emby媒体服务器") return - # 开启Danmu插件 - # 检查是否已经禁用了Danmu插件,如禁用则先启用 - enabled_danmu = False - library_disabled_subtitle_fetchers = library_options.get("DisabledSubtitleFetchers", []) - if "Danmu" in library_disabled_subtitle_fetchers: - library_disabled_subtitle_fetchers.remove("Danmu") - library_options.update({ - "DisabledSubtitleFetchers": library_disabled_subtitle_fetchers, - }) - enabled_danmu = True + for emby_name, emby_server in emby_servers.items(): + logger.info(f"开始处理媒体服务器 {emby_name}") + self._EMBY_USER = emby_server.instance.get_user() + self._EMBY_APIKEY = emby_server.config.config.get("apikey") + self._EMBY_HOST = emby_server.config.config.get("host") + if not self._EMBY_HOST.endswith("/"): + self._EMBY_HOST += "/" + if not self._EMBY_HOST.startswith("http"): + self._EMBY_HOST = "http://" + self._EMBY_HOST - # 启用Danmu插件 - if enabled_danmu: - update_flag = self.__update_library(library_id, library_options) - if update_flag: - logger.info(f"已启用媒体库:{library_name}的Danmu插件") - else: - logger.error(f"启用媒体库:{library_name}的Danmu插件失败") + library_name = args_list[0] + library_item_name = args_list[1] + library_item_season = None + if len(args_list) == 3: + library_item_season = int(args_list[2]) + logger.info( + f"开始下载弹幕文件:{library_name} - {library_item_name} {f'(季{library_item_season})' if library_item_season else ''}") + + # 获取媒体库信息 + librarys = self.__get_librarys() + + library_id = None + library_options = None + library_type = None + # 匹配需要的媒体库 + for library in librarys: + if library.get("Name") == library_name: + logger.info(f"找到媒体库:{library_name},ID:{library.get('Id')}") + library_type = library.get("CollectionType") + library_id = library.get("Id") + library_options = library.get("LibraryOptions") + break + + if not library_id or not library_options: + logger.error(f"未找到媒体库:{library_name}") self.post_message(channel=event.event_data.get("channel"), - title=f"启用媒体库:{library_name}的Danmu插件失败", + title=f"未找到媒体库:{library_name}", userid=event.event_data.get("user")) - return - else: - logger.info(f"媒体库:{library_name}的Danmu插件已启用") + break - # 媒体库设置为正在任务,不关闭弹幕插件 - _library_task = self._library_task.get(library_id, []) - _library_task.append(library_item_name) - self._library_task[library_id] = _library_task + # 开启Danmu插件 + # 检查是否已经禁用了Danmu插件,如禁用则先启用 + enabled_danmu = False + library_disabled_subtitle_fetchers = library_options.get("DisabledSubtitleFetchers", []) + if "Danmu" in library_disabled_subtitle_fetchers: + library_disabled_subtitle_fetchers.remove("Danmu") + library_options.update({ + "DisabledSubtitleFetchers": library_disabled_subtitle_fetchers, + }) + enabled_danmu = True - try: - # 获取媒体库媒体列表 - library_items = self.__get_items(library_id) - if not library_items: - logger.error(f"获取媒体库:{library_name}的媒体列表失败") - self.post_message(channel=event.event_data.get("channel"), - title=f"获取媒体库:{library_name}的媒体列表失败", - userid=event.event_data.get("user")) + # 启用Danmu插件 + if enabled_danmu: + update_flag = self.__update_library(library_id, library_options) + if update_flag: + logger.info(f"已启用媒体库:{library_name}的Danmu插件") + else: + logger.error(f"启用媒体库:{library_name}的Danmu插件失败") + self.post_message(channel=event.event_data.get("channel"), + title=f"启用媒体库:{library_name}的Danmu插件失败", + userid=event.event_data.get("user")) + return else: - found_item = False - # 遍历媒体列表,获取媒体的ID和名称 - for item in library_items: - if library_type == "tvshows": - if item.get("Name") == library_item_name: - found_item = True - logger.info(f"找到媒体:{library_item_name},ID:{item.get('Id')}") + logger.info(f"媒体库:{library_name}的Danmu插件已启用") - # 电视剧弹幕 - seasons = self.__get_items(item.get("Id")) - if len(seasons) == 1: - season_item = seasons[0] - if library_item_season and season_item.get("IndexNumber") != library_item_season: - found_item = False - break + # 媒体库设置为正在任务,不关闭弹幕插件 + _library_task = self._library_task.get(library_id, []) + _library_task.append(library_item_name) + self._library_task[library_id] = _library_task - # 通知Danmu插件获取弹幕 - season_id = season_item.get("Id") - # 判断本地弹幕是否存在 - danmu_cnt, season_item_cnt = self.__check_danmu_exists(season_id, - only_check=True) - if season_item_cnt == danmu_cnt: - logger.info( - f"{library_name} {library_item_name} 第{season_item.get('IndexNumber')}季 弹幕文件已全部存在:{danmu_cnt}/{season_item_cnt}") - self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {library_item_name} 第{season_item.get('IndexNumber')}季 弹幕文件已全部存在:{danmu_cnt}/{season_item_cnt}", - userid=event.event_data.get("user")) - break + try: + # 获取媒体库媒体列表 + library_items = self.__get_items(library_id) + if not library_items: + logger.error(f"获取媒体库:{library_name}的媒体列表失败") + self.post_message(channel=event.event_data.get("channel"), + title=f"获取媒体库:{library_name}的媒体列表失败", + userid=event.event_data.get("user")) + else: + found_item = False + # 遍历媒体列表,获取媒体的ID和名称 + for item in library_items: + if library_type == "tvshows": + if item.get("Name") == library_item_name: + found_item = True + logger.info(f"找到媒体:{library_item_name},ID:{item.get('Id')}") - danmu_flag = self.__download_danmu(season_id) - if danmu_flag: - logger.info( - f"已通知弹幕插件获取 {library_name} {library_item_name} 第{season_item.get('IndexNumber')}季 的弹幕") - self.post_message(channel=event.event_data.get("channel"), - title=f"开始通知Emby下载 {library_name} {library_item_name} 第{season_item.get('IndexNumber')}季 弹幕,异步执行,请耐心等候执行完成消息", - userid=event.event_data.get("user")) + # 电视剧弹幕 + seasons = self.__get_items(item.get("Id")) + if len(seasons) == 1: + season_item = seasons[0] + if library_item_season and season_item.get("IndexNumber") != library_item_season: + found_item = False + break + + # 通知Danmu插件获取弹幕 + season_id = season_item.get("Id") + # 判断本地弹幕是否存在 danmu_cnt, season_item_cnt = self.__check_danmu_exists(season_id, - only_check=False) - if danmu_cnt == 0: - logger.error( - f"{library_name} {library_item_name} Emby已配置弹幕源全部匹配弹幕失败") + only_check=True) + if season_item_cnt == danmu_cnt: + logger.info( + f"{library_name} {library_item_name} 第{season_item.get('IndexNumber')}季 弹幕文件已全部存在:{danmu_cnt}/{season_item_cnt}") self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {library_item_name} Emby已配置弹幕源全部匹配弹幕失败", + title=f"{library_name} {library_item_name} 第{season_item.get('IndexNumber')}季 弹幕文件已全部存在:{danmu_cnt}/{season_item_cnt}", userid=event.event_data.get("user")) - else: - if season_item_cnt == danmu_cnt: - logger.info( - f"{library_name} {library_item_name} 第{season_item.get('IndexNumber')}季 弹幕文件已全部下载完成:{danmu_cnt}/{season_item_cnt}") + break + + danmu_flag = self.__download_danmu(season_id) + if danmu_flag: + logger.info( + f"已通知弹幕插件获取 {library_name} {library_item_name} 第{season_item.get('IndexNumber')}季 的弹幕") + self.post_message(channel=event.event_data.get("channel"), + title=f"开始通知Emby下载 {library_name} {library_item_name} 第{season_item.get('IndexNumber')}季 弹幕,异步执行,请耐心等候执行完成消息", + userid=event.event_data.get("user")) + danmu_cnt, season_item_cnt = self.__check_danmu_exists(season_id, + only_check=False) + if danmu_cnt == 0: + logger.error( + f"{library_name} {library_item_name} Emby已配置弹幕源全部匹配弹幕失败") self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {library_item_name} 第{season_item.get('IndexNumber')}季 弹幕文件已全部下载完成:{danmu_cnt}/{season_item_cnt}", + title=f"{library_name} {library_item_name} 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} 第{season_item.get('IndexNumber')}季 弹幕文件未全部下载完成:{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} 第{season_item.get('IndexNumber')}季 弹幕文件已全部下载完成:{danmu_cnt}/{season_item_cnt}") + self.post_message(channel=event.event_data.get("channel"), + title=f"{library_name} {library_item_name} 第{season_item.get('IndexNumber')}季 弹幕文件已全部下载完成:{danmu_cnt}/{season_item_cnt}", + 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} 第{season_item.get('IndexNumber')}季 弹幕文件未全部下载完成:{danmu_cnt}/{season_item_cnt}", + userid=event.event_data.get("user")) + else: + logger.error( + f"通知弹幕插件获取 {library_name} {library_item_name} 第{season_item.get('IndexNumber')}季 的弹幕失败") + self.post_message(channel=event.event_data.get("channel"), + title=f"通知弹幕插件获取 {library_name} {library_item_name} 第{season_item.get('IndexNumber')}季 的弹幕失败", + userid=event.event_data.get("user")) else: - logger.error( - f"通知弹幕插件获取 {library_name} {library_item_name} 第{season_item.get('IndexNumber')}季 的弹幕失败") - self.post_message(channel=event.event_data.get("channel"), - title=f"通知弹幕插件获取 {library_name} {library_item_name} 第{season_item.get('IndexNumber')}季 的弹幕失败", - userid=event.event_data.get("user")) - else: - for season in seasons: - # 指定季度则只获取指定季度的弹幕 - if library_item_season: - found_item = False - if season.get("IndexNumber") == library_item_season: - found_item = True + for season in seasons: + # 指定季度则只获取指定季度的弹幕 + if library_item_season: + found_item = False + if season.get("IndexNumber") == library_item_season: + found_item = True + season_id = season.get("Id") + # 判断本地弹幕是否存在 + danmu_cnt, season_item_cnt = self.__check_danmu_exists(season_id, + only_check=True) + if season_item_cnt == danmu_cnt: + logger.info( + f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 弹幕文件已全部存在:{danmu_cnt}/{season_item_cnt}") + self.post_message(channel=event.event_data.get("channel"), + title=f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 弹幕文件已全部存在:{danmu_cnt}/{season_item_cnt}", + userid=event.event_data.get("user")) + break + + # 通知Danmu插件获取弹幕 + danmu_flag = self.__download_danmu(season_id) + if danmu_flag: + logger.info( + f"已通知弹幕插件获取 {library_name} {library_item_name} 第{season.get('IndexNumber')}季 的弹幕") + self.post_message(channel=event.event_data.get("channel"), + title=f"开始通知Emby下载 {library_name} {library_item_name} 第{season.get('IndexNumber')}季 弹幕,异步执行,请耐心等候执行完成消息", + userid=event.event_data.get("user")) + danmu_cnt, season_item_cnt = self.__check_danmu_exists(season_id, + only_check=False) + if danmu_cnt == 0: + logger.error( + f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 Emby已配置弹幕源全部匹配弹幕失败") + self.post_message(channel=event.event_data.get("channel"), + title=f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 Emby已配置弹幕源全部匹配弹幕失败", + userid=event.event_data.get("user")) + else: + if season_item_cnt == danmu_cnt: + logger.info( + f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 弹幕文件已全部下载完成:{danmu_cnt}/{season_item_cnt}") + self.post_message(channel=event.event_data.get("channel"), + title=f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 弹幕文件已全部下载完成:{danmu_cnt}/{season_item_cnt}", + userid=event.event_data.get("user")) + else: + logger.error( + f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 弹幕文件未全部下载完成:{danmu_cnt}/{season_item_cnt}") + self.post_message(channel=event.event_data.get("channel"), + title=f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 弹幕文件未全部下载完成:{danmu_cnt}/{season_item_cnt}", + userid=event.event_data.get("user")) + else: + logger.error( + f"通知弹幕插件获取 {library_name} {library_item_name} 第{season.get('IndexNumber')}季 的弹幕失败") + self.post_message(channel=event.event_data.get("channel"), + title=f"通知弹幕插件获取 {library_name} {library_item_name} 第{season.get('IndexNumber')}季 的弹幕失败", + userid=event.event_data.get("user")) + break + else: + # 未指定季度则获取全部季度的弹幕 season_id = season.get("Id") # 判断本地弹幕是否存在 danmu_cnt, season_item_cnt = self.__check_danmu_exists(season_id, @@ -244,7 +298,7 @@ class EmbyDanmu(_PluginBase): self.post_message(channel=event.event_data.get("channel"), title=f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 弹幕文件已全部存在:{danmu_cnt}/{season_item_cnt}", userid=event.event_data.get("user")) - break + continue # 通知Danmu插件获取弹幕 danmu_flag = self.__download_danmu(season_id) @@ -281,150 +335,100 @@ class EmbyDanmu(_PluginBase): self.post_message(channel=event.event_data.get("channel"), title=f"通知弹幕插件获取 {library_name} {library_item_name} 第{season.get('IndexNumber')}季 的弹幕失败", userid=event.event_data.get("user")) - break - else: - # 未指定季度则获取全部季度的弹幕 - season_id = season.get("Id") - # 判断本地弹幕是否存在 - danmu_cnt, season_item_cnt = self.__check_danmu_exists(season_id, - only_check=True) - if season_item_cnt == danmu_cnt: - logger.info( - f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 弹幕文件已全部存在:{danmu_cnt}/{season_item_cnt}") - self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 弹幕文件已全部存在:{danmu_cnt}/{season_item_cnt}", - userid=event.event_data.get("user")) - continue - - # 通知Danmu插件获取弹幕 - danmu_flag = self.__download_danmu(season_id) - if danmu_flag: - logger.info( - f"已通知弹幕插件获取 {library_name} {library_item_name} 第{season.get('IndexNumber')}季 的弹幕") - self.post_message(channel=event.event_data.get("channel"), - title=f"开始通知Emby下载 {library_name} {library_item_name} 第{season.get('IndexNumber')}季 弹幕,异步执行,请耐心等候执行完成消息", - userid=event.event_data.get("user")) - danmu_cnt, season_item_cnt = self.__check_danmu_exists(season_id, - only_check=False) - if danmu_cnt == 0: - logger.error( - f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 Emby已配置弹幕源全部匹配弹幕失败") - self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 Emby已配置弹幕源全部匹配弹幕失败", - userid=event.event_data.get("user")) - else: - if season_item_cnt == danmu_cnt: - logger.info( - f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 弹幕文件已全部下载完成:{danmu_cnt}/{season_item_cnt}") - self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 弹幕文件已全部下载完成:{danmu_cnt}/{season_item_cnt}", - userid=event.event_data.get("user")) - else: - logger.error( - f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 弹幕文件未全部下载完成:{danmu_cnt}/{season_item_cnt}") - self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {library_item_name} 第{season.get('IndexNumber')}季 弹幕文件未全部下载完成:{danmu_cnt}/{season_item_cnt}", - userid=event.event_data.get("user")) - else: - logger.error( - f"通知弹幕插件获取 {library_name} {library_item_name} 第{season.get('IndexNumber')}季 的弹幕失败") - self.post_message(channel=event.event_data.get("channel"), - title=f"通知弹幕插件获取 {library_name} {library_item_name} 第{season.get('IndexNumber')}季 的弹幕失败", - userid=event.event_data.get("user")) - else: - # 电影弹幕 - matches = re.findall(r'^(.+?)\s\(\d{4}\)', item.get("Name"), re.MULTILINE) - if matches and str(matches[0]) == library_item_name: - found_item = True - movie_id = item.get("Id") - movie_items = self.__get_items(movie_id) - if not movie_items: - logger.error(f"获取 {library_name} {item.get('Name')}的媒体列表失败") - self.post_message(channel=event.event_data.get("channel"), - title=f"获取电影:{library_name} {item.get('Name')}的媒体列表失败", - userid=event.event_data.get("user")) - else: - movie_id = movie_items[0].get("Id") - # 获取媒体详情 - 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(item_path).stem + "*.xml" - - if len(list(parent_path.glob(danmu_path_pattern))) >= 1: - logger.info(f"{parent_path} 下已存在弹幕文件:{danmu_path_pattern}") + else: + # 电影弹幕 + matches = re.findall(r'^(.+?)\s\(\d{4}\)', item.get("Name"), re.MULTILINE) + if matches and str(matches[0]) == library_item_name: + found_item = True + movie_id = item.get("Id") + movie_items = self.__get_items(movie_id) + if not movie_items: + logger.error(f"获取 {library_name} {item.get('Name')}的媒体列表失败") self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {item.get('Name')} 弹幕已存在", + title=f"获取电影:{library_name} {item.get('Name')}的媒体列表失败", userid=event.event_data.get("user")) else: - # 通知Danmu插件获取弹幕 - danmu_flag = self.__download_danmu(movie_id) - if danmu_flag: - logger.info( - f"已通知弹幕插件获取 {library_name} {item.get('Name')} {movie_id} 的弹幕") - self.post_message(channel=event.event_data.get("channel"), - title=f"开始通知Emby下载 {library_name} {item.get('Name')} 弹幕,异步执行,请耐心等候执行完成消息", - userid=event.event_data.get("user")) - retry_cnt = 3 - while len( - list(parent_path.glob(danmu_path_pattern))) == 0 and retry_cnt > 0: - # 解析日志判断是否全部失败 - 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) + movie_id = movie_items[0].get("Id") + # 获取媒体详情 + 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(item_path).stem + "*.xml" - if len(list(parent_path.glob(danmu_path_pattern))) >= 1: - logger.info(f"{parent_path} 下已找到弹幕文件:{danmu_path_pattern}") - self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {item.get('Name')} 下载弹幕文件成功", - userid=event.event_data.get("user")) - else: - logger.error(f"{parent_path} 下未找到弹幕文件:{danmu_path_pattern}") - self.post_message(channel=event.event_data.get("channel"), - title=f"{library_name} {item.get('Name')} 已配置弹幕源全部匹配弹幕失败", - userid=event.event_data.get("user")) + if len(list(parent_path.glob(danmu_path_pattern))) >= 1: + logger.info(f"{parent_path} 下已存在弹幕文件:{danmu_path_pattern}") + self.post_message(channel=event.event_data.get("channel"), + title=f"{library_name} {item.get('Name')} 弹幕已存在", + userid=event.event_data.get("user")) else: - logger.error( - f"通知弹幕插件获取 {library_name} {item.get('Name')} {movie_id} 的弹幕失败") - self.post_message(channel=event.event_data.get("channel"), - title=f"通知弹幕插件获取 {library_name} 电影 {item.get('Name')} {movie_id} 的弹幕失败", - userid=event.event_data.get("user")) - if not found_item: - logger.error( - f"未找到媒体:{library_name} {library_item_name} {f'第{library_item_season}季 ' if library_item_season else ''}") - self.post_message(channel=event.event_data.get("channel"), - title=f"未找到媒体:{library_name} {library_item_name} {f'第{library_item_season}季 ' if library_item_season else ''}", - userid=event.event_data.get("user")) - except Exception as e: - logger.error( - f"{library_name} {library_item_name} {f'第{library_item_season}季 ' if library_item_season else ''}获取弹幕任务出错:{str(e)}") + # 通知Danmu插件获取弹幕 + danmu_flag = self.__download_danmu(movie_id) + if danmu_flag: + logger.info( + f"已通知弹幕插件获取 {library_name} {item.get('Name')} {movie_id} 的弹幕") + self.post_message(channel=event.event_data.get("channel"), + title=f"开始通知Emby下载 {library_name} {item.get('Name')} 弹幕,异步执行,请耐心等候执行完成消息", + userid=event.event_data.get("user")) + retry_cnt = 3 + while len( + list(parent_path.glob(danmu_path_pattern))) == 0 and retry_cnt > 0: + # 解析日志判断是否全部失败 + 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) - # 判断当前媒体库是否有其他任务在执行 - self._library_task[library_id].remove(library_item_name) - if len(self._library_task[library_id]) == 0: - # 关闭弹幕插件 - logger.info( - f"{library_name} {library_item_name} {f'第{library_item_season}季 ' if library_item_season else ''}获取弹幕任务完成,关闭弹幕插件") - # 禁用媒体库的Danmu插件 - library_disabled_subtitle_fetchers = library_options.get("DisabledSubtitleFetchers", []) - library_disabled_subtitle_fetchers.append("Danmu") - library_options.update({ - "DisabledSubtitleFetchers": library_disabled_subtitle_fetchers, - }) - update_flag = self.__update_library(library_id, library_options) - if update_flag: - logger.info(f"已禁用媒体库:{library_name} Danmu插件") - else: - logger.error(f"禁用媒体库:{library_name} Danmu插件失败") + if len(list(parent_path.glob(danmu_path_pattern))) >= 1: + logger.info(f"{parent_path} 下已找到弹幕文件:{danmu_path_pattern}") + self.post_message(channel=event.event_data.get("channel"), + title=f"{library_name} {item.get('Name')} 下载弹幕文件成功", + userid=event.event_data.get("user")) + else: + logger.error(f"{parent_path} 下未找到弹幕文件:{danmu_path_pattern}") + self.post_message(channel=event.event_data.get("channel"), + title=f"{library_name} {item.get('Name')} 已配置弹幕源全部匹配弹幕失败", + userid=event.event_data.get("user")) + else: + logger.error( + f"通知弹幕插件获取 {library_name} {item.get('Name')} {movie_id} 的弹幕失败") + self.post_message(channel=event.event_data.get("channel"), + title=f"通知弹幕插件获取 {library_name} 电影 {item.get('Name')} {movie_id} 的弹幕失败", + userid=event.event_data.get("user")) + if not found_item: + logger.error( + f"未找到媒体:{library_name} {library_item_name} {f'第{library_item_season}季 ' if library_item_season else ''}") + self.post_message(channel=event.event_data.get("channel"), + title=f"未找到媒体:{library_name} {library_item_name} {f'第{library_item_season}季 ' if library_item_season else ''}", + userid=event.event_data.get("user")) + except Exception as e: + logger.error( + f"{library_name} {library_item_name} {f'第{library_item_season}季 ' if library_item_season else ''}获取弹幕任务出错:{str(e)}") + + # 判断当前媒体库是否有其他任务在执行 + self._library_task[library_id].remove(library_item_name) + if len(self._library_task[library_id]) == 0: + # 关闭弹幕插件 + logger.info( + f"{library_name} {library_item_name} {f'第{library_item_season}季 ' if library_item_season else ''}获取弹幕任务完成,关闭弹幕插件") + # 禁用媒体库的Danmu插件 + library_disabled_subtitle_fetchers = library_options.get("DisabledSubtitleFetchers", []) + library_disabled_subtitle_fetchers.append("Danmu") + library_options.update({ + "DisabledSubtitleFetchers": library_disabled_subtitle_fetchers, + }) + update_flag = self.__update_library(library_id, library_options) + if update_flag: + logger.info(f"已禁用媒体库:{library_name} Danmu插件") + else: + logger.error(f"禁用媒体库:{library_name} Danmu插件失败") def get_state(self) -> bool: return self._enabled @@ -705,6 +709,32 @@ class EmbyDanmu(_PluginBase): } ] }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12 + }, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'multiple': True, + 'chips': True, + 'clearable': True, + 'model': 'mediaservers', + 'label': '媒体服务器', + 'items': [{"title": config.name, "value": config.name} + for config in self.mediaserver_helper.get_configs().values() if + config.type == "emby"] + } + } + ] + } + ] + }, { 'component': 'VRow', 'content': [ @@ -751,6 +781,7 @@ class EmbyDanmu(_PluginBase): } ], { "enabled": False, + "mediaservers": [], } def get_page(self) -> List[dict]: diff --git a/plugins.v2/embyextendtype/__init__.py b/plugins.v2/embyextendtype/__init__.py index 02c82c4..ed37e07 100644 --- a/plugins.v2/embyextendtype/__init__.py +++ b/plugins.v2/embyextendtype/__init__.py @@ -25,7 +25,7 @@ class EmbyExtendType(_PluginBase): # 插件图标 plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/extendtype.png" # 插件版本 - plugin_version = "1.0.1" + plugin_version = "1.1" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -46,6 +46,7 @@ class EmbyExtendType(_PluginBase): _librarys = None _extend = None _msgtype = None + _mediaservers = None # 退出事件 _event = threading.Event() @@ -66,19 +67,7 @@ class EmbyExtendType(_PluginBase): self._notify = config.get("notify") self._extend = config.get("extend") self._msgtype = config.get("msgtype") - - emby_server = self.mediaserver_helper.get_service(name="Emby") - if not emby_server: - logger.error("未配置Emby媒体服务器") - return - - self._EMBY_USER = emby_server.instance.get_user() - self._EMBY_HOST = emby_server.config.get("host") - self._EMBY_APIKEY = emby_server.config.get("apikey") - if not self._EMBY_HOST.endswith("/"): - self._EMBY_HOST += "/" - if not self._EMBY_HOST.startswith("http"): - self._EMBY_HOST = "http://" + self._EMBY_HOST + self._mediaservers = config.get("mediaservers") or [] # 停止现有任务 self.stop_service() @@ -123,30 +112,44 @@ class EmbyExtendType(_PluginBase): logger.error("视频类型为空,不进行检查") return - if not self._librarys: - logger.error("媒体库为空,不进行检查") + emby_servers = self.mediaserver_helper.get_services(name_filters=self._mediaservers, type_filter="emby") + if not emby_servers: + logger.error("未配置Emby媒体服务器") return - logger.info(f"开始检查媒体库 {self._librarys} 中是否包含 {self._extend} 类型") - for library in self._librarys: - library_name, library_id = library.split(" ") - logger.info(f"开始检查媒体库 {library_name} 中是否包含 {self._extend} 类型") - library_extends = self.__get_extend_type(library_id) - if library_extends: - for extend in self._extend.split(","): - if extend in [item.get("Name") for item in library_extends]: - logger.info(f"媒体库 {library_name} 中包含 {extend} 类型") - # 发送通知 - if self._notify: - mtype = NotificationType.Manual - if self._msgtype: - mtype = NotificationType.__getitem__(str(self._msgtype)) or NotificationType.Manual - self.post_message(title="Emby视频类型检查", - mtype=mtype, - text=f"媒体库 {library_name} 命中 {extend} 视频类型") - logger.info(f"媒体库 {library_name} 中全部视频类型检查完毕") + for emby_name, emby_server in emby_servers.items(): + logger.info(f"开始处理媒体服务器 {emby_name}") + self._EMBY_USER = emby_server.instance.get_user() + self._EMBY_APIKEY = emby_server.config.config.get("apikey") + self._EMBY_HOST = emby_server.config.config.get("host") + if not self._EMBY_HOST.endswith("/"): + self._EMBY_HOST += "/" + if not self._EMBY_HOST.startswith("http"): + self._EMBY_HOST = "http://" + self._EMBY_HOST - logger.info(f"媒体库 {self._librarys} 中全部视频类型检查完毕") + # 获取媒体库信息 + librarys = emby_server.instance.get_librarys() + for library in librarys: + logger.info(f"开始检查媒体库 {emby_name} {library.name} 中是否包含 {self._extend} 类型") + library_id = library.id + library_name = library.name + logger.info(f"开始检查媒体库 {library_name} 中是否包含 {self._extend} 类型") + library_extends = self.__get_extend_type(library_id) + if library_extends: + for extend in self._extend.split(","): + if extend in [item.get("Name") for item in library_extends]: + logger.info(f"媒体库 {library_name} 中包含 {extend} 类型") + # 发送通知 + if self._notify: + mtype = NotificationType.Manual + if self._msgtype: + mtype = NotificationType.__getitem__(str(self._msgtype)) or NotificationType.Manual + self.post_message(title="Emby视频类型检查", + mtype=mtype, + text=f"媒体库 {library_name} 命中 {extend} 视频类型") + logger.info(f"{emby_name} 媒体库 {library_name} 中全部视频类型检查完毕") + + logger.info(f"{emby_name} 媒体库中全部视频类型检查完毕") def __get_extend_type(self, parent_id) -> list: """ @@ -179,6 +182,7 @@ class EmbyExtendType(_PluginBase): "extend": self._extend, "notify": self._notify, "msgtype": self._msgtype, + "mediaservers": self._mediaservers, }) def get_state(self) -> bool: @@ -325,7 +329,7 @@ class EmbyExtendType(_PluginBase): { 'component': 'VCol', 'props': { - 'cols': 12, + 'cols': 12 }, 'content': [ { @@ -333,13 +337,16 @@ class EmbyExtendType(_PluginBase): 'props': { 'multiple': True, 'chips': True, - 'model': 'librarys', - 'label': '媒体库', - 'items': library_items + 'clearable': True, + 'model': 'mediaservers', + 'label': '媒体服务器', + 'items': [{"title": config.name, "value": config.name} + for config in self.mediaserver_helper.get_configs().values() if + config.type == "emby"] } } ] - }, + } ] }, ] @@ -350,7 +357,7 @@ class EmbyExtendType(_PluginBase): "notify": False, "cron": "", "extend": "", - "librarys": [], + "mediaservers": [], } def get_page(self) -> List[dict]: diff --git a/plugins.v2/embymetatag/__init__.py b/plugins.v2/embymetatag/__init__.py index de072c3..abdbd73 100644 --- a/plugins.v2/embymetatag/__init__.py +++ b/plugins.v2/embymetatag/__init__.py @@ -23,7 +23,7 @@ class EmbyMetaTag(_PluginBase): # 插件图标 plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/tag.png" # 插件版本 - plugin_version = "1.2.1" + plugin_version = "1.3" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -41,6 +41,8 @@ class EmbyMetaTag(_PluginBase): _cron = None _tag_confs = None _name_tag_confs = None + _mediaservers = None + mediaserver_helper = None _EMBY_HOST = None _EMBY_USER = None @@ -62,19 +64,7 @@ class EmbyMetaTag(_PluginBase): self._cron = config.get("cron") self._tag_confs = config.get("tag_confs") self._name_tag_confs = config.get("name_tag_confs") - - emby_server = self.mediaserver_helper.get_service(name="Emby") - if not emby_server: - logger.error("未配置Emby媒体服务器") - return - - self._EMBY_USER = emby_server.instance.get_user() - self._EMBY_HOST = emby_server.config.get("host") - self._EMBY_APIKEY = emby_server.config.get("apikey") - if not self._EMBY_HOST.endswith("/"): - self._EMBY_HOST += "/" - if not self._EMBY_HOST.startswith("http"): - self._EMBY_HOST = "http://" + self._EMBY_HOST + self._mediaservers = config.get("mediaservers") or [] _tags = {} if self._tag_confs: @@ -147,6 +137,7 @@ class EmbyMetaTag(_PluginBase): "enabled": self._enabled, "tag_confs": self._tag_confs, "name_tag_confs": self._name_tag_confs, + "mediaservers": self._mediaservers, } ) @@ -154,85 +145,96 @@ class EmbyMetaTag(_PluginBase): """ 给设定媒体库打标签 """ - if "emby" not in settings.MEDIASERVER: - logger.error("未配置Emby媒体服务器") - return - if (not self._tags or len(self._tags.keys()) == 0) and ( not self._media_tags or len(self._media_tags.keys()) == 0): logger.error("未配置Emby媒体标签") return - # 媒体库标签 - if self._tags and len(self._tags.keys()) > 0: - # 获取emby 媒体库 - librarys = Emby().get_librarys() - if not librarys: - logger.error("获取媒体库失败") - return + emby_servers = self.mediaserver_helper.get_services(name_filters=self._mediaservers, type_filter="emby") + if not emby_servers: + logger.error("未配置Emby媒体服务器") + return - # 遍历媒体库,获取媒体库媒体 - for library in librarys: - # 获取媒体库标签 - library_tags = self._tags.get(library.name) - if not library_tags: - continue + for emby_name, emby_server in emby_servers.items(): + logger.info(f"开始处理媒体服务器 {emby_name}") + self._EMBY_USER = emby_server.instance.get_user() + self._EMBY_APIKEY = emby_server.config.config.get("apikey") + self._EMBY_HOST = emby_server.config.config.get("host") + if not self._EMBY_HOST.endswith("/"): + self._EMBY_HOST += "/" + if not self._EMBY_HOST.startswith("http"): + self._EMBY_HOST = "http://" + self._EMBY_HOST - # 获取媒体库媒体 - library_items = Emby().get_items(library.id) - if not library_items: - continue + # 媒体库标签 + if self._tags and len(self._tags.keys()) > 0: + # 获取emby 媒体库 + librarys = emby_server.instance.get_librarys() + if not librarys: + logger.error("获取媒体库失败") + return - for library_item in library_items: - if not library_item: - continue - # 获取item的tag - item_tags = self.__get_item_tags(library_item.item_id) or [] - - # 获取缺少的tag - add_tags = [] - for library_tag in library_tags: - if not item_tags or library_tag not in item_tags: - add_tags.append(library_tag) - - # 添加标签 - if add_tags: - tags = [{"Name": str(add_tag)} for add_tag in add_tags] - tags = {"Tags": tags} - add_flag = self.__add_tag(library_item.item_id, tags) - logger.info(f"{library.name} 添加标签成功:{library_item.title} {tags} {add_flag}") - - # 特殊媒体名标签 - if self._media_tags and len(self._media_tags.keys()) > 0: - for media_name, media_tags in self._media_tags.items(): - - match_medias = [] - # 根据Series/Movie搜索媒体 - for media_type in self._media_type.get(media_name): - match_medias += self.__get_medias_by_name(media_name, media_type) - - # 遍历媒体 补充缺失tag - for media in match_medias: - if not media: + # 遍历媒体库,获取媒体库媒体 + for library in librarys: + # 获取媒体库标签 + library_tags = self._tags.get(library.name) + if not library_tags: continue - # 获取item的tag - item_tags = self.__get_item_tags(media.get("Id")) or [] + # 获取媒体库媒体 + library_items = emby_server.instance.get_items(library.id) + if not library_items: + continue - # 获取缺少的tag - add_tags = [] - for media_tag in media_tags: - if not item_tags or media_tag not in item_tags: - add_tags.append(media_tag) + for library_item in library_items: + if not library_item: + continue + # 获取item的tag + item_tags = self.__get_item_tags(library_item.item_id) or [] - # 添加标签 - if add_tags: - tags = [{"Name": str(add_tag)} for add_tag in add_tags] - tags = {"Tags": tags} - add_flag = self.__add_tag(media.get("Id"), tags) - logger.info(f"特殊媒体添加标签成功:{media.get('Name')} {tags} {add_flag}") + # 获取缺少的tag + add_tags = [] + for library_tag in library_tags: + if not item_tags or library_tag not in item_tags: + add_tags.append(library_tag) - logger.info("Emby媒体标签任务完成") + # 添加标签 + if add_tags: + tags = [{"Name": str(add_tag)} for add_tag in add_tags] + tags = {"Tags": tags} + add_flag = self.__add_tag(library_item.item_id, tags) + logger.info(f"{library.name} 添加标签成功:{library_item.title} {tags} {add_flag}") + + # 特殊媒体名标签 + if self._media_tags and len(self._media_tags.keys()) > 0: + for media_name, media_tags in self._media_tags.items(): + + match_medias = [] + # 根据Series/Movie搜索媒体 + for media_type in self._media_type.get(media_name): + match_medias += self.__get_medias_by_name(media_name, media_type) + + # 遍历媒体 补充缺失tag + for media in match_medias: + if not media: + continue + + # 获取item的tag + item_tags = self.__get_item_tags(media.get("Id")) or [] + + # 获取缺少的tag + add_tags = [] + for media_tag in media_tags: + if not item_tags or media_tag not in item_tags: + add_tags.append(media_tag) + + # 添加标签 + if add_tags: + tags = [{"Name": str(add_tag)} for add_tag in add_tags] + tags = {"Tags": tags} + add_flag = self.__add_tag(media.get("Id"), tags) + logger.info(f"特殊媒体添加标签成功:{media.get('Name')} {tags} {add_flag}") + + logger.info(f"{emby_name} 媒体标签任务完成") @eventmanager.register(EventType.PluginAction) def remote_sync(self, event: Event): @@ -423,6 +425,32 @@ class EmbyMetaTag(_PluginBase): } ] }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12 + }, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'multiple': True, + 'chips': True, + 'clearable': True, + 'model': 'mediaservers', + 'label': '媒体服务器', + 'items': [{"title": config.name, "value": config.name} + for config in self.mediaserver_helper.get_configs().values() if + config.type == "emby"] + } + } + ] + } + ] + }, { 'component': 'VRow', 'content': [ @@ -452,6 +480,7 @@ class EmbyMetaTag(_PluginBase): "cron": "5 1 * * *", "tag_confs": "", "name_tag_confs": "", + "mediaservers": [], } def get_page(self) -> List[dict]: diff --git a/plugins.v2/embyreporter/__init__.py b/plugins.v2/embyreporter/__init__.py index ee702dc..c74ac0b 100644 --- a/plugins.v2/embyreporter/__init__.py +++ b/plugins.v2/embyreporter/__init__.py @@ -34,7 +34,7 @@ class EmbyReporter(_PluginBase): # 插件图标 plugin_icon = "Pydiocells_A.png" # 插件版本 - plugin_version = "1.8.1" + plugin_version = "1.9" # 插件作者 plugin_author = "thsrite" # 作者主页 @@ -58,6 +58,8 @@ class EmbyReporter(_PluginBase): _emby_host = None _emby_api_key = None show_time = True + _mediaservers = None + _scheduler: Optional[BackgroundScheduler] = None mediaserver_helper = None PLAYBACK_REPORTING_TYPE_MOVIE = "ItemName" @@ -69,12 +71,6 @@ class EmbyReporter(_PluginBase): # 停止现有任务 self.stop_service() self.mediaserver_helper = MediaServerHelper() - emby_server = self.mediaserver_helper.get_service(name="Emby") - if not emby_server: - logger.error("未配置Emby媒体服务器") - return - self.host = emby_server.config.get("host") - self.api_key = emby_server.config.get("apikey") if config: self._enabled = config.get("enabled") @@ -88,10 +84,7 @@ class EmbyReporter(_PluginBase): self.show_time = config.get("show_time") self._emby_host = config.get("emby_host") self._emby_api_key = config.get("emby_api_key") - if self._emby_host and self._emby_api_key: - self.host = f"http://{self._emby_host}" if not str(self._emby_host).startswith( - "http") else self._emby_host - self.api_key = self._emby_api_key + self._mediaservers = config.get("mediaservers") or [] if self._enabled or self._onlyonce: # 定时服务 @@ -137,47 +130,62 @@ class EmbyReporter(_PluginBase): if not self._type: return - # 获取当前时间并格式化 - current_time = datetime.now().strftime("%Y%m%d%H%M%S") - - # 获取数据 - success, movies = self.get_report(types=self.PLAYBACK_REPORTING_TYPE_MOVIE, days=int(self._days), - limit=int(self._cnt)) - if not success: - exit(movies) - logger.info(f"获取到电影 {movies}") - success, tvshows = self.get_report(types=self.PLAYBACK_REPORTING_TYPE_TVSHOWS, days=int(self._days), - limit=int(self._cnt)) - if not success: - exit(tvshows) - logger.info(f"获取到电视剧 {tvshows}") - - # 绘制海报 - report_path = self.draw(res_path=self._res_dir, - movies=movies, - tvshows=tvshows, - show_time=self.show_time) - - if not report_path: - logger.error("生成海报失败") + emby_servers = self.mediaserver_helper.get_services(name_filters=self._mediaservers, type_filter="emby") + if not emby_servers: + logger.error("未配置Emby媒体服务器") return - # 示例调用 - self.__split_image_by_height(report_path, "/public/report", [250, 330, 335]) + for emby_name, emby_server in emby_servers.items(): + logger.info(f"开始处理媒体服务器 {emby_name}") + self.host = emby_server.config.get("host") + self.api_key = emby_server.config.get("apikey") + if not self.host.endswith("/"): + self.host += "/" + if not self.host.startswith("http"): + self.host = "http://" + self.host - # 分块推送 - for i in range(2, 4): - report_path_part = f"/public/report_part_{i}.jpg" - report_url = self._mp_host + report_path_part.replace("/public", "") + f"?_timestamp={current_time}" - mtype = NotificationType.MediaServer - if self._type: - mtype = NotificationType.__getitem__(str(self._type)) or NotificationType.MediaServer + # 获取当前时间并格式化 + current_time = datetime.now().strftime("%Y%m%d%H%M%S") - self.post_message( - title=f'Movies 近{self._days}日观影排行' if i == 2 else f'TV Shows 近{self._days}日观影排行', - mtype=mtype, - image=report_url) - logger.info(f"Emby观影记录推送成功 {report_url}") + # 获取数据 + success, movies = self.get_report(types=self.PLAYBACK_REPORTING_TYPE_MOVIE, days=int(self._days), + limit=int(self._cnt)) + if not success: + exit(movies) + logger.info(f"获取到电影 {movies}") + success, tvshows = self.get_report(types=self.PLAYBACK_REPORTING_TYPE_TVSHOWS, days=int(self._days), + limit=int(self._cnt)) + if not success: + exit(tvshows) + logger.info(f"获取到电视剧 {tvshows}") + + # 绘制海报 + report_path = self.draw(res_path=self._res_dir, + movies=movies, + tvshows=tvshows, + show_time=self.show_time, + emby_name=emby_name) + + if not report_path: + logger.error("生成海报失败") + break + + # 示例调用 + self.__split_image_by_height(report_path, f"/public/report_{emby_name}", [250, 330, 335]) + + # 分块推送 + for i in range(2, 4): + report_path_part = f"/public/report_{emby_name}_part_{i}.jpg" + report_url = self._mp_host + report_path_part.replace("/public", "") + f"?_timestamp={current_time}" + mtype = NotificationType.MediaServer + if self._type: + mtype = NotificationType.__getitem__(str(self._type)) or NotificationType.MediaServer + + self.post_message( + title=f'Movies 近{self._days}日观影排行' if i == 2 else f'TV Shows 近{self._days}日观影排行', + mtype=mtype, + image=report_url) + logger.info(f"{emby_name} 观影记录推送成功 {report_url}") @staticmethod def __split_image_by_height(image_path, output_path_prefix, heights): @@ -228,7 +236,8 @@ class EmbyReporter(_PluginBase): "show_time": self.show_time, "emby_host": self._emby_host, "emby_api_key": self._emby_api_key, - "res_dir": self._res_dir + "res_dir": self._res_dir, + "mediaservers": self._mediaservers, }) def get_state(self) -> bool: @@ -475,6 +484,32 @@ class EmbyReporter(_PluginBase): } ] }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12 + }, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'multiple': True, + 'chips': True, + 'clearable': True, + 'model': 'mediaservers', + 'label': '媒体服务器', + 'items': [{"title": config.name, "value": config.name} + for config in self.mediaserver_helper.get_configs().values() if + config.type == "emby"] + } + } + ] + } + ] + }, { 'component': 'VRow', 'content': [ @@ -530,7 +565,8 @@ class EmbyReporter(_PluginBase): "emby_api_key": "", "mp_host": "", "show_time": True, - "type": "" + "type": "", + "mediaservers": [], } def get_page(self) -> List[dict]: @@ -549,7 +585,7 @@ class EmbyReporter(_PluginBase): except Exception as e: logger.error("退出插件失败:%s" % str(e)) - def draw(self, res_path, movies, tvshows, show_time=True): + def draw(self, res_path, movies, tvshows, show_time=True, emby_name=None): # 默认路径 默认图 if not res_path: res_path = os.path.join(Path(__file__).parent, "res") @@ -668,7 +704,7 @@ class EmbyReporter(_PluginBase): continue if index >= 0: - save_path = "/public/report.jpg" + save_path = f"/public/report_{emby_name}.jpg" if Path(save_path).exists(): Path.unlink(Path(save_path)) bg.save(save_path)