Recently Written · git

subread-overlay

Shows the lines of an .srt over any Android media player, in time with it. Tap a word to look it up.

git clone https://github.com/equwal/subread-overlay

Log | Files | Refs


commit 2284014bba3b548f4ecbdcb4f0a5561cb922421a
equwal <truex@equwal.com>
2026-09-22 14:05:13 -0700

Test the panel with the text of five languages

The overlay shows the lines of a subtitle file and cuts a word out of a line.
Both steps are sensitive to the script. Only English and Japanese had a test,
so a defect in another language was not found by a test.

Add a property test that writes and reads a subtitle file with lines of
English, Portuguese, Spanish, Russian and Japanese. The line must come back
byte for byte: the accents, the inverted marks, the guillemets and the
ideographic space.

Add cases to the word cutter test on the device for Portuguese, Spanish and
Russian. A word with an accent, and a word after an inverted mark or a
guillemet, must be one word.

 .../kotlin/space/subread/overlay/OverlayTest.kt    | 15 ++++++++++++
 .../kotlin/space/subread/overlay/core/CoreTest.kt  | 27 ++++++++++++++++++++++
 2 files changed, 42 insertions(+)
diff --git a/app/src/androidTest/kotlin/space/subread/overlay/OverlayTest.kt b/app/src/androidTest/kotlin/space/subread/overlay/OverlayTest.kt
index d62659d..27bfb31 100644
--- a/app/src/androidTest/kotlin/space/subread/overlay/OverlayTest.kt
+++ b/app/src/androidTest/kotlin/space/subread/overlay/OverlayTest.kt
@@ -36,6 +36,21 @@ class WordsTest {
         assertNull(word("", 0))
     }
 
+    @Test
+    fun aWordWithAccentsOrInvertedMarks() {
+        val pt = "— Não me parece bonito — disse ela, à porta."
+        assertEquals("Não", word(pt, pt.indexOf("Não")))
+        assertEquals("à", word(pt, pt.indexOf("à")))
+        assertNull("a dash", word(pt, 0))
+        val es = "¿Qué es esto? ¡Ñandú, señor!"
+        assertEquals("Qué", word(es, es.indexOf("Qué")))
+        assertEquals("Ñandú", word(es, es.indexOf("Ñandú")))
+        assertNull("the inverted mark", word(es, 0))
+        val ru = "«Ёлка, — сказал он, — и её огни»."
+        assertEquals("Ёлка", word(ru, ru.indexOf("Ёлка")))
+        assertEquals("её", word(ru, ru.indexOf("её")))
+    }
+
     @Test
     fun aWordOfJapaneseThatHasNoSpaces() {
         val text = "女のいない男たちは、東京で暮らしている。"
diff --git a/core/src/test/kotlin/space/subread/overlay/core/CoreTest.kt b/core/src/test/kotlin/space/subread/overlay/core/CoreTest.kt
index ef1d200..09611eb 100644
--- a/core/src/test/kotlin/space/subread/overlay/core/CoreTest.kt
+++ b/core/src/test/kotlin/space/subread/overlay/core/CoreTest.kt
@@ -2,11 +2,13 @@ package space.subread.overlay.core
 
 import io.kotest.property.Arb
 import io.kotest.property.arbitrary.boolean
+import io.kotest.property.arbitrary.element
 import io.kotest.property.arbitrary.float
 import io.kotest.property.arbitrary.int
 import io.kotest.property.arbitrary.list
 import io.kotest.property.arbitrary.long
 import io.kotest.property.arbitrary.map
+import io.kotest.property.arbitrary.pair
 import io.kotest.property.arbitrary.stringPattern
 import io.kotest.property.checkAll
 import kotlinx.coroutines.runBlocking
@@ -44,6 +46,31 @@ class SrtTest {
         }
     }
 
+    /** Lines of the languages the overlay is checked against, with the marks their books use. */
+    private val lines = Arb.element(
+        "It was a dark night; the rain fell.",                    // en
+        "— Não me parece bonito — disse ela, à porta.",           // pt
+        "¿Qué es esto? ¡Ñandú, señor Quijote!",                   // es
+        "«Ёлка, — сказал он, — и её огни».",                      // ru
+        "吾輩は猫である。名前はまだ無い。",                          // ja
+        "「女のいない男たち」 東京で暮らしている",                    // ja, ideographic space and brackets
+    )
+
+    private val multilingualCues = Arb.list(Arb.pair(Arb.int(1..20_000).map { it.toLong() }, lines), 1..30).map { list ->
+        var at = 0L
+        list.map { (gap, text) ->
+            at += gap
+            Cue(at, at + 1 + gap, text)
+        }
+    }
+
+    @Test
+    fun aLineOfAnyScriptIsReadBackUnchanged(): Unit = runBlocking {
+        checkAll(multilingualCues) { list ->
+            assertEquals(list, Srt.parse(write(list)))
+        }
+    }
+
     @Test
     fun noTextMakesTheReaderFail(): Unit = runBlocking {
         checkAll(Arb.stringPattern("[0-9a-z:>,\\- \n]{0,200}")) { junk -> Srt.parse(junk) }