优化了剧集编号提取和文件排序的逻辑,修复了一些 BUG

Merge pull request #2 from x1ao4/dev
This commit is contained in:
x1ao4 2025-04-23 17:32:06 +08:00 committed by GitHub
commit 0743fe2576
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 609 additions and 234 deletions

View File

@ -509,7 +509,10 @@ def get_share_detail():
return None return None
# 构建剧集命名的正则表达式 (主要用于检测已命名文件) # 构建剧集命名的正则表达式 (主要用于检测已命名文件)
if "[]" in episode_pattern: if episode_pattern == "[]":
# 对于单独的[],使用特殊匹配
regex_pattern = "^(\\d+)$" # 匹配纯数字文件名
elif "[]" in episode_pattern:
regex_pattern = re.escape(episode_pattern).replace('\\[\\]', '(\\d+)') regex_pattern = re.escape(episode_pattern).replace('\\[\\]', '(\\d+)')
else: else:
# 如果输入模式不包含[],则使用简单匹配模式,避免正则表达式错误 # 如果输入模式不包含[],则使用简单匹配模式,避免正则表达式错误
@ -563,6 +566,10 @@ def get_share_detail():
episode_num = extract_episode_number(file["file_name"]) episode_num = extract_episode_number(file["file_name"])
if episode_num is not None: if episode_num is not None:
# 生成预览文件名 # 生成预览文件名
if episode_pattern == "[]":
# 对于单独的[],直接使用数字序号作为文件名
file["file_name_re"] = f"{episode_num:02d}{file_ext}"
else:
file["file_name_re"] = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext file["file_name_re"] = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext
else: else:
# 无法提取剧集号,标记为无法处理 # 无法提取剧集号,标记为无法处理

View File

@ -176,7 +176,7 @@
<div class="col-10"> <div class="col-10">
<h2 style="display: inline-block;"><i class="bi bi-list-ol"></i> 剧集识别</h2> <h2 style="display: inline-block;"><i class="bi bi-list-ol"></i> 剧集识别</h2>
<span class="badge badge-pill badge-light"> <span class="badge badge-pill badge-light">
<a href="#" target="_blank" @click.prevent="alert('剧集识别说明:\n在任务配置中填写包含E[]的匹配表达式,如「剧名 - S01E[]」,将自动切换至剧集命名模式,自动识别文件名中的编号作为集编号,并替换[]部分。\n\n集编号匹配规则\n用于识别集编号的正则表达式多个表达式之间用竖线「|」分隔,每个表达式必须包含一个捕获组,用于提取剧集编号')">?</a> <a href="https://github.com/x1ao4/quark-auto-save-x/wiki/正则处理教程#25-剧集命名" target="_blank">?</a>
</span> </span>
</div> </div>
</div> </div>
@ -185,7 +185,7 @@
<div class="input-group-prepend"> <div class="input-group-prepend">
<span class="input-group-text">集编号识别规则</span> <span class="input-group-text">集编号识别规则</span>
</div> </div>
<input type="text" class="form-control" v-model="episodePatternsText" placeholder="多个正则表达式用竖线「|」分隔,如:(\\d+)|[Ee](\\d+)|第(\\d+)集"> <input type="text" class="form-control" v-model="episodePatternsText" placeholder="输入用于识别集编号的正则表达式,多个表达式用竖线「|」分隔,如:第(\d+)集|[Ee][Pp]?(\d+)|(\d+)[-_\\s]*4[Kk]">
</div> </div>
</div> </div>
@ -677,19 +677,17 @@
// 如果没有剧集识别模式,添加默认模式 // 如果没有剧集识别模式,添加默认模式
if (!this.formData.episode_patterns || this.formData.episode_patterns.length === 0) { if (!this.formData.episode_patterns || this.formData.episode_patterns.length === 0) {
this.formData.episode_patterns = [ this.formData.episode_patterns = [
{ name: 'EP_BASIC', description: '[]', regex: '(\\d+)' },
{ name: 'EP_4K', description: '[]-4K', regex: '(\\d+)[-_\\s]*4[Kk]' },
{ name: 'EP_HUA', description: '[]话', regex: '(\\d+)话' },
{ name: 'EP_E', description: 'E[]', regex: '[Ee](\\d+)' },
{ name: 'EP_EP', description: 'EP[]', regex: '[Ee][Pp](\\d+)' },
{ name: 'EP_DIHUA', description: '第[]话', regex: '第(\\d+)话' },
{ name: 'EP_DIJI', description: '第[]集', regex: '第(\\d+)集' }, { name: 'EP_DIJI', description: '第[]集', regex: '第(\\d+)集' },
{ name: 'EP_DIQI', description: '第[]期', regex: '第(\\d+)期' }, { name: 'EP_DIQI', description: '第[]期', regex: '第(\\d+)期' },
{ name: 'EP_4KSPACE', description: '[] 4K', regex: '(\\d+)\\s+4[Kk]' }, { name: 'EP_DIHUA', description: '第[]话', regex: '第(\\d+)话' },
{ name: 'EP_4KUNDER', description: '[]_4K', regex: '(\\d+)[_\\s]4[Kk]' }, { name: 'EP_JI', description: '[]集', regex: '(\\d+)集' },
{ name: 'EP_BRACKET', description: '【[]】', regex: '【(\\d+)】' }, { name: 'EP_QI', description: '[]期', regex: '(\\d+)期' },
{ name: 'EP_HUA', description: '[]话', regex: '(\\d+)话' },
{ name: 'EP_E_EP', description: 'E/EP[]', regex: '[Ee][Pp]?(\\d+)' },
{ name: 'EP_4K', description: '[]-4K', regex: '(\\d+)[-_\\s]*4[Kk]' },
{ name: 'EP_SQUAREBRACKET', description: '方括号数字', regex: '\\[(\\d+)\\]' }, { name: 'EP_SQUAREBRACKET', description: '方括号数字', regex: '\\[(\\d+)\\]' },
{ name: 'EP_UNDERSCORE', description: '_[]_', regex: '_?(\\d+)_' } { name: 'EP_BRACKET', description: '【[]】', regex: '【(\\d+)】' },
{ name: 'EP_UNDERSCORE', description: '_[]_', regex: '_?(\\d+)_?' }
]; ];
} }
}, 500); }, 500);
@ -793,9 +791,11 @@
task.sequence_naming = task.pattern; task.sequence_naming = task.pattern;
} }
// 如果是剧集命名模式确保episode_naming字段已正确设置 // 如果是剧集命名模式确保episode_naming字段已正确设置
if (task.use_episode_naming && task.pattern && task.pattern.includes('[]')) { if (task.use_episode_naming && task.pattern) {
if (task.pattern === "[]" || task.pattern.includes('[]')) {
task.episode_naming = task.pattern; task.episode_naming = task.pattern;
} }
}
}); });
} }
@ -840,7 +840,7 @@
if (newTask.taskname) { if (newTask.taskname) {
newTask.savepath = lastTask.savepath.replace(lastTask.taskname, newTask.taskname); newTask.savepath = lastTask.savepath.replace(lastTask.taskname, newTask.taskname);
} else { } else {
newTask.savepath = lastTask.taskname ? lastTask.savepath.replace(lastTask.taskname, 'TASKNAME') : lastTask.savepath; newTask.savepath = lastTask.savepath.replace(lastTask.taskname, 'TASKNAME') || lastTask.savepath;
} }
} }
} }
@ -1220,8 +1220,8 @@
}, },
detectNamingMode(task) { detectNamingMode(task) {
// 检测是否为顺序命名模式或剧集命名模式 // 检测是否为顺序命名模式或剧集命名模式
const sequencePatterns = ['{}', 'E{}', 'EP{}', 'S\\d+E{}', '第{}集', '第{}话', '第{}期']; const sequencePatterns = ['{}集', '第{}期', '第{}话', '{}集', '{}期', '{}话', 'E{}', 'EP{}', 'S\\d+E{}', '[{}]', '【{}】', '_{}_'];
const episodePatterns = ['[]', 'E[]', 'EP[]', 'S\\d+E[]', '第[]集', '第[]话', '第[]期']; const episodePatterns = ['第{% raw %}[]{% endraw %}集', '第{% raw %}[]{% endraw %}期', '第{% raw %}[]{% endraw %}话', '{% raw %}[]{% endraw %}集', '{% raw %}[]{% endraw %}期', '{% raw %}[]{% endraw %}话', 'E{% raw %}[]{% endraw %}', 'EP{% raw %}[]{% endraw %}', 'S\\d+E{% raw %}[]{% endraw %}', '[{% raw %}[]{% endraw %}', '【{% raw %}[]{% endraw %}】', '_{% raw %}[]{% endraw %}_'];
let isSequenceNaming = false; let isSequenceNaming = false;
let isEpisodeNaming = false; let isEpisodeNaming = false;
@ -1229,29 +1229,13 @@
// 保存当前值以支持撤销操作 // 保存当前值以支持撤销操作
const currentValue = task.pattern; const currentValue = task.pattern;
if (task.pattern) { if (task.pattern !== undefined) {
// 检查是否包含任何顺序命名模式 // 检查是否包含完整的{}占位符(确保不是分开的{和}
isSequenceNaming = sequencePatterns.some(pattern => { isSequenceNaming = task.pattern.includes('{}') && (!task.replace || task.replace === '');
const regexPattern = pattern.replace('{}', '\\{\\}');
return new RegExp(regexPattern).test(task.pattern);
});
// 或者用户直接输入包含{}的格式,且替换表达式为空 // 如果不是顺序命名模式,检查是否包含完整的[]占位符或就是单独的[]
if (!isSequenceNaming && task.pattern.includes('{}') && (!task.replace || task.replace === '')) {
isSequenceNaming = true;
}
// 检查是否包含任何剧集命名模式
if (!isSequenceNaming) { if (!isSequenceNaming) {
isEpisodeNaming = episodePatterns.some(pattern => { isEpisodeNaming = (task.pattern === '[]' || task.pattern.includes('[]')) && (!task.replace || task.replace === '');
const regexPattern = pattern.replace('[]', '\\[\\]');
return new RegExp(regexPattern).test(task.pattern);
});
// 或者用户直接输入包含[]的格式,且替换表达式为空
if (!isEpisodeNaming && task.pattern.includes('[]') && (!task.replace || task.replace === '')) {
isEpisodeNaming = true;
}
} }
} }

View File

@ -244,19 +244,17 @@ class Config:
if not config_data.get("episode_patterns"): if not config_data.get("episode_patterns"):
print("🔼 添加剧集识别模式配置") print("🔼 添加剧集识别模式配置")
config_data["episode_patterns"] = [ config_data["episode_patterns"] = [
{"description": "[]", "regex": "(\\d+)"},
{"description": "[]-4K", "regex": "(\\d+)[-_\\s]*4[Kk]"},
{"description": "[]话", "regex": "(\\d+)话"},
{"description": "E[]", "regex": "[Ee](\\d+)"},
{"description": "EP[]", "regex": "[Ee][Pp](\\d+)"},
{"description": "第[]话", "regex": "第(\\d+)话"},
{"description": "第[]集", "regex": "第(\\d+)集"}, {"description": "第[]集", "regex": "第(\\d+)集"},
{"description": "第[]期", "regex": "第(\\d+)期"}, {"description": "第[]期", "regex": "第(\\d+)期"},
{"description": "[] 4K", "regex": "(\\d+)\\s+4[Kk]"}, {"description": "第[]话", "regex": "第(\\d+)话"},
{"description": "[]_4K", "regex": "(\\d+)[_\\s]4[Kk]"}, {"description": "[]集", "regex": "(\\d+)集"},
{"description": "【[]】", "regex": "【(\\d+)】"}, {"description": "[]期", "regex": "(\\d+)期"},
{"description": "[]话", "regex": "(\\d+)话"},
{"description": "E/EP[]", "regex": "[Ee][Pp]?(\\d+)"},
{"description": "[]-4K", "regex": "(\\d+)[-_\\s]*4[Kk]"},
{"description": "[[]", "regex": "\\[(\\d+)\\]"}, {"description": "[[]", "regex": "\\[(\\d+)\\]"},
{"description": "_[]_", "regex": "_?(\\d+)_"} {"description": "【[]】", "regex": "【(\\d+)】"},
{"description": "_[]_", "regex": "_?(\\d+)_?"}
] ]
@ -791,7 +789,10 @@ class Quark:
# 构建剧集命名的正则表达式 # 构建剧集命名的正则表达式
episode_pattern = task["episode_naming"] episode_pattern = task["episode_naming"]
# 先检查是否包含合法的[]字符 # 先检查是否包含合法的[]字符
if "[]" in episode_pattern: if episode_pattern == "[]":
# 对于单独的[],使用特殊匹配
regex_pattern = "^(\\d+)$" # 匹配纯数字文件名
elif "[]" in episode_pattern:
# 将[] 替换为 (\d+) # 将[] 替换为 (\d+)
# 先将模式字符串进行转义,确保其他特殊字符不会干扰 # 先将模式字符串进行转义,确保其他特殊字符不会干扰
escaped_pattern = re.escape(episode_pattern) escaped_pattern = re.escape(episode_pattern)
@ -847,6 +848,9 @@ class Quark:
# 应用过滤词过滤 # 应用过滤词过滤
if task.get("filterwords"): if task.get("filterwords"):
# 记录过滤前的文件总数(包括文件夹)
original_total_count = len(share_file_list)
# 同时支持中英文逗号分隔 # 同时支持中英文逗号分隔
filterwords = task["filterwords"].replace("", ",") filterwords = task["filterwords"].replace("", ",")
filterwords_list = [word.strip().lower() for word in filterwords.split(',')] filterwords_list = [word.strip().lower() for word in filterwords.split(',')]
@ -863,7 +867,17 @@ class Quark:
filtered_files.append(file) filtered_files.append(file)
share_file_list = filtered_files share_file_list = filtered_files
print(f"📑 应用过滤词: {task['filterwords']},剩余{len(share_file_list)}个文件")
# 打印过滤信息(格式保持不变)
# 如果是顺序命名模式或剧集命名模式,需要排除文件夹,因为它们会自动过滤掉文件夹
remaining_count = len(share_file_list)
if task.get("use_sequence_naming") or task.get("use_episode_naming"):
# 计算剩余的实际可用文件数(排除文件夹)
remaining_usable_count = len([f for f in share_file_list if not f.get("dir", False)])
print(f"📑 应用过滤词: {task['filterwords']},剩余{remaining_usable_count}个文件")
else:
# 正则模式下正常显示
print(f"📑 应用过滤词: {task['filterwords']},剩余{remaining_count}个文件")
print() print()
# 获取目标目录文件列表 # 获取目标目录文件列表
@ -952,7 +966,7 @@ class Quark:
file_size = share_file.get("size", 0) file_size = share_file.get("size", 0)
file_ext = os.path.splitext(share_file["file_name"])[1].lower() file_ext = os.path.splitext(share_file["file_name"])[1].lower()
share_update_time = share_file.get("last_update_at", 0) share_update_time = share_file.get("last_update_at", 0) or share_file.get("updated_at", 0)
# 检查是否已存在相同大小和扩展名的文件 # 检查是否已存在相同大小和扩展名的文件
key = f"{file_size}_{file_ext}" key = f"{file_size}_{file_ext}"
@ -962,10 +976,18 @@ class Quark:
for existing_file in dir_files_map[key]: for existing_file in dir_files_map[key]:
existing_update_time = existing_file.get("updated_at", 0) existing_update_time = existing_file.get("updated_at", 0)
# 防止除零错误
if existing_update_time == 0:
continue
# 如果修改时间相近30天内或者差距不大10%以内),认为是同一个文件 # 如果修改时间相近30天内或者差距不大10%以内),认为是同一个文件
if (abs(share_update_time - existing_update_time) < 2592000 or time_diff = abs(share_update_time - existing_update_time)
abs(1 - (share_update_time / existing_update_time if existing_update_time else 1)) < 0.1): time_ratio = abs(1 - (share_update_time / existing_update_time)) if existing_update_time else 1
if time_diff < 2592000 or time_ratio < 0.1:
# 文件已存在,跳过处理
is_duplicate = True is_duplicate = True
# print(f"跳过已存在的文件: {share_file['file_name']} (size={file_size}, time_diff={time_diff}s, ratio={time_ratio:.2f})")
break break
# 只有非重复文件才进行处理 # 只有非重复文件才进行处理
@ -986,7 +1008,7 @@ class Quark:
# 提取文件名,不含扩展名 # 提取文件名,不含扩展名
file_name_without_ext = os.path.splitext(filename)[0] file_name_without_ext = os.path.splitext(filename)[0]
# 1. "第X期/集/话" 格式 # 1. "第X期/集/话" 格式 - 保持最高优先级
match_chinese = re.search(r'第(\d+)[期集话]', filename) match_chinese = re.search(r'第(\d+)[期集话]', filename)
episode_num = int(match_chinese.group(1)) if match_chinese else 0 episode_num = int(match_chinese.group(1)) if match_chinese else 0
@ -1007,6 +1029,11 @@ class Quark:
elif '' in filename: elif '' in filename:
return 3 return 3
# 1.2 "X集/期/话" 格式 - 与我们修改后的优先级一致
match_chinese_simple = re.search(r'(\d+)[期集话]', filename)
if match_chinese_simple:
return int(match_chinese_simple.group(1))
# 2.1 S01E01 格式,提取季数和集数 # 2.1 S01E01 格式,提取季数和集数
match_s_e = re.search(r'[Ss](\d+)[Ee](\d+)', filename) match_s_e = re.search(r'[Ss](\d+)[Ee](\d+)', filename)
if match_s_e: if match_s_e:
@ -1014,7 +1041,7 @@ class Quark:
episode = int(match_s_e.group(2)) episode = int(match_s_e.group(2))
return season * 1000 + episode return season * 1000 + episode
# 2.2 E01 格式,仅提取集数 # 2.2 E01/EP01 格式,仅提取集数
match_e = re.search(r'[Ee][Pp]?(\d+)', filename) match_e = re.search(r'[Ee][Pp]?(\d+)', filename)
if match_e: if match_e:
return int(match_e.group(1)) return int(match_e.group(1))
@ -1026,6 +1053,26 @@ class Quark:
episode = int(match_x.group(2)) episode = int(match_x.group(2))
return season * 1000 + episode return season * 1000 + episode
# 2.4 数字后接4K格式
match_4k = re.search(r'(\d+)[-_\s]*4[Kk]', filename)
if match_4k:
return int(match_4k.group(1))
# 2.5 方括号包围的数字
match_bracket = re.search(r'\[(\d+)\]', filename)
if match_bracket:
return int(match_bracket.group(1))
# 2.6 中括号包围的数字
match_cn_bracket = re.search(r'【(\d+)】', filename)
if match_cn_bracket:
return int(match_cn_bracket.group(1))
# 2.7 下划线包围的数字
match_underscore = re.search(r'_?(\d+)_', filename)
if match_underscore:
return int(match_underscore.group(1))
# 3. 日期格式识别(支持多种格式) # 3. 日期格式识别(支持多种格式)
# 3.1 完整的YYYYMMDD格式 # 3.1 完整的YYYYMMDD格式
@ -1187,8 +1234,57 @@ class Quark:
else: else:
# 正则命名模式 # 正则命名模式
need_save_list = [] need_save_list = []
# 构建目标目录中所有文件的查重索引(按大小和修改时间)- 加入文件查重机制
dir_files_map = {}
for dir_file in dir_file_list:
if not dir_file["dir"]: # 仅处理文件
file_size = dir_file.get("size", 0)
file_ext = os.path.splitext(dir_file["file_name"])[1].lower()
update_time = dir_file.get("updated_at", 0)
# 创建大小+扩展名的索引,用于快速查重
key = f"{file_size}_{file_ext}"
if key not in dir_files_map:
dir_files_map[key] = []
dir_files_map[key].append({
"file_name": dir_file["file_name"],
"updated_at": update_time,
})
# 添加符合的 # 添加符合的
for share_file in share_file_list: for share_file in share_file_list:
# 检查文件是否已存在(通过大小和扩展名)- 新增的文件查重逻辑
is_duplicate = False
if not share_file["dir"]:
file_size = share_file.get("size", 0)
file_ext = os.path.splitext(share_file["file_name"])[1].lower()
share_update_time = share_file.get("last_update_at", 0) or share_file.get("updated_at", 0)
# 检查是否已存在相同大小和扩展名的文件
key = f"{file_size}_{file_ext}"
if key in dir_files_map:
for existing_file in dir_files_map[key]:
existing_update_time = existing_file.get("updated_at", 0)
# 防止除零错误
if existing_update_time == 0:
continue
# 如果修改时间相近30天内或者差距不大10%以内),认为是同一个文件
time_diff = abs(share_update_time - existing_update_time)
time_ratio = abs(1 - (share_update_time / existing_update_time)) if existing_update_time else 1
if time_diff < 2592000 or time_ratio < 0.1:
# 文件已存在,跳过处理
is_duplicate = True
# print(f"跳过已存在的文件: {share_file['file_name']} (size={file_size}, time_diff={time_diff}s, ratio={time_ratio:.2f})")
break
# 如果文件已经存在并且不是目录,跳过后续处理
if is_duplicate and not share_file["dir"]:
continue
if share_file["dir"] and task.get("update_subdir", False): if share_file["dir"] and task.get("update_subdir", False):
pattern, replace = task["update_subdir"], "" pattern, replace = task["update_subdir"], ""
else: else:
@ -1216,6 +1312,20 @@ class Quark:
if replace != "" if replace != ""
else share_file["file_name"] else share_file["file_name"]
) )
# 检查新名称是否存在重复的前缀
if replace and " - " in save_name:
parts = save_name.split(" - ")
if len(parts) >= 2 and parts[0] == parts[1]:
# 如果新名称包含重复前缀,使用原文件名
save_name = share_file["file_name"]
# 检查是否任务名已经存在于原文件名中
taskname = task.get("taskname", "")
if taskname and taskname in share_file["file_name"] and share_file["file_name"].startswith(taskname):
# 如果原文件名已包含任务名作为前缀,保持原样
save_name = share_file["file_name"]
# 忽略后缀 # 忽略后缀
if task.get("ignore_extension") and not share_file["dir"]: if task.get("ignore_extension") and not share_file["dir"]:
compare_func = lambda a, b1, b2: ( compare_func = lambda a, b1, b2: (
@ -1231,11 +1341,15 @@ class Quark:
dir_file["file_name"], share_file["file_name"], save_name dir_file["file_name"], share_file["file_name"], save_name
): ):
file_exists = True file_exists = True
# print(f"跳过已存在的文件: {dir_file['file_name']}") # print(f"跳过已存在的文件: {dir_file['file_name']} - 与源文件或保存文件同名")
# 删除对文件打印部分
break break
if not file_exists: if not file_exists:
# 再次检查是否已经通过文件内容(大小+时间)被识别为重复
if is_duplicate:
# print(f"跳过已存在的文件: {share_file['file_name']} - 通过大小和时间匹配到相同文件")
continue
# 不打印保存信息 # 不打印保存信息
share_file["save_name"] = save_name share_file["save_name"] = save_name
share_file["original_name"] = share_file["file_name"] # 保存原文件名,用于排序 share_file["original_name"] = share_file["file_name"] # 保存原文件名,用于排序
@ -1291,11 +1405,20 @@ class Quark:
icon = ( icon = (
"📁" "📁"
if item["dir"] == True if item["dir"] == True
else "🎞️" if item["obj_category"] == "video" else "" else "🎞️" if item["obj_category"] == "video" else get_file_icon(item["save_name"], False)
) )
saved_files.append(f"{icon}{item['save_name']}")
# 修复文件树显示问题 - 防止文件名重复重复显示
# 如果save_name与original_name相似一个是"你好,星期六 - 2025-04-05.mp4",另一个是"20250405期.mp4"
# 则只显示save_name避免重复
display_name = item['save_name']
# 不再自动添加任务名称前缀,尊重用户选择
# 保存到树中
saved_files.append(f"{icon}{display_name}")
tree.create_node( tree.create_node(
f"{icon}{item['save_name']}", f"{icon}{display_name}",
item["fid"], item["fid"],
parent=pdir_fid, parent=pdir_fid,
data={ data={
@ -1374,7 +1497,7 @@ class Quark:
# 提取文件名,不含扩展名 # 提取文件名,不含扩展名
file_name_without_ext = os.path.splitext(filename)[0] file_name_without_ext = os.path.splitext(filename)[0]
# 1. "第X期/集/话" 格式 # 1. "第X期/集/话" 格式 - 保持最高优先级
match_chinese = re.search(r'第(\d+)[期集话]', filename) match_chinese = re.search(r'第(\d+)[期集话]', filename)
episode_num = int(match_chinese.group(1)) if match_chinese else 0 episode_num = int(match_chinese.group(1)) if match_chinese else 0
@ -1395,6 +1518,11 @@ class Quark:
elif '' in filename: elif '' in filename:
return 3 return 3
# 1.2 "X集/期/话" 格式 - 与我们修改后的优先级一致
match_chinese_simple = re.search(r'(\d+)[期集话]', filename)
if match_chinese_simple:
return int(match_chinese_simple.group(1))
# 2.1 S01E01 格式,提取季数和集数 # 2.1 S01E01 格式,提取季数和集数
match_s_e = re.search(r'[Ss](\d+)[Ee](\d+)', filename) match_s_e = re.search(r'[Ss](\d+)[Ee](\d+)', filename)
if match_s_e: if match_s_e:
@ -1402,7 +1530,7 @@ class Quark:
episode = int(match_s_e.group(2)) episode = int(match_s_e.group(2))
return season * 1000 + episode return season * 1000 + episode
# 2.2 E01 格式,仅提取集数 # 2.2 E01/EP01 格式,仅提取集数
match_e = re.search(r'[Ee][Pp]?(\d+)', filename) match_e = re.search(r'[Ee][Pp]?(\d+)', filename)
if match_e: if match_e:
return int(match_e.group(1)) return int(match_e.group(1))
@ -1414,6 +1542,26 @@ class Quark:
episode = int(match_x.group(2)) episode = int(match_x.group(2))
return season * 1000 + episode return season * 1000 + episode
# 2.4 数字后接4K格式
match_4k = re.search(r'(\d+)[-_\s]*4[Kk]', filename)
if match_4k:
return int(match_4k.group(1))
# 2.5 方括号包围的数字
match_bracket = re.search(r'\[(\d+)\]', filename)
if match_bracket:
return int(match_bracket.group(1))
# 2.6 中括号包围的数字
match_cn_bracket = re.search(r'【(\d+)】', filename)
if match_cn_bracket:
return int(match_cn_bracket.group(1))
# 2.7 下划线包围的数字
match_underscore = re.search(r'_?(\d+)_', filename)
if match_underscore:
return int(match_underscore.group(1))
# 3. 日期格式识别(支持多种格式) # 3. 日期格式识别(支持多种格式)
# 3.1 完整的YYYYMMDD格式 # 3.1 完整的YYYYMMDD格式
@ -1642,17 +1790,17 @@ class Quark:
# 尝试匹配更多格式 # 尝试匹配更多格式
default_patterns = [ default_patterns = [
r'(\d+)',
r'(\d+)[-_\s]*4[Kk]',
r'(\d+)话',
r'第(\d+)话',
r'第(\d+)集', r'第(\d+)集',
r'第(\d+)期', r'第(\d+)期',
r'(\d+)\s+4[Kk]', r'第(\d+)话',
r'(\d+)[_\s]4[Kk]', r'(\d+)集',
r'【(\d+)】', r'(\d+)期',
r'(\d+)话',
r'[Ee][Pp]?(\d+)',
r'(\d+)[-_\s]*4[Kk]',
r'\[(\d+)\]', r'\[(\d+)\]',
r'_?(\d+)_' r'【(\d+)】',
r'_?(\d+)_?'
] ]
# 如果配置了自定义规则,优先使用 # 如果配置了自定义规则,优先使用
@ -1721,7 +1869,7 @@ class Quark:
# 检查文件是否已存在(基于大小和修改时间) # 检查文件是否已存在(基于大小和修改时间)
file_size = share_file.get("size", 0) file_size = share_file.get("size", 0)
file_ext = os.path.splitext(share_file["file_name"])[1].lower() file_ext = os.path.splitext(share_file["file_name"])[1].lower()
share_update_time = share_file.get("last_update_at", 0) share_update_time = share_file.get("last_update_at", 0) or share_file.get("updated_at", 0)
key = f"{file_size}_{file_ext}" key = f"{file_size}_{file_ext}"
is_duplicate = False is_duplicate = False
@ -1729,9 +1877,19 @@ class Quark:
if key in dir_files_map: if key in dir_files_map:
for existing_file in dir_files_map[key]: for existing_file in dir_files_map[key]:
existing_update_time = existing_file.get("updated_at", 0) existing_update_time = existing_file.get("updated_at", 0)
if (abs(share_update_time - existing_update_time) < 2592000 or
abs(1 - (share_update_time / existing_update_time if existing_update_time else 1)) < 0.1): # 防止除零错误
if existing_update_time == 0:
continue
# 如果修改时间相近30天内或者差距不大10%以内),认为是同一个文件
time_diff = abs(share_update_time - existing_update_time)
time_ratio = abs(1 - (share_update_time / existing_update_time)) if existing_update_time else 1
if time_diff < 2592000 or time_ratio < 0.1:
# 文件已存在,跳过处理
is_duplicate = True is_duplicate = True
# print(f"跳过已存在的文件: {share_file['file_name']} (size={file_size}, time_diff={time_diff}s, ratio={time_ratio:.2f})")
break break
# 检查剧集号是否已经存在 # 检查剧集号是否已经存在
@ -1743,6 +1901,10 @@ class Quark:
# 生成预期的目标文件名并检查是否已存在 # 生成预期的目标文件名并检查是否已存在
if episode_num is not None and not is_duplicate: if episode_num is not None and not is_duplicate:
file_ext = os.path.splitext(share_file["file_name"])[1] file_ext = os.path.splitext(share_file["file_name"])[1]
if episode_pattern == "[]":
# 对于单独的[],直接使用数字序号作为文件名
expected_name = f"{episode_num:02d}{file_ext}"
else:
expected_name = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext expected_name = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext
# 检查目标文件名是否存在于目录中 # 检查目标文件名是否存在于目录中
if any(dir_file["file_name"] == expected_name for dir_file in dir_file_list): if any(dir_file["file_name"] == expected_name for dir_file in dir_file_list):
@ -1771,12 +1933,12 @@ class Quark:
episode = int(match_s_e.group(2)) episode = int(match_s_e.group(2))
return (season * 1000 + episode, 0) return (season * 1000 + episode, 0)
# 其他匹配方式 # 使用统一的剧集提取函数
episode_num = extract_episode_number(filename) episode_num = extract_episode_number(filename)
if episode_num is not None: if episode_num is not None:
return (episode_num, 0) return (episode_num, 0)
# 无法识别,使用修改时间 # 无法识别,回退到修改时间排序
return (float('inf'), file.get("last_update_at", 0)) return (float('inf'), file.get("last_update_at", 0))
# 过滤出文件并排序 # 过滤出文件并排序
@ -1792,6 +1954,10 @@ class Quark:
if episode_num is not None: if episode_num is not None:
# 生成新文件名 # 生成新文件名
file_ext = os.path.splitext(share_file["file_name"])[1] file_ext = os.path.splitext(share_file["file_name"])[1]
if episode_pattern == "[]":
# 对于单独的[],直接使用数字序号作为文件名
save_name = f"{episode_num:02d}{file_ext}"
else:
save_name = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext save_name = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext
# 检查过滤词 # 检查过滤词
@ -2001,7 +2167,15 @@ class Quark:
episode_num = extract_episode_number(dir_file["file_name"]) episode_num = extract_episode_number(dir_file["file_name"])
if episode_num is not None: if episode_num is not None:
# 检查文件名是否符合指定的剧集命名格式 # 检查文件名是否符合指定的剧集命名格式
if not re.match(regex_pattern, dir_file["file_name"]): if episode_pattern == "[]":
# 对于单独的[]模式,检查文件名是否已经是纯数字格式
file_name_without_ext = os.path.splitext(dir_file["file_name"])[0]
# 如果文件名不是纯数字格式,才进行重命名
if not file_name_without_ext.isdigit() or len(file_name_without_ext) != 2:
file_ext = os.path.splitext(dir_file["file_name"])[1]
new_name = f"{episode_num:02d}{file_ext}"
rename_operations.append((dir_file, new_name, episode_num))
elif not re.match(regex_pattern, dir_file["file_name"]):
file_ext = os.path.splitext(dir_file["file_name"])[1] file_ext = os.path.splitext(dir_file["file_name"])[1]
new_name = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext new_name = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext
rename_operations.append((dir_file, new_name, episode_num)) rename_operations.append((dir_file, new_name, episode_num))
@ -2079,21 +2253,73 @@ class Quark:
# 应用正则表达式 # 应用正则表达式
try: try:
new_name = re.sub(pattern, replace, dir_file["file_name"]) # 在应用正则表达式前,先检查文件名是否已经是符合目标格式的
orig_name = dir_file["file_name"]
# 应用正则表达式获取目标文件名
new_name = re.sub(pattern, replace, orig_name)
# 如果替换后的文件名没有变化,跳过
if new_name == orig_name:
continue
# 如果替换后的文件名是任务名的重复嵌套,跳过
# 例如:对于"你好,星期六 - 2025-04-05.mp4" -> "你好,星期六 - 你好,星期六 - 2025-04-05.mp4"
if " - " in new_name:
parts = new_name.split(" - ")
# 检查是否有重复的部分
if len(parts) >= 2 and parts[0] == parts[1]:
continue
# 另一种情况:检查前缀是否已存在于文件名中
prefix = replace.split(" - ")[0] if " - " in replace else ""
if prefix and prefix in orig_name:
# 如果原始文件名已经包含了需要添加的前缀,跳过重命名
continue
# 如果文件名发生变化,需要重命名 # 如果文件名发生变化,需要重命名
if new_name != dir_file["file_name"]: if new_name != orig_name:
rename_operations.append((dir_file, new_name)) rename_operations.append((dir_file, new_name))
except Exception as e: except Exception as e:
print(f"正则替换出错: {dir_file['file_name']},错误:{str(e)}") print(f"正则替换出错: {dir_file['file_name']},错误:{str(e)}")
# 按原始文件名字母顺序排序,使重命名操作有序进行 # 按原始文件名字母顺序排序,使重命名操作有序进行
rename_operations.sort(key=lambda x: x[0]["file_name"]) # rename_operations.sort(key=lambda x: x[0]["file_name"])
# 修改为按日期或数字排序(复用与文件树相同的排序逻辑)
def extract_sort_value(file_name):
# 尝试提取日期格式优先YYYY-MM-DD格式
date_match = re.search(r'(\d{4})[-./](\d{1,2})[-./](\d{1,2})', file_name)
if date_match:
year = int(date_match.group(1))
month = int(date_match.group(2))
day = int(date_match.group(3))
return year * 10000 + month * 100 + day
# 尝试提取紧凑日期格式YYYYMMDD
compact_date_match = re.search(r'(\d{4})(\d{2})(\d{2})', file_name)
if compact_date_match:
year = int(compact_date_match.group(1))
month = int(compact_date_match.group(2))
day = int(compact_date_match.group(3))
return year * 10000 + month * 100 + day
# 尝试提取任何数字
number_match = re.search(r'(\d+)', file_name)
if number_match:
return int(number_match.group(1))
# 默认使用原文件名
return float('inf')
# 按目标文件名中的日期或数字进行排序,与顺序命名和剧集命名模式保持一致
rename_operations.sort(key=lambda x: extract_sort_value(x[1]))
# 执行重命名操作,并收集日志 # 执行重命名操作,并收集日志
already_renamed_files = set() # 用于防止重复重命名
for dir_file, new_name in rename_operations: for dir_file, new_name in rename_operations:
# 检查是否会导致重名 # 检查是否会导致重名
if new_name not in [f["file_name"] for f in dir_file_list]: if new_name not in [f["file_name"] for f in dir_file_list] and new_name not in already_renamed_files:
try: try:
rename_return = self.rename(dir_file["fid"], new_name) rename_return = self.rename(dir_file["fid"], new_name)
if rename_return["code"] == 0: if rename_return["code"] == 0:
@ -2105,6 +2331,8 @@ class Quark:
if df["fid"] == dir_file["fid"]: if df["fid"] == dir_file["fid"]:
df["file_name"] = new_name df["file_name"] = new_name
break break
# 记录已重命名的文件
already_renamed_files.add(new_name)
else: else:
# 收集错误日志但不打印 # 收集错误日志但不打印
error_msg = rename_return.get("message", "未知错误") error_msg = rename_return.get("message", "未知错误")
@ -2240,10 +2468,51 @@ def do_save(account, tasklist=[]):
# 检查是否需要清除重复的通知 # 检查是否需要清除重复的通知
# 由于在修改顺序,现在不需要特殊处理通知 # 由于在修改顺序,现在不需要特殊处理通知
is_special_sequence = (task.get("use_sequence_naming") and task.get("sequence_naming")) or (task.get("use_episode_naming") and task.get("episode_naming")) is_special_sequence = (task.get("use_sequence_naming") and task.get("sequence_naming")) or (task.get("use_episode_naming") and task.get("episode_naming"))
# 对于正则命名模式,也将其视为特殊序列
is_regex_mode = not is_special_sequence and task.get("pattern") is not None
# 执行重命名任务,但收集日志而不是立即打印 # 执行重命名任务,但收集日志而不是立即打印
is_rename, rename_logs = account.do_rename_task(task) is_rename, rename_logs = account.do_rename_task(task)
# 如果是正则命名模式且没有Tree对象即通过转存得到的Tree对象则需要手动创建一个Tree视图
if is_regex_mode and not (is_new_tree and isinstance(is_new_tree, Tree) and is_new_tree.size() > 1):
# 当is_new_tree明确为False时表示没有新文件不处理
if is_new_tree is not False and is_rename:
# 获取当前目录下的所有文件
savepath = re.sub(r"/{2,}", "/", f"/{task['savepath']}")
if account.savepath_fid.get(savepath):
dir_file_list = account.ls_dir(account.savepath_fid[savepath])
# 如果有文件并且没有Tree对象创建一个Tree对象
if dir_file_list: # 去掉is_new_tree的检查因为上面已经做了
# 创建一个新的Tree对象
new_tree = Tree()
# 创建根节点
new_tree.create_node(
savepath,
"root",
data={
"is_dir": True,
},
)
# 添加文件节点
for file in dir_file_list:
if not file["dir"]: # 只处理文件
new_tree.create_node(
file["file_name"],
file["fid"],
parent="root",
data={
"is_dir": False,
"path": f"{savepath}/{file['file_name']}",
},
)
# 如果树的大小大于1有文件则设置为新的Tree对象
if new_tree.size() > 1:
is_new_tree = new_tree
# 添加生成文件树的功能(无论是否是顺序命名模式) # 添加生成文件树的功能(无论是否是顺序命名模式)
# 如果is_new_tree返回了Tree对象则打印文件树 # 如果is_new_tree返回了Tree对象则打印文件树
if is_new_tree and isinstance(is_new_tree, Tree) and is_new_tree.size() > 1: if is_new_tree and isinstance(is_new_tree, Tree) and is_new_tree.size() > 1:
@ -2258,6 +2527,7 @@ def do_save(account, tasklist=[]):
# 按文件名排序 # 按文件名排序
if is_special_sequence: if is_special_sequence:
if task.get("use_sequence_naming") and task.get("sequence_naming"):
# 如果是顺序命名模式,直接使用顺序命名模式的模板来显示 # 如果是顺序命名模式,直接使用顺序命名模式的模板来显示
sequence_pattern = task["sequence_naming"] sequence_pattern = task["sequence_naming"]
@ -2299,20 +2569,32 @@ def do_save(account, tasklist=[]):
return int(match_e.group(1)) return int(match_e.group(1))
# 尝试匹配更多格式 # 尝试匹配更多格式
patterns = [ default_patterns = [
r'(\d+)',
r'(\d+)[-_\s]*4[Kk]',
r'(\d+)话',
r'第(\d+)话',
r'第(\d+)集', r'第(\d+)集',
r'第(\d+)期', r'第(\d+)期',
r'(\d+)\s+4[Kk]', r'第(\d+)话',
r'(\d+)[_\s]4[Kk]', r'(\d+)集',
r'【(\d+)】', r'(\d+)期',
r'(\d+)话',
r'[Ee][Pp]?(\d+)',
r'(\d+)[-_\s]*4[Kk]',
r'\[(\d+)\]', r'\[(\d+)\]',
r'_?(\d+)_' r'【(\d+)】',
r'_?(\d+)_?'
] ]
# 如果配置了自定义规则,优先使用
if "config_data" in task and isinstance(task["config_data"].get("episode_patterns"), list) and task["config_data"]["episode_patterns"]:
patterns = [p.get("regex", "(\\d+)") for p in task["config_data"]["episode_patterns"]]
else:
# 尝试从全局配置获取
global CONFIG_DATA
if isinstance(CONFIG_DATA.get("episode_patterns"), list) and CONFIG_DATA["episode_patterns"]:
patterns = [p.get("regex", "(\\d+)") for p in CONFIG_DATA["episode_patterns"]]
else:
patterns = default_patterns
# 尝试使用每个正则表达式匹配文件名
for pattern_regex in patterns: for pattern_regex in patterns:
try: try:
match = re.search(pattern_regex, filename) match = re.search(pattern_regex, filename)
@ -2332,6 +2614,10 @@ def do_save(account, tasklist=[]):
# 获取扩展名 # 获取扩展名
file_ext = os.path.splitext(orig_filename)[1] file_ext = os.path.splitext(orig_filename)[1]
# 生成新的文件名(使用剧集命名模式) # 生成新的文件名(使用剧集命名模式)
if episode_pattern == "[]":
# 对于单独的[],直接使用数字序号作为文件名
new_filename = f"{episode_num:02d}{file_ext}"
else:
new_filename = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext new_filename = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext
# 获取适当的图标 # 获取适当的图标
icon = get_file_icon(orig_filename, is_dir=node.data.get("is_dir", False)) icon = get_file_icon(orig_filename, is_dir=node.data.get("is_dir", False))
@ -2362,6 +2648,14 @@ def do_save(account, tasklist=[]):
try: try:
# 应用正则表达式 # 应用正则表达式
new_name = re.sub(pattern, replace, orig_filename) new_name = re.sub(pattern, replace, orig_filename)
# 检查新名称是否包含重复的前缀
if " - " in new_name:
parts = new_name.split(" - ")
if len(parts) >= 2 and parts[0] == parts[1]:
# 如果有重复前缀,使用原文件名
new_name = orig_filename
# 为文件添加图标 # 为文件添加图标
icon = get_file_icon(orig_filename, is_dir=node.data.get("is_dir", False)) icon = get_file_icon(orig_filename, is_dir=node.data.get("is_dir", False))
display_files.append((f"{icon}{new_name}", node)) display_files.append((f"{icon}{new_name}", node))
@ -2378,17 +2672,95 @@ def do_save(account, tasklist=[]):
# 添加适当的图标 # 添加适当的图标
icon = get_file_icon(orig_filename, is_dir=node.data.get("is_dir", False)) icon = get_file_icon(orig_filename, is_dir=node.data.get("is_dir", False))
display_files.append((f"{icon}{orig_filename}", node)) display_files.append((f"{icon}{orig_filename}", node))
elif is_regex_mode:
# 正则模式:显示正则替换后的文件名
if task.get("pattern") is not None:
# 获取正则模式
pattern, replace = account.magic_regex_func(
task.get("pattern", ""), task.get("replace", ""), task["taskname"]
)
# 添加成功通知 # 对文件名应用正则替换
for node in file_nodes:
orig_filename = node.tag.lstrip("🎞️")
try:
# 应用正则表达式
new_name = re.sub(pattern, replace, orig_filename)
# 检查新名称是否包含重复的前缀
if " - " in new_name:
parts = new_name.split(" - ")
if len(parts) >= 2 and parts[0] == parts[1]:
# 如果有重复前缀,使用原文件名
new_name = orig_filename
# 为文件添加图标
icon = get_file_icon(orig_filename, is_dir=node.data.get("is_dir", False))
display_files.append((f"{icon}{new_name}", node))
except Exception as e:
# 如果正则替换失败,使用原文件名
icon = get_file_icon(orig_filename, is_dir=node.data.get("is_dir", False))
display_files.append((f"{icon}{orig_filename}", node))
else:
# 使用字母顺序和原始文件名
display_files = []
for node in sorted(file_nodes, key=lambda node: node.tag):
# 获取原始文件名(去除已有图标)
orig_filename = node.tag.lstrip("🎞️")
# 添加适当的图标
icon = get_file_icon(orig_filename, is_dir=node.data.get("is_dir", False))
display_files.append((f"{icon}{orig_filename}", node))
else:
# 其他模式:显示原始文件名
display_files = []
for node in sorted(file_nodes, key=lambda node: node.tag):
# 获取原始文件名(去除已有图标)
orig_filename = node.tag.lstrip("🎞️")
# 添加适当的图标
icon = get_file_icon(orig_filename, is_dir=node.data.get("is_dir", False))
display_files.append((f"{icon}{orig_filename}", node))
# 添加成功通知,带文件数量图标
add_notify(f"✅《{task['taskname']}》 添加追更:") add_notify(f"✅《{task['taskname']}》 添加追更:")
add_notify(f"/{task['savepath']}") add_notify(f"/{task['savepath']}")
# 在显示树状结构之前,定义一个本地排序函数
def local_sort_key(item):
file_name = item[0][item[0].find("🎞️")+1:] if "🎞️" in item[0] else item[0]
# 尝试提取日期格式优先YYYY-MM-DD格式
date_match = re.search(r'(\d{4})[-./](\d{1,2})[-./](\d{1,2})', file_name)
if date_match:
year = int(date_match.group(1))
month = int(date_match.group(2))
day = int(date_match.group(3))
return year * 10000 + month * 100 + day
# 尝试提取紧凑日期格式YYYYMMDD
compact_date_match = re.search(r'(\d{4})(\d{2})(\d{2})', file_name)
if compact_date_match:
year = int(compact_date_match.group(1))
month = int(compact_date_match.group(2))
day = int(compact_date_match.group(3))
return year * 10000 + month * 100 + day
# 尝试提取任何数字
number_match = re.search(r'(\d+)', file_name)
if number_match:
return int(number_match.group(1))
# 默认使用原文件名
return float('inf')
# 对显示文件进行排序,使用本地排序函数
display_files = sorted(display_files, key=local_sort_key)
# 打印保存文件列表 # 打印保存文件列表
for idx, (display_name, _) in enumerate(display_files): for idx, (display_name, _) in enumerate(display_files):
prefix = "├── " if idx < len(display_files) - 1 else "└── " prefix = "├── " if idx < len(display_files) - 1 else "└── "
add_notify(f"{prefix}{display_name}") add_notify(f"{prefix}{display_name}")
add_notify("") add_notify("")
# 如果是剧集命名模式并且成功进行了重命名,单独显示排序好的文件列表 # 如果是剧集命名模式并且成功进行了重命名,单独显示排序好的文件列表
elif is_rename and task.get("use_episode_naming") and task.get("episode_naming"): elif is_rename and task.get("use_episode_naming") and task.get("episode_naming"):
# 重新获取文件列表 # 重新获取文件列表
@ -2414,20 +2786,32 @@ def do_save(account, tasklist=[]):
return int(match_e.group(1)) return int(match_e.group(1))
# 尝试匹配更多格式 # 尝试匹配更多格式
patterns = [ default_patterns = [
r'(\d+)',
r'(\d+)[-_\s]*4[Kk]',
r'(\d+)话',
r'第(\d+)话',
r'第(\d+)集', r'第(\d+)集',
r'第(\d+)期', r'第(\d+)期',
r'(\d+)\s+4[Kk]', r'第(\d+)话',
r'(\d+)[_\s]4[Kk]', r'(\d+)集',
r'【(\d+)】', r'(\d+)期',
r'(\d+)话',
r'[Ee][Pp]?(\d+)',
r'(\d+)[-_\s]*4[Kk]',
r'\[(\d+)\]', r'\[(\d+)\]',
r'_?(\d+)_' r'【(\d+)】',
r'_?(\d+)_?'
] ]
# 如果配置了自定义规则,优先使用
if "config_data" in task and isinstance(task["config_data"].get("episode_patterns"), list) and task["config_data"]["episode_patterns"]:
patterns = [p.get("regex", "(\\d+)") for p in task["config_data"]["episode_patterns"]]
else:
# 尝试从全局配置获取
global CONFIG_DATA
if isinstance(CONFIG_DATA.get("episode_patterns"), list) and CONFIG_DATA["episode_patterns"]:
patterns = [p.get("regex", "(\\d+)") for p in CONFIG_DATA["episode_patterns"]]
else:
patterns = default_patterns
# 尝试使用每个正则表达式匹配文件名
for pattern_regex in patterns: for pattern_regex in patterns:
try: try:
match = re.search(pattern_regex, filename) match = re.search(pattern_regex, filename)
@ -2442,10 +2826,8 @@ def do_save(account, tasklist=[]):
episode_pattern = task["episode_naming"] episode_pattern = task["episode_naming"]
regex_pattern = task.get("regex_pattern") regex_pattern = task.get("regex_pattern")
# 找出符合剧集命名格式的文件 # 找出所有文件,无论是否符合命名格式
for file in file_nodes: display_files = [file["file_name"] for file in file_nodes]
if re.match(regex_pattern, file["file_name"]):
display_files.append(file["file_name"])
# 按剧集号排序 # 按剧集号排序
display_files.sort( display_files.sort(
@ -2454,7 +2836,6 @@ def do_save(account, tasklist=[]):
# 添加成功通知 # 添加成功通知
add_notify(f"✅《{task['taskname']}》 添加追更:") add_notify(f"✅《{task['taskname']}》 添加追更:")
if file_count > 0:
add_notify(f"/{task['savepath']}") add_notify(f"/{task['savepath']}")
# 打印文件列表 # 打印文件列表
@ -2463,12 +2844,9 @@ def do_save(account, tasklist=[]):
file_info = file_nodes[next((i for i, f in enumerate(file_nodes) if f["file_name"] == file_name), 0)] file_info = file_nodes[next((i for i, f in enumerate(file_nodes) if f["file_name"] == file_name), 0)]
icon = get_file_icon(file_name, is_dir=file_info.get("dir", False)) icon = get_file_icon(file_name, is_dir=file_info.get("dir", False))
add_notify(f"{prefix}{icon}{file_name}") add_notify(f"{prefix}{icon}{file_name}")
else:
add_notify(f"/{task['savepath']} (空目录)")
add_notify("") add_notify("")
# 添加正则命名模式的文件树显示逻辑
# 如果是正则模式且成功重命名,显示重命名后的文件列表 elif is_rename and not is_special_sequence and task.get("pattern") is not None:
elif is_rename and task.get("pattern") and task.get("replace") is not None:
# 重新获取文件列表 # 重新获取文件列表
savepath = re.sub(r"/{2,}", "/", f"/{task['savepath']}") savepath = re.sub(r"/{2,}", "/", f"/{task['savepath']}")
dir_file_list = account.ls_dir(account.savepath_fid[savepath]) dir_file_list = account.ls_dir(account.savepath_fid[savepath])
@ -2476,42 +2854,48 @@ def do_save(account, tasklist=[]):
# 过滤出非目录的文件 # 过滤出非目录的文件
file_nodes = [f for f in dir_file_list if not f["dir"]] file_nodes = [f for f in dir_file_list if not f["dir"]]
# 计算文件数量 # 创建一个映射列表,包含所有文件
file_count = len(file_nodes) display_files = [file["file_name"] for file in file_nodes]
# 获取正则模式和替换规则 # 按日期或任何数字排序 (复用local_sort_key函数逻辑)
pattern, replace = account.magic_regex_func( def extract_sort_value(file_name):
task.get("pattern", ""), task.get("replace", ""), task["taskname"] # 尝试提取日期格式优先YYYY-MM-DD格式
) date_match = re.search(r'(\d{4})[-./](\d{1,2})[-./](\d{1,2})', file_name)
if date_match:
year = int(date_match.group(1))
month = int(date_match.group(2))
day = int(date_match.group(3))
return year * 10000 + month * 100 + day
# 创建显示文件列表 # 尝试提取紧凑日期格式YYYYMMDD
display_files = [] compact_date_match = re.search(r'(\d{4})(\d{2})(\d{2})', file_name)
for file in file_nodes: if compact_date_match:
# 尝试应用正则替换,显示替换后的名称 year = int(compact_date_match.group(1))
try: month = int(compact_date_match.group(2))
new_name = re.sub(pattern, replace, file["file_name"]) day = int(compact_date_match.group(3))
# 为文件添加图标 return year * 10000 + month * 100 + day
icon = get_file_icon(file["file_name"], is_dir=file.get("dir", False))
display_files.append((f"{icon}{new_name}", file))
except Exception as e:
# 如果正则替换失败,使用原名
icon = get_file_icon(file["file_name"], is_dir=file.get("dir", False))
display_files.append((f"{icon}{file['file_name']}", file))
# 按文件名排序 # 尝试提取任何数字
display_files.sort(key=lambda x: x[0]) number_match = re.search(r'(\d+)', file_name)
if number_match:
return int(number_match.group(1))
# 添加成功通知,带文件数量图标 # 默认使用原文件名
return float('inf')
# 按提取的排序值进行排序
display_files.sort(key=extract_sort_value)
# 添加成功通知
add_notify(f"✅《{task['taskname']}》 添加追更:") add_notify(f"✅《{task['taskname']}》 添加追更:")
if file_count > 0:
add_notify(f"/{task['savepath']}") add_notify(f"/{task['savepath']}")
# 打印文件列表 # 打印文件列表
for idx, (display_name, _) in enumerate(display_files): for idx, file_name in enumerate(display_files):
prefix = "├── " if idx < len(display_files) - 1 else "└── " prefix = "├── " if idx < len(display_files) - 1 else "└── "
add_notify(f"{prefix}{display_name}") file_info = file_nodes[next((i for i, f in enumerate(file_nodes) if f["file_name"] == file_name), 0)]
else: icon = get_file_icon(file_name, is_dir=file_info.get("dir", False))
add_notify(f"/{task['savepath']} (空目录)") add_notify(f"{prefix}{icon}{file_name}")
add_notify("") add_notify("")
# 现在打印重命名日志 # 现在打印重命名日志