From 5e6cf4d7d396cbaef701fd7402acc8c2f34496cf Mon Sep 17 00:00:00 2001 From: thsrite Date: Tue, 8 Aug 2023 19:53:44 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat=20=E5=90=8C=E6=AD=A5=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E5=A2=9E=E5=8A=A0Scripter=20X=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/db/models/transferhistory.py | 12 +- app/db/transferhistory_oper.py | 7 +- app/plugins/mediasyncdel/__init__.py | 435 ++++++++++++++++++++------- 3 files changed, 333 insertions(+), 121 deletions(-) diff --git a/app/db/models/transferhistory.py b/app/db/models/transferhistory.py index 6cde6304..d1eec1a6 100644 --- a/app/db/models/transferhistory.py +++ b/app/db/models/transferhistory.py @@ -83,10 +83,20 @@ class TransferHistory(Base): return db.query(func.count(TransferHistory.id)).filter(TransferHistory.title.like(f'%{title}%')).first()[0] @staticmethod - def list_by(db: Session, mtype: str, title: str, year: int, season=None, episode=None): + def list_by(db: Session, mtype: str = None, title: str = None, year: int = None, season: str = None, + episode: str = None, tmdbid: str = None): """ 据tmdbid、season、season_episode查询转移记录 """ + if tmdbid and not season and not episode: + return db.query(TransferHistory).filter(TransferHistory.tmdbid == tmdbid).all() + if tmdbid and season and not episode: + return db.query(TransferHistory).filter(TransferHistory.tmdbid == tmdbid, + TransferHistory.seasons == season).all() + if tmdbid and season and episode: + return db.query(TransferHistory).filter(TransferHistory.tmdbid == tmdbid, + TransferHistory.seasons == season, + TransferHistory.episodes == episode).all() # 电视剧所有季集|电影 if not season and not episode: return db.query(TransferHistory).filter(TransferHistory.type == mtype, diff --git a/app/db/transferhistory_oper.py b/app/db/transferhistory_oper.py index 5959cdda..64d24173 100644 --- a/app/db/transferhistory_oper.py +++ b/app/db/transferhistory_oper.py @@ -43,8 +43,8 @@ class TransferHistoryOper(DbOper): """ return TransferHistory.statistic(self._db, days) - def get_by(self, mtype: str, title: str, year: int, - season: str = None, episode: str = None) -> Any: + def get_by(self, mtype: str = None, title: str = None, year: int = None, + season: str = None, episode: str = None, tmdbid: str = None) -> Any: """ 按类型、标题、年份、季集查询转移记录 """ @@ -53,7 +53,8 @@ class TransferHistoryOper(DbOper): title=title, year=year, season=season, - episode=episode) + episode=episode, + tmdbid=tmdbid) def delete(self, historyid): """ diff --git a/app/plugins/mediasyncdel/__init__.py b/app/plugins/mediasyncdel/__init__.py index ceb70ba7..262345af 100644 --- a/app/plugins/mediasyncdel/__init__.py +++ b/app/plugins/mediasyncdel/__init__.py @@ -15,6 +15,7 @@ from app.db.transferhistory_oper import TransferHistoryOper from app.log import logger from app.modules.emby import Emby from app.modules.jellyfin import Jellyfin +from app.modules.themoviedb.tmdbv3api import Episode from app.plugins import _PluginBase from app.schemas.types import NotificationType, EventType @@ -44,15 +45,17 @@ class MediaSyncDel(_PluginBase): # 私有属性 _scheduler: Optional[BackgroundScheduler] = None _enabled = False + _sync_type: str = "" _cron: str = "" _notify = False _del_source = False _exclude_path = None - + _episode = None _transferhis = None def init_plugin(self, config: dict = None): self._transferhis = TransferHistoryOper() + self.episode = Episode() # 停止现有任务 self.stop_service() @@ -60,6 +63,7 @@ class MediaSyncDel(_PluginBase): # 读取配置 if config: self._enabled = config.get("enabled") + self._sync_type = config.get("sync_type") self._cron = config.get("cron") self._notify = config.get("notify") self._del_source = config.get("del_source") @@ -69,16 +73,23 @@ class MediaSyncDel(_PluginBase): self._scheduler = BackgroundScheduler(timezone=settings.TZ) if self._cron: try: - self._scheduler.add_job(func=self.sync_del, - trigger=CronTrigger.from_crontab(self._cron), - name="媒体库同步删除") + if str(self._sync_type) == "log": + self._scheduler.add_job(func=self.sync_del_by_log, + trigger=CronTrigger.from_crontab(self._cron), + name="媒体库同步删除") + if str(self._sync_type) == "plugin": + self._scheduler.add_job(func=self.sync_del_by_plugin, + trigger=CronTrigger.from_crontab(self._cron), + name="媒体库同步删除") except Exception as err: logger.error(f"定时任务配置错误:{err}") # 推送实时消息 self.systemmessage.put(f"执行周期配置错误:{err}") else: - self._scheduler.add_job(self.sync_del, "interval", minutes=30, name="媒体库同步删除") - + if str(self._sync_type) == "log": + self._scheduler.add_job(self.sync_del_by_log, "interval", minutes=30, name="媒体库同步删除") + if str(self._sync_type) == "plugin": + self._scheduler.add_job(self.sync_del_by_plugin, "interval", minutes=30, name="媒体库同步删除") # 启动任务 if self._scheduler.get_jobs(): self._scheduler.print_jobs() @@ -105,115 +116,150 @@ class MediaSyncDel(_PluginBase): 拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构 """ return [ - { - 'component': 'VForm', - 'content': [ - { - 'component': 'VRow', - 'content': [ - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 6 - }, - 'content': [ - { - 'component': 'VSwitch', - 'props': { - 'model': 'enabled', - 'label': '启用插件', - } - } - ] - }, - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 6 - }, - 'content': [ - { - 'component': 'VSwitch', - 'props': { - 'model': 'notify', - 'label': '发送通知', - } - } - ] - } - ] - }, - { - 'component': 'VRow', - 'content': [ - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 6 - }, - 'content': [ - { - 'component': 'VSwitch', - 'props': { - 'model': 'del_source', - 'label': '删除源文件', - } - } - ] - } - ] - }, - { - 'component': 'VRow', - 'content': [ - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 6 - }, - 'content': [ - { - 'component': 'VTextField', - 'props': { - 'model': 'cron', - 'label': '执行周期', - 'placeholder': '5位cron表达式,留空自动' - } - } - ] - }, - { - 'component': 'VCol', - 'props': { - 'cols': 12, - 'md': 6 - }, - 'content': [ - { - 'component': 'VTextField', - 'props': { - 'model': 'exclude_path', - 'label': '排除路径' - } - } - ] - } - ] - }, - - ] - } - ], { - "enabled": False, - "notify": True, - "del_source": False, - "cron": "*/30 * * * *", - "exclude_path": "", - } + { + 'component': 'VForm', + 'content': [ + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'enabled', + 'label': '启用插件', + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'notify', + 'label': '发送通知', + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'del_source', + 'label': '删除源文件', + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VSelect', + 'props': { + 'model': 'sync_type', + 'label': '同步方式', + 'items': [ + {'title': '日志', 'value': 'log'}, + {'title': 'Scripter X', 'value': 'plugin'} + ] + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'cron', + 'label': '执行周期', + 'placeholder': '5位cron表达式,留空自动' + } + } + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 4 + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'exclude_path', + 'label': '排除路径' + } + } + ] + } + ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + }, + 'content': [ + { + 'component': 'VAlert', + 'props': { + 'text': '同步方式分为日志同步和Scripter X。日志同步需要配置执行周期,默认30分钟执行一次。' + 'Scripter X方式需要emby安装并配置Scripter X插件,无需配置执行周期。' + } + } + ] + } + ] + } + ] + } + ], { + "enabled": False, + "notify": True, + "del_source": False, + "sync_type": "log", + "cron": "*/30 * * * *", + "exclude_path": "", + } def get_page(self) -> List[dict]: """ @@ -341,9 +387,165 @@ class MediaSyncDel(_PluginBase): } ] - def sync_del(self): + @eventmanager.register(EventType.WebhookMessage) + def sync_del_by_plugin(self, event): """ emby删除媒体库同步删除历史记录 + Scripter X插件 + """ + if not self._enabled: + return + event_data = event.event_data + event_type = event_data.get("event_type") + if not event_type or str(event_type) != 'media_del': + return + + # 是否虚拟标识 + item_isvirtual = event_data.get("item_isvirtual") + if not item_isvirtual: + logger.error("item_isvirtual参数未配置,为防止误删除,暂停插件运行") + self.update_config({ + "enable": False, + "del_source": self._del_source, + "exclude_path": self._exclude_path, + "notify": self._notify, + "cron": self._cron, + "sync_type": self._sync_type, + }) + return + + # 如果是虚拟item,则直接return,不进行删除 + if item_isvirtual == 'True': + return + + # 读取历史记录 + history = self.get_data('history') or [] + + # 媒体类型 + media_type = event_data.get("media_type") + # 媒体名称 + media_name = event_data.get("media_name") + # 媒体路径 + media_path = event_data.get("media_path") + # tmdb_id + tmdb_id = event_data.get("tmdb_id") + # 季数 + season_num = event_data.get("season_num") + if season_num and str(season_num).isdigit() and int(season_num) < 10: + season_num = f'S0{season_num}' + else: + season_num = f'S{season_num}' + # 集数 + episode_num = event_data.get("episode_num") + if episode_num and str(episode_num).isdigit() and int(episode_num) < 10: + episode_num = f'E0{episode_num}' + else: + episode_num = f'E{episode_num}' + + if not media_type: + logger.error(f"{media_name} 同步删除失败,未获取到媒体类型") + return + if not tmdb_id or not str(tmdb_id).isdigit(): + logger.error(f"{media_name} 同步删除失败,未获取到TMDB ID") + return + + if self._exclude_path and media_path and any( + os.path.abspath(media_path).startswith(os.path.abspath(path)) for path in + self._exclude_path.split(",")): + logger.info(f"媒体路径 {media_path} 已被排除,暂不处理") + return + + # 删除电影 + if media_type == "Movie": + msg = f'电影 {media_name} {tmdb_id}' + transfer_history: List[TransferHistory] = self._transferhis.get_by(tmdbid=tmdb_id) + # 删除电视剧 + elif media_type == "Series": + msg = f'剧集 {media_name} {tmdb_id}' + transfer_history: List[TransferHistory] = self._transferhis.get_by(tmdbid=tmdb_id) + # 删除季 S02 + elif media_type == "Season": + if not season_num or not str(season_num).isdigit(): + logger.error(f"{media_name} 季同步删除失败,未获取到具体季") + return + msg = f'剧集 {media_name} S{season_num} {tmdb_id}' + transfer_history: List[TransferHistory] = self._transferhis.get_by(tmdbid=tmdb_id, + season=season_num) + # 删除剧集S02E02 + elif media_type == "Episode": + if not season_num or not str(season_num).isdigit() or not episode_num or not str(episode_num).isdigit(): + logger.error(f"{media_name} 集同步删除失败,未获取到具体集") + return + msg = f'剧集 {media_name} S{season_num}E{episode_num} {tmdb_id}' + transfer_history: List[TransferHistory] = self._transferhis.get_by(tmdbid=tmdb_id, + season=season_num, + episode=episode_num) + else: + return + + logger.info(f"正在同步删除{msg}") + + if not transfer_history: + logger.warn(f"{media_type} {media_name} 未获取到可删除数据") + return + + # 开始删除 + del_cnt = 0 + image = 'https://emby.media/notificationicon.png' + year = None + for transferhis in transfer_history: + image = transferhis.image + year = transferhis.year + if media_type == "Episode" or media_type == "Movie": + # 如果有剧集或者电影有多个版本的话,需要根据名称筛选下要删除的版本 + if os.path.basename(transferhis.dest) != os.path.basename(media_path): + continue + self._transferhis.delete(transferhis.id) + del_cnt += 1 + # 删除种子任务 + if self._del_source and transferhis.download_hash: + self.chain.remove_torrents(transferhis.download_hash) + + logger.info(f"同步删除 {msg} 完成!") + + # 发送消息 + if self._notify: + if media_type == "Episode": + # 根据tmdbid获取图片 + image = self._episode().images(tv_id=tmdb_id, + season_id=season_num, + episode_id=episode_num, + orginal=True) + # 发送通知 + self.post_message( + mtype=NotificationType.MediaServer, + title="媒体库同步删除任务完成", + image=image, + text=f"{msg}\n" + f"数量 {del_cnt}\n" + f"时间 {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))}" + ) + + history.append({ + "type": "电影" if media_type == "Movie" else "电视剧", + "title": media_name, + "year": year, + "path": media_path, + "season": season_num, + "episode": episode_num, + "image": image, + "del_time": str(datetime.datetime.now()) + }) + + # 保存历史 + self.save_data("history", history) + + self.save_data("last_time", datetime.datetime.now()) + + def sync_del_by_log(self): + """ + emby删除媒体库同步删除历史记录 + 日志方式 """ # 读取历史记录 history = self.get_data('history') or [] @@ -445,7 +647,6 @@ class MediaSyncDel(_PluginBase): # 发送消息 if self._notify: self.post_message( - mtype=NotificationType.MediaServer, title="媒体库同步删除任务完成", text=f"{msg}\n" f"数量 {len(transfer_history)}\n" From faabee47530bb50acec6562737fdc2eef3801909 Mon Sep 17 00:00:00 2001 From: thsrite Date: Tue, 8 Aug 2023 19:55:15 +0800 Subject: [PATCH 2/5] fix --- app/plugins/mediasyncdel/__init__.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/app/plugins/mediasyncdel/__init__.py b/app/plugins/mediasyncdel/__init__.py index 262345af..4de05b3c 100644 --- a/app/plugins/mediasyncdel/__init__.py +++ b/app/plugins/mediasyncdel/__init__.py @@ -69,27 +69,21 @@ class MediaSyncDel(_PluginBase): self._del_source = config.get("del_source") self._exclude_path = config.get("exclude_path") - if self._enabled: + if self._enabled and str(self._sync_type) == "log": self._scheduler = BackgroundScheduler(timezone=settings.TZ) if self._cron: try: - if str(self._sync_type) == "log": - self._scheduler.add_job(func=self.sync_del_by_log, - trigger=CronTrigger.from_crontab(self._cron), - name="媒体库同步删除") - if str(self._sync_type) == "plugin": - self._scheduler.add_job(func=self.sync_del_by_plugin, - trigger=CronTrigger.from_crontab(self._cron), - name="媒体库同步删除") + + self._scheduler.add_job(func=self.sync_del_by_log, + trigger=CronTrigger.from_crontab(self._cron), + name="媒体库同步删除") except Exception as err: logger.error(f"定时任务配置错误:{err}") # 推送实时消息 self.systemmessage.put(f"执行周期配置错误:{err}") else: - if str(self._sync_type) == "log": - self._scheduler.add_job(self.sync_del_by_log, "interval", minutes=30, name="媒体库同步删除") - if str(self._sync_type) == "plugin": - self._scheduler.add_job(self.sync_del_by_plugin, "interval", minutes=30, name="媒体库同步删除") + self._scheduler.add_job(self.sync_del_by_log, "interval", minutes=30, name="媒体库同步删除") + # 启动任务 if self._scheduler.get_jobs(): self._scheduler.print_jobs() From 7d4ec4d8a043557e17bd9d8885463dda2519c1ac Mon Sep 17 00:00:00 2001 From: thsrite Date: Tue, 8 Aug 2023 19:55:45 +0800 Subject: [PATCH 3/5] fix --- app/plugins/mediasyncdel/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/plugins/mediasyncdel/__init__.py b/app/plugins/mediasyncdel/__init__.py index 4de05b3c..b060d56b 100644 --- a/app/plugins/mediasyncdel/__init__.py +++ b/app/plugins/mediasyncdel/__init__.py @@ -73,7 +73,6 @@ class MediaSyncDel(_PluginBase): self._scheduler = BackgroundScheduler(timezone=settings.TZ) if self._cron: try: - self._scheduler.add_job(func=self.sync_del_by_log, trigger=CronTrigger.from_crontab(self._cron), name="媒体库同步删除") From 270c11bb8b93e55dae3eb673c283d2ac3c8905a4 Mon Sep 17 00:00:00 2001 From: thsrite Date: Tue, 8 Aug 2023 20:25:30 +0800 Subject: [PATCH 4/5] =?UTF-8?q?fix=20=E5=88=A0=E7=A7=8D=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=BA=90=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/plugins/mediasyncdel/__init__.py | 74 ++++++++++++- app/utils/path_utils.py | 155 +++++++++++++++++++++++++++ 2 files changed, 224 insertions(+), 5 deletions(-) create mode 100644 app/utils/path_utils.py diff --git a/app/plugins/mediasyncdel/__init__.py b/app/plugins/mediasyncdel/__init__.py index b060d56b..8fb3af96 100644 --- a/app/plugins/mediasyncdel/__init__.py +++ b/app/plugins/mediasyncdel/__init__.py @@ -2,6 +2,7 @@ import datetime import json import os import re +import shutil import time from typing import List, Tuple, Dict, Any, Optional @@ -18,6 +19,7 @@ from app.modules.jellyfin import Jellyfin from app.modules.themoviedb.tmdbv3api import Episode from app.plugins import _PluginBase from app.schemas.types import NotificationType, EventType +from app.utils.path_utils import PathUtils class MediaSyncDel(_PluginBase): @@ -496,8 +498,21 @@ class MediaSyncDel(_PluginBase): self._transferhis.delete(transferhis.id) del_cnt += 1 # 删除种子任务 - if self._del_source and transferhis.download_hash: - self.chain.remove_torrents(transferhis.download_hash) + if self._del_source: + del_source = False + if transferhis.download_hash: + try: + self.chain.remove_torrents(transferhis.download_hash) + except Exception as e: + logger.error("删除种子失败,尝试删除源文件:%s" % str(e)) + del_source = True + + # 直接删除源文件 + if del_source: + source_name = os.path.basename(transferhis.src) + source_path = str(transferhis.src).replace(source_name, "") + self.delete_media_file(filedir=source_path, + filename=source_name) logger.info(f"同步删除 {msg} 完成!") @@ -632,8 +647,21 @@ class MediaSyncDel(_PluginBase): image = transferhis.image self._transferhis.delete(transferhis.id) # 删除种子任务 - if self._del_source and transferhis.download_hash: - self.chain.remove_torrents(transferhis.download_hash) + if self._del_source: + del_source = False + if transferhis.download_hash: + try: + self.chain.remove_torrents(transferhis.download_hash) + except Exception as e: + logger.error("删除种子失败,尝试删除源文件:%s" % str(e)) + del_source = True + + # 直接删除源文件 + if del_source: + source_name = os.path.basename(transferhis.src) + source_path = str(transferhis.src).replace(source_name, "") + self.delete_media_file(filedir=source_path, + filename=source_name) logger.info(f"同步删除 {msg} 完成!") @@ -807,6 +835,42 @@ class MediaSyncDel(_PluginBase): return del_medias + @staticmethod + def delete_media_file(filedir, filename): + """ + 删除媒体文件,空目录也会被删除 + """ + filedir = os.path.normpath(filedir).replace("\\", "/") + file = os.path.join(filedir, filename) + try: + if not os.path.exists(file): + return False, f"{file} 不存在" + os.remove(file) + nfoname = f"{os.path.splitext(filename)[0]}.nfo" + nfofile = os.path.join(filedir, nfoname) + if os.path.exists(nfofile): + os.remove(nfofile) + # 检查空目录并删除 + if re.findall(r"^S\d{2}|^Season", os.path.basename(filedir), re.I): + # 当前是季文件夹,判断并删除 + seaon_dir = filedir + if seaon_dir.count('/') > 1 and not PathUtils.get_dir_files(seaon_dir, exts=settings.RMT_MEDIAEXT): + shutil.rmtree(seaon_dir) + # 媒体文件夹 + media_dir = os.path.dirname(seaon_dir) + else: + media_dir = filedir + # 检查并删除媒体文件夹,非根目录且目录大于二级,且没有媒体文件时才会删除 + if media_dir != '/' \ + and media_dir.count('/') > 1 \ + and not re.search(r'[a-zA-Z]:/$', media_dir) \ + and not PathUtils.get_dir_files(media_dir, exts=settings.RMT_MEDIAEXT): + shutil.rmtree(media_dir) + return True, f"{file} 删除成功" + except Exception as e: + logger.error("删除源文件失败:%s" % str(e)) + return True, f"{file} 删除失败" + def get_state(self): return self._enabled @@ -833,7 +897,7 @@ class MediaSyncDel(_PluginBase): self.post_message(channel=event.event_data.get("channel"), title="开始媒体库同步删除 ...", userid=event.event_data.get("user")) - self.sync_del() + self.sync_del_by_log() if event: self.post_message(channel=event.event_data.get("channel"), diff --git a/app/utils/path_utils.py b/app/utils/path_utils.py new file mode 100644 index 00000000..5f02faef --- /dev/null +++ b/app/utils/path_utils.py @@ -0,0 +1,155 @@ +import os + + +class PathUtils: + + @staticmethod + def get_dir_files(in_path, exts="", filesize=0, episode_format=None): + """ + 获得目录下的媒体文件列表List ,按后缀、大小、格式过滤 + """ + if not in_path: + return [] + if not os.path.exists(in_path): + return [] + ret_list = [] + if os.path.isdir(in_path): + for root, dirs, files in os.walk(in_path): + for file in files: + cur_path = os.path.join(root, file) + # 检查路径是否合法 + if PathUtils.is_invalid_path(cur_path): + continue + # 检查格式匹配 + if episode_format and not episode_format.match(file): + continue + # 检查后缀 + if exts and os.path.splitext(file)[-1].lower() not in exts: + continue + # 检查文件大小 + if filesize and os.path.getsize(cur_path) < filesize: + continue + # 命中 + if cur_path not in ret_list: + ret_list.append(cur_path) + else: + # 检查路径是否合法 + if PathUtils.is_invalid_path(in_path): + return [] + # 检查后缀 + if exts and os.path.splitext(in_path)[-1].lower() not in exts: + return [] + # 检查格式 + if episode_format and not episode_format.match(os.path.basename(in_path)): + return [] + # 检查文件大小 + if filesize and os.path.getsize(in_path) < filesize: + return [] + ret_list.append(in_path) + return ret_list + + @staticmethod + def get_dir_level1_files(in_path, exts=""): + """ + 查询目录下的文件(只查询一级) + """ + ret_list = [] + if not os.path.exists(in_path): + return [] + for file in os.listdir(in_path): + path = os.path.join(in_path, file) + if os.path.isfile(path): + if not exts or os.path.splitext(file)[-1].lower() in exts: + ret_list.append(path) + return ret_list + + @staticmethod + def get_dir_level1_medias(in_path, exts=""): + """ + 根据后缀,返回目录下所有的文件及文件夹列表(只查询一级) + """ + ret_list = [] + if not os.path.exists(in_path): + return [] + if os.path.isdir(in_path): + for file in os.listdir(in_path): + path = os.path.join(in_path, file) + if os.path.isfile(path): + if not exts or os.path.splitext(file)[-1].lower() in exts: + ret_list.append(path) + else: + ret_list.append(path) + else: + ret_list.append(in_path) + return ret_list + + @staticmethod + def is_invalid_path(path): + """ + 判断是否不能处理的路径 + """ + if not path: + return True + if path.find('/@Recycle/') != -1 or path.find('/#recycle/') != -1 or path.find('/.') != -1 or path.find( + '/@eaDir') != -1: + return True + return False + + @staticmethod + def is_path_in_path(path1, path2): + """ + 判断两个路径是否包含关系 path1 in path2 + """ + if not path1 or not path2: + return False + path1 = os.path.normpath(path1).replace("\\", "/") + path2 = os.path.normpath(path2).replace("\\", "/") + if path1 == path2: + return True + path = os.path.dirname(path2) + while True: + if path == path1: + return True + path = os.path.dirname(path) + if path == os.path.dirname(path): + break + return False + + @staticmethod + def get_bluray_dir(path): + """ + 判断是否蓝光原盘目录,是则返回原盘的根目录,否则返回空 + """ + if not path or not os.path.exists(path): + return None + if os.path.isdir(path): + if os.path.exists(os.path.join(path, "BDMV", "index.bdmv")): + return path + elif os.path.normpath(path).endswith("BDMV") \ + and os.path.exists(os.path.join(path, "index.bdmv")): + return os.path.dirname(path) + elif os.path.normpath(path).endswith("STREAM") \ + and os.path.exists(os.path.join(os.path.dirname(path), "index.bdmv")): + return PathUtils.get_parent_paths(path, 2) + else: + # 电视剧原盘下会存在多个目录形如:Spider Man 2021/DIsc1, Spider Man 2021/Disc2 + for level1 in PathUtils.get_dir_level1_medias(path): + if os.path.exists(os.path.join(level1, "BDMV", "index.bdmv")): + return path + return None + else: + if str(os.path.splitext(path)[-1]).lower() in [".m2ts", ".ts"] \ + and os.path.normpath(os.path.dirname(path)).endswith("STREAM") \ + and os.path.exists(os.path.join(PathUtils.get_parent_paths(path, 2), "index.bdmv")): + return PathUtils.get_parent_paths(path, 3) + else: + return None + + @staticmethod + def get_parent_paths(path, level: int = 1): + """ + 获取父目录路径,level为向上查找的层数 + """ + for lv in range(0, level): + path = os.path.dirname(path) + return path From f697e16447fdfa66dfeec75f500bea315665703e Mon Sep 17 00:00:00 2001 From: thsrite Date: Tue, 8 Aug 2023 20:27:05 +0800 Subject: [PATCH 5/5] fix --- app/plugins/mediasyncdel/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/plugins/mediasyncdel/__init__.py b/app/plugins/mediasyncdel/__init__.py index 8fb3af96..bd5838f4 100644 --- a/app/plugins/mediasyncdel/__init__.py +++ b/app/plugins/mediasyncdel/__init__.py @@ -668,6 +668,7 @@ class MediaSyncDel(_PluginBase): # 发送消息 if self._notify: self.post_message( + mtype=NotificationType.MediaServer, title="媒体库同步删除任务完成", text=f"{msg}\n" f"数量 {len(transfer_history)}\n"