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