Files
UHD_Media_Tool/packages/domain/src/identity/normalize.ts
T
rootandClaude Opus 5.5 f6ec993b5f feat(identity): weighted evidence table, sequel numbers, generic-name guard (ID-2)
Configurable DEFAULT_MATCH_WEIGHTS; Chinese/English names both count and
corroborate; Chapter/Part/第N章 sequel conflicts reject; missing season
penalised; category words and single-word aliases colliding in the pool
cannot confirm alone; alias-only hits need year or second-script support;
same-level ties broken by exact year then matched name scripts.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-29 17:41:22 +08:00

104 lines
2.5 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.
import { CN_NUM } from "../release-parser/cn-number.ts";
import { toSimplified } from "./zh-map.ts";
const SEASON_SUFFIX = [
new RegExp(`\\s*第\\s*${CN_NUM}\\s*[季部]\\s*$`, "u"),
/\s*[::-]?\s*(?:season|series)\s*\d+\s*$/iu,
/\s+S\d{1,2}\s*$/iu,
/\s*[((]\s*(?:19|20)\d{2}\s*[))]\s*$/u,
];
/** 去掉标题末尾的季号/年份后缀("庆余年 第二季"、"Show Season 2"、"Title (2019)") */
export function stripSeasonSuffix(s: string): string {
let out = s.trim();
for (let changed = true; changed; ) {
changed = false;
for (const re of SEASON_SUFFIX) {
const next = out.replace(re, "");
if (next !== out && next.trim().length > 0) {
out = next.trim();
changed = true;
}
}
}
return out;
}
/**
* 标题归一化(用于比较,不用于展示):
* 全角→半角(NFKC)、繁→简、小写、& → and、去季号后缀、去标点与空白。
*/
export function normalizeTitle(s: string): string {
const base = stripSeasonSuffix(toSimplified(s.normalize("NFKC")));
return base
.toLowerCase()
.replace(/&/g, "and")
.replace(/[^\p{L}\p{N}]+/gu, "");
}
/** 分类词(站点分类 / RSS 标题前缀)与常见停用词:不能当片名或别名用于身份比对(ID-2) */
const GENERIC_NAMES = new Set(
[
"电影",
"电视剧",
"剧集",
"连续剧",
"动漫",
"动画",
"番剧",
"国漫",
"日漫",
"综艺",
"纪录片",
"记录片",
"音乐",
"短剧",
"微短剧",
"体育",
"游戏",
"软件",
"学习",
"其他",
"其它",
"未知",
"新",
"合集",
"全集",
"中字",
"国语",
"粤语",
"高清",
"蓝光",
"原盘",
"movie",
"movies",
"tv",
"tvseries",
"series",
"anime",
"animation",
"documentary",
"music",
"variety",
"show",
"the",
"season",
"complete",
"unknown",
"other",
].map((s) => s.toLowerCase()),
);
/**
* 归一化后的名称是否太"通用"不能用于身份比对:分类词、停用词、单个汉字、两个以内的拉丁字符、纯数字。
* 参数是 normalizeTitle 之后的形态。
*/
export function isGenericAliasName(norm: string): boolean {
if (!norm) return true;
if (GENERIC_NAMES.has(norm)) return true;
if (/^\d+$/.test(norm)) return true;
const han = norm.match(/\p{Script=Han}/gu)?.length ?? 0;
if (han > 0) return [...norm].length < 2;
return norm.length <= 2;
}