forked from miao-moe/QZMusic_PC
fork(fix): Clone AMLL 并修复 BUG
- 将AMLL Clone到本以地进行修复和优化(emm虽然这很不优雅但是暂时无时间做子模块和Fork) - 修复在当前播放歌词行不可见的视口Seek会出现滚动偏移的问题
This commit is contained in:
109
amll-local/packages/lyric/src/formats/eslrc.ts
Normal file
109
amll-local/packages/lyric/src/formats/eslrc.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* @fileoverview ESLyric 逐词歌词格式解析与生成。
|
||||
* 每行以行首时间戳开头,后续每个单词后都跟一个结束时间戳。
|
||||
*
|
||||
* 格式示例:
|
||||
* [00:10.82]Test[00:10.97] Word[00:12.62]
|
||||
* [00:12.62]Next[00:13.20] line[00:14.10]
|
||||
*/
|
||||
import type { LyricLine, LyricWord } from "../types";
|
||||
import {
|
||||
clampTimestamp,
|
||||
createLine,
|
||||
createWord,
|
||||
formatTime,
|
||||
parseTime,
|
||||
} from "../utils";
|
||||
|
||||
const TIME_REGEX = /^\[((?:\d+:)*\d+(?:\.\d+)?)\]/;
|
||||
|
||||
function parseTimestampPrefix(
|
||||
src: string,
|
||||
): { time: number; length: number } | null {
|
||||
const match = src.match(TIME_REGEX);
|
||||
if (!match) return null;
|
||||
const [raw, timeStr] = match;
|
||||
return { time: parseTime(timeStr), length: raw.length };
|
||||
}
|
||||
|
||||
function parseEslrcLine(rawLine: string): LyricLine | null {
|
||||
let src = rawLine.trim();
|
||||
const first = parseTimestampPrefix(src);
|
||||
if (!first) return null;
|
||||
src = src.slice(first.length);
|
||||
let startTime = first.time;
|
||||
if (!src.trim()) return null;
|
||||
|
||||
const words: LyricWord[] = [];
|
||||
while (src.trim().length > 0) {
|
||||
const nextTimePos = src.indexOf("[");
|
||||
if (nextTimePos <= 0) return null;
|
||||
|
||||
const word = src.slice(0, nextTimePos);
|
||||
const nextTime = parseTimestampPrefix(src.slice(nextTimePos));
|
||||
if (!nextTime) return null;
|
||||
|
||||
words.push(
|
||||
createWord({
|
||||
word,
|
||||
startTime,
|
||||
endTime: nextTime.time,
|
||||
}),
|
||||
);
|
||||
src = src.slice(nextTimePos + nextTime.length);
|
||||
startTime = nextTime.time;
|
||||
}
|
||||
|
||||
return createLine({ words });
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 ESLyric 逐词歌词格式字符串
|
||||
* @param eslrc 歌词字符串
|
||||
* @returns 成功解析出来的歌词
|
||||
*/
|
||||
export function parseEslrc(eslrc: string): LyricLine[] {
|
||||
const result: LyricLine[] = [];
|
||||
for (const rawLine of eslrc.split(/\r?\n/)) {
|
||||
const line = parseEslrcLine(rawLine);
|
||||
if (line) result.push(line);
|
||||
}
|
||||
|
||||
result.sort(
|
||||
(a, b) =>
|
||||
(a.words[0]?.startTime ?? Number.MAX_SAFE_INTEGER) -
|
||||
(b.words[0]?.startTime ?? Number.MAX_SAFE_INTEGER),
|
||||
);
|
||||
|
||||
for (const line of result) {
|
||||
for (const word of line.words) {
|
||||
word.startTime = clampTimestamp(word.startTime);
|
||||
word.endTime = clampTimestamp(word.endTime);
|
||||
}
|
||||
line.startTime = clampTimestamp(line.words[0]?.startTime ?? 0);
|
||||
line.endTime = clampTimestamp(
|
||||
line.words[line.words.length - 1]?.endTime ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将歌词数组转换为 ESLyric 逐词歌词格式字符串
|
||||
* @param lines 歌词数组
|
||||
* @returns ESLyric 逐词歌词格式字符串
|
||||
*/
|
||||
export function stringifyEslrc(lines: LyricLine[]): string {
|
||||
return lines
|
||||
.map((line) => {
|
||||
if (!line.words.length) return "";
|
||||
return `[${formatTime(clampTimestamp(line.words[0].startTime))}]${line.words
|
||||
.map(
|
||||
(word) => `${word.word}[${formatTime(clampTimestamp(word.endTime))}]`,
|
||||
)
|
||||
.join("")}`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
Reference in New Issue
Block a user