From 4b40560a806ae8a914b6e94006e6a4de64792a37 Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Mon, 22 Sep 2025 23:00:08 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=88=86=E4=BA=AB?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E7=8A=B6=E6=80=81=E5=BC=82=E5=B8=B8=E5=90=8E?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=B8=8A/=E4=B8=8B=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E6=97=B6=E4=BB=8D=E6=98=BE=E7=A4=BA=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E6=8F=90=E7=A4=BA=E3=80=81=E6=97=A0=E6=B3=95=E7=BB=A7?= =?UTF-8?q?=E7=BB=AD=E6=B5=8F=E8=A7=88=E5=85=B6=E4=BB=96=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 切换资源时清理 error/fileList/paths,重置到选择目录界面 - 链接基础部分变化时清空 stoken 强制刷新 - getShareDetail 调用前清空错误状态,避免残留覆盖新资源展示 --- app/templates/index.html | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/app/templates/index.html b/app/templates/index.html index 5f0b19d..7fc52c0 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -8112,6 +8112,23 @@ this.smart_param.currentResourceIndex--; const previousResource = this.smart_param.taskSuggestions.data[this.smart_param.currentResourceIndex]; if (previousResource) { + // 在切换资源时,重置展示相关状态,避免错误提示残留 + this.fileSelect.error = undefined; + this.fileSelect.fileList = []; + this.fileSelect.paths = []; + // 确保处于“选择需转存的文件夹”界面 + this.fileSelect.previewRegex = false; + this.fileSelect.selectDir = true; + // 如果基础链接变化,清空stoken强制刷新 + try { + const oldBase = this.getShareurl(this.fileSelect.shareurl); + const newBase = this.getShareurl(previousResource.shareurl); + if (oldBase !== newBase) { + this.fileSelect.stoken = ""; + } + } catch (e) { + this.fileSelect.stoken = ""; + } // 更新当前分享链接并重新加载内容 this.fileSelect.shareurl = previousResource.shareurl; this.getShareDetail(0, 1); @@ -8124,6 +8141,23 @@ this.smart_param.currentResourceIndex++; const nextResource = this.smart_param.taskSuggestions.data[this.smart_param.currentResourceIndex]; if (nextResource) { + // 在切换资源时,重置展示相关状态,避免错误提示残留 + this.fileSelect.error = undefined; + this.fileSelect.fileList = []; + this.fileSelect.paths = []; + // 确保处于“选择需转存的文件夹”界面 + this.fileSelect.previewRegex = false; + this.fileSelect.selectDir = true; + // 如果基础链接变化,清空stoken强制刷新 + try { + const oldBase = this.getShareurl(this.fileSelect.shareurl); + const newBase = this.getShareurl(nextResource.shareurl); + if (oldBase !== newBase) { + this.fileSelect.stoken = ""; + } + } catch (e) { + this.fileSelect.stoken = ""; + } // 更新当前分享链接并重新加载内容 this.fileSelect.shareurl = nextResource.shareurl; this.getShareDetail(0, 1); @@ -8370,6 +8404,8 @@ } }, getShareDetail(retryCount = 0, maxRetries = 1) { + // 切换或重试前清理残留错误提示,避免覆盖新资源展示 + this.fileSelect.error = undefined; this.modalLoading = true; // 检查index是否有效,如果无效则使用默认值 From 06b6ab04d46ab8cb4b007d0bf87edaca12910b00 Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Mon, 22 Sep 2025 23:17:06 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E4=B8=BA=E8=BF=BD=E5=89=A7=E6=97=A5?= =?UTF-8?q?=E5=8E=86=E7=9A=84=E6=B5=B7=E6=8A=A5=E8=A7=86=E5=9B=BE=E5=92=8C?= =?UTF-8?q?=E6=97=A5=E5=8E=86=E8=A7=86=E5=9B=BE=E5=A2=9E=E5=8A=A0=E6=8B=BC?= =?UTF-8?q?=E9=9F=B3=E6=8E=92=E5=BA=8F=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 getEpisodesByDate 中对当日节目结果进行拼音升序排序 - 合并集开启时先合并同节目多集,再按节目名拼音排序 - 与内容管理视图排序规则一致,提升一致性与可预期性 - 增加 try-catch 保护,异常时回退原顺序,保证稳定性 --- app/templates/index.html | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/app/templates/index.html b/app/templates/index.html index 7fc52c0..ac91831 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -4777,11 +4777,23 @@ }); // 如果启用了合并集功能,则进行合并处理 - if (this.calendar.mergeEpisodes) { - return this.mergeEpisodesByShow(filteredEpisodes); - } - - return filteredEpisodes; + let result = this.calendar.mergeEpisodes + ? this.mergeEpisodesByShow(filteredEpisodes) + : filteredEpisodes; + + // 统一对同一天节目按节目名称拼音排序(与内容管理一致) + try { + result = result.slice().sort((a, b) => { + const an = (a && a.show_name) ? String(a.show_name) : ''; + const bn = (b && b.show_name) ? String(b.show_name) : ''; + const ak = pinyinPro.pinyin(an, { toneType: 'none', type: 'string' }).toLowerCase(); + const bk = pinyinPro.pinyin(bn, { toneType: 'none', type: 'string' }).toLowerCase(); + if (ak === bk) return 0; + return ak > bk ? 1 : -1; + }); + } catch (e) {} + + return result; }, // 根据剧集名称查找对应的任务 From f7371b660b03d7c09a5a42854e241582cc3d29c5 Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Mon, 22 Sep 2025 23:37:34 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E5=B0=86=20TMDB=20=E5=8F=8A=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E9=87=8D=E8=AF=95=E6=97=A5=E5=BF=97=E7=9A=84=E8=BE=93?= =?UTF-8?q?=E5=87=BA=E8=B0=83=E6=95=B4=E4=B8=BA=20DEBUG=20=E7=BA=A7?= =?UTF-8?q?=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tmdb_service 中主/备地址失败与切换日志改为 debug - run.py 设置 urllib3/requests 日志级别为 ERROR - 为 urllib3.connectionpool 添加过滤器,自动将 “Retrying (...)” 告警降为 DEBUG --- app/run.py | 21 +++++++++++++++++++++ app/sdk/tmdb_service.py | 16 ++++++++-------- app/templates/index.html | 2 +- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/app/run.py b/app/run.py index 72c5607..7a6b361 100644 --- a/app/run.py +++ b/app/run.py @@ -586,6 +586,27 @@ logging.basicConfig( format="[%(asctime)s][%(levelname)s] %(message)s", datefmt="%m-%d %H:%M:%S", ) +# 降低第三方网络库的重试噪音:将 urllib3/requests 的日志调为 ERROR,并把“Retrying ...”消息降级为 DEBUG +try: + logging.getLogger("urllib3").setLevel(logging.ERROR) + logging.getLogger("requests").setLevel(logging.ERROR) + + class _RetryWarningToDebug(logging.Filter): + def filter(self, record: logging.LogRecord) -> bool: + try: + msg = str(record.getMessage()) + if "Retrying (Retry(" in msg or "Retrying (" in msg: + # 将此条记录的等级降到 DEBUG + record.levelno = logging.DEBUG + record.levelname = "DEBUG" + except Exception: + pass + return True + + _urllib3_logger = logging.getLogger("urllib3.connectionpool") + _urllib3_logger.addFilter(_RetryWarningToDebug()) +except Exception: + pass # 过滤werkzeug日志输出 if not DEBUG: logging.getLogger("werkzeug").setLevel(logging.ERROR) diff --git a/app/sdk/tmdb_service.py b/app/sdk/tmdb_service.py index af6e8d5..8d443ff 100644 --- a/app/sdk/tmdb_service.py +++ b/app/sdk/tmdb_service.py @@ -41,7 +41,7 @@ class TMDBService: def reset_to_primary_url(self): """重置到主API地址""" self.current_url = self.primary_url - logger.info("TMDB API地址已重置为主地址") + logger.debug("TMDB API地址已重置为主地址") def get_current_api_url(self) -> str: """获取当前使用的API地址""" @@ -87,17 +87,17 @@ class TMDBService: pass return data except Exception as e: - logger.warning(f"TMDB主地址请求失败: {e}") + logger.debug(f"TMDB主地址请求失败: {e}") # 如果当前使用的是主地址,尝试切换到备用地址 if self.current_url == self.primary_url: - logger.info("尝试切换到TMDB备用地址...") + logger.debug("尝试切换到TMDB备用地址...") self.current_url = self.backup_url try: url = f"{self.current_url}{endpoint}" response = self.session.get(url, params=params, timeout=10) response.raise_for_status() - logger.info("TMDB备用地址连接成功") + logger.debug("TMDB备用地址连接成功") data = response.json() try: self._cache[cache_key] = (_now(), data) @@ -111,7 +111,7 @@ class TMDBService: return None else: # 如果备用地址也失败,重置回主地址 - logger.error(f"TMDB备用地址请求失败: {e}") + logger.debug(f"TMDB备用地址请求失败: {e}") self.current_url = self.primary_url return None @@ -186,7 +186,7 @@ class TMDBService: return original_title except Exception as e: - logger.warning(f"获取中文标题失败: {e}, 使用原始标题: {original_title}") + logger.debug(f"获取中文标题失败: {e}, 使用原始标题: {original_title}") return original_title def get_tv_show_episodes(self, tv_id: int, season_number: int) -> Optional[Dict]: @@ -458,7 +458,7 @@ class TMDBService: if poster_path: return poster_path except Exception as e: - logger.warning(f"获取原始语言海报失败: {e}") + logger.debug(f"获取原始语言海报失败: {e}") # 如果设置为中文或原始语言获取失败,尝试中文海报 if self.poster_language == "zh-CN" or self.poster_language == "original": @@ -477,7 +477,7 @@ class TMDBService: if poster_path: return poster_path except Exception as e: - logger.warning(f"获取中文海报失败: {e}") + logger.debug(f"获取中文海报失败: {e}") # 如果都失败了,返回默认海报路径 return details.get('poster_path', '') diff --git a/app/templates/index.html b/app/templates/index.html index ac91831..d72889b 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1886,7 +1886,7 @@
名称筛选
- +
From 27ee3948b8b72c4f4787778813ed49bd19dee1b3 Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Tue, 23 Sep 2025 09:14:04 +0800 Subject: [PATCH 04/10] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=9B=86=E7=BC=96?= =?UTF-8?q?=E5=8F=B7=E8=AF=86=E5=88=AB=E8=A7=84=E5=88=99=E4=B8=8E=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E4=BF=AE=E5=A4=8D=E6=96=87=E4=BB=B6=E6=95=B4?= =?UTF-8?q?=E7=90=86=E9=A1=B5=E9=9D=A2=E5=91=BD=E5=90=8D=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E6=A8=A1=E6=80=81=E6=A1=86=E9=87=8D=E5=91=BD=E5=90=8D=E5=88=97?= =?UTF-8?q?=E7=9A=84=E6=8E=92=E5=BA=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/run.py | 177 ++++++++++++++++++++----------- app/templates/index.html | 58 +++++++--- quark_auto_save.py | 223 +++++++++++++++++++++++++++------------ quark_config.json | 6 +- 4 files changed, 317 insertions(+), 147 deletions(-) diff --git a/app/run.py b/app/run.py index 7a6b361..d285e89 100644 --- a/app/run.py +++ b/app/run.py @@ -144,7 +144,7 @@ def enrich_tasks_with_calendar_meta(tasks_info: list) -> list: except Exception: transferred_by_task = {} - # 统计“已播出集数”:读取本地 episodes 表中有 air_date 且 <= 今天的集数 + # 统计"已播出集数":读取本地 episodes 表中有 air_date 且 <= 今天的集数 from datetime import datetime as _dt today = _dt.now().strftime('%Y-%m-%d') aired_by_show_season = {} @@ -586,7 +586,7 @@ logging.basicConfig( format="[%(asctime)s][%(levelname)s] %(message)s", datefmt="%m-%d %H:%M:%S", ) -# 降低第三方网络库的重试噪音:将 urllib3/requests 的日志调为 ERROR,并把“Retrying ...”消息降级为 DEBUG +# 降低第三方网络库的重试噪音:将 urllib3/requests 的日志调为 ERROR,并把"Retrying ..."消息降级为 DEBUG try: logging.getLogger("urllib3").setLevel(logging.ERROR) logging.getLogger("requests").setLevel(logging.ERROR) @@ -2113,32 +2113,15 @@ def get_share_detail(): episode_pattern = regex.get("episode_naming") episode_patterns = regex.get("episode_patterns", []) - # 获取默认的剧集模式 - default_episode_pattern = {"regex": '第(\\d+)集|第(\\d+)期|第(\\d+)话|(\\d+)集|(\\d+)期|(\\d+)话|[Ee][Pp]?(\\d+)|(\\d+)[-_\\s]*4[Kk]|\\[(\\d+)\\]|【(\\d+)】|_?(\\d+)_?'} - - # 获取配置的剧集模式,确保每个模式都是字典格式 + # 获取用户补充的剧集模式(默认模式由后端内部提供,这里只处理用户补充) episode_patterns = [] - raw_patterns = config_data.get("episode_patterns", [default_episode_pattern]) + raw_patterns = config_data.get("episode_patterns", []) for p in raw_patterns: if isinstance(p, dict) and p.get("regex"): episode_patterns.append(p) elif isinstance(p, str): episode_patterns.append({"regex": p}) - - # 如果没有有效的模式,使用默认模式 - if not episode_patterns: - episode_patterns = [default_episode_pattern] - # 添加中文数字匹配模式 - chinese_patterns = [ - {"regex": r'第([一二三四五六七八九十百千万零两]+)集'}, - {"regex": r'第([一二三四五六七八九十百千万零两]+)期'}, - {"regex": r'第([一二三四五六七八九十百千万零两]+)话'}, - {"regex": r'([一二三四五六七八九十百千万零两]+)集'}, - {"regex": r'([一二三四五六七八九十百千万零两]+)期'}, - {"regex": r'([一二三四五六七八九十百千万零两]+)话'} - ] - episode_patterns.extend(chinese_patterns) # 应用高级过滤词过滤 filterwords = regex.get("filterwords", "") @@ -2558,6 +2541,9 @@ def init(): # 读取配置 config_data = Config.read_json(CONFIG_PATH) Config.breaking_change_update(config_data) + + # 自动清理剧集识别规则配置 + cleanup_episode_patterns_config(config_data) # 默认管理账号 config_data["webui"] = { @@ -3400,55 +3386,46 @@ def preview_rename(): elif naming_mode == "episode": # 剧集命名模式 - # 获取默认的剧集模式 - default_episode_pattern = {"regex": '第(\\d+)集|第(\\d+)期|第(\\d+)话|(\\d+)集|(\\d+)期|(\\d+)话|[Ee][Pp]?(\\d+)|(\\d+)[-_\\s]*4[Kk]|\\[(\\d+)\\]|【(\\d+)】|_?(\\d+)_?'} - - # 获取配置的剧集模式,确保每个模式都是字典格式 + # 获取用户补充的剧集模式(默认模式由后端内部提供,这里只处理用户补充) episode_patterns = [] - raw_patterns = config_data.get("episode_patterns", [default_episode_pattern]) + raw_patterns = config_data.get("episode_patterns", []) for p in raw_patterns: if isinstance(p, dict) and p.get("regex"): episode_patterns.append(p) elif isinstance(p, str): episode_patterns.append({"regex": p}) - - # 如果没有有效的模式,使用默认模式 - if not episode_patterns: - episode_patterns = [default_episode_pattern] - # 添加中文数字匹配模式 - chinese_patterns = [ - {"regex": r'第([一二三四五六七八九十百千万零两]+)集'}, - {"regex": r'第([一二三四五六七八九十百千万零两]+)期'}, - {"regex": r'第([一二三四五六七八九十百千万零两]+)话'}, - {"regex": r'([一二三四五六七八九十百千万零两]+)集'}, - {"regex": r'([一二三四五六七八九十百千万零两]+)期'}, - {"regex": r'([一二三四五六七八九十百千万零两]+)话'} - ] - episode_patterns.extend(chinese_patterns) - # 处理每个文件 + # 应用高级过滤词过滤(filterwords 已在函数开头获取) + if filterwords: + # 使用高级过滤函数 + filtered_files = advanced_filter_files(filtered_files, filterwords) + # 标记被过滤的文件 + for item in filtered_files: + if item not in filtered_files: + item["filtered"] = True + + # 处理未被过滤的文件 for file in filtered_files: - extension = os.path.splitext(file["file_name"])[1] if not file["dir"] else "" - # 从文件名中提取集号 - episode_num = extract_episode_number(file["file_name"], episode_patterns=episode_patterns) - - if episode_num is not None: - new_name = pattern.replace("[]", f"{episode_num:02d}") + extension - preview_results.append({ - "original_name": file["file_name"], - "new_name": new_name, - "file_id": file["fid"], - "episode_number": episode_num # 添加集数字段用于前端排序 - }) - else: - # 没有提取到集号,显示无法识别的提示 - preview_results.append({ - "original_name": file["file_name"], - "new_name": "× 无法识别剧集编号", - "file_id": file["fid"] - }) - + if not file["dir"] and not file.get("filtered"): # 只处理未被过滤的非目录文件 + extension = os.path.splitext(file["file_name"])[1] + # 从文件名中提取集号 + episode_num = extract_episode_number(file["file_name"], episode_patterns=episode_patterns) + + if episode_num is not None: + new_name = pattern.replace("[]", f"{episode_num:02d}") + extension + preview_results.append({ + "original_name": file["file_name"], + "new_name": new_name, + "file_id": file["fid"] + }) + else: + # 没有提取到集号,显示无法识别的提示 + preview_results.append({ + "original_name": file["file_name"], + "new_name": "× 无法识别剧集编号", + "file_id": file["fid"] + }) else: # 正则命名模式 for file in filtered_files: @@ -5682,6 +5659,84 @@ def get_content_types(): 'message': f'获取节目内容类型失败: {str(e)}' }) +def cleanup_episode_patterns_config(config_data): + """清理剧集识别规则配置""" + try: + # 需要清理的默认剧集识别规则(按部分规则匹配) + default_pattern_parts = [ + "第(\\d+)集", + "第(\\d+)期", + "第(\\d+)话", + "(\\d+)集", + "(\\d+)期", + "(\\d+)话", + "[Ee][Pp]?(\\d+)", + "(\\d+)[-_\\s]*4[Kk]", + "(\\d+)[-_\\\\s]*4[Kk]", + "\\[(\\d+)\\]", + "【(\\d+)】", + "_?(\\d+)_?" + ] + + cleaned_tasks = 0 + cleaned_global = False + + # 1. 清理任务级别的 config_data.episode_patterns + if 'tasklist' in config_data: + for task in config_data['tasklist']: + if 'config_data' in task and 'episode_patterns' in task['config_data']: + del task['config_data']['episode_patterns'] + cleaned_tasks += 1 + # 如果 config_data 为空,删除整个 config_data + if not task['config_data']: + del task['config_data'] + + # 2. 清理全局配置中的默认规则 + if 'episode_patterns' in config_data: + current_patterns = config_data['episode_patterns'] + if isinstance(current_patterns, list): + # 过滤掉包含默认规则的配置 + filtered_patterns = [] + for pattern in current_patterns: + if isinstance(pattern, dict) and 'regex' in pattern: + pattern_regex = pattern['regex'] + # 用竖线分割规则 + pattern_parts = pattern_regex.split('|') + # 过滤掉默认规则部分,保留自定义规则 + custom_parts = [part.strip() for part in pattern_parts if part.strip() not in default_pattern_parts] + + if custom_parts: + # 如果有自定义规则,保留并重新组合 + filtered_patterns.append({ + 'regex': '|'.join(custom_parts) + }) + elif isinstance(pattern, str): + pattern_regex = pattern + # 用竖线分割规则 + pattern_parts = pattern_regex.split('|') + # 过滤掉默认规则部分,保留自定义规则 + custom_parts = [part.strip() for part in pattern_parts if part.strip() not in default_pattern_parts] + + if custom_parts: + # 如果有自定义规则,保留并重新组合 + filtered_patterns.append('|'.join(custom_parts)) + + # 更新配置 + if filtered_patterns: + config_data['episode_patterns'] = filtered_patterns + else: + # 如果没有剩余规则,清空配置 + config_data['episode_patterns'] = [] + cleaned_global = True + + # 静默执行清理操作,不输出日志 + + return True + + except Exception as e: + logging.error(f"清理剧集识别规则配置失败: {str(e)}") + return False + if __name__ == "__main__": init() reload_tasks() diff --git a/app/templates/index.html b/app/templates/index.html index d72889b..c9a9b60 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -692,7 +692,7 @@ -
+

剧集识别

@@ -705,7 +705,7 @@
集编号识别规则
- +
@@ -3256,11 +3256,15 @@ return this.formData.episode_patterns.map(p => p.regex || '').join('|'); }, set(value) { - // 允许直接输入正则表达式,当用户按下Enter键或失焦时再处理 - // 这里我们创建一个单一的正则表达式对象,而不是拆分 - this.formData.episode_patterns = [{ - regex: value.trim() - }]; + // 支持竖线分割的多个正则表达式 + if (!value || value.trim() === '') { + this.formData.episode_patterns = []; + return; + } + + // 按竖线分割并创建多个正则表达式对象 + const patterns = value.split('|').map(p => p.trim()).filter(p => p !== ''); + this.formData.episode_patterns = patterns.map(regex => ({ regex })); } }, // 管理视图:按任务名(拼音)排序并应用顶部筛选 @@ -9689,9 +9693,23 @@ bValue = String(b.episode_number); } } else { - // 否则使用重命名后的文件名进行拼音排序 - aValue = pinyinPro.pinyin(a.file_name_re || '', { toneType: 'none', type: 'string' }).toLowerCase(); - bValue = pinyinPro.pinyin(b.file_name_re || '', { toneType: 'none', type: 'string' }).toLowerCase(); + // 否则尝试从重命名结果中提取数字进行数值排序 + const aRename = a.file_name_re || ''; + const bRename = b.file_name_re || ''; + + // 尝试提取数字(包括小数) + const aMatch = aRename.match(/(\d+(?:\.\d+)?)/); + const bMatch = bRename.match(/(\d+(?:\.\d+)?)/); + + if (aMatch && bMatch) { + // 如果都能提取到数字,进行数值比较 + aValue = parseFloat(aMatch[1]); + bValue = parseFloat(bMatch[1]); + } else { + // 否则使用重命名后的文件名进行拼音排序 + aValue = pinyinPro.pinyin(aRename, { toneType: 'none', type: 'string' }).toLowerCase(); + bValue = pinyinPro.pinyin(bRename, { toneType: 'none', type: 'string' }).toLowerCase(); + } } if (this.fileSelect.sortOrder === 'asc') { @@ -9820,9 +9838,23 @@ bValue = String(b.episode_number); } } else { - // 否则使用重命名后的文件名进行拼音排序 - aValue = pinyinPro.pinyin(a.file_name_re || '', { toneType: 'none', type: 'string' }).toLowerCase(); - bValue = pinyinPro.pinyin(b.file_name_re || '', { toneType: 'none', type: 'string' }).toLowerCase(); + // 否则尝试从重命名结果中提取数字进行数值排序 + const aRename = a.file_name_re || ''; + const bRename = b.file_name_re || ''; + + // 尝试提取数字(包括小数) + const aMatch = aRename.match(/(\d+(?:\.\d+)?)/); + const bMatch = bRename.match(/(\d+(?:\.\d+)?)/); + + if (aMatch && bMatch) { + // 如果都能提取到数字,进行数值比较 + aValue = parseFloat(aMatch[1]); + bValue = parseFloat(bMatch[1]); + } else { + // 否则使用重命名后的文件名进行拼音排序 + aValue = pinyinPro.pinyin(aRename, { toneType: 'none', type: 'string' }).toLowerCase(); + bValue = pinyinPro.pinyin(bRename, { toneType: 'none', type: 'string' }).toLowerCase(); + } } if (order === 'asc') { diff --git a/quark_auto_save.py b/quark_auto_save.py index 9b06370..9f758fe 100644 --- a/quark_auto_save.py +++ b/quark_auto_save.py @@ -289,7 +289,9 @@ def sort_file_by_name(file): if episode_value == float('inf'): match_e = re.search(r'[Ee][Pp]?(\d+)', filename) if match_e: - episode_value = int(match_e.group(1)) + # 若数字位于含字母的中括号内部,跳过该匹配 + if not _in_alpha_brackets(filename, match_e.start(1), match_e.end(1)): + episode_value = int(match_e.group(1)) # 2.5 1x01格式 if episode_value == float('inf'): @@ -315,16 +317,33 @@ def sort_file_by_name(file): resolution_patterns = [ r'\b\d+[pP]\b', # 匹配 720p, 1080P, 2160p 等 r'\b\d+x\d+\b', # 匹配 1920x1080 等 - # 注意:不移除4K/8K,避免误删文件名中的4K标识 + r'(? 9999: + continue + candidates.append((m.start(), value)) + if candidates: + candidates.sort(key=lambda x: x[0]) + episode_value = candidates[0][1] # 3. 提取上中下标记或其他细分 - 第三级排序键 segment_base = 0 # 基础值:上=1, 中=2, 下=3 @@ -415,6 +434,54 @@ def sort_file_by_name(file): # 全局的剧集编号提取函数 +def _in_alpha_brackets(text, start, end): + """ + 判断 [start,end) 范围内的数字是否位于"含字母的中括号对"内部。 + 支持英文方括号 [] 和中文方括号 【】。 + 要求:数字左侧最近的未闭合括号与右侧最近的对应闭合括号形成对,且括号内容包含字母。 + 但是允许 E/e 和 EP/ep/Ep 这样的集数格式。 + """ + if start < 0 or end > len(text): + return False + + # 检查英文方括号 [] + last_open_en = text.rfind('[', 0, start) + if last_open_en != -1: + close_before_en = text.rfind(']', 0, start) + if close_before_en == -1 or close_before_en < last_open_en: + close_after_en = text.find(']', end) + if close_after_en != -1: + content = text[last_open_en + 1:close_after_en] + if _check_bracket_content(content): + return True + + # 检查中文方括号 【】 + last_open_cn = text.rfind('【', 0, start) + if last_open_cn != -1: + close_before_cn = text.rfind('】', 0, start) + if close_before_cn == -1 or close_before_cn < last_open_cn: + close_after_cn = text.find('】', end) + if close_after_cn != -1: + content = text[last_open_cn + 1:close_after_cn] + if _check_bracket_content(content): + return True + + return False + +def _check_bracket_content(content): + """ + 检查括号内容是否应该被排除 + """ + # 检查是否包含字母 + has_letters = bool(re.search(r'[A-Za-z]', content)) + if not has_letters: + return False + + # 如果是 E/e 或 EP/ep/Ep 格式,则允许通过 + if re.match(r'^[Ee][Pp]?\d+$', content): + return False + + return True def extract_episode_number(filename, episode_patterns=None, config_data=None): """ 从文件名中提取剧集编号 @@ -430,6 +497,11 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None): # 首先去除文件扩展名 file_name_without_ext = os.path.splitext(filename)[0] + # 特判:SxxEyy.zz 模式(例如 S01E11.11),在日期清洗前优先识别 + m_spec = re.search(r'[Ss](\d+)[Ee](\d{1,2})[._\-/]\d{1,2}', file_name_without_ext) + if m_spec: + return int(m_spec.group(2)) + # 预处理:排除文件名中可能是日期的部分,避免误识别 date_patterns = [ # YYYY-MM-DD 或 YYYY.MM.DD 或 YYYY/MM/DD 或 YYYY MM DD格式(四位年份) @@ -453,6 +525,11 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None): for match in matches: # 检查匹配的内容是否确实是日期 date_str = match.group(0) + # 针对短日期 x.x 或 xx.xx:前一字符为 E/e 时不清洗(保护 E11.11 场景) + if re.match(r'^\d{1,2}[./-]\d{1,2}$', date_str): + prev_char = filename_without_dates[match.start()-1] if match.start() > 0 else '' + if prev_char in 'Ee': + continue month = None day = None @@ -496,7 +573,7 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None): resolution_patterns = [ r'\b\d+[pP]\b', # 匹配 720p, 1080P, 2160p 等 r'\b\d+x\d+\b', # 匹配 1920x1080 等 - # 注意:不移除4K/8K,避免误删文件名中的4K标识 + r'(? 9999: - return None # 跳过过大的数字 - return episode_num - + candidates = [] + for m in re.finditer(r'\\d+', filename_without_dates): + num_str = m.group(0) + # 过滤日期模式 + if is_date_format(num_str): + continue + # 过滤中括号内且含字母的片段 + span_l, span_r = m.start(), m.end() + if _in_alpha_brackets(filename_without_dates, span_l, span_r): + continue + try: + value = int(num_str) + except ValueError: + continue + if value > 9999: + continue + candidates.append((m.start(), value)) + if candidates: + candidates.sort(key=lambda x: x[0]) + return candidates[0][1] + return None # 全局变量 @@ -684,7 +790,7 @@ NOTIFYS = [] def is_date_format(number_str): """ 判断一个纯数字字符串是否可能是日期格式 - 支持的格式:YYYYMMDD, MMDD, YYMMDD + 支持的格式:YYYYMMDD, YYMMDD """ # 判断YYYYMMDD格式 (8位数字) if len(number_str) == 8 and number_str.startswith('20'): @@ -708,16 +814,8 @@ def is_date_format(number_str): # 可能是日期格式 return True - # 判断MMDD格式 (4位数字) - elif len(number_str) == 4: - month = int(number_str[:2]) - day = int(number_str[2:4]) - - # 简单检查月份和日期是否有效 - if 1 <= month <= 12 and 1 <= day <= 31: - # 可能是日期格式 - return True - + # 不再将 4 位纯数字按 MMDD 视为日期,避免误伤集号(如 1124) + # 其他格式不视为日期格式 return False @@ -1038,22 +1136,6 @@ class Config: if task.get("media_id"): del task["media_id"] - # 添加剧集识别模式配置 - if not config_data.get("episode_patterns"): - print("🔼 添加剧集识别模式配置") - config_data["episode_patterns"] = [ - {"description": "第[]集", "regex": "第(\\d+)集"}, - {"description": "第[]期", "regex": "第(\\d+)期"}, - {"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+)_?"} - ] class Quark: @@ -3417,6 +3499,9 @@ class Quark: episode_pattern = task["episode_naming"] regex_pattern = task.get("regex_pattern") + # 初始化变量 + already_renamed_files = set() # 用于防止重复重命名 + # 获取目录文件列表 - 添加这行代码初始化dir_file_list savepath = re.sub(r"/{2,}", "/", f"/{task['savepath']}{subdir_path}") if not self.savepath_fid.get(savepath): @@ -3455,8 +3540,10 @@ class Quark: # 实现序号提取函数 def extract_episode_number_local(filename): - # 使用全局的统一提取函数 - return extract_episode_number(filename, config_data=task.get("config_data")) + # 使用全局的统一提取函数,直接使用全局CONFIG_DATA + if 'CONFIG_DATA' not in globals() or not CONFIG_DATA: + return extract_episode_number(filename) + return extract_episode_number(filename, config_data=CONFIG_DATA) # 找出已命名的文件列表,避免重复转存 existing_episode_numbers = set() diff --git a/quark_config.json b/quark_config.json index 77f590d..e248dc9 100644 --- a/quark_config.json +++ b/quark_config.json @@ -65,9 +65,5 @@ "enddate": "2099-01-30" } ], - "episode_patterns": [ - { - "regex": "第(\\d+)集|第(\\d+)期|第(\\d+)话|(\\d+)集|(\\d+)期|(\\d+)话|[Ee][Pp]?(\\d+)|(\\d+)[-_\\s]*4[Kk]|\\[(\\d+)\\]|【(\\d+)】|_?(\\d+)_?" - } - ] + "episode_patterns": [] } From 9bd7068f0e62e586b59aa0b6bd2af4db4ec69e7a Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Tue, 23 Sep 2025 10:56:44 +0800 Subject: [PATCH 05/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=A8=A1=E6=80=81?= =?UTF-8?q?=E6=A1=86=E9=87=8D=E5=91=BD=E5=90=8D=E5=88=97=E7=9A=84=E6=8E=92?= =?UTF-8?q?=E5=BA=8F=E9=97=AE=E9=A2=98=EF=BC=8C=E4=BF=AE=E5=A4=8D=E9=9B=86?= =?UTF-8?q?=E7=BC=96=E5=8F=B7=E8=AF=86=E5=88=AB=E8=A7=84=E5=88=99=E6=9C=AA?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=B8=85=E7=A9=BA=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/templates/index.html | 111 +++++++++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 38 deletions(-) diff --git a/app/templates/index.html b/app/templates/index.html index c9a9b60..3311d25 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -3752,11 +3752,9 @@ }); } - // 如果没有剧集识别模式,添加默认模式 - if (!this.formData.episode_patterns || this.formData.episode_patterns.length === 0) { - this.formData.episode_patterns = [ - { regex: '第(\\d+)集|第(\\d+)期|第(\\d+)话|(\\d+)集|(\\d+)期|(\\d+)话|[Ee][Pp]?(\\d+)|(\\d+)[-_\\s]*4[Kk]|\\[(\\d+)\\]|【(\\d+)】|_?(\\d+)_?' } - ]; + // 确保剧集识别模式字段存在(但不自动添加默认规则) + if (!this.formData.episode_patterns) { + this.formData.episode_patterns = []; } // 如果当前标签是历史记录,则加载历史记录 @@ -9656,14 +9654,12 @@ // 其他模态框:文件夹排在文件前面 if (a.dir && !b.dir) return -1; if (!a.dir && b.dir) return 1; - // 其他模态框:使用拼音排序 - let aValue = pinyinPro.pinyin(a.file_name, { toneType: 'none', type: 'string' }).toLowerCase(); - let bValue = pinyinPro.pinyin(b.file_name, { toneType: 'none', type: 'string' }).toLowerCase(); - if (this.fileSelect.sortOrder === 'asc') { - return aValue > bValue ? 1 : -1; - } else { - return aValue < bValue ? 1 : -1; + // 其他模态框:使用与任务列表一致的自然排序算法 + const ka = sortFileByName(a), kb = sortFileByName(b); + for (let i = 0; i < ka.length; ++i) { + if (ka[i] !== kb[i]) return this.fileSelect.sortOrder === 'asc' ? (ka[i] > kb[i] ? 1 : -1) : (ka[i] < kb[i] ? 1 : -1); } + return 0; } } if (field === 'file_name_re') { @@ -9693,28 +9689,48 @@ bValue = String(b.episode_number); } } else { - // 否则尝试从重命名结果中提取数字进行数值排序 + // 否则使用智能排序:优先数值排序,其次日期排序,最后字符串排序 const aRename = a.file_name_re || ''; const bRename = b.file_name_re || ''; - // 尝试提取数字(包括小数) - const aMatch = aRename.match(/(\d+(?:\.\d+)?)/); - const bMatch = bRename.match(/(\d+(?:\.\d+)?)/); + // 尝试提取所有数字进行数值排序 + const aNumbers = aRename.match(/\d+/g); + const bNumbers = bRename.match(/\d+/g); - if (aMatch && bMatch) { + if (aNumbers && bNumbers && aNumbers.length > 0 && bNumbers.length > 0) { // 如果都能提取到数字,进行数值比较 - aValue = parseFloat(aMatch[1]); - bValue = parseFloat(bMatch[1]); + // 优先比较第一个数字,如果相同则比较后续数字 + for (let i = 0; i < Math.max(aNumbers.length, bNumbers.length); i++) { + const aNum = parseInt(aNumbers[i] || '0', 10); + const bNum = parseInt(bNumbers[i] || '0', 10); + if (aNum !== bNum) { + aValue = aNum; + bValue = bNum; + break; + } + } + // 如果所有数字都相同,使用自然排序 + if (aValue === undefined) { + aValue = aRename; + bValue = bRename; + } } else { - // 否则使用重命名后的文件名进行拼音排序 - aValue = pinyinPro.pinyin(aRename, { toneType: 'none', type: 'string' }).toLowerCase(); - bValue = pinyinPro.pinyin(bRename, { toneType: 'none', type: 'string' }).toLowerCase(); + // 否则使用自然排序(支持数值和日期的智能排序) + aValue = aRename; + bValue = bRename; } } if (this.fileSelect.sortOrder === 'asc') { + // 字符串使用自然排序,数值直接比较 + if (typeof aValue === 'string' && typeof bValue === 'string') { + return aValue.localeCompare(bValue, undefined, { numeric: true, sensitivity: 'base' }); + } return aValue > bValue ? 1 : -1; } else { + if (typeof aValue === 'string' && typeof bValue === 'string') { + return bValue.localeCompare(aValue, undefined, { numeric: true, sensitivity: 'base' }); + } return aValue < bValue ? 1 : -1; } } @@ -9838,28 +9854,49 @@ bValue = String(b.episode_number); } } else { - // 否则尝试从重命名结果中提取数字进行数值排序 + // 否则使用智能排序:优先数值排序,其次日期排序,最后字符串排序 const aRename = a.file_name_re || ''; const bRename = b.file_name_re || ''; - // 尝试提取数字(包括小数) - const aMatch = aRename.match(/(\d+(?:\.\d+)?)/); - const bMatch = bRename.match(/(\d+(?:\.\d+)?)/); + // 尝试提取所有数字进行数值排序 + const aNumbers = aRename.match(/\d+/g); + const bNumbers = bRename.match(/\d+/g); - if (aMatch && bMatch) { + if (aNumbers && bNumbers && aNumbers.length > 0 && bNumbers.length > 0) { // 如果都能提取到数字,进行数值比较 - aValue = parseFloat(aMatch[1]); - bValue = parseFloat(bMatch[1]); + // 优先比较第一个数字,如果相同则比较后续数字 + for (let i = 0; i < Math.max(aNumbers.length, bNumbers.length); i++) { + const aNum = parseInt(aNumbers[i] || '0', 10); + const bNum = parseInt(bNumbers[i] || '0', 10); + if (aNum !== bNum) { + aValue = aNum; + bValue = bNum; + break; + } + } + // 如果所有数字都相同,使用自然排序 + if (aValue === undefined) { + aValue = aRename; + bValue = bRename; + } } else { - // 否则使用重命名后的文件名进行拼音排序 - aValue = pinyinPro.pinyin(aRename, { toneType: 'none', type: 'string' }).toLowerCase(); - bValue = pinyinPro.pinyin(bRename, { toneType: 'none', type: 'string' }).toLowerCase(); + // 否则使用自然排序(支持数值和日期的智能排序) + aValue = aRename; + bValue = bRename; } } if (order === 'asc') { + // 如果都是字符串,使用自然排序 + if (typeof aValue === 'string' && typeof bValue === 'string') { + return aValue.localeCompare(bValue, undefined, { numeric: true, sensitivity: 'base' }); + } return aValue > bValue ? 1 : -1; } else { + // 如果都是字符串,使用自然排序 + if (typeof aValue === 'string' && typeof bValue === 'string') { + return bValue.localeCompare(aValue, undefined, { numeric: true, sensitivity: 'base' }); + } return aValue < bValue ? 1 : -1; } } @@ -10921,7 +10958,7 @@ this.fileSelect.paths = []; this.fileSelect.error = undefined; this.fileSelect.selectedFiles = []; - // 设置排序方式为按重命名结果降序排序 + // 预览默认按重命名列降序(与任务列表一致) this.fileSelect.sortBy = "file_name_re"; this.fileSelect.sortOrder = "desc"; // 展示当前文件夹的文件 @@ -13326,11 +13363,9 @@ }); } - // 如果没有剧集识别模式,添加默认模式 - if (!this.formData.episode_patterns || this.formData.episode_patterns.length === 0) { - this.formData.episode_patterns = [ - { regex: '第(\\d+)集|第(\\d+)期|第(\\d+)话|(\\d+)集|(\\d+)期|(\\d+)话|[Ee][Pp]?(\\d+)|(\\d+)[-_\\s]*4[Kk]|\\[(\\d+)\\]|【(\\d+)】|_?(\\d+)_?' } - ]; + // 确保剧集识别模式字段存在(但不自动添加默认规则) + if (!this.formData.episode_patterns) { + this.formData.episode_patterns = []; } // 如果当前标签是历史记录,则加载历史记录 From 3096702051eb048e526c016457eca4ac8eebbcfd Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Tue, 23 Sep 2025 12:50:07 +0800 Subject: [PATCH 06/10] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=89=A7=E9=9B=86?= =?UTF-8?q?=E7=BC=96=E5=8F=B7=E6=8F=90=E5=8F=96=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8A=80=E6=9C=AF=E8=A7=84=E6=A0=BC=E8=BF=87?= =?UTF-8?q?=E6=BB=A4=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/js/sort_file_by_name.js | 57 +++++++++++++++++++-- quark_auto_save.py | 81 +++++++++++++++++++++++++----- 2 files changed, 122 insertions(+), 16 deletions(-) diff --git a/app/static/js/sort_file_by_name.js b/app/static/js/sort_file_by_name.js index 1aaf4bf..745d211 100644 --- a/app/static/js/sort_file_by_name.js +++ b/app/static/js/sort_file_by_name.js @@ -149,15 +149,64 @@ function sortFileByName(file) { if (/^\d+$/.test(file_name_without_ext)) { episode_value = parseInt(file_name_without_ext); } else { - // 预处理:移除分辨率标识(如 720p, 1080P, 2160p 等) + // 预处理:移除技术规格信息,避免误提取技术参数中的数字为集编号 let filename_without_resolution = filename; - const resolution_patterns = [ + const tech_spec_patterns = [ + // 分辨率相关 /\b\d+[pP]\b/g, // 匹配 720p, 1080P, 2160p 等 /\b\d+x\d+\b/g, // 匹配 1920x1080 等 - // 注意:不移除4K/8K,避免误删文件名中的4K标识 + /(? 0 else '' if prev_char in 'Ee': continue + # 保护 E 格式的集编号,如 E07, E14 等 + if re.match(r'^\d+$', date_str) and len(date_str) <= 3: + prev_char = filename_without_dates[match.start()-1] if match.start() > 0 else '' + if prev_char in 'Ee': + continue month = None day = None @@ -569,15 +574,65 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None): if month and day and 1 <= month <= 12 and 1 <= day <= 31: filename_without_dates = filename_without_dates.replace(date_str, " ") - # 预处理:移除分辨率标识(如 720p, 1080P, 2160p 等) - resolution_patterns = [ + # 预处理:移除技术规格信息,避免误提取技术参数中的数字为集编号 + tech_spec_patterns = [ + # 分辨率相关 r'\b\d+[pP]\b', # 匹配 720p, 1080P, 2160p 等 r'\b\d+x\d+\b', # 匹配 1920x1080 等 r'(? Date: Tue, 23 Sep 2025 13:21:39 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20EP=20=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=89=A7=E9=9B=86=E7=BC=96=E5=8F=B7=E7=9A=84=E8=AF=86?= =?UTF-8?q?=E5=88=AB=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. **Python版本** (`quark_auto_save.py`) - 更新日期过滤正则表达式,保护 E/EP 格式不被误伤 - 完善日期过滤逻辑,添加对 MM-DD 格式的处理 - 增强分辨率标识保护,防止 4K/4k 等被误识别为日期 2. **JavaScript版本** (`sort_file_by_name.js`) - 同步更新日期过滤正则表达式 - 保持前后端逻辑一致性 --- app/static/js/sort_file_by_name.js | 4 ++-- quark_auto_save.py | 38 ++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/app/static/js/sort_file_by_name.js b/app/static/js/sort_file_by_name.js index 745d211..68c00e0 100644 --- a/app/static/js/sort_file_by_name.js +++ b/app/static/js/sort_file_by_name.js @@ -52,7 +52,7 @@ function sortFileByName(file) { } // YY-MM-DD if (date_value === Infinity) { - match = filename.match(/((?:19|20)?\d{2})[-./\s](\d{1,2})[-./\s](\d{1,2})/); + match = filename.match(/(? 0 else '' - if prev_char in 'Ee': + prev_chars = filename_without_dates[max(0, match.start()-2):match.start()] + if prev_chars.endswith(('E', 'e', 'EP', 'Ep', 'ep')): continue - # 保护 E 格式的集编号,如 E07, E14 等 + # 保护 E/EP 格式的集编号,如 E07, E14, EP08 等 if re.match(r'^\d+$', date_str) and len(date_str) <= 3: - prev_char = filename_without_dates[match.start()-1] if match.start() > 0 else '' - if prev_char in 'Ee': + prev_chars = filename_without_dates[max(0, match.start()-2):match.start()] + if prev_chars.endswith(('E', 'e', 'EP', 'Ep', 'ep')): continue month = None day = None @@ -569,6 +569,24 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None): except ValueError: # 转换失败,保持month和day为None pass + elif len(match.groups()) == 2: + # 处理两个分组的模式(如MM-DD格式) + try: + month = int(match.group(1)) + day = int(match.group(2)) + + # 检查月和日的有效性 + if not (1 <= month <= 12 and 1 <= day <= 31): + # 尝试另一种解释:日-月 + month = int(match.group(2)) + day = int(match.group(1)) + if not (1 <= month <= 12 and 1 <= day <= 31): + # 仍然无效,重置month和day + month = None + day = None + except ValueError: + # 转换失败,保持month和day为None + pass # 如果能确定月日且是有效的日期,则从文件名中删除该日期 if month and day and 1 <= month <= 12 and 1 <= day <= 31: From 338418e59592d406843521e1ef15a146bbe9a540 Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Tue, 23 Sep 2025 13:58:59 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E6=A0=91=E5=9B=BE=E6=A0=87=E6=98=A0=E5=B0=84=E8=A1=A5=E5=85=85?= =?UTF-8?q?=20webm/3gp/f4v=20=E4=B8=BA=E8=A7=86=E9=A2=91=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=EF=BC=8C=E5=89=8D=E5=90=8E=E7=AB=AF=E6=89=A9=E5=B1=95=E5=90=8D?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E4=BF=9D=E6=8C=81=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- quark_auto_save.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quark_auto_save.py b/quark_auto_save.py index 55f7613..b820dac 100644 --- a/quark_auto_save.py +++ b/quark_auto_save.py @@ -1063,7 +1063,7 @@ def get_file_icon(file_name, is_dir=False): lower_name = file_name.lower() # 视频文件 - if any(lower_name.endswith(ext) for ext in ['.mp4', '.mkv', '.avi', '.mov', '.rmvb', '.flv', '.wmv', '.m4v', '.ts']): + if any(lower_name.endswith(ext) for ext in ['.mp4', '.mkv', '.avi', '.mov', '.rmvb', '.flv', '.wmv', '.m4v', '.ts', '.webm', '.3gp', '.f4v']): return "🎞️" # 图片文件 From da051409dff81b0e08888ab5de928839f0b8222e Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Tue, 23 Sep 2025 14:10:27 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=E4=B8=BA=E9=87=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=A4=B9=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=8A=A0=20SSE=20?= =?UTF-8?q?=E6=8E=A8=E9=80=81=EF=BC=8C=E6=94=AF=E6=8C=81=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=9A=84=E5=AE=9E=E6=97=B6=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/run.py | 6 ++++++ app/templates/index.html | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/app/run.py b/app/run.py index d285e89..d46dd9a 100644 --- a/app/run.py +++ b/app/run.py @@ -3169,6 +3169,12 @@ def reset_folder(): logging.error(f">>> 删除记录时出错: {str(e)}") # 即使删除记录失败,也返回文件删除成功 + # 成功后通过 SSE 通知前端进行热更新 + try: + notify_calendar_changed('reset_folder') + except Exception: + pass + return jsonify({ "success": True, "message": f"重置成功,删除了 {deleted_files} 个文件和 {deleted_records} 条记录", diff --git a/app/templates/index.html b/app/templates/index.html index 3311d25..9f26aa4 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -10488,6 +10488,19 @@ .then(response => { if (response.data.success) { this.showToast(`重置成功:删除了 ${response.data.deleted_files || 0} 个文件,${response.data.deleted_records || 0} 条记录`); + // 重置成功后,主动热更新最近转存文件与任务元数据,避免等待 SSE 或轮询 + (async () => { + try { + const latestRes = await axios.get('/task_latest_info'); + if (latestRes.data && latestRes.data.success) { + const latestFiles = latestRes.data.data.latest_files || {}; + this.taskLatestFiles = latestFiles; + this.taskLatestRecords = latestRes.data.data.latest_records || {}; + } + } catch (e) {} + // 重新加载任务元数据(海报与详细信息) + try { await this.loadTasklistMetadata(); } catch (e) {} + })(); // 如果当前是历史记录页面,刷新记录 if (this.activeTab === 'history') { this.loadHistoryRecords(); From 49b2a0c81f0d9bac8ed2ac2e8c9fc251cc5eceb4 Mon Sep 17 00:00:00 2001 From: x1ao4 Date: Tue, 23 Sep 2025 16:14:34 +0800 Subject: [PATCH 10/10] =?UTF-8?q?=E5=B0=86=E7=A7=BB=E9=99=A4=E6=8A=80?= =?UTF-8?q?=E6=9C=AF=E8=A7=84=E6=A0=BC=E4=BF=A1=E6=81=AF=E7=9A=84=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E6=94=B9=E4=B8=BA=E4=BB=85=E5=A4=84=E7=90=86=E5=B8=B8?= =?UTF-8?q?=E8=A7=81=E5=80=BC=E7=99=BD=E5=90=8D=E5=8D=95=EF=BC=8C=E9=99=8D?= =?UTF-8?q?=E4=BD=8E=E8=AF=AF=E5=88=A4=E9=A3=8E=E9=99=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/js/sort_file_by_name.js | 153 ++++++++++++++--------------- quark_auto_save.py | 144 +++++++++++++-------------- 2 files changed, 142 insertions(+), 155 deletions(-) diff --git a/app/static/js/sort_file_by_name.js b/app/static/js/sort_file_by_name.js index 68c00e0..f91ad94 100644 --- a/app/static/js/sort_file_by_name.js +++ b/app/static/js/sort_file_by_name.js @@ -28,6 +28,59 @@ function sortFileByName(file) { let filename = typeof file === 'object' ? (file.file_name || '') : file; let update_time = typeof file === 'object' ? (file.updated_at || 0) : 0; let file_name_without_ext = filename.replace(/\.[^/.]+$/, ''); + + // 0. 预处理(前移):移除技术规格与季号,供后续“日期与集数”提取共同使用 + // 这样可以避免 30FPS/1080p/Season 等噪音影响识别 + let cleanedName = file_name_without_ext; + try { + const techSpecs = [ + // 分辨率相关(限定常见p档) + /\b(?:240|360|480|540|720|900|960|1080|1440|2160|4320)[pP]\b/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, + /(? 12) [month, day] = [day, month]; @@ -87,7 +140,7 @@ function sortFileByName(file) { } // MM-DD if (date_value === Infinity) { - match = filename.match(/(?