1- package com.subpilot.data
1+ package com.geekneuron. subpilot.subtitle
22
3- object SubFileParser { .. . }
3+ import okio.BufferedSource
4+ import okio.FileSystem
5+ import okio.Path
6+ import okio.buffer
7+ import okio.use
8+
9+ sealed class SubtitleEntry (open val startMs : Long , open val endMs : Long , open val text : String )
10+
11+ data class SRTEntry (
12+ override val startMs : Long ,
13+ override val endMs : Long ,
14+ override val text : String
15+ ) : SubtitleEntry(startMs, endMs, text)
16+
17+ data class ASSEntry (
18+ override val startMs : Long ,
19+ override val endMs : Long ,
20+ override val text : String ,
21+ val style : String = " Default"
22+ ) : SubtitleEntry(startMs, endMs, text)
23+
24+ object SubFileParser {
25+ fun parse (fileSystem : FileSystem , path : Path ): List <SubtitleEntry > {
26+ val extension = path.name.substringAfterLast(" ." ).lowercase()
27+ return when (extension) {
28+ " srt" -> parseSRT(fileSystem, path)
29+ " ass" -> parseASS(fileSystem, path)
30+ else -> emptyList()
31+ }
32+ }
33+
34+ private fun parseSRT (fs : FileSystem , path : Path ): List <SRTEntry > {
35+ val result = mutableListOf<SRTEntry >()
36+ fs.source(path).buffer().use { source ->
37+ val lines = source.readUtf8().split(" \r ?\n " )
38+ var i = 0
39+ while (i < lines.size) {
40+ val index = lines[i].trim()
41+ i++
42+ if (i >= lines.size) break
43+ val timeLine = lines[i++ ].trim()
44+ val timeParts = timeLine.split(" --> " )
45+ if (timeParts.size != 2 ) continue
46+ val startMs = parseSrtTime(timeParts[0 ])
47+ val endMs = parseSrtTime(timeParts[1 ])
48+ val textLines = mutableListOf<String >()
49+ while (i < lines.size && lines[i].isNotBlank()) {
50+ textLines.add(lines[i++ ])
51+ }
52+ result.add(SRTEntry (startMs, endMs, textLines.joinToString(" \n " )))
53+ while (i < lines.size && lines[i].isBlank()) i++
54+ }
55+ }
56+ return result
57+ }
58+
59+ private fun parseSrtTime (timeStr : String ): Long {
60+ val parts = timeStr.split(" :" , " ," )
61+ val h = parts[0 ].toInt()
62+ val m = parts[1 ].toInt()
63+ val s = parts[2 ].toInt()
64+ val ms = parts[3 ].toInt()
65+ return ((h * 3600 + m * 60 + s) * 1000L + ms)
66+ }
67+
68+ private fun parseASS (fs : FileSystem , path : Path ): List <ASSEntry > {
69+ val result = mutableListOf<ASSEntry >()
70+ var dialogueFound = false
71+ fs.source(path).buffer().use { source ->
72+ source.readUtf8LineSequence().forEach { line ->
73+ if (line.startsWith(" [Events]" )) {
74+ dialogueFound = true
75+ } else if (dialogueFound && line.startsWith(" Dialogue:" )) {
76+ val parts = line.split(" ," , limit = 10 )
77+ if (parts.size >= 10 ) {
78+ val startMs = parseAssTime(parts[1 ])
79+ val endMs = parseAssTime(parts[2 ])
80+ val text = parts[9 ].trim()
81+ result.add(ASSEntry (startMs, endMs, text))
82+ }
83+ }
84+ }
85+ }
86+ return result
87+ }
88+
89+ private fun parseAssTime (timeStr : String ): Long {
90+ val parts = timeStr.split(" :" , " ." )
91+ val h = parts[0 ].toInt()
92+ val m = parts[1 ].toInt()
93+ val s = parts[2 ].toInt()
94+ val cs = parts[3 ].toInt()
95+ return ((h * 3600 + m * 60 + s) * 1000L + cs * 10 )
96+ }
97+ }
0 commit comments