|
@@ -69,6 +69,7 @@ export class LyricParser implements IParser {
|
|
|
|
|
|
|
|
// 处理双语歌词的特殊情况(英文行+中文行交替)
|
|
// 处理双语歌词的特殊情况(英文行+中文行交替)
|
|
|
if (this.isBilingualLyric(src, i)) {
|
|
if (this.isBilingualLyric(src, i)) {
|
|
|
|
|
+ console.info(`onecold isBilingualLyric 双语歌词处理中`)
|
|
|
const englishLine = src[i];
|
|
const englishLine = src[i];
|
|
|
const chineseLine = src[i+1];
|
|
const chineseLine = src[i+1];
|
|
|
|
|
|
|
@@ -96,10 +97,17 @@ export class LyricParser implements IParser {
|
|
|
|
|
|
|
|
// 新增:逐字歌词[]检测方括号逐字歌词格式 [mm:ss.xxx] 文字
|
|
// 新增:逐字歌词[]检测方括号逐字歌词格式 [mm:ss.xxx] 文字
|
|
|
if (this.isSquareBracketWordByWordLyric(line)) {
|
|
if (this.isSquareBracketWordByWordLyric(line)) {
|
|
|
- const { timeline, words } = this.parseSquareBracketWordLine(line, offset);
|
|
|
|
|
|
|
+ // console.info(`onecold 普通歌词处理中`)
|
|
|
|
|
+ const { timeline, words,translation } = this.parseSquareBracketWordLine(line, offset,src[i+1]);
|
|
|
|
|
+ // if (words.length > 0) {
|
|
|
|
|
+ // lyricLines.push(new LyricLine('', timeline, -1, words))
|
|
|
|
|
+ // }
|
|
|
if (words.length > 0) {
|
|
if (words.length > 0) {
|
|
|
- lyricLines.push(new LyricLine('', timeline, -1, words))
|
|
|
|
|
|
|
+ const lyricLine = new LyricLine('', timeline, -1, words, translation);
|
|
|
|
|
+ lyricLines.push(lyricLine);
|
|
|
|
|
+ if (translation) i++; // 跳过已处理的中文行
|
|
|
}
|
|
}
|
|
|
|
|
+ continue;
|
|
|
} else {
|
|
} else {
|
|
|
// 原逻辑处理,但支持逐字歌词
|
|
// 原逻辑处理,但支持逐字歌词
|
|
|
// [00:00.10]画心 - 张靓颖
|
|
// [00:00.10]画心 - 张靓颖
|
|
@@ -164,33 +172,19 @@ export class LyricParser implements IParser {
|
|
|
return /\[\d{2}:\d{2}\.\d{2,3}\]\S/.test(line);
|
|
return /\[\d{2}:\d{2}\.\d{2,3}\]\S/.test(line);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 解析方括号格式的逐字歌词行
|
|
|
|
|
- private parseSquareBracketWordLine(line: string, offset: number):
|
|
|
|
|
- { timeline: number, words: LyricWord[] } {
|
|
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
|
+ private parseSquareBracketWordLine(
|
|
|
|
|
+ line: string,
|
|
|
|
|
+ offset: number,
|
|
|
|
|
+ nextLine?: string
|
|
|
|
|
+ ): { timeline: number, words: LyricWord[], translation?: string } {
|
|
|
const words: LyricWord[] = [];
|
|
const words: LyricWord[] = [];
|
|
|
let firstTimeline = -1;
|
|
let firstTimeline = -1;
|
|
|
|
|
|
|
|
- // 正则匹配:[00:00.000]中文字
|
|
|
|
|
- // const regex = /\[(\d{2}:\d{2}\.\d{2,3})\]([^\[]*)/g;
|
|
|
|
|
- // let match;
|
|
|
|
|
- //
|
|
|
|
|
- // while ((match = regex.exec(line)) !== null) {
|
|
|
|
|
- // const timeStr = match[1]; // 时间部分 00:00.000
|
|
|
|
|
- // const word = match[2].trim(); // 歌词文本
|
|
|
|
|
- //
|
|
|
|
|
- // if (!word) continue; // 跳过空词
|
|
|
|
|
- //
|
|
|
|
|
- // const timeline = this.parseTimeline2(timeStr) - offset;
|
|
|
|
|
- // if (firstTimeline < 0) firstTimeline = timeline;
|
|
|
|
|
- //
|
|
|
|
|
- // words.push(new LyricWord(word, timeline, 0));
|
|
|
|
|
- // }
|
|
|
|
|
-
|
|
|
|
|
- // 正则增强:支持高精度时间戳(如 [02:34.54001])
|
|
|
|
|
|
|
+ // 解析逐字歌词(原有逻辑)
|
|
|
const regex = /\[(\d{2}:\d{2}[.:]\d{2,5})\]([^\[]*)/g;
|
|
const regex = /\[(\d{2}:\d{2}[.:]\d{2,5})\]([^\[]*)/g;
|
|
|
let match;
|
|
let match;
|
|
|
-
|
|
|
|
|
while ((match = regex.exec(line)) !== null) {
|
|
while ((match = regex.exec(line)) !== null) {
|
|
|
const timeStr = match[1];
|
|
const timeStr = match[1];
|
|
|
const word = match[2].trim();
|
|
const word = match[2].trim();
|
|
@@ -201,12 +195,30 @@ export class LyricParser implements IParser {
|
|
|
words.push(new LyricWord(word, timeline, 0));
|
|
words.push(new LyricWord(word, timeline, 0));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 计算每个词的持续时间
|
|
|
|
|
|
|
+ // 计算词持续时间
|
|
|
for (let i = 0; i < words.length - 1; i++) {
|
|
for (let i = 0; i < words.length - 1; i++) {
|
|
|
words[i].duration = words[i + 1].startTime - words[i].startTime;
|
|
words[i].duration = words[i + 1].startTime - words[i].startTime;
|
|
|
}
|
|
}
|
|
|
if (words.length > 0 && words[words.length - 1].duration === 0) {
|
|
if (words.length > 0 && words[words.length - 1].duration === 0) {
|
|
|
- words[words.length - 1].duration = 200; // 默认200ms
|
|
|
|
|
|
|
+ words[words.length - 1].duration = 200;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 双语支持
|
|
|
|
|
+ if (nextLine && /^\[\d{2}:\d{2}\.\d{2,3}\]/.test(nextLine)) {
|
|
|
|
|
+ // 提取两行时间戳(需完全一致)
|
|
|
|
|
+ const currentLineTime = line.match(/^\[(\d{2}:\d{2}\.\d{2,3})\]/)?.[1];
|
|
|
|
|
+ const nextLineTime = nextLine.match(/^\[(\d{2}:\d{2}\.\d{2,3})\]/)?.[1];
|
|
|
|
|
+
|
|
|
|
|
+ if (currentLineTime && nextLineTime && currentLineTime === nextLineTime) {
|
|
|
|
|
+ const chineseText = nextLine.replace(/^\[\d{2}:\d{2}\.\d{2,3}\]/, '').trim();
|
|
|
|
|
+ if (chineseText) { // 非空文本才作为翻译
|
|
|
|
|
+ return {
|
|
|
|
|
+ timeline: firstTimeline,
|
|
|
|
|
+ words,
|
|
|
|
|
+ translation: chineseText
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return { timeline: firstTimeline, words };
|
|
return { timeline: firstTimeline, words };
|