Skip to content

Commit 132ffb2

Browse files
committed
🐞 fix: 修复 QM 逐字音译
1 parent 3eda65d commit 132ffb2

1 file changed

Lines changed: 84 additions & 58 deletions

File tree

src/core/player/LyricManager.ts

Lines changed: 84 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class LyricManager {
9696
if (lyricsData.length && otherLyrics.length) {
9797
lyricsData.forEach((v: LyricLine) => {
9898
otherLyrics.forEach((x: LyricLine) => {
99-
if (v.startTime === x.startTime || Math.abs(v.startTime - x.startTime) < 600) {
99+
if (v.startTime === x.startTime || Math.abs(v.startTime - x.startTime) < 300) {
100100
v[key] = x.words.map((word) => word.word).join("");
101101
}
102102
});
@@ -244,67 +244,100 @@ class LyricManager {
244244
* 解析 QQ 音乐 QRC 格式歌词
245245
* @param qrcContent QRC 原始内容
246246
* @param trans 翻译歌词
247-
* @param roma 罗马音歌词
247+
* @param roma 罗马音歌词(QRC 格式)
248248
* @returns LyricLine 数组
249249
*/
250250
private parseQRCLyric(qrcContent: string, trans?: string, roma?: string): LyricLine[] {
251-
const lines: LyricLine[] = [];
252-
253-
// 从 XML 中提取歌词内容
254-
const contentMatch = /<Lyric_1[^>]*LyricContent="([^"]*)"[^>]*\/>/.exec(qrcContent);
255-
const content = contentMatch ? contentMatch[1] : qrcContent;
256-
257251
// 行匹配: [开始时间,持续时间]内容
258252
const linePattern = /^\[(\d+),(\d+)\](.*)$/;
259253
// 逐字匹配: 文字(开始时间,持续时间)
260254
const wordPattern = /([^(]*)\((\d+),(\d+)\)/g;
255+
/**
256+
* 解析 QRC 内容为行数据
257+
*/
258+
const parseQRCContent = (
259+
rawContent: string,
260+
): Array<{
261+
startTime: number;
262+
endTime: number;
263+
words: Array<{ word: string; startTime: number; endTime: number }>;
264+
}> => {
265+
// 从 XML 中提取歌词内容
266+
const contentMatch = /<Lyric_1[^>]*LyricContent="([^"]*)"[^>]*\/>/.exec(rawContent);
267+
const content = contentMatch ? contentMatch[1] : rawContent;
268+
269+
const result: Array<{
270+
startTime: number;
271+
endTime: number;
272+
words: Array<{ word: string; startTime: number; endTime: number }>;
273+
}> = [];
274+
275+
for (const rawLine of content.split("\n")) {
276+
const line = rawLine.trim();
277+
if (!line) continue;
278+
279+
// 跳过元数据标签 [ti:xxx] [ar:xxx] 等
280+
if (/^\[[a-z]+:/i.test(line)) continue;
281+
282+
const lineMatch = linePattern.exec(line);
283+
if (!lineMatch) continue;
284+
285+
const lineStart = parseInt(lineMatch[1], 10);
286+
const lineDuration = parseInt(lineMatch[2], 10);
287+
const lineContent = lineMatch[3];
288+
289+
// 解析逐字
290+
const words: Array<{ word: string; startTime: number; endTime: number }> = [];
291+
let wordMatch: RegExpExecArray | null;
292+
const wordRegex = new RegExp(wordPattern.source, "g");
293+
294+
while ((wordMatch = wordRegex.exec(lineContent)) !== null) {
295+
const wordText = wordMatch[1];
296+
const wordStart = parseInt(wordMatch[2], 10);
297+
const wordDuration = parseInt(wordMatch[3], 10);
298+
299+
if (wordText) {
300+
words.push({
301+
word: wordText,
302+
startTime: wordStart,
303+
endTime: wordStart + wordDuration,
304+
});
305+
}
306+
}
261307

262-
for (const rawLine of content.split("\n")) {
263-
const line = rawLine.trim();
264-
if (!line) continue;
265-
266-
// 跳过元数据标签 [ti:xxx] [ar:xxx] 等
267-
if (/^\[[a-z]+:/i.test(line)) continue;
268-
269-
const lineMatch = linePattern.exec(line);
270-
if (!lineMatch) continue;
271-
272-
const lineStart = parseInt(lineMatch[1], 10);
273-
const lineDuration = parseInt(lineMatch[2], 10);
274-
const lineContent = lineMatch[3];
275-
276-
// 解析逐字
277-
const words: Array<{ word: string; startTime: number; endTime: number }> = [];
278-
let wordMatch: RegExpExecArray | null;
279-
const wordRegex = new RegExp(wordPattern.source, "g");
280-
281-
while ((wordMatch = wordRegex.exec(lineContent)) !== null) {
282-
const wordText = wordMatch[1];
283-
const wordStart = parseInt(wordMatch[2], 10);
284-
const wordDuration = parseInt(wordMatch[3], 10);
285-
286-
if (wordText) {
287-
words.push({
288-
word: wordText,
289-
startTime: wordStart,
290-
endTime: wordStart + wordDuration,
308+
if (words.length > 0) {
309+
result.push({
310+
startTime: lineStart,
311+
endTime: lineStart + lineDuration,
312+
words,
291313
});
292314
}
293315
}
294-
295-
if (words.length > 0) {
296-
lines.push({
297-
words: words.map((w) => ({ ...w, romanWord: "" })),
298-
startTime: lineStart,
299-
endTime: lineStart + lineDuration,
300-
translatedLyric: "",
301-
romanLyric: "",
302-
isBG: false,
303-
isDuet: false,
304-
});
305-
}
306-
}
307-
316+
return result;
317+
};
318+
// 解析主歌词
319+
const qrcLines = parseQRCContent(qrcContent);
320+
// 解析罗马音(如果有)
321+
const romaLines = roma ? parseQRCContent(roma) : [];
322+
// 构建 LyricLine 数组,同时填充 romanWord
323+
const lines: LyricLine[] = qrcLines.map((qrcLine, lineIndex) => {
324+
// 找到对应的罗马音行
325+
const romaLine = romaLines[lineIndex];
326+
// 按索引填充 romanWord
327+
const words = qrcLine.words.map((w, wordIndex) => ({
328+
...w,
329+
romanWord: romaLine?.words[wordIndex]?.word || "",
330+
}));
331+
return {
332+
words,
333+
startTime: qrcLine.startTime,
334+
endTime: qrcLine.endTime,
335+
translatedLyric: "",
336+
romanLyric: romaLine?.words.map((w) => w.word).join("") || "",
337+
isBG: false,
338+
isDuet: false,
339+
};
340+
});
308341
// 处理翻译
309342
let result = lines;
310343
if (trans) {
@@ -313,13 +346,6 @@ class LyricManager {
313346
result = this.alignLyrics(result, transLines, "translatedLyric");
314347
}
315348
}
316-
// 处理罗马音
317-
if (roma) {
318-
const romaLines = parseLrc(roma);
319-
if (romaLines?.length) {
320-
result = this.alignLyrics(result, romaLines, "romanLyric");
321-
}
322-
}
323349
return result;
324350
}
325351

0 commit comments

Comments
 (0)