app/src/main/kotlin/com/honjimaku/subrep/Models.kt (3127 bytes)
1 package com.honjimaku.subrep 2 3 import android.content.Context 4 import okhttp3.OkHttpClient 5 import okhttp3.Request 6 import java.io.File 7 8 /** 9 * The speech models. Each is a whisper.cpp file from the ggerganov/whisper.cpp repository on 10 * Hugging Face, in 8-bit, which is what a phone runs fastest. 11 */ 12 enum class Model(val id: String, val label: String, val about: String) { 13 TINY("tiny-q8_0", "tiny", "43 MB. The fastest. Rough words."), 14 BASE("base-q8_0", "base", "82 MB. Keeps up on most phones. Fair words."), 15 SMALL("small-q8_0", "small", "264 MB. Good words. Slow on a phone: the captions come late."); 16 17 val fileName: String get() = "ggml-$id.bin" 18 val url: String get() = "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/$fileName" 19 20 fun file(context: Context): File = File(File(context.filesDir, "models"), fileName) 21 22 fun downloaded(context: Context): Boolean = file(context).isFile 23 24 companion object { 25 fun byId(id: String): Model = entries.firstOrNull { it.id == id } ?: BASE 26 } 27 } 28 29 /** Downloads a model to its file. Runs on the thread of the caller. */ 30 class Downloader(private val http: OkHttpClient) { 31 32 /** 33 * Downloads [model] into its file under [context]. [onProgress] gets the bytes so far and 34 * the total (-1 when unknown). Returns null when done, else the reason it failed. 35 */ 36 fun download(context: Context, model: Model, onProgress: (Long, Long) -> Unit): String? { 37 val target = model.file(context) 38 target.parentFile?.mkdirs() 39 // The whole file goes to a temporary name first: a half file must never pass as a model. 40 val temp = File(target.path + ".part") 41 try { 42 http.newCall(Request.Builder().url(model.url).build()).execute().use { response -> 43 if (!response.isSuccessful) return "HTTP ${response.code}" 44 val body = response.body ?: return "empty answer" 45 val total = body.contentLength() 46 var done = 0L 47 var lastReport = 0L 48 body.byteStream().use { input -> 49 temp.outputStream().use { output -> 50 val buffer = ByteArray(64 * 1024) 51 while (true) { 52 val n = input.read(buffer) 53 if (n < 0) break 54 output.write(buffer, 0, n) 55 done += n 56 if (done - lastReport >= 1024 * 1024) { 57 lastReport = done 58 onProgress(done, total) 59 } 60 } 61 } 62 } 63 if (total > 0 && done != total) return "the download stopped at $done of $total bytes" 64 if (!temp.renameTo(target)) return "cannot write ${target.name}" 65 onProgress(done, total) 66 return null 67 } 68 } catch (e: Exception) { 69 return "${e.javaClass.simpleName}: ${e.message}" 70 } finally { 71 temp.delete() 72 } 73 } 74 }