diff --git a/.github/workflows/run-java-test.yaml b/.github/workflows/run-java-test.yaml index 647384f056..240ec4217d 100644 --- a/.github/workflows/run-java-test.yaml +++ b/.github/workflows/run-java-test.yaml @@ -252,6 +252,14 @@ jobs: # Delete model files to save space rm -rf sherpa-onnx-punct-* + - name: Run java test (Offline add diacritics) + shell: bash + run: | + cd ./java-api-examples + ./run-offline-add-diacritics.sh + # Delete model files to save space + rm -rf catt_eo_model_onnx + - name: Run java test (speaker diarization) shell: bash run: | diff --git a/java-api-examples/OfflineAddDiacritics.java b/java-api-examples/OfflineAddDiacritics.java new file mode 100644 index 0000000000..f1da5031ea --- /dev/null +++ b/java-api-examples/OfflineAddDiacritics.java @@ -0,0 +1,38 @@ +// Copyright 2026 Matias Lin + +// This file shows how to use a CATT diacritization model to add diacritics to Arabic text. +import com.k2fsa.sherpa.onnx.*; + +public class OfflineAddDiacritics { + public static void main(String[] args) { + // please download the model from + // https://github.com/abjadai/catt/releases/download/v2/eo_model_onnx.zip + String cattEncoder = "./catt_eo_model_onnx/encoder.onnx"; + String cattDecoder = "./catt_eo_model_onnx/decoder.onnx"; + OfflineDiacritizationModelConfig modelConfig = + OfflineDiacritizationModelConfig.builder() + .setCattEncoder(cattEncoder) + .setCattDecoder(cattDecoder) + .setNumThreads(1) + .setDebug(true) + .build(); + OfflineDiacritizationConfig config = + OfflineDiacritizationConfig.builder().setModel(modelConfig).build(); + + OfflineDiacritization diacrt = new OfflineDiacritization(config); + + String[] sentences = + new String[] { + "وقالت مجلة نيوزويك الأمريكية التحديث الجديد ل إنستجرام يمكن أن يساهم في إيقاف وكشف الحسابات المزورة بسهولة شديدة", + "اللغة العربية من أقدم اللغات السامية", + }; + + System.out.println("---"); + for (String text : sentences) { + String out = diacrt.addDiacritics(text); + System.out.printf("Input: %s\n", text); + System.out.printf("Output: %s\n", out); + System.out.println("---"); + } + } +} diff --git a/java-api-examples/run-offline-add-diacritics.sh b/java-api-examples/run-offline-add-diacritics.sh new file mode 100755 index 0000000000..ef9099be79 --- /dev/null +++ b/java-api-examples/run-offline-add-diacritics.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +set -ex + +if [[ ! -f ../build/lib/libsherpa-onnx-jni.dylib && ! -f ../build/lib/libsherpa-onnx-jni.so ]]; then + mkdir -p ../build + pushd ../build + cmake \ + -DSHERPA_ONNX_ENABLE_PYTHON=OFF \ + -DSHERPA_ONNX_ENABLE_TESTS=OFF \ + -DSHERPA_ONNX_ENABLE_CHECK=OFF \ + -DBUILD_SHARED_LIBS=ON \ + -DSHERPA_ONNX_ENABLE_PORTAUDIO=OFF \ + -DSHERPA_ONNX_ENABLE_JNI=ON \ + .. + + make -j4 + ls -lh lib + popd +fi + +if [ ! -f ../sherpa-onnx/java-api/build/sherpa-onnx.jar ]; then + pushd ../sherpa-onnx/java-api + make + popd +fi + +if [[ ! -f "./catt_eo_model_onnx/encoder.onnx" || ! -f "./catt_eo_model_onnx/decoder.onnx" ]]; then + curl -SL -O https://github.com/abjadai/catt/releases/download/v2/eo_model_onnx.zip + unzip eo_model_onnx.zip -d catt_eo_model_onnx + rm eo_model_onnx.zip +fi + +java \ + -Djava.library.path=$PWD/../build/lib \ + -cp ../sherpa-onnx/java-api/build/sherpa-onnx.jar \ + ./OfflineAddDiacritics.java diff --git a/sherpa-onnx/java-api/Makefile b/sherpa-onnx/java-api/Makefile index fcb61fcda0..56c3688336 100644 --- a/sherpa-onnx/java-api/Makefile +++ b/sherpa-onnx/java-api/Makefile @@ -117,6 +117,10 @@ java_files += OfflineSpeechDenoiser.java java_files += OnlineSpeechDenoiserConfig.java java_files += OnlineSpeechDenoiser.java +java_files += OfflineDiacritizationModelConfig.java +java_files += OfflineDiacritizationConfig.java +java_files += OfflineDiacritization.java + class_files := $(java_files:%.java=%.class) java_files := $(addprefix src/main/java/$(package_dir)/,$(java_files)) diff --git a/sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineDiacritization.java b/sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineDiacritization.java new file mode 100644 index 0000000000..1e2b08f24a --- /dev/null +++ b/sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineDiacritization.java @@ -0,0 +1,39 @@ +// Copyright 2026 Matias Lin + +package com.k2fsa.sherpa.onnx; + +public class OfflineDiacritization { + private long ptr = 0; + + public OfflineDiacritization(OfflineDiacritizationConfig config) { + LibraryLoader.maybeLoad(); + ptr = newFromFile(config); + if (ptr == 0) { + throw new IllegalArgumentException("Invalid OfflineDiacritizationConfig: failed to create native OfflineDiacritization"); + } + } + + public String addDiacritics(String text) { + return addDiacritics(ptr, text); + } + + @Override + protected void finalize() throws Throwable { + release(); + } + + // You'd better call it manually if it is not used anymore + public void release() { + if (this.ptr == 0) { + return; + } + delete(this.ptr); + this.ptr = 0; + } + + private native void delete(long ptr); + + private native long newFromFile(OfflineDiacritizationConfig config); + + private native String addDiacritics(long ptr, String text); +} diff --git a/sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineDiacritizationConfig.java b/sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineDiacritizationConfig.java new file mode 100644 index 0000000000..39334b2bd9 --- /dev/null +++ b/sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineDiacritizationConfig.java @@ -0,0 +1,32 @@ +// Copyright 2026 Matias Lin + +package com.k2fsa.sherpa.onnx; + +public class OfflineDiacritizationConfig { + private final OfflineDiacritizationModelConfig model; + + private OfflineDiacritizationConfig(Builder builder) { + this.model = builder.model; + } + + public static Builder builder() { + return new Builder(); + } + + public OfflineDiacritizationModelConfig getModel() { + return model; + } + + public static class Builder { + private OfflineDiacritizationModelConfig model = OfflineDiacritizationModelConfig.builder().build(); + + public OfflineDiacritizationConfig build() { + return new OfflineDiacritizationConfig(this); + } + + public Builder setModel(OfflineDiacritizationModelConfig model) { + this.model = model; + return this; + } + } +} diff --git a/sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineDiacritizationModelConfig.java b/sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineDiacritizationModelConfig.java new file mode 100644 index 0000000000..a41aaf1611 --- /dev/null +++ b/sherpa-onnx/java-api/src/main/java/com/k2fsa/sherpa/onnx/OfflineDiacritizationModelConfig.java @@ -0,0 +1,68 @@ +// Copyright 2026 Matias Lin + +package com.k2fsa.sherpa.onnx; + +public class OfflineDiacritizationModelConfig { + private final String cattEncoder; + private final String cattDecoder; + private final int numThreads; + private final boolean debug; + private final String provider; + + private OfflineDiacritizationModelConfig(Builder builder) { + this.cattEncoder = builder.cattEncoder; + this.cattDecoder = builder.cattDecoder; + this.numThreads = builder.numThreads; + this.debug = builder.debug; + this.provider = builder.provider; + } + + public static Builder builder() { + return new Builder(); + } + + public String getCattEncoder() { + return cattEncoder; + } + + public String getCattDecoder() { + return cattDecoder; + } + + public static class Builder { + private String cattEncoder = ""; + private String cattDecoder = ""; + private int numThreads = 1; + private boolean debug = true; + private String provider = "cpu"; + + public OfflineDiacritizationModelConfig build() { + return new OfflineDiacritizationModelConfig(this); + } + + public Builder setCattEncoder(String cattEncoder) { + this.cattEncoder = cattEncoder; + return this; + } + + public Builder setCattDecoder(String cattDecoder) { + this.cattDecoder = cattDecoder; + return this; + } + + public Builder setNumThreads(int numThreads) { + this.numThreads = numThreads; + return this; + } + + public Builder setDebug(boolean debug) { + this.debug = debug; + return this; + } + + public Builder setProvider(String provider) { + this.provider = provider; + return this; + } + } +} diff --git a/sherpa-onnx/jni/CMakeLists.txt b/sherpa-onnx/jni/CMakeLists.txt index 3996afb43a..d0ce4e3f2e 100644 --- a/sherpa-onnx/jni/CMakeLists.txt +++ b/sherpa-onnx/jni/CMakeLists.txt @@ -15,6 +15,7 @@ set(sources common.cc jni.cc keyword-spotter.cc + offline-diacritization.cc offline-punctuation.cc offline-recognizer.cc offline-speech-denoiser.cc diff --git a/sherpa-onnx/jni/offline-diacritization.cc b/sherpa-onnx/jni/offline-diacritization.cc new file mode 100644 index 0000000000..16cbcaecae --- /dev/null +++ b/sherpa-onnx/jni/offline-diacritization.cc @@ -0,0 +1,124 @@ +// sherpa-onnx/jni/offline-diacritization.cc +// +// Copyright (c) 2026 Matias Lin + +#include "sherpa-onnx/csrc/offline-diacritization.h" + +#include + +#include "sherpa-onnx/csrc/macros.h" +#include "sherpa-onnx/jni/common.h" + +namespace sherpa_onnx { + +static OfflineDiacritizationConfig GetOfflineDiacritizationConfig( + JNIEnv *env, jobject config, bool *ok) { + OfflineDiacritizationConfig ans; + + jclass cls = env->GetObjectClass(config); + jfieldID fid; + + fid = env->GetFieldID( + cls, "model", "Lcom/k2fsa/sherpa/onnx/OfflineDiacritizationModelConfig;"); + jobject model_config = env->GetObjectField(config, fid); + jclass model_config_cls = env->GetObjectClass(model_config); + + SHERPA_ONNX_JNI_READ_STRING(ans.model.catt_encoder, cattEncoder, + model_config_cls, model_config); + + SHERPA_ONNX_JNI_READ_STRING(ans.model.catt_decoder, cattDecoder, + model_config_cls, model_config); + + SHERPA_ONNX_JNI_READ_INT(ans.model.num_threads, numThreads, model_config_cls, + model_config); + + SHERPA_ONNX_JNI_READ_BOOL(ans.model.debug, debug, model_config_cls, + model_config); + + SHERPA_ONNX_JNI_READ_STRING(ans.model.provider, provider, model_config_cls, + model_config); + + *ok = true; + return ans; +} + +} // namespace sherpa_onnx + +SHERPA_ONNX_EXTERN_C +JNIEXPORT jlong JNICALL +Java_com_k2fsa_sherpa_onnx_OfflineDiacritization_newFromAsset( + JNIEnv *env, jobject /*obj*/, jobject asset_manager, jobject _config) { +#if __ANDROID_API__ >= 9 + AAssetManager *mgr = AAssetManager_fromJava(env, asset_manager); + if (!mgr) { + SHERPA_ONNX_LOGE("Failed to get asset manager: %p", mgr); + return 0; + } +#endif + bool ok = false; + auto config = sherpa_onnx::GetOfflineDiacritizationConfig(env, _config, &ok); + + if (!ok) { + SHERPA_ONNX_LOGE("Please read the error message carefully"); + return 0; + } + + SHERPA_ONNX_LOGE("config:\n%s", config.ToString().c_str()); + + auto model = new sherpa_onnx::OfflineDiacritization( +#if __ANDROID_API__ >= 9 + mgr, +#endif + config); + + return (jlong)model; +} + +SHERPA_ONNX_EXTERN_C +JNIEXPORT jlong JNICALL +Java_com_k2fsa_sherpa_onnx_OfflineDiacritization_newFromFile(JNIEnv *env, + jobject /*obj*/, + jobject _config) { + bool ok = false; + auto config = sherpa_onnx::GetOfflineDiacritizationConfig(env, _config, &ok); + + if (!ok) { + SHERPA_ONNX_LOGE("Please read the error message carefully"); + return 0; + } + + SHERPA_ONNX_LOGE("config:\n%s", config.ToString().c_str()); + + if (!config.Validate()) { + SHERPA_ONNX_LOGE("Errors found in config!"); + return 0; + } + + auto model = new sherpa_onnx::OfflineDiacritization(config); + + return (jlong)model; +} + +SHERPA_ONNX_EXTERN_C +JNIEXPORT void JNICALL Java_com_k2fsa_sherpa_onnx_OfflineDiacritization_delete( + JNIEnv * /*env*/, jobject /*obj*/, jlong ptr) { + delete reinterpret_cast(ptr); +} + +SHERPA_ONNX_EXTERN_C +JNIEXPORT jstring JNICALL +Java_com_k2fsa_sherpa_onnx_OfflineDiacritization_addDiacritics(JNIEnv *env, + jobject /*obj*/, + jlong ptr, + jstring text) { + auto diacrt = + reinterpret_cast(ptr); + + const char *ptext = env->GetStringUTFChars(text, nullptr); + + std::string result = diacrt->AddDiacritics(ptext); + + env->ReleaseStringUTFChars(text, ptext); + + return SafeNewStringUTF(env, result); +} diff --git a/sherpa-onnx/jni/sherpa-onnx-symbols.exp b/sherpa-onnx/jni/sherpa-onnx-symbols.exp index bb3ef6c313..7353a2b603 100644 --- a/sherpa-onnx/jni/sherpa-onnx-symbols.exp +++ b/sherpa-onnx/jni/sherpa-onnx-symbols.exp @@ -13,6 +13,10 @@ _Java_com_k2fsa_sherpa_onnx_KeywordSpotter_isReady _Java_com_k2fsa_sherpa_onnx_KeywordSpotter_newFromAsset _Java_com_k2fsa_sherpa_onnx_KeywordSpotter_newFromFile _Java_com_k2fsa_sherpa_onnx_KeywordSpotter_reset +_Java_com_k2fsa_sherpa_onnx_OfflineDiacritization_addDiacritics +_Java_com_k2fsa_sherpa_onnx_OfflineDiacritization_delete +_Java_com_k2fsa_sherpa_onnx_OfflineDiacritization_newFromAsset +_Java_com_k2fsa_sherpa_onnx_OfflineDiacritization_newFromFile _Java_com_k2fsa_sherpa_onnx_OfflinePunctuation_addPunctuation _Java_com_k2fsa_sherpa_onnx_OfflinePunctuation_delete _Java_com_k2fsa_sherpa_onnx_OfflinePunctuation_newFromAsset