refactor: implement lenient M3U/TS stream parsing
This commit is contained in:
@@ -13,6 +13,7 @@ data class M3UChannel(
|
||||
|
||||
object M3UParser {
|
||||
|
||||
private const val M3U_HEADER_MARK = "#EXTM3U"
|
||||
private const val M3U_INFO_MARK = "#EXTINF:"
|
||||
private const val KODI_MARK = "#KODIPROP:"
|
||||
private const val M3U_TVG_ID_MARK = "tvg-id"
|
||||
@@ -22,40 +23,33 @@ object M3UParser {
|
||||
private const val KODI_LICENSE_TYPE = "inputstream.adaptive.license_type"
|
||||
private const val KODI_LICENSE_KEY = "inputstream.adaptive.license_key"
|
||||
|
||||
private val infoRegex = """(-?\d+)(.*),(.+)""".toRegex()
|
||||
private val metadataRegex = """([\w-_.]+)=\s*(?:"([^"]*)"|(\S+))""".toRegex()
|
||||
private val kodiPropRegex = """([^=]+)=(.+)""".toRegex()
|
||||
|
||||
fun parse(content: String): Pair<List<M3UChannel>, String?> {
|
||||
val channels = mutableListOf<M3UChannel>()
|
||||
var epgUrl: String? = null
|
||||
|
||||
val epgUrl = content.lineSequence()
|
||||
.firstOrNull { it.startsWith(M3U_HEADER_MARK) }
|
||||
?.let { extractEpgUrl(it) }
|
||||
|
||||
val lines = content.lineSequence()
|
||||
.filter { it.isNotEmpty() }
|
||||
.map { it.trimEnd() }
|
||||
.dropWhile { it.startsWith(M3U_HEADER_MARK) }
|
||||
.iterator()
|
||||
|
||||
var currentLine: String
|
||||
var metadata = emptyMap<String, String>()
|
||||
var title = ""
|
||||
var infoMatch: MatchResult? = null
|
||||
val kodiMatches = mutableListOf<MatchResult>()
|
||||
|
||||
while (lines.hasNext()) {
|
||||
currentLine = lines.next()
|
||||
|
||||
if (currentLine.startsWith("#EXTM3U")) {
|
||||
epgUrl = extractEpgUrl(currentLine)
|
||||
continue
|
||||
}
|
||||
|
||||
while (currentLine.startsWith("#")) {
|
||||
if (currentLine.startsWith(M3U_INFO_MARK)) {
|
||||
val infoContent = currentLine.drop(M3U_INFO_MARK.length).trim()
|
||||
val commaIndex = infoContent.lastIndexOf(',')
|
||||
if (commaIndex != -1) {
|
||||
val metadataText = infoContent.take(commaIndex)
|
||||
title = infoContent.substring(commaIndex + 1).trim()
|
||||
metadata = parseMetadata(metadataText)
|
||||
}
|
||||
infoMatch = infoRegex.matchEntire(currentLine.drop(M3U_INFO_MARK.length).trim())
|
||||
}
|
||||
if (currentLine.startsWith(KODI_MARK)) {
|
||||
kodiPropRegex
|
||||
@@ -67,16 +61,19 @@ object M3UParser {
|
||||
} else break
|
||||
}
|
||||
|
||||
if (metadata.isEmpty() && !currentLine.startsWith("#")) continue
|
||||
if (infoMatch == null && !currentLine.startsWith("#")) continue
|
||||
|
||||
if (!currentLine.startsWith("#") &&
|
||||
(currentLine.startsWith("http") || currentLine.startsWith("rtmp") ||
|
||||
currentLine.startsWith("rtsp") || currentLine.startsWith("udp"))) {
|
||||
|
||||
val tvgId = metadata[M3U_TVG_ID_MARK].orEmpty()
|
||||
val tvgName = metadata[M3U_TVG_NAME_MARK].orEmpty()
|
||||
val relationId = tvgId.ifEmpty { tvgName }
|
||||
val finalTitle = title.ifEmpty { "Channel ${channels.size + 1}" }
|
||||
if (!currentLine.startsWith("#")) {
|
||||
val title = infoMatch?.groups?.get(3)?.value.orEmpty().trim()
|
||||
val metadata = buildMap {
|
||||
val text = infoMatch?.groups?.get(2)?.value.orEmpty().trim()
|
||||
val matches = metadataRegex.findAll(text)
|
||||
for (match in matches) {
|
||||
val key = match.groups[1]!!.value
|
||||
val value = match.groups[2]?.value?.ifBlank { null } ?: continue
|
||||
put(key.trim(), value.trim())
|
||||
}
|
||||
}
|
||||
|
||||
val kodiMetadata = buildMap {
|
||||
for (match in kodiMatches) {
|
||||
@@ -86,6 +83,11 @@ object M3UParser {
|
||||
}
|
||||
}
|
||||
|
||||
val tvgId = metadata[M3U_TVG_ID_MARK].orEmpty()
|
||||
val tvgName = metadata[M3U_TVG_NAME_MARK].orEmpty()
|
||||
val relationId = tvgId.ifEmpty { tvgName }
|
||||
val finalTitle = title.ifEmpty { "Channel ${channels.size + 1}" }
|
||||
|
||||
channels.add(
|
||||
M3UChannel(
|
||||
id = "${currentLine.hashCode()}_${channels.size}",
|
||||
@@ -99,8 +101,7 @@ object M3UParser {
|
||||
)
|
||||
)
|
||||
|
||||
metadata = emptyMap()
|
||||
title = ""
|
||||
infoMatch = null
|
||||
kodiMatches.clear()
|
||||
}
|
||||
}
|
||||
@@ -108,15 +109,6 @@ object M3UParser {
|
||||
return channels to epgUrl
|
||||
}
|
||||
|
||||
private fun parseMetadata(text: String): Map<String, String> = buildMap {
|
||||
val matches = metadataRegex.findAll(text)
|
||||
for (match in matches) {
|
||||
val key = match.groups[1]!!.value
|
||||
val value = match.groups[2]?.value?.ifBlank { null } ?: continue
|
||||
put(key.trim(), value.trim())
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractEpgUrl(line: String): String? {
|
||||
val xTvgUrlMatch = Regex("""x-tvg-url\s*=\s*"([^"]+)"""").find(line)
|
||||
val urlTvgMatch = Regex("""url-tvg\s*=\s*"([^"]+)"""").find(line)
|
||||
|
||||
Reference in New Issue
Block a user