xref: /MusicFree/src/utils/lrcParser.ts (revision 4d0d956507a5e90230a0a07fc80821ac4f800408)
1const timeReg = /\[[\d:.]+\]/g;
2const metaReg = /\[(.+)\:(.+)\]/g;
3
4export default class LyricParser {
5    private lastIndex: number = 0;
6    private lrcItems: Array<ILyric.IParsedLrcItem>;
7    private meta: Record<string, any>;
8    private currentMusicItem?: IMusic.IMusicItem;
9
10    constructor(raw: string, currentMusicItem?: IMusic.IMusicItem) {
11        raw = raw.trim();
12        this.currentMusicItem = currentMusicItem;
13        const rawLrcItems: Array<ILyric.IParsedLrcItem> = [];
14        const rawLrcs = raw.split(timeReg) ?? [];
15        const rawTimes = raw.match(timeReg) ?? [];
16        const len = rawTimes.length;
17
18        this.meta = this.parseMeta(rawLrcs[0].trim());
19        rawLrcs.shift();
20
21        let counter = 0;
22        let j, lrc;
23        for (let i = 0; i < len; ++i) {
24            counter = 0;
25            while (rawLrcs[0] === '') {
26                ++counter;
27                rawLrcs.shift();
28            }
29            lrc = rawLrcs[0]?.trim?.() ?? '';
30            for (j = i; j < i + counter; ++j) {
31                rawLrcItems.push({
32                    time: this.parseTime(rawTimes[j]),
33                    lrc,
34                });
35            }
36            i += counter;
37            if (i < len) {
38                rawLrcItems.push({
39                    time: this.parseTime(rawTimes[i]),
40                    lrc,
41                });
42            }
43            rawLrcs.shift();
44        }
45        this.lrcItems = rawLrcItems.sort((a, b) => a.time - b.time);
46        if (this.lrcItems.length === 0 && raw.length) {
47            this.lrcItems = raw.split('\n').map(_ => ({
48                time: 0,
49                lrc: _,
50            }));
51        }
52
53        for (let i = 0; i < this.lrcItems.length; ++i) {
54            this.lrcItems[i].index = i;
55        }
56    }
57
58    getPosition(position: number): {
59        lrc?: ILyric.IParsedLrcItem;
60        index: number;
61    } {
62        position = position - (this.meta?.offset ?? 0);
63        let index;
64        /** 最前面 */
65        if (!this.lrcItems[0] || position < this.lrcItems[0].time) {
66            this.lastIndex = 0;
67            return {
68                lrc: undefined,
69                index: -1,
70            };
71        }
72        for (
73            index = this.lastIndex;
74            index < this.lrcItems.length - 1;
75            ++index
76        ) {
77            if (
78                position >= this.lrcItems[index].time &&
79                position < this.lrcItems[index + 1].time
80            ) {
81                this.lastIndex = index;
82                return {
83                    lrc: this.lrcItems[index],
84                    index,
85                };
86            }
87        }
88
89        for (index = 0; index < this.lastIndex; ++index) {
90            if (
91                position >= this.lrcItems[index].time &&
92                position < this.lrcItems[index + 1].time
93            ) {
94                this.lastIndex = index;
95                return {
96                    lrc: this.lrcItems[index],
97                    index,
98                };
99            }
100        }
101
102        index = this.lrcItems.length - 1;
103        this.lastIndex = index;
104        return {
105            lrc: this.lrcItems[index],
106            index,
107        };
108    }
109
110    getCurrentMusicItem() {
111        return this.currentMusicItem;
112    }
113
114    getLyric() {
115        return this.lrcItems;
116    }
117
118    getMeta() {
119        return this.meta;
120    }
121
122    parseMeta(metaStr: string) {
123        if (metaStr === '') {
124            return {};
125        }
126        const metaArr = metaStr.match(metaReg) ?? [];
127        const meta: any = {};
128        let k, v;
129        for (let m of metaArr) {
130            k = m.substring(1, m.indexOf(':'));
131            v = m.substring(k.length + 2, m.length - 1);
132            if (k === 'offset') {
133                meta[k] = +v / 1000;
134            } else {
135                meta[k] = v;
136            }
137        }
138        return meta;
139    }
140
141    /** [xx:xx.xx] => x s */
142    parseTime(timeStr: string): number {
143        let result = 0;
144        const nums = timeStr.slice(1, timeStr.length - 1).split(':');
145        for (let i = 0; i < nums.length; ++i) {
146            result = result * 60 + +nums[i];
147        }
148        return result;
149    }
150}
151