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