Offline voice input keyboard & live subtitle tool for Android
Java
323
139 commits
updated Oct 17, 2026
An offline, privacy-focused speech-to-text tool for Android, built with Rust. Tap the microphone on the keyboard you already use — your speech is transcribed entirely on-device and typed into any app. Also includes live subtitles and an optional dedicated voice keyboard.
The app plugs into Android's speech-to-text in three ways, so it works with a wide range of keyboards and apps:
| Path | Who uses it | What happens |
|---|---|---|
Voice-input popup (RECOGNIZE_SPEECH) | SwiftKey, website voice search, many apps | The compact bottom panel opens over the current app |
System speech service (RecognitionService) | Keyboards/apps using Android's SpeechRecognizer | Recognition runs invisibly in the background with automatic endpointing |
| Voice keyboard (IME) | Any keyboard, via the keyboard switcher — also HeliBoard/AnySoftKeyboard-style "switch to voice IME" mic keys | The dedicated voice keyboard opens |
Tap Try voice input on the home screen to test the whole flow in one tap.
Keyboard notes (mic-key behavior verified against each keyboard's source and tested in the emulator):
Prefer voice input as its own keyboard? Enable the Offline Voice Input keyboard via Open Keyboard Settings on the home screen, switch to it from your keyboard switcher, then tap Tap to Record. By default the recording keeps running even if you switch apps or the keyboard closes (turn off Record in background in settings if you don't want that) — the text is inserted when you come back.
Tap Start Live Subtitles and choose Share entire screen to get real-time, on-device captions for any audio or video playing on your device.
Advanced: skip the screen-capture dialog. Android shows a "Start recording or casting?" consent dialog every time subtitles start. You can pre-approve it once via adb — after that, subtitles start instantly and the setting survives reboots (USB debugging can be turned off again afterwards):
adb shell appops set --user 0 dev.notune.transcribe PROJECT_MEDIA allow
To undo it:
adb shell appops set --user 0 dev.notune.transcribe PROJECT_MEDIA default
This relies on the undocumented PROJECT_MEDIA app-op; on some OEM builds it may not work or may get reset by the system — the normal dialog remains the fallback. The same instructions are shown in-app under Skip the permission dialog (advanced).
The built-in Parakeet model works out of the box. (The F-Droid build has no built-in model, since F-Droid builds everything from source: on first start, the app shows how to download the same model in your browser and import it. The F-Droid build is reproducible and carries the same signature as the GitHub releases, which also include it as …_fdroid.apk, so you can switch between the GitHub/Obtainium and F-Droid versions without reinstalling.) Under Manage speech models you can additionally import any transcribe.cpp GGUF model: download a .gguf file in your browser (the in-app Where to get models dialog lists direct links, e.g. a tiny 135 MB English-only Parakeet, the multilingual Nemotron 3.5 streaming model with punctuation, or Whisper large-v3-turbo), then import it via the system file picker and select it. The app itself needs no internet permission — model files are simply copied into the app's private storage. An optional language hint (e.g. en-US, or auto) can be set for imported models.
| Dependency | Installation |
|---|---|
| JDK 17 | Android Studio (bundled) or sudo pacman -S jdk17-openjdk |
| Android SDK | Via Android Studio or sdkmanager |
| Android NDK | sdkmanager "ndk;28.0.13004108" |
| Rust | rustup.rs + rustup target add aarch64-linux-android |
| cargo-ndk | cargo install cargo-ndk |
Create a local.properties file in the project root (this file is gitignored):
sdk.dir=/path/to/your/Android/Sdk
Gradle uses JAVA_HOME. If your default Java is not JDK 17 (Gradle 8.9 can't run on JDK 22+), point Gradle at one in your user-level ~/.gradle/gradle.properties instead of the project file:
org.gradle.java.home=/path/to/jdk17
# Examples:
# /opt/android-studio/jbr (Android Studio bundled JBR, if it is JDK 17/21)
# /usr/lib/jvm/java-17-openjdk (System JDK 17)
The app has two build flavors: standard (GitHub releases, Google Play) bundles the Parakeet model, fdroid is the same app without it.
./gradlew assembleStandardDebug
# Output: app/build/outputs/apk/standard/debug/app-standard-debug.apk
./gradlew assembleStandardRelease
# Output: app/build/outputs/apk/standard/release/app-standard-release.apk
./gradlew bundleStandardRelease
# Output: app/build/outputs/bundle/standardRelease/app-standard-release.aab
./gradlew assembleFdroidRelease
# Output: app/build/outputs/apk/fdroid/release/app-fdroid-release.apk
For release builds, place a release.keystore in the project root and set these environment variables:
export KEY_ALIAS=release
export KEY_PASS=yourpassword
export STORE_PASS=yourpassword
The built-in Parakeet TDT GGUF model (~485 MB) is automatically downloaded from HuggingFace during the first standard build via a Gradle task. The checksum is verified with SHA-256. No manual download is needed. fdroid builds never download it.
├── app/
│ └── src/main/
│ ├── AndroidManifest.xml
│ ├── java/dev/notune/transcribe/ # Android Java code
│ ├── res/ # Resources (layouts, drawables, etc.)
│ ├── assets/ # Model files (downloaded at build time)
│ └── jniLibs/ # Native .so files (built by cargo-ndk)
├── src/ # Rust source code (cdylib)
├── Cargo.toml # Rust crate manifest
├── build.gradle.kts # Root Gradle config
├── app/build.gradle.kts # App module config (AGP 8.13.2)
├── settings.gradle.kts
├── gradle.properties
└── fastlane/metadata/android/ # F-Droid store listing
581 followers · starred Aug 2026
195 followers · starred May 2026
467 followers · starred Jun 2026
66 followers · starred Jul 2026
Offline voice input keyboard & live subtitle tool for Android
Java
323
139 commits
updated Oct 17, 2026
An offline, privacy-focused speech-to-text tool for Android, built with Rust. Tap the microphone on the keyboard you already use — your speech is transcribed entirely on-device and typed into any app. Also includes live subtitles and an optional dedicated voice keyboard.
The app plugs into Android's speech-to-text in three ways, so it works with a wide range of keyboards and apps:
| Path | Who uses it | What happens |
|---|---|---|
Voice-input popup (RECOGNIZE_SPEECH) | SwiftKey, website voice search, many apps | The compact bottom panel opens over the current app |
System speech service (RecognitionService) | Keyboards/apps using Android's SpeechRecognizer | Recognition runs invisibly in the background with automatic endpointing |
| Voice keyboard (IME) | Any keyboard, via the keyboard switcher — also HeliBoard/AnySoftKeyboard-style "switch to voice IME" mic keys | The dedicated voice keyboard opens |
Tap Try voice input on the home screen to test the whole flow in one tap.
Keyboard notes (mic-key behavior verified against each keyboard's source and tested in the emulator):
Prefer voice input as its own keyboard? Enable the Offline Voice Input keyboard via Open Keyboard Settings on the home screen, switch to it from your keyboard switcher, then tap Tap to Record. By default the recording keeps running even if you switch apps or the keyboard closes (turn off Record in background in settings if you don't want that) — the text is inserted when you come back.
Tap Start Live Subtitles and choose Share entire screen to get real-time, on-device captions for any audio or video playing on your device.
Advanced: skip the screen-capture dialog. Android shows a "Start recording or casting?" consent dialog every time subtitles start. You can pre-approve it once via adb — after that, subtitles start instantly and the setting survives reboots (USB debugging can be turned off again afterwards):
adb shell appops set --user 0 dev.notune.transcribe PROJECT_MEDIA allow
To undo it:
adb shell appops set --user 0 dev.notune.transcribe PROJECT_MEDIA default
This relies on the undocumented PROJECT_MEDIA app-op; on some OEM builds it may not work or may get reset by the system — the normal dialog remains the fallback. The same instructions are shown in-app under Skip the permission dialog (advanced).
The built-in Parakeet model works out of the box. (The F-Droid build has no built-in model, since F-Droid builds everything from source: on first start, the app shows how to download the same model in your browser and import it. The F-Droid build is reproducible and carries the same signature as the GitHub releases, which also include it as …_fdroid.apk, so you can switch between the GitHub/Obtainium and F-Droid versions without reinstalling.) Under Manage speech models you can additionally import any transcribe.cpp GGUF model: download a .gguf file in your browser (the in-app Where to get models dialog lists direct links, e.g. a tiny 135 MB English-only Parakeet, the multilingual Nemotron 3.5 streaming model with punctuation, or Whisper large-v3-turbo), then import it via the system file picker and select it. The app itself needs no internet permission — model files are simply copied into the app's private storage. An optional language hint (e.g. en-US, or auto) can be set for imported models.
| Dependency | Installation |
|---|---|
| JDK 17 | Android Studio (bundled) or sudo pacman -S jdk17-openjdk |
| Android SDK | Via Android Studio or sdkmanager |
| Android NDK | sdkmanager "ndk;28.0.13004108" |
| Rust | rustup.rs + rustup target add aarch64-linux-android |
| cargo-ndk | cargo install cargo-ndk |
Create a local.properties file in the project root (this file is gitignored):
sdk.dir=/path/to/your/Android/Sdk
Gradle uses JAVA_HOME. If your default Java is not JDK 17 (Gradle 8.9 can't run on JDK 22+), point Gradle at one in your user-level ~/.gradle/gradle.properties instead of the project file:
org.gradle.java.home=/path/to/jdk17
# Examples:
# /opt/android-studio/jbr (Android Studio bundled JBR, if it is JDK 17/21)
# /usr/lib/jvm/java-17-openjdk (System JDK 17)
The app has two build flavors: standard (GitHub releases, Google Play) bundles the Parakeet model, fdroid is the same app without it.
./gradlew assembleStandardDebug
# Output: app/build/outputs/apk/standard/debug/app-standard-debug.apk
./gradlew assembleStandardRelease
# Output: app/build/outputs/apk/standard/release/app-standard-release.apk
./gradlew bundleStandardRelease
# Output: app/build/outputs/bundle/standardRelease/app-standard-release.aab
./gradlew assembleFdroidRelease
# Output: app/build/outputs/apk/fdroid/release/app-fdroid-release.apk
For release builds, place a release.keystore in the project root and set these environment variables:
export KEY_ALIAS=release
export KEY_PASS=yourpassword
export STORE_PASS=yourpassword
The built-in Parakeet TDT GGUF model (~485 MB) is automatically downloaded from HuggingFace during the first standard build via a Gradle task. The checksum is verified with SHA-256. No manual download is needed. fdroid builds never download it.
├── app/
│ └── src/main/
│ ├── AndroidManifest.xml
│ ├── java/dev/notune/transcribe/ # Android Java code
│ ├── res/ # Resources (layouts, drawables, etc.)
│ ├── assets/ # Model files (downloaded at build time)
│ └── jniLibs/ # Native .so files (built by cargo-ndk)
├── src/ # Rust source code (cdylib)
├── Cargo.toml # Rust crate manifest
├── build.gradle.kts # Root Gradle config
├── app/build.gradle.kts # App module config (AGP 8.13.2)
├── settings.gradle.kts
├── gradle.properties
└── fastlane/metadata/android/ # F-Droid store listing
581 followers · starred Aug 2026
195 followers · starred May 2026
467 followers · starred Jun 2026
66 followers · starred Jul 2026