Merge pull request #67 from x1ao4/dev

修复剧集编号提取时某些技术规格未被正确过滤的问题,新增字幕命名规则功能
This commit is contained in:
x1ao4 2025-09-29 17:00:58 +08:00 committed by GitHub
commit 5bc9ec6349
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 126 additions and 7 deletions

View File

@ -19,6 +19,7 @@
- **任务视图**:支持在任务列表页面使用列表视图和海报视图两种视图浏览和管理任务,海报视图将通过 TMDB 与任务对应的电视节目进行匹配,并显示对应的节目海报。
- **任务匹配**:在配置了 TMDB API 密钥后,所有任务将自动通过 TMDB 进行元数据匹配,匹配后将获取电视节目的海报和元数据,丰富任务相关信息的展示,包括集数信息统计、当前任务进度和电视节目状态等。
- **追剧日历**:支持在追剧日历页面查看和浏览任务对应电视节目的信息和播出时间表,追踪电视节目的播出情况,了解任务的完成进度。追剧日历支持海报视图和日历视图两种视图,可方便快捷的了解订阅内容的实时状态。
- **字幕命名规则**:支持在全局设置字幕文件的语言代码后缀,在重命名字幕文件时自动添加语言代码后缀,如 `.zh.srt`、`.zh.ass` 等。
本项目修改后的版本为个人需求定制版,目的是满足我自己的使用需求,某些(我不用的)功能可能会因为修改而出现 BUG不一定会被修复。若你要使用本项目请知晓本人不是程序员我无法保证本项目的稳定性如果你在使用过程中发现了 BUG可以在 Issues 中提交,但不保证每个 BUG 都能被修复,请谨慎使用,风险自担。

View File

@ -44,7 +44,7 @@ from quark_auto_save import Config, format_bytes
# 添加导入全局extract_episode_number和sort_file_by_name函数
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from quark_auto_save import extract_episode_number, sort_file_by_name, chinese_to_arabic, is_date_format
from quark_auto_save import extract_episode_number, sort_file_by_name, chinese_to_arabic, is_date_format, apply_subtitle_naming_rule
# 导入豆瓣服务
from sdk.douban_service import douban_service
@ -2105,6 +2105,9 @@ def get_share_detail():
else:
# 替换所有的{}为当前序号
file["file_name_re"] = sequence_pattern.replace("{}", f"{current_sequence:02d}") + file_ext
# 应用字幕命名规则
file["file_name_re"] = apply_subtitle_naming_rule(file["file_name_re"], config_data["task_settings"])
current_sequence += 1
return share_detail
@ -2143,6 +2146,8 @@ def get_share_detail():
if episode_num is not None:
file["file_name_re"] = episode_pattern.replace("[]", f"{episode_num:02d}") + extension
# 应用字幕命名规则
file["file_name_re"] = apply_subtitle_naming_rule(file["file_name_re"], config_data["task_settings"])
# 添加episode_number字段用于前端排序
file["episode_number"] = episode_num
else:
@ -2177,6 +2182,8 @@ def get_share_detail():
item["file_name_re"] = (
re.sub(pattern, replace, file_name) if replace != "" else file_name
)
# 应用字幕命名规则
item["file_name_re"] = apply_subtitle_naming_rule(item["file_name_re"], config_data["task_settings"])
return share_detail
share_detail = preview_regex(share_detail)
@ -3383,6 +3390,8 @@ def preview_rename():
for file in filtered_files:
extension = os.path.splitext(file["file_name"])[1] if not file["dir"] else ""
new_name = pattern.replace("{}", f"{sequence:02d}") + extension
# 应用字幕命名规则
new_name = apply_subtitle_naming_rule(new_name, config_data["task_settings"])
preview_results.append({
"original_name": file["file_name"],
"new_name": new_name,
@ -3420,6 +3429,8 @@ def preview_rename():
if episode_num is not None:
new_name = pattern.replace("[]", f"{episode_num:02d}") + extension
# 应用字幕命名规则
new_name = apply_subtitle_naming_rule(new_name, config_data["task_settings"])
preview_results.append({
"original_name": file["file_name"],
"new_name": new_name,
@ -3439,10 +3450,14 @@ def preview_rename():
# 应用正则表达式
if replace:
new_name = re.sub(pattern, replace, file["file_name"])
# 应用字幕命名规则
new_name = apply_subtitle_naming_rule(new_name, config_data["task_settings"])
else:
# 如果没有提供替换表达式,则检查是否匹配
if re.search(pattern, file["file_name"]):
new_name = file["file_name"] # 匹配但不替换
# 应用字幕命名规则
new_name = apply_subtitle_naming_rule(new_name, config_data["task_settings"])
else:
new_name = "" # 表示不匹配

View File

@ -35,7 +35,7 @@ function sortFileByName(file) {
try {
const techSpecs = [
// 分辨率相关限定常见p档
/\b(?:240|360|480|540|720|900|960|1080|1440|2160|4320)[pP]\b/g,
/(?:240|360|480|540|720|900|960|1080|1440|2160|4320)[pP]/g,
// 常见分辨率 WxH白名单
/\b(?:640x360|640x480|720x480|720x576|854x480|960x540|1024x576|1280x720|1280x800|1280x960|1366x768|1440x900|1600x900|1920x1080|2560x1080|2560x1440|3440x1440|3840x1600|3840x2160|4096x2160|7680x4320)\b/g,
/(?<!\d)[248]\s*[Kk](?!\d)/g, // 2K/4K/8K
@ -58,6 +58,8 @@ function sortFileByName(file) {
/\b(?:8|10|12)\s*[Bb][Ii][Tt]s?\b/g,
// 严格限定常见帧率,避免将 "07.30FPS" 视为帧率从而连带清除集数
/\b(?:23\.976|29\.97|59\.94|24|25|30|50|60|90|120)\s*[Ff][Pp][Ss]\b/g,
// 无空格的帧率格式(如 60fps, 30fps 等)
/(?:23\.976|29\.97|59\.94|24|25|30|50|60|90|120)[Ff][Pp][Ss]/g,
// 频率相关(白名单,含空格/无空格)
/\b(?:100|144|200|240|400|800)\s*[Mm][Hh][Zz]\b/g,

View File

@ -788,6 +788,20 @@
</div>
</div>
<div class="form-group">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">字幕命名规则</span>
</div>
<input type="text" class="form-control" v-model="formData.task_settings.subtitle_naming_rule" placeholder="zh" title="设置字幕文件的语言代码,在重命名字幕文件时会在文件名末尾添加此代码">
<div class="input-group-append" title="启用后,重命名字幕文件时会在文件名末尾添加语言代码后缀">
<div class="input-group-text">
<input type="checkbox" v-model="formData.task_settings.subtitle_add_language_code">&nbsp;添加语言代码
</div>
</div>
</div>
</div>
<div class="form-group">
<div class="input-group">
<div class="input-group-prepend">
@ -2831,7 +2845,9 @@
movie_naming_pattern: "^(.*)\\.([^.]+)",
movie_naming_replace: "片名 (年份).\\2",
tv_naming_rule: "剧名 - S季数E[]",
tv_ignore_extension: true
tv_ignore_extension: true,
subtitle_naming_rule: "zh",
subtitle_add_language_code: false
},
source: {
cloudsaver: {
@ -6935,6 +6951,8 @@
movie_naming_replace: "片名 (年份).\\2",
tv_naming_rule: "剧名 - S季数E[]",
tv_ignore_extension: true,
subtitle_naming_rule: "zh",
subtitle_add_language_code: false,
auto_search_resources: "enabled"
};
}
@ -6949,6 +6967,13 @@
if (config_data.task_settings.tv_ignore_extension === undefined) {
config_data.task_settings.tv_ignore_extension = true;
}
// 为字幕命名规则添加默认值(向后兼容)
if (config_data.task_settings.subtitle_naming_rule === undefined) {
config_data.task_settings.subtitle_naming_rule = "zh";
}
if (config_data.task_settings.subtitle_add_language_code === undefined) {
config_data.task_settings.subtitle_add_language_code = false;
}
// 确保自动搜索资源设置存在
if (!config_data.task_settings.auto_search_resources) {
config_data.task_settings.auto_search_resources = "enabled";

View File

@ -507,7 +507,7 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None):
filename_without_dates = file_name_without_ext
tech_spec_patterns = [
# 分辨率相关限定常见p档
r'\b(?:240|360|480|540|720|900|960|1080|1440|2160|4320)[pP]\b',
r'(?:240|360|480|540|720|900|960|1080|1440|2160|4320)[pP]',
# 常见分辨率 WxH白名单
r'\b(?:640x360|640x480|720x480|720x576|854x480|960x540|1024x576|1280x720|1280x800|1280x960|1366x768|1440x900|1600x900|1920x1080|2560x1080|2560x1440|3440x1440|3840x1600|3840x2160|4096x2160|7680x4320)\b',
r'(?<!\d)[248]\s*[Kk](?!\d)', # 匹配 2K/4K/8K
@ -530,6 +530,8 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None):
r'\b(?:8|10|12)\s*[Bb][Ii][Tt]s?\b',
# 严格限定常见帧率,避免将 "07.30FPS" 视为帧率从而连带清除集数
r'\b(?:23\.976|29\.97|59\.94|24|25|30|50|60|90|120)\s*[Ff][Pp][Ss]\b',
# 无空格的帧率格式(如 60fps, 30fps 等)
r'(?:23\.976|29\.97|59\.94|24|25|30|50|60|90|120)[Ff][Pp][Ss]',
# 频率相关
# 频率相关(白名单,含空格/无空格)
@ -712,8 +714,8 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None):
r'([一二三四五六七八九十百千万零两]+)期',
r'([一二三四五六七八九十百千万零两]+)话',
# 先匹配"前方有分隔符"的数字,避免后一个规则优先命中单字符
r'[- _\s\.]([0-9]+)(?:[^0-9]|$)',
r'(?:^|[^0-9])(\d+)(?=[- _\s\.][^0-9])',
r'[- _\s\.]([0-9]+)',
r'(?:^|[^0-9])(\d+)(?=[- _\s\.])',
# 新增:文件名起始纯数字后接非数字(如 1094(1).mp4
r'^(\d+)(?=\D)'
]
@ -1081,7 +1083,7 @@ def get_file_icon(file_name, is_dir=False):
return "📝"
# 字幕文件
if any(lower_name.endswith(ext) for ext in ['.srt', '.ass', '.ssa', '.vtt']):
if any(lower_name.endswith(ext) for ext in ['.srt', '.ass', '.ssa', '.vtt', '.sup']):
return "💬"
# 歌词文件
@ -1106,6 +1108,54 @@ def remove_file_icons(filename):
return clean_name
# 定义一个函数来检测字幕文件并应用语言代码后缀
def apply_subtitle_naming_rule(filename, task_settings):
"""
检测字幕文件并应用语言代码后缀
Args:
filename: 原始文件名
task_settings: 任务设置包含字幕命名规则配置
Returns:
处理后的文件名
"""
# 从任务设置中获取字幕命名规则配置
subtitle_add_language_code = task_settings.get("subtitle_add_language_code", False)
subtitle_naming_rule = task_settings.get("subtitle_naming_rule", "zh")
# 如果任务设置中没有配置,尝试从全局配置中获取
if not subtitle_add_language_code:
try:
if 'CONFIG_DATA' in globals() and CONFIG_DATA:
global_task_settings = CONFIG_DATA.get("task_settings", {})
subtitle_add_language_code = global_task_settings.get("subtitle_add_language_code", False)
subtitle_naming_rule = global_task_settings.get("subtitle_naming_rule", "zh")
except (KeyError, AttributeError, TypeError):
# 配置获取失败,使用默认值
pass
# 检查是否启用了字幕命名规则
if not subtitle_add_language_code or not subtitle_naming_rule:
return filename
# 检测是否为字幕文件
lower_name = filename.lower()
subtitle_extensions = ['.srt', '.ass', '.ssa', '.vtt', '.sup']
if not any(lower_name.endswith(ext) for ext in subtitle_extensions):
return filename
# 分离文件名和扩展名
name_without_ext, ext = os.path.splitext(filename)
# 检查是否已经包含语言代码后缀,避免重复添加
if f".{subtitle_naming_rule}" in name_without_ext:
return filename
# 添加语言代码后缀
return f"{name_without_ext}.{subtitle_naming_rule}{ext}"
class Config:
# 下载配置
@ -2559,6 +2609,9 @@ class Quark:
# 生成新文件名
save_name = sequence_pattern.replace("{}", f"{current_sequence:02d}") + file_ext
# 应用字幕命名规则
save_name = apply_subtitle_naming_rule(save_name, task)
# 检查目标目录是否已存在此文件,支持忽略后缀选项
if task.get("ignore_extension", False):
# 忽略后缀模式:只比较文件名部分,不比较扩展名
@ -2761,6 +2814,9 @@ class Quark:
target_name = f"{episode_num:02d}{file_ext}"
else:
target_name = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext
# 应用字幕命名规则
target_name = apply_subtitle_naming_rule(target_name, task)
# 检查目标文件名是否已存在,支持忽略后缀选项
if task.get("ignore_extension", False):
@ -2902,6 +2958,8 @@ class Quark:
# 尝试应用正则替换
if re.search(pattern, original_file_name):
renamed_file = re.sub(pattern, replace, original_file_name)
# 应用字幕命名规则
renamed_file = apply_subtitle_naming_rule(renamed_file, task)
renamed_base = os.path.splitext(renamed_file)[0]
else:
renamed_file = None
@ -3056,6 +3114,9 @@ class Quark:
else share_file["file_name"]
)
# 应用字幕命名规则
save_name = apply_subtitle_naming_rule(save_name, task)
# 检查新名称是否存在重复的前缀
if replace and " - " in save_name:
parts = save_name.split(" - ")
@ -3507,6 +3568,9 @@ class Quark:
else:
save_name = sequence_pattern.replace("{}", f"{current_sequence:02d}") + file_ext
# 应用字幕命名规则
save_name = apply_subtitle_naming_rule(save_name, task)
# 检查是否需要重命名,支持忽略后缀选项
name_conflict = False
if task.get("ignore_extension", False):
@ -3699,6 +3763,8 @@ class Quark:
new_name = f"{episode_num:02d}{file_ext}"
else:
new_name = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext
# 应用字幕命名规则
new_name = apply_subtitle_naming_rule(new_name, task)
else:
new_name = None
@ -3773,6 +3839,9 @@ class Quark:
else:
save_name = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext
# 应用字幕命名规则
save_name = apply_subtitle_naming_rule(save_name, task)
# 检查过滤词
should_filter = False
if task.get("filterwords"):
@ -3985,12 +4054,16 @@ class Quark:
# 使用完整的剧集号识别逻辑,而不是简单的纯数字判断
# 生成新文件名
new_name = f"{episode_num:02d}{file_ext}"
# 应用字幕命名规则
new_name = apply_subtitle_naming_rule(new_name, task)
# 只有当当前文件名与目标文件名不同时才重命名
if dir_file["file_name"] != new_name:
rename_operations.append((dir_file, new_name, episode_num))
else:
# 生成目标文件名
new_name = episode_pattern.replace("[]", f"{episode_num:02d}") + file_ext
# 应用字幕命名规则
new_name = apply_subtitle_naming_rule(new_name, task)
# 检查文件名是否已经符合目标格式
if dir_file["file_name"] != new_name:
rename_operations.append((dir_file, new_name, episode_num))
@ -4138,6 +4211,9 @@ class Quark:
# 应用正则表达式获取目标文件名
new_name = re.sub(pattern, replace, orig_name)
# 应用字幕命名规则
new_name = apply_subtitle_naming_rule(new_name, task)
# 如果替换后的文件名没有变化,跳过
if new_name == orig_name:
continue