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