mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 15:38:19 +08:00
feat: TransferRename 事件增加 source_item 源文件信息
This commit is contained in:
@@ -13,8 +13,14 @@ from app.helper.directory import DirectoryHelper
|
|||||||
from app.helper.message import TemplateHelper
|
from app.helper.message import TemplateHelper
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.modules.filemanager.storages import StorageBase
|
from app.modules.filemanager.storages import StorageBase
|
||||||
from app.schemas import TransferInfo, TmdbEpisode, TransferDirectoryConf, FileItem, TransferInterceptEventData, \
|
from app.schemas import (
|
||||||
TransferRenameEventData
|
TransferInfo,
|
||||||
|
TmdbEpisode,
|
||||||
|
TransferDirectoryConf,
|
||||||
|
FileItem,
|
||||||
|
TransferInterceptEventData,
|
||||||
|
TransferRenameEventData,
|
||||||
|
)
|
||||||
from app.schemas.types import MediaType, ChainEventType
|
from app.schemas.types import MediaType, ChainEventType
|
||||||
from app.utils.system import SystemUtils
|
from app.utils.system import SystemUtils
|
||||||
|
|
||||||
@@ -51,12 +57,13 @@ class TransHandler:
|
|||||||
elif isinstance(current_value, bool):
|
elif isinstance(current_value, bool):
|
||||||
current_value = value
|
current_value = value
|
||||||
elif isinstance(current_value, int):
|
elif isinstance(current_value, int):
|
||||||
current_value += (value or 0)
|
current_value += value or 0
|
||||||
else:
|
else:
|
||||||
current_value = value
|
current_value = value
|
||||||
setattr(result, key, current_value)
|
setattr(result, key, current_value)
|
||||||
|
|
||||||
def transfer_media(self,
|
def transfer_media(
|
||||||
|
self,
|
||||||
fileitem: FileItem,
|
fileitem: FileItem,
|
||||||
in_meta: MetaBase,
|
in_meta: MetaBase,
|
||||||
mediainfo: MediaInfo,
|
mediainfo: MediaInfo,
|
||||||
@@ -69,7 +76,7 @@ class TransHandler:
|
|||||||
need_rename: Optional[bool] = True,
|
need_rename: Optional[bool] = True,
|
||||||
need_notify: Optional[bool] = True,
|
need_notify: Optional[bool] = True,
|
||||||
overwrite_mode: Optional[str] = None,
|
overwrite_mode: Optional[str] = None,
|
||||||
episodes_info: List[TmdbEpisode] = None
|
episodes_info: List[TmdbEpisode] = None,
|
||||||
) -> TransferInfo:
|
) -> TransferInfo:
|
||||||
"""
|
"""
|
||||||
识别并整理一个文件或者一个目录下的所有文件
|
识别并整理一个文件或者一个目录下的所有文件
|
||||||
@@ -109,7 +116,9 @@ class TransHandler:
|
|||||||
"""
|
"""
|
||||||
if not _fileitem.extension:
|
if not _fileitem.extension:
|
||||||
return False
|
return False
|
||||||
if f".{_fileitem.extension.lower()}" in (settings.RMT_SUBEXT + settings.RMT_AUDIOEXT):
|
if f".{_fileitem.extension.lower()}" in (
|
||||||
|
settings.RMT_SUBEXT + settings.RMT_AUDIOEXT
|
||||||
|
):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -117,7 +126,6 @@ class TransHandler:
|
|||||||
result = TransferInfo()
|
result = TransferInfo()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
# 重命名格式
|
# 重命名格式
|
||||||
rename_format = settings.RENAME_FORMAT(mediainfo.type)
|
rename_format = settings.RENAME_FORMAT(mediainfo.type)
|
||||||
|
|
||||||
@@ -128,9 +136,11 @@ class TransHandler:
|
|||||||
new_path = self.get_rename_path(
|
new_path = self.get_rename_path(
|
||||||
path=target_path,
|
path=target_path,
|
||||||
template_string=rename_format,
|
template_string=rename_format,
|
||||||
rename_dict=self.get_naming_dict(meta=in_meta,
|
rename_dict=self.get_naming_dict(
|
||||||
mediainfo=mediainfo),
|
meta=in_meta, mediainfo=mediainfo
|
||||||
source_path=fileitem.path
|
),
|
||||||
|
source_path=fileitem.path,
|
||||||
|
source_item=fileitem,
|
||||||
)
|
)
|
||||||
new_path = DirectoryHelper.get_media_root_path(
|
new_path = DirectoryHelper.get_media_root_path(
|
||||||
rename_format, rename_path=new_path
|
rename_format, rename_path=new_path
|
||||||
@@ -155,34 +165,40 @@ class TransHandler:
|
|||||||
file.size for file in source_oper.list(stream_fileitem) or []
|
file.size for file in source_oper.list(stream_fileitem) or []
|
||||||
)
|
)
|
||||||
# 整理目录
|
# 整理目录
|
||||||
new_diritem, errmsg = self.__transfer_dir(fileitem=fileitem,
|
new_diritem, errmsg = self.__transfer_dir(
|
||||||
|
fileitem=fileitem,
|
||||||
mediainfo=mediainfo,
|
mediainfo=mediainfo,
|
||||||
source_oper=source_oper,
|
source_oper=source_oper,
|
||||||
target_oper=target_oper,
|
target_oper=target_oper,
|
||||||
target_storage=target_storage,
|
target_storage=target_storage,
|
||||||
target_path=new_path,
|
target_path=new_path,
|
||||||
transfer_type=transfer_type,
|
transfer_type=transfer_type,
|
||||||
result=result)
|
result=result,
|
||||||
|
)
|
||||||
if not new_diritem:
|
if not new_diritem:
|
||||||
logger.error(f"文件夹 {fileitem.path} 整理失败:{errmsg}")
|
logger.error(f"文件夹 {fileitem.path} 整理失败:{errmsg}")
|
||||||
self.__update_result(result=result,
|
self.__update_result(
|
||||||
|
result=result,
|
||||||
success=False,
|
success=False,
|
||||||
message=errmsg,
|
message=errmsg,
|
||||||
fileitem=fileitem,
|
fileitem=fileitem,
|
||||||
transfer_type=transfer_type,
|
transfer_type=transfer_type,
|
||||||
need_notify=need_notify)
|
need_notify=need_notify,
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
logger.info(f"文件夹 {fileitem.path} 整理成功")
|
logger.info(f"文件夹 {fileitem.path} 整理成功")
|
||||||
# 返回整理后的路径
|
# 返回整理后的路径
|
||||||
self.__update_result(result=result,
|
self.__update_result(
|
||||||
|
result=result,
|
||||||
success=True,
|
success=True,
|
||||||
fileitem=fileitem,
|
fileitem=fileitem,
|
||||||
target_item=new_diritem,
|
target_item=new_diritem,
|
||||||
target_diritem=new_diritem,
|
target_diritem=new_diritem,
|
||||||
need_scrape=need_scrape,
|
need_scrape=need_scrape,
|
||||||
need_notify=need_notify,
|
need_notify=need_notify,
|
||||||
transfer_type=transfer_type)
|
transfer_type=transfer_type,
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
# 整理单个文件
|
# 整理单个文件
|
||||||
@@ -190,13 +206,15 @@ class TransHandler:
|
|||||||
# 电视剧
|
# 电视剧
|
||||||
if in_meta.begin_episode is None:
|
if in_meta.begin_episode is None:
|
||||||
logger.warn(f"文件 {fileitem.path} 整理失败:未识别到文件集数")
|
logger.warn(f"文件 {fileitem.path} 整理失败:未识别到文件集数")
|
||||||
self.__update_result(result=result,
|
self.__update_result(
|
||||||
|
result=result,
|
||||||
success=False,
|
success=False,
|
||||||
message="未识别到文件集数",
|
message="未识别到文件集数",
|
||||||
fileitem=fileitem,
|
fileitem=fileitem,
|
||||||
fail_list=[fileitem.path],
|
fail_list=[fileitem.path],
|
||||||
transfer_type=transfer_type,
|
transfer_type=transfer_type,
|
||||||
need_notify=need_notify)
|
need_notify=need_notify,
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# 文件结束季为空
|
# 文件结束季为空
|
||||||
@@ -218,9 +236,10 @@ class TransHandler:
|
|||||||
meta=in_meta,
|
meta=in_meta,
|
||||||
mediainfo=mediainfo,
|
mediainfo=mediainfo,
|
||||||
episodes_info=episodes_info,
|
episodes_info=episodes_info,
|
||||||
file_ext=f".{fileitem.extension}"
|
file_ext=f".{fileitem.extension}",
|
||||||
),
|
),
|
||||||
source_path=fileitem.path
|
source_path=fileitem.path,
|
||||||
|
source_item=fileitem,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 针对字幕文件,文件名中补充额外标识信息
|
# 针对字幕文件,文件名中补充额外标识信息
|
||||||
@@ -250,13 +269,15 @@ class TransHandler:
|
|||||||
target_diritem = target_oper.get_folder(folder_path)
|
target_diritem = target_oper.get_folder(folder_path)
|
||||||
if not target_diritem:
|
if not target_diritem:
|
||||||
logger.error(f"目标目录 {folder_path} 获取失败")
|
logger.error(f"目标目录 {folder_path} 获取失败")
|
||||||
self.__update_result(result=result,
|
self.__update_result(
|
||||||
|
result=result,
|
||||||
success=False,
|
success=False,
|
||||||
message=f"目标目录 {folder_path} 获取失败",
|
message=f"目标目录 {folder_path} 获取失败",
|
||||||
fileitem=fileitem,
|
fileitem=fileitem,
|
||||||
fail_list=[fileitem.path],
|
fail_list=[fileitem.path],
|
||||||
transfer_type=transfer_type,
|
transfer_type=transfer_type,
|
||||||
need_notify=need_notify)
|
need_notify=need_notify,
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# 判断是否要覆盖,附加文件强制覆盖
|
# 判断是否要覆盖,附加文件强制覆盖
|
||||||
@@ -274,17 +295,21 @@ class TransHandler:
|
|||||||
if not overflag:
|
if not overflag:
|
||||||
# 目标文件已存在
|
# 目标文件已存在
|
||||||
logger.info(
|
logger.info(
|
||||||
f"目的文件系统中已经存在同名文件 {target_file},当前整理覆盖模式设置为 {overwrite_mode}")
|
f"目的文件系统中已经存在同名文件 {target_file},当前整理覆盖模式设置为 {overwrite_mode}"
|
||||||
if overwrite_mode == 'always':
|
)
|
||||||
|
if overwrite_mode == "always":
|
||||||
# 总是覆盖同名文件
|
# 总是覆盖同名文件
|
||||||
overflag = True
|
overflag = True
|
||||||
elif overwrite_mode == 'size':
|
elif overwrite_mode == "size":
|
||||||
# 存在时大覆盖小
|
# 存在时大覆盖小
|
||||||
if target_item.size < fileitem.size:
|
if target_item.size < fileitem.size:
|
||||||
logger.info(f"目标文件文件大小更小,将覆盖:{new_file}")
|
logger.info(
|
||||||
|
f"目标文件文件大小更小,将覆盖:{new_file}"
|
||||||
|
)
|
||||||
overflag = True
|
overflag = True
|
||||||
else:
|
else:
|
||||||
self.__update_result(result=result,
|
self.__update_result(
|
||||||
|
result=result,
|
||||||
success=False,
|
success=False,
|
||||||
message=f"媒体库存在同名文件,且质量更好",
|
message=f"媒体库存在同名文件,且质量更好",
|
||||||
fileitem=fileitem,
|
fileitem=fileitem,
|
||||||
@@ -292,11 +317,13 @@ class TransHandler:
|
|||||||
target_diritem=target_diritem,
|
target_diritem=target_diritem,
|
||||||
fail_list=[fileitem.path],
|
fail_list=[fileitem.path],
|
||||||
transfer_type=transfer_type,
|
transfer_type=transfer_type,
|
||||||
need_notify=need_notify)
|
need_notify=need_notify,
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
elif overwrite_mode == 'never':
|
elif overwrite_mode == "never":
|
||||||
# 存在不覆盖
|
# 存在不覆盖
|
||||||
self.__update_result(result=result,
|
self.__update_result(
|
||||||
|
result=result,
|
||||||
success=False,
|
success=False,
|
||||||
message=f"媒体库存在同名文件,当前覆盖模式为不覆盖",
|
message=f"媒体库存在同名文件,当前覆盖模式为不覆盖",
|
||||||
fileitem=fileitem,
|
fileitem=fileitem,
|
||||||
@@ -304,24 +331,29 @@ class TransHandler:
|
|||||||
target_diritem=target_diritem,
|
target_diritem=target_diritem,
|
||||||
fail_list=[fileitem.path],
|
fail_list=[fileitem.path],
|
||||||
transfer_type=transfer_type,
|
transfer_type=transfer_type,
|
||||||
need_notify=need_notify)
|
need_notify=need_notify,
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
elif overwrite_mode == 'latest':
|
elif overwrite_mode == "latest":
|
||||||
# 仅保留最新版本
|
# 仅保留最新版本
|
||||||
logger.info(f"当前整理覆盖模式设置为仅保留最新版本,将覆盖:{new_file}")
|
logger.info(
|
||||||
|
f"当前整理覆盖模式设置为仅保留最新版本,将覆盖:{new_file}"
|
||||||
|
)
|
||||||
overflag = True
|
overflag = True
|
||||||
else:
|
else:
|
||||||
if overwrite_mode == 'latest':
|
if overwrite_mode == "latest":
|
||||||
# 文件不存在,但仅保留最新版本
|
# 文件不存在,但仅保留最新版本
|
||||||
logger.info(
|
logger.info(
|
||||||
f"当前整理覆盖模式设置为 {overwrite_mode},仅保留最新版本,正在删除已有版本文件 ...")
|
f"当前整理覆盖模式设置为 {overwrite_mode},仅保留最新版本,正在删除已有版本文件 ..."
|
||||||
|
)
|
||||||
self.__delete_version_files(target_oper, new_file)
|
self.__delete_version_files(target_oper, new_file)
|
||||||
else:
|
else:
|
||||||
# 附加文件 总是需要覆盖
|
# 附加文件 总是需要覆盖
|
||||||
overflag = True
|
overflag = True
|
||||||
|
|
||||||
# 整理文件
|
# 整理文件
|
||||||
new_item, err_msg = self.__transfer_file(fileitem=fileitem,
|
new_item, err_msg = self.__transfer_file(
|
||||||
|
fileitem=fileitem,
|
||||||
mediainfo=mediainfo,
|
mediainfo=mediainfo,
|
||||||
target_storage=target_storage,
|
target_storage=target_storage,
|
||||||
target_file=new_file,
|
target_file=new_file,
|
||||||
@@ -329,36 +361,45 @@ class TransHandler:
|
|||||||
over_flag=overflag,
|
over_flag=overflag,
|
||||||
source_oper=source_oper,
|
source_oper=source_oper,
|
||||||
target_oper=target_oper,
|
target_oper=target_oper,
|
||||||
result=result)
|
result=result,
|
||||||
|
)
|
||||||
if not new_item:
|
if not new_item:
|
||||||
logger.error(f"文件 {fileitem.path} 整理失败:{err_msg}")
|
logger.error(f"文件 {fileitem.path} 整理失败:{err_msg}")
|
||||||
self.__update_result(result=result,
|
self.__update_result(
|
||||||
|
result=result,
|
||||||
success=False,
|
success=False,
|
||||||
message=err_msg,
|
message=err_msg,
|
||||||
fileitem=fileitem,
|
fileitem=fileitem,
|
||||||
fail_list=[fileitem.path],
|
fail_list=[fileitem.path],
|
||||||
transfer_type=transfer_type,
|
transfer_type=transfer_type,
|
||||||
need_notify=need_notify)
|
need_notify=need_notify,
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
logger.info(f"文件 {fileitem.path} 整理成功")
|
logger.info(f"文件 {fileitem.path} 整理成功")
|
||||||
self.__update_result(result=result,
|
self.__update_result(
|
||||||
|
result=result,
|
||||||
success=True,
|
success=True,
|
||||||
fileitem=fileitem,
|
fileitem=fileitem,
|
||||||
target_item=new_item,
|
target_item=new_item,
|
||||||
target_diritem=target_diritem,
|
target_diritem=target_diritem,
|
||||||
need_scrape=need_scrape,
|
need_scrape=need_scrape,
|
||||||
transfer_type=transfer_type,
|
transfer_type=transfer_type,
|
||||||
need_notify=need_notify)
|
need_notify=need_notify,
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"媒体整理出错:{e}")
|
logger.error(f"媒体整理出错:{e}")
|
||||||
return TransferInfo(success=False, message=str(e))
|
return TransferInfo(success=False, message=str(e))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __transfer_command(fileitem: FileItem, target_storage: str,
|
def __transfer_command(
|
||||||
source_oper: StorageBase, target_oper: StorageBase,
|
fileitem: FileItem,
|
||||||
target_file: Path, transfer_type: str,
|
target_storage: str,
|
||||||
|
source_oper: StorageBase,
|
||||||
|
target_oper: StorageBase,
|
||||||
|
target_file: Path,
|
||||||
|
transfer_type: str,
|
||||||
) -> Tuple[Optional[FileItem], str]:
|
) -> Tuple[Optional[FileItem], str]:
|
||||||
"""
|
"""
|
||||||
处理单个文件
|
处理单个文件
|
||||||
@@ -381,12 +422,15 @@ class TransHandler:
|
|||||||
basename=_path.stem,
|
basename=_path.stem,
|
||||||
type="file",
|
type="file",
|
||||||
size=_path.stat().st_size,
|
size=_path.stat().st_size,
|
||||||
extension=_path.suffix.lstrip('.'),
|
extension=_path.suffix.lstrip("."),
|
||||||
modify_time=_path.stat().st_mtime
|
modify_time=_path.stat().st_mtime,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (fileitem.storage != target_storage
|
if (
|
||||||
and fileitem.storage != "local" and target_storage != "local"):
|
fileitem.storage != target_storage
|
||||||
|
and fileitem.storage != "local"
|
||||||
|
and target_storage != "local"
|
||||||
|
):
|
||||||
return None, f"不支持 {fileitem.storage} 到 {target_storage} 的文件整理"
|
return None, f"不支持 {fileitem.storage} 到 {target_storage} 的文件整理"
|
||||||
|
|
||||||
if fileitem.storage == "local" and target_storage == "local":
|
if fileitem.storage == "local" and target_storage == "local":
|
||||||
@@ -419,20 +463,27 @@ class TransHandler:
|
|||||||
target_fileitem = target_oper.get_folder(target_file.parent)
|
target_fileitem = target_oper.get_folder(target_file.parent)
|
||||||
if target_fileitem:
|
if target_fileitem:
|
||||||
# 上传文件
|
# 上传文件
|
||||||
new_item = target_oper.upload(target_fileitem, filepath, target_file.name)
|
new_item = target_oper.upload(
|
||||||
|
target_fileitem, filepath, target_file.name
|
||||||
|
)
|
||||||
if new_item:
|
if new_item:
|
||||||
return new_item, ""
|
return new_item, ""
|
||||||
else:
|
else:
|
||||||
return None, f"{fileitem.path} 上传 {target_storage} 失败"
|
return None, f"{fileitem.path} 上传 {target_storage} 失败"
|
||||||
else:
|
else:
|
||||||
return None, f"【{target_storage}】{target_file.parent} 目录获取失败"
|
return (
|
||||||
|
None,
|
||||||
|
f"【{target_storage}】{target_file.parent} 目录获取失败",
|
||||||
|
)
|
||||||
elif transfer_type == "move":
|
elif transfer_type == "move":
|
||||||
# 移动
|
# 移动
|
||||||
# 根据目的路径获取文件夹
|
# 根据目的路径获取文件夹
|
||||||
target_fileitem = target_oper.get_folder(target_file.parent)
|
target_fileitem = target_oper.get_folder(target_file.parent)
|
||||||
if target_fileitem:
|
if target_fileitem:
|
||||||
# 上传文件
|
# 上传文件
|
||||||
new_item = target_oper.upload(target_fileitem, filepath, target_file.name)
|
new_item = target_oper.upload(
|
||||||
|
target_fileitem, filepath, target_file.name
|
||||||
|
)
|
||||||
if new_item:
|
if new_item:
|
||||||
# 删除源文件
|
# 删除源文件
|
||||||
source_oper.delete(fileitem)
|
source_oper.delete(fileitem)
|
||||||
@@ -440,7 +491,10 @@ class TransHandler:
|
|||||||
else:
|
else:
|
||||||
return None, f"{fileitem.path} 上传 {target_storage} 失败"
|
return None, f"{fileitem.path} 上传 {target_storage} 失败"
|
||||||
else:
|
else:
|
||||||
return None, f"【{target_storage}】{target_file.parent} 目录获取失败"
|
return (
|
||||||
|
None,
|
||||||
|
f"【{target_storage}】{target_file.parent} 目录获取失败",
|
||||||
|
)
|
||||||
elif fileitem.storage != "local" and target_storage == "local":
|
elif fileitem.storage != "local" and target_storage == "local":
|
||||||
# 网盘到本地
|
# 网盘到本地
|
||||||
if target_file.exists():
|
if target_file.exists():
|
||||||
@@ -449,7 +503,9 @@ class TransHandler:
|
|||||||
# 网盘到本地
|
# 网盘到本地
|
||||||
if transfer_type in ["copy", "move"]:
|
if transfer_type in ["copy", "move"]:
|
||||||
# 下载
|
# 下载
|
||||||
tmp_file = source_oper.download(fileitem=fileitem, path=target_file.parent)
|
tmp_file = source_oper.download(
|
||||||
|
fileitem=fileitem, path=target_file.parent
|
||||||
|
)
|
||||||
if tmp_file:
|
if tmp_file:
|
||||||
# 创建目录
|
# 创建目录
|
||||||
if not target_file.parent.exists():
|
if not target_file.parent.exists():
|
||||||
@@ -471,22 +527,32 @@ class TransHandler:
|
|||||||
# 复制文件到新目录
|
# 复制文件到新目录
|
||||||
target_fileitem = target_oper.get_folder(target_file.parent)
|
target_fileitem = target_oper.get_folder(target_file.parent)
|
||||||
if target_fileitem:
|
if target_fileitem:
|
||||||
if source_oper.copy(fileitem, Path(target_fileitem.path), target_file.name):
|
if source_oper.copy(
|
||||||
|
fileitem, Path(target_fileitem.path), target_file.name
|
||||||
|
):
|
||||||
return target_oper.get_item(target_file), ""
|
return target_oper.get_item(target_file), ""
|
||||||
else:
|
else:
|
||||||
return None, f"【{target_storage}】{fileitem.path} 复制文件失败"
|
return None, f"【{target_storage}】{fileitem.path} 复制文件失败"
|
||||||
else:
|
else:
|
||||||
return None, f"【{target_storage}】{target_file.parent} 目录获取失败"
|
return (
|
||||||
|
None,
|
||||||
|
f"【{target_storage}】{target_file.parent} 目录获取失败",
|
||||||
|
)
|
||||||
elif transfer_type == "move":
|
elif transfer_type == "move":
|
||||||
# 移动文件到新目录
|
# 移动文件到新目录
|
||||||
target_fileitem = target_oper.get_folder(target_file.parent)
|
target_fileitem = target_oper.get_folder(target_file.parent)
|
||||||
if target_fileitem:
|
if target_fileitem:
|
||||||
if source_oper.move(fileitem, Path(target_fileitem.path), target_file.name):
|
if source_oper.move(
|
||||||
|
fileitem, Path(target_fileitem.path), target_file.name
|
||||||
|
):
|
||||||
return target_oper.get_item(target_file), ""
|
return target_oper.get_item(target_file), ""
|
||||||
else:
|
else:
|
||||||
return None, f"【{target_storage}】{fileitem.path} 移动文件失败"
|
return None, f"【{target_storage}】{fileitem.path} 移动文件失败"
|
||||||
else:
|
else:
|
||||||
return None, f"【{target_storage}】{target_file.parent} 目录获取失败"
|
return (
|
||||||
|
None,
|
||||||
|
f"【{target_storage}】{target_file.parent} 目录获取失败",
|
||||||
|
)
|
||||||
elif transfer_type == "link":
|
elif transfer_type == "link":
|
||||||
if source_oper.link(fileitem, target_file):
|
if source_oper.link(fileitem, target_file):
|
||||||
return target_oper.get_item(target_file), ""
|
return target_oper.get_item(target_file), ""
|
||||||
@@ -503,22 +569,28 @@ class TransHandler:
|
|||||||
重命名字幕文件,补充附加信息
|
重命名字幕文件,补充附加信息
|
||||||
"""
|
"""
|
||||||
# 字幕正则式
|
# 字幕正则式
|
||||||
_zhcn_sub_re = r"([.\[(\s](((zh[-_])?(cn|ch[si]|sg|sc))|zho?" \
|
_zhcn_sub_re = (
|
||||||
r"|chinese|(cn|ch[si]|sg|zho?)[-_&]?(cn|ch[si]|sg|zho?|eng|jap|ja|jpn)" \
|
r"([.\[(\s](((zh[-_])?(cn|ch[si]|sg|sc))|zho?"
|
||||||
r"|eng[-_&]?(cn|ch[si]|sg|zho?)|(jap|ja|jpn)[-_&]?(cn|ch[si]|sg|zho?)" \
|
r"|chinese|(cn|ch[si]|sg|zho?)[-_&]?(cn|ch[si]|sg|zho?|eng|jap|ja|jpn)"
|
||||||
r"|简[体中]?)[.\])\s])" \
|
r"|eng[-_&]?(cn|ch[si]|sg|zho?)|(jap|ja|jpn)[-_&]?(cn|ch[si]|sg|zho?)"
|
||||||
r"|([\u4e00-\u9fa5]{0,3}[中双][\u4e00-\u9fa5]{0,2}[字文语][\u4e00-\u9fa5]{0,3})" \
|
r"|简[体中]?)[.\])\s])"
|
||||||
r"|简体|简中|JPSC|sc_jp" \
|
r"|([\u4e00-\u9fa5]{0,3}[中双][\u4e00-\u9fa5]{0,2}[字文语][\u4e00-\u9fa5]{0,3})"
|
||||||
|
r"|简体|简中|JPSC|sc_jp"
|
||||||
r"|(?<![a-z0-9])gb(?![a-z0-9])"
|
r"|(?<![a-z0-9])gb(?![a-z0-9])"
|
||||||
_zhtw_sub_re = r"([.\[(\s](((zh[-_])?(hk|tw|cht|tc))" \
|
)
|
||||||
r"|cht[-_&]?(cht|eng|jap|ja|jpn)" \
|
_zhtw_sub_re = (
|
||||||
r"|eng[-_&]?cht|(jap|ja|jpn)[-_&]?cht" \
|
r"([.\[(\s](((zh[-_])?(hk|tw|cht|tc))"
|
||||||
r"|繁[体中]?)[.\])\s])" \
|
r"|cht[-_&]?(cht|eng|jap|ja|jpn)"
|
||||||
r"|繁体中[文字]|中[文字]繁体|繁体|JPTC|tc_jp" \
|
r"|eng[-_&]?cht|(jap|ja|jpn)[-_&]?cht"
|
||||||
|
r"|繁[体中]?)[.\])\s])"
|
||||||
|
r"|繁体中[文字]|中[文字]繁体|繁体|JPTC|tc_jp"
|
||||||
r"|(?<![a-z0-9])big5(?![a-z0-9])"
|
r"|(?<![a-z0-9])big5(?![a-z0-9])"
|
||||||
_ja_sub_re = r"([.\[(\s](ja-jp|jap|ja|jpn" \
|
)
|
||||||
r"|(jap|ja|jpn)[-_&]?eng|eng[-_&]?(jap|ja|jpn))[.\])\s])" \
|
_ja_sub_re = (
|
||||||
|
r"([.\[(\s](ja-jp|jap|ja|jpn"
|
||||||
|
r"|(jap|ja|jpn)[-_&]?eng|eng[-_&]?(jap|ja|jpn))[.\])\s])"
|
||||||
r"|日本語|日語"
|
r"|日本語|日語"
|
||||||
|
)
|
||||||
_eng_sub_re = r"[.\[(\s]eng[.\])\s]"
|
_eng_sub_re = r"[.\[(\s]eng[.\])\s]"
|
||||||
|
|
||||||
# 原文件后缀
|
# 原文件后缀
|
||||||
@@ -537,20 +609,29 @@ class TransHandler:
|
|||||||
new_file_type = ".eng"
|
new_file_type = ".eng"
|
||||||
|
|
||||||
# 添加默认字幕标识
|
# 添加默认字幕标识
|
||||||
if ((settings.DEFAULT_SUB == "zh-cn" and new_file_type == ".chi.zh-cn")
|
if (
|
||||||
|
(settings.DEFAULT_SUB == "zh-cn" and new_file_type == ".chi.zh-cn")
|
||||||
or (settings.DEFAULT_SUB == "zh-tw" and new_file_type == ".zh-tw")
|
or (settings.DEFAULT_SUB == "zh-tw" and new_file_type == ".zh-tw")
|
||||||
or (settings.DEFAULT_SUB == "ja" and new_file_type == ".ja")
|
or (settings.DEFAULT_SUB == "ja" and new_file_type == ".ja")
|
||||||
or (settings.DEFAULT_SUB == "eng" and new_file_type == ".eng")):
|
or (settings.DEFAULT_SUB == "eng" and new_file_type == ".eng")
|
||||||
|
):
|
||||||
new_sub_tag = ".default" + new_file_type
|
new_sub_tag = ".default" + new_file_type
|
||||||
else:
|
else:
|
||||||
new_sub_tag = new_file_type
|
new_sub_tag = new_file_type
|
||||||
|
|
||||||
return new_file.with_name(new_file.stem + new_sub_tag + file_ext)
|
return new_file.with_name(new_file.stem + new_sub_tag + file_ext)
|
||||||
|
|
||||||
def __transfer_dir(self, fileitem: FileItem, mediainfo: MediaInfo,
|
def __transfer_dir(
|
||||||
source_oper: StorageBase, target_oper: StorageBase,
|
self,
|
||||||
transfer_type: str, target_storage: str, target_path: Path,
|
fileitem: FileItem,
|
||||||
result: TransferInfo) -> Tuple[Optional[FileItem], str]:
|
mediainfo: MediaInfo,
|
||||||
|
source_oper: StorageBase,
|
||||||
|
target_oper: StorageBase,
|
||||||
|
transfer_type: str,
|
||||||
|
target_storage: str,
|
||||||
|
target_path: Path,
|
||||||
|
result: TransferInfo,
|
||||||
|
) -> Tuple[Optional[FileItem], str]:
|
||||||
"""
|
"""
|
||||||
整理整个文件夹
|
整理整个文件夹
|
||||||
:param fileitem: 源文件
|
:param fileitem: 源文件
|
||||||
@@ -570,7 +651,7 @@ class TransHandler:
|
|||||||
mediainfo=mediainfo,
|
mediainfo=mediainfo,
|
||||||
target_storage=target_storage,
|
target_storage=target_storage,
|
||||||
target_path=target_path,
|
target_path=target_path,
|
||||||
transfer_type=transfer_type
|
transfer_type=transfer_type,
|
||||||
)
|
)
|
||||||
event = eventmanager.send_event(ChainEventType.TransferIntercept, event_data)
|
event = eventmanager.send_event(ChainEventType.TransferIntercept, event_data)
|
||||||
if event and event.event_data:
|
if event and event.event_data:
|
||||||
@@ -579,25 +660,34 @@ class TransHandler:
|
|||||||
if event_data.cancel:
|
if event_data.cancel:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Transfer dir canceled by event: {event_data.source},"
|
f"Transfer dir canceled by event: {event_data.source},"
|
||||||
f"Reason: {event_data.reason}")
|
f"Reason: {event_data.reason}"
|
||||||
|
)
|
||||||
return None, event_data.reason
|
return None, event_data.reason
|
||||||
# 处理所有文件
|
# 处理所有文件
|
||||||
state, errmsg = self.__transfer_dir_files(fileitem=fileitem,
|
state, errmsg = self.__transfer_dir_files(
|
||||||
|
fileitem=fileitem,
|
||||||
target_storage=target_storage,
|
target_storage=target_storage,
|
||||||
source_oper=source_oper,
|
source_oper=source_oper,
|
||||||
target_oper=target_oper,
|
target_oper=target_oper,
|
||||||
target_path=target_path,
|
target_path=target_path,
|
||||||
transfer_type=transfer_type,
|
transfer_type=transfer_type,
|
||||||
result=result)
|
result=result,
|
||||||
|
)
|
||||||
if state:
|
if state:
|
||||||
return target_item, errmsg
|
return target_item, errmsg
|
||||||
else:
|
else:
|
||||||
return None, errmsg
|
return None, errmsg
|
||||||
|
|
||||||
def __transfer_dir_files(self, fileitem: FileItem, target_storage: str,
|
def __transfer_dir_files(
|
||||||
source_oper: StorageBase, target_oper: StorageBase,
|
self,
|
||||||
transfer_type: str, target_path: Path,
|
fileitem: FileItem,
|
||||||
result: TransferInfo) -> Tuple[bool, str]:
|
target_storage: str,
|
||||||
|
source_oper: StorageBase,
|
||||||
|
target_oper: StorageBase,
|
||||||
|
transfer_type: str,
|
||||||
|
target_path: Path,
|
||||||
|
result: TransferInfo,
|
||||||
|
) -> Tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
按目录结构整理目录下所有文件
|
按目录结构整理目录下所有文件
|
||||||
:param fileitem: 源文件
|
:param fileitem: 源文件
|
||||||
@@ -613,24 +703,28 @@ class TransHandler:
|
|||||||
if item.type == "dir":
|
if item.type == "dir":
|
||||||
# 递归整理目录
|
# 递归整理目录
|
||||||
new_path = target_path / item.name
|
new_path = target_path / item.name
|
||||||
state, errmsg = self.__transfer_dir_files(fileitem=item,
|
state, errmsg = self.__transfer_dir_files(
|
||||||
|
fileitem=item,
|
||||||
target_storage=target_storage,
|
target_storage=target_storage,
|
||||||
source_oper=source_oper,
|
source_oper=source_oper,
|
||||||
target_oper=target_oper,
|
target_oper=target_oper,
|
||||||
transfer_type=transfer_type,
|
transfer_type=transfer_type,
|
||||||
target_path=new_path,
|
target_path=new_path,
|
||||||
result=result)
|
result=result,
|
||||||
|
)
|
||||||
if not state:
|
if not state:
|
||||||
return False, errmsg
|
return False, errmsg
|
||||||
else:
|
else:
|
||||||
# 整理文件
|
# 整理文件
|
||||||
new_file = target_path / item.name
|
new_file = target_path / item.name
|
||||||
new_item, errmsg = self.__transfer_command(fileitem=item,
|
new_item, errmsg = self.__transfer_command(
|
||||||
|
fileitem=item,
|
||||||
target_storage=target_storage,
|
target_storage=target_storage,
|
||||||
source_oper=source_oper,
|
source_oper=source_oper,
|
||||||
target_oper=target_oper,
|
target_oper=target_oper,
|
||||||
target_file=new_file,
|
target_file=new_file,
|
||||||
transfer_type=transfer_type)
|
transfer_type=transfer_type,
|
||||||
|
)
|
||||||
if not new_item:
|
if not new_item:
|
||||||
return False, errmsg
|
return False, errmsg
|
||||||
self.__update_result(
|
self.__update_result(
|
||||||
@@ -641,11 +735,18 @@ class TransHandler:
|
|||||||
# 返回成功
|
# 返回成功
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
def __transfer_file(self, fileitem: FileItem, mediainfo: MediaInfo,
|
def __transfer_file(
|
||||||
source_oper: StorageBase, target_oper: StorageBase,
|
self,
|
||||||
target_storage: str, target_file: Path,
|
fileitem: FileItem,
|
||||||
transfer_type: str, result: TransferInfo,
|
mediainfo: MediaInfo,
|
||||||
over_flag: Optional[bool] = False) -> Tuple[Optional[FileItem], str]:
|
source_oper: StorageBase,
|
||||||
|
target_oper: StorageBase,
|
||||||
|
target_storage: str,
|
||||||
|
target_file: Path,
|
||||||
|
transfer_type: str,
|
||||||
|
result: TransferInfo,
|
||||||
|
over_flag: Optional[bool] = False,
|
||||||
|
) -> Tuple[Optional[FileItem], str]:
|
||||||
"""
|
"""
|
||||||
整理一个文件,同时处理其他相关文件
|
整理一个文件,同时处理其他相关文件
|
||||||
:param fileitem: 原文件
|
:param fileitem: 原文件
|
||||||
@@ -659,17 +760,17 @@ class TransHandler:
|
|||||||
:param source_oper: 源存储操作对象
|
:param source_oper: 源存储操作对象
|
||||||
:param target_oper: 目标存储操作对象
|
:param target_oper: 目标存储操作对象
|
||||||
"""
|
"""
|
||||||
logger.info(f"正在整理文件:【{fileitem.storage}】{fileitem.path} 到 【{target_storage}】{target_file},"
|
logger.info(
|
||||||
f"操作类型:{transfer_type}")
|
f"正在整理文件:【{fileitem.storage}】{fileitem.path} 到 【{target_storage}】{target_file},"
|
||||||
|
f"操作类型:{transfer_type}"
|
||||||
|
)
|
||||||
event_data = TransferInterceptEventData(
|
event_data = TransferInterceptEventData(
|
||||||
fileitem=fileitem,
|
fileitem=fileitem,
|
||||||
mediainfo=mediainfo,
|
mediainfo=mediainfo,
|
||||||
target_storage=target_storage,
|
target_storage=target_storage,
|
||||||
target_path=target_file,
|
target_path=target_file,
|
||||||
transfer_type=transfer_type,
|
transfer_type=transfer_type,
|
||||||
options={
|
options={"over_flag": over_flag},
|
||||||
"over_flag": over_flag
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
event = eventmanager.send_event(ChainEventType.TransferIntercept, event_data)
|
event = eventmanager.send_event(ChainEventType.TransferIntercept, event_data)
|
||||||
if event and event.event_data:
|
if event and event.event_data:
|
||||||
@@ -678,9 +779,12 @@ class TransHandler:
|
|||||||
if event_data.cancel:
|
if event_data.cancel:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Transfer file canceled by event: {event_data.source},"
|
f"Transfer file canceled by event: {event_data.source},"
|
||||||
f"Reason: {event_data.reason}")
|
f"Reason: {event_data.reason}"
|
||||||
|
)
|
||||||
return None, event_data.reason
|
return None, event_data.reason
|
||||||
if target_storage == "local" and (target_file.exists() or target_file.is_symlink()):
|
if target_storage == "local" and (
|
||||||
|
target_file.exists() or target_file.is_symlink()
|
||||||
|
):
|
||||||
if not over_flag:
|
if not over_flag:
|
||||||
logger.warn(f"文件已存在:{target_file}")
|
logger.warn(f"文件已存在:{target_file}")
|
||||||
return None, f"{target_file} 已存在"
|
return None, f"{target_file} 已存在"
|
||||||
@@ -694,15 +798,19 @@ class TransHandler:
|
|||||||
logger.warn(f"文件已存在:【{target_storage}】{target_file}")
|
logger.warn(f"文件已存在:【{target_storage}】{target_file}")
|
||||||
return None, f"【{target_storage}】{target_file} 已存在"
|
return None, f"【{target_storage}】{target_file} 已存在"
|
||||||
else:
|
else:
|
||||||
logger.info(f"正在删除已存在的文件:【{target_storage}】{target_file}")
|
logger.info(
|
||||||
|
f"正在删除已存在的文件:【{target_storage}】{target_file}"
|
||||||
|
)
|
||||||
target_oper.delete(exists_item)
|
target_oper.delete(exists_item)
|
||||||
# 执行文件整理命令
|
# 执行文件整理命令
|
||||||
new_item, errmsg = self.__transfer_command(fileitem=fileitem,
|
new_item, errmsg = self.__transfer_command(
|
||||||
|
fileitem=fileitem,
|
||||||
target_storage=target_storage,
|
target_storage=target_storage,
|
||||||
source_oper=source_oper,
|
source_oper=source_oper,
|
||||||
target_oper=target_oper,
|
target_oper=target_oper,
|
||||||
target_file=target_file,
|
target_file=target_file,
|
||||||
transfer_type=transfer_type)
|
transfer_type=transfer_type,
|
||||||
|
)
|
||||||
if new_item:
|
if new_item:
|
||||||
self.__update_result(
|
self.__update_result(
|
||||||
result=result,
|
result=result,
|
||||||
@@ -716,8 +824,12 @@ class TransHandler:
|
|||||||
return None, errmsg
|
return None, errmsg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_dest_path(mediainfo: MediaInfo, target_path: Path,
|
def get_dest_path(
|
||||||
need_type_folder: Optional[bool] = False, need_category_folder: Optional[bool] = False):
|
mediainfo: MediaInfo,
|
||||||
|
target_path: Path,
|
||||||
|
need_type_folder: Optional[bool] = False,
|
||||||
|
need_category_folder: Optional[bool] = False,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
获取目标路径
|
获取目标路径
|
||||||
"""
|
"""
|
||||||
@@ -728,8 +840,12 @@ class TransHandler:
|
|||||||
return target_path
|
return target_path
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_dest_dir(mediainfo: MediaInfo, target_dir: TransferDirectoryConf,
|
def get_dest_dir(
|
||||||
need_type_folder: Optional[bool] = None, need_category_folder: Optional[bool] = None) -> Path:
|
mediainfo: MediaInfo,
|
||||||
|
target_dir: TransferDirectoryConf,
|
||||||
|
need_type_folder: Optional[bool] = None,
|
||||||
|
need_category_folder: Optional[bool] = None,
|
||||||
|
) -> Path:
|
||||||
"""
|
"""
|
||||||
根据设置并装媒体库目录
|
根据设置并装媒体库目录
|
||||||
:param mediainfo: 媒体信息
|
:param mediainfo: 媒体信息
|
||||||
@@ -749,7 +865,11 @@ class TransHandler:
|
|||||||
library_dir = Path(target_dir.library_path) / target_dir.media_type
|
library_dir = Path(target_dir.library_path) / target_dir.media_type
|
||||||
else:
|
else:
|
||||||
library_dir = Path(target_dir.library_path)
|
library_dir = Path(target_dir.library_path)
|
||||||
if not target_dir.media_category and need_category_folder and mediainfo.category:
|
if (
|
||||||
|
not target_dir.media_category
|
||||||
|
and need_category_folder
|
||||||
|
and mediainfo.category
|
||||||
|
):
|
||||||
# 二级自动分类
|
# 二级自动分类
|
||||||
library_dir = library_dir / mediainfo.category
|
library_dir = library_dir / mediainfo.category
|
||||||
elif target_dir.media_category and need_category_folder:
|
elif target_dir.media_category and need_category_folder:
|
||||||
@@ -759,8 +879,12 @@ class TransHandler:
|
|||||||
return library_dir
|
return library_dir
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_naming_dict(meta: MetaBase, mediainfo: MediaInfo, file_ext: Optional[str] = None,
|
def get_naming_dict(
|
||||||
episodes_info: List[TmdbEpisode] = None) -> dict:
|
meta: MetaBase,
|
||||||
|
mediainfo: MediaInfo,
|
||||||
|
file_ext: Optional[str] = None,
|
||||||
|
episodes_info: List[TmdbEpisode] = None,
|
||||||
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
根据媒体信息,返回Format字典
|
根据媒体信息,返回Format字典
|
||||||
:param meta: 文件元数据
|
:param meta: 文件元数据
|
||||||
@@ -768,8 +892,12 @@ class TransHandler:
|
|||||||
:param file_ext: 文件扩展名
|
:param file_ext: 文件扩展名
|
||||||
:param episodes_info: 当前季的全部集信息
|
:param episodes_info: 当前季的全部集信息
|
||||||
"""
|
"""
|
||||||
return TemplateHelper().builder.build(meta=meta, mediainfo=mediainfo,
|
return TemplateHelper().builder.build(
|
||||||
file_extension=file_ext, episodes_info=episodes_info)
|
meta=meta,
|
||||||
|
mediainfo=mediainfo,
|
||||||
|
file_extension=file_ext,
|
||||||
|
episodes_info=episodes_info,
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __delete_version_files(storage_oper: StorageBase, path: Path) -> bool:
|
def __delete_version_files(storage_oper: StorageBase, path: Path) -> bool:
|
||||||
@@ -816,14 +944,20 @@ class TransHandler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_rename_path(template_string: str, rename_dict: dict,
|
def get_rename_path(
|
||||||
path: Path = None, source_path: str = None) -> Path:
|
template_string: str,
|
||||||
|
rename_dict: dict,
|
||||||
|
path: Optional[Path] = None,
|
||||||
|
source_path: Optional[str] = None,
|
||||||
|
source_item: Optional[FileItem] = None,
|
||||||
|
) -> Path:
|
||||||
"""
|
"""
|
||||||
生成重命名后的完整路径,支持智能重命名事件
|
生成重命名后的完整路径,支持智能重命名事件
|
||||||
:param template_string: Jinja2 模板字符串
|
:param template_string: Jinja2 模板字符串
|
||||||
:param rename_dict: 渲染上下文,用于替换模板中的变量
|
:param rename_dict: 渲染上下文,用于替换模板中的变量
|
||||||
:param path: 可选的基础路径,如果提供,将在其基础上拼接生成的路径
|
:param path: 可选的基础路径,如果提供,将在其基础上拼接生成的路径
|
||||||
:param source_path: 源文件路径,即待整理的文件路径
|
:param source_path: 源文件路径,即待整理的文件路径
|
||||||
|
:param source_item: 源文件信息,即待整理的文件信息
|
||||||
:return: 生成的完整路径
|
:return: 生成的完整路径
|
||||||
"""
|
"""
|
||||||
# 创建jinja2模板对象
|
# 创建jinja2模板对象
|
||||||
@@ -838,15 +972,18 @@ class TransHandler:
|
|||||||
rename_dict=rename_dict,
|
rename_dict=rename_dict,
|
||||||
render_str=render_str,
|
render_str=render_str,
|
||||||
path=path,
|
path=path,
|
||||||
source_path=source_path
|
source_path=source_path,
|
||||||
|
source_item=source_item,
|
||||||
)
|
)
|
||||||
event = eventmanager.send_event(ChainEventType.TransferRename, event_data)
|
event = eventmanager.send_event(ChainEventType.TransferRename, event_data)
|
||||||
# 检查事件返回的结果
|
# 检查事件返回的结果
|
||||||
if event and event.event_data:
|
if event and event.event_data:
|
||||||
event_data: TransferRenameEventData = event.event_data
|
event_data: TransferRenameEventData = event.event_data
|
||||||
if event_data.updated and event_data.updated_str:
|
if event_data.updated and event_data.updated_str:
|
||||||
logger.debug(f"Render string updated by event: "
|
logger.debug(
|
||||||
f"{render_str} -> {event_data.updated_str} (source: {event_data.source})")
|
f"Render string updated by event: "
|
||||||
|
f"{render_str} -> {event_data.updated_str} (source: {event_data.source})"
|
||||||
|
)
|
||||||
render_str = event_data.updated_str
|
render_str = event_data.updated_str
|
||||||
|
|
||||||
# 目的路径
|
# 目的路径
|
||||||
|
|||||||
+70
-17
@@ -11,6 +11,7 @@ class Event(BaseModel):
|
|||||||
"""
|
"""
|
||||||
事件模型
|
事件模型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
event_type: str = Field(..., description="事件类型")
|
event_type: str = Field(..., description="事件类型")
|
||||||
event_data: Optional[dict] = Field(default={}, description="事件数据")
|
event_data: Optional[dict] = Field(default={}, description="事件数据")
|
||||||
priority: Optional[int] = Field(0, description="事件优先级")
|
priority: Optional[int] = Field(0, description="事件优先级")
|
||||||
@@ -20,6 +21,7 @@ class BaseEventData(BaseModel):
|
|||||||
"""
|
"""
|
||||||
事件数据的基类,所有具体事件数据类应继承自此类
|
事件数据的基类,所有具体事件数据类应继承自此类
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -27,11 +29,14 @@ class ConfigChangeEventData(BaseEventData):
|
|||||||
"""
|
"""
|
||||||
ConfigChange 事件的数据模型
|
ConfigChange 事件的数据模型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key: set[str] = Field(..., description="配置项的键(集合类型)")
|
key: set[str] = Field(..., description="配置项的键(集合类型)")
|
||||||
value: Optional[Any] = Field(default=None, description="配置项的新值")
|
value: Optional[Any] = Field(default=None, description="配置项的新值")
|
||||||
change_type: str = Field(default="update", description="配置项的变更类型,如 'add', 'update', 'delete'")
|
change_type: str = Field(
|
||||||
|
default="update", description="配置项的变更类型,如 'add', 'update', 'delete'"
|
||||||
|
)
|
||||||
|
|
||||||
@field_validator('key', mode='before')
|
@field_validator("key", mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
def convert_to_set(cls, v):
|
def convert_to_set(cls, v):
|
||||||
"""将输入的 str、list、dict.keys() 等转为 set"""
|
"""将输入的 str、list、dict.keys() 等转为 set"""
|
||||||
@@ -55,6 +60,7 @@ class ChainEventData(BaseEventData):
|
|||||||
"""
|
"""
|
||||||
链式事件数据的基类,所有具体事件数据类应继承自此类
|
链式事件数据的基类,所有具体事件数据类应继承自此类
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -73,12 +79,24 @@ class AuthCredentials(ChainEventData):
|
|||||||
channel (Optional[str]): 认证渠道
|
channel (Optional[str]): 认证渠道
|
||||||
service (Optional[str]): 服务名称
|
service (Optional[str]): 服务名称
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 输入参数
|
# 输入参数
|
||||||
username: Optional[str] = Field(None, description="用户名,适用于 'password' 认证类型")
|
username: Optional[str] = Field(
|
||||||
password: Optional[str] = Field(None, description="用户密码,适用于 'password' 认证类型")
|
None, description="用户名,适用于 'password' 认证类型"
|
||||||
mfa_code: Optional[str] = Field(None, description="一次性密码,目前仅适用于 'password' 认证类型")
|
)
|
||||||
code: Optional[str] = Field(None, description="授权码,适用于 'authorization_code' 认证类型")
|
password: Optional[str] = Field(
|
||||||
grant_type: str = Field(..., description="认证类型,如 'password', 'authorization_code', 'client_credentials'")
|
None, description="用户密码,适用于 'password' 认证类型"
|
||||||
|
)
|
||||||
|
mfa_code: Optional[str] = Field(
|
||||||
|
None, description="一次性密码,目前仅适用于 'password' 认证类型"
|
||||||
|
)
|
||||||
|
code: Optional[str] = Field(
|
||||||
|
None, description="授权码,适用于 'authorization_code' 认证类型"
|
||||||
|
)
|
||||||
|
grant_type: str = Field(
|
||||||
|
...,
|
||||||
|
description="认证类型,如 'password', 'authorization_code', 'client_credentials'",
|
||||||
|
)
|
||||||
# scope: List[str] = Field(default_factory=list, description="权限范围,如 ['read', 'write']")
|
# scope: List[str] = Field(default_factory=list, description="权限范围,如 ['read', 'write']")
|
||||||
|
|
||||||
# 输出参数
|
# 输出参数
|
||||||
@@ -87,7 +105,7 @@ class AuthCredentials(ChainEventData):
|
|||||||
channel: Optional[str] = Field(default=None, description="认证渠道")
|
channel: Optional[str] = Field(default=None, description="认证渠道")
|
||||||
service: Optional[str] = Field(default=None, description="服务名称")
|
service: Optional[str] = Field(default=None, description="服务名称")
|
||||||
|
|
||||||
@model_validator(mode='before')
|
@model_validator(mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
def check_fields_based_on_grant_type(cls, values): # noqa
|
def check_fields_based_on_grant_type(cls, values): # noqa
|
||||||
grant_type = values.get("grant_type")
|
grant_type = values.get("grant_type")
|
||||||
@@ -97,7 +115,9 @@ class AuthCredentials(ChainEventData):
|
|||||||
|
|
||||||
if grant_type == "password":
|
if grant_type == "password":
|
||||||
if not values.get("username") or not values.get("password"):
|
if not values.get("username") or not values.get("password"):
|
||||||
raise ValueError("username and password are required for grant_type 'password'")
|
raise ValueError(
|
||||||
|
"username and password are required for grant_type 'password'"
|
||||||
|
)
|
||||||
|
|
||||||
elif grant_type == "authorization_code":
|
elif grant_type == "authorization_code":
|
||||||
if not values.get("code"):
|
if not values.get("code"):
|
||||||
@@ -122,11 +142,15 @@ class AuthInterceptCredentials(ChainEventData):
|
|||||||
source (str): 拦截源,默认值为 "未知拦截源"
|
source (str): 拦截源,默认值为 "未知拦截源"
|
||||||
cancel (bool): 是否取消认证,默认值为 False
|
cancel (bool): 是否取消认证,默认值为 False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 输入参数
|
# 输入参数
|
||||||
username: Optional[str] = Field(..., description="用户名")
|
username: Optional[str] = Field(..., description="用户名")
|
||||||
channel: str = Field(..., description="认证渠道")
|
channel: str = Field(..., description="认证渠道")
|
||||||
service: str = Field(..., description="服务名称")
|
service: str = Field(..., description="服务名称")
|
||||||
status: str = Field(..., description="认证状态, 包含 'triggered' 表示认证触发,'completed' 表示认证成功")
|
status: str = Field(
|
||||||
|
...,
|
||||||
|
description="认证状态, 包含 'triggered' 表示认证触发,'completed' 表示认证成功",
|
||||||
|
)
|
||||||
token: Optional[str] = Field(default=None, description="认证令牌")
|
token: Optional[str] = Field(default=None, description="认证令牌")
|
||||||
|
|
||||||
# 输出参数
|
# 输出参数
|
||||||
@@ -148,6 +172,7 @@ class CommandRegisterEventData(ChainEventData):
|
|||||||
source (str): 拦截源,默认值为 "未知拦截源"
|
source (str): 拦截源,默认值为 "未知拦截源"
|
||||||
cancel (bool): 是否取消认证,默认值为 False
|
cancel (bool): 是否取消认证,默认值为 False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 输入参数
|
# 输入参数
|
||||||
commands: Dict[str, dict] = Field(..., description="菜单命令")
|
commands: Dict[str, dict] = Field(..., description="菜单命令")
|
||||||
origin: str = Field(..., description="事件源")
|
origin: str = Field(..., description="事件源")
|
||||||
@@ -169,18 +194,25 @@ class TransferRenameEventData(ChainEventData):
|
|||||||
render_str (str): 渲染生成的字符串
|
render_str (str): 渲染生成的字符串
|
||||||
path (Optional[Path]): 当前文件的目标路径
|
path (Optional[Path]): 当前文件的目标路径
|
||||||
source_path (Optional[str]): 源文件路径,即待整理的文件路径
|
source_path (Optional[str]): 源文件路径,即待整理的文件路径
|
||||||
|
source_item (Optional[FileItem]): 源文件信息,即待整理的文件信息
|
||||||
|
|
||||||
# 输出参数
|
# 输出参数
|
||||||
updated (bool): 是否已更新,默认值为 False
|
updated (bool): 是否已更新,默认值为 False
|
||||||
updated_str (str): 更新后的字符串
|
updated_str (str): 更新后的字符串
|
||||||
source (str): 拦截源,默认值为 "未知拦截源"
|
source (str): 拦截源,默认值为 "未知拦截源"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 输入参数
|
# 输入参数
|
||||||
template_string: str = Field(..., description="模板字符串")
|
template_string: str = Field(..., description="模板字符串")
|
||||||
rename_dict: Dict[str, Any] = Field(..., description="渲染上下文")
|
rename_dict: Dict[str, Any] = Field(..., description="渲染上下文")
|
||||||
path: Optional[Path] = Field(None, description="文件的目标路径")
|
path: Optional[Path] = Field(None, description="文件的目标路径")
|
||||||
render_str: str = Field(..., description="渲染生成的字符串")
|
render_str: str = Field(..., description="渲染生成的字符串")
|
||||||
source_path: Optional[str] = Field(None, description="源文件路径,即待整理的文件路径")
|
source_path: Optional[str] = Field(
|
||||||
|
None, description="源文件路径,即待整理的文件路径"
|
||||||
|
)
|
||||||
|
source_item: Optional[FileItem] = Field(
|
||||||
|
None, description="源文件信息,即待整理的文件信息"
|
||||||
|
)
|
||||||
|
|
||||||
# 输出参数
|
# 输出参数
|
||||||
updated: bool = Field(default=False, description="是否已更新")
|
updated: bool = Field(default=False, description="是否已更新")
|
||||||
@@ -202,6 +234,7 @@ class ResourceSelectionEventData(BaseModel):
|
|||||||
updated_contexts (Optional[List[Context]]): 已更新的资源上下文列表,默认值为 None
|
updated_contexts (Optional[List[Context]]): 已更新的资源上下文列表,默认值为 None
|
||||||
source (str): 更新源,默认值为 "未知更新源"
|
source (str): 更新源,默认值为 "未知更新源"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 输入参数
|
# 输入参数
|
||||||
contexts: Any = Field(None, description="待选择的资源上下文列表")
|
contexts: Any = Field(None, description="待选择的资源上下文列表")
|
||||||
downloader: Optional[str] = Field(None, description="下载器")
|
downloader: Optional[str] = Field(None, description="下载器")
|
||||||
@@ -209,7 +242,9 @@ class ResourceSelectionEventData(BaseModel):
|
|||||||
|
|
||||||
# 输出参数
|
# 输出参数
|
||||||
updated: bool = Field(default=False, description="是否已更新")
|
updated: bool = Field(default=False, description="是否已更新")
|
||||||
updated_contexts: Optional[List[Any]] = Field(default=None, description="已更新的资源上下文列表")
|
updated_contexts: Optional[List[Any]] = Field(
|
||||||
|
default=None, description="已更新的资源上下文列表"
|
||||||
|
)
|
||||||
source: Optional[str] = Field(default="未知拦截源", description="拦截源")
|
source: Optional[str] = Field(default="未知拦截源", description="拦截源")
|
||||||
|
|
||||||
|
|
||||||
@@ -231,6 +266,7 @@ class ResourceDownloadEventData(ChainEventData):
|
|||||||
source (str): 拦截源,默认值为 "未知拦截源"
|
source (str): 拦截源,默认值为 "未知拦截源"
|
||||||
reason (str): 拦截原因,描述拦截的具体原因
|
reason (str): 拦截原因,描述拦截的具体原因
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 输入参数
|
# 输入参数
|
||||||
context: Any = Field(None, description="当前资源上下文")
|
context: Any = Field(None, description="当前资源上下文")
|
||||||
episodes: Optional[Set[int]] = Field(None, description="需要下载的集数")
|
episodes: Optional[Set[int]] = Field(None, description="需要下载的集数")
|
||||||
@@ -262,6 +298,7 @@ class TransferInterceptEventData(ChainEventData):
|
|||||||
source (str): 拦截源,默认值为 "未知拦截源"
|
source (str): 拦截源,默认值为 "未知拦截源"
|
||||||
reason (str): 拦截原因,描述拦截的具体原因
|
reason (str): 拦截原因,描述拦截的具体原因
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 输入参数
|
# 输入参数
|
||||||
fileitem: FileItem = Field(..., description="源文件")
|
fileitem: FileItem = Field(..., description="源文件")
|
||||||
mediainfo: Any = Field(..., description="媒体信息")
|
mediainfo: Any = Field(..., description="媒体信息")
|
||||||
@@ -280,12 +317,17 @@ class DiscoverMediaSource(BaseModel):
|
|||||||
"""
|
"""
|
||||||
探索媒体数据源的基类
|
探索媒体数据源的基类
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = Field(..., description="数据源名称")
|
name: str = Field(..., description="数据源名称")
|
||||||
mediaid_prefix: str = Field(..., description="媒体ID的前缀,不含:")
|
mediaid_prefix: str = Field(..., description="媒体ID的前缀,不含:")
|
||||||
api_path: str = Field(..., description="媒体数据源API地址")
|
api_path: str = Field(..., description="媒体数据源API地址")
|
||||||
filter_params: Optional[Dict[str, Any]] = Field(default=None, description="过滤参数")
|
filter_params: Optional[Dict[str, Any]] = Field(
|
||||||
|
default=None, description="过滤参数"
|
||||||
|
)
|
||||||
filter_ui: Optional[List[dict]] = Field(default=[], description="过滤参数UI配置")
|
filter_ui: Optional[List[dict]] = Field(default=[], description="过滤参数UI配置")
|
||||||
depends: Optional[Dict[str, list]] = Field(default=None, description="UI依赖关系字典")
|
depends: Optional[Dict[str, list]] = Field(
|
||||||
|
default=None, description="UI依赖关系字典"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DiscoverSourceEventData(ChainEventData):
|
class DiscoverSourceEventData(ChainEventData):
|
||||||
@@ -296,14 +338,18 @@ class DiscoverSourceEventData(ChainEventData):
|
|||||||
# 输出参数
|
# 输出参数
|
||||||
extra_sources (List[DiscoverMediaSource]): 额外媒体数据源
|
extra_sources (List[DiscoverMediaSource]): 额外媒体数据源
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 输出参数
|
# 输出参数
|
||||||
extra_sources: List[DiscoverMediaSource] = Field(default_factory=list, description="额外媒体数据源")
|
extra_sources: List[DiscoverMediaSource] = Field(
|
||||||
|
default_factory=list, description="额外媒体数据源"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RecommendMediaSource(BaseModel):
|
class RecommendMediaSource(BaseModel):
|
||||||
"""
|
"""
|
||||||
推荐媒体数据源的基类
|
推荐媒体数据源的基类
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str = Field(..., description="数据源名称")
|
name: str = Field(..., description="数据源名称")
|
||||||
api_path: str = Field(..., description="媒体数据源API地址")
|
api_path: str = Field(..., description="媒体数据源API地址")
|
||||||
type: str = Field(..., description="类型")
|
type: str = Field(..., description="类型")
|
||||||
@@ -317,8 +363,11 @@ class RecommendSourceEventData(ChainEventData):
|
|||||||
# 输出参数
|
# 输出参数
|
||||||
extra_sources (List[RecommendMediaSource]): 额外媒体数据源
|
extra_sources (List[RecommendMediaSource]): 额外媒体数据源
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 输出参数
|
# 输出参数
|
||||||
extra_sources: List[RecommendMediaSource] = Field(default_factory=list, description="额外媒体数据源")
|
extra_sources: List[RecommendMediaSource] = Field(
|
||||||
|
default_factory=list, description="额外媒体数据源"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MediaRecognizeConvertEventData(ChainEventData):
|
class MediaRecognizeConvertEventData(ChainEventData):
|
||||||
@@ -333,12 +382,15 @@ class MediaRecognizeConvertEventData(ChainEventData):
|
|||||||
# 输出参数
|
# 输出参数
|
||||||
media_dict (dict): TheMovieDb/豆瓣的媒体数据
|
media_dict (dict): TheMovieDb/豆瓣的媒体数据
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 输入参数
|
# 输入参数
|
||||||
mediaid: str = Field(..., description="媒体ID")
|
mediaid: str = Field(..., description="媒体ID")
|
||||||
convert_type: str = Field(..., description="转换类型(themoviedb/douban)")
|
convert_type: str = Field(..., description="转换类型(themoviedb/douban)")
|
||||||
|
|
||||||
# 输出参数
|
# 输出参数
|
||||||
media_dict: dict = Field(default_factory=dict, description="转换后的媒体信息(TheMovieDb/豆瓣)")
|
media_dict: dict = Field(
|
||||||
|
default_factory=dict, description="转换后的媒体信息(TheMovieDb/豆瓣)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StorageOperSelectionEventData(ChainEventData):
|
class StorageOperSelectionEventData(ChainEventData):
|
||||||
@@ -352,6 +404,7 @@ class StorageOperSelectionEventData(ChainEventData):
|
|||||||
# 输出参数
|
# 输出参数
|
||||||
storage_oper (Callable): 存储操作对象
|
storage_oper (Callable): 存储操作对象
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 输入参数
|
# 输入参数
|
||||||
storage: Optional[str] = Field(default=None, description="存储类型")
|
storage: Optional[str] = Field(default=None, description="存储类型")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user