react-native-sherpa-onnx 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +28 -15
  2. package/SherpaOnnx.podspec +13 -5
  3. package/android/prebuilt-download.gradle +18 -5
  4. package/android/prebuilt-versions.gradle +8 -4
  5. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-helper.cpp +43 -142
  6. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-helper.h +12 -4
  7. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-stt.cpp +694 -307
  8. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-tts.cpp +194 -99
  9. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect.h +90 -0
  10. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-stt-wrapper.cpp +3 -0
  11. package/android/src/main/java/com/sherpaonnx/SherpaOnnxModule.kt +70 -0
  12. package/android/src/main/java/com/sherpaonnx/SherpaOnnxPcmCapture.kt +150 -0
  13. package/android/src/main/java/com/sherpaonnx/SherpaOnnxSttHelper.kt +39 -19
  14. package/ios/SherpaOnnx+PcmLiveStream.mm +288 -0
  15. package/ios/SherpaOnnx+STT.mm +2 -0
  16. package/ios/SherpaOnnx.mm +1 -1
  17. package/ios/model_detect/sherpa-onnx-model-detect-helper.h +9 -3
  18. package/ios/model_detect/sherpa-onnx-model-detect-helper.mm +38 -54
  19. package/ios/model_detect/sherpa-onnx-model-detect-stt.mm +620 -267
  20. package/ios/model_detect/sherpa-onnx-model-detect-tts.mm +131 -28
  21. package/ios/model_detect/sherpa-onnx-model-detect.h +70 -0
  22. package/ios/stt/sherpa-onnx-stt-wrapper.mm +4 -0
  23. package/lib/module/NativeSherpaOnnx.js.map +1 -1
  24. package/lib/module/audio/index.js +52 -0
  25. package/lib/module/audio/index.js.map +1 -1
  26. package/lib/module/stt/streaming.js +6 -3
  27. package/lib/module/stt/streaming.js.map +1 -1
  28. package/lib/typescript/src/NativeSherpaOnnx.d.ts +16 -2
  29. package/lib/typescript/src/NativeSherpaOnnx.d.ts.map +1 -1
  30. package/lib/typescript/src/audio/index.d.ts +17 -0
  31. package/lib/typescript/src/audio/index.d.ts.map +1 -1
  32. package/lib/typescript/src/stt/streaming.d.ts.map +1 -1
  33. package/lib/typescript/src/stt/streamingTypes.d.ts +1 -1
  34. package/lib/typescript/src/stt/streamingTypes.d.ts.map +1 -1
  35. package/package.json +6 -1
  36. package/scripts/check-model-csvs.sh +72 -0
  37. package/scripts/setup-ios-framework.sh +48 -48
  38. package/src/NativeSherpaOnnx.ts +18 -2
  39. package/src/audio/index.ts +81 -0
  40. package/src/stt/streaming.ts +10 -5
  41. package/src/stt/streamingTypes.ts +1 -1
  42. package/third_party/sherpa-onnx-prebuilt/ANDROID_RELEASE_TAG +1 -1
  43. package/third_party/sherpa-onnx-prebuilt/IOS_RELEASE_TAG +1 -1
package/README.md CHANGED
@@ -14,6 +14,8 @@ React Native SDK for sherpa-onnx – offline and streaming speech processing
14
14
  [![Android](https://img.shields.io/badge/Android-Supported-green)](https://www.android.com/)
15
15
  [![iOS](https://img.shields.io/badge/iOS-Supported-blue)](https://www.apple.com/ios/)
16
16
 
17
+ <a href="https://www.buymeacoffee.com/xdcobra" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" width="150" /></a>
18
+
17
19
  </div>
18
20
 
19
21
  > **⚠️ SDK 0.3.0 – Breaking changes from 0.2.0**
@@ -75,14 +77,23 @@ A React Native TurboModule that provides offline and streaming speech processing
75
77
 
76
78
  | Model Type | `modelType` Value | Description | Download Links |
77
79
  | ------------------------ | ----------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
78
- | **Zipformer/Transducer** | `'transducer'` | Requires `encoder.onnx`, `decoder.onnx`, `joiner.onnx`, and `tokens.txt` | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/offline-transducer/index.html) |
79
- | **Paraformer** | `'paraformer'` | Requires `model.onnx` (or `model.int8.onnx`) and `tokens.txt` | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/offline-paraformer/index.html) |
80
- | **NeMo CTC** | `'nemo_ctc'` | Requires `model.onnx` (or `model.int8.onnx`) and `tokens.txt` | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/offline-ctc/nemo/index.html) |
81
- | **Whisper** | `'whisper'` | Requires `encoder.onnx`, `decoder.onnx`, and `tokens.txt` | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/whisper/index.html) |
82
- | **WeNet CTC** | `'wenet_ctc'` | Requires `model.onnx` (or `model.int8.onnx`) and `tokens.txt` | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/offline-ctc/wenet/index.html) |
83
- | **SenseVoice** | `'sense_voice'` | Requires `model.onnx` (or `model.int8.onnx`) and `tokens.txt` | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/sense-voice/index.html) |
84
- | **FunASR Nano** | `'funasr_nano'` | Requires `encoder_adaptor.onnx`, `llm.onnx`, `embedding.onnx`, and `tokenizer` directory | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/funasr-nano/index.html) |
85
- | **Tone CTC (t-one)** | `'tone_ctc'` | Single `model.onnx` + `tokens.txt`. Folder name usually contains `t-one`, `t_one` or `tone` | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/online-ctc/index.html) |
80
+ | **Zipformer/Transducer** | `'transducer'` | Encoder–decoder–joiner (e.g. icefall). Good balance of speed and accuracy. Folder name should contain **zipformer** or **transducer** for auto-detection. | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/offline-transducer/index.html) |
81
+ | **LSTM Transducer** | `'transducer'` | Same layout as Zipformer (encoder–decoder–joiner). LSTM-based streaming ASR; detected as transducer. Folder name may contain **lstm**. | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/online-transducer/lstm-transducer-models.html) |
82
+ | **Paraformer** | `'paraformer'` | Single-model non-autoregressive ASR; fast and accurate. Detected by `model.onnx`; no folder token required. | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/offline-paraformer/index.html) |
83
+ | **NeMo CTC** | `'nemo_ctc'` | NeMo CTC; good for English and streaming. Folder name should contain **nemo** or **parakeet**. | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/offline-ctc/nemo/index.html) |
84
+ | **Whisper** | `'whisper'` | Multilingual, encoder–decoder; strong zero-shot. Detected by encoder+decoder (no joiner); folder token optional. | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/whisper/index.html) |
85
+ | **WeNet CTC** | `'wenet_ctc'` | CTC from WeNet; compact. Folder name should contain **wenet**. | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/offline-ctc/wenet/index.html) |
86
+ | **SenseVoice** | `'sense_voice'` | Multilingual with emotion/punctuation. Folder name should contain **sense** or **sensevoice**. | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/sense-voice/index.html) |
87
+ | **FunASR Nano** | `'funasr_nano'` | Lightweight LLM-based ASR. Folder name should contain **funasr** or **funasr-nano**. | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/funasr-nano/index.html) |
88
+ | **Moonshine (v1)** | `'moonshine'` | Four-part streaming-capable ASR (preprocess, encode, uncached/cached decode). Folder name should contain **moonshine**. | [Download](https://k2-fsa.github.io/sherpa/onnx/moonshine/index.html) |
89
+ | **Moonshine (v2)** | `'moonshine_v2'` | Two-part Moonshine (encoder + merged decoder); `.onnx` or `.ort`. Folder name should contain **moonshine** (v2 preferred if both layouts present). | [Download](https://k2-fsa.github.io/sherpa/onnx/moonshine/index.html) |
90
+ | **Fire Red ASR** | `'fire_red_asr'` | Fire Red encoder–decoder ASR. Folder name should contain **fire_red** or **fire-red**. | [Download](https://k2-fsa.github.io/sherpa/onnx/FireRedAsr/index.html) |
91
+ | **Dolphin** | `'dolphin'` | Single-model CTC. Folder name should contain **dolphin**. | [Download](https://k2-fsa.github.io/sherpa/onnx/Dolphin/index.html) |
92
+ | **Canary** | `'canary'` | NeMo Canary multilingual. Folder name should contain **canary**. | [Download](https://k2-fsa.github.io/sherpa/onnx/nemo/canary.html) |
93
+ | **Omnilingual** | `'omnilingual'` | Omnilingual CTC. Folder name should contain **omnilingual**. | [Download](https://k2-fsa.github.io/sherpa/onnx/omnilingual-asr/index.html) |
94
+ | **MedASR** | `'medasr'` | Medical ASR CTC. Folder name should contain **medasr**. | [Download](https://github.com/k2-fsa/sherpa-onnx) |
95
+ | **Telespeech CTC** | `'telespeech_ctc'`| Telespeech CTC. Folder name should contain **telespeech**. | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/telespeech/index.html) |
96
+ | **Tone CTC (t-one)** | `'tone_ctc'` | Lightweight streaming CTC (e.g. t-one). Folder name should contain **t-one**, **t_one**, or **tone** (as word). | [Download](https://k2-fsa.github.io/sherpa/onnx/pretrained_models/online-ctc/index.html) |
86
97
 
87
98
  For **real-time (streaming) recognition** from a microphone or audio stream, use streaming-capable model types: `transducer`, `paraformer`, `zipformer2_ctc`, `nemo_ctc`, or `tone_ctc`. See [Streaming (Online) Speech-to-Text](./docs/stt_streaming.md).
88
99
 
@@ -90,12 +101,12 @@ For **real-time (streaming) recognition** from a microphone or audio stream, use
90
101
 
91
102
  | Model Type | `modelType` Value | Description | Download Links |
92
103
  | ---------------- | ----------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
93
- | **VITS** | `'vits'` | Fast, high-quality TTS. Includes Piper, Coqui, MeloTTS, MMS variants. Requires `model.onnx`, `tokens.txt` | [Download](https://github.com/k2-fsa/sherpa-onnx/releases/tag/tts-models) |
94
- | **Matcha** | `'matcha'` | High-quality acoustic model + vocoder. Requires `acoustic_model.onnx`, `vocoder.onnx`, `tokens.txt` | [Download](https://k2-fsa.github.io/sherpa/onnx/tts/pretrained_models/matcha.html) |
95
- | **Kokoro** | `'kokoro'` | Multi-speaker, multi-language. Requires `model.onnx`, `voices.bin`, `tokens.txt`, `espeak-ng-data/` | [Download](https://github.com/k2-fsa/sherpa-onnx/releases/tag/tts-models) |
96
- | **KittenTTS** | `'kitten'` | Lightweight, multi-speaker. Requires `model.onnx`, `voices.bin`, `tokens.txt`, `espeak-ng-data/` | [Download](https://github.com/k2-fsa/sherpa-onnx/releases/tag/tts-models) |
97
- | **Zipvoice** | `'zipvoice'` | Voice cloning capable. Requires `encoder.onnx`, `decoder.onnx`, `vocoder.onnx`, `tokens.txt` | [Download](https://k2-fsa.github.io/sherpa/onnx/tts/pretrained_models/zipvoice.html) |
98
- | **Pocket** | `'pocket'` | Flow-matching TTS. Requires `lm_flow.onnx`, `lm_main.onnx`, `encoder.onnx`, `decoder.onnx`, `text_conditioner.onnx`, `vocab.json`, `token_scores.json` | [Download](https://github.com/k2-fsa/sherpa-onnx/releases/tag/tts-models) |
104
+ | **VITS** | `'vits'` | Fast, high-quality TTS (Piper, Coqui, MeloTTS, MMS). Folder name should contain **vits** if used with other voice models. | [Download](https://github.com/k2-fsa/sherpa-onnx/releases/tag/tts-models) |
105
+ | **Matcha** | `'matcha'` | High-quality acoustic model + vocoder. Detected by acoustic_model + vocoder; no folder token required. | [Download](https://k2-fsa.github.io/sherpa/onnx/tts/pretrained_models/matcha.html) |
106
+ | **Kokoro** | `'kokoro'` | Multi-speaker, multi-language. Folder name should contain **kokoro** (not kitten) for auto-detection. | [Download](https://github.com/k2-fsa/sherpa-onnx/releases/tag/tts-models) |
107
+ | **KittenTTS** | `'kitten'` | Lightweight, multi-speaker. Folder name should contain **kitten** (not kokoro) for auto-detection. | [Download](https://github.com/k2-fsa/sherpa-onnx/releases/tag/tts-models) |
108
+ | **Zipvoice** | `'zipvoice'` | Voice cloning (encoder + decoder + vocoder). Detected by file layout; folder token optional. | [Download](https://k2-fsa.github.io/sherpa/onnx/tts/pretrained_models/zipvoice.html) |
109
+ | **Pocket** | `'pocket'` | Flow-matching TTS. Detected by lm_flow, lm_main, text_conditioner, vocab/token_scores; no folder token required. | [Download](https://github.com/k2-fsa/sherpa-onnx/releases/tag/tts-models) |
99
110
 
100
111
  For **streaming TTS** (incremental generation, low latency), use `createStreamingTTS()` with supported model types. See [Streaming Text-to-Speech](./docs/tts_streaming.md).
101
112
 
@@ -151,6 +162,7 @@ The XCFramework must include the C++ API (`libsherpa-onnx-cxx-api.a` merged or l
151
162
 
152
163
  - [Speech-to-Text (STT)](./docs/stt.md) – Offline transcription (file or samples)
153
164
  - [Streaming (Online) Speech-to-Text](./docs/stt_streaming.md) – Real-time recognition, partial results, endpoint detection
165
+ - [PCM Live Stream](./docs/pcm_live_stream.md) – Native microphone capture with resampling for live transcription (use with streaming STT)
154
166
  - [Text-to-Speech (TTS)](./docs/tts.md) – Offline and streaming generation
155
167
  - [Streaming Text-to-Speech](./docs/tts_streaming.md) – Incremental TTS (createStreamingTTS)
156
168
  - [Execution provider support (QNN, NNAPI, XNNPACK, Core ML)](./docs/execution-providers.md) – Checking and using acceleration backends
@@ -179,7 +191,7 @@ We provide example applications to help you get started with `react-native-sherp
179
191
 
180
192
  The example app included in this repository demonstrates audio-to-text transcription, text-to-speech, and streaming features. It includes:
181
193
 
182
- - Multiple model type support (Zipformer, Paraformer, NeMo CTC, Whisper, WeNet CTC, SenseVoice, FunASR Nano)
194
+ - Multiple model type support (Zipformer, Paraformer, NeMo CTC, Whisper, WeNet CTC, SenseVoice, FunASR Nano, Moonshine, and more)
183
195
  - Model selection and configuration
184
196
  - **Offline** audio file transcription
185
197
  - **Online (streaming) STT** – live transcription from the microphone with partial results
@@ -202,6 +214,7 @@ yarn android # or yarn ios
202
214
  <td><img src="./docs/images/example_stt_2.png" alt="Transcribe cantonese audio" width="240" /></td>
203
215
  </tr>
204
216
  <tr>
217
+ <td><img src="./docs/images/example_streaming.png" alt="Text to speech generation" width="240" /></td>
205
218
  <td><img src="./docs/images/example_tts.png" alt="Text to speech generation" width="240" /></td>
206
219
  <td><img src="./docs/images/example_provider.png" alt="Text to speech generation" width="240" /></td>
207
220
  </tr>
@@ -37,6 +37,18 @@ if libarchive_sources.empty?
37
37
  abort("[SherpaOnnx] Libarchive sources missing. Ensure third_party/libarchive_prebuilt/libarchive-ios-layout exists (run third_party/libarchive_prebuilt/build_libarchive_ios.sh) or ios/scripts/setup-ios-libarchive.sh has run, and that ios/scripts/patch-libarchive-includes.sh succeeds. Check pod install logs for patch script errors.")
38
38
  end
39
39
 
40
+ # Run iOS framework setup when podspec is loaded (works for :path pods).
41
+ setup_script = File.join(pod_root, "scripts", "setup-ios-framework.sh")
42
+ if File.exist?(setup_script)
43
+ prev = ENV["SHERPA_ONNX_PROJECT_ROOT"]
44
+ ENV["SHERPA_ONNX_PROJECT_ROOT"] = pod_root
45
+ unless system("bash", setup_script)
46
+ ENV["SHERPA_ONNX_PROJECT_ROOT"] = prev
47
+ abort("[SherpaOnnx] setup-ios-framework.sh failed. Check third_party/sherpa-onnx-prebuilt/IOS_RELEASE_TAG and network. Run manually: bash #{setup_script}")
48
+ end
49
+ ENV["SHERPA_ONNX_PROJECT_ROOT"] = prev
50
+ end
51
+
40
52
  Pod::Spec.new do |s|
41
53
  s.name = "SherpaOnnx"
42
54
  s.version = package["version"]
@@ -48,14 +60,10 @@ Pod::Spec.new do |s|
48
60
  s.platforms = { :ios => min_ios_version_supported }
49
61
  s.source = { :git => "https://github.com/XDcobra/react-native-sherpa-onnx.git", :tag => "#{s.version}" }
50
62
 
51
- # Download sherpa-onnx XCFramework from GitHub Releases before pod install (uses IOS_RELEASE_TAG for pinned version).
52
- setup_script = File.join(pod_root, "scripts", "setup-ios-framework.sh")
53
- s.prepare_command = "bash \"#{setup_script}\""
54
-
55
63
  s.source_files = ["ios/**/*.{h,m,mm,swift,cpp}", *libarchive_sources]
56
64
  s.private_header_files = "ios/**/*.h"
57
65
 
58
- s.frameworks = "Foundation", "Accelerate", "CoreML"
66
+ s.frameworks = "Foundation", "Accelerate", "CoreML", "AVFoundation", "AudioToolbox"
59
67
  s.vendored_frameworks = "ios/Frameworks/sherpa_onnx.xcframework"
60
68
  # Absolute paths so headers are found regardless of PODS_TARGET_SRCROOT (e.g. when building via React Native CLI).
61
69
  xcframework_root = File.join(pod_root, "ios", "Frameworks", "sherpa_onnx.xcframework")
@@ -99,12 +99,20 @@ def readReleaseTag = { File tagFile ->
99
99
  project.tasks.register("downloadNativeLibsIfNeeded") {
100
100
  doLast {
101
101
  def downloadDir = file("${project.buildDir}/prebuilt-downloads")
102
+ def currentSherpaVersion = project.ext.sherpaOnnxVersion
103
+ def sherpaVersionFile = new File(downloadDir, "sherpa-onnx-version.txt")
104
+ def storedSherpaVersion = (sherpaVersionFile.exists() ? sherpaVersionFile.text.trim() : null)
105
+ def sherpaNeedsUpdate = !hasAllSherpaLibs() || !hasSherpaHeaders() || storedSherpaVersion == null || storedSherpaVersion != currentSherpaVersion
102
106
 
103
- if (hasAllSherpaLibs() && hasSherpaHeaders()) {
104
- println "[sherpa-onnx] Native libs + headers: (1) local third_party (jniLibs + cpp/include already present)"
107
+ if (hasAllSherpaLibs() && hasSherpaHeaders() && !sherpaNeedsUpdate) {
108
+ println "[sherpa-onnx] Native libs + headers: (1) already present, version ${currentSherpaVersion}"
109
+ }
110
+ if (sherpaNeedsUpdate && storedSherpaVersion != null && storedSherpaVersion != currentSherpaVersion) {
111
+ println "[sherpa-onnx] Version change detected (${storedSherpaVersion} -> ${currentSherpaVersion}), refreshing libs and headers"
105
112
  }
106
113
 
107
- if (!hasAllSherpaLibs() || !hasSherpaHeaders()) {
114
+ def sherpaUpdatedFromAar = [false]
115
+ if (sherpaNeedsUpdate) {
108
116
  try {
109
117
  def aarFiles = project.configurations.sherpaOnnxAar.files
110
118
  if (!aarFiles.isEmpty()) {
@@ -127,6 +135,9 @@ project.tasks.register("downloadNativeLibsIfNeeded") {
127
135
  copy { from fileTree(aarExtractDir) { include 'c-api/**' }; into includeSherpaDir }
128
136
  println "Extracted sherpa-onnx C-API headers from Maven AAR"
129
137
  }
138
+ downloadDir.mkdirs()
139
+ sherpaVersionFile.text = currentSherpaVersion
140
+ sherpaUpdatedFromAar[0] = true
130
141
  println "[sherpa-onnx] Native libs + headers: (2) Maven AAR (${aar.name})"
131
142
  }
132
143
  } catch (Exception e) {
@@ -228,7 +239,7 @@ project.tasks.register("downloadNativeLibsIfNeeded") {
228
239
  if (!repo) {
229
240
  def needFfmpeg = !sherpaOnnxDisableFfmpeg && (!hasAllFfmpegLibs() || !hasFfmpegHeaders())
230
241
  def needLibarchive = !sherpaOnnxDisableLibarchive && (!hasAllLibarchiveLibs() || !hasLibarchiveHeaders())
231
- if (needFfmpeg || needLibarchive || !hasAllSherpaLibs() || !hasSherpaHeaders()) {
242
+ if (needFfmpeg || needLibarchive || (sherpaNeedsUpdate && !sherpaUpdatedFromAar[0])) {
232
243
  throw new RuntimeException(
233
244
  "Native libs/headers still missing and GitHub repo unknown. Set -PprebuiltGitHubRepo=owner/repo or ensure git remote origin is a GitHub URL. " +
234
245
  "Alternatively run third_party/ffmpeg_prebuilt/copy_prebuilts_to_sdk.js, third_party/sherpa-onnx-prebuilt/copy_prebuilts_to_sdk.js, third_party/libarchive_prebuilt/copy_prebuilts_to_sdk.js, or use Maven (com.xdcobra.sherpa:ffmpeg / sherpa-onnx / libarchive), or ensure ANDROID_RELEASE_TAG releases exist. " +
@@ -290,7 +301,7 @@ project.tasks.register("downloadNativeLibsIfNeeded") {
290
301
  println "Downloaded and extracted libarchive prebuilts (libs + include) from ${tag}"
291
302
  }
292
303
 
293
- if (!hasAllSherpaLibs() || !hasSherpaHeaders()) {
304
+ if (sherpaNeedsUpdate && !sherpaUpdatedFromAar[0]) {
294
305
  def tagFile = file("${project.projectDir.parent}/third_party/sherpa-onnx-prebuilt/ANDROID_RELEASE_TAG")
295
306
  def tag = readReleaseTag(tagFile)
296
307
  if (!tag) throw new RuntimeException("Missing or empty third_party/sherpa-onnx-prebuilt/ANDROID_RELEASE_TAG")
@@ -319,6 +330,8 @@ project.tasks.register("downloadNativeLibsIfNeeded") {
319
330
  sherpaOnnxClassesDir.mkdirs()
320
331
  copy { from sherpaJavaJar; into sherpaOnnxClassesDir }
321
332
  }
333
+ downloadDir.mkdirs()
334
+ sherpaVersionFile.text = currentSherpaVersion
322
335
  println "[sherpa-onnx] Native libs + headers: (3) GitHub release (${tag})"
323
336
  } else {
324
337
  def sherpaClassesJar = file("${project.projectDir.parent}/third_party/sherpa-onnx-prebuilt/android/java/classes.jar")
@@ -11,18 +11,22 @@ def readVersionFromTagFile(File tagFile, String prefix) {
11
11
  return null
12
12
  }
13
13
 
14
+ // Module root: parent of android/ (so tag files resolve when SDK is used as dependency, e.g. node_modules/react-native-sherpa-onnx).
15
+ def moduleRoot = project.projectDir.parentFile
16
+
14
17
  // sherpa-onnx: 1. SHERPA_ONNX_VERSION env 2. ANDROID_RELEASE_TAG 3. property 4. default
15
18
  def sherpaOnnxVersion = System.getenv('SHERPA_ONNX_VERSION')
16
19
  if (!sherpaOnnxVersion) {
17
- def v = readVersionFromTagFile(new File(rootDir, 'third_party/sherpa-onnx-prebuilt/ANDROID_RELEASE_TAG'), 'sherpa-onnx-android-v')
18
- sherpaOnnxVersion = v ?: (project.hasProperty('sherpaOnnxVersion') ? project.sherpaOnnxVersion : '1.12.24')
20
+ def v = readVersionFromTagFile(new File(moduleRoot, 'third_party/sherpa-onnx-prebuilt/ANDROID_RELEASE_TAG'), 'sherpa-onnx-android-v')
21
+ sherpaOnnxVersion = v ?: (project.hasProperty('sherpaOnnxVersion') ? project.sherpaOnnxVersion : '1.12.28')
19
22
  }
20
23
  project.ext.sherpaOnnxVersion = sherpaOnnxVersion
24
+ println "[react-native-sherpa-onnx] sherpa-onnx version (extracted/used): ${sherpaOnnxVersion}"
21
25
 
22
26
  // FFmpeg: 1. FFMPEG_VERSION env 2. ANDROID_RELEASE_TAG 3. property 4. default
23
27
  def ffmpegVersion = System.getenv('FFMPEG_VERSION')
24
28
  if (!ffmpegVersion) {
25
- def v = readVersionFromTagFile(new File(rootDir, 'third_party/ffmpeg_prebuilt/ANDROID_RELEASE_TAG'), 'ffmpeg-android-v')
29
+ def v = readVersionFromTagFile(new File(moduleRoot, 'third_party/ffmpeg_prebuilt/ANDROID_RELEASE_TAG'), 'ffmpeg-android-v')
26
30
  ffmpegVersion = v ?: (project.hasProperty('ffmpegVersion') ? project.ffmpegVersion : '8.0.1')
27
31
  }
28
32
  project.ext.ffmpegVersion = ffmpegVersion
@@ -30,7 +34,7 @@ project.ext.ffmpegVersion = ffmpegVersion
30
34
  // libarchive: 1. LIBARCHIVE_VERSION env 2. ANDROID_RELEASE_TAG 3. property 4. default
31
35
  def libarchiveVersion = System.getenv('LIBARCHIVE_VERSION')
32
36
  if (!libarchiveVersion) {
33
- def v = readVersionFromTagFile(new File(rootDir, 'third_party/libarchive_prebuilt/ANDROID_RELEASE_TAG'), 'libarchive-android-v')
37
+ def v = readVersionFromTagFile(new File(moduleRoot, 'third_party/libarchive_prebuilt/ANDROID_RELEASE_TAG'), 'libarchive-android-v')
34
38
  libarchiveVersion = v ?: (project.hasProperty('libarchiveVersion') ? project.libarchiveVersion : '3.8.5')
35
39
  }
36
40
  project.ext.libarchiveVersion = libarchiveVersion
@@ -35,8 +35,8 @@ bool ContainsToken(const std::string& value, const std::string& token) {
35
35
  return value.find(token) != std::string::npos;
36
36
  }
37
37
 
38
- bool IsOnnxFile(const FileEntry& entry) {
39
- return EndsWith(entry.nameLower, ".onnx");
38
+ bool IsOnnxOrOrtFile(const FileEntry& entry) {
39
+ return EndsWith(entry.nameLower, ".onnx") || EndsWith(entry.nameLower, ".ort");
40
40
  }
41
41
 
42
42
  std::string BaseName(const std::string& path) {
@@ -55,7 +55,7 @@ std::string ChooseLargest(
55
55
  std::uint64_t bestSize = 0;
56
56
 
57
57
  for (const auto& entry : files) {
58
- if (!IsOnnxFile(entry)) continue;
58
+ if (!IsOnnxOrOrtFile(entry)) continue;
59
59
 
60
60
  bool hasExcluded = false;
61
61
  for (const auto& token : excludeTokens) {
@@ -212,9 +212,8 @@ std::string ToLower(std::string value) {
212
212
  return value;
213
213
  }
214
214
 
215
- std::string FindFileByName(const std::string& baseDir, const std::string& fileName, int maxDepth) {
215
+ std::string FindFileByName(const std::vector<FileEntry>& files, const std::string& fileName) {
216
216
  std::string target = ToLower(fileName);
217
- auto files = ListFilesRecursive(baseDir, maxDepth);
218
217
  for (const auto& entry : files) {
219
218
  if (entry.nameLower == target) {
220
219
  return entry.path;
@@ -223,149 +222,17 @@ std::string FindFileByName(const std::string& baseDir, const std::string& fileNa
223
222
  return "";
224
223
  }
225
224
 
226
- std::string FindFileEndingWith(const std::string& baseDir, const std::string& suffix, int maxDepth) {
225
+ std::string FindFileEndingWith(const std::vector<FileEntry>& files, const std::string& suffix) {
227
226
  std::string targetSuffix = ToLower(suffix);
228
- auto files = ListFilesRecursive(baseDir, maxDepth);
229
- // 1) exact match (e.g. "tokens.txt")
230
227
  for (const auto& entry : files) {
231
- if (entry.nameLower == targetSuffix) {
232
- return entry.path;
233
- }
228
+ if (entry.nameLower == targetSuffix) return entry.path;
234
229
  }
235
-
236
- // 2) true suffix match (preferred over substring to avoid false positives
237
- // like "tokens.txt.bak" or "mytokens.txt.tmp").
238
230
  for (const auto& entry : files) {
239
- if (EndsWith(entry.nameLower, targetSuffix)) {
231
+ if (targetSuffix.size() <= entry.nameLower.size() &&
232
+ std::equal(targetSuffix.rbegin(), targetSuffix.rend(), entry.nameLower.rbegin())) {
240
233
  return entry.path;
241
234
  }
242
235
  }
243
-
244
- // 3) If we are looking for tokens, fallback to inspecting .txt files' contents.
245
- // Heuristic: many token files are plain text with lines like "token <index>".
246
- if (targetSuffix.find("tokens") != std::string::npos) {
247
- auto IsLikelyTokensFile = [](const std::string& path) -> bool {
248
- std::ifstream ifs(path);
249
- if (!ifs.is_open()) return false;
250
- std::string line;
251
- int total = 0;
252
- int matched = 0;
253
- const int maxLines = 2000;
254
-
255
- while (total < maxLines && std::getline(ifs, line)) {
256
- ++total;
257
- if (line.empty()) continue;
258
- // Trim trailing CR if present
259
- if (!line.empty() && line.back() == '\r') line.pop_back();
260
-
261
- // Check if the line ends with an integer index (common token format)
262
- size_t sp = line.find_last_of(" \t");
263
- if (sp != std::string::npos && sp + 1 < line.size()) {
264
- std::string idx = line.substr(sp + 1);
265
- bool allDigits = !idx.empty();
266
- for (char c : idx) {
267
- if (!std::isdigit(static_cast<unsigned char>(c))) { allDigits = false; break; }
268
- }
269
- if (allDigits) ++matched;
270
- }
271
- }
272
-
273
- ifs.close();
274
- if (total < 2) return false;
275
- // Heuristic: at least half of non-empty lines should match the token pattern
276
- return matched >= std::max(1, total / 2);
277
- };
278
-
279
- for (const auto& entry : files) {
280
- if (EndsWith(entry.nameLower, ".txt")) {
281
- if (IsLikelyTokensFile(entry.path)) {
282
- return entry.path;
283
- }
284
- }
285
- }
286
- }
287
- return "";
288
- }
289
-
290
- std::string FindDirectoryByName(const std::string& baseDir, const std::string& dirName, int maxDepth) {
291
- std::string target = ToLower(dirName);
292
- std::vector<std::string> toVisit = ListDirectories(baseDir);
293
- int depth = 0;
294
-
295
- while (!toVisit.empty() && depth <= maxDepth) {
296
- std::vector<std::string> next;
297
- for (const auto& dir : toVisit) {
298
- std::string name = dir;
299
- #if __cplusplus >= 201703L && __has_include(<filesystem>)
300
- try {
301
- name = fs::path(dir).filename().string();
302
- } catch (const std::exception&) {
303
- }
304
- #elif __has_include(<experimental/filesystem>)
305
- try {
306
- name = fs::path(dir).filename().string();
307
- } catch (const std::exception&) {
308
- }
309
- #else
310
- name = BaseName(dir);
311
- #endif
312
- if (ToLower(name) == target) {
313
- return dir;
314
- }
315
- if (depth < maxDepth) {
316
- auto nested = ListDirectories(dir);
317
- next.insert(next.end(), nested.begin(), nested.end());
318
- }
319
- }
320
- toVisit.swap(next);
321
- depth += 1;
322
- }
323
-
324
- return "";
325
- }
326
-
327
- std::string ResolveTokenizerDir(const std::string& modelDir) {
328
- std::string vocabInMain = modelDir + "/vocab.json";
329
- if (FileExists(vocabInMain)) {
330
- return modelDir;
331
- }
332
-
333
- std::vector<std::string> toVisit = ListDirectories(modelDir);
334
- int depth = 0;
335
- while (!toVisit.empty() && depth <= 2) {
336
- std::vector<std::string> next;
337
- for (const auto& dir : toVisit) {
338
- std::string dirName = dir;
339
- #if __cplusplus >= 201703L && __has_include(<filesystem>)
340
- try {
341
- dirName = fs::path(dir).filename().string();
342
- } catch (const std::exception&) {
343
- }
344
- #elif __has_include(<experimental/filesystem>)
345
- try {
346
- dirName = fs::path(dir).filename().string();
347
- } catch (const std::exception&) {
348
- }
349
- #else
350
- dirName = BaseName(dir);
351
- #endif
352
- std::string dirNameLower = ToLower(dirName);
353
- if (dirNameLower.find("qwen3") != std::string::npos) {
354
- std::string vocabPath = dir + "/vocab.json";
355
- if (FileExists(vocabPath)) {
356
- return dir;
357
- }
358
- }
359
-
360
- if (depth < 2) {
361
- auto nested = ListDirectories(dir);
362
- next.insert(next.end(), nested.begin(), nested.end());
363
- }
364
- }
365
- toVisit.swap(next);
366
- depth += 1;
367
- }
368
-
369
236
  return "";
370
237
  }
371
238
 
@@ -377,7 +244,7 @@ std::string FindOnnxByToken(
377
244
  std::vector<FileEntry> matches;
378
245
  std::string tokenLower = ToLower(token);
379
246
  for (const auto& entry : files) {
380
- if (!IsOnnxFile(entry)) continue;
247
+ if (!IsOnnxOrOrtFile(entry)) continue;
381
248
  if (ContainsToken(entry.nameLower, tokenLower)) {
382
249
  matches.push_back(entry);
383
250
  }
@@ -407,6 +274,40 @@ std::string FindOnnxByAnyToken(
407
274
  return "";
408
275
  }
409
276
 
277
+ std::string FindOnnxByAnyTokenExcluding(
278
+ const std::vector<FileEntry>& files,
279
+ const std::vector<std::string>& tokens,
280
+ const std::vector<std::string>& excludeInName,
281
+ const std::optional<bool>& preferInt8
282
+ ) {
283
+ for (const auto& token : tokens) {
284
+ std::string tokenLower = ToLower(token);
285
+ std::vector<FileEntry> matches;
286
+ for (const auto& entry : files) {
287
+ if (!IsOnnxOrOrtFile(entry)) continue;
288
+ if (!ContainsToken(entry.nameLower, tokenLower)) continue;
289
+ bool excluded = false;
290
+ for (const auto& ex : excludeInName) {
291
+ std::string exLower = ToLower(ex);
292
+ if (ContainsToken(entry.nameLower, exLower)) {
293
+ excluded = true;
294
+ break;
295
+ }
296
+ }
297
+ if (!excluded) matches.push_back(entry);
298
+ }
299
+ if (matches.empty()) continue;
300
+ std::vector<std::string> emptyTokens;
301
+ bool wantInt8 = preferInt8.has_value() && preferInt8.value();
302
+ bool wantNonInt8 = preferInt8.has_value() && !preferInt8.value();
303
+ std::string chosen = ChooseLargest(matches, emptyTokens, wantInt8, wantNonInt8);
304
+ if (!chosen.empty()) return chosen;
305
+ chosen = ChooseLargest(matches, emptyTokens, false, false);
306
+ if (!chosen.empty()) return chosen;
307
+ }
308
+ return "";
309
+ }
310
+
410
311
  std::string FindLargestOnnx(const std::vector<FileEntry>& files) {
411
312
  std::vector<std::string> emptyTokens;
412
313
  return ChooseLargest(files, emptyTokens, false, false);
@@ -22,11 +22,11 @@ std::vector<std::string> ListDirectories(const std::string& path);
22
22
  std::vector<FileEntry> ListFiles(const std::string& path);
23
23
  std::vector<FileEntry> ListFilesRecursive(const std::string& path, int maxDepth = 2);
24
24
  std::string ToLower(std::string value);
25
- std::string ResolveTokenizerDir(const std::string& modelDir);
26
25
 
27
- std::string FindFileByName(const std::string& baseDir, const std::string& fileName, int maxDepth = 2);
28
- std::string FindFileEndingWith(const std::string& baseDir, const std::string& suffix, int maxDepth = 2);
29
- std::string FindDirectoryByName(const std::string& baseDir, const std::string& dirName, int maxDepth = 2);
26
+ /** Find file in \p files whose name equals \p fileName (case-insensitive). Uses file tree only, no filesystem. */
27
+ std::string FindFileByName(const std::vector<FileEntry>& files, const std::string& fileName);
28
+ /** Find file in \p files whose name equals or ends with \p suffix (e.g. tokens.txt). Case-insensitive. */
29
+ std::string FindFileEndingWith(const std::vector<FileEntry>& files, const std::string& suffix);
30
30
 
31
31
  std::string FindOnnxByToken(
32
32
  const std::vector<FileEntry>& files,
@@ -40,6 +40,14 @@ std::string FindOnnxByAnyToken(
40
40
  const std::optional<bool>& preferInt8
41
41
  );
42
42
 
43
+ /** Like FindOnnxByAnyToken but skips any file whose nameLower contains any of \p excludeInName. */
44
+ std::string FindOnnxByAnyTokenExcluding(
45
+ const std::vector<FileEntry>& files,
46
+ const std::vector<std::string>& tokens,
47
+ const std::vector<std::string>& excludeInName,
48
+ const std::optional<bool>& preferInt8
49
+ );
50
+
43
51
  std::string FindLargestOnnx(
44
52
  const std::vector<FileEntry>& files
45
53
  );