Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a379ef8
fix(app): keep generated protobuf messages in proguard
283375 Jul 29, 2024
ae6c1b9
introduce onnx ocr model
283375 Jul 29, 2024
026fb93
feat(core.ocr): new ImageHashesDatabase core
283375 Aug 10, 2024
55d9231
refactor(core.ocr): OCR dependency loader & status builder
283375 Aug 12, 2024
1e9fa05
feat(app): introduce WorkManager
283375 Aug 18, 2024
7bce03d
refactor(helpers): GlobalArcaeaButtonStateHelper
283375 Aug 18, 2024
83119b6
refactor(app.ui): ocr dependencies status viewer
283375 Aug 19, 2024
ecef8d0
feat: CRNN ocr model status
283375 Aug 19, 2024
e4bfcc1
feat: detailed ocr dependency status viewers
283375 Aug 19, 2024
82d5a88
feat: OcrDependenciesScreen
283375 Aug 19, 2024
220a42c
refactor: remove legacy ocr dependency status components
283375 Aug 19, 2024
7c67653
wip: dependency loading during ocr process
283375 Aug 20, 2024
70c8ebe
refactor: ocr queue
283375 Aug 20, 2024
422fb1e
impr(core.ocr): onnx model info loading
283375 Aug 24, 2024
c3ff794
chore(db): entity & database improvements
283375 Aug 28, 2024
f2db3b4
feat(app): add notification channels for work manager jobs
283375 Aug 30, 2024
ed9357c
feat(app.db): ocr queue database
283375 Aug 30, 2024
b8b76e1
refactor(core.ocr): DeviceOcr
283375 Aug 30, 2024
37d1761
feat(app): new work manager job for ocr queue
283375 Aug 30, 2024
4bfa2b5
wip: OcrQueueScreen
283375 Aug 30, 2024
b9f4470
chore(ci): download crnn ocr model before building
283375 Sep 3, 2024
1c03153
chore: sync master branch changes
283375 Sep 3, 2024
280e683
fix(core.ocr): fix `model_info.json` column name
283375 Sep 3, 2024
e050538
chore(core.ocr): ImagePhashDatabase cleanup
283375 Sep 3, 2024
9a519c7
impr(app): OcrQueueJob cancellation handling
283375 Sep 3, 2024
e395cff
impr(app): emergency mode actions
283375 Sep 9, 2024
5302434
impr(app): ui logic
283375 Sep 9, 2024
95f93b3
Merge branch 'master' into ocr-onnx
283375 Sep 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/build_unstable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ jobs:
fileDir: ${{ github.workspace }}
encodedString: ${{ secrets.LOCAL_PROPERTIES_FILE_BASE64 }}

- name: Ensure CRNN OCR model asset folder exists
run: mkdir -p app/src/main/assets/ocr

- name: Download CRNN OCR model to assets
run: |
curl -L -o app/src/main/assets/ocr/model_patched.onnx https://huggingface.co/ArcaeaOffline/crnn-pytorch/resolve/main/model-early_stop_patched.onnx
curl -L -o app/src/main/assets/ocr/model_info.json https://huggingface.co/ArcaeaOffline/crnn-pytorch/resolve/main/model_info.json

- name: Build APK using gradle
run: ./gradlew assembleUnstableRelease

Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
output-metadata.json

# OCR ONNX models
app/src/main/assets/ocr/model*.onnx
app/src/main/assets/ocr/model*.ort
app/src/main/assets/ocr/model_info.json

# Android Studio added
/.idea/caches
/.idea/libraries
Expand Down
13 changes: 12 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import java.io.FileInputStream
import java.util.Properties

plugins {
alias(libs.plugins.ksp)
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.plugin.serialization)
alias(libs.plugins.secrets.gradle.plugin)
alias(libs.plugins.protobuf)
alias(libs.plugins.androidGitVersion)
Expand Down Expand Up @@ -166,7 +168,9 @@ android {

dependencies {
// android & androidx
implementation(androidx.room.runtime)
api(androidx.room.runtime)
ksp(androidx.room.compiler)
implementation(androidx.room.ktx)

implementation(androidx.datastore.core)
implementation(androidx.datastore.preferences)
Expand Down Expand Up @@ -200,9 +204,16 @@ dependencies {

implementation(androidx.documentfile)

implementation(androidx.work.workRuntime)
implementation(androidx.work.workRuntimeKtx)

// 3rd party
implementation(libs.kotlinx.serialization)

implementation(libs.opencv)

implementation(libs.onnxruntime.android)

implementation(libs.apache.commons.io)

implementation(libs.protobuf.protobufJavalite)
Expand Down
3 changes: 3 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@
-keep public class org.opencv.core.CvException
# generated protobuf messages
-keep public class * extends com.google.protobuf.GeneratedMessageLite { *; }
# onnx runtime
# @see https://onnxruntime.ai/docs/build/android.html#note-proguard-rules-for-r8-minimization-android-app-builds-to-work
-keep class ai.onnxruntime.** { *; }

-dontwarn javax.annotation.processing.*
106 changes: 106 additions & 0 deletions app/schemas/xyz.sevive.arcaeaoffline.database.AppDatabase/1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "b733cbfb9112b42e22784ea6006f66f6",
"entities": [
{
"tableName": "ocr_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `source_package_name` TEXT, `store_date` INTEGER NOT NULL, `song_id` TEXT, `rating_class` TEXT NOT NULL, `playResult` INTEGER NOT NULL, `pure` INTEGER, `far` INTEGER, `lost` INTEGER, `date` INTEGER, `max_recall` INTEGER, `modifier` TEXT, `clear_type` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "sourcePackageName",
"columnName": "source_package_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "storeDate",
"columnName": "store_date",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "songId",
"columnName": "song_id",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "ratingClass",
"columnName": "rating_class",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "score",
"columnName": "playResult",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pure",
"columnName": "pure",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "far",
"columnName": "far",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "lost",
"columnName": "lost",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "date",
"columnName": "date",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "maxRecall",
"columnName": "max_recall",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "modifier",
"columnName": "modifier",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "clearType",
"columnName": "clear_type",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b733cbfb9112b42e22784ea6006f66f6')"
]
}
}
140 changes: 140 additions & 0 deletions app/schemas/xyz.sevive.arcaeaoffline.database.OcrQueueDatabase/1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "6c239fc091cf43980a8287de2452e5a5",
"entities": [
{
"tableName": "ocr_queue_tasks",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `inserted_at` INTEGER NOT NULL, `file_uri` TEXT NOT NULL, `file_name` TEXT, `status` INTEGER NOT NULL, `result` TEXT, `play_result` TEXT, `warnings` TEXT, `exception` BLOB)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "insertedAt",
"columnName": "inserted_at",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "fileUri",
"columnName": "file_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "fileName",
"columnName": "file_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "status",
"columnName": "status",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "result",
"columnName": "result",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "playResult",
"columnName": "play_result",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "warnings",
"columnName": "warnings",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "exception",
"columnName": "exception",
"affinity": "BLOB",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_ocr_queue_tasks_file_uri",
"unique": true,
"columnNames": [
"file_uri"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ocr_queue_tasks_file_uri` ON `${TABLE_NAME}` (`file_uri`)"
}
],
"foreignKeys": []
},
{
"tableName": "ocr_queue_enqueue_buffer",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uri` TEXT NOT NULL, `checked` INTEGER NOT NULL, `should_insert` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "uri",
"columnName": "uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "checked",
"columnName": "checked",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "shouldInsert",
"columnName": "should_insert",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_ocr_queue_enqueue_buffer_uri",
"unique": true,
"columnNames": [
"uri"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ocr_queue_enqueue_buffer_uri` ON `${TABLE_NAME}` (`uri`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6c239fc091cf43980a8287de2452e5a5')"
]
}
}
10 changes: 8 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="33" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />

<uses-sdk tools:overrideLibrary="com.jvziyaoyao.scale.image, com.jvziyaoyao.scale.zoomable" />

Expand Down Expand Up @@ -65,10 +67,14 @@

<activity
android:name=".EmergencyModeActivity"
android:exported="true"
android:excludeFromRecents="true"
android:exported="true"
android:finishOnTaskLaunch="true"
android:launchMode="singleInstance" />
</application>

<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="specialUse"
tools:node="merge" />
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,27 @@ import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build
import com.jakewharton.threetenabp.AndroidThreeTen
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import org.acra.config.dialog
import org.acra.config.httpSender
import org.acra.data.StringFormat
import org.acra.ktx.initAcra
import org.acra.sender.HttpSender
import xyz.sevive.arcaeaoffline.core.ocr.device.DeviceOcrOnnxHelper
import xyz.sevive.arcaeaoffline.data.notification.Notifications
import xyz.sevive.arcaeaoffline.helpers.GlobalArcaeaButtonStateHelper
import xyz.sevive.arcaeaoffline.ui.containers.AppDatabaseRepositoryContainer
import xyz.sevive.arcaeaoffline.ui.containers.ArcaeaOfflineDatabaseRepositoryContainer
import xyz.sevive.arcaeaoffline.ui.containers.ArcaeaOfflineDatabaseRepositoryContainerImpl
import xyz.sevive.arcaeaoffline.ui.containers.DataStoreRepositoryContainerImpl
import xyz.sevive.arcaeaoffline.ui.containers.OcrQueueDatabaseRepositoryContainer


class ArcaeaOfflineApplication : Application() {
lateinit var arcaeaOfflineDatabaseRepositoryContainer: ArcaeaOfflineDatabaseRepositoryContainer
lateinit var appDatabaseRepositoryContainer: AppDatabaseRepositoryContainer
val ocrQueueDatabaseRepositoryContainer by lazy { OcrQueueDatabaseRepositoryContainer(this) }
val dataStoreRepositoryContainer by lazy { DataStoreRepositoryContainerImpl(this) }

override fun attachBaseContext(base: Context) {
Expand Down Expand Up @@ -70,6 +77,12 @@ class ArcaeaOfflineApplication : Application() {
super.onCreate()

AndroidThreeTen.init(this)
DeviceOcrOnnxHelper.loadModelInfo(this)
Notifications.createChannels(this)

MainScope().launch {
GlobalArcaeaButtonStateHelper.reload(this@ArcaeaOfflineApplication)
}

addEmergencyModeShortcut()
arcaeaOfflineDatabaseRepositoryContainer =
Expand Down
Loading