mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-04 23:17:20 +08:00
fix 文件整理
This commit is contained in:
@@ -1,8 +1,9 @@
|
|||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any, Optional
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends
|
||||||
|
from pydantic import BaseModel
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
@@ -19,6 +20,25 @@ from app.schemas import MediaType, FileItem
|
|||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
class ManualTransferItem(BaseModel):
|
||||||
|
fileitem: FileItem = None,
|
||||||
|
logid: Optional[int] = None,
|
||||||
|
target_storage: Optional[str] = None,
|
||||||
|
target_path: Optional[str] = None,
|
||||||
|
tmdbid: Optional[int] = None,
|
||||||
|
doubanid: Optional[str] = None,
|
||||||
|
type_name: Optional[str] = None,
|
||||||
|
season: Optional[int] = None,
|
||||||
|
transfer_type: Optional[str] = None,
|
||||||
|
episode_format: Optional[str] = None,
|
||||||
|
episode_detail: Optional[str] = None,
|
||||||
|
episode_part: Optional[str] = None,
|
||||||
|
episode_offset: Optional[int] = 0,
|
||||||
|
min_filesize: Optional[int] = 0,
|
||||||
|
scrape: bool = False,
|
||||||
|
from_history: bool = False
|
||||||
|
|
||||||
|
|
||||||
@router.get("/name", summary="查询整理后的名称", response_model=schemas.Response)
|
@router.get("/name", summary="查询整理后的名称", response_model=schemas.Response)
|
||||||
def query_name(path: str, filetype: str,
|
def query_name(path: str, filetype: str,
|
||||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||||
@@ -49,52 +69,22 @@ def query_name(path: str, filetype: str,
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/manual", summary="手动转移", response_model=schemas.Response)
|
@router.post("/manual", summary="手动转移", response_model=schemas.Response)
|
||||||
def manual_transfer(fileitem: FileItem = None,
|
def manual_transfer(transer_item: ManualTransferItem,
|
||||||
logid: int = None,
|
|
||||||
target_storage: str = None,
|
|
||||||
target_path: str = None,
|
|
||||||
tmdbid: int = None,
|
|
||||||
doubanid: str = None,
|
|
||||||
type_name: str = None,
|
|
||||||
season: int = None,
|
|
||||||
transfer_type: str = None,
|
|
||||||
episode_format: str = None,
|
|
||||||
episode_detail: str = None,
|
|
||||||
episode_part: str = None,
|
|
||||||
episode_offset: int = 0,
|
|
||||||
min_filesize: int = 0,
|
|
||||||
scrape: bool = None,
|
|
||||||
from_history: bool = None,
|
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
_: schemas.TokenPayload = Depends(get_current_active_superuser)) -> Any:
|
_: schemas.TokenPayload = Depends(get_current_active_superuser)) -> Any:
|
||||||
"""
|
"""
|
||||||
手动转移,文件或历史记录,支持自定义剧集识别格式
|
手动转移,文件或历史记录,支持自定义剧集识别格式
|
||||||
:param fileitem: 文件信息
|
:param transer_item: 手工整理项
|
||||||
:param logid: 转移历史记录ID
|
|
||||||
:param target_storage: 目标存储
|
|
||||||
:param target_path: 目标路径
|
|
||||||
:param type_name: 媒体类型、电影/电视剧
|
|
||||||
:param tmdbid: tmdbid
|
|
||||||
:param doubanid: 豆瓣ID
|
|
||||||
:param season: 剧集季号
|
|
||||||
:param transfer_type: 转移类型,move/copy 等
|
|
||||||
:param episode_format: 剧集识别格式
|
|
||||||
:param episode_detail: 剧集识别详细信息
|
|
||||||
:param episode_part: 剧集识别分集信息
|
|
||||||
:param episode_offset: 剧集识别偏移量
|
|
||||||
:param min_filesize: 最小文件大小(MB)
|
|
||||||
:param scrape: 是否刮削元数据
|
|
||||||
:param from_history: 从历史记录中获取tmdbid、season、episode_detail等信息
|
|
||||||
:param db: 数据库
|
:param db: 数据库
|
||||||
:param _: Token校验
|
:param _: Token校验
|
||||||
"""
|
"""
|
||||||
force = False
|
force = False
|
||||||
target_path = Path(target_path) if target_path else None
|
target_path = Path(transer_item.target_path) if transer_item.target_path else None
|
||||||
if logid:
|
if transer_item.logid:
|
||||||
# 查询历史记录
|
# 查询历史记录
|
||||||
history: TransferHistory = TransferHistory.get(db, logid)
|
history: TransferHistory = TransferHistory.get(db, transer_item.logid)
|
||||||
if not history:
|
if not history:
|
||||||
return schemas.Response(success=False, message=f"历史记录不存在,ID:{logid}")
|
return schemas.Response(success=False, message=f"历史记录不存在,ID:{transer_item.logid}")
|
||||||
# 强制转移
|
# 强制转移
|
||||||
force = True
|
force = True
|
||||||
if history.status and ("move" in history.mode):
|
if history.status and ("move" in history.mode):
|
||||||
@@ -110,11 +100,11 @@ def manual_transfer(fileitem: FileItem = None,
|
|||||||
StorageChain().delete_file(dest_fileitem)
|
StorageChain().delete_file(dest_fileitem)
|
||||||
|
|
||||||
# 从历史数据获取信息
|
# 从历史数据获取信息
|
||||||
if from_history:
|
if transer_item.from_history:
|
||||||
type_name = history.type if history.type else type_name
|
transer_item.type_name = history.type if history.type else transer_item.type_name
|
||||||
tmdbid = int(history.tmdbid) if history.tmdbid else tmdbid
|
transer_item.tmdbid = int(history.tmdbid) if history.tmdbid else transer_item.tmdbid
|
||||||
doubanid = str(history.doubanid) if history.doubanid else doubanid
|
transer_item.doubanid = str(history.doubanid) if history.doubanid else transer_item.doubanid
|
||||||
season = int(str(history.seasons).replace("S", "")) if history.seasons else season
|
transer_item.season = int(str(history.seasons).replace("S", "")) if history.seasons else transer_item.season
|
||||||
if history.episodes:
|
if history.episodes:
|
||||||
if "-" in str(history.episodes):
|
if "-" in str(history.episodes):
|
||||||
# E01-E03多集合并
|
# E01-E03多集合并
|
||||||
@@ -122,40 +112,41 @@ def manual_transfer(fileitem: FileItem = None,
|
|||||||
episode_list: list[int] = []
|
episode_list: list[int] = []
|
||||||
for i in range(int(episode_start.replace("E", "")), int(episode_end.replace("E", "")) + 1):
|
for i in range(int(episode_start.replace("E", "")), int(episode_end.replace("E", "")) + 1):
|
||||||
episode_list.append(i)
|
episode_list.append(i)
|
||||||
episode_detail = ",".join(str(e) for e in episode_list)
|
transer_item.episode_detail = ",".join(str(e) for e in episode_list)
|
||||||
else:
|
else:
|
||||||
# E01单集
|
# E01单集
|
||||||
episode_detail = str(history.episodes).replace("E", "")
|
transer_item.episode_detail = str(history.episodes).replace("E", "")
|
||||||
|
|
||||||
elif fileitem:
|
elif transer_item.fileitem:
|
||||||
src_fileitem = fileitem
|
src_fileitem = transer_item.fileitem
|
||||||
else:
|
else:
|
||||||
return schemas.Response(success=False, message=f"缺少参数")
|
return schemas.Response(success=False, message=f"缺少参数")
|
||||||
|
|
||||||
# 类型
|
# 类型
|
||||||
mtype = MediaType(type_name) if type_name else None
|
mtype = MediaType(transer_item.type_name) if transer_item.type_name else None
|
||||||
# 自定义格式
|
# 自定义格式
|
||||||
epformat = None
|
epformat = None
|
||||||
if episode_offset or episode_part or episode_detail or episode_format:
|
if transer_item.episode_offset or transer_item.episode_part \
|
||||||
|
or transer_item.episode_detail or transer_item.episode_format:
|
||||||
epformat = schemas.EpisodeFormat(
|
epformat = schemas.EpisodeFormat(
|
||||||
format=episode_format,
|
format=transer_item.episode_format,
|
||||||
detail=episode_detail,
|
detail=transer_item.episode_detail,
|
||||||
part=episode_part,
|
part=transer_item.episode_part,
|
||||||
offset=episode_offset,
|
offset=transer_item.episode_offset,
|
||||||
)
|
)
|
||||||
# 开始转移
|
# 开始转移
|
||||||
state, errormsg = TransferChain().manual_transfer(
|
state, errormsg = TransferChain().manual_transfer(
|
||||||
fileitem=src_fileitem,
|
fileitem=src_fileitem,
|
||||||
target_storage=target_storage,
|
target_storage=transer_item.target_storage,
|
||||||
target_path=target_path,
|
target_path=target_path,
|
||||||
tmdbid=tmdbid,
|
tmdbid=transer_item.tmdbid,
|
||||||
doubanid=doubanid,
|
doubanid=transer_item.doubanid,
|
||||||
mtype=mtype,
|
mtype=mtype,
|
||||||
season=season,
|
season=transer_item.season,
|
||||||
transfer_type=transfer_type,
|
transfer_type=transer_item.transfer_type,
|
||||||
epformat=epformat,
|
epformat=epformat,
|
||||||
min_filesize=min_filesize,
|
min_filesize=transer_item.min_filesize,
|
||||||
scrape=scrape,
|
scrape=transer_item.scrape,
|
||||||
force=force
|
force=force
|
||||||
)
|
)
|
||||||
# 失败
|
# 失败
|
||||||
|
|||||||
+62
-57
@@ -32,7 +32,7 @@ lock = threading.Lock()
|
|||||||
|
|
||||||
class TransferChain(ChainBase):
|
class TransferChain(ChainBase):
|
||||||
"""
|
"""
|
||||||
文件转移处理链
|
文件整理处理链
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -45,7 +45,7 @@ class TransferChain(ChainBase):
|
|||||||
self.storagechain = StorageChain()
|
self.storagechain = StorageChain()
|
||||||
self.systemconfig = SystemConfigOper()
|
self.systemconfig = SystemConfigOper()
|
||||||
self.directoryhelper = DirectoryHelper()
|
self.directoryhelper = DirectoryHelper()
|
||||||
self.all_exts = settings.RMT_MEDIAEXT + settings.RMT_SUBEXT + settings.RMT_AUDIO_TRACK_EXT
|
self.all_exts = settings.RMT_MEDIAEXT
|
||||||
|
|
||||||
def recommend_name(self, meta: MetaBase, mediainfo: MediaInfo) -> Optional[str]:
|
def recommend_name(self, meta: MetaBase, mediainfo: MediaInfo) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
@@ -58,7 +58,7 @@ class TransferChain(ChainBase):
|
|||||||
|
|
||||||
def process(self) -> bool:
|
def process(self) -> bool:
|
||||||
"""
|
"""
|
||||||
获取下载器中的种子列表,并执行转移
|
获取下载器中的种子列表,并执行整理
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 全局锁,避免重复处理
|
# 全局锁,避免重复处理
|
||||||
@@ -122,7 +122,7 @@ class TransferChain(ChainBase):
|
|||||||
# 非MoviePilot下载的任务,按文件识别
|
# 非MoviePilot下载的任务,按文件识别
|
||||||
mediainfo = None
|
mediainfo = None
|
||||||
|
|
||||||
# 执行转移
|
# 执行整理
|
||||||
self.__do_transfer(
|
self.__do_transfer(
|
||||||
fileitem=FileItem(
|
fileitem=FileItem(
|
||||||
storage="local",
|
storage="local",
|
||||||
@@ -152,23 +152,21 @@ class TransferChain(ChainBase):
|
|||||||
min_filesize: int = 0, scrape: bool = None,
|
min_filesize: int = 0, scrape: bool = None,
|
||||||
force: bool = False) -> Tuple[bool, str]:
|
force: bool = False) -> Tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
执行一个复杂目录的转移操作
|
执行一个复杂目录的整理操作
|
||||||
:param fileitem: 文件项
|
:param fileitem: 文件项
|
||||||
:param meta: 元数据
|
:param meta: 元数据
|
||||||
:param mediainfo: 媒体信息
|
:param mediainfo: 媒体信息
|
||||||
:param download_hash: 下载记录hash
|
:param download_hash: 下载记录hash
|
||||||
:param target_storage: 目标存储器
|
:param target_storage: 目标存储器
|
||||||
:param target_path: 目标路径
|
:param target_path: 目标路径
|
||||||
:param transfer_type: 转移类型
|
:param transfer_type: 整理类型
|
||||||
:param season: 季
|
:param season: 季
|
||||||
:param epformat: 剧集格式
|
:param epformat: 剧集格式
|
||||||
:param min_filesize: 最小文件大小(MB)
|
:param min_filesize: 最小文件大小(MB)
|
||||||
:param scrape: 是否刮削元数据
|
:param scrape: 是否刮削元数据
|
||||||
:param force: 是否强制转移
|
:param force: 是否强制整理
|
||||||
返回:成功标识,错误信息
|
返回:成功标识,错误信息
|
||||||
"""
|
"""
|
||||||
if not transfer_type:
|
|
||||||
transfer_type = 'link'
|
|
||||||
|
|
||||||
# 自定义格式
|
# 自定义格式
|
||||||
formaterHandler = FormatParser(eformat=epformat.format,
|
formaterHandler = FormatParser(eformat=epformat.format,
|
||||||
@@ -191,19 +189,19 @@ class TransferChain(ChainBase):
|
|||||||
# 跳过数量
|
# 跳过数量
|
||||||
skip_num = 0
|
skip_num = 0
|
||||||
|
|
||||||
# 获取待转移路径清单
|
# 获取待整理路径清单
|
||||||
trans_items = self.__get_trans_fileitems(fileitem)
|
trans_items = self.__get_trans_fileitems(fileitem)
|
||||||
# 总文件数
|
# 总文件数
|
||||||
total_num = len(trans_items)
|
total_num = len(trans_items)
|
||||||
self.progress.update(value=0,
|
self.progress.update(value=0,
|
||||||
text=f"开始转移 {fileitem.path},共 {total_num} 个文件或子目录 ...",
|
text=f"开始 {fileitem.path},共 {total_num} 个文件或子目录 ...",
|
||||||
key=ProgressKey.FileTransfer)
|
key=ProgressKey.FileTransfer)
|
||||||
|
|
||||||
if not trans_items:
|
if not trans_items:
|
||||||
logger.warn(f"{fileitem.path} 没有找到可转移的媒体文件")
|
logger.warn(f"{fileitem.path} 没有找到可整理的媒体文件")
|
||||||
return False, f"{fileitem.name} 没有找到可转移的媒体文件"
|
return False, f"{fileitem.name} 没有找到可整理的媒体文件"
|
||||||
|
|
||||||
# 处理所有待转移目录或文件,默认一个转移路径或文件只有一个媒体信息
|
# 处理所有待整理目录或文件,默认一个整理路径或文件只有一个媒体信息
|
||||||
for trans_item in trans_items:
|
for trans_item in trans_items:
|
||||||
# 汇总季集清单
|
# 汇总季集清单
|
||||||
season_episodes: Dict[Tuple, List[int]] = {}
|
season_episodes: Dict[Tuple, List[int]] = {}
|
||||||
@@ -211,21 +209,17 @@ class TransferChain(ChainBase):
|
|||||||
metas: Dict[Tuple, MetaBase] = {}
|
metas: Dict[Tuple, MetaBase] = {}
|
||||||
# 汇总媒体信息
|
# 汇总媒体信息
|
||||||
medias: Dict[Tuple, MediaInfo] = {}
|
medias: Dict[Tuple, MediaInfo] = {}
|
||||||
# 汇总转移信息
|
# 汇总整理信息
|
||||||
transfers: Dict[Tuple, TransferInfo] = {}
|
transfers: Dict[Tuple, TransferInfo] = {}
|
||||||
|
|
||||||
item_path = Path(trans_item.path)
|
item_path = Path(trans_item.path)
|
||||||
# 如果是目录且不是⼀蓝光原盘,获取所有文件并转移
|
# 如果是目录且不是⼀蓝光原盘,获取所有文件并整理
|
||||||
if (trans_item.type == "dir"
|
if (trans_item.type == "dir"
|
||||||
and not (trans_item.storage == "local" and not SystemUtils.is_bluray_dir(item_path))):
|
and not (trans_item.storage == "local" and SystemUtils.is_bluray_dir(item_path))):
|
||||||
# 遍历获取下载目录所有文件(递归)
|
# 遍历获取下载目录所有文件(递归)
|
||||||
file_items = self.storagechain.list_files(trans_item, recursion=True)
|
file_items = self.storagechain.list_files(trans_item, recursion=True)
|
||||||
if not file_items:
|
if not file_items:
|
||||||
continue
|
continue
|
||||||
# 过滤后缀和大小
|
|
||||||
file_items = [f for f in file_items
|
|
||||||
if f.extension and (f".{f.extension.lower()}" in self.all_exts
|
|
||||||
and (not min_filesize or f.size > min_filesize * 1024 * 1024))]
|
|
||||||
else:
|
else:
|
||||||
# 文件或蓝光目录
|
# 文件或蓝光目录
|
||||||
file_items = [trans_item]
|
file_items = [trans_item]
|
||||||
@@ -234,7 +228,18 @@ class TransferChain(ChainBase):
|
|||||||
# 有集自定义格式,过滤文件
|
# 有集自定义格式,过滤文件
|
||||||
file_items = [f for f in file_items if formaterHandler.match(f.name)]
|
file_items = [f for f in file_items if formaterHandler.match(f.name)]
|
||||||
|
|
||||||
# 转移所有文件
|
# 过滤后缀和大小
|
||||||
|
file_items = [f for f in file_items
|
||||||
|
if f.extension and (f".{f.extension.lower()}" in self.all_exts
|
||||||
|
and (not min_filesize or f.size > min_filesize * 1024 * 1024))]
|
||||||
|
|
||||||
|
if not file_items:
|
||||||
|
logger.warn(f"{fileitem.path} 没有找到可整理的媒体文件")
|
||||||
|
return False, f"{fileitem.name} 没有找到可整理的媒体文件"
|
||||||
|
|
||||||
|
logger.info(f"正在整理 {len(file_items)} 个文件...")
|
||||||
|
|
||||||
|
# 整理所有文件
|
||||||
for file_item in file_items:
|
for file_item in file_items:
|
||||||
file_path = Path(file_item.path)
|
file_path = Path(file_item.path)
|
||||||
# 回收站及隐藏的文件不处理
|
# 回收站及隐藏的文件不处理
|
||||||
@@ -265,11 +270,11 @@ class TransferChain(ChainBase):
|
|||||||
skip_num += 1
|
skip_num += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 转移成功的不再处理
|
# 整理成功的不再处理
|
||||||
if not force:
|
if not force:
|
||||||
transferd = self.transferhis.get_by_src(file_item.path)
|
transferd = self.transferhis.get_by_src(file_item.path)
|
||||||
if transferd and transferd.status:
|
if transferd and transferd.status:
|
||||||
logger.info(f"{file_item.path} 已成功转移过,如需重新处理,请删除历史记录。")
|
logger.info(f"{file_item.path} 已成功整理过,如需重新处理,请删除历史记录。")
|
||||||
# 计数
|
# 计数
|
||||||
processed_num += 1
|
processed_num += 1
|
||||||
skip_num += 1
|
skip_num += 1
|
||||||
@@ -277,7 +282,7 @@ class TransferChain(ChainBase):
|
|||||||
|
|
||||||
# 更新进度
|
# 更新进度
|
||||||
self.progress.update(value=processed_num / total_num * 100,
|
self.progress.update(value=processed_num / total_num * 100,
|
||||||
text=f"正在转移 ({processed_num + 1}/{total_num}){file_item.name} ...",
|
text=f"正在整理 ({processed_num + 1}/{total_num}){file_item.name} ...",
|
||||||
key=ProgressKey.FileTransfer)
|
key=ProgressKey.FileTransfer)
|
||||||
|
|
||||||
if not meta:
|
if not meta:
|
||||||
@@ -316,7 +321,7 @@ class TransferChain(ChainBase):
|
|||||||
|
|
||||||
if not file_mediainfo:
|
if not file_mediainfo:
|
||||||
logger.warn(f'{file_path} 未识别到媒体信息')
|
logger.warn(f'{file_path} 未识别到媒体信息')
|
||||||
# 新增转移失败历史记录
|
# 新增整理失败历史记录
|
||||||
his = self.transferhis.add_fail(
|
his = self.transferhis.add_fail(
|
||||||
fileitem=file_item,
|
fileitem=file_item,
|
||||||
mode=transfer_type,
|
mode=transfer_type,
|
||||||
@@ -326,7 +331,7 @@ class TransferChain(ChainBase):
|
|||||||
self.post_message(Notification(
|
self.post_message(Notification(
|
||||||
mtype=NotificationType.Manual,
|
mtype=NotificationType.Manual,
|
||||||
title=f"{file_path.name} 未识别到媒体信息,无法入库!",
|
title=f"{file_path.name} 未识别到媒体信息,无法入库!",
|
||||||
text=f"回复:```\n/redo {his.id} [tmdbid]|[类型]\n``` 手动识别转移。",
|
text=f"回复:```\n/redo {his.id} [tmdbid]|[类型]\n``` 手动识别整理。",
|
||||||
link=settings.MP_DOMAIN('#/history')
|
link=settings.MP_DOMAIN('#/history')
|
||||||
))
|
))
|
||||||
# 计数
|
# 计数
|
||||||
@@ -361,7 +366,7 @@ class TransferChain(ChainBase):
|
|||||||
if download_file:
|
if download_file:
|
||||||
download_hash = download_file.download_hash
|
download_hash = download_file.download_hash
|
||||||
|
|
||||||
# 执行转移
|
# 执行整理
|
||||||
transferinfo: TransferInfo = self.transfer(fileitem=file_item,
|
transferinfo: TransferInfo = self.transfer(fileitem=file_item,
|
||||||
meta=file_meta,
|
meta=file_meta,
|
||||||
mediainfo=file_mediainfo,
|
mediainfo=file_mediainfo,
|
||||||
@@ -371,13 +376,13 @@ class TransferChain(ChainBase):
|
|||||||
episodes_info=episodes_info,
|
episodes_info=episodes_info,
|
||||||
scrape=scrape)
|
scrape=scrape)
|
||||||
if not transferinfo:
|
if not transferinfo:
|
||||||
logger.error("文件转移模块运行失败")
|
logger.error("文件整理模块运行失败")
|
||||||
return False, "文件转移模块运行失败"
|
return False, "文件整理模块运行失败"
|
||||||
if not transferinfo.success:
|
if not transferinfo.success:
|
||||||
# 转移失败
|
# 整理失败
|
||||||
logger.warn(f"{file_path.name} 入库失败:{transferinfo.message}")
|
logger.warn(f"{file_path.name} 入库失败:{transferinfo.message}")
|
||||||
err_msgs.append(f"{file_path.name} {transferinfo.message}")
|
err_msgs.append(f"{file_path.name} {transferinfo.message}")
|
||||||
# 新增转移失败历史记录
|
# 新增整理失败历史记录
|
||||||
self.transferhis.add_fail(
|
self.transferhis.add_fail(
|
||||||
fileitem=file_item,
|
fileitem=file_item,
|
||||||
mode=transfer_type,
|
mode=transfer_type,
|
||||||
@@ -410,17 +415,17 @@ class TransferChain(ChainBase):
|
|||||||
else:
|
else:
|
||||||
# 合并季集清单
|
# 合并季集清单
|
||||||
season_episodes[mkey] = list(set(season_episodes[mkey] + file_meta.episode_list))
|
season_episodes[mkey] = list(set(season_episodes[mkey] + file_meta.episode_list))
|
||||||
# 合并转移数据
|
# 合并整理数据
|
||||||
transfers[mkey].file_count += transferinfo.file_count
|
transfers[mkey].file_count += transferinfo.file_count
|
||||||
transfers[mkey].total_size += transferinfo.total_size
|
transfers[mkey].total_size += transferinfo.total_size
|
||||||
transfers[mkey].file_list.extend(transferinfo.file_list)
|
transfers[mkey].file_list.extend(transferinfo.file_list)
|
||||||
transfers[mkey].file_list_new.extend(transferinfo.file_list_new)
|
transfers[mkey].file_list_new.extend(transferinfo.file_list_new)
|
||||||
transfers[mkey].fail_list.extend(transferinfo.fail_list)
|
transfers[mkey].fail_list.extend(transferinfo.fail_list)
|
||||||
|
|
||||||
# 新增转移成功历史记录
|
# 新增整理成功历史记录
|
||||||
self.transferhis.add_success(
|
self.transferhis.add_success(
|
||||||
fileitem=file_item,
|
fileitem=file_item,
|
||||||
mode=transfer_type,
|
mode=transfer_type or transferinfo.transfer_type,
|
||||||
download_hash=download_hash,
|
download_hash=download_hash,
|
||||||
meta=file_meta,
|
meta=file_meta,
|
||||||
mediainfo=file_mediainfo,
|
mediainfo=file_mediainfo,
|
||||||
@@ -438,11 +443,11 @@ class TransferChain(ChainBase):
|
|||||||
# 更新进度
|
# 更新进度
|
||||||
processed_num += 1
|
processed_num += 1
|
||||||
self.progress.update(value=processed_num / total_num * 100,
|
self.progress.update(value=processed_num / total_num * 100,
|
||||||
text=f"{file_path.name} 转移完成",
|
text=f"{file_path.name} 整理完成",
|
||||||
key=ProgressKey.FileTransfer)
|
key=ProgressKey.FileTransfer)
|
||||||
|
|
||||||
# 目录或文件转移完成
|
# 目录或文件整理完成
|
||||||
self.progress.update(text=f"{trans_item.path} 转移完成,正在执行后续处理 ...",
|
self.progress.update(text=f"{trans_item.path} 整理完成,正在执行后续处理 ...",
|
||||||
key=ProgressKey.FileTransfer)
|
key=ProgressKey.FileTransfer)
|
||||||
|
|
||||||
# 执行后续处理
|
# 执行后续处理
|
||||||
@@ -464,11 +469,11 @@ class TransferChain(ChainBase):
|
|||||||
'transferinfo': transfer_info
|
'transferinfo': transfer_info
|
||||||
})
|
})
|
||||||
# 结束进度
|
# 结束进度
|
||||||
logger.info(f"{fileitem.path} 转移完成,共 {total_num} 个文件,"
|
logger.info(f"{fileitem.path} 整理完成,共 {total_num} 个文件,"
|
||||||
f"失败 {fail_num} 个,跳过 {skip_num} 个")
|
f"失败 {fail_num} 个,跳过 {skip_num} 个")
|
||||||
|
|
||||||
self.progress.update(value=100,
|
self.progress.update(value=100,
|
||||||
text=f"{fileitem.path} 转移完成,共 {total_num} 个文件,"
|
text=f"{fileitem.path} 整理完成,共 {total_num} 个文件,"
|
||||||
f"失败 {fail_num} 个,跳过 {skip_num} 个",
|
f"失败 {fail_num} 个,跳过 {skip_num} 个",
|
||||||
key=ProgressKey.FileTransfer)
|
key=ProgressKey.FileTransfer)
|
||||||
# 结速进度
|
# 结速进度
|
||||||
@@ -478,7 +483,7 @@ class TransferChain(ChainBase):
|
|||||||
|
|
||||||
def __get_trans_fileitems(self, fileitem: FileItem):
|
def __get_trans_fileitems(self, fileitem: FileItem):
|
||||||
"""
|
"""
|
||||||
获取转移目录列表
|
获取整理目录或文件列表
|
||||||
"""
|
"""
|
||||||
|
|
||||||
file_path = Path(fileitem.path)
|
file_path = Path(fileitem.path)
|
||||||
@@ -495,7 +500,7 @@ class TransferChain(ChainBase):
|
|||||||
if fileitem.storage == "local" and SystemUtils.is_bluray_dir(file_path):
|
if fileitem.storage == "local" and SystemUtils.is_bluray_dir(file_path):
|
||||||
return [fileitem]
|
return [fileitem]
|
||||||
|
|
||||||
# 需要转移的文件项列表
|
# 需要整理的文件项列表
|
||||||
trans_items = []
|
trans_items = []
|
||||||
|
|
||||||
# 先检查当前目录的下级目录,以支持合集的情况
|
# 先检查当前目录的下级目录,以支持合集的情况
|
||||||
@@ -511,13 +516,13 @@ class TransferChain(ChainBase):
|
|||||||
trans_items.append(sub_dir)
|
trans_items.append(sub_dir)
|
||||||
|
|
||||||
if not trans_items:
|
if not trans_items:
|
||||||
# 没有有效子目录,直接转移当前目录
|
# 没有有效子目录,直接整理当前目录
|
||||||
trans_items.append(fileitem)
|
trans_items.append(fileitem)
|
||||||
else:
|
else:
|
||||||
# 有子目录时,把当前目录的文件添加到转移任务中
|
# 有子目录时,把当前目录的文件添加到整理任务中
|
||||||
sub_items = self.storagechain.list_files(fileitem)
|
sub_items = self.storagechain.list_files(fileitem)
|
||||||
if sub_items:
|
if sub_items:
|
||||||
sub_files = [f for f in sub_items if f.type == "file" and f".{f.extension.lower()}" in self.all_exts]
|
sub_files = [f for f in sub_items if f.type == "file"]
|
||||||
if sub_files:
|
if sub_files:
|
||||||
trans_items.extend(sub_files)
|
trans_items.extend(sub_files)
|
||||||
|
|
||||||
@@ -525,7 +530,7 @@ class TransferChain(ChainBase):
|
|||||||
|
|
||||||
def remote_transfer(self, arg_str: str, channel: MessageChannel, userid: Union[str, int] = None):
|
def remote_transfer(self, arg_str: str, channel: MessageChannel, userid: Union[str, int] = None):
|
||||||
"""
|
"""
|
||||||
远程重新转移,参数 历史记录ID TMDBID|类型
|
远程重新整理,参数 历史记录ID TMDBID|类型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def args_error():
|
def args_error():
|
||||||
@@ -567,7 +572,7 @@ class TransferChain(ChainBase):
|
|||||||
def __re_transfer(self, logid: int, mtype: MediaType = None,
|
def __re_transfer(self, logid: int, mtype: MediaType = None,
|
||||||
mediaid: str = None) -> Tuple[bool, str]:
|
mediaid: str = None) -> Tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
根据历史记录,重新识别转移,只支持简单条件
|
根据历史记录,重新识别整理,只支持简单条件
|
||||||
:param logid: 历史记录ID
|
:param logid: 历史记录ID
|
||||||
:param mtype: 媒体类型
|
:param mtype: 媒体类型
|
||||||
:param mediaid: TMDB ID/豆瓣ID
|
:param mediaid: TMDB ID/豆瓣ID
|
||||||
@@ -577,7 +582,7 @@ class TransferChain(ChainBase):
|
|||||||
if not history:
|
if not history:
|
||||||
logger.error(f"历史记录不存在,ID:{logid}")
|
logger.error(f"历史记录不存在,ID:{logid}")
|
||||||
return False, "历史记录不存在"
|
return False, "历史记录不存在"
|
||||||
# 按源目录路径重新转移
|
# 按源目录路径重新整理
|
||||||
src_path = Path(history.src)
|
src_path = Path(history.src)
|
||||||
if not src_path.exists():
|
if not src_path.exists():
|
||||||
return False, f"源目录不存在:{src_path}"
|
return False, f"源目录不存在:{src_path}"
|
||||||
@@ -592,7 +597,7 @@ class TransferChain(ChainBase):
|
|||||||
mediainfo = self.mediachain.recognize_by_path(str(src_path))
|
mediainfo = self.mediachain.recognize_by_path(str(src_path))
|
||||||
if not mediainfo:
|
if not mediainfo:
|
||||||
return False, f"未识别到媒体信息,类型:{mtype.value},id:{mediaid}"
|
return False, f"未识别到媒体信息,类型:{mtype.value},id:{mediaid}"
|
||||||
# 重新执行转移
|
# 重新执行整理
|
||||||
logger.info(f"{src_path.name} 识别为:{mediainfo.title_year}")
|
logger.info(f"{src_path.name} 识别为:{mediainfo.title_year}")
|
||||||
|
|
||||||
# 删除旧的已整理文件
|
# 删除旧的已整理文件
|
||||||
@@ -601,7 +606,7 @@ class TransferChain(ChainBase):
|
|||||||
dest_fileitem = FileItem(**json.loads(history.dest_fileitem))
|
dest_fileitem = FileItem(**json.loads(history.dest_fileitem))
|
||||||
self.storagechain.delete_file(dest_fileitem)
|
self.storagechain.delete_file(dest_fileitem)
|
||||||
|
|
||||||
# 强制转移
|
# 强制整理
|
||||||
if history.src_fileitem:
|
if history.src_fileitem:
|
||||||
# 解析源文件对象
|
# 解析源文件对象
|
||||||
fileitem = FileItem(**json.loads(history.src_fileitem))
|
fileitem = FileItem(**json.loads(history.src_fileitem))
|
||||||
@@ -628,7 +633,7 @@ class TransferChain(ChainBase):
|
|||||||
scrape: bool = None,
|
scrape: bool = None,
|
||||||
force: bool = False) -> Tuple[bool, Union[str, list]]:
|
force: bool = False) -> Tuple[bool, Union[str, list]]:
|
||||||
"""
|
"""
|
||||||
手动转移,支持复杂条件,带进度显示
|
手动整理,支持复杂条件,带进度显示
|
||||||
:param fileitem: 文件项
|
:param fileitem: 文件项
|
||||||
:param target_storage: 目标存储
|
:param target_storage: 目标存储
|
||||||
:param target_path: 目标路径
|
:param target_path: 目标路径
|
||||||
@@ -636,13 +641,13 @@ class TransferChain(ChainBase):
|
|||||||
:param doubanid: 豆瓣ID
|
:param doubanid: 豆瓣ID
|
||||||
:param mtype: 媒体类型
|
:param mtype: 媒体类型
|
||||||
:param season: 季度
|
:param season: 季度
|
||||||
:param transfer_type: 转移类型
|
:param transfer_type: 整理类型
|
||||||
:param epformat: 剧集格式
|
:param epformat: 剧集格式
|
||||||
:param min_filesize: 最小文件大小(MB)
|
:param min_filesize: 最小文件大小(MB)
|
||||||
:param scrape: 是否刮削元数据
|
:param scrape: 是否刮削元数据
|
||||||
:param force: 是否强制转移
|
:param force: 是否强制整理
|
||||||
"""
|
"""
|
||||||
logger.info(f"手动转移:{fileitem.path} ...")
|
logger.info(f"手动整理:{fileitem.path} ...")
|
||||||
|
|
||||||
if tmdbid or doubanid:
|
if tmdbid or doubanid:
|
||||||
# 有输入TMDBID时单个识别
|
# 有输入TMDBID时单个识别
|
||||||
@@ -656,9 +661,9 @@ class TransferChain(ChainBase):
|
|||||||
# 开始进度
|
# 开始进度
|
||||||
self.progress.start(ProgressKey.FileTransfer)
|
self.progress.start(ProgressKey.FileTransfer)
|
||||||
self.progress.update(value=0,
|
self.progress.update(value=0,
|
||||||
text=f"开始转移 {fileitem.path} ...",
|
text=f"开始整理 {fileitem.path} ...",
|
||||||
key=ProgressKey.FileTransfer)
|
key=ProgressKey.FileTransfer)
|
||||||
# 开始转移
|
# 开始整理
|
||||||
state, errmsg = self.__do_transfer(
|
state, errmsg = self.__do_transfer(
|
||||||
fileitem=fileitem,
|
fileitem=fileitem,
|
||||||
target_storage=target_storage,
|
target_storage=target_storage,
|
||||||
@@ -675,7 +680,7 @@ class TransferChain(ChainBase):
|
|||||||
return False, errmsg
|
return False, errmsg
|
||||||
|
|
||||||
self.progress.end(ProgressKey.FileTransfer)
|
self.progress.end(ProgressKey.FileTransfer)
|
||||||
logger.info(f"{fileitem.path} 转移完成")
|
logger.info(f"{fileitem.path} 整理完成")
|
||||||
return True, ""
|
return True, ""
|
||||||
else:
|
else:
|
||||||
# 没有输入TMDBID时,按文件识别
|
# 没有输入TMDBID时,按文件识别
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ class TransferHistoryOper(DbOper):
|
|||||||
src_fileitem=json.dumps(fileitem.dict()),
|
src_fileitem=json.dumps(fileitem.dict()),
|
||||||
dest=transferinfo.target_item.path if transferinfo.target_item else None,
|
dest=transferinfo.target_item.path if transferinfo.target_item else None,
|
||||||
dest_storage=transferinfo.target_item.storage if transferinfo.target_item else None,
|
dest_storage=transferinfo.target_item.storage if transferinfo.target_item else None,
|
||||||
dest_fileitem=json.dumps(transferinfo.target_item.dict()),
|
dest_fileitem=json.dumps(transferinfo.target_item.dict()) if transferinfo.target_item else None,
|
||||||
mode=mode,
|
mode=mode,
|
||||||
type=mediainfo.type.value,
|
type=mediainfo.type.value,
|
||||||
category=mediainfo.category,
|
category=mediainfo.category,
|
||||||
|
|||||||
@@ -166,6 +166,7 @@ class FileManagerModule(_ModuleBase):
|
|||||||
result.append(t)
|
result.append(t)
|
||||||
else:
|
else:
|
||||||
result.extend(_items)
|
result.extend(_items)
|
||||||
|
|
||||||
# 返回结果
|
# 返回结果
|
||||||
result = []
|
result = []
|
||||||
__get_files(fileitem, recursion)
|
__get_files(fileitem, recursion)
|
||||||
@@ -292,6 +293,9 @@ class FileManagerModule(_ModuleBase):
|
|||||||
# 目标存储类型
|
# 目标存储类型
|
||||||
if not target_storage:
|
if not target_storage:
|
||||||
target_storage = dir_info.library_storage
|
target_storage = dir_info.library_storage
|
||||||
|
# 整理方式
|
||||||
|
if not transfer_type:
|
||||||
|
transfer_type = dir_info.transfer_type
|
||||||
# 是否需要刮削
|
# 是否需要刮削
|
||||||
if scrape is None:
|
if scrape is None:
|
||||||
need_scrape = dir_info.scraping
|
need_scrape = dir_info.scraping
|
||||||
@@ -692,10 +696,11 @@ class FileManagerModule(_ModuleBase):
|
|||||||
transfer_type=transfer_type)
|
transfer_type=transfer_type)
|
||||||
if new_item:
|
if new_item:
|
||||||
logger.info(f"文件 {fileitem.path} {transfer_type}完成")
|
logger.info(f"文件 {fileitem.path} {transfer_type}完成")
|
||||||
|
return new_item, errmsg
|
||||||
else:
|
else:
|
||||||
logger.error(f"文件{fileitem.path} {transfer_type}失败:{errmsg}")
|
logger.error(f"文件{fileitem.path} {transfer_type}失败:{errmsg}")
|
||||||
|
|
||||||
return target_item, errmsg
|
return None, errmsg
|
||||||
|
|
||||||
def __transfer_dir_files(self, fileitem: FileItem, transfer_type: str,
|
def __transfer_dir_files(self, fileitem: FileItem, transfer_type: str,
|
||||||
target_storage: str, target_path: Path) -> Tuple[Optional[FileItem], str]:
|
target_storage: str, target_path: Path) -> Tuple[Optional[FileItem], str]:
|
||||||
@@ -733,7 +738,7 @@ class FileManagerModule(_ModuleBase):
|
|||||||
if not new_item:
|
if not new_item:
|
||||||
return None, errmsg
|
return None, errmsg
|
||||||
# 返回成功
|
# 返回成功
|
||||||
return FileItem(), ""
|
return storage_oper.get_item(target_path), ""
|
||||||
|
|
||||||
def __transfer_file(self, fileitem: FileItem, target_storage: str, target_file: Path,
|
def __transfer_file(self, fileitem: FileItem, target_storage: str, target_file: Path,
|
||||||
transfer_type: str, over_flag: bool = False) -> Tuple[Optional[FileItem], str]:
|
transfer_type: str, over_flag: bool = False) -> Tuple[Optional[FileItem], str]:
|
||||||
@@ -857,15 +862,17 @@ class FileManagerModule(_ModuleBase):
|
|||||||
return TransferInfo(success=False,
|
return TransferInfo(success=False,
|
||||||
message=errmsg,
|
message=errmsg,
|
||||||
fileitem=fileitem,
|
fileitem=fileitem,
|
||||||
target_path=new_path)
|
target_path=new_path,
|
||||||
|
transfer_type=transfer_type)
|
||||||
|
|
||||||
logger.info(f"文件夹 {fileitem.path} 整理成功")
|
logger.info(f"文件夹 {fileitem.path} 整理成功")
|
||||||
# 返回整理后的路径
|
# 返回整理后的路径
|
||||||
return TransferInfo(success=True,
|
return TransferInfo(success=True,
|
||||||
fileitem=fileitem,
|
fileitem=fileitem,
|
||||||
target_fileitem=new_item,
|
target_item=new_item,
|
||||||
total_size=fileitem.size,
|
total_size=fileitem.size,
|
||||||
need_scrape=need_scrape)
|
need_scrape=need_scrape,
|
||||||
|
transfer_type=transfer_type)
|
||||||
else:
|
else:
|
||||||
# 整理单个文件
|
# 整理单个文件
|
||||||
if mediainfo.type == MediaType.TV:
|
if mediainfo.type == MediaType.TV:
|
||||||
@@ -875,7 +882,8 @@ class FileManagerModule(_ModuleBase):
|
|||||||
return TransferInfo(success=False,
|
return TransferInfo(success=False,
|
||||||
message=f"未识别到文件集数",
|
message=f"未识别到文件集数",
|
||||||
fileitem=fileitem,
|
fileitem=fileitem,
|
||||||
fail_list=[fileitem.path])
|
fail_list=[fileitem.path],
|
||||||
|
transfer_type=transfer_type)
|
||||||
|
|
||||||
# 文件结束季为空
|
# 文件结束季为空
|
||||||
in_meta.end_season = None
|
in_meta.end_season = None
|
||||||
@@ -930,15 +938,17 @@ class FileManagerModule(_ModuleBase):
|
|||||||
return TransferInfo(success=False,
|
return TransferInfo(success=False,
|
||||||
message=f"媒体库存在同名文件,且质量更好",
|
message=f"媒体库存在同名文件,且质量更好",
|
||||||
fileitem=fileitem,
|
fileitem=fileitem,
|
||||||
target_fileitem=__get_targetitem(target_file),
|
target_item=__get_targetitem(target_file),
|
||||||
fail_list=[fileitem.path])
|
fail_list=[fileitem.path],
|
||||||
|
transfer_type=transfer_type)
|
||||||
case 'never':
|
case 'never':
|
||||||
# 存在不覆盖
|
# 存在不覆盖
|
||||||
return TransferInfo(success=False,
|
return TransferInfo(success=False,
|
||||||
message=f"媒体库存在同名文件,当前覆盖模式为不覆盖",
|
message=f"媒体库存在同名文件,当前覆盖模式为不覆盖",
|
||||||
fileitem=fileitem,
|
fileitem=fileitem,
|
||||||
target_fileitem=__get_targetitem(target_file),
|
target_item=__get_targetitem(target_file),
|
||||||
fail_list=[fileitem.path])
|
fail_list=[fileitem.path],
|
||||||
|
transfer_type=transfer_type)
|
||||||
case 'latest':
|
case 'latest':
|
||||||
# 仅保留最新版本
|
# 仅保留最新版本
|
||||||
logger.info(f"当前整理覆盖模式设置为仅保留最新版本,将覆盖:{new_file}")
|
logger.info(f"当前整理覆盖模式设置为仅保留最新版本,将覆盖:{new_file}")
|
||||||
@@ -959,7 +969,8 @@ class FileManagerModule(_ModuleBase):
|
|||||||
return TransferInfo(success=False,
|
return TransferInfo(success=False,
|
||||||
message=err_msg,
|
message=err_msg,
|
||||||
fileitem=fileitem,
|
fileitem=fileitem,
|
||||||
fail_list=[fileitem.path])
|
fail_list=[fileitem.path],
|
||||||
|
transfer_type=transfer_type)
|
||||||
|
|
||||||
logger.info(f"文件 {fileitem.path} 整理成功")
|
logger.info(f"文件 {fileitem.path} 整理成功")
|
||||||
return TransferInfo(success=True,
|
return TransferInfo(success=True,
|
||||||
@@ -969,7 +980,8 @@ class FileManagerModule(_ModuleBase):
|
|||||||
total_size=fileitem.size,
|
total_size=fileitem.size,
|
||||||
file_list=[fileitem.path],
|
file_list=[fileitem.path],
|
||||||
file_list_new=[new_item.path],
|
file_list_new=[new_item.path],
|
||||||
need_scrape=need_scrape)
|
need_scrape=need_scrape,
|
||||||
|
transfer_type=transfer_type)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __get_naming_dict(meta: MetaBase, mediainfo: MediaInfo, file_ext: str = None,
|
def __get_naming_dict(meta: MetaBase, mediainfo: MediaInfo, file_ext: str = None,
|
||||||
|
|||||||
+3
-1
@@ -97,6 +97,8 @@ class Monitor(metaclass=Singleton):
|
|||||||
self.systemmessage = MessageHelper()
|
self.systemmessage = MessageHelper()
|
||||||
self.systemconfig = SystemConfigOper()
|
self.systemconfig = SystemConfigOper()
|
||||||
|
|
||||||
|
self.all_exts = settings.RMT_MEDIAEXT
|
||||||
|
|
||||||
# 启动目录监控和文件整理
|
# 启动目录监控和文件整理
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
@@ -269,7 +271,7 @@ class Monitor(metaclass=Singleton):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# 不是媒体文件不处理
|
# 不是媒体文件不处理
|
||||||
if event_path.suffix not in settings.RMT_MEDIAEXT:
|
if event_path.suffix.lower() not in self.all_exts:
|
||||||
logger.debug(f"{event_path} 不是媒体文件")
|
logger.debug(f"{event_path} 不是媒体文件")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ class TransferInfo(BaseModel):
|
|||||||
fileitem: Optional[FileItem] = None
|
fileitem: Optional[FileItem] = None
|
||||||
# 转移后路径
|
# 转移后路径
|
||||||
target_item: Optional[FileItem] = None
|
target_item: Optional[FileItem] = None
|
||||||
|
# 整理方式
|
||||||
|
transfer_type: Optional[str] = None
|
||||||
# 处理文件数
|
# 处理文件数
|
||||||
file_count: Optional[int] = 0
|
file_count: Optional[int] = 0
|
||||||
# 处理文件清单
|
# 处理文件清单
|
||||||
|
|||||||
Reference in New Issue
Block a user