Files
MoonTVPlus/src/lib/season-parser.ts

199 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 季度标识解析工具
* 用于从文件夹名称中识别和提取季度信息
*/
export interface SeasonInfo {
/** 清理后的标题(移除季度标识和年份) */
cleanTitle: string;
/** 季度编号,如果未识别则为 null */
seasonNumber: number | null;
/** 年份,如果未识别则为 null */
year: number | null;
/** 原始标题 */
originalTitle: string;
}
/**
* 从文件夹名称中提取季度信息
* 支持多种格式:
* - S01, S1, s01, s1
* - [S01], [S1]
* - Season 1, Season 01
* - 第一季, 第1季, 第01季
* - [第一季], [第1季]
* - 第一部, 第1部
* - 年份: 2023, [2023], (2023)
*/
export function parseSeasonFromTitle(title: string): SeasonInfo {
const originalTitle = title;
// 先将下划线替换成空格,方便后续解析和搜索
let cleanTitle = title.replace(/_/g, ' ');
let seasonNumber: number | null = null;
let year: number | null = null;
// 定义季度匹配模式(按优先级排序)
const patterns = [
// [S01], [S1], [s01], [s1] 格式(方括号包裹)
{
regex: /\[([Ss]\d{1,2})\]/,
extract: (match: RegExpMatchArray) => {
const seasonMatch = match[1].match(/[Ss](\d{1,2})/);
return seasonMatch ? parseInt(seasonMatch[1], 10) : null;
},
},
// S01, S1, s01, s1 格式
{
regex: /\b[Ss](\d{1,2})\b/,
extract: (match: RegExpMatchArray) => parseInt(match[1], 10),
},
// [Season 1], [Season 01] 格式(方括号包裹)
{
regex: /\[Season\s+(\d{1,2})\]/i,
extract: (match: RegExpMatchArray) => parseInt(match[1], 10),
},
// Season 1, Season 01 格式
{
regex: /\bSeason\s+(\d{1,2})\b/i,
extract: (match: RegExpMatchArray) => parseInt(match[1], 10),
},
// [第一季], [第1季], [第01季] 格式(方括号包裹)
{
regex: /\[第([一二三四五六七八九十\d]{1,2})季\]/,
extract: (match: RegExpMatchArray) => chineseNumberToInt(match[1]),
},
// 第一季, 第1季, 第01季 格式
{
regex: /第([一二三四五六七八九十\d]{1,2})季/,
extract: (match: RegExpMatchArray) => chineseNumberToInt(match[1]),
},
// [第一部], [第1部] 格式(方括号包裹)
{
regex: /\[第([一二三四五六七八九十\d]{1,2})部\]/,
extract: (match: RegExpMatchArray) => chineseNumberToInt(match[1]),
},
// 第一部, 第1部, 第01部 格式
{
regex: /第([一二三四五六七八九十\d]{1,2})部/,
extract: (match: RegExpMatchArray) => chineseNumberToInt(match[1]),
},
];
// 尝试匹配每个模式
for (const pattern of patterns) {
const match = cleanTitle.match(pattern.regex);
if (match) {
const extracted = pattern.extract(match);
if (extracted !== null) {
seasonNumber = extracted;
// 移除匹配到的季度标识
cleanTitle = cleanTitle.replace(pattern.regex, '').trim();
break;
}
}
}
// 提取年份(支持多种格式)
const yearPatterns = [
/\[(\d{4})\]/, // [2023]
/\((\d{4})\)/, // (2023)
/\b(\d{4})\b/, // 2023
];
for (const yearPattern of yearPatterns) {
const yearMatch = cleanTitle.match(yearPattern);
if (yearMatch) {
const extractedYear = parseInt(yearMatch[1], 10);
// 验证年份合理性1900-2100
if (extractedYear >= 1900 && extractedYear <= 2100) {
year = extractedYear;
cleanTitle = cleanTitle.replace(yearPattern, '').trim();
break;
}
}
}
// 清理标题:移除空的方括号和多余的空格
cleanTitle = cleanTitle
.replace(/\[\s*\]/g, '') // 移除空方括号
.replace(/\(\s*\)/g, '') // 移除空圆括号
.replace(/\s+/g, ' ') // 合并多个空格
.replace(/[·\-_\s]+$/, '') // 移除末尾的特殊字符
.trim();
return {
cleanTitle,
seasonNumber,
year,
originalTitle,
};
}
/**
* 将中文数字转换为阿拉伯数字
*/
function chineseNumberToInt(str: string): number {
// 如果已经是数字,直接返回
if (/^\d+$/.test(str)) {
return parseInt(str, 10);
}
const chineseNumbers: Record<string, number> = {
'一': 1, '二': 2, '三': 3, '四': 4, '五': 5,
'六': 6, '七': 7, '八': 8, '九': 9, '十': 10,
};
// 处理"十"的特殊情况
if (str === '十') {
return 10;
}
// 处理"十X"的情况(如"十一"
if (str.startsWith('十')) {
const unit = str.substring(1);
return 10 + (chineseNumbers[unit] || 0);
}
// 处理"X十"的情况(如"二十"
if (str.endsWith('十')) {
const tens = str.substring(0, str.length - 1);
return (chineseNumbers[tens] || 0) * 10;
}
// 处理"X十Y"的情况(如"二十一"
const tenIndex = str.indexOf('十');
if (tenIndex !== -1) {
const tens = str.substring(0, tenIndex);
const units = str.substring(tenIndex + 1);
return (chineseNumbers[tens] || 0) * 10 + (chineseNumbers[units] || 0);
}
// 单个中文数字
return chineseNumbers[str] || parseInt(str, 10) || 1;
}
/**
* 测试示例
*/
export function testSeasonParser() {
const testCases = [
'权力的游戏 第一季',
'Breaking Bad S01',
'Game of Thrones Season 1',
'绝命毒师 第1季',
'权力的游戏 S1',
'权力的游戏',
'绝命毒师 第二部',
'Stranger Things S03',
];
console.log('Season Parser Test Results:');
testCases.forEach((title) => {
const result = parseSeasonFromTitle(title);
console.log(`Input: "${title}"`);
console.log(` Clean Title: "${result.cleanTitle}"`);
console.log(` Season: ${result.seasonNumber}`);
console.log('');
});
}