native-lyrics-tools
Version:
A JavaScript library for parsing and generating various lyric formats.
115 lines (105 loc) • 3.14 kB
JavaScript
// LyricWord and LyricLine should be compatible with your LRC and QRC modules
class LyricWord {
constructor(word, start_time, end_time) {
this.word = word;
this.start_time = start_time;
this.end_time = end_time;
}
}
class LyricLine {
constructor(words = []) {
this.words = words;
}
}
// Parses a [mm:ss.xx] timestamp (e.g. [00:10.97]) to milliseconds
function parseTimestamp(str) {
const match = str.match(/^\[(\d+):(\d+)[.:](\d{1,3})]/);
if (!match) throw new Error(`Invalid timestamp: ${str}`);
const min = parseInt(match[1], 10);
const sec = parseInt(match[2], 10);
let ms = match[3].padEnd(3, '0');
ms = parseInt(ms, 10);
return {
time: min * 60000 + sec * 1000 + ms,
consumed: match[0].length
};
}
// Parse a single ESLyric line (e.g. [00:10.82]Test[00:10.97] Word[00:12.62])
function parseEslrcLine(line) {
let src = line.trim();
// Initial timestamp
const first = parseTimestamp(src);
let pos = first.consumed;
let start_time = first.time;
const words = [];
// parse word[timestamp] pairs
while (pos < src.length) {
// Get text up to next [
const nextBracket = src.indexOf('[', pos);
if (nextBracket === -1) break;
const word = src.slice(pos, nextBracket).trim();
const t = parseTimestamp(src.slice(nextBracket));
const end_time = t.time;
if (word) {
words.push(new LyricWord(word, start_time, end_time));
}
start_time = end_time;
pos = nextBracket + t.consumed;
}
return new LyricLine(words);
}
// Parse full ESLRC string
function parseESLRC(src) {
const lines = src.split(/\r?\n/);
const result = [];
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) continue;
try {
const lyricLine = parseEslrcLine(trimmed);
if (lyricLine.words.length > 0) {
result.push(lyricLine);
}
} catch (e) {
// ignore malformed lines
}
}
processLyrics(result);
return result;
}
// In-place adjust end_time for lines/words if needed (for compatibility)
function processLyrics(lines) {
// For ESLRC, word end_times are explicit; you may want to set line end_times as the last word end_time
for (const line of lines) {
if (line.words.length) {
line.end_time = line.words[line.words.length - 1].end_time;
}
}
}
// Write LRC-style timestamp
function writeTimestamp(ms) {
const min = Math.floor(ms / 60000);
const sec = Math.floor(ms / 1000) % 60;
const ms3 = String(ms % 1000).padStart(3, '0');
return `[${String(min).padStart(2, '0')}:${String(sec).padStart(2, '0')}.${ms3}]`;
}
// Stringify ESLRC
function stringifyESLRC(lines) {
let result = '';
for (const line of lines) {
if (!line.words.length) continue;
result += writeTimestamp(line.words[0].start_time);
for (const word of line.words) {
result += word.word + writeTimestamp(word.end_time);
}
result += '\n';
}
return result;
}
// ES module exports
export {
LyricWord,
LyricLine,
parseESLRC,
stringifyESLRC
};