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