mirror of
https://github.com/thsrite/MoviePilot-Plugins.git
synced 2026-03-27 10:05:57 +00:00
feat 云盘Strm生成(增量版)
This commit is contained in:
@@ -11,7 +11,8 @@ MoviePilot三方插件市场:https://github.com/thsrite/MoviePilot-Plugins/
|
||||
|
||||
- 站点数据统计 1.4 (无未读消息版本)(废弃)
|
||||
- 站点未读消息 1.9 (依赖于[站点数据统计]插件)
|
||||
- [云盘Strm生成 3.8](docs%2FCloudStrm.md)
|
||||
- [云盘Strm生成 4.0](docs%2FCloudStrm.md)
|
||||
- [云盘Strm生成(增量版) 1.0](docs%2FCloudStrmIncrement.md)
|
||||
- [Strm文件模式转换 1.0](docs%2FStrmConvert.md)
|
||||
- 清理订阅缓存 1.0
|
||||
- 添加种子下载 1.0
|
||||
|
||||
@@ -4,39 +4,22 @@
|
||||
|
||||
目录监控格式:
|
||||
|
||||
- 1.增量目录#监控目录#目的目录#媒体服务器内源文件路径
|
||||
- 2.增量目录#监控目录#目的目录#cd2#cd2挂载本地跟路径#cd2服务地址
|
||||
- 3.增量目录#监控目录#目的目录#alist#alist挂载本地跟路径#alist服务地址
|
||||
- 1.监控目录#目的目录#媒体服务器内源文件路径
|
||||
- 2.监控目录#目的目录#cd2#cd2挂载本地跟路径#cd2服务地址
|
||||
- 3.监控目录#目的目录#alist#alist挂载本地跟路径#alist服务地址
|
||||
|
||||
路径:
|
||||
|
||||
- 增量目录:转存到云盘的路径,插件只会扫描该路径下的文件,移动到监控路径,生成目的路径的strm文件
|
||||
- 监控目录:源文件目录即云盘挂载到MoviePilot中的路径
|
||||
- 目的路径:MoviePilot中strm生成路径
|
||||
- 媒体服务器内源文件路径:源文件目录即云盘挂载到媒体服务器的路径
|
||||
|
||||
示例:
|
||||
|
||||
- 增量目录:/increment`/tvshow/爸爸去哪儿/Season 5/14.特别版.mp4`
|
||||
|
||||
- MoviePilot上云盘源文件路径 /mount/cloud/aliyun/emby`/tvshow/爸爸去哪儿/Season 5/14.特别版.mp4`
|
||||
|
||||
- MoviePilot上strm生成路径 /mnt/link/aliyun`/tvshow/爸爸去哪儿/Season 5/14.特别版.strm`
|
||||
|
||||
- 媒体服务器内源文件路径 /mount/cloud/aliyun/emby`/tvshow/爸爸去哪儿/Season 5/14.特别版.mp4`
|
||||
|
||||
- 监控配置为:/increment#/mount/cloud/aliyun/emby#/mnt/link/aliyun#/mount/cloud/aliyun/emby
|
||||
|
||||
|
||||
保留路径:
|
||||
|
||||
扫描到增量目录的文件,会移动到监控目录,并生成目的路径的strm文件,删除空的增量目录,如果想保留某些父目录,可以将它们添加到保留路径中。
|
||||
|
||||
例如:
|
||||
|
||||
/increment/series/庆余年/Season 1/1.第一集.mp4
|
||||
|
||||
保留路径为series
|
||||
|
||||
则文件移动到目的路径名后,会删除庆余年/Season 1,父路径/increment/series保留
|
||||
|
||||
- 监控配置为:/mount/cloud/aliyun/emby#/mnt/link/aliyun#/mount/cloud/aliyun/emby
|
||||
|
||||
42
docs/CloudStrmIncrement.md
Normal file
42
docs/CloudStrmIncrement.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 云盘strm生成(增量版)
|
||||
|
||||
### 使用说明
|
||||
|
||||
目录监控格式:
|
||||
|
||||
- 1.增量目录#监控目录#目的目录#媒体服务器内源文件路径
|
||||
- 2.增量目录#监控目录#目的目录#cd2#cd2挂载本地跟路径#cd2服务地址
|
||||
- 3.增量目录#监控目录#目的目录#alist#alist挂载本地跟路径#alist服务地址
|
||||
|
||||
路径:
|
||||
|
||||
- 增量目录:转存到云盘的路径,插件只会扫描该路径下的文件,移动到监控路径,生成目的路径的strm文件
|
||||
- 监控目录:源文件目录即云盘挂载到MoviePilot中的路径
|
||||
- 目的路径:MoviePilot中strm生成路径
|
||||
- 媒体服务器内源文件路径:源文件目录即云盘挂载到媒体服务器的路径
|
||||
|
||||
示例:
|
||||
|
||||
- 增量目录:/increment`/tvshow/爸爸去哪儿/Season 5/14.特别版.mp4`
|
||||
|
||||
- MoviePilot上云盘源文件路径 /mount/cloud/aliyun/emby`/tvshow/爸爸去哪儿/Season 5/14.特别版.mp4`
|
||||
|
||||
- MoviePilot上strm生成路径 /mnt/link/aliyun`/tvshow/爸爸去哪儿/Season 5/14.特别版.strm`
|
||||
|
||||
- 媒体服务器内源文件路径 /mount/cloud/aliyun/emby`/tvshow/爸爸去哪儿/Season 5/14.特别版.mp4`
|
||||
|
||||
- 监控配置为:/increment#/mount/cloud/aliyun/emby#/mnt/link/aliyun#/mount/cloud/aliyun/emby
|
||||
|
||||
|
||||
保留路径:
|
||||
|
||||
扫描到增量目录的文件,会移动到监控目录,并生成目的路径的strm文件,删除空的增量目录,如果想保留某些父目录,可以将它们添加到保留路径中。
|
||||
|
||||
例如:
|
||||
|
||||
/increment/series/庆余年/Season 1/1.第一集.mp4
|
||||
|
||||
保留路径为series
|
||||
|
||||
则文件移动到目的路径名后,会删除庆余年/Season 1,父路径/increment/series保留
|
||||
|
||||
15
package.json
15
package.json
@@ -3,11 +3,12 @@
|
||||
"name": "云盘Strm生成",
|
||||
"description": "监控文件创建,生成Strm文件。",
|
||||
"labels": "云盘",
|
||||
"version": "3.8",
|
||||
"version": "4.0",
|
||||
"icon": "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/create.png",
|
||||
"author": "thsrite",
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v4.0": "回归老版本",
|
||||
"v3.8": "支持增量路径、支持自定义媒体类型(注:本次更新需修改配置使用)",
|
||||
"v3.7": "api模式支持启用https",
|
||||
"v3.6": "支持重建索引周期运行",
|
||||
@@ -16,6 +17,18 @@
|
||||
"v3.0": "实现改为定时扫描"
|
||||
}
|
||||
},
|
||||
"CloudStrmIncrement": {
|
||||
"name": "云盘Strm生成(增量版)",
|
||||
"description": "监控文件创建,生成Strm文件(增量版)。",
|
||||
"labels": "云盘",
|
||||
"version": "1.0",
|
||||
"icon": "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/create.png",
|
||||
"author": "thsrite",
|
||||
"level": 1,
|
||||
"history": {
|
||||
"v1.0": "增量监控"
|
||||
}
|
||||
},
|
||||
"StrmConvert": {
|
||||
"name": "Strm文件模式转换",
|
||||
"description": "Strm文件内容转为本地路径或者cd2/alist API路径。",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import urllib.parse
|
||||
@@ -15,7 +16,6 @@ from apscheduler.triggers.cron import CronTrigger
|
||||
from app.log import logger
|
||||
from app.plugins import _PluginBase
|
||||
from app.core.config import settings
|
||||
from app.utils.system import SystemUtils
|
||||
|
||||
|
||||
class CloudStrm(_PluginBase):
|
||||
@@ -26,7 +26,7 @@ class CloudStrm(_PluginBase):
|
||||
# 插件图标
|
||||
plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/create.png"
|
||||
# 插件版本
|
||||
plugin_version = "3.8"
|
||||
plugin_version = "4.0"
|
||||
# 插件作者
|
||||
plugin_author = "thsrite"
|
||||
# 作者主页
|
||||
@@ -41,21 +41,21 @@ class CloudStrm(_PluginBase):
|
||||
# 私有属性
|
||||
_enabled = False
|
||||
_cron = None
|
||||
_rebuild_cron = None
|
||||
_monitor_confs = None
|
||||
_onlyonce = False
|
||||
_copy_files = False
|
||||
_rebuild = False
|
||||
_https = False
|
||||
_no_del_dirs = None
|
||||
_rmt_mediaext = ".mp4, .mkv, .ts, .iso,.rmvb, .avi, .mov, .mpeg,.mpg, .wmv, .3gp, .asf, .m4v, .flv, .m2ts, .strm,.tp, .f4v"
|
||||
_observer = []
|
||||
__cloud_files_json = "cloud_files.json"
|
||||
|
||||
# 公开属性
|
||||
_increment_dir = {}
|
||||
_dirconf = {}
|
||||
_libraryconf = {}
|
||||
_cloudtypeconf = {}
|
||||
_cloudurlconf = {}
|
||||
_cloudpathconf = {}
|
||||
__cloud_files = []
|
||||
|
||||
# 定时器
|
||||
_scheduler: Optional[BackgroundScheduler] = None
|
||||
@@ -67,18 +67,17 @@ class CloudStrm(_PluginBase):
|
||||
self._cloudtypeconf = {}
|
||||
self._cloudurlconf = {}
|
||||
self._cloudpathconf = {}
|
||||
self._increment_dir = {}
|
||||
self.__cloud_files_json = os.path.join(self.get_data_path(), self.__cloud_files_json)
|
||||
|
||||
if config:
|
||||
self._enabled = config.get("enabled")
|
||||
self._cron = config.get("cron")
|
||||
self._rebuild_cron = config.get("rebuild_cron")
|
||||
self._onlyonce = config.get("onlyonce")
|
||||
self._rebuild = config.get("rebuild")
|
||||
self._https = config.get("https")
|
||||
self._copy_files = config.get("copy_files")
|
||||
self._monitor_confs = config.get("monitor_confs")
|
||||
self._no_del_dirs = config.get("no_del_dirs")
|
||||
self._rmt_mediaext = config.get(
|
||||
"rmt_mediaext") or ".mp4, .mkv, .ts, .iso,.rmvb, .avi, .mov, .mpeg,.mpg, .wmv, .3gp, .asf, .m4v, .flv, .m2ts, .strm,.tp, .f4v"
|
||||
|
||||
# 停止现有任务
|
||||
self.stop_service()
|
||||
@@ -98,33 +97,26 @@ class CloudStrm(_PluginBase):
|
||||
# 注释
|
||||
if str(monitor_conf).startswith("#"):
|
||||
continue
|
||||
|
||||
if str(monitor_conf).count("#") == 3:
|
||||
increment_dir = str(monitor_conf).split("#")[0]
|
||||
source_dir = str(monitor_conf).split("#")[1]
|
||||
target_dir = str(monitor_conf).split("#")[2]
|
||||
library_dir = str(monitor_conf).split("#")[3]
|
||||
if str(monitor_conf).count("#") == 2:
|
||||
source_dir = str(monitor_conf).split("#")[0]
|
||||
target_dir = str(monitor_conf).split("#")[1]
|
||||
library_dir = str(monitor_conf).split("#")[2]
|
||||
self._libraryconf[source_dir] = library_dir
|
||||
elif str(monitor_conf).count("#") == 5:
|
||||
increment_dir = str(monitor_conf).split("#")[0]
|
||||
source_dir = str(monitor_conf).split("#")[1]
|
||||
target_dir = str(monitor_conf).split("#")[2]
|
||||
cloud_type = str(monitor_conf).split("#")[3]
|
||||
cloud_path = str(monitor_conf).split("#")[4]
|
||||
cloud_url = str(monitor_conf).split("#")[5]
|
||||
elif str(monitor_conf).count("#") == 4:
|
||||
source_dir = str(monitor_conf).split("#")[0]
|
||||
target_dir = str(monitor_conf).split("#")[1]
|
||||
cloud_type = str(monitor_conf).split("#")[2]
|
||||
cloud_path = str(monitor_conf).split("#")[3]
|
||||
cloud_url = str(monitor_conf).split("#")[4]
|
||||
self._cloudtypeconf[source_dir] = cloud_type
|
||||
self._cloudpathconf[source_dir] = cloud_path
|
||||
self._cloudurlconf[source_dir] = cloud_url
|
||||
else:
|
||||
logger.error(f"{monitor_conf} 格式错误")
|
||||
continue
|
||||
|
||||
# 存储目录监控配置
|
||||
self._dirconf[source_dir] = target_dir
|
||||
|
||||
# 增量配置
|
||||
self._increment_dir[increment_dir] = source_dir
|
||||
|
||||
# 检查媒体库目录是不是下载目录的子目录
|
||||
try:
|
||||
if target_dir and Path(target_dir).is_relative_to(Path(source_dir)):
|
||||
@@ -137,10 +129,10 @@ class CloudStrm(_PluginBase):
|
||||
|
||||
# 运行一次定时服务
|
||||
if self._onlyonce:
|
||||
logger.info("云盘增量监控执行服务启动,立即运行一次")
|
||||
logger.info("云盘监控全量执行服务启动,立即运行一次")
|
||||
self._scheduler.add_job(func=self.scan, trigger='date',
|
||||
run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3),
|
||||
name="云盘增量监控")
|
||||
name="云盘监控全量执行")
|
||||
# 关闭一次性开关
|
||||
self._onlyonce = False
|
||||
# 保存配置
|
||||
@@ -151,7 +143,18 @@ class CloudStrm(_PluginBase):
|
||||
try:
|
||||
self._scheduler.add_job(func=self.scan,
|
||||
trigger=CronTrigger.from_crontab(self._cron),
|
||||
name="云盘增量监控")
|
||||
name="云盘监控生成")
|
||||
except Exception as err:
|
||||
logger.error(f"定时任务配置错误:{err}")
|
||||
# 推送实时消息
|
||||
self.systemmessage.put(f"执行周期配置错误:{err}")
|
||||
|
||||
# 周期运行
|
||||
if self._rebuild_cron:
|
||||
try:
|
||||
self._scheduler.add_job(func=self.__init_cloud_files_json,
|
||||
trigger=CronTrigger.from_crontab(self._rebuild_cron),
|
||||
name="云盘监控重建索引")
|
||||
except Exception as err:
|
||||
logger.error(f"定时任务配置错误:{err}")
|
||||
# 推送实时消息
|
||||
@@ -184,62 +187,68 @@ class CloudStrm(_PluginBase):
|
||||
userid=event.event_data.get("user"))
|
||||
|
||||
logger.info("云盘strm生成任务开始")
|
||||
for increment_dir in self._increment_dir.keys():
|
||||
logger.info(f"正在扫描增量目录 {increment_dir}")
|
||||
for root, dirs, files in os.walk(increment_dir):
|
||||
# 如果遇到名为'extrafanart'的文件夹,则跳过处理该文件夹,继续处理其他文件夹
|
||||
if "extrafanart" in dirs:
|
||||
dirs.remove("extrafanart")
|
||||
# 首次扫描或者重建索引
|
||||
__init_flag = False
|
||||
if self._rebuild or not Path(self.__cloud_files_json).exists():
|
||||
logger.info("正在重建索引或初始化运行")
|
||||
self.__init_cloud_files_json()
|
||||
self._rebuild = False
|
||||
self.__update_config()
|
||||
__init_flag = True
|
||||
else:
|
||||
logger.info("尝试加载本地缓存")
|
||||
# 尝试加载本地
|
||||
with open(self.__cloud_files_json, 'r') as file:
|
||||
content = file.read()
|
||||
if content:
|
||||
self.__cloud_files = json.loads(content)
|
||||
|
||||
# 处理文件
|
||||
for file in files:
|
||||
increment_file = os.path.join(root, file)
|
||||
# 回收站及隐藏的文件不处理
|
||||
if (increment_file.find("/@Recycle") != -1
|
||||
or increment_file.find("/#recycle") != -1
|
||||
or increment_file.find("/.") != -1
|
||||
or increment_file.find("/@eaDir") != -1):
|
||||
logger.info(f"{increment_file} 是回收站或隐藏的文件,跳过处理")
|
||||
continue
|
||||
# 本地没加载到则重建索引
|
||||
if not self.__cloud_files:
|
||||
logger.error("尝试加载本地缓存,开始重建索引")
|
||||
self.__init_cloud_files_json()
|
||||
self._rebuild = False
|
||||
self.__update_config()
|
||||
__init_flag = True
|
||||
|
||||
# 不复制非媒体文件时直接过滤掉非媒体文件
|
||||
if not self._copy_files and Path(file).suffix not in [ext.strip() for ext in
|
||||
self._rmt_mediaext.split(",")]:
|
||||
continue
|
||||
# 不是首次索引,则重新扫描、判断是否有新文件
|
||||
if not __init_flag:
|
||||
__save_flag = False
|
||||
for source_dir in self._dirconf.keys():
|
||||
logger.info(f"正在处理监控文件 {source_dir}")
|
||||
for root, dirs, files in os.walk(source_dir):
|
||||
# 如果遇到名为'extrafanart'的文件夹,则跳过处理该文件夹,继续处理其他文件夹
|
||||
if "extrafanart" in dirs:
|
||||
dirs.remove("extrafanart")
|
||||
|
||||
logger.info(f"扫描到增量文件 {increment_file},正在开始处理")
|
||||
# 处理文件
|
||||
for file in files:
|
||||
source_file = os.path.join(root, file)
|
||||
# 回收站及隐藏的文件不处理
|
||||
if (source_file.find("/@Recycle") != -1
|
||||
or source_file.find("/#recycle") != -1
|
||||
or source_file.find("/.") != -1
|
||||
or source_file.find("/@eaDir") != -1):
|
||||
logger.info(f"{source_file} 是回收站或隐藏的文件,跳过处理")
|
||||
continue
|
||||
|
||||
# 移动到目标目录
|
||||
source_dir = self._increment_dir.get(increment_dir)
|
||||
# 移动后文件
|
||||
source_file = increment_file.replace(increment_dir, source_dir)
|
||||
# 不复制非媒体文件时直接过滤掉非媒体文件
|
||||
if not self._copy_files and Path(file).suffix not in settings.RMT_MEDIAEXT:
|
||||
continue
|
||||
|
||||
# 判断目标文件是否存在
|
||||
if not Path(source_file).parent.exists():
|
||||
Path(source_file).parent.mkdir(parents=True, exist_ok=True)
|
||||
if source_file not in self.__cloud_files:
|
||||
logger.info(f"扫描到新文件 {source_file},正在开始处理")
|
||||
# 云盘文件json新增
|
||||
self.__cloud_files.append(source_file)
|
||||
# 扫描云盘文件,判断是否有对应strm
|
||||
self.__strm(source_file)
|
||||
__save_flag = True
|
||||
else:
|
||||
logger.debug(f"{source_file} 已在缓存中!跳过处理")
|
||||
|
||||
shutil.move(increment_file, source_file, copy_function=shutil.copy2)
|
||||
logger.info(f"移动增量文件 {increment_file} 到 {source_file}")
|
||||
|
||||
# 扫描云盘文件,判断是否有对应strm
|
||||
self.__strm(source_file)
|
||||
logger.info(f"增量文件 {increment_file} 处理完成")
|
||||
|
||||
# 判断当前媒体父路径下是否有媒体文件,如有则无需遍历父级
|
||||
if not SystemUtils.exits_files(Path(increment_file).parent,
|
||||
[ext.strip() for ext in self._rmt_mediaext.split(",")]):
|
||||
# 判断父目录是否为空, 为空则删除
|
||||
for parent_path in Path(increment_file).parents:
|
||||
if parent_path.name in self._no_del_dirs:
|
||||
break
|
||||
if str(parent_path.name) == str(increment_dir):
|
||||
break
|
||||
if str(parent_path.parent) != str(Path(increment_file).root):
|
||||
# 父目录非根目录,才删除父目录
|
||||
if not SystemUtils.exits_files(parent_path, settings.RMT_MEDIAEXT):
|
||||
# 当前路径下没有媒体文件则删除
|
||||
shutil.rmtree(parent_path)
|
||||
logger.warn(f"增量目录 {parent_path} 已删除")
|
||||
# 重新保存json文件
|
||||
if __save_flag:
|
||||
self.__sava_json()
|
||||
|
||||
logger.info("云盘strm生成任务完成")
|
||||
if event:
|
||||
@@ -247,58 +256,53 @@ class CloudStrm(_PluginBase):
|
||||
title="云盘strm生成任务完成!",
|
||||
userid=event.event_data.get("user"))
|
||||
|
||||
# def move_file(self,
|
||||
# file_path: Path,
|
||||
# dest_path: Path,
|
||||
# is_check_disk_space: bool = True,
|
||||
# min_free_space: int = 300,
|
||||
# wait_time: int = 300,
|
||||
# check_paths: Optional[List[Path]] = None,
|
||||
# ) -> bool:
|
||||
# """
|
||||
# 移动文件,如果父文件夹为空,则删除空父文件夹
|
||||
# """
|
||||
# # 在目标路径存在时,会尝试覆盖它
|
||||
# if not file_path.exists():
|
||||
# logger.debug(f"move文件不存在,跳过处理: {file_path}")
|
||||
#
|
||||
# if is_check_disk_space:
|
||||
# if not check_paths:
|
||||
# check_paths = [dest_path.parent]
|
||||
# check_paths.append(data_path)
|
||||
#
|
||||
# for check_path in check_paths:
|
||||
# while check_disk_space(check_path, min_free_space):
|
||||
# logger.warning(
|
||||
# f"文件 {check_path} 空间不足,等待 {wait_time}s再处理:"
|
||||
# f" {file_path}"
|
||||
# )
|
||||
# sleep(wait_time)
|
||||
#
|
||||
# logger.debug(f"移动文件: {file_path} -> {dest_path}")
|
||||
#
|
||||
# # # 改用copy2,避免移动文件夹时,程序中断导致文件丢失
|
||||
# # is_copyed = copy(file_path, dest_path)
|
||||
# # # 复制成功才继续执行
|
||||
# # if not is_copyed:
|
||||
# # logger.warning(f"移动文件失败: {file_path} -> {dest_path}")
|
||||
# # return False
|
||||
#
|
||||
# # # 复制后再删除文件
|
||||
# # logger.debug(f"已复制文件:{file_path}, 正在删除文件: {file_path}")
|
||||
#
|
||||
# try:
|
||||
# if not dest_path.parent.exists():
|
||||
# dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
#
|
||||
# cloud_str = "/mnt/cloud"
|
||||
# if str(file_path).startswith(cloud_str) and str(dest_path).startswith(
|
||||
# cloud_str
|
||||
# ):
|
||||
# # 如果是云盘路径,则使用重命名
|
||||
# file_path.rename(dest_path)
|
||||
# else:
|
||||
# shutil.move(file_path, dest_path, copy_function=shutil.copy2)
|
||||
def __init_cloud_files_json(self):
|
||||
"""
|
||||
初始化云盘文件json
|
||||
"""
|
||||
# init
|
||||
for source_dir in self._dirconf.keys():
|
||||
logger.info(f"正在处理监控文件 {source_dir}")
|
||||
for root, dirs, files in os.walk(source_dir):
|
||||
# 如果遇到名为'extrafanart'的文件夹,则跳过处理该文件夹,继续处理其他文件夹
|
||||
if "extrafanart" in dirs:
|
||||
dirs.remove("extrafanart")
|
||||
|
||||
# 处理文件
|
||||
for file in files:
|
||||
source_file = os.path.join(root, file)
|
||||
# 回收站及隐藏的文件不处理
|
||||
if (source_file.find("/@Recycle") != -1
|
||||
or source_file.find("/#recycle") != -1
|
||||
or source_file.find("/.") != -1
|
||||
or source_file.find("/@eaDir") != -1):
|
||||
logger.info(f"{source_file} 是回收站或隐藏的文件,跳过处理")
|
||||
continue
|
||||
|
||||
# 不复制非媒体文件时直接过滤掉非媒体文件
|
||||
if not self._copy_files and Path(file).suffix not in settings.RMT_MEDIAEXT:
|
||||
continue
|
||||
|
||||
logger.info(f"扫描到新文件 {source_file},正在开始处理")
|
||||
# 云盘文件json新增
|
||||
self.__cloud_files.append(source_file)
|
||||
# 扫描云盘文件,判断是否有对应strm
|
||||
self.__strm(source_file)
|
||||
|
||||
# 写入本地文件
|
||||
if self.__cloud_files:
|
||||
self.__sava_json()
|
||||
else:
|
||||
logger.warning(f"未获取到文件列表")
|
||||
|
||||
def __sava_json(self):
|
||||
"""
|
||||
保存json文件
|
||||
"""
|
||||
logger.info(f"开始写入本地文件 {self.__cloud_files_json}")
|
||||
file = open(self.__cloud_files_json, 'w')
|
||||
file.write(json.dumps(self.__cloud_files))
|
||||
file.close()
|
||||
|
||||
def __strm(self, source_file):
|
||||
"""
|
||||
@@ -339,7 +343,7 @@ class CloudStrm(_PluginBase):
|
||||
os.makedirs(Path(dest_file).parent)
|
||||
|
||||
# 视频文件创建.strm文件
|
||||
if Path(dest_file).suffix in [ext.strip() for ext in self._rmt_mediaext.split(",")]:
|
||||
if Path(dest_file).suffix in settings.RMT_MEDIAEXT:
|
||||
# 创建.strm文件
|
||||
self.__create_strm_file(scheme="https" if self._https else "http",
|
||||
dest_file=dest_file,
|
||||
@@ -425,12 +429,11 @@ class CloudStrm(_PluginBase):
|
||||
self.update_config({
|
||||
"enabled": self._enabled,
|
||||
"onlyonce": self._onlyonce,
|
||||
"rebuild": self._rebuild,
|
||||
"copy_files": self._copy_files,
|
||||
"https": self._https,
|
||||
"cron": self._cron,
|
||||
"monitor_confs": self._monitor_confs,
|
||||
"no_del_dirs": self._no_del_dirs,
|
||||
"rmt_mediaext": self._rmt_mediaext
|
||||
"monitor_confs": self._monitor_confs
|
||||
})
|
||||
|
||||
def get_state(self) -> bool:
|
||||
@@ -491,7 +494,7 @@ class CloudStrm(_PluginBase):
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 3
|
||||
'md': 4
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
@@ -507,7 +510,7 @@ class CloudStrm(_PluginBase):
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 3
|
||||
'md': 4
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
@@ -523,34 +526,18 @@ class CloudStrm(_PluginBase):
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 3
|
||||
'md': 4
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'copy_files',
|
||||
'label': '复制非媒体文件',
|
||||
'model': 'rebuild',
|
||||
'label': '重建索引',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 3
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'https',
|
||||
'label': '启用https',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -583,13 +570,13 @@ class CloudStrm(_PluginBase):
|
||||
{
|
||||
'component': 'VTextField',
|
||||
'props': {
|
||||
'model': 'no_del_dirs',
|
||||
'label': '保留路径',
|
||||
'placeholder': 'series、movies、downloads、others'
|
||||
'model': 'rebuild_cron',
|
||||
'label': '重建索引周期',
|
||||
'placeholder': '0 1 * * *'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -607,7 +594,7 @@ class CloudStrm(_PluginBase):
|
||||
'model': 'monitor_confs',
|
||||
'label': '监控目录',
|
||||
'rows': 5,
|
||||
'placeholder': '增量目录#监控目录#目的目录#媒体服务器内源文件路径'
|
||||
'placeholder': '监控方式#监控目录#目的目录#媒体服务器内源文件路径'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -620,20 +607,35 @@ class CloudStrm(_PluginBase):
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12
|
||||
'cols': 12,
|
||||
'md': 4
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VTextarea',
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'rmt_mediaext',
|
||||
'label': '视频格式',
|
||||
'rows': 2,
|
||||
'placeholder': ".mp4, .mkv, .ts, .iso,.rmvb, .avi, .mov, .mpeg,.mpg, .wmv, .3gp, .asf, .m4v, .flv, .m2ts, .strm,.tp, .f4v"
|
||||
'model': 'copy_files',
|
||||
'label': '复制非媒体文件',
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 4
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'https',
|
||||
'label': '启用https',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -651,9 +653,9 @@ class CloudStrm(_PluginBase):
|
||||
'type': 'info',
|
||||
'variant': 'tonal',
|
||||
'text': '目录监控格式:'
|
||||
'1.增量目录#监控目录#目的目录#媒体服务器内源文件路径;'
|
||||
'2.增量目录#监控目录#目的目录#cd2#cd2挂载本地跟路径#cd2服务地址;'
|
||||
'3.增量目录#监控目录#目的目录#alist#alist挂载本地跟路径#alist服务地址。'
|
||||
'1.监控目录#目的目录#媒体服务器内源文件路径;'
|
||||
'2.监控目录#目的目录#cd2#cd2挂载本地跟路径#cd2服务地址;'
|
||||
'3.监控目录#目的目录#alist#alist挂载本地跟路径#alist服务地址。'
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -693,39 +695,49 @@ class CloudStrm(_PluginBase):
|
||||
{
|
||||
'component': 'VAlert',
|
||||
'props': {
|
||||
'type': 'success',
|
||||
'variant': 'tonal'
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'span',
|
||||
'text': '配置教程请参考:'
|
||||
},
|
||||
{
|
||||
'component': 'a',
|
||||
'props': {
|
||||
'href': 'https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/docs/CloudStrm.md',
|
||||
'target': '_blank'
|
||||
},
|
||||
'text': 'https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/docs/CloudStrm.md'
|
||||
}
|
||||
]
|
||||
'type': 'info',
|
||||
'variant': 'tonal',
|
||||
'text': '立即运行一次:'
|
||||
'全量运行一次。'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VAlert',
|
||||
'props': {
|
||||
'type': 'info',
|
||||
'variant': 'tonal',
|
||||
'text': '配置说明:'
|
||||
'https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/docs/CloudStrm.md'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
], {
|
||||
"enabled": False,
|
||||
"cron": "",
|
||||
"rebuild_cron": "",
|
||||
"onlyonce": False,
|
||||
"rebuild": False,
|
||||
"copy_files": False,
|
||||
"https": False,
|
||||
"monitor_confs": "",
|
||||
"no_del_dirs": "",
|
||||
"rmt_mediaext": ".mp4, .mkv, .ts, .iso,.rmvb, .avi, .mov, .mpeg,.mpg, .wmv, .3gp, .asf, .m4v, .flv, .m2ts, .strm,.tp, .f4v"
|
||||
"monitor_confs": ""
|
||||
}
|
||||
|
||||
def get_page(self) -> List[dict]:
|
||||
|
||||
745
plugins/cloudstrmincrement/__init__.py
Normal file
745
plugins/cloudstrmincrement/__init__.py
Normal file
@@ -0,0 +1,745 @@
|
||||
import os
|
||||
import shutil
|
||||
import urllib.parse
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
import pytz
|
||||
from typing import Any, List, Dict, Tuple, Optional
|
||||
|
||||
from app.core.event import eventmanager, Event
|
||||
from app.schemas.types import EventType
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
|
||||
from app.log import logger
|
||||
from app.plugins import _PluginBase
|
||||
from app.core.config import settings
|
||||
from app.utils.system import SystemUtils
|
||||
|
||||
|
||||
class CloudStrmIncrement(_PluginBase):
|
||||
# 插件名称
|
||||
plugin_name = "云盘Strm生成(增量版)"
|
||||
# 插件描述
|
||||
plugin_desc = "定时扫描云盘文件,生成Strm文件(增量版)。"
|
||||
# 插件图标
|
||||
plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/create.png"
|
||||
# 插件版本
|
||||
plugin_version = "1.0"
|
||||
# 插件作者
|
||||
plugin_author = "thsrite"
|
||||
# 作者主页
|
||||
author_url = "https://github.com/thsrite"
|
||||
# 插件配置项ID前缀
|
||||
plugin_config_prefix = "cloudstrm_"
|
||||
# 加载顺序
|
||||
plugin_order = 26
|
||||
# 可使用的用户级别
|
||||
auth_level = 1
|
||||
|
||||
# 私有属性
|
||||
_enabled = False
|
||||
_cron = None
|
||||
_monitor_confs = None
|
||||
_onlyonce = False
|
||||
_copy_files = False
|
||||
_https = False
|
||||
_no_del_dirs = None
|
||||
_rmt_mediaext = ".mp4, .mkv, .ts, .iso,.rmvb, .avi, .mov, .mpeg,.mpg, .wmv, .3gp, .asf, .m4v, .flv, .m2ts, .strm,.tp, .f4v"
|
||||
_observer = []
|
||||
|
||||
# 公开属性
|
||||
_increment_dir = {}
|
||||
_dirconf = {}
|
||||
_libraryconf = {}
|
||||
_cloudtypeconf = {}
|
||||
_cloudurlconf = {}
|
||||
_cloudpathconf = {}
|
||||
|
||||
# 定时器
|
||||
_scheduler: Optional[BackgroundScheduler] = None
|
||||
|
||||
def init_plugin(self, config: dict = None):
|
||||
# 清空配置
|
||||
self._dirconf = {}
|
||||
self._libraryconf = {}
|
||||
self._cloudtypeconf = {}
|
||||
self._cloudurlconf = {}
|
||||
self._cloudpathconf = {}
|
||||
self._increment_dir = {}
|
||||
|
||||
if config:
|
||||
self._enabled = config.get("enabled")
|
||||
self._cron = config.get("cron")
|
||||
self._onlyonce = config.get("onlyonce")
|
||||
self._https = config.get("https")
|
||||
self._copy_files = config.get("copy_files")
|
||||
self._monitor_confs = config.get("monitor_confs")
|
||||
self._no_del_dirs = config.get("no_del_dirs")
|
||||
self._rmt_mediaext = config.get(
|
||||
"rmt_mediaext") or ".mp4, .mkv, .ts, .iso,.rmvb, .avi, .mov, .mpeg,.mpg, .wmv, .3gp, .asf, .m4v, .flv, .m2ts, .strm,.tp, .f4v"
|
||||
|
||||
# 停止现有任务
|
||||
self.stop_service()
|
||||
|
||||
if self._enabled or self._onlyonce:
|
||||
# 定时服务
|
||||
self._scheduler = BackgroundScheduler(timezone=settings.TZ)
|
||||
|
||||
# 读取目录配置
|
||||
monitor_confs = self._monitor_confs.split("\n")
|
||||
if not monitor_confs:
|
||||
return
|
||||
for monitor_conf in monitor_confs:
|
||||
# 格式 源目录:目的目录:媒体库内网盘路径:监控模式
|
||||
if not monitor_conf:
|
||||
continue
|
||||
# 注释
|
||||
if str(monitor_conf).startswith("#"):
|
||||
continue
|
||||
|
||||
if str(monitor_conf).count("#") == 3:
|
||||
increment_dir = str(monitor_conf).split("#")[0]
|
||||
source_dir = str(monitor_conf).split("#")[1]
|
||||
target_dir = str(monitor_conf).split("#")[2]
|
||||
library_dir = str(monitor_conf).split("#")[3]
|
||||
self._libraryconf[source_dir] = library_dir
|
||||
elif str(monitor_conf).count("#") == 5:
|
||||
increment_dir = str(monitor_conf).split("#")[0]
|
||||
source_dir = str(monitor_conf).split("#")[1]
|
||||
target_dir = str(monitor_conf).split("#")[2]
|
||||
cloud_type = str(monitor_conf).split("#")[3]
|
||||
cloud_path = str(monitor_conf).split("#")[4]
|
||||
cloud_url = str(monitor_conf).split("#")[5]
|
||||
self._cloudtypeconf[source_dir] = cloud_type
|
||||
self._cloudpathconf[source_dir] = cloud_path
|
||||
self._cloudurlconf[source_dir] = cloud_url
|
||||
else:
|
||||
logger.error(f"{monitor_conf} 格式错误")
|
||||
continue
|
||||
|
||||
# 存储目录监控配置
|
||||
self._dirconf[source_dir] = target_dir
|
||||
|
||||
# 增量配置
|
||||
self._increment_dir[increment_dir] = source_dir
|
||||
|
||||
# 检查媒体库目录是不是下载目录的子目录
|
||||
try:
|
||||
if target_dir and Path(target_dir).is_relative_to(Path(source_dir)):
|
||||
logger.warn(f"{target_dir} 是下载目录 {source_dir} 的子目录,无法监控")
|
||||
self.systemmessage.put(f"{target_dir} 是下载目录 {source_dir} 的子目录,无法监控")
|
||||
continue
|
||||
except Exception as e:
|
||||
logger.debug(str(e))
|
||||
pass
|
||||
|
||||
# 运行一次定时服务
|
||||
if self._onlyonce:
|
||||
logger.info("云盘增量监控执行服务启动,立即运行一次")
|
||||
self._scheduler.add_job(func=self.scan, trigger='date',
|
||||
run_date=datetime.now(tz=pytz.timezone(settings.TZ)) + timedelta(seconds=3),
|
||||
name="云盘增量监控")
|
||||
# 关闭一次性开关
|
||||
self._onlyonce = False
|
||||
# 保存配置
|
||||
self.__update_config()
|
||||
|
||||
# 周期运行
|
||||
if self._cron:
|
||||
try:
|
||||
self._scheduler.add_job(func=self.scan,
|
||||
trigger=CronTrigger.from_crontab(self._cron),
|
||||
name="云盘增量监控")
|
||||
except Exception as err:
|
||||
logger.error(f"定时任务配置错误:{err}")
|
||||
# 推送实时消息
|
||||
self.systemmessage.put(f"执行周期配置错误:{err}")
|
||||
|
||||
# 启动任务
|
||||
if self._scheduler.get_jobs():
|
||||
self._scheduler.print_jobs()
|
||||
self._scheduler.start()
|
||||
|
||||
@eventmanager.register(EventType.PluginAction)
|
||||
def scan(self, event: Event = None):
|
||||
"""
|
||||
扫描
|
||||
"""
|
||||
if not self._enabled:
|
||||
logger.error("插件未开启")
|
||||
return
|
||||
if not self._dirconf or not self._dirconf.keys():
|
||||
logger.error("未获取到可用目录监控配置,请检查")
|
||||
return
|
||||
|
||||
if event:
|
||||
event_data = event.event_data
|
||||
if not event_data or event_data.get("action") != "cloud_strm_increment":
|
||||
return
|
||||
logger.info("收到命令,开始云盘strm生成 ...")
|
||||
self.post_message(channel=event.event_data.get("channel"),
|
||||
title="开始云盘strm生成 ...",
|
||||
userid=event.event_data.get("user"))
|
||||
|
||||
logger.info("云盘strm生成任务开始")
|
||||
for increment_dir in self._increment_dir.keys():
|
||||
logger.info(f"正在扫描增量目录 {increment_dir}")
|
||||
for root, dirs, files in os.walk(increment_dir):
|
||||
# 如果遇到名为'extrafanart'的文件夹,则跳过处理该文件夹,继续处理其他文件夹
|
||||
if "extrafanart" in dirs:
|
||||
dirs.remove("extrafanart")
|
||||
|
||||
# 处理文件
|
||||
for file in files:
|
||||
increment_file = os.path.join(root, file)
|
||||
# 回收站及隐藏的文件不处理
|
||||
if (increment_file.find("/@Recycle") != -1
|
||||
or increment_file.find("/#recycle") != -1
|
||||
or increment_file.find("/.") != -1
|
||||
or increment_file.find("/@eaDir") != -1):
|
||||
logger.info(f"{increment_file} 是回收站或隐藏的文件,跳过处理")
|
||||
continue
|
||||
|
||||
# 不复制非媒体文件时直接过滤掉非媒体文件
|
||||
if not self._copy_files and Path(file).suffix not in [ext.strip() for ext in
|
||||
self._rmt_mediaext.split(",")]:
|
||||
continue
|
||||
|
||||
logger.info(f"扫描到增量文件 {increment_file},正在开始处理")
|
||||
|
||||
# 移动到目标目录
|
||||
source_dir = self._increment_dir.get(increment_dir)
|
||||
# 移动后文件
|
||||
source_file = increment_file.replace(increment_dir, source_dir)
|
||||
|
||||
# 判断目标文件是否存在
|
||||
if not Path(source_file).parent.exists():
|
||||
Path(source_file).parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
shutil.move(increment_file, source_file, copy_function=shutil.copy2)
|
||||
logger.info(f"移动增量文件 {increment_file} 到 {source_file}")
|
||||
|
||||
# 扫描云盘文件,判断是否有对应strm
|
||||
self.__strm(source_file)
|
||||
logger.info(f"增量文件 {increment_file} 处理完成")
|
||||
|
||||
# 判断当前媒体父路径下是否有媒体文件,如有则无需遍历父级
|
||||
if not SystemUtils.exits_files(Path(increment_file).parent,
|
||||
[ext.strip() for ext in self._rmt_mediaext.split(",")]):
|
||||
# 判断父目录是否为空, 为空则删除
|
||||
for parent_path in Path(increment_file).parents:
|
||||
if parent_path.name in self._no_del_dirs:
|
||||
break
|
||||
if str(parent_path.name) == str(increment_dir):
|
||||
break
|
||||
if str(parent_path.parent) != str(Path(increment_file).root):
|
||||
# 父目录非根目录,才删除父目录
|
||||
if not SystemUtils.exits_files(parent_path, settings.RMT_MEDIAEXT):
|
||||
# 当前路径下没有媒体文件则删除
|
||||
shutil.rmtree(parent_path)
|
||||
logger.warn(f"增量非保留目录 {parent_path} 已删除")
|
||||
|
||||
logger.info("云盘strm生成任务完成")
|
||||
if event:
|
||||
self.post_message(channel=event.event_data.get("channel"),
|
||||
title="云盘strm生成任务完成!",
|
||||
userid=event.event_data.get("user"))
|
||||
|
||||
# def move_file(self,
|
||||
# file_path: Path,
|
||||
# dest_path: Path,
|
||||
# is_check_disk_space: bool = True,
|
||||
# min_free_space: int = 300,
|
||||
# wait_time: int = 300,
|
||||
# check_paths: Optional[List[Path]] = None,
|
||||
# ) -> bool:
|
||||
# """
|
||||
# 移动文件,如果父文件夹为空,则删除空父文件夹
|
||||
# """
|
||||
# # 在目标路径存在时,会尝试覆盖它
|
||||
# if not file_path.exists():
|
||||
# logger.debug(f"move文件不存在,跳过处理: {file_path}")
|
||||
#
|
||||
# if is_check_disk_space:
|
||||
# if not check_paths:
|
||||
# check_paths = [dest_path.parent]
|
||||
# check_paths.append(data_path)
|
||||
#
|
||||
# for check_path in check_paths:
|
||||
# while check_disk_space(check_path, min_free_space):
|
||||
# logger.warning(
|
||||
# f"文件 {check_path} 空间不足,等待 {wait_time}s再处理:"
|
||||
# f" {file_path}"
|
||||
# )
|
||||
# sleep(wait_time)
|
||||
#
|
||||
# logger.debug(f"移动文件: {file_path} -> {dest_path}")
|
||||
#
|
||||
# # # 改用copy2,避免移动文件夹时,程序中断导致文件丢失
|
||||
# # is_copyed = copy(file_path, dest_path)
|
||||
# # # 复制成功才继续执行
|
||||
# # if not is_copyed:
|
||||
# # logger.warning(f"移动文件失败: {file_path} -> {dest_path}")
|
||||
# # return False
|
||||
#
|
||||
# # # 复制后再删除文件
|
||||
# # logger.debug(f"已复制文件:{file_path}, 正在删除文件: {file_path}")
|
||||
#
|
||||
# try:
|
||||
# if not dest_path.parent.exists():
|
||||
# dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
#
|
||||
# cloud_str = "/mnt/cloud"
|
||||
# if str(file_path).startswith(cloud_str) and str(dest_path).startswith(
|
||||
# cloud_str
|
||||
# ):
|
||||
# # 如果是云盘路径,则使用重命名
|
||||
# file_path.rename(dest_path)
|
||||
# else:
|
||||
# shutil.move(file_path, dest_path, copy_function=shutil.copy2)
|
||||
|
||||
def __strm(self, source_file):
|
||||
"""
|
||||
判断文件是否有对应strm
|
||||
"""
|
||||
try:
|
||||
# 获取文件的转移路径
|
||||
for source_dir in self._dirconf.keys():
|
||||
if str(source_file).startswith(source_dir):
|
||||
# 转移路径
|
||||
dest_dir = self._dirconf.get(source_dir)
|
||||
# 媒体库容器内挂载路径
|
||||
library_dir = self._libraryconf.get(source_dir)
|
||||
# 云服务类型
|
||||
cloud_type = self._cloudtypeconf.get(source_dir)
|
||||
# 云服务挂载本地跟路径
|
||||
cloud_path = self._cloudpathconf.get(source_dir)
|
||||
# 云服务地址
|
||||
cloud_url = self._cloudurlconf.get(source_dir)
|
||||
|
||||
# 转移后文件
|
||||
dest_file = source_file.replace(source_dir, dest_dir)
|
||||
# 如果是文件夹
|
||||
if Path(dest_file).is_dir():
|
||||
if not Path(dest_file).exists():
|
||||
logger.info(f"创建目标文件夹 {dest_file}")
|
||||
os.makedirs(dest_file)
|
||||
continue
|
||||
else:
|
||||
# 非媒体文件
|
||||
if Path(dest_file).exists():
|
||||
logger.info(f"目标文件 {dest_file} 已存在")
|
||||
continue
|
||||
|
||||
# 文件
|
||||
if not Path(dest_file).parent.exists():
|
||||
logger.info(f"创建目标文件夹 {Path(dest_file).parent}")
|
||||
os.makedirs(Path(dest_file).parent)
|
||||
|
||||
# 视频文件创建.strm文件
|
||||
if Path(dest_file).suffix in [ext.strip() for ext in self._rmt_mediaext.split(",")]:
|
||||
# 创建.strm文件
|
||||
self.__create_strm_file(scheme="https" if self._https else "http",
|
||||
dest_file=dest_file,
|
||||
dest_dir=dest_dir,
|
||||
source_file=source_file,
|
||||
library_dir=library_dir,
|
||||
cloud_type=cloud_type,
|
||||
cloud_path=cloud_path,
|
||||
cloud_url=cloud_url)
|
||||
else:
|
||||
if self._copy_files:
|
||||
# 其他nfo、jpg等复制文件
|
||||
shutil.copy2(source_file, dest_file)
|
||||
logger.info(f"复制其他文件 {source_file} 到 {dest_file}")
|
||||
except Exception as e:
|
||||
logger.error(f"create strm file error: {e}")
|
||||
print(str(e))
|
||||
|
||||
@staticmethod
|
||||
def __create_strm_file(dest_file: str, dest_dir: str, source_file: str, library_dir: str = None,
|
||||
cloud_type: str = None, cloud_path: str = None, cloud_url: str = None,
|
||||
scheme: str = None):
|
||||
"""
|
||||
生成strm文件
|
||||
:param library_dir:
|
||||
:param dest_dir:
|
||||
:param dest_file:
|
||||
"""
|
||||
try:
|
||||
# 获取视频文件名和目录
|
||||
video_name = Path(dest_file).name
|
||||
# 获取视频目录
|
||||
dest_path = Path(dest_file).parent
|
||||
|
||||
if not dest_path.exists():
|
||||
logger.info(f"创建目标文件夹 {dest_path}")
|
||||
os.makedirs(str(dest_path))
|
||||
|
||||
# 构造.strm文件路径
|
||||
strm_path = os.path.join(dest_path, f"{os.path.splitext(video_name)[0]}.strm")
|
||||
# strm已存在跳过处理
|
||||
if Path(strm_path).exists():
|
||||
logger.info(f"strm文件已存在 {strm_path}")
|
||||
return
|
||||
|
||||
logger.info(f"替换前本地路径:::{dest_file}")
|
||||
|
||||
# 云盘模式
|
||||
if cloud_type:
|
||||
# 替换路径中的\为/
|
||||
dest_file = source_file.replace("\\", "/")
|
||||
dest_file = dest_file.replace(cloud_path, "")
|
||||
# 对盘符之后的所有内容进行url转码
|
||||
dest_file = urllib.parse.quote(dest_file, safe='')
|
||||
if str(cloud_type) == "cd2":
|
||||
# 将路径的开头盘符"/mnt/user/downloads"替换为"http://localhost:19798/static/http/localhost:19798/False/"
|
||||
dest_file = f"{scheme}://{cloud_url}/static/{scheme}/{cloud_url}/False/{dest_file}"
|
||||
logger.info(f"替换后cd2路径:::{dest_file}")
|
||||
elif str(cloud_type) == "alist":
|
||||
dest_file = f"{scheme}://{cloud_url}/d/{dest_file}"
|
||||
logger.info(f"替换后alist路径:::{dest_file}")
|
||||
else:
|
||||
logger.error(f"云盘类型 {cloud_type} 错误")
|
||||
return
|
||||
else:
|
||||
# 本地挂载路径转为emby路径
|
||||
dest_file = dest_file.replace(dest_dir, library_dir)
|
||||
logger.info(f"替换后emby容器内路径:::{dest_file}")
|
||||
|
||||
# 写入.strm文件
|
||||
with open(strm_path, 'w') as f:
|
||||
f.write(dest_file)
|
||||
|
||||
logger.info(f"创建strm文件 {strm_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"创建strm文件失败")
|
||||
print(str(e))
|
||||
|
||||
def __update_config(self):
|
||||
"""
|
||||
更新配置
|
||||
"""
|
||||
self.update_config({
|
||||
"enabled": self._enabled,
|
||||
"onlyonce": self._onlyonce,
|
||||
"copy_files": self._copy_files,
|
||||
"https": self._https,
|
||||
"cron": self._cron,
|
||||
"monitor_confs": self._monitor_confs,
|
||||
"no_del_dirs": self._no_del_dirs,
|
||||
"rmt_mediaext": self._rmt_mediaext
|
||||
})
|
||||
|
||||
def get_state(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
@staticmethod
|
||||
def get_command() -> List[Dict[str, Any]]:
|
||||
"""
|
||||
定义远程控制命令
|
||||
:return: 命令关键字、事件、描述、附带数据
|
||||
"""
|
||||
return [{
|
||||
"cmd": "/cloud_strm_increment",
|
||||
"event": EventType.PluginAction,
|
||||
"desc": "云盘strm文件生成(增量版)",
|
||||
"category": "",
|
||||
"data": {
|
||||
"action": "cloud_strm_increment"
|
||||
}
|
||||
}]
|
||||
|
||||
def get_service(self) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
注册插件公共服务
|
||||
[{
|
||||
"id": "服务ID",
|
||||
"name": "服务名称",
|
||||
"trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()",
|
||||
"func": self.xxx,
|
||||
"kwargs": {} # 定时器参数
|
||||
}]
|
||||
"""
|
||||
if self._enabled and self._cron:
|
||||
return [{
|
||||
"id": "CloudStrm",
|
||||
"name": "云盘strm文件生成服务",
|
||||
"trigger": CronTrigger.from_crontab(self._cron),
|
||||
"func": self.scan,
|
||||
"kwargs": {}
|
||||
}]
|
||||
return []
|
||||
|
||||
def get_api(self) -> List[Dict[str, Any]]:
|
||||
pass
|
||||
|
||||
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
|
||||
"""
|
||||
拼装插件配置页面,需要返回两块数据:1、页面配置;2、数据结构
|
||||
"""
|
||||
return [
|
||||
{
|
||||
'component': 'VForm',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 3
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'enabled',
|
||||
'label': '启用插件',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 3
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'onlyonce',
|
||||
'label': '立即运行一次',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 3
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'copy_files',
|
||||
'label': '复制非媒体文件',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 3
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VSwitch',
|
||||
'props': {
|
||||
'model': 'https',
|
||||
'label': '启用https',
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 6
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VTextField',
|
||||
'props': {
|
||||
'model': 'cron',
|
||||
'label': '生成周期',
|
||||
'placeholder': '0 0 * * *'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
'md': 6
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VTextField',
|
||||
'props': {
|
||||
'model': 'no_del_dirs',
|
||||
'label': '保留路径',
|
||||
'placeholder': 'series、movies、downloads、others'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VTextarea',
|
||||
'props': {
|
||||
'model': 'monitor_confs',
|
||||
'label': '监控目录',
|
||||
'rows': 5,
|
||||
'placeholder': '增量目录#监控目录#目的目录#媒体服务器内源文件路径'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VTextarea',
|
||||
'props': {
|
||||
'model': 'rmt_mediaext',
|
||||
'label': '视频格式',
|
||||
'rows': 2,
|
||||
'placeholder': ".mp4, .mkv, .ts, .iso,.rmvb, .avi, .mov, .mpeg,.mpg, .wmv, .3gp, .asf, .m4v, .flv, .m2ts, .strm,.tp, .f4v"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VAlert',
|
||||
'props': {
|
||||
'type': 'info',
|
||||
'variant': 'tonal',
|
||||
'text': '目录监控格式:'
|
||||
'1.增量目录#监控目录#目的目录#媒体服务器内源文件路径;'
|
||||
'2.增量目录#监控目录#目的目录#cd2#cd2挂载本地跟路径#cd2服务地址;'
|
||||
'3.增量目录#监控目录#目的目录#alist#alist挂载本地跟路径#alist服务地址。'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VAlert',
|
||||
'props': {
|
||||
'type': 'info',
|
||||
'variant': 'tonal',
|
||||
'text': '媒体服务器内源文件路径:源文件目录即云盘挂载到媒体服务器的路径。'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'component': 'VRow',
|
||||
'content': [
|
||||
{
|
||||
'component': 'VCol',
|
||||
'props': {
|
||||
'cols': 12,
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'VAlert',
|
||||
'props': {
|
||||
'type': 'success',
|
||||
'variant': 'tonal'
|
||||
},
|
||||
'content': [
|
||||
{
|
||||
'component': 'span',
|
||||
'text': '配置教程请参考:'
|
||||
},
|
||||
{
|
||||
'component': 'a',
|
||||
'props': {
|
||||
'href': 'https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/docs/CloudStrm.md',
|
||||
'target': '_blank'
|
||||
},
|
||||
'text': 'https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/docs/CloudStrm.md'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
], {
|
||||
"enabled": False,
|
||||
"cron": "",
|
||||
"onlyonce": False,
|
||||
"copy_files": False,
|
||||
"https": False,
|
||||
"monitor_confs": "",
|
||||
"no_del_dirs": "",
|
||||
"rmt_mediaext": ".mp4, .mkv, .ts, .iso,.rmvb, .avi, .mov, .mpeg,.mpg, .wmv, .3gp, .asf, .m4v, .flv, .m2ts, .strm,.tp, .f4v"
|
||||
}
|
||||
|
||||
def get_page(self) -> List[dict]:
|
||||
pass
|
||||
|
||||
def stop_service(self):
|
||||
"""
|
||||
退出插件
|
||||
"""
|
||||
try:
|
||||
if self._scheduler:
|
||||
self._scheduler.remove_all_jobs()
|
||||
if self._scheduler.running:
|
||||
self._scheduler.shutdown()
|
||||
self._scheduler = None
|
||||
except Exception as e:
|
||||
logger.error("退出插件失败:%s" % str(e))
|
||||
Reference in New Issue
Block a user