Updated and tested using the sherpa onnx streaming file - had to download it and extract from tar.bz2 and recompress as zip but that was a start....
This commit is contained in:
@@ -11,7 +11,7 @@ android {
|
||||
applicationId = "net.mmanningau.speechtokeyboard"
|
||||
minSdk = 28
|
||||
targetSdk = 36
|
||||
versionCode = 5
|
||||
versionCode = 8
|
||||
versionName = "1.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.mmanningau.speechtokeyboard // <--- MAKE SURE THIS MATCHES YOUR PACKAGE NAME
|
||||
package net.mmanningau.speechtokeyboard
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
@@ -9,27 +9,18 @@ import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
// import org.vosk.Model
|
||||
// import org.vosk.android.SpeechService --- removed as part of migratoin to whisper.cpp
|
||||
import java.io.File
|
||||
import java.util.zip.ZipInputStream
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
// UI Components
|
||||
private lateinit var statusText: TextView
|
||||
|
||||
// Vosk Components - removed as part of whisper migration
|
||||
// private var model: Model? = null
|
||||
// private var speechService: SpeechService? = null
|
||||
|
||||
// 1. THE FILE PICKER REGISTRY
|
||||
// This handles the result when the user picks a ZIP file
|
||||
// 1. FILE PICKER REGISTRY
|
||||
private val pickZipFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let { uri ->
|
||||
// User picked a file. Now we install it.
|
||||
installModelFromUri(uri)
|
||||
installSherpaModel(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,21 +29,23 @@ class MainActivity : AppCompatActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
statusText = findViewById(R.id.status_text_view) // Make sure you have a TextView with this ID in your layout
|
||||
statusText.text = "Checking for existing model...."
|
||||
statusText = findViewById(R.id.status_text_view)
|
||||
|
||||
// ADD THIS LINE AT THE BOTTOM:
|
||||
// This attempts to load the model immediately if files exist
|
||||
// initVoskModel() - removed as part of whisper migration
|
||||
// Auto-check status on launch
|
||||
checkModelStatus()
|
||||
|
||||
// Button listener (if you are using the button layout)
|
||||
findViewById<android.widget.Button>(R.id.button_load_model)?.setOnClickListener {
|
||||
openFilePicker()
|
||||
}
|
||||
}
|
||||
|
||||
// 2. SETUP THE MENU
|
||||
// 2. MENU SETUP
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.main_menu, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
// 3. HANDLE MENU CLICKS
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_load_model -> {
|
||||
@@ -60,16 +53,17 @@ class MainActivity : AppCompatActivity() {
|
||||
true
|
||||
}
|
||||
R.id.action_test_model -> {
|
||||
// Launch the new Test Activity
|
||||
val intent = Intent(this, TestModelActivity::class.java)
|
||||
startActivity(intent)
|
||||
if (checkModelStatus()) {
|
||||
startActivity(Intent(this, TestModelActivity::class.java))
|
||||
} else {
|
||||
Toast.makeText(this, "Please Load Model First", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. OPEN SYSTEM FILE PICKER
|
||||
private fun openFilePicker() {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
@@ -78,75 +72,85 @@ class MainActivity : AppCompatActivity() {
|
||||
pickZipFile.launch(intent)
|
||||
}
|
||||
|
||||
// 5. INSTALLATION LOGIC (Unzip to private storage)
|
||||
private fun installModelFromUri(uri: android.net.Uri) {
|
||||
statusText.text = "Installing model... please wait."
|
||||
// 3. THE SMART UNZIPPER (Renames & Flattens)
|
||||
private fun installSherpaModel(uri: android.net.Uri) {
|
||||
statusText.text = "Extracting & Optimizing Model..."
|
||||
|
||||
// Run in background thread to avoid freezing UI
|
||||
Thread {
|
||||
try {
|
||||
contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||
val zipInputStream = ZipInputStream(inputStream)
|
||||
val targetDir = File(filesDir, "vosk-model")
|
||||
val targetDir = File(filesDir, "sherpa-model")
|
||||
|
||||
// Clean up old model if exists
|
||||
// Clean start
|
||||
if (targetDir.exists()) targetDir.deleteRecursively()
|
||||
targetDir.mkdirs()
|
||||
|
||||
// Unzip loop
|
||||
var entry = zipInputStream.nextEntry
|
||||
var foundEncoder = false
|
||||
var foundDecoder = false
|
||||
var foundJoiner = false
|
||||
var foundTokens = false
|
||||
|
||||
while (entry != null) {
|
||||
val outFile = File(targetDir, entry.name)
|
||||
if (entry.isDirectory) {
|
||||
outFile.mkdirs()
|
||||
} else {
|
||||
// Ensure parent dir exists
|
||||
outFile.parentFile?.mkdirs()
|
||||
val name = entry.name.lowercase()
|
||||
|
||||
// Smart Rename Logic
|
||||
// We ignore the folders and look for keywords in the filename
|
||||
val targetFileName = when {
|
||||
name.contains("encoder") && name.endsWith(".onnx") -> "encoder.onnx"
|
||||
name.contains("decoder") && name.endsWith(".onnx") -> "decoder.onnx"
|
||||
name.contains("joiner") && name.endsWith(".onnx") -> "joiner.onnx"
|
||||
name.contains("tokens.txt") -> "tokens.txt"
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (targetFileName != null) {
|
||||
val outFile = File(targetDir, targetFileName)
|
||||
outFile.outputStream().use { output ->
|
||||
zipInputStream.copyTo(output)
|
||||
}
|
||||
|
||||
// Track success
|
||||
when (targetFileName) {
|
||||
"encoder.onnx" -> foundEncoder = true
|
||||
"decoder.onnx" -> foundDecoder = true
|
||||
"joiner.onnx" -> foundJoiner = true
|
||||
"tokens.txt" -> foundTokens = true
|
||||
}
|
||||
}
|
||||
|
||||
entry = zipInputStream.nextEntry
|
||||
}
|
||||
}
|
||||
|
||||
// Back to UI Thread to say success
|
||||
runOnUiThread {
|
||||
statusText.text = "Model Installed! Initializing..."
|
||||
// initVoskModel() - removed as part of the whisper migration
|
||||
if (foundEncoder && foundDecoder && foundJoiner && foundTokens) {
|
||||
statusText.text = "Model Installed Successfully!"
|
||||
Toast.makeText(this, "Ready to use!", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
statusText.text = "Error: Invalid Model Zip.\nMissing files."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
runOnUiThread {
|
||||
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_LONG).show()
|
||||
statusText.text = "Installation Failed."
|
||||
statusText.text = "Install Failed: ${e.message}"
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
// 6. INITIALIZE VOSK "BRAIN"
|
||||
// Replace your existing initVoskModel with this updated version
|
||||
/*
|
||||
private fun initVoskModel() {
|
||||
// 4. CHECK IF READY
|
||||
private fun checkModelStatus(): Boolean {
|
||||
val modelDir = File(filesDir, "sherpa-model")
|
||||
val isReady = File(modelDir, "encoder.onnx").exists() &&
|
||||
File(modelDir, "tokens.txt").exists()
|
||||
|
||||
val modelPath = File(filesDir, "vosk-model")
|
||||
|
||||
// Check if the directory exists before trying to load
|
||||
if (!modelPath.exists()) {
|
||||
statusText.text = "No model found. Please load one."
|
||||
return
|
||||
if (isReady) {
|
||||
statusText.text = "Model Loaded & Ready"
|
||||
} else {
|
||||
statusText.text = "No Model Found.\nPlease Load Zip."
|
||||
}
|
||||
|
||||
val actualModelDir = modelPath.listFiles()?.firstOrNull { it.isDirectory } ?: modelPath
|
||||
|
||||
try {
|
||||
model = Model(actualModelDir.absolutePath)
|
||||
statusText.text = "Model Loaded & Ready!"
|
||||
// Optional: Enable your 'Test' button here if you disabled it
|
||||
} catch (e: Exception) {
|
||||
statusText.text = "Error loading saved model: ${e.message}"
|
||||
return isReady
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -111,7 +111,8 @@ class TestModelActivity : AppCompatActivity() {
|
||||
maxActivePaths = 4
|
||||
)
|
||||
|
||||
recognizer = OnlineRecognizer(assetManager = assets, config = config)
|
||||
// recognizer = OnlineRecognizer(assetManager = assets, config = config)
|
||||
recognizer = OnlineRecognizer(config = config)
|
||||
stream = recognizer?.createStream()
|
||||
|
||||
outputText.text = "Engine Loaded. Ready to Stream."
|
||||
|
||||
@@ -10,7 +10,15 @@
|
||||
android:id="@+id/status_text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Status"
|
||||
android:textSize="18sp" />
|
||||
android:text="Status: Checking..."
|
||||
android:textSize="18sp"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="24dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_load_model"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Load Model from Zip" />
|
||||
|
||||
</LinearLayout>
|
||||
Reference in New Issue
Block a user