Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,23 @@ docs/.vitepress/dist
# AI
.trae/*
.cursor/*
.claude/*
.claude/*
*.tsbuildinfo

# Build-time auto-generated files
auto-imports.d.ts
components.d.ts
dev-app-update.yml
electron-builder.yml

# IDE - Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/code-snippets

# Local env files
.env.local
.env.*.local
2 changes: 2 additions & 0 deletions src/core/player/LyricManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ class LyricManager {
}

/**

* 检测本地歌词覆盖
* @param id 歌曲 ID
* @returns 歌词数据和元数据
Expand Down Expand Up @@ -749,6 +750,7 @@ class LyricManager {
// 应用括号替换
lyricData = applyBracketReplacement(lyricData);
lyricData = applyProfanityUncensor(lyricData, settingStore.uncensorMaskedProfanity);
// 拒绝胎教 Mode: 汉语拼音音译已在 cleanTTMLTranslations 中基于 xml:lang 声明精确剔除
// 规范化时间
this.normalizeLyricLines(lyricData.yrcData);
this.normalizeLyricLines(lyricData.lrcData);
Expand Down
25 changes: 18 additions & 7 deletions src/utils/lyric/parseTTML.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,19 +197,30 @@ export const extractTtmlBgWithOwner = (ttml: string): TtmlBgExtractResult[] => {
*/
// TODO: 当支持 i18n 之后,需要对其中的部分函数进行修改,使其优选逻辑能够根据用户界面语言变化
export const cleanTTMLTranslations = (ttmlContent: string): string => {
// 无条件剔除繁体替换翻译段 <translation type="replacement" xml:lang="zh-Hant*">
ttmlContent = ttmlContent.replace(
/<translation(?=[^>]*type="replacement")(?=[^>]*xml:lang="zh-Hant[^"]*")[^>]*>[\s\S]*?<\/translation>/g,
"",
);
// 无条件剔除音译类型且语言声明为汉语拼音的 span (拒绝胎教 Mode)
ttmlContent = ttmlContent.replace(
/<span(?=[^>]*ttm:role="x-roman")(?=[^>]*xml:lang="zh-Hans-Latn")[^>]*>[\s\S]*?<\/span>/g,
"",
);
Comment on lines +200 to +209
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

虽然使用正则表达式处理 XML 字符串在特定场景下性能较好,但这种方法通常比较脆弱,并且难以维护。当 TTML 结构稍微复杂或属性顺序、格式有变动时,正则表达式可能会失效或产生意想不到的结果。

为了提高代码的健壮性和可读性,建议使用标准的 DOM 解析器(如 DOMParser)来处理 TTML 内容。通过操作 DOM 树来移除或修改节点,可以更安全、更清晰地实现过滤逻辑,避免潜在的解析错误。

例如,可以这样重构:

const parser = new DOMParser();
const xmlDoc = parser.parseFromString(ttmlContent, "application/xml");

// 无条件剔除繁体替换翻译段
const hantTranslations = xmlDoc.querySelectorAll('translation[type=\"replacement\"][xml\\:lang^=\"zh-Hant\"]');
hantTranslations.forEach(node => node.remove());

// 无条件剔除汉语拼音音译
const pinyinSpans = xmlDoc.querySelectorAll('span[ttm\\:role=\"x-roman\"][xml\\:lang=\"zh-Hans-Latn\"]');
pinyinSpans.forEach(node => node.remove());

// ... 其他逻辑也可以在 DOM 上操作 ...

const serializer = new XMLSerializer();
ttmlContent = serializer.serializeToString(xmlDoc);

考虑到函数内其他部分也使用了正则表达式,这可能需要对整个函数进行重构。如果这是一个有意的性能优化,可以添加注释说明。否则,为了长期的可维护性,迁移到 DOM 解析器是更优的选择。


/**
* 统计 TTML 中的语言
* @param ttml_text TTML 文本
* @returns 语言列表
*/
const langCounter = (ttml_text: string) => {
// 使用正则匹配所有 xml:lang="xx-XX" 格式的字符串
const langRegex = /(?<=<(span|translation)[^<>]+)xml:lang="([^"]+)"/g;
// 仅提取 translation 的语言,不对 span (原词) 进行匹配,防止被意外清洗
const langRegex = /<translation[^>]+xml:lang="([^"]+)"/g;
const matches = ttml_text.matchAll(langRegex);
// 提取匹配结果并去重
const langSet = new Set<string>();
for (const match of matches) {
if (match[2]) langSet.add(match[2]);
if (match[1]) langSet.add(match[1]);
}
return Array.from(langSet);
};
Expand Down Expand Up @@ -256,13 +267,13 @@ export const cleanTTMLTranslations = (ttmlContent: string): string => {
if (major_lang === null) return ttml_text;
/**
* 替换逻辑回调函数
* @param match 完整匹配到的标签字符串 (例如 <code><span ...>...<\/span></code>)
* @param lang 正则中第一个捕获组匹配到的语言代码 (例如 "ja-JP")
* @param match 完整匹配到的标签字符串
* @param lang 语言代码
*/
const replacer = (match: string, lang: string) => (lang === major_lang ? match : "");
const translationRegex = /<translation[^>]+xml:lang="([^"]+)"[^>]*>[\s\S]*?<\/translation>/g;
const spanRegex = /<span[^>]+xml:lang="([^" ]+)"[^>]*>[\s\S]*?<\/span>/g;
return ttml_text.replace(translationRegex, replacer).replace(spanRegex, replacer);
// 不清理 span,保留原词和音译内容
return ttml_text.replace(translationRegex, replacer);
};
// 统计语言
const context_lang = langCounter(ttmlContent);
Expand Down