Recently Written · git

subread-android

SubRead for Android: times an audiobook against its ebook on the device

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

Log | Files | Refs


commit 95d0437e0233fe956840a2719005ed74a2458df5
equwal <truex@equwal.com>
2026-09-21 02:20:34 -0700

Make the build fit F-Droid

- The version is plain numbers in the build file. F-Droid reads them
  there to find a new release.
- The speech model is in the repository (43 MB, MIT licence, the file
  of the whisper.cpp project). A build needs no network.
- The APK holds no dependency list that only Google can read.
- fastlane/metadata has the listing; fdroid/ has the recipe for the
  merge request to fdroiddata.

 .github/workflows/build.yml                        |  20 ++-----------
 .gitignore                                         |   2 --
 README.md                                          |  27 +++++++++++++----
 app/build.gradle.kts                               |  12 ++++++--
 app/src/main/assets/models/ggml-tiny-q8_0.bin      | Bin 0 -> 43537433 bytes
 fastlane/metadata/android/en-US/changelogs/7.txt   |   1 +
 .../metadata/android/en-US/full_description.txt    |  25 ++++++++++++++++
 .../android/en-US/images/featureGraphic.png        | Bin 0 -> 31507 bytes
 fastlane/metadata/android/en-US/images/icon.png    | Bin 0 -> 7555 bytes
 .../images/phoneScreenshots/1-first-screen.png     | Bin 0 -> 115416 bytes
 .../en-US/images/phoneScreenshots/2-outputs.png    | Bin 0 -> 106247 bytes
 .../metadata/android/en-US/short_description.txt   |   1 +
 fastlane/metadata/android/en-US/title.txt          |   1 +
 fdroid/space.subread.app.yml                       |  33 +++++++++++++++++++++
 14 files changed, 94 insertions(+), 28 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 17e51bc..d84744a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -21,9 +21,7 @@ jobs:
     env:
       CMAKE_VERSION: "3.31.6"
       NDK_VERSION: "29.0.14206865"
-      # The speech model is not in git (43 MB). Pinned by hash, so the build
-      # fails loudly if what is served ever changes.
-      MODEL_URL: https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny-q8_0.bin
+      # The speech model is in git. The hash says that it is the file of the whisper.cpp project.
       MODEL_SHA256: c2085835d3f50733e2ff6e4b41ae8a2b8d8110461e18821b09a15c40c42d1cca
 
     steps:
@@ -53,20 +51,6 @@ jobs:
       - name: Set up Gradle
         uses: gradle/actions/setup-gradle@v4
 
-      - name: Restore the speech model
-        id: model
-        uses: actions/cache@v4
-        with:
-          path: app/src/main/assets/models
-          key: model-${{ env.MODEL_SHA256 }}
-
-      - name: Download the speech model
-        if: steps.model.outputs.cache-hit != 'true'
-        run: |
-          set -euo pipefail
-          mkdir -p app/src/main/assets/models
-          curl -fsSL --retry 3 -o app/src/main/assets/models/ggml-tiny-q8_0.bin "${MODEL_URL}"
-
       - name: Verify the speech model
         run: echo "${MODEL_SHA256}  app/src/main/assets/models/ggml-tiny-q8_0.bin" | sha256sum -c -
 
@@ -88,7 +72,7 @@ jobs:
       - name: Build the APK
         env:
           SUBREAD_KEYSTORE_PASSWORD: ${{ secrets.SUBREAD_KEYSTORE_PASSWORD }}
-        run: ./gradlew --no-daemon :app:assembleRelease -PversionCode=${{ github.run_number }}
+        run: ./gradlew --no-daemon :app:assembleRelease
 
       - name: Upload the APK
         uses: actions/upload-artifact@v4
diff --git a/.gitignore b/.gitignore
index 3c0322d..02bd9b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,8 +15,6 @@ core/src/test/resources/golden-local/
 # On-device test media. Bring your own pair; the test skips without it.
 app/src/androidTest/assets/local/
 
-# Downloaded at build time, never committed.
-app/src/main/assets/models/
 
 # Python byte code
 __pycache__/
diff --git a/README.md b/README.md
index d05ca2b..c9886aa 100644
--- a/README.md
+++ b/README.md
@@ -57,26 +57,41 @@ encoder). No Android encoder makes 4K or 8K H.264 today.
 ## Building the app
 
 CI builds it (`.github/workflows/build.yml`): the APK is an artifact of every
-push to `main`. Locally it needs the Android SDK, NDK 29 and CMake 3.31, and the
-speech model at `app/src/main/assets/models/ggml-tiny-q8_0.bin` (the workflow has
-the URL and checksum). Without an SDK, Gradle leaves `:app` out and `:core`
-still builds.
+push to `main`. Locally it needs the Android SDK, NDK 29 and CMake 3.31. Without
+an SDK, Gradle leaves `:app` out and `:core` still builds.
+
+The speech model (`app/src/main/assets/models/ggml-tiny-q8_0.bin`, 43 MB, MIT
+licence) is in the repository. It is the file of the whisper.cpp project, and
+the workflow checks its hash. It is in git and not downloaded by the build, so
+that a build needs no network: F-Droid asks for that, and the app itself has no
+network permission.
 
 The release key is not in the repository; CI reads it from Actions secrets.
 
 ## Releases
 
 Each release is a tag (`vMAJOR.MINOR.PATCH`) and a GitHub release with the signed
-APK attached. The tag is the app's `versionName`:
+APK attached. Set `versionName` (the tag without the `v`) and `versionCode` in
+`app/build.gradle.kts` first, and write `fastlane/metadata/android/en-US/changelogs/<versionCode>.txt`:
 
 ```bash
-./gradlew :app:assembleRelease -PversionName=0.2.0 -PversionCode=2
+./gradlew :app:assembleRelease
 git tag -a v0.2.0 -m "what changed" && git push origin main --tags
 gh release create v0.2.0 app/build/outputs/apk/release/app-release.apk --notes "what changed"
 ```
 
 `versionCode` must go up every release, or Android refuses the update.
 
+### F-Droid
+
+The build has what F-Droid asks for: free licences only, no network at build
+time, no Google dependency list in the APK, and the version as plain numbers in
+the build file. F-Droid reads the listing from `fastlane/metadata/android/`, and
+finds new releases by their tags. `fdroid/space.subread.app.yml` is the recipe
+for a merge request to [fdroiddata](https://gitlab.com/fdroid/fdroiddata).
+F-Droid signs its builds with its own key, so an install from F-Droid and one
+from GitHub or Google Play do not update each other.
+
 ## Requirements
 
 Android 8+, a 64-bit ARM processor with ARMv8.2 half-precision and dot-product
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 01a6062..07ccba0 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -16,12 +16,20 @@ android {
     compileSdk = 36
     ndkVersion = "29.0.14206865"
 
+    // No list of dependencies, encrypted for Google alone, inside the app: F-Droid
+    // does not accept a part of the app that nobody else can read.
+    dependenciesInfo {
+        includeInApk = false
+        includeInBundle = false
+    }
+
     defaultConfig {
         applicationId = "space.subread.app"
         minSdk = 26
         targetSdk = 36
-        versionCode = (providers.gradleProperty("versionCode").orNull ?: "1").toInt()
-        versionName = providers.gradleProperty("versionName").orNull ?: "0.1.0"
+        // Plain numbers, in this file: F-Droid reads them from here to find a new release.
+        versionCode = 7
+        versionName = "0.6.0"
         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
 
         // Every phone worth transcribing on is 64-bit ARM; the speech library
diff --git a/app/src/main/assets/models/ggml-tiny-q8_0.bin b/app/src/main/assets/models/ggml-tiny-q8_0.bin
new file mode 100644
index 0000000..1653436
Binary files /dev/null and b/app/src/main/assets/models/ggml-tiny-q8_0.bin differ
diff --git a/fastlane/metadata/android/en-US/changelogs/7.txt b/fastlane/metadata/android/en-US/changelogs/7.txt
new file mode 100644
index 0000000..efeb53a
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/7.txt
@@ -0,0 +1 @@
+The listing and the build now fit F-Droid: the version is in the build file, the speech model is in the repository, and the app holds no dependency list that only Google can read. No change to what the app does.
diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt
new file mode 100644
index 0000000..6276126
--- /dev/null
+++ b/fastlane/metadata/android/en-US/full_description.txt
@@ -0,0 +1,25 @@
+SubRead lines an audiobook up with its ebook, sentence by sentence, and gives you subtitles with the time of each line. Read along while you listen: for language learners, and for anyone who wants text and voice together.
+
+The words come from your book, not from a speech model. A small model only listens for where the narrator is; the text of each line is the book's own. Names, spelling and punctuation stay right.
+
+WHAT IT MAKES
+• Subtitles (.srt) - each line of the book with the time it is read. Made for Hoshi Reader's read-along, and they work in any player.
+• Video + subtitles - an .mp4 of the book's cover over the audio, with the .srt beside it. It plays with subtitles in any video player, and you can upload it to YouTube. 720p and larger, 1 to 60 frames a second. The picture is still, so the video adds only about 60 MB to a 10-hour book.
+
+WHAT IT TAKES
+• Audio: m4b, m4a, mp3, opus, ogg, flac, wav
+• Book: epub, plain text, Aozora Bunko zip
+• Languages: Japanese, Spanish, Portuguese, Russian, Finnish, English, German, French, Italian, Chinese, Korean and more, or automatic detection
+
+PRIVATE, AND YOURS
+• All of the work is done on your device. Nothing is uploaded.
+• No account, no ads, no tracking. The app asks for no permissions and does not use the network.
+• Free, and open source (AGPL-3.0): https://github.com/equwal/subread-android
+
+GOOD TO KNOW
+• A long book takes hours: about a third of the length of the audio on a mid-range device. Keep the app open while it works. If it is interrupted, Continue starts from where it was.
+• Pages that nobody reads aloud (front matter, notes) are left out by themselves.
+• The app needs a 64-bit ARM processor from about 2018 or later (ARMv8.2).
+• You need the audiobook and the ebook of the same text. A different edition or translation gives poor results; the app tells you how much of the book it found.
+
+In a hurry, or on an old phone? The same conversion runs in a browser at https://subread.space
diff --git a/fastlane/metadata/android/en-US/images/featureGraphic.png b/fastlane/metadata/android/en-US/images/featureGraphic.png
new file mode 100644
index 0000000..766db21
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/featureGraphic.png differ
diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png
new file mode 100644
index 0000000..6687070
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/icon.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1-first-screen.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1-first-screen.png
new file mode 100644
index 0000000..da8314f
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1-first-screen.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2-outputs.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2-outputs.png
new file mode 100644
index 0000000..852a387
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2-outputs.png differ
diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt
new file mode 100644
index 0000000..b8b3746
--- /dev/null
+++ b/fastlane/metadata/android/en-US/short_description.txt
@@ -0,0 +1 @@
+Line an audiobook up with its ebook: subtitles and video, made on your device.
diff --git a/fastlane/metadata/android/en-US/title.txt b/fastlane/metadata/android/en-US/title.txt
new file mode 100644
index 0000000..a95d9ca
--- /dev/null
+++ b/fastlane/metadata/android/en-US/title.txt
@@ -0,0 +1 @@
+SubRead
diff --git a/fdroid/space.subread.app.yml b/fdroid/space.subread.app.yml
new file mode 100644
index 0000000..def2b50
--- /dev/null
+++ b/fdroid/space.subread.app.yml
@@ -0,0 +1,33 @@
+# For https://gitlab.com/fdroid/fdroiddata : copy to metadata/space.subread.app.yml
+Categories:
+  - Reading
+  - Multimedia
+License: AGPL-3.0-only
+AuthorName: equwal
+WebSite: https://subread.space
+SourceCode: https://github.com/equwal/subread-android
+IssueTracker: https://github.com/equwal/subread-android/issues
+Changelog: https://github.com/equwal/subread-android/releases
+
+AutoName: SubRead
+
+RepoType: git
+Repo: https://github.com/equwal/subread-android.git
+
+Builds:
+  - versionName: 0.6.0
+    versionCode: 7
+    commit: v0.6.0
+    subdir: app
+    submodules: true
+    sudo:
+      - apt-get update
+      - apt-get install -y cmake ninja-build
+    gradle:
+      - yes
+    ndk: 29.0.14206865
+
+AutoUpdateMode: Version
+UpdateCheckMode: Tags
+CurrentVersion: 0.6.0
+CurrentVersionCode: 7