From 3c1c4e48e22a50a27b0bb3efc1b5a916e9c253a5 Mon Sep 17 00:00:00 2001 From: xufuji456 <839789740@qq.com> Date: Sun, 1 Aug 2021 19:02:29 +0800 Subject: [PATCH] parse lrc format of lyrics --- .../java/com/frank/ffmpeg/model/LrcInfo.kt | 18 +++ .../java/com/frank/ffmpeg/tool/LrcLineTool.kt | 2 +- .../java/com/frank/ffmpeg/tool/LrcParser.kt | 152 ++++++++++++++++++ .../frank/ffmpeg/tool/UnicodeInputStream.kt | 85 ++++++++++ .../java/com/frank/ffmpeg/util/TimeUtil.kt | 2 +- 5 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/frank/ffmpeg/model/LrcInfo.kt create mode 100644 app/src/main/java/com/frank/ffmpeg/tool/LrcParser.kt create mode 100644 app/src/main/java/com/frank/ffmpeg/tool/UnicodeInputStream.kt diff --git a/app/src/main/java/com/frank/ffmpeg/model/LrcInfo.kt b/app/src/main/java/com/frank/ffmpeg/model/LrcInfo.kt new file mode 100644 index 0000000..4b9383c --- /dev/null +++ b/app/src/main/java/com/frank/ffmpeg/model/LrcInfo.kt @@ -0,0 +1,18 @@ +package com.frank.ffmpeg.model + +import java.util.ArrayList + + +class LrcInfo { + + var title: String? = null + var album: String? = null + var artist: String? = null + var author: String? = null + var creator: String? = null + var encoder: String? = null + var version: String? = null + var offset: Int = 0 + + var lrcLineList: ArrayList? = null +} diff --git a/app/src/main/java/com/frank/ffmpeg/tool/LrcLineTool.kt b/app/src/main/java/com/frank/ffmpeg/tool/LrcLineTool.kt index 451b305..d8783c9 100644 --- a/app/src/main/java/com/frank/ffmpeg/tool/LrcLineTool.kt +++ b/app/src/main/java/com/frank/ffmpeg/tool/LrcLineTool.kt @@ -29,7 +29,7 @@ object LrcLineTool { val mLrcLine = LrcLine() mLrcLine.content = content mLrcLine.timeString = temp - val startTime = TimeUtil.timeConvert(temp) + val startTime = TimeUtil.timeStrToLong(temp) mLrcLine.startTime = startTime listTimes.add(mLrcLine) } diff --git a/app/src/main/java/com/frank/ffmpeg/tool/LrcParser.kt b/app/src/main/java/com/frank/ffmpeg/tool/LrcParser.kt new file mode 100644 index 0000000..e1563c8 --- /dev/null +++ b/app/src/main/java/com/frank/ffmpeg/tool/LrcParser.kt @@ -0,0 +1,152 @@ +package com.frank.ffmpeg.tool + +import com.frank.ffmpeg.model.LrcInfo +import com.frank.ffmpeg.model.LrcLine +import com.frank.ffmpeg.util.TimeUtil + +import java.io.BufferedInputStream +import java.io.BufferedReader +import java.io.FileInputStream +import java.io.IOException +import java.io.InputStream +import java.io.InputStreamReader +import java.util.* +import java.util.regex.Pattern + +/** + * Parse lrc format of lyrics + */ +class LrcParser { + + private val lrcInfo = LrcInfo() + + private val lrcLineList = ArrayList() + + fun readLrc(path: String): LrcInfo? { + var inputStream: InputStream? = null + var inputStreamReader: InputStreamReader? = null + try { + var charsetName: String? = getCharsetName(path) + inputStream = FileInputStream(path) + if (charsetName!!.toLowerCase(Locale.getDefault()) == "utf-8") { + inputStream = UnicodeInputStream(inputStream, charsetName) + charsetName = inputStream.getEncoding() + } + inputStreamReader = if (!charsetName.isNullOrEmpty()) { + InputStreamReader(inputStream, charsetName) + } else { + InputStreamReader(inputStream) + } + val reader = BufferedReader(inputStreamReader) + while (true) { + val str: String? = reader.readLine() ?: break + if (!str.isNullOrEmpty()) { + decodeLine(str) + } + } + lrcInfo.lrcLineList = lrcLineList + return lrcInfo + } catch (e: IOException) { + return null + } finally { + try { + inputStreamReader?.close() + inputStream?.close() + } catch (e: IOException) { + e.printStackTrace() + } + + } + } + + private fun getCharsetName(path: String): String { + var code = "GBK" + var bin: BufferedInputStream? = null + try { + bin = BufferedInputStream(FileInputStream(path)) + when ((bin.read() shl 8) + bin.read()) { + 0xefbb -> code = "UTF-8" + 0xfffe -> code = "Unicode" + 0xfeff -> code = "UTF-16BE" + else -> { + } + } + } catch (e: Exception) { + e.printStackTrace() + } finally { + if (bin != null) { + try { + bin.close() + } catch (e: IOException) { + e.printStackTrace() + } + + } + } + return code + } + + private fun match(line: String): Boolean { + return line.length > 4 && line.lastIndexOf("]") > 4 + } + + private fun decodeLine(str: String) { + if (str.startsWith("[ti:")) { + if (match(str)) + lrcInfo.title = str.substring(4, str.lastIndexOf("]")) + } else if (str.startsWith("[ar:")) { + if (match(str)) + lrcInfo.artist = str.substring(4, str.lastIndexOf("]")) + } else if (str.startsWith("[al:")) { + if (match(str)) + lrcInfo.album = str.substring(4, str.lastIndexOf("]")) + } else if (str.startsWith("[au:")) { + if (match(str)) + lrcInfo.author = str.substring(4, str.lastIndexOf("]")) + } else if (str.startsWith("[by:")) { + if (match(str)) + lrcInfo.creator = str.substring(4, str.lastIndexOf("]")) + } else if (str.startsWith("[re:")) { + if (match(str)) + lrcInfo.encoder = str.substring(4, str.lastIndexOf("]")) + } else if (str.startsWith("[ve:")) { + if (match(str)) + lrcInfo.version = str.substring(4, str.lastIndexOf("]")) + } else if (str.startsWith("[offset:")) { + if (str.lastIndexOf("]") > 8) { + val offset = str.substring(8, str.lastIndexOf("]")) + try { + lrcInfo.offset = Integer.parseInt(offset) + } catch (e: NumberFormatException) { + e.printStackTrace() + } + + } + } else { + val timeExpress = "\\[(\\d{1,2}:\\d{1,2}\\.\\d{1,2})]|\\[(\\d{1,2}:\\d{1,2})]" + val pattern = Pattern.compile(timeExpress) + val matcher = pattern.matcher(str) + var currentTime: Long = 0 + while (matcher.find()) { + val groupCount = matcher.groupCount() + for (index in 0 until groupCount) { + val timeStr = matcher.group(index) + if (index == 0) { + currentTime = TimeUtil.timeStrToLong(timeStr.substring(1, timeStr.length - 1)) + } + } + val content = pattern.split(str) + var currentContent: String? = "" + if (content.isNotEmpty()) { + currentContent = content[content.size - 1] + } + val lrcLine = LrcLine() + lrcLine.startTime = currentTime + lrcLine.endTime = currentTime + 10 * 1000 + lrcLine.content = currentContent + lrcLineList.add(lrcLine) + } + } + } + +} diff --git a/app/src/main/java/com/frank/ffmpeg/tool/UnicodeInputStream.kt b/app/src/main/java/com/frank/ffmpeg/tool/UnicodeInputStream.kt new file mode 100644 index 0000000..6865b10 --- /dev/null +++ b/app/src/main/java/com/frank/ffmpeg/tool/UnicodeInputStream.kt @@ -0,0 +1,85 @@ +package com.frank.ffmpeg.tool + +import java.io.IOException +import java.io.InputStream +import java.io.PushbackInputStream + +class UnicodeInputStream(`in`: InputStream, private val defaultEnc: String) : InputStream() { + + private var encoding: String? = null + private var isInitialed = false + private val internalIn: PushbackInputStream + + init { + internalIn = PushbackInputStream(`in`, BOM_SIZE) + } + + fun getEncoding(): String? { + if (!isInitialed) { + try { + init() + } catch (ex: IOException) { + throw IllegalStateException("getEncoding error=" + ex.message) + } + + } + return encoding + } + + /** + * Read-ahead four bytes and check for BOM marks. Extra bytes are unread + * back to the stream, only BOM bytes are skipped. + */ + @Throws(IOException::class) + private fun init() { + if (isInitialed) + return + + val bom = ByteArray(BOM_SIZE) + val n: Int + val unread: Int + n = internalIn.read(bom, 0, bom.size) + + if (bom[0] == 0x00.toByte() && bom[1] == 0x00.toByte() + && bom[2] == 0xFE.toByte() && bom[3] == 0xFF.toByte()) { + encoding = "UTF-32BE" + unread = n - 4 + } else if (bom[0] == 0xFF.toByte() && bom[1] == 0xFE.toByte() + && bom[2] == 0x00.toByte() && bom[3] == 0x00.toByte()) { + encoding = "UTF-32LE" + unread = n - 4 + } else if (bom[0] == 0xEF.toByte() && bom[1] == 0xBB.toByte() + && bom[2] == 0xBF.toByte()) { + encoding = "UTF-8" + unread = n - 3 + } else if (bom[0] == 0xFE.toByte() && bom[1] == 0xFF.toByte()) { + encoding = "UTF-16BE" + unread = n - 2 + } else if (bom[0] == 0xFF.toByte() && bom[1] == 0xFE.toByte()) { + encoding = "UTF-16LE" + unread = n - 2 + } else { + encoding = defaultEnc + unread = n + } + if (unread > 0) + internalIn.unread(bom, n - unread, unread) + isInitialed = true + } + + @Throws(IOException::class) + override fun close() { + isInitialed = false + internalIn.close() + } + + @Throws(IOException::class) + override fun read(): Int { + isInitialed = true + return internalIn.read() + } + + companion object { + private const val BOM_SIZE = 4 + } +} diff --git a/app/src/main/java/com/frank/ffmpeg/util/TimeUtil.kt b/app/src/main/java/com/frank/ffmpeg/util/TimeUtil.kt index 7d85562..66db81f 100644 --- a/app/src/main/java/com/frank/ffmpeg/util/TimeUtil.kt +++ b/app/src/main/java/com/frank/ffmpeg/util/TimeUtil.kt @@ -82,7 +82,7 @@ object TimeUtil { /** * string time to milliseconds */ - fun timeConvert(timeStr: String): Long { + fun timeStrToLong(timeStr: String): Long { var timeString = timeStr timeString = timeString.replace('.', ':') val times = timeString.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()