xref: /MusicFree/src/utils/lrcParser.ts (revision 437330137ec34d17508be0462e27fb68a4cf66d5)
157277364S猫头猫const timeReg = /\[[\d:.]+\]/g;
2*43733013S猫头猫const metaReg = /\[(.+):(.+)\]/g;
357277364S猫头猫
4*43733013S猫头猫type LyricMeta = Record<string, any>;
5*43733013S猫头猫
6*43733013S猫头猫interface IOptions {
7*43733013S猫头猫    musicItem?: IMusic.IMusicItem;
8*43733013S猫头猫    lyricSource?: ILyric.ILyricSource;
9*43733013S猫头猫    translation?: string;
10*43733013S猫头猫    extra?: Record<string, any>;
11*43733013S猫头猫}
12*43733013S猫头猫
13*43733013S猫头猫export interface IParsedLrcItem {
14*43733013S猫头猫    /** 时间 s */
15*43733013S猫头猫    time: number;
16*43733013S猫头猫    /** 歌词 */
17*43733013S猫头猫    lrc: string;
18*43733013S猫头猫    /** 翻译 */
19*43733013S猫头猫    translation?: string;
20*43733013S猫头猫    /** 位置 */
21*43733013S猫头猫    index: number;
2272dabe5bS猫头猫}
2372dabe5bS猫头猫
2457277364S猫头猫export default class LyricParser {
25*43733013S猫头猫    private _musicItem?: IMusic.IMusicItem;
2657277364S猫头猫
27*43733013S猫头猫    private meta: LyricMeta;
28*43733013S猫头猫    private lrcItems: Array<IParsedLrcItem>;
29*43733013S猫头猫
30*43733013S猫头猫    private extra: Record<string, any>;
31*43733013S猫头猫
32*43733013S猫头猫    private lastSearchIndex = 0;
33*43733013S猫头猫
34*43733013S猫头猫    public hasTranslation = false;
35*43733013S猫头猫    public lyricSource?: ILyric.ILyricSource;
36*43733013S猫头猫
37*43733013S猫头猫    get musicItem() {
38*43733013S猫头猫        return this._musicItem;
39*43733013S猫头猫    }
40*43733013S猫头猫
41*43733013S猫头猫    constructor(raw: string, options?: IOptions) {
42*43733013S猫头猫        // init
43*43733013S猫头猫        this._musicItem = options?.musicItem;
44*43733013S猫头猫        this.extra = options?.extra || {};
45*43733013S猫头猫        this.lyricSource = options?.lyricSource;
46*43733013S猫头猫
47*43733013S猫头猫        let translation = options?.translation;
48*43733013S猫头猫        if (!raw && translation) {
49*43733013S猫头猫            raw = translation;
50*43733013S猫头猫            translation = undefined;
51*43733013S猫头猫        }
52*43733013S猫头猫
53*43733013S猫头猫        const {lrcItems, meta} = this.parseLyricImpl(raw);
54*43733013S猫头猫        if (this.extra.offset) {
55*43733013S猫头猫            meta.offset = (meta.offset ?? 0) + this.extra.offset;
56*43733013S猫头猫        }
57*43733013S猫头猫        this.meta = meta;
58*43733013S猫头猫        this.lrcItems = lrcItems;
59*43733013S猫头猫
60*43733013S猫头猫        if (translation) {
61*43733013S猫头猫            this.hasTranslation = true;
62*43733013S猫头猫            const transLrcItems = this.parseLyricImpl(translation).lrcItems;
63*43733013S猫头猫
64*43733013S猫头猫            // 2 pointer
65*43733013S猫头猫            let p1 = 0;
66*43733013S猫头猫            let p2 = 0;
67*43733013S猫头猫
68*43733013S猫头猫            while (p1 < this.lrcItems.length) {
69*43733013S猫头猫                const lrcItem = this.lrcItems[p1];
70*43733013S猫头猫                while (
71*43733013S猫头猫                    transLrcItems[p2].time < lrcItem.time &&
72*43733013S猫头猫                    p2 < transLrcItems.length - 1
7372dabe5bS猫头猫                ) {
74*43733013S猫头猫                    ++p2;
75*43733013S猫头猫                }
76*43733013S猫头猫                if (transLrcItems[p2].time === lrcItem.time) {
77*43733013S猫头猫                    lrcItem.translation = transLrcItems[p2].lrc;
78*43733013S猫头猫                } else {
79*43733013S猫头猫                    lrcItem.translation = '';
807e883dbbS猫头猫                }
817e883dbbS猫头猫
82*43733013S猫头猫                ++p1;
837e883dbbS猫头猫            }
84*43733013S猫头猫        }
85*43733013S猫头猫    }
86*43733013S猫头猫
87*43733013S猫头猫    getPosition(position: number): IParsedLrcItem | null {
88*43733013S猫头猫        position = position - (this.meta?.offset ?? 0);
89*43733013S猫头猫        let index;
90*43733013S猫头猫        /** 最前面 */
91*43733013S猫头猫        if (!this.lrcItems[0] || position < this.lrcItems[0].time) {
92*43733013S猫头猫            this.lastSearchIndex = 0;
93*43733013S猫头猫            return null;
94*43733013S猫头猫        }
95*43733013S猫头猫        for (
96*43733013S猫头猫            index = this.lastSearchIndex;
97*43733013S猫头猫            index < this.lrcItems.length - 1;
98*43733013S猫头猫            ++index
99*43733013S猫头猫        ) {
100*43733013S猫头猫            if (
101*43733013S猫头猫                position >= this.lrcItems[index].time &&
102*43733013S猫头猫                position < this.lrcItems[index + 1].time
103*43733013S猫头猫            ) {
104*43733013S猫头猫                this.lastSearchIndex = index;
105*43733013S猫头猫                return this.lrcItems[index];
106*43733013S猫头猫            }
107*43733013S猫头猫        }
108*43733013S猫头猫
109*43733013S猫头猫        for (index = 0; index < this.lastSearchIndex; ++index) {
110*43733013S猫头猫            if (
111*43733013S猫头猫                position >= this.lrcItems[index].time &&
112*43733013S猫头猫                position < this.lrcItems[index + 1].time
113*43733013S猫头猫            ) {
114*43733013S猫头猫                this.lastSearchIndex = index;
115*43733013S猫头猫                return this.lrcItems[index];
116*43733013S猫头猫            }
117*43733013S猫头猫        }
118*43733013S猫头猫
119*43733013S猫头猫        index = this.lrcItems.length - 1;
120*43733013S猫头猫        this.lastSearchIndex = index;
121*43733013S猫头猫        return this.lrcItems[index];
122*43733013S猫头猫    }
123*43733013S猫头猫
124*43733013S猫头猫    getLyricItems() {
125*43733013S猫头猫        return this.lrcItems;
126*43733013S猫头猫    }
127*43733013S猫头猫
128*43733013S猫头猫    getMeta() {
129*43733013S猫头猫        return this.meta;
130*43733013S猫头猫    }
131*43733013S猫头猫
132*43733013S猫头猫    toString(options?: {
133*43733013S猫头猫        withTimestamp?: boolean;
134*43733013S猫头猫        type?: 'raw' | 'translation';
135*43733013S猫头猫    }) {
136*43733013S猫头猫        const {type = 'raw', withTimestamp = true} = options || {};
137*43733013S猫头猫
138*43733013S猫头猫        if (withTimestamp) {
139*43733013S猫头猫            return this.lrcItems
140*43733013S猫头猫                .map(
141*43733013S猫头猫                    item =>
142*43733013S猫头猫                        `${this.timeToLrctime(item.time)} ${
143*43733013S猫头猫                            type === 'raw' ? item.lrc : item.translation
144*43733013S猫头猫                        }`,
145*43733013S猫头猫                )
146*43733013S猫头猫                .join('\r\n');
147*43733013S猫头猫        } else {
148*43733013S猫头猫            return this.lrcItems
149*43733013S猫头猫                .map(item => (type === 'raw' ? item.lrc : item.translation))
150*43733013S猫头猫                .join('\r\n');
151*43733013S猫头猫        }
152*43733013S猫头猫    }
153*43733013S猫头猫
154*43733013S猫头猫    /** [xx:xx.xx] => x s */
155*43733013S猫头猫    private parseTime(timeStr: string): number {
156*43733013S猫头猫        let result = 0;
157*43733013S猫头猫        const nums = timeStr.slice(1, timeStr.length - 1).split(':');
158*43733013S猫头猫        for (let i = 0; i < nums.length; ++i) {
159*43733013S猫头猫            result = result * 60 + +nums[i];
160*43733013S猫头猫        }
161*43733013S猫头猫        return result;
162*43733013S猫头猫    }
163*43733013S猫头猫    /** x s => [xx:xx.xx] */
164*43733013S猫头猫    private timeToLrctime(sec: number) {
165*43733013S猫头猫        const min = Math.floor(sec / 60);
166*43733013S猫头猫        sec = sec - min * 60;
167*43733013S猫头猫        const secInt = Math.floor(sec);
168*43733013S猫头猫        const secFloat = sec - secInt;
169*43733013S猫头猫        return `[${min.toFixed(0).padStart(2, '0')}:${secInt
170*43733013S猫头猫            .toString()
171*43733013S猫头猫            .padStart(2, '0')}.${secFloat.toFixed(2).slice(2)}]`;
172*43733013S猫头猫    }
173*43733013S猫头猫
174*43733013S猫头猫    private parseMetaImpl(metaStr: string) {
175*43733013S猫头猫        if (metaStr === '') {
176*43733013S猫头猫            return {};
177*43733013S猫头猫        }
178*43733013S猫头猫        const metaArr = metaStr.match(metaReg) ?? [];
179*43733013S猫头猫        const meta: any = {};
180*43733013S猫头猫        let k, v;
181*43733013S猫头猫        for (const m of metaArr) {
182*43733013S猫头猫            k = m.substring(1, m.indexOf(':'));
183*43733013S猫头猫            v = m.substring(k.length + 2, m.length - 1);
184*43733013S猫头猫            if (k === 'offset') {
185*43733013S猫头猫                meta[k] = +v / 1000;
186*43733013S猫头猫            } else {
187*43733013S猫头猫                meta[k] = v;
188*43733013S猫头猫            }
189*43733013S猫头猫        }
190*43733013S猫头猫        return meta;
191*43733013S猫头猫    }
192*43733013S猫头猫
193*43733013S猫头猫    private parseLyricImpl(raw: string) {
1947e883dbbS猫头猫        raw = raw.trim();
195*43733013S猫头猫        const rawLrcItems: Array<IParsedLrcItem> = [];
19657277364S猫头猫        const rawLrcs = raw.split(timeReg) ?? [];
19757277364S猫头猫        const rawTimes = raw.match(timeReg) ?? [];
19857277364S猫头猫        const len = rawTimes.length;
19957277364S猫头猫
200*43733013S猫头猫        const meta = this.parseMetaImpl(rawLrcs[0].trim());
20157277364S猫头猫        rawLrcs.shift();
20257277364S猫头猫
20357277364S猫头猫        let counter = 0;
20457277364S猫头猫        let j, lrc;
20557277364S猫头猫        for (let i = 0; i < len; ++i) {
20657277364S猫头猫            counter = 0;
20757277364S猫头猫            while (rawLrcs[0] === '') {
20857277364S猫头猫                ++counter;
20957277364S猫头猫                rawLrcs.shift();
21057277364S猫头猫            }
21157277364S猫头猫            lrc = rawLrcs[0]?.trim?.() ?? '';
21257277364S猫头猫            for (j = i; j < i + counter; ++j) {
21357277364S猫头猫                rawLrcItems.push({
21457277364S猫头猫                    time: this.parseTime(rawTimes[j]),
21557277364S猫头猫                    lrc,
216*43733013S猫头猫                    index: j,
21757277364S猫头猫                });
21857277364S猫头猫            }
21957277364S猫头猫            i += counter;
22057277364S猫头猫            if (i < len) {
22157277364S猫头猫                rawLrcItems.push({
22257277364S猫头猫                    time: this.parseTime(rawTimes[i]),
22357277364S猫头猫                    lrc,
224*43733013S猫头猫                    index: j,
22557277364S猫头猫                });
22657277364S猫头猫            }
22757277364S猫头猫            rawLrcs.shift();
22857277364S猫头猫        }
2297e883dbbS猫头猫        let lrcItems = rawLrcItems.sort((a, b) => a.time - b.time);
2307e883dbbS猫头猫        if (lrcItems.length === 0 && raw.length) {
231*43733013S猫头猫            lrcItems = raw.split('\n').map((_, index) => ({
23257277364S猫头猫                time: 0,
23357277364S猫头猫                lrc: _,
234*43733013S猫头猫                index,
23557277364S猫头猫            }));
23657277364S猫头猫        }
23757277364S猫头猫
2387e883dbbS猫头猫        return {
2397e883dbbS猫头猫            lrcItems,
2407e883dbbS猫头猫            meta,
2417e883dbbS猫头猫        };
24257277364S猫头猫    }
24357277364S猫头猫}
244