mirror of
https://github.com/Cp0204/quark-auto-save.git
synced 2026-01-13 15:50:45 +08:00
commit
57dbccf418
93
app/run.py
93
app/run.py
@ -27,6 +27,7 @@ import os
|
||||
import re
|
||||
import random
|
||||
import time
|
||||
import treelib
|
||||
|
||||
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
sys.path.insert(0, parent_dir)
|
||||
@ -35,19 +36,41 @@ 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
|
||||
from quark_auto_save import extract_episode_number, sort_file_by_name, chinese_to_arabic, is_date_format
|
||||
|
||||
# 导入数据库模块
|
||||
try:
|
||||
from app.sdk.db import RecordDB
|
||||
# 先尝试相对导入
|
||||
from sdk.db import RecordDB
|
||||
except ImportError:
|
||||
# 如果没有数据库模块,定义一个空类
|
||||
class RecordDB:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def get_records(self, *args, **kwargs):
|
||||
return {"records": [], "pagination": {"total_records": 0, "total_pages": 0, "current_page": 1, "page_size": 20}}
|
||||
try:
|
||||
# 如果相对导入失败,尝试从app包导入
|
||||
from app.sdk.db import RecordDB
|
||||
except ImportError:
|
||||
# 如果没有数据库模块,定义一个空类
|
||||
class RecordDB:
|
||||
def __init__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def get_records(self, *args, **kwargs):
|
||||
return {"records": [], "pagination": {"total_records": 0, "total_pages": 0, "current_page": 1, "page_size": 20}}
|
||||
|
||||
# 导入工具函数
|
||||
try:
|
||||
# 先尝试相对导入
|
||||
from sdk.utils import format_bytes, get_file_icon, format_file_display
|
||||
except ImportError:
|
||||
try:
|
||||
# 如果相对导入失败,尝试从app包导入
|
||||
from app.sdk.utils import format_bytes, get_file_icon, format_file_display
|
||||
except ImportError:
|
||||
# 如果导入失败,使用默认实现或从quark_auto_save导入
|
||||
# format_bytes已从quark_auto_save导入
|
||||
def get_file_icon(file_name, is_dir=False):
|
||||
return "📄" if not is_dir else "📁"
|
||||
|
||||
def format_file_display(prefix, icon, name):
|
||||
return f"{prefix}{icon} {name}"
|
||||
|
||||
|
||||
def get_app_ver():
|
||||
@ -463,37 +486,6 @@ def get_task_suggestions():
|
||||
return jsonify({"success": True, "message": f"error: {str(e)}"})
|
||||
|
||||
|
||||
# 添加函数,与主程序保持一致
|
||||
def is_date_format(number_str):
|
||||
"""
|
||||
判断一个纯数字字符串是否可能是日期格式
|
||||
支持的格式:YYYYMMDD, MMDD
|
||||
"""
|
||||
# 判断YYYYMMDD格式 (8位数字)
|
||||
if len(number_str) == 8 and number_str.startswith('20'):
|
||||
year = int(number_str[:4])
|
||||
month = int(number_str[4:6])
|
||||
day = int(number_str[6:8])
|
||||
|
||||
# 简单检查月份和日期是否有效
|
||||
if 1 <= month <= 12 and 1 <= day <= 31:
|
||||
# 可能是日期格式
|
||||
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
|
||||
|
||||
# 其他长度的纯数字不视为日期格式
|
||||
return False
|
||||
|
||||
|
||||
# 获取分享详情接口
|
||||
@app.route("/get_share_detail", methods=["GET", "POST"])
|
||||
def get_share_detail():
|
||||
@ -599,6 +591,22 @@ def get_share_detail():
|
||||
episode_pattern = regex.get("episode_naming")
|
||||
episode_patterns = regex.get("episode_patterns", [])
|
||||
|
||||
# 添加中文数字匹配模式
|
||||
chinese_patterns = [
|
||||
{"regex": r'第([一二三四五六七八九十百千万零两]+)集'},
|
||||
{"regex": r'第([一二三四五六七八九十百千万零两]+)期'},
|
||||
{"regex": r'第([一二三四五六七八九十百千万零两]+)话'},
|
||||
{"regex": r'([一二三四五六七八九十百千万零两]+)集'},
|
||||
{"regex": r'([一二三四五六七八九十百千万零两]+)期'},
|
||||
{"regex": r'([一二三四五六七八九十百千万零两]+)话'}
|
||||
]
|
||||
|
||||
# 合并中文模式到episode_patterns
|
||||
if episode_patterns:
|
||||
episode_patterns.extend(chinese_patterns)
|
||||
else:
|
||||
episode_patterns = chinese_patterns
|
||||
|
||||
# 调用全局的集编号提取函数
|
||||
def extract_episode_number_local(filename):
|
||||
return extract_episode_number(filename, episode_patterns=episode_patterns)
|
||||
@ -672,7 +680,7 @@ def get_share_detail():
|
||||
if any(word in item['file_name'] for word in filterwords_list):
|
||||
item["filtered"] = True
|
||||
|
||||
# 为每个文件生成新文件名
|
||||
# 为每个文件生成新文件名并存储剧集编号用于排序
|
||||
for file in sorted_files:
|
||||
if not file.get("filtered"):
|
||||
# 获取文件扩展名
|
||||
@ -686,9 +694,12 @@ def get_share_detail():
|
||||
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["episode_number"] = episode_num
|
||||
else:
|
||||
# 无法提取剧集号,标记为无法处理
|
||||
file["file_name_re"] = "❌ 无法识别剧集号"
|
||||
file["episode_number"] = 9999999 # 给一个很大的值,确保排在最后
|
||||
|
||||
return share_detail
|
||||
else:
|
||||
|
||||
@ -849,6 +849,8 @@ select.form-control {
|
||||
box-sizing: border-box !important;
|
||||
border-top-left-radius: 0 !important; /* 移除左上角圆角 */
|
||||
border-bottom-left-radius: 0 !important; /* 移除左下角圆角 */
|
||||
border-top-right-radius: 0 !important; /* 移除右上角圆角 */
|
||||
border-bottom-right-radius: 0 !important; /* 移除右下角圆角 */
|
||||
}
|
||||
|
||||
/* 为确保输入组中select元素的边角正确 */
|
||||
@ -4099,3 +4101,31 @@ table.selectable-records .expand-button:hover {
|
||||
.task:hover .btn.btn-block.text-left {
|
||||
color: var(--focus-border-color);
|
||||
}
|
||||
|
||||
select.task-filter-select,
|
||||
.task-filter-select {
|
||||
border-top-right-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
.input-group .form-control:focus {
|
||||
z-index: 3;
|
||||
position: relative;
|
||||
}
|
||||
.input-group .form-control:focus + .input-group-append .btn,
|
||||
.input-group .form-control:focus + .input-group-append .btn:focus {
|
||||
border-left-color: #2563eb !important; /* 激活时的边框色 */
|
||||
box-shadow: none !important;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 添加任务名称悬停样式 */
|
||||
.task-name-hover {
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.task-name-hover:hover {
|
||||
color: var(--focus-border-color) !important;
|
||||
}
|
||||
|
||||
@ -115,6 +115,66 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 添加中文数字转阿拉伯数字的函数
|
||||
function chineseToArabic(chinese) {
|
||||
if (!chinese) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 数字映射
|
||||
const digitMap = {
|
||||
'零': 0, '一': 1, '二': 2, '三': 3, '四': 4,
|
||||
'五': 5, '六': 6, '七': 7, '八': 8, '九': 9,
|
||||
'两': 2
|
||||
};
|
||||
|
||||
// 单位映射
|
||||
const unitMap = {
|
||||
'十': 10,
|
||||
'百': 100,
|
||||
'千': 1000,
|
||||
'万': 10000
|
||||
};
|
||||
|
||||
// 如果是单个字符,直接返回对应数字
|
||||
if (chinese.length === 1) {
|
||||
if (chinese === '十') return 10;
|
||||
return digitMap[chinese];
|
||||
}
|
||||
|
||||
let result = 0;
|
||||
let section = 0;
|
||||
let number = 0;
|
||||
|
||||
// 从左向右处理
|
||||
for (let i = 0; i < chinese.length; i++) {
|
||||
const char = chinese[i];
|
||||
|
||||
if (char in digitMap) {
|
||||
number = digitMap[char];
|
||||
} else if (char in unitMap) {
|
||||
const unit = unitMap[char];
|
||||
// 如果前面没有数字,默认为1,例如"十"表示1*10=10
|
||||
section += (number || 1) * unit;
|
||||
number = 0;
|
||||
|
||||
// 如果是万级单位,累加到结果并重置section
|
||||
if (unit === 10000) {
|
||||
result += section;
|
||||
section = 0;
|
||||
}
|
||||
} else {
|
||||
// 非法字符
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 加上最后的数字和小节
|
||||
result += section + number;
|
||||
|
||||
return result;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
@ -523,7 +583,7 @@
|
||||
<option value="">全部任务</option>
|
||||
<option v-for="task in taskNames" :value="task" v-html="task"></option>
|
||||
</select>
|
||||
<i class="bi bi-chevron-down select-arrow" style="position: absolute; pointer-events: none; color: var(--dark-text-color);"></i>
|
||||
<!-- <i class="bi bi-chevron-down select-arrow" style="position: absolute; pointer-events: none; color: var(--dark-text-color);"></i> -->
|
||||
</div>
|
||||
<div class="input-group-append">
|
||||
<button type="button" class="btn btn-outline-secondary filter-btn-square" @click="clearData('taskDirSelected')"><i class="bi bi-x-lg"></i></button>
|
||||
@ -735,7 +795,7 @@
|
||||
<option value="">全部任务</option>
|
||||
<option v-for="task in historyTasks" :value="task" v-html="task"></option>
|
||||
</select>
|
||||
<i class="bi bi-chevron-down select-arrow" style="position: absolute; pointer-events: none; color: var(--dark-text-color);"></i>
|
||||
<!-- <i class="bi bi-chevron-down select-arrow" style="position: absolute; pointer-events: none; color: var(--dark-text-color);"></i> -->
|
||||
</div>
|
||||
<div class="input-group-append">
|
||||
<button type="button" class="btn btn-outline-secondary filter-btn-square" @click="clearData('historyTaskSelected')"><i class="bi bi-x-lg"></i></button>
|
||||
@ -768,13 +828,15 @@
|
||||
<div v-if="!record._expandedFields || !record._expandedFields.includes('task_name')"
|
||||
class="text-truncate"
|
||||
:title="record.task_name"
|
||||
v-check-overflow="index + '|task_name'">
|
||||
v-check-overflow="index + '|task_name'"
|
||||
:class="{'task-name-hover': true}"
|
||||
@click.stop="filterByTaskName(record.task_name, $event)">
|
||||
{{ record.task_name }}
|
||||
</div>
|
||||
<div class="expand-button" v-if="isTextTruncated(record.task_name, index, 'task_name')" @click.stop="toggleExpand(index, 'task_name', $event)">
|
||||
<i :class="record._expandedFields && record._expandedFields.length > 0 ? 'bi bi-chevron-up' : 'bi bi-chevron-down'"></i>
|
||||
</div>
|
||||
<div class="expanded-text" v-if="record._expandedFields && record._expandedFields.includes('task_name')">
|
||||
<div class="expanded-text task-name-hover" v-if="record._expandedFields && record._expandedFields.includes('task_name')" @click.stop="filterByTaskName(record.task_name, $event)">
|
||||
{{ record.task_name }}
|
||||
</div>
|
||||
</td>
|
||||
@ -1788,13 +1850,10 @@
|
||||
if (match) {
|
||||
separatorMatch = match;
|
||||
// 根据不同的格式,确定季序号的位置
|
||||
if (match[3] && match[3].match(/[一二三四五六七八九十零]/)) {
|
||||
if (match[3] && match[3].match(/[一二三四五六七八九十百千万零两]/)) {
|
||||
// 将中文数字转换为阿拉伯数字
|
||||
const chineseToNumber = {
|
||||
'零': '0', '一': '1', '二': '2', '三': '3', '四': '4',
|
||||
'五': '5', '六': '6', '七': '7', '八': '8', '九': '9', '十': '10'
|
||||
};
|
||||
seasonNumber = chineseToNumber[match[3]] || '01';
|
||||
const arabicNumber = chineseToArabic(match[3]);
|
||||
seasonNumber = arabicNumber !== null ? String(arabicNumber) : '01';
|
||||
} else {
|
||||
seasonNumber = match[3] || '01';
|
||||
}
|
||||
@ -1891,18 +1950,15 @@
|
||||
if (pathParts.length > 0) {
|
||||
const lastPart = pathParts[pathParts.length - 1];
|
||||
// 匹配中文季序号格式
|
||||
const chineseSeasonMatch = lastPart.match(/^(.*?)([\s\.\-_]+)第([一二三四五六七八九十零]+)季$/);
|
||||
const chineseSeasonMatch = lastPart.match(/^(.*?)([\s\.\-_]+)第([一二三四五六七八九十百千万零两]+)季$/);
|
||||
if (chineseSeasonMatch) {
|
||||
const showName = chineseSeasonMatch[1].trim();
|
||||
const separator = chineseSeasonMatch[2];
|
||||
const chineseSeason = chineseSeasonMatch[3];
|
||||
|
||||
// 将中文数字转换为阿拉伯数字
|
||||
const chineseToNumber = {
|
||||
'零': '0', '一': '1', '二': '2', '三': '3', '四': '4',
|
||||
'五': '5', '六': '6', '七': '7', '八': '8', '九': '9', '十': '10'
|
||||
};
|
||||
const seasonNumber = chineseToNumber[chineseSeason] || '1';
|
||||
const arabicNumber = chineseToArabic(chineseSeason);
|
||||
const seasonNumber = arabicNumber !== null ? String(arabicNumber) : '1';
|
||||
|
||||
// 更新最末级目录为标准格式
|
||||
pathParts[pathParts.length - 1] = showName + separator + 'S' + seasonNumber.padStart(2, '0');
|
||||
@ -2345,6 +2401,9 @@
|
||||
},
|
||||
selectSuggestion(index, suggestion) {
|
||||
this.smart_param.showSuggestions = false;
|
||||
// 确保显示的是选择需转存的文件夹界面,而不是命名预览界面
|
||||
this.fileSelect.previewRegex = false;
|
||||
this.fileSelect.selectDir = true;
|
||||
this.showShareSelect(index, suggestion.shareurl);
|
||||
},
|
||||
addMagicRegex() {
|
||||
@ -2419,7 +2478,7 @@
|
||||
alert('删除项目出错: ' + (error.response?.data?.message || error.message));
|
||||
});
|
||||
},
|
||||
getSavepathDetail(params = 0) {
|
||||
getSavepathDetail(params = 0, retryCount = 0, maxRetries = 3) {
|
||||
if (params === "" || params === null || params === undefined) {
|
||||
// 为空字符串时直接使用根目录fid
|
||||
params = 0;
|
||||
@ -2440,8 +2499,18 @@
|
||||
}
|
||||
this.modalLoading = false;
|
||||
}).catch(error => {
|
||||
this.fileSelect.error = "获取文件夹列表失败";
|
||||
this.modalLoading = false;
|
||||
// 如果还有重试次数,则进行重试
|
||||
if (retryCount < maxRetries) {
|
||||
console.log(`获取文件夹列表失败,正在进行第 ${retryCount + 1} 次重试...`);
|
||||
// 短暂延迟后重试
|
||||
setTimeout(() => {
|
||||
this.getSavepathDetail(params, retryCount + 1, maxRetries);
|
||||
}, 1000); // 1秒后重试
|
||||
} else {
|
||||
// 超过最大重试次数,显示错误信息
|
||||
this.fileSelect.error = "获取文件夹列表失败,请关闭窗口再试一次";
|
||||
this.modalLoading = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
showSavepathSelect(index) {
|
||||
@ -2469,7 +2538,7 @@
|
||||
this.getSavepathDetail(savepath);
|
||||
}
|
||||
},
|
||||
getShareDetail() {
|
||||
getShareDetail(retryCount = 0, maxRetries = 3) {
|
||||
this.modalLoading = true;
|
||||
axios.post('/get_share_detail', {
|
||||
shareurl: this.fileSelect.shareurl,
|
||||
@ -2515,8 +2584,18 @@
|
||||
}
|
||||
this.modalLoading = false;
|
||||
}).catch(error => {
|
||||
this.fileSelect.error = "获取文件夹列表失败";
|
||||
this.modalLoading = false;
|
||||
// 如果还有重试次数,则进行重试
|
||||
if (retryCount < maxRetries) {
|
||||
console.log(`获取文件夹列表失败,正在进行第 ${retryCount + 1} 次重试...`);
|
||||
// 短暂延迟后重试
|
||||
setTimeout(() => {
|
||||
this.getShareDetail(retryCount + 1, maxRetries);
|
||||
}, 1000); // 1秒后重试
|
||||
} else {
|
||||
// 超过最大重试次数,显示错误信息
|
||||
this.fileSelect.error = "获取文件夹列表失败,请关闭窗口再试一次";
|
||||
this.modalLoading = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
showShareSelect(index, shareurl = null) {
|
||||
@ -2550,7 +2629,8 @@
|
||||
}
|
||||
|
||||
$('#fileSelectModal').modal('toggle');
|
||||
this.getShareDetail();
|
||||
// 调用getShareDetail时不传递任何参数,使用默认的重试机制
|
||||
this.getShareDetail(0, 3);
|
||||
|
||||
// 命名预览模式下,确保在模态框显示后检查滚动条状态
|
||||
if (this.fileSelect.previewRegex) {
|
||||
@ -2563,7 +2643,8 @@
|
||||
path = { fid: fid, name: name }
|
||||
if (this.fileSelect.selectShare) {
|
||||
this.fileSelect.shareurl = this.getShareurl(this.fileSelect.shareurl, path);
|
||||
this.getShareDetail();
|
||||
// 使用重试机制调用getShareDetail
|
||||
this.getShareDetail(0, 3);
|
||||
} else {
|
||||
if (fid == "0") {
|
||||
this.fileSelect.paths = []
|
||||
@ -2575,7 +2656,8 @@
|
||||
this.fileSelect.paths.push({ fid: fid, name: name })
|
||||
}
|
||||
}
|
||||
this.getSavepathDetail(fid);
|
||||
// 使用重试机制调用getSavepathDetail
|
||||
this.getSavepathDetail(fid, 0, 3);
|
||||
}
|
||||
},
|
||||
selectCurrentFolder(addTaskname = false) {
|
||||
@ -3156,14 +3238,13 @@
|
||||
},
|
||||
// 文件选择模态框的排序方法
|
||||
sortFileList(field) {
|
||||
// 如果点击了当前排序字段,则切换排序顺序
|
||||
// 切换排序方向
|
||||
if (this.fileSelect.sortBy === field) {
|
||||
this.fileSelect.sortOrder = this.fileSelect.sortOrder === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
// 如果点击了不同的字段,则设置为该字段并使用默认排序顺序
|
||||
this.fileSelect.sortBy = field;
|
||||
// 对于日期和重命名列使用降序,其他字段使用升序
|
||||
this.fileSelect.sortOrder = field === 'updated_at' || field === 'file_name_re' ? 'desc' : 'asc';
|
||||
// 默认降序(除了文件名外)
|
||||
this.fileSelect.sortOrder = field === 'file_name' ? 'asc' : 'desc';
|
||||
}
|
||||
|
||||
// 按选定字段和顺序对文件列表进行排序
|
||||
@ -3179,9 +3260,15 @@
|
||||
aValue = a.file_name.toLowerCase();
|
||||
bValue = b.file_name.toLowerCase();
|
||||
} else if (field === 'file_name_re') {
|
||||
// 对于重命名列,使用重命名后的文件名进行排序
|
||||
aValue = (a.file_name_re || '').toLowerCase();
|
||||
bValue = (b.file_name_re || '').toLowerCase();
|
||||
// 对于重命名列,优先使用episode_number进行数值排序(如果存在)
|
||||
if (a.episode_number !== undefined && b.episode_number !== undefined) {
|
||||
aValue = a.episode_number;
|
||||
bValue = b.episode_number;
|
||||
} else {
|
||||
// 否则使用重命名后的文件名进行字符串排序
|
||||
aValue = (a.file_name_re || '').toLowerCase();
|
||||
bValue = (b.file_name_re || '').toLowerCase();
|
||||
}
|
||||
} else if (field === 'size') {
|
||||
// 对于文件大小,使用数字进行排序
|
||||
if (a.dir && b.dir) {
|
||||
@ -3223,9 +3310,15 @@
|
||||
aValue = a.file_name.toLowerCase();
|
||||
bValue = b.file_name.toLowerCase();
|
||||
} else if (field === 'file_name_re') {
|
||||
// 对于重命名列,使用重命名后的文件名进行排序
|
||||
aValue = (a.file_name_re || '').toLowerCase();
|
||||
bValue = (b.file_name_re || '').toLowerCase();
|
||||
// 对于重命名列,优先使用episode_number进行数值排序(如果存在)
|
||||
if (a.episode_number !== undefined && b.episode_number !== undefined) {
|
||||
aValue = a.episode_number;
|
||||
bValue = b.episode_number;
|
||||
} else {
|
||||
// 否则使用重命名后的文件名进行字符串排序
|
||||
aValue = (a.file_name_re || '').toLowerCase();
|
||||
bValue = (b.file_name_re || '').toLowerCase();
|
||||
}
|
||||
} else if (field === 'size') {
|
||||
// 对于文件大小,使用数字进行排序
|
||||
if (a.dir && b.dir) {
|
||||
@ -3684,6 +3777,23 @@
|
||||
alert("刷新 AList 目录失败: " + (error.response?.data?.message || error.message || "未知错误"));
|
||||
});
|
||||
},
|
||||
filterByTaskName(taskName, event) {
|
||||
// 防止事件冒泡,避免触发行选择
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
// 如果当前已经筛选了该任务,则取消筛选
|
||||
if (this.historyTaskSelected === taskName) {
|
||||
this.historyTaskSelected = "";
|
||||
} else {
|
||||
// 设置任务筛选值
|
||||
this.historyTaskSelected = taskName;
|
||||
}
|
||||
|
||||
// 重新加载记录
|
||||
this.loadHistoryRecords();
|
||||
},
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -141,12 +141,30 @@ def sort_file_by_name(file):
|
||||
if match_chinese:
|
||||
episode_value = int(match_chinese.group(1))
|
||||
|
||||
# 2.1.1 "第[中文数字]期/集/话" 格式
|
||||
if episode_value == float('inf'):
|
||||
match_chinese_num = re.search(r'第([一二三四五六七八九十百千万零两]+)[期集话]', filename)
|
||||
if match_chinese_num:
|
||||
chinese_num = match_chinese_num.group(1)
|
||||
arabic_num = chinese_to_arabic(chinese_num)
|
||||
if arabic_num is not None:
|
||||
episode_value = arabic_num
|
||||
|
||||
# 2.2 "X集/期/话" 格式
|
||||
if episode_value == float('inf'):
|
||||
match_chinese_simple = re.search(r'(\d+)[期集话]', filename)
|
||||
if match_chinese_simple:
|
||||
episode_value = int(match_chinese_simple.group(1))
|
||||
|
||||
# 2.2.1 "[中文数字]集/期/话" 格式
|
||||
if episode_value == float('inf'):
|
||||
match_chinese_simple_num = re.search(r'([一二三四五六七八九十百千万零两]+)[期集话]', filename)
|
||||
if match_chinese_simple_num:
|
||||
chinese_num = match_chinese_simple_num.group(1)
|
||||
arabic_num = chinese_to_arabic(chinese_num)
|
||||
if arabic_num is not None:
|
||||
episode_value = arabic_num
|
||||
|
||||
# 2.3 S01E01格式
|
||||
if episode_value == float('inf'):
|
||||
match_s_e = re.search(r'[Ss](\d+)[Ee](\d+)', filename)
|
||||
@ -299,6 +317,16 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None):
|
||||
r'_?(\d+)_?'
|
||||
]
|
||||
|
||||
# 添加中文数字匹配模式
|
||||
chinese_patterns = [
|
||||
r'第([一二三四五六七八九十百千万零两]+)集',
|
||||
r'第([一二三四五六七八九十百千万零两]+)期',
|
||||
r'第([一二三四五六七八九十百千万零两]+)话',
|
||||
r'([一二三四五六七八九十百千万零两]+)集',
|
||||
r'([一二三四五六七八九十百千万零两]+)期',
|
||||
r'([一二三四五六七八九十百千万零两]+)话'
|
||||
]
|
||||
|
||||
patterns = None
|
||||
|
||||
# 检查传入的episode_patterns参数
|
||||
@ -328,7 +356,19 @@ def extract_episode_number(filename, episode_patterns=None, config_data=None):
|
||||
return episode_num
|
||||
except:
|
||||
continue
|
||||
|
||||
|
||||
# 尝试匹配中文数字模式
|
||||
for pattern_regex in chinese_patterns:
|
||||
try:
|
||||
match = re.search(pattern_regex, filename_without_dates)
|
||||
if match:
|
||||
chinese_num = match.group(1)
|
||||
arabic_num = chinese_to_arabic(chinese_num)
|
||||
if arabic_num is not None:
|
||||
return arabic_num
|
||||
except:
|
||||
continue
|
||||
|
||||
# 如果从不含日期的文件名中没有找到剧集号,尝试从原始文件名中提取
|
||||
# 这是为了兼容某些特殊情况,但要检查提取的数字不是日期
|
||||
file_name_without_ext = os.path.splitext(filename)[0]
|
||||
@ -395,6 +435,71 @@ def is_date_format(number_str):
|
||||
# 其他格式不视为日期格式
|
||||
return False
|
||||
|
||||
def chinese_to_arabic(chinese):
|
||||
"""
|
||||
将中文数字转换为阿拉伯数字
|
||||
支持格式:一、二、三、四、五、六、七、八、九、十、百、千、万
|
||||
以及:零、两(特殊处理为2)
|
||||
|
||||
Args:
|
||||
chinese: 中文数字字符串
|
||||
|
||||
Returns:
|
||||
int: 转换后的阿拉伯数字,如果无法转换则返回None
|
||||
"""
|
||||
if not chinese:
|
||||
return None
|
||||
|
||||
# 数字映射
|
||||
digit_map = {
|
||||
'零': 0, '一': 1, '二': 2, '三': 3, '四': 4,
|
||||
'五': 5, '六': 6, '七': 7, '八': 8, '九': 9,
|
||||
'两': 2
|
||||
}
|
||||
|
||||
# 单位映射
|
||||
unit_map = {
|
||||
'十': 10,
|
||||
'百': 100,
|
||||
'千': 1000,
|
||||
'万': 10000
|
||||
}
|
||||
|
||||
# 如果是单个字符,直接返回对应数字
|
||||
if len(chinese) == 1:
|
||||
if chinese == '十':
|
||||
return 10
|
||||
return digit_map.get(chinese)
|
||||
|
||||
result = 0
|
||||
section = 0
|
||||
number = 0
|
||||
|
||||
# 从左向右处理
|
||||
for i in range(len(chinese)):
|
||||
char = chinese[i]
|
||||
|
||||
if char in digit_map:
|
||||
number = digit_map[char]
|
||||
elif char in unit_map:
|
||||
unit = unit_map[char]
|
||||
# 如果前面没有数字,默认为1,例如"十"表示1*10=10
|
||||
section += (number or 1) * unit
|
||||
number = 0
|
||||
|
||||
# 如果是万级单位,累加到结果并重置section
|
||||
if unit == 10000:
|
||||
result += section
|
||||
section = 0
|
||||
else:
|
||||
# 非法字符
|
||||
return None
|
||||
|
||||
# 加上最后的数字和小节
|
||||
result += section + number
|
||||
|
||||
return result
|
||||
|
||||
# 兼容青龙
|
||||
try:
|
||||
from treelib import Tree
|
||||
@ -3133,7 +3238,7 @@ def format_bytes(size_bytes: int) -> str:
|
||||
while size_bytes >= 1024 and i < len(units) - 1:
|
||||
size_bytes /= 1024
|
||||
i += 1
|
||||
return f"{size_bytes:.2f}{units[i]}"
|
||||
return f"{size_bytes:.2f} {units[i]}"
|
||||
|
||||
|
||||
def do_sign(account):
|
||||
@ -4258,6 +4363,11 @@ def do_save(account, tasklist=[]):
|
||||
number_part = filename[len(prefix):].split(suffix)[0] if suffix else filename[len(prefix):]
|
||||
if number_part.isdigit():
|
||||
return int(number_part)
|
||||
# 尝试转换中文数字
|
||||
else:
|
||||
arabic_num = chinese_to_arabic(number_part)
|
||||
if arabic_num is not None:
|
||||
return arabic_num
|
||||
|
||||
# 如果所有方法都失败,返回float('inf')
|
||||
return float('inf')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user