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