diff --git a/app/run.py b/app/run.py index 1bd8c48..2c28a65 100644 --- a/app/run.py +++ b/app/run.py @@ -863,6 +863,20 @@ def get_share_detail(): share_detail["paths"] = paths share_detail["stoken"] = stoken + # 处理文件夹的include_items字段,确保空文件夹显示为0项而不是undefined + if "list" in share_detail and isinstance(share_detail["list"], list): + for file_item in share_detail["list"]: + if file_item.get("dir", False): + # 如果是文件夹,确保include_items字段存在且为数字 + if "include_items" not in file_item or file_item["include_items"] is None: + file_item["include_items"] = 0 + elif not isinstance(file_item["include_items"], (int, float)): + # 如果include_items不是数字类型,尝试转换为整数,失败则设为0 + try: + file_item["include_items"] = int(file_item["include_items"]) + except (ValueError, TypeError): + file_item["include_items"] = 0 + # 如果是GET请求或者不需要预览正则,直接返回分享详情 if request.method == "GET" or not request.json.get("regex"): return jsonify({"success": True, "data": share_detail}) @@ -1028,6 +1042,20 @@ def get_share_detail(): share_detail = preview_regex(share_detail) + # 再次处理文件夹的include_items字段,确保预览后的数据也正确 + if "list" in share_detail and isinstance(share_detail["list"], list): + for file_item in share_detail["list"]: + if file_item.get("dir", False): + # 如果是文件夹,确保include_items字段存在且为数字 + if "include_items" not in file_item or file_item["include_items"] is None: + file_item["include_items"] = 0 + elif not isinstance(file_item["include_items"], (int, float)): + # 如果include_items不是数字类型,尝试转换为整数,失败则设为0 + try: + file_item["include_items"] = int(file_item["include_items"]) + except (ValueError, TypeError): + file_item["include_items"] = 0 + return jsonify({"success": True, "data": share_detail}) @@ -1067,8 +1095,26 @@ def get_savepath_detail(): return jsonify({"success": False, "data": {"error": "获取fid失败"}}) else: fid = request.args.get("fid", "0") + + # 获取文件列表 + files = account.ls_dir(fid) + + # 处理文件夹的include_items字段,确保空文件夹显示为0项而不是undefined + if isinstance(files, list): + for file_item in files: + if file_item.get("dir", False): + # 如果是文件夹,确保include_items字段存在且为数字 + if "include_items" not in file_item or file_item["include_items"] is None: + file_item["include_items"] = 0 + elif not isinstance(file_item["include_items"], (int, float)): + # 如果include_items不是数字类型,尝试转换为整数,失败则设为0 + try: + file_item["include_items"] = int(file_item["include_items"]) + except (ValueError, TypeError): + file_item["include_items"] = 0 + file_list = { - "list": account.ls_dir(fid), + "list": files, "paths": paths, } return jsonify({"success": True, "data": file_list}) @@ -1921,6 +1967,19 @@ def get_file_list(): else: return jsonify({"success": False, "message": f"获取文件列表失败: {error_msg}"}) + # 处理文件夹的include_items字段,确保空文件夹显示为0项而不是undefined + for file_item in files: + if file_item.get("dir", False): + # 如果是文件夹,确保include_items字段存在且为数字 + if "include_items" not in file_item or file_item["include_items"] is None: + file_item["include_items"] = 0 + elif not isinstance(file_item["include_items"], (int, float)): + # 如果include_items不是数字类型,尝试转换为整数,失败则设为0 + try: + file_item["include_items"] = int(file_item["include_items"]) + except (ValueError, TypeError): + file_item["include_items"] = 0 + # 计算总数 total = len(files) diff --git a/app/static/css/main.css b/app/static/css/main.css index 29b1e32..cd4eb25 100644 --- a/app/static/css/main.css +++ b/app/static/css/main.css @@ -38,7 +38,7 @@ body.login-page { padding-right: 10px; /* 默认是15px */ padding-left: 10px; /* 默认是15px */ } - + /* 同时调整row的负margin以保持对齐 */ .row { margin-right: -10px; @@ -382,7 +382,7 @@ main div[v-if="activeTab === 'config'"] .row.title:first-child { padding: 0; } -.sidebar-collapsed .nav-text, +.sidebar-collapsed .nav-text, .sidebar-collapsed .collapse-text { display: block; /* 改用opacity控制而不是display:none,避免跳动 */ visibility: hidden; @@ -447,7 +447,7 @@ main div[v-if="activeTab === 'config'"] .row.title:first-child { max-width: 184px; flex: 0 0 184px; } - + main.col-md-10 { max-width: calc(100% - 184px); flex: 0 0 calc(100% - 184px); @@ -493,19 +493,19 @@ main div[v-if="activeTab === 'config'"] .row.title:first-child { max-width: none !important; flex: auto !important; } - + .sidebar-collapsed + main { max-width: 100% !important; flex: 0 0 100% !important; } - + .sidebar-collapsed-navbar-brand { width: auto !important; min-width: auto !important; max-width: none !important; flex: auto !important; } - + .sidebar-collapsed .nav-text, .sidebar-collapsed .collapse-text { display: block; @@ -514,20 +514,20 @@ main div[v-if="activeTab === 'config'"] .row.title:first-child { width: auto; max-width: none; } - + .sidebar-collapsed .nav-link { justify-content: flex-start; padding-left: 10px; padding-right: 10px; width: 100%; } - + .sidebar-collapsed .nav-link i { margin-right: 8px; margin-left: 0; width: 20px; } - + /* 移动设备下底部链接的样式 */ .sidebar-collapsed .bottom-links .nav-text { display: block; @@ -536,20 +536,20 @@ main div[v-if="activeTab === 'config'"] .row.title:first-child { width: auto; max-width: none; } - + .sidebar-collapsed .bottom-links .nav-link { justify-content: flex-start; padding-left: 10px; padding-right: 10px; width: 100%; } - + .sidebar-collapsed .nav-item, .sidebar-collapsed .bottom-links .nav-item { width: 100%; display: block; } - + /* 确保导航栏高度足够 */ .navbar { min-height: 54px; @@ -603,7 +603,7 @@ main div[v-if="activeTab === 'config'"] .row.title:first-child { color: var(--navbar-bg-color) !important; border-color: #fff !important; } - + /* 确保按钮内图标颜色也随之变化 */ .navbar-action-btn:hover i { color: var(--navbar-bg-color) !important; @@ -617,7 +617,7 @@ main div[v-if="activeTab === 'config'"] .row.title:first-child { color: #272b30 !important; border-color: #fff !important; } - + /* 确保按钮内图标颜色也随之变化 */ .navbar-action-btn:hover i { color: #272b30 !important; @@ -637,7 +637,7 @@ main div[v-if="activeTab === 'config'"] .row.title:first-child { right: 52px; /* 菜单按钮宽度(32px) + 间距(8px) */ flex-direction: row; } - + /* 确保在移动设备上隐藏页面宽度按钮 */ .navbar-action-btn.d-none.d-md-inline-block { display: none !important; @@ -673,7 +673,7 @@ main div[v-if="activeTab === 'config'"] .row.title:first-child { color: #272b30 !important; border-color: #fff !important; } - + /* 确保按钮内图标颜色也随之变化 */ .navbar-toggler-square:hover i { color: #272b30 !important; @@ -827,7 +827,7 @@ main div[v-if="activeTab === 'config'"] .row.title:first-child { /* 调整下拉选择框的内边距,使文本与输入框占位符位置一致 */ select.form-control { padding: 0 8px !important; /* 移除右侧额外空间,图标已经用DOM实现 */ - text-indent: 0 !important; + text-indent: 0 !important; appearance: none !important; -webkit-appearance: none !important; -moz-appearance: none !important; @@ -1290,7 +1290,7 @@ button.close:focus, margin-bottom: 8px; margin-left: 3.5px; /* 添加左边距 */ height: 32px !important; /* 设置固定高度 */ - min-height: 32px !important; + min-height: 32px !important; max-height: 32px !important; /* 确保不超过指定高度 */ padding-top: 0 !important; /* 重置顶部内边距 */ padding-bottom: 0 !important; /* 重置底部内边距 */ @@ -2477,7 +2477,7 @@ body { /* 历史记录页面的文本和表格内容 */ .table td, -.text-truncate, +.text-truncate, .expanded-text, .table th, .cursor-pointer { @@ -2502,7 +2502,7 @@ body { } /* 设置禁用状态下输入框的背景色 */ -.form-control:disabled, +.form-control:disabled, .form-control[readonly] { background-color: #ededf0 !important; opacity: 1; @@ -2789,7 +2789,7 @@ div.jsoneditor-treepath * { flex-direction: row; justify-content: center !important; } - + .pagination-controls { display: flex; align-items: center; @@ -2833,7 +2833,7 @@ div.jsoneditor-treepath * { transform: translateY(-10px); gap: 12px; /* 垂直间距调整为12px,与整体间距一致 */ } - + .pagination-controls { display: flex; align-items: center; @@ -2869,18 +2869,18 @@ div.jsoneditor-treepath * { order: 3; flex-wrap: wrap; } - + /* 不修改按钮大小,与桌面版保持一致 */ .pagination-controls .btn-sm { min-width: 32px; height: 32px; } - + /* 保持与桌面版一致的跳页区域尺寸 */ .pagination-settings .form-control-sm { height: 32px; } - + /* 保持与桌面版一致的下拉菜单按钮尺寸 */ .pagination-settings .dropdown-toggle { height: 32px; @@ -3311,7 +3311,7 @@ div[id^="collapse_"] .input-group.mb-2:last-child { width: 100%; padding-top: 0 !important; } - + /* 确保侧边栏内容正确定位 */ #sidebarMenu .sidebar-sticky { height: calc(100vh - 54px); /* 计算正确的高度 */ @@ -3345,7 +3345,7 @@ div[data-toggle="collapse"] .btn.text-left i.bi-caret-right-fill { } /* 优化完全收起过程,避免卡顿,特别是当高度接近0时 */ -.collapsing[style*="height: 0"], +.collapsing[style*="height: 0"], .collapsing[style*="height:0"] { padding-top: 0 !important; padding-bottom: 0 !important; @@ -3525,7 +3525,7 @@ input::-moz-list-button { padding-left: 15px !important; padding-right: 4px !important; } - + .cloudsaver-password-col, .webui-password-col { padding-left: 4px !important; @@ -3539,7 +3539,7 @@ input::-moz-list-button { .col-lg-6.col-md-6.mb-2.mb-md-0 { padding-right: 4px; } - + /* 匹配调整,确保任务筛选框左侧的间距 */ .col-lg-6.col-md-6:not(.mb-2) { padding-left: 4px; @@ -3557,7 +3557,7 @@ input::-moz-list-button { padding-left: 10px; top: 2.5px; } - + .task .form-group.row .col-sm-10 { width: 100%; /* 全宽 */ max-width: 100%; /* 最大宽度全宽 */ @@ -3565,38 +3565,38 @@ input::-moz-list-button { padding-top: 0; /* 移除顶部内边距 */ padding-bottom: 0; /* 移除底部内边距 */ } - + /* 确保移动模式下配置选项间距与桌面模式一致 */ .task .form-group.row { margin-bottom: 8px; /* 与桌面模式保持一致的行间距 */ padding-top: 0; /* 移除顶部内边距 */ padding-bottom: 0; /* 移除底部内边距 */ } - + /* 调整移动模式下表单控件的间距 */ .task .form-group.row .form-control, .task .form-group.row .input-group { margin-bottom: 0; /* 确保无底部边距 */ } - + /* 避免在移动模式下有额外内边距 */ .task .collapse > div .form-group.row:last-child { margin-bottom: 8px; /* 保持最后一个选项与其他选项有相同的间距 */ } - + /* 调整任务配置标签的间距 */ .task .form-group.row .col-form-label { padding-top: 2px; /* 减少顶部内边距 */ padding-bottom: 2px; /* 减少底部内边距 */ } - + /* 专门针对配置选项标题在配置框上方的情况调整 */ .task .form-group.row:not(.align-items-center) .col-sm-2 { margin-bottom: 4.5px; /* 设置标题与配置框之间的距离 */ padding-left: 15px; /* 左对齐与其他元素保持一致 */ font-size: 0.95rem; /* 保持字体大小一致 */ } - + /* 标题在上方时配置框的左内边距调整 */ .task .form-group.row:not(.align-items-center) .col-sm-10 { padding-left: 18.5px; /* 为标题在上方的配置框增加左内边距 */ @@ -3623,7 +3623,7 @@ input::-moz-list-button { overflow-x: auto; -webkit-overflow-scrolling: touch; } - + /* 确保表格使用固定布局算法,保持列宽 */ #fileSelectModal .table { table-layout: fixed; @@ -3631,28 +3631,28 @@ input::-moz-list-button { min-width: 100%; margin-bottom: 0; /* 避免多余的底部间距 */ } - + /* 移动模式下面包屑导航宽度与表格匹配 */ /* 针对选择需转存的文件夹/选择起始文件模式 - 4列表格 */ #fileSelectModal[data-modal-type="source"] .breadcrumb, #fileSelectModal[data-modal-type="start-file"] .breadcrumb { min-width: 690px; /* 4列表格总宽度: 230px + 230px + 90px + 140px */ } - + #fileSelectModal[data-modal-type="source"] .table, #fileSelectModal[data-modal-type="start-file"] .table { width: 690px; } - + /* 针对选择保存到的文件夹模式 - 带操作列的表格 */ #fileSelectModal[data-modal-type="target"] .breadcrumb { min-width: 648px; /* 4列表格总宽度: 230px + 90px + 140px + 188px */ } - + #fileSelectModal[data-modal-type="target"] .table { width: 648px; } - + /* 针对命名预览模式 - 2列表格 */ #fileSelectModal[data-modal-type="preview"] .breadcrumb { min-width: 460px; /* 2列表格总宽度: 230px + 230px */ @@ -3676,12 +3676,12 @@ input::-moz-list-button { #fileSelectModal[data-modal-type="preview-filemanager"] .table { width: 460px; } - + /* 确保面包屑导航内容不被截断 */ #fileSelectModal .breadcrumb-item { white-space: nowrap; } - + /* 修复模态框内边距问题 */ #fileSelectModal .modal-body { padding-right: 16px !important; @@ -3689,55 +3689,55 @@ input::-moz-list-button { -webkit-overflow-scrolling: touch; /* 在iOS设备上启用惯性滚动 */ overflow-x: hidden; /* 禁用整个modal-body的水平滚动,由内部元素单独控制 */ } - + /* 修复表格和面包屑导航的边距问题 */ #fileSelectModal .table-responsive { padding-right: 0; margin-right: 0; width: 100%; } - + /* 确保表格容器有正确的溢出行为 */ #fileSelectModal .modal-body > div:not(.alert-warning) { overflow-x: auto; width: 100%; padding-bottom: 8px; /* 添加底部内边距,避免滚动条遮挡内容 */ } - + /* 确保面包屑导航在滚动容器内有正确的空间 */ #fileSelectModal nav[aria-label="breadcrumb"] { padding-right: 16px; /* 确保右侧有足够边距 */ margin-right: 0; margin-bottom: 8px; /* 保持与表格的间距一致 */ } - + /* 特别处理预览模式下的导航栏,防止双重内边距 */ #fileSelectModal[data-modal-type="preview"] .modal-body > div > nav[aria-label="breadcrumb"] { padding-right: 0 !important; /* 清除可能的额外内边距 */ } - + /* 确保模态框内的预览区域也有合适的边距和滚动行为 */ #fileSelectModal .mb-3[v-if="fileSelect.previewRegex"] { padding-right: 16px; overflow-x: auto; width: 100%; } - + /* 优化表格的标题行在滚动时始终可见 */ #fileSelectModal .table th { z-index: 5; /* 保证在滚动时标题行位于上层 */ } - + /* 优化滚动条样式,使其更细小不占用过多空间 */ #fileSelectModal .modal-body > div::-webkit-scrollbar { height: 8px; /* 较细的滚动条 */ } - + #fileSelectModal .modal-body > div::-webkit-scrollbar-thumb { background-color: var(--border-color); /* 滚动条滑块 */ border-radius: 4px; /* 圆角滚动条 */ } - + #fileSelectModal .modal-body > div::-webkit-scrollbar-track { background-color: #f7f7fa; /* 滚动条轨道 */ border-radius: 4px; /* 圆角滚动条 */ @@ -3853,12 +3853,12 @@ input.no-spinner { flex: 0 0 50%; max-width: 50%; } - + .row.mb-2 .col-sm-6.pr-1 { padding-right: 4px !important; padding-left: 15px !important; } - + .row.mb-2 .col-sm-6.pl-1 { padding-left: 4px !important; padding-right: 15px !important; @@ -3967,17 +3967,44 @@ table.selectable-records .expand-button:hover { top: 2px !important; /* 将"×"标记上移1px */ } -#fileSelectModal[data-modal-type="preview"] .table td.col-rename > * { +#fileSelectModal[data-modal-type="preview"] .table td.col-rename > :not(.expand-button) { position: relative; top: 3px !important; } /* 文件整理页面命名预览模式下的重命名列通用样式 */ -#fileSelectModal[data-modal-type="preview-filemanager"] .table td.col-rename > * { +#fileSelectModal[data-modal-type="preview-filemanager"] .table td.col-rename > :not(.expand-button) { position: relative; top: 3px !important; /* 与任务配置页面保持一致 */ } +/* 确保文件整理页面命名预览模式下的重命名列文本截断样式正确应用 */ +#fileSelectModal[data-modal-type="preview-filemanager"] .table td.col-rename div[style*="white-space: nowrap"] { + white-space: nowrap !important; + overflow: hidden !important; + text-overflow: ellipsis !important; + padding-right: 25px !important; + padding-top: 0.5px !important; /* 文本和超长省略号下移0.5px */ + max-width: 100% !important; + display: block !important; + line-height: 19px !important; + box-sizing: border-box !important; +} + +/* 强制文件整理页面重命名列的所有子元素使用inline显示,确保文本截断正常工作 */ +#fileSelectModal[data-modal-type="preview-filemanager"] .table td.col-rename div[style*="white-space: nowrap"] * { + display: inline !important; + white-space: nowrap !important; +} + +/* 命名预览模态框 - 重命名列展开按钮位置微调,仅作用于命名预览 */ +#fileSelectModal[data-modal-type="preview"] .table td.col-rename .expand-button, +#fileSelectModal[data-modal-type="preview-filemanager"] .table td.col-rename .expand-button { + top: 6.5px !important; /* 单独上移 1px(基础为 7.5px) */ + right: 8px !important; /* 略向左移,参考模态框其他列的间距 */ +} + + /* 模态框通用文件夹图标样式 */ #fileSelectModal .bi-folder-fill { color: #098eff; @@ -4607,11 +4634,11 @@ select.task-filter-select, .file-manager-rule-bar { flex-direction: column; } - + .file-manager-rule-bar .input-group { margin-bottom: 10px; } - + .batch-rename-btn { margin-top: 10px; width: 32px; @@ -4632,7 +4659,7 @@ select.task-filter-select, .file-manager-rule-bar-responsive .batch-rename-btn:first-of-type { margin-left: 8px; } - + #batchRenameModal .modal-dialog { max-width: 95%; } @@ -4700,7 +4727,7 @@ tr.selected-file .file-size-cell .delete-record-btn { border-collapse: collapse !important; } -.selectable-files td, +.selectable-files td, .selectable-files th { border: none !important; border-top: 1px solid var(--border-color) !important; /* 添加顶部边框线 */ @@ -5510,7 +5537,7 @@ body .selectable-files tr.selected-file:has([style*="white-space: normal"]) .fil /* 文件整理页面无法识别剧集编号样式 */ #fileSelectModal[data-modal-type="preview-filemanager"] .episode-number-text { position: relative; - top: 1.5px; /* 或你想要的像素 */ + top: 1px; /* 上移0.5px,从1.5px改为1px */ display: inline-block; } @@ -5525,13 +5552,18 @@ body .selectable-files tr.selected-file:has([style*="white-space: normal"]) .fil /* 文件整理页面无法识别剧集编号前面的 × 样式 */ #fileSelectModal[data-modal-type="preview-filemanager"] .episode-x { position: relative; - top: 0.5px; - display: inline-block; + display: inline; margin-right: 2px; } +/* 确保文件整理页面重命名列中的episode-x在未展开状态下不影响文本截断 */ +#fileSelectModal[data-modal-type="preview-filemanager"] .table td.col-rename div[style*="white-space: nowrap"] .episode-x { + display: inline !important; + white-space: nowrap !important; +} + /* 文件整理页面命名预览模式下的绿色重命名文本上移0.5px */ -#fileSelectModal[data-modal-type="preview-filemanager"] .table td.col-rename.text-success > * { +#fileSelectModal[data-modal-type="preview-filemanager"] .table td.col-rename.text-success > :not(.expand-button) { position: relative; top: 3px !important; /* 原来是3px,上移0.5px */ } @@ -6281,3 +6313,15 @@ body .selectable-files tr.selected-file:has([style*="white-space: normal"]) .fil grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); } } + +/* 文件整理页面命名预览模式下的展开状态文本位置调整 - 最高优先级 */ +#fileSelectModal[data-modal-type="preview-filemanager"] .table td.col-rename > div[style*="white-space: normal"][style*="word-break: break-word"] { + position: relative !important; + top: 3.5px !important; /* 展开状态文本下移0.5px,覆盖所有其他设置 */ + margin-bottom: 8px !important; /* 文件整理页面展开状态下边距增加1px */ +} + +/* 任务列表和影视发现页面命名预览模式下的展开状态文本下边距调整 */ +#fileSelectModal[data-modal-type="preview"] .table td.col-rename > div[style*="white-space: normal"][style*="word-break: break-word"] { + margin-bottom: 7.5px !important; /* 任务列表页面展开状态下边距增加0.5px */ +} diff --git a/app/static/js/sort_file_by_name.js b/app/static/js/sort_file_by_name.js index d5d4bfd..d12d214 100644 --- a/app/static/js/sort_file_by_name.js +++ b/app/static/js/sort_file_by_name.js @@ -170,46 +170,84 @@ function sortFileByName(file) { let segment_base = 0; // 基础值:上=1, 中=2, 下=3 let sequence_number = 0; // 序号值:用于处理上中下后的数字或中文数字序号 - if (/[上][集期话部篇]?|[集期话部篇]上/.test(filename)) { + // 严格匹配上中下标记:只有当上中下与集期话部篇相邻时才认为是段落标记 + // 避免误匹配文件内容中偶然出现的上中下字符 + if (/上[集期话部篇]|[集期话部篇]上/.test(filename)) { segment_base = 1; - } else if (/[中][集期话部篇]?|[集期话部篇]中/.test(filename)) { + } else if (/中[集期话部篇]|[集期话部篇]中/.test(filename)) { segment_base = 2; - } else if (/[下][集期话部篇]?|[集期话部篇]下/.test(filename)) { + } else if (/下[集期话部篇]|[集期话部篇]下/.test(filename)) { segment_base = 3; } - // 当有上中下标记时,进一步提取后续的序号 - if (segment_base > 0) { - // 提取上中下后的中文数字序号,如:上(一)、上(二) - let chinese_seq_match = filename.match(/[上中下][集期话部篇]?[((]([一二三四五六七八九十百千万零两]+)[))]/); - if (chinese_seq_match) { - let arabic_num = chineseToArabic(chinese_seq_match[1]); - if (arabic_num !== null) { - sequence_number = arabic_num; - } - } else { - // 提取上中下后的阿拉伯数字序号,如:上1、上2 - let arabic_seq_match = filename.match(/[上中下][集期话部篇]?(\d+)/); - if (arabic_seq_match) { - sequence_number = parseInt(arabic_seq_match[1]); - } - } - } else { - // 如果没有上中下标记,检查是否有括号内的中文数字序号 - // 匹配格式如:第2期(一)、第2期(二)等 - let parentheses_chinese_match = filename.match(/[期集话部篇][((]([一二三四五六七八九十百千万零两]+)[))]/); - if (parentheses_chinese_match) { - let arabic_num = chineseToArabic(parentheses_chinese_match[1]); - if (arabic_num !== null) { - sequence_number = arabic_num; - segment_base = 1; // 给一个基础值,确保有括号序号的文件能正确排序 - } - } else { - // 匹配格式如:第2期(1)、第2期(2)等 - let parentheses_arabic_match = filename.match(/[期集话部篇][((](\d+)[))]/); - if (parentheses_arabic_match) { - sequence_number = parseInt(parentheses_arabic_match[1]); - segment_base = 1; // 给一个基础值,确保有括号序号的文件能正确排序 + // 统一的序号提取逻辑,支持多种分隔符和格式 + // 无论是否有上中下标记,都使用相同的序号提取逻辑 + + // 定义序号提取的模式,使用正向匹配组合的方式 + // 这样可以精准匹配,避免误判"星期六"等内容 + const sequence_patterns = [ + // 第+中文数字+期集话部篇+序号:第一期(一)、第五十六期-二、第 一 期 三 + { pattern: /第\s*[一二三四五六七八九十百千万零两]+\s*[期集话部篇]\s*[((]\s*([一二三四五六七八九十百千万零两]+)\s*[))]/u, type: 'chinese' }, + { pattern: /第\s*[一二三四五六七八九十百千万零两]+\s*[期集话部篇]\s*[((]\s*(\d+)\s*[))]/u, type: 'arabic' }, + { pattern: /第\s*[一二三四五六七八九十百千万零两]+\s*[期集话部篇]\s*[-_·丨]\s*([一二三四五六七八九十百千万零两]+)/u, type: 'chinese' }, + { pattern: /第\s*[一二三四五六七八九十百千万零两]+\s*[期集话部篇]\s*[-_·丨]\s*(\d+)/u, type: 'arabic' }, + { pattern: /第\s*[一二三四五六七八九十百千万零两]+\s*[期集话部篇]\s+([一二三四五六七八九十百千万零两]+)(?![一二三四五六七八九十])/u, type: 'chinese' }, + { pattern: /第\s*[一二三四五六七八九十百千万零两]+\s*[期集话部篇]\s+(\d+)(?!\d)/u, type: 'arabic' }, + { pattern: /第\s*[一二三四五六七八九十百千万零两]+\s*[期集话部篇]([一二三四五六七八九十])(?![一二三四五六七八九十])/u, type: 'chinese' }, + { pattern: /第\s*[一二三四五六七八九十百千万零两]+\s*[期集话部篇](\d+)(?!\d)/u, type: 'arabic' }, + + // 第+阿拉伯数字+期集话部篇+序号:第1期(一)、第100期-二、第 1 期 三 + { pattern: /第\s*\d+\s*[期集话部篇]\s*[((]\s*([一二三四五六七八九十百千万零两]+)\s*[))]/u, type: 'chinese' }, + { pattern: /第\s*\d+\s*[期集话部篇]\s*[((]\s*(\d+)\s*[))]/u, type: 'arabic' }, + { pattern: /第\s*\d+\s*[期集话部篇]\s*[-_·丨]\s*([一二三四五六七八九十百千万零两]+)/u, type: 'chinese' }, + { pattern: /第\s*\d+\s*[期集话部篇]\s*[-_·丨]\s*(\d+)/u, type: 'arabic' }, + { pattern: /第\s*\d+\s*[期集话部篇]\s+([一二三四五六七八九十百千万零两]+)(?![一二三四五六七八九十])/u, type: 'chinese' }, + { pattern: /第\s*\d+\s*[期集话部篇]\s+(\d+)(?!\d)/u, type: 'arabic' }, + { pattern: /第\s*\d+\s*[期集话部篇]([一二三四五六七八九十])(?![一二三四五六七八九十])/u, type: 'chinese' }, + { pattern: /第\s*\d+\s*[期集话部篇](\d+)(?!\d)/u, type: 'arabic' }, + + // 上中下+集期话部篇+序号:上集(一)、中部-二、下篇 三 + { pattern: /[上中下][集期话部篇]\s*[((]\s*([一二三四五六七八九十百千万零两]+)\s*[))]/u, type: 'chinese' }, + { pattern: /[上中下][集期话部篇]\s*[((]\s*(\d+)\s*[))]/u, type: 'arabic' }, + { pattern: /[上中下][集期话部篇]\s*[-_·丨]\s*([一二三四五六七八九十百千万零两]+)/u, type: 'chinese' }, + { pattern: /[上中下][集期话部篇]\s*[-_·丨]\s*(\d+)/u, type: 'arabic' }, + { pattern: /[上中下][集期话部篇]\s+([一二三四五六七八九十百千万零两]+)(?![一二三四五六七八九十])/u, type: 'chinese' }, + { pattern: /[上中下][集期话部篇]\s+(\d+)(?!\d)/u, type: 'arabic' }, + { pattern: /[上中下][集期话部篇]([一二三四五六七八九十])(?![一二三四五六七八九十])/u, type: 'chinese' }, + { pattern: /[上中下][集期话部篇](\d+)(?!\d)/u, type: 'arabic' }, + + // 集期话部篇+上中下+序号:集上(一)、部中-二、篇下 三 + { pattern: /[集期话部篇][上中下]\s*[((]\s*([一二三四五六七八九十百千万零两]+)\s*[))]/u, type: 'chinese' }, + { pattern: /[集期话部篇][上中下]\s*[((]\s*(\d+)\s*[))]/u, type: 'arabic' }, + { pattern: /[集期话部篇][上中下]\s*[-_·丨]\s*([一二三四五六七八九十百千万零两]+)/u, type: 'chinese' }, + { pattern: /[集期话部篇][上中下]\s*[-_·丨]\s*(\d+)/u, type: 'arabic' }, + { pattern: /[集期话部篇][上中下]\s+([一二三四五六七八九十百千万零两]+)(?![一二三四五六七八九十])/u, type: 'chinese' }, + { pattern: /[集期话部篇][上中下]\s+(\d+)(?!\d)/u, type: 'arabic' }, + { pattern: /[集期话部篇][上中下]([一二三四五六七八九十])(?![一二三四五六七八九十])/u, type: 'chinese' }, + { pattern: /[集期话部篇][上中下](\d+)(?!\d)/u, type: 'arabic' }, + ]; + + // 尝试匹配序号 + for (const { pattern, type } of sequence_patterns) { + const match = filename.match(pattern); + if (match) { + if (type === 'chinese') { + const arabic_num = chineseToArabic(match[1]); + if (arabic_num !== null) { + sequence_number = arabic_num; + // 如果之前没有检测到上中下标记,给一个基础值 + if (segment_base === 0) { + segment_base = 1; + } + break; + } + } else { // arabic + sequence_number = parseInt(match[1]); + // 如果之前没有检测到上中下标记,给一个基础值 + if (segment_base === 0) { + segment_base = 1; + } + break; } } } diff --git a/app/templates/index.html b/app/templates/index.html index c1e6127..4686b21 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -656,6 +656,16 @@ +
+
+
+ 电影命名规则 +
+ + +
+
+
@@ -1484,7 +1494,7 @@
- 显示 {{ fileManager.currentPage > 0 ? ((fileManager.currentPage - 1) * fileManager.pageSize + 1) + '-' + Math.min(fileManager.currentPage * fileManager.pageSize, fileManager.total) : '0' }} 项,共 {{ fileManager.total }} 个项目{{ fileManager.selectedFiles.length > 0 ? ',已选中 ' + fileManager.selectedFiles.length + ' 项' : '' }} + 显示 {{ fileManager.total > 0 ? ((fileManager.currentPage - 1) * fileManager.pageSize + 1) + '-' + Math.min(fileManager.currentPage * fileManager.pageSize, fileManager.total) : '0-0' }} 项,共 {{ fileManager.total }} 个项目{{ fileManager.selectedFiles.length > 0 ? ',已选中 ' + fileManager.selectedFiles.length + ' 项' : '' }}
+
@@ -2068,6 +2081,8 @@ anime_save_path: "动画目录前缀/剧名 (年份)/剧名 - S季数", variety_save_path: "综艺目录前缀/剧名 (年份)/剧名 - S季数", documentary_save_path: "纪录片目录前缀/剧名 (年份)/剧名 - S季数", + movie_naming_pattern: "^(.*)\.([^.]+)", + movie_naming_replace: "片名 (年份).\\2", tv_naming_rule: "剧名 - S季数E[]", tv_ignore_extension: true }, @@ -3163,11 +3178,20 @@ anime_save_path: "动画目录前缀/剧名 (年份)/剧名 - S季数", variety_save_path: "综艺目录前缀/剧名 (年份)/剧名 - S季数", documentary_save_path: "纪录片目录前缀/剧名 (年份)/剧名 - S季数", + movie_naming_pattern: "^(.*)\.([^.]+)", + movie_naming_replace: "片名 (年份).\\2", tv_naming_rule: "剧名 - S季数E[]", tv_ignore_extension: true, auto_search_resources: "enabled" }; } + // 确保电影命名规则字段存在 + if (!config_data.task_settings.movie_naming_pattern) { + config_data.task_settings.movie_naming_pattern = "^(.*)\.([^.]+)"; + } + if (!config_data.task_settings.movie_naming_replace) { + config_data.task_settings.movie_naming_replace = "片名 (年份).\\2"; + } // 确保电视忽略后缀设置存在 if (config_data.task_settings.tv_ignore_extension === undefined) { config_data.task_settings.tv_ignore_extension = true; @@ -7499,8 +7523,25 @@ if (contentType === 'movie') { this.createTask.taskData.taskname = title; this.createTask.taskData.savepath = this.generateMovieSavePath(savePathTemplate, title, year); - this.createTask.taskData.pattern = ""; - this.createTask.taskData.replace = ""; + + // 应用电影命名规则 + const movieNamingPattern = taskSettings.movie_naming_pattern && taskSettings.movie_naming_pattern.trim() !== "" + ? taskSettings.movie_naming_pattern + : ""; + const movieNamingReplace = taskSettings.movie_naming_replace && taskSettings.movie_naming_replace.trim() !== "" + ? taskSettings.movie_naming_replace + : ""; + + if (movieNamingPattern && movieNamingReplace) { + // 生成智能填充的替换表达式 + const generatedReplace = this.generateMovieNamingRule(movieNamingReplace, title, year); + this.createTask.taskData.pattern = movieNamingPattern; + this.createTask.taskData.replace = generatedReplace; + } else { + this.createTask.taskData.pattern = ""; + this.createTask.taskData.replace = ""; + } + this.createTask.taskData.use_sequence_naming = false; this.createTask.taskData.use_episode_naming = false; this.createTask.taskData.sequence_naming = ""; @@ -7698,6 +7739,26 @@ return namingRule; }, + generateMovieNamingRule(replaceTemplate, movieTitle, year) { + // 生成电影命名规则 + let namingRule = replaceTemplate; + + // 替换片名 + namingRule = namingRule.replace(/片名/g, movieTitle); + + // 替换年份 + if (year) { + namingRule = namingRule.replace(/年份/g, year); + } else { + // 如果没有年份,移除包含年份的部分 + namingRule = namingRule.replace(/\s*\(年份\)/g, ''); + namingRule = namingRule.replace(/\s*(年份)/g, ''); + } + + // 注意:正则表达式的反向引用(如\2)保持不变,将在实际重命名时由正则引擎处理 + + return namingRule; + }, generateCustomFolderPath(taskData) { // 根据任务设置生成自定义文件夹路径 const taskSettings = this.formData.task_settings || {}; @@ -7952,6 +8013,164 @@ this.createTask.loading = false; }); }, + confirmCreateRunAndDeleteTask() { + // 确认创建、运行并删除任务 + if (this.createTask.loading) return; + + // 验证必填字段 + if (!this.createTask.taskData.taskname.trim()) { + this.createTask.error = '任务名称不能为空'; + return; + } + if (!this.createTask.taskData.shareurl.trim()) { + this.createTask.error = '分享链接不能为空'; + return; + } + if (!this.createTask.taskData.savepath.trim()) { + this.createTask.error = '保存路径不能为空'; + return; + } + + this.createTask.loading = true; + this.createTask.error = null; + + // 创建新任务 + const newTask = { ...this.createTask.taskData }; + + // 处理命名模式 + if (newTask.use_sequence_naming) { + newTask.pattern = newTask.sequence_naming; + } else if (newTask.use_episode_naming) { + newTask.pattern = newTask.episode_naming; + } + + // 添加到任务列表 + if (!this.formData.tasklist) { + this.formData.tasklist = []; + } + this.formData.tasklist.push(newTask); + + // 保存配置(不显示配置更新消息) + axios.post('/update', this.formData) + .then(response => { + if (response.data.success) { + this.configModified = false; + // 保存成功后更新用户信息 + this.fetchUserInfo(); + + // 显示任务创建成功消息 + this.showToast('任务创建成功,开始运行并将在完成后自动删除', 'success'); + this.createTask.loading = false; + + // 先关闭创建任务模态框,然后运行新创建的任务 + this.cancelCreateTask(); + + // 等待模态框完全关闭后再打开运行日志模态框并运行任务 + setTimeout(async () => { + const taskIndex = this.formData.tasklist.length - 1; + + // 运行任务并等待完成 + await this.runScriptNowWithCallback(taskIndex, () => { + // 任务完成后删除该任务 + setTimeout(() => { + this.removeTaskSilently(taskIndex); + this.showToast('一次性任务已完成并自动删除', 'info'); + }, 1000); // 等待1秒确保任务状态更新 + }); + }, 300); + } else { + // 错误信息使用alert,确保用户看到 + alert(response.data.message); + this.createTask.loading = false; + } + }) + .catch(error => { + // 错误处理 + alert("保存失败: " + (error.response?.data?.message || error.message || "未知错误")); + this.createTask.loading = false; + }); + }, + async runScriptNowWithCallback(task_index, callback) { + // 运行任务的包装函数,支持完成回调 + body = {}; + if (task_index != null) { + task = { ...this.formData.tasklist[task_index] }; + delete task.runweek; + delete task.enddate; + body = { + "tasklist": [task], + "original_index": task_index + 1 // 添加原始索引,从1开始计数 + }; + } else if (this.configModified) { + if (!confirm('配置已修改但未保存,是否继续运行?')) { + return; + } + } + $('#logModal').modal('toggle'); + this.modalLoading = true; + this.run_log = ''; + try { + // 1. 发送 POST 请求 + const response = await fetch(`/run_script_now`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + // 2. 处理 SSE 流 + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let partialData = ''; + while (true) { + const { done, value } = await reader.read(); + if (done) { + this.modalLoading = false; + // 运行后刷新数据 + this.fetchData(); + // 调用完成回调 + if (callback) callback(); + break; + } + partialData += decoder.decode(value); + const lines = partialData.split('\n').filter(line => line.trim() !== ''); + for (const line of lines) { + if (line.startsWith('data:')) { + const eventData = line.substring(5).trim(); + if (eventData === '[DONE]') { + this.modalLoading = false; + this.fetchData(); + // 调用完成回调 + if (callback) callback(); + return; + } + this.run_log += eventData + '\n'; + // 在更新 run_log 后将滚动条滚动到底部 + this.$nextTick(() => { + const modalBody = document.querySelector('.modal-body'); + modalBody.scrollTop = modalBody.scrollHeight; + }); + } + } + partialData = ''; + } + } catch (error) { + this.modalLoading = false; + // 即使出错也调用回调 + if (callback) callback(); + } + }, + removeTaskSilently(index) { + // 静默删除任务,不显示确认对话框 + if (index >= 0 && index < this.formData.tasklist.length) { + this.formData.tasklist.splice(index, 1); + // 保存配置 + this.saveConfig(); + } + }, openCreateTaskDatePicker() { // 打开创建任务的日期选择器 if (this.$refs.createTaskEnddate) { diff --git a/quark_auto_save.py b/quark_auto_save.py index 8f1b5ab..b8aa823 100644 --- a/quark_auto_save.py +++ b/quark_auto_save.py @@ -147,14 +147,14 @@ def sort_file_by_name(file): # 2. 提取期数/集数 - 第二级排序键 - # 2.1 "第X期/集/话" 格式 - match_chinese = re.search(r'第(\d+)[期集话]', filename) + # 2.1 "第X期/集/话/部/篇" 格式(支持空格) + match_chinese = re.search(r'第\s*(\d+)\s*[期集话部篇]', filename) if match_chinese: episode_value = int(match_chinese.group(1)) - - # 2.1.1 "第[中文数字]期/集/话" 格式 + + # 2.1.1 "第[中文数字]期/集/话/部/篇" 格式(支持空格) if episode_value == float('inf'): - match_chinese_num = re.search(r'第([一二三四五六七八九十百千万零两]+)[期集话]', filename) + match_chinese_num = re.search(r'第\s*([一二三四五六七八九十百千万零两]+)\s*[期集话部篇]', filename) if match_chinese_num: chinese_num = match_chinese_num.group(1) arabic_num = chinese_to_arabic(chinese_num) @@ -230,43 +230,80 @@ def sort_file_by_name(file): segment_base = 0 # 基础值:上=1, 中=2, 下=3 sequence_number = 0 # 序号值:用于处理上中下后的数字或中文数字序号 - if re.search(r'上[集期话部篇]?|[集期话部篇]上', filename): + # 严格匹配上中下标记:只有当上中下与集期话部篇相邻时才认为是段落标记 + # 避免误匹配文件内容中偶然出现的上中下字符 + if re.search(r'上[集期话部篇]|[集期话部篇]上', filename): segment_base = 1 - elif re.search(r'中[集期话部篇]?|[集期话部篇]中', filename): + elif re.search(r'中[集期话部篇]|[集期话部篇]中', filename): segment_base = 2 - elif re.search(r'下[集期话部篇]?|[集期话部篇]下', filename): + elif re.search(r'下[集期话部篇]|[集期话部篇]下', filename): segment_base = 3 - # 当有上中下标记时,进一步提取后续的序号 - if segment_base > 0: - # 提取上中下后的中文数字序号,如:上(一)、上(二) - chinese_seq_match = re.search(r'[上中下][集期话部篇]?[((]([一二三四五六七八九十百千万零两]+)[))]', filename) - if chinese_seq_match: - chinese_num = chinese_seq_match.group(1) - arabic_num = chinese_to_arabic(chinese_num) - if arabic_num is not None: - sequence_number = arabic_num - else: - # 提取上中下后的阿拉伯数字序号,如:上1、上2 - arabic_seq_match = re.search(r'[上中下][集期话部篇]?(\d+)', filename) - if arabic_seq_match: - sequence_number = int(arabic_seq_match.group(1)) - else: - # 如果没有上中下标记,检查是否有括号内的中文数字序号 - # 匹配格式如:第2期(一)、第2期(二)等 - parentheses_chinese_match = re.search(r'[期集话部篇][((]([一二三四五六七八九十百千万零两]+)[))]', filename) - if parentheses_chinese_match: - chinese_num = parentheses_chinese_match.group(1) - arabic_num = chinese_to_arabic(chinese_num) - if arabic_num is not None: - sequence_number = arabic_num - segment_base = 1 # 给一个基础值,确保有括号序号的文件能正确排序 - else: - # 匹配格式如:第2期(1)、第2期(2)等 - parentheses_arabic_match = re.search(r'[期集话部篇][((](\d+)[))]', filename) - if parentheses_arabic_match: - sequence_number = int(parentheses_arabic_match.group(1)) - segment_base = 1 # 给一个基础值,确保有括号序号的文件能正确排序 + # 统一的序号提取逻辑,支持多种分隔符和格式 + # 无论是否有上中下标记,都使用相同的序号提取逻辑 + + # 定义序号提取的模式,使用正向匹配组合的方式 + # 这样可以精准匹配,避免误判"星期六"等内容 + sequence_patterns = [ + # 第+中文数字+期集话部篇+序号:第一期(一)、第五十六期-二、第 一 期 三 + (r'第\s*[一二三四五六七八九十百千万零两]+\s*[期集话部篇]\s*[((]\s*([一二三四五六七八九十百千万零两]+)\s*[))]', 'chinese'), + (r'第\s*[一二三四五六七八九十百千万零两]+\s*[期集话部篇]\s*[((]\s*(\d+)\s*[))]', 'arabic'), + (r'第\s*[一二三四五六七八九十百千万零两]+\s*[期集话部篇]\s*[-_·丨]\s*([一二三四五六七八九十百千万零两]+)', 'chinese'), + (r'第\s*[一二三四五六七八九十百千万零两]+\s*[期集话部篇]\s*[-_·丨]\s*(\d+)', 'arabic'), + (r'第\s*[一二三四五六七八九十百千万零两]+\s*[期集话部篇]\s+([一二三四五六七八九十百千万零两]+)(?![一二三四五六七八九十])', 'chinese'), + (r'第\s*[一二三四五六七八九十百千万零两]+\s*[期集话部篇]\s+(\d+)(?!\d)', 'arabic'), + (r'第\s*[一二三四五六七八九十百千万零两]+\s*[期集话部篇]([一二三四五六七八九十])(?![一二三四五六七八九十])', 'chinese'), + (r'第\s*[一二三四五六七八九十百千万零两]+\s*[期集话部篇](\d+)(?!\d)', 'arabic'), + + # 第+阿拉伯数字+期集话部篇+序号:第1期(一)、第100期-二、第 1 期 三 + (r'第\s*\d+\s*[期集话部篇]\s*[((]\s*([一二三四五六七八九十百千万零两]+)\s*[))]', 'chinese'), + (r'第\s*\d+\s*[期集话部篇]\s*[((]\s*(\d+)\s*[))]', 'arabic'), + (r'第\s*\d+\s*[期集话部篇]\s*[-_·丨]\s*([一二三四五六七八九十百千万零两]+)', 'chinese'), + (r'第\s*\d+\s*[期集话部篇]\s*[-_·丨]\s*(\d+)', 'arabic'), + (r'第\s*\d+\s*[期集话部篇]\s+([一二三四五六七八九十百千万零两]+)(?![一二三四五六七八九十])', 'chinese'), + (r'第\s*\d+\s*[期集话部篇]\s+(\d+)(?!\d)', 'arabic'), + (r'第\s*\d+\s*[期集话部篇]([一二三四五六七八九十])(?![一二三四五六七八九十])', 'chinese'), + (r'第\s*\d+\s*[期集话部篇](\d+)(?!\d)', 'arabic'), + + # 上中下+集期话部篇+序号:上集(一)、中部-二、下篇 三 + (r'[上中下][集期话部篇]\s*[((]\s*([一二三四五六七八九十百千万零两]+)\s*[))]', 'chinese'), + (r'[上中下][集期话部篇]\s*[((]\s*(\d+)\s*[))]', 'arabic'), + (r'[上中下][集期话部篇]\s*[-_·丨]\s*([一二三四五六七八九十百千万零两]+)', 'chinese'), + (r'[上中下][集期话部篇]\s*[-_·丨]\s*(\d+)', 'arabic'), + (r'[上中下][集期话部篇]\s+([一二三四五六七八九十百千万零两]+)(?![一二三四五六七八九十])', 'chinese'), + (r'[上中下][集期话部篇]\s+(\d+)(?!\d)', 'arabic'), + (r'[上中下][集期话部篇]([一二三四五六七八九十])(?![一二三四五六七八九十])', 'chinese'), + (r'[上中下][集期话部篇](\d+)(?!\d)', 'arabic'), + + # 集期话部篇+上中下+序号:集上(一)、部中-二、篇下 三 + (r'[集期话部篇][上中下]\s*[((]\s*([一二三四五六七八九十百千万零两]+)\s*[))]', 'chinese'), + (r'[集期话部篇][上中下]\s*[((]\s*(\d+)\s*[))]', 'arabic'), + (r'[集期话部篇][上中下]\s*[-_·丨]\s*([一二三四五六七八九十百千万零两]+)', 'chinese'), + (r'[集期话部篇][上中下]\s*[-_·丨]\s*(\d+)', 'arabic'), + (r'[集期话部篇][上中下]\s+([一二三四五六七八九十百千万零两]+)(?![一二三四五六七八九十])', 'chinese'), + (r'[集期话部篇][上中下]\s+(\d+)(?!\d)', 'arabic'), + (r'[集期话部篇][上中下]([一二三四五六七八九十])(?![一二三四五六七八九十])', 'chinese'), + (r'[集期话部篇][上中下](\d+)(?!\d)', 'arabic'), + ] + + # 尝试匹配序号 + for pattern, num_type in sequence_patterns: + match = re.search(pattern, filename) + if match: + if num_type == 'chinese': + arabic_num = chinese_to_arabic(match.group(1)) + if arabic_num is not None: + sequence_number = arabic_num + # 如果之前没有检测到上中下标记,给一个基础值 + if segment_base == 0: + segment_base = 1 + break + else: # arabic + sequence_number = int(match.group(1)) + # 如果之前没有检测到上中下标记,给一个基础值 + if segment_base == 0: + segment_base = 1 + break # 组合segment_value:基础值*1000 + 序号值,确保排序正确 segment_value = segment_base * 1000 + sequence_number @@ -363,6 +400,18 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None): for pattern in resolution_patterns: filename_without_dates = re.sub(pattern, ' ', filename_without_dates) + # 预处理:移除季编号标识,避免误提取季编号为集编号 + season_patterns = [ + r'[Ss]\d+(?![Ee])', # S1, S01 (但不包括S01E01中的S01) + r'[Ss]\s+\d+', # S 1, S 01 + r'Season\s*\d+', # Season1, Season 1 + r'第\s*\d+\s*季', # 第1季, 第 1 季 + r'第\s*[一二三四五六七八九十百千万零两]+\s*季', # 第一季, 第 二 季 + ] + + for pattern in season_patterns: + filename_without_dates = re.sub(pattern, ' ', filename_without_dates, flags=re.IGNORECASE) + # 优先匹配SxxExx格式 match_s_e = re.search(r'[Ss](\d+)[Ee](\d+)', filename_without_dates) if match_s_e: