lrc-kit
Version:
lrc parser, maker, runner
134 lines (116 loc) • 3.33 kB
text/typescript
import { Lrc, type Lyric } from './lrc';
export class Runner {
readonly offset: boolean;
private _lrc: Lrc;
private _currentIndex: number;
private _currentWordIndexes: {
wordIndex: number;
charStartIndex: number;
charEndIndex: number;
} | null;
constructor(lrc: Lrc = new Lrc(), offset: boolean = true) {
this.offset = offset;
this._currentIndex = -1;
this._currentWordIndexes = null;
this._lrc = lrc.clone();
this.lrcUpdate();
}
get lrc(): Lrc {
return this._lrc;
}
set lrc(lrc: Lrc) {
this._lrc = lrc.clone();
this.lrcUpdate();
}
lrcUpdate() {
if (this.offset) {
this._offsetAlign();
}
this._sort();
}
private _offsetAlign() {
if ('offset' in this.lrc.info) {
const offset = parseInt(this.lrc.info.offset) / 1000;
if (!isNaN(offset)) {
this.lrc.offset(offset);
delete this.lrc.info.offset;
}
}
}
private _sort() {
this.lrc.lyrics.sort((a, b) => a.timestamp - b.timestamp);
this.lrc.lyrics.forEach((line) => {
line.wordTimestamps?.sort((a, b) => a.timestamp - b.timestamp);
});
}
private timeUpdateAndGetWordIndexes(index: number, timestamp: number) {
const words = this.lrc.lyrics[index]?.wordTimestamps;
if (!words || words.length === 0) return null;
const wordIndex = this._findWordIndex(timestamp, words);
if (wordIndex === -1) return null;
const aheadWords = words.slice(0, wordIndex + 1);
const currentWord = aheadWords[aheadWords.length - 1];
if (!currentWord) return null;
let charEndIndex = aheadWords.reduce(
(sum, word) => sum + word.content.length,
0,
);
let charStartIndex = charEndIndex - currentWord.content.length;
if (currentWord.content.startsWith(' ')) charStartIndex += 1;
if (currentWord.content.endsWith(' ')) charEndIndex -= 1;
return {
wordIndex,
charStartIndex,
charEndIndex,
};
}
timeUpdate(timestamp: number) {
this._currentIndex = this._findIndex(timestamp);
this._currentWordIndexes = this.timeUpdateAndGetWordIndexes(
this._currentIndex,
timestamp,
);
}
private _findIndex(timestamp: number): number {
const nextIndex = this.lrc.lyrics.findIndex(
(lyric) => lyric.timestamp > timestamp,
);
if (nextIndex === -1) return this.lrc.lyrics.length - 1;
return nextIndex - 1;
}
private _findWordIndex(
timestamp: number,
wordTimestamps: { timestamp: number; content: string }[],
): number {
const nextIndex = wordTimestamps.findIndex(
(word) => word.timestamp > timestamp,
);
if (nextIndex === -1) return wordTimestamps.length - 1;
return nextIndex - 1;
}
getInfo() {
return this.lrc.info;
}
getLyrics() {
return this.lrc.lyrics;
}
getLyric(index: number = this.curIndex()): Lyric | undefined {
if (index < 0) return this.lrc.lyrics[0];
if (index > this.lrc.lyrics.length - 1)
return this.lrc.lyrics[this.lrc.lyrics.length - 1];
return this.lrc.lyrics[index];
}
curIndex() {
return this._currentIndex;
}
curWordIndexes(): {
wordIndex: number;
charStartIndex: number;
charEndIndex: number;
} | null {
return this._currentWordIndexes;
}
curLyric() {
return this.getLyric();
}
}