app/src/main/kotlin/com/honjimaku/subrep/Hallucinations.kt (2617 bytes)
1 package com.honjimaku.subrep 2 3 /** 4 * Lines that Whisper invents over silence or noise: the credits of the subtitles it learned 5 * from. The list is that of the desktop app. 6 */ 7 object Hallucinations { 8 9 private val patterns = listOf( 10 // Finnish 11 "^tekstitys", 12 "^tekstityksen tuotti", 13 "^k[aä]ä?nn[oö]s", 14 "^kiitos( kun katsoit| paljon| katsomisesta)?[.!]?$", 15 "^suomennos", 16 // Japanese, which Whisper emits over silence all the time 17 "ご(視聴|清聴)(いただき)?ありがとうございま", 18 "チャンネル登録", 19 "^字幕", 20 "^おわり[。]?$", 21 "^(お|ご)?しまい[。]?$", 22 "^ありがとうございま(した|す)[。]?$", 23 "^(えー|あー|ん|う)+[ー。]?$", 24 // Russian 25 "^субтитры", 26 "^спасибо за просмотр", 27 "^продолжение следует", 28 "^редактор субтитров", 29 // English and generic 30 "^subtitles? by", 31 "^amara\\.org", 32 "^thanks? for watching", 33 "^thank you[.!]?$", 34 "^please subscribe", 35 // Marks, symbols and spaces only: no word in any script. (Java's \W is ASCII-only.) 36 "^[\\p{P}\\p{S}\\p{Z}\\s]*$", 37 ).map { Regex(it, RegexOption.IGNORE_CASE) } 38 39 /** 40 * A piece repeated four times or more: "о-о-о-о-о-о", "はいはいはいはいはい", or a phrase 41 * of up to [LOOP_PIECE_MAX] characters. The lazy group finds the shortest piece. 42 */ 43 private val loop = Regex("(.{1,$LOOP_PIECE_MAX}?)\\1{4,}") 44 45 /** True when [text] is one of the invented lines, or has no word at all. */ 46 fun isHallucination(text: String): Boolean { 47 val line = text.trim() 48 return patterns.any { it.containsMatchIn(line) } 49 } 50 51 /** 52 * [text] with each loop cut. Whisper falls into a loop on singing or noise. A short piece 53 * (up to [SHORT_PIECE_MAX] characters) keeps three repeats, so that a real "no, no, no" stays. 54 * A phrase keeps one. The cut runs again until nothing changes, so a loop that a cut uncovers 55 * is cut too. 56 */ 57 fun collapse(text: String): String { 58 var line = text 59 while (true) { 60 val cut = loop.replace(line) { match -> 61 val piece = match.groupValues[1] 62 if (piece.length <= SHORT_PIECE_MAX) piece.repeat(3) else piece 63 } 64 if (cut == line) return line 65 line = cut 66 } 67 } 68 69 private const val LOOP_PIECE_MAX = 40 70 private const val SHORT_PIECE_MAX = 6 71 }