|
|
@@ -0,0 +1,141 @@
|
|
|
+import { MUSIC_CARD_EMPTY_ARTIST, MUSIC_CARD_EMPTY_TITLE } from './MusicCardConstants'
|
|
|
+
|
|
|
+export interface MusicCardSnapshot {
|
|
|
+ title: string
|
|
|
+ artist: string
|
|
|
+ lyricText: string
|
|
|
+ positionMs: number
|
|
|
+ hasSong: boolean
|
|
|
+}
|
|
|
+
|
|
|
+export interface MusicCardLyricLines {
|
|
|
+ line1: string
|
|
|
+ line2: string
|
|
|
+ hasLyric: boolean
|
|
|
+}
|
|
|
+
|
|
|
+export class MusicCardBindingData {
|
|
|
+ title: string
|
|
|
+ artist: string
|
|
|
+ hasSong: boolean
|
|
|
+ lyricLine1: string
|
|
|
+ lyricLine2: string
|
|
|
+
|
|
|
+ constructor(
|
|
|
+ title: string,
|
|
|
+ artist: string,
|
|
|
+ hasSong: boolean,
|
|
|
+ lyricLine1: string,
|
|
|
+ lyricLine2: string
|
|
|
+ ) {
|
|
|
+ this.title = title
|
|
|
+ this.artist = artist
|
|
|
+ this.hasSong = hasSong
|
|
|
+ this.lyricLine1 = lyricLine1
|
|
|
+ this.lyricLine2 = lyricLine2
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const LYRIC_LINE_PATTERN = /^\[(\d{2}):(\d{2})\.(\d{2})\](.*)$/
|
|
|
+
|
|
|
+export function createEmptyMusicCardSnapshot(): MusicCardSnapshot {
|
|
|
+ return {
|
|
|
+ title: '',
|
|
|
+ artist: '',
|
|
|
+ lyricText: '',
|
|
|
+ positionMs: 0,
|
|
|
+ hasSong: false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export function resolveMusicCardLyricLines(
|
|
|
+ lyricText: string,
|
|
|
+ positionMs: number
|
|
|
+): MusicCardLyricLines {
|
|
|
+ const trimmedText = lyricText ? lyricText.trim() : ''
|
|
|
+ if (trimmedText === '') {
|
|
|
+ return {
|
|
|
+ line1: MUSIC_CARD_EMPTY_TITLE,
|
|
|
+ line2: MUSIC_CARD_EMPTY_ARTIST,
|
|
|
+ hasLyric: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const entries: Array<{ timeMs: number; text: string }> = []
|
|
|
+ const rawLines = trimmedText.split(/\r?\n/)
|
|
|
+ for (let i = 0; i < rawLines.length; i++) {
|
|
|
+ const rawLine = rawLines[i].trim()
|
|
|
+ if (rawLine === '') {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ const match = rawLine.match(LYRIC_LINE_PATTERN)
|
|
|
+ if (!match) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ const minutes = parseInt(match[1])
|
|
|
+ const seconds = parseInt(match[2])
|
|
|
+ const centiseconds = parseInt(match[3])
|
|
|
+ const text = match[4].trim()
|
|
|
+ const timeMs = minutes * 60 * 1000 + seconds * 1000 + centiseconds * 10
|
|
|
+
|
|
|
+ entries.push({
|
|
|
+ timeMs,
|
|
|
+ text
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ if (entries.length === 0) {
|
|
|
+ return {
|
|
|
+ line1: MUSIC_CARD_EMPTY_TITLE,
|
|
|
+ line2: MUSIC_CARD_EMPTY_ARTIST,
|
|
|
+ hasLyric: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ entries.sort((lhs, rhs) => lhs.timeMs - rhs.timeMs)
|
|
|
+
|
|
|
+ let currentIndex = -1
|
|
|
+ for (let i = 0; i < entries.length; i++) {
|
|
|
+ if (entries[i].timeMs <= positionMs) {
|
|
|
+ currentIndex = i
|
|
|
+ } else {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (currentIndex === -1) {
|
|
|
+ currentIndex = 0
|
|
|
+ }
|
|
|
+
|
|
|
+ const currentLine = entries[currentIndex]
|
|
|
+ const nextLine = entries[currentIndex + 1]
|
|
|
+
|
|
|
+ return {
|
|
|
+ line1: currentLine.text,
|
|
|
+ line2: nextLine ? nextLine.text : '',
|
|
|
+ hasLyric: true
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export function buildMusicCardBindingData(snapshot: MusicCardSnapshot): MusicCardBindingData {
|
|
|
+ const source = snapshot || createEmptyMusicCardSnapshot()
|
|
|
+ const hasSong = Boolean(source.hasSong)
|
|
|
+ const lyricLines = resolveMusicCardLyricLines(source.lyricText, source.positionMs)
|
|
|
+
|
|
|
+ const title =
|
|
|
+ hasSong && source.title && source.title.trim() !== ''
|
|
|
+ ? source.title
|
|
|
+ : MUSIC_CARD_EMPTY_TITLE
|
|
|
+ const artist =
|
|
|
+ hasSong && source.artist && source.artist.trim() !== ''
|
|
|
+ ? source.artist
|
|
|
+ : MUSIC_CARD_EMPTY_ARTIST
|
|
|
+ const lyricLine1 =
|
|
|
+ lyricLines.hasLyric && lyricLines.line1 ? lyricLines.line1 : MUSIC_CARD_EMPTY_TITLE
|
|
|
+ const lyricLine2 =
|
|
|
+ lyricLines.hasLyric && lyricLines.line2 ? lyricLines.line2 : MUSIC_CARD_EMPTY_ARTIST
|
|
|
+
|
|
|
+ return new MusicCardBindingData(title, artist, hasSong, lyricLine1, lyricLine2)
|
|
|
+}
|