react-native-sherpa-onnx 0.3.5 → 0.3.7

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 (232) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +90 -21
  3. package/SherpaOnnx.podspec +3 -0
  4. package/THIRD_PARTY_LICENSES/README.md +62 -0
  5. package/THIRD_PARTY_LICENSES/ffmpeg.txt +502 -0
  6. package/THIRD_PARTY_LICENSES/libarchive.txt +65 -0
  7. package/THIRD_PARTY_LICENSES/nvidia_omla.txt +181 -0
  8. package/THIRD_PARTY_LICENSES/onnxruntime.txt +21 -0
  9. package/THIRD_PARTY_LICENSES/opus.txt +44 -0
  10. package/THIRD_PARTY_LICENSES/sherpa-onnx.txt +201 -0
  11. package/THIRD_PARTY_LICENSES/shine.txt +482 -0
  12. package/THIRD_PARTY_LICENSES/zstd.txt +30 -0
  13. package/android/build.gradle +7 -3
  14. package/android/prebuilt-download.gradle +345 -153
  15. package/android/prebuilt-versions.gradle +2 -2
  16. package/android/src/main/assets/model_licenses/asr-models-license-status.csv +409 -0
  17. package/android/src/main/assets/model_licenses/qnn-asr-models-license-status.csv +695 -0
  18. package/android/src/main/assets/model_licenses/tts-models-license-status.csv +596 -0
  19. package/android/src/main/cpp/CMakeLists.txt +28 -10
  20. package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-helper.cpp +306 -6
  21. package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-helper.h +33 -4
  22. package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-jni.cpp +266 -7
  23. package/android/src/main/cpp/jni/audio/sherpa-onnx-audio-convert-jni.cpp +268 -2
  24. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-tts.cpp +6 -2
  25. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-validate-tts.cpp +4 -2
  26. package/android/src/main/java/com/sherpaonnx/SherpaOnnxArchiveHelper.kt +137 -7
  27. package/android/src/main/java/com/sherpaonnx/SherpaOnnxAssetHelper.kt +51 -6
  28. package/android/src/main/java/com/sherpaonnx/SherpaOnnxModule.kt +159 -0
  29. package/android/src/main/java/com/sherpaonnx/SherpaOnnxOnlineSttHelper.kt +4 -1
  30. package/android/src/main/java/com/sherpaonnx/SherpaOnnxTtsHelper.kt +112 -97
  31. package/ios/Resources/model_licenses/asr-models-license-status.csv +409 -0
  32. package/ios/Resources/model_licenses/qnn-asr-models-license-status.csv +695 -0
  33. package/ios/Resources/model_licenses/tts-models-license-status.csv +596 -0
  34. package/ios/SherpaOnnx+OnlineSTT.mm +2 -0
  35. package/ios/SherpaOnnx+PcmLiveStream.mm +2 -29
  36. package/ios/SherpaOnnx+TTS.mm +178 -20
  37. package/ios/SherpaOnnx.mm +108 -1
  38. package/ios/SherpaOnnxAudioConvert.h +10 -0
  39. package/ios/SherpaOnnxAudioConvert.mm +257 -1
  40. package/ios/archive/sherpa-onnx-archive-helper.h +10 -0
  41. package/ios/archive/sherpa-onnx-archive-helper.mm +56 -5
  42. package/ios/model_detect/sherpa-onnx-model-detect-tts.mm +13 -2
  43. package/ios/model_detect/sherpa-onnx-validate-tts.mm +4 -2
  44. package/ios/online_stt/sherpa-onnx-online-stt-wrapper.h +1 -0
  45. package/ios/online_stt/sherpa-onnx-online-stt-wrapper.mm +4 -0
  46. package/ios/tts/sherpa-onnx-tts-wrapper.h +37 -0
  47. package/ios/tts/sherpa-onnx-tts-wrapper.mm +149 -3
  48. package/lib/module/NativeSherpaOnnx.js.map +1 -1
  49. package/lib/module/audio/index.js +8 -0
  50. package/lib/module/audio/index.js.map +1 -1
  51. package/lib/module/download/ModelDownloadManager.js +10 -929
  52. package/lib/module/download/ModelDownloadManager.js.map +1 -1
  53. package/lib/module/download/activeModelOperations.js +26 -0
  54. package/lib/module/download/activeModelOperations.js.map +1 -0
  55. package/lib/module/download/background-downloader.d.js +2 -0
  56. package/lib/module/download/background-downloader.d.js.map +1 -0
  57. package/lib/module/download/bulkPurge.js +72 -0
  58. package/lib/module/download/bulkPurge.js.map +1 -0
  59. package/lib/module/download/checksumPrompt.js +19 -0
  60. package/lib/module/download/checksumPrompt.js.map +1 -0
  61. package/lib/module/download/constants.js +7 -0
  62. package/lib/module/download/constants.js.map +1 -0
  63. package/lib/module/download/downloadEvents.js +35 -0
  64. package/lib/module/download/downloadEvents.js.map +1 -0
  65. package/lib/module/download/downloadTask.js +385 -0
  66. package/lib/module/download/downloadTask.js.map +1 -0
  67. package/lib/module/download/ensureModel.js +89 -0
  68. package/lib/module/download/ensureModel.js.map +1 -0
  69. package/lib/module/download/index.js +4 -3
  70. package/lib/module/download/index.js.map +1 -1
  71. package/lib/module/download/localModels.js +151 -0
  72. package/lib/module/download/localModels.js.map +1 -0
  73. package/lib/module/download/modelExtraction.js +174 -0
  74. package/lib/module/download/modelExtraction.js.map +1 -0
  75. package/lib/module/download/paths.js +98 -0
  76. package/lib/module/download/paths.js.map +1 -0
  77. package/lib/module/download/postDownloadProcessing.js +206 -0
  78. package/lib/module/download/postDownloadProcessing.js.map +1 -0
  79. package/lib/module/download/protectedModelKeys.js +31 -0
  80. package/lib/module/download/protectedModelKeys.js.map +1 -0
  81. package/lib/module/download/registry.js +267 -0
  82. package/lib/module/download/registry.js.map +1 -0
  83. package/lib/module/download/retry.js +59 -0
  84. package/lib/module/download/retry.js.map +1 -0
  85. package/lib/module/download/types.js +17 -0
  86. package/lib/module/download/types.js.map +1 -0
  87. package/lib/module/download/validation.js +101 -5
  88. package/lib/module/download/validation.js.map +1 -1
  89. package/lib/module/{download → extraction}/extractTarBz2.js +3 -1
  90. package/lib/module/extraction/extractTarBz2.js.map +1 -0
  91. package/lib/module/extraction/extractTarZst.js +54 -0
  92. package/lib/module/extraction/extractTarZst.js.map +1 -0
  93. package/lib/module/extraction/index.js +190 -0
  94. package/lib/module/extraction/index.js.map +1 -0
  95. package/lib/module/extraction/types.js +2 -0
  96. package/lib/module/extraction/types.js.map +1 -0
  97. package/lib/module/index.js +2 -1
  98. package/lib/module/index.js.map +1 -1
  99. package/lib/module/licenses.js +63 -0
  100. package/lib/module/licenses.js.map +1 -0
  101. package/lib/module/stt/index.js +16 -2
  102. package/lib/module/stt/index.js.map +1 -1
  103. package/lib/module/stt/streaming.js +2 -0
  104. package/lib/module/stt/streaming.js.map +1 -1
  105. package/lib/module/stt/streamingTypes.js.map +1 -1
  106. package/lib/module/stt/types.js.map +1 -1
  107. package/lib/module/tts/index.js +20 -2
  108. package/lib/module/tts/index.js.map +1 -1
  109. package/lib/module/tts/streaming.js +4 -0
  110. package/lib/module/tts/streaming.js.map +1 -1
  111. package/lib/module/tts/types.js.map +1 -1
  112. package/lib/module/utils.js +16 -1
  113. package/lib/module/utils.js.map +1 -1
  114. package/lib/typescript/src/NativeSherpaOnnx.d.ts +72 -5
  115. package/lib/typescript/src/NativeSherpaOnnx.d.ts.map +1 -1
  116. package/lib/typescript/src/audio/index.d.ts +10 -0
  117. package/lib/typescript/src/audio/index.d.ts.map +1 -1
  118. package/lib/typescript/src/download/ModelDownloadManager.d.ts +10 -108
  119. package/lib/typescript/src/download/ModelDownloadManager.d.ts.map +1 -1
  120. package/lib/typescript/src/download/activeModelOperations.d.ts +6 -0
  121. package/lib/typescript/src/download/activeModelOperations.d.ts.map +1 -0
  122. package/lib/typescript/src/download/bulkPurge.d.ts +14 -0
  123. package/lib/typescript/src/download/bulkPurge.d.ts.map +1 -0
  124. package/lib/typescript/src/download/checksumPrompt.d.ts +3 -0
  125. package/lib/typescript/src/download/checksumPrompt.d.ts.map +1 -0
  126. package/lib/typescript/src/download/constants.d.ts +5 -0
  127. package/lib/typescript/src/download/constants.d.ts.map +1 -0
  128. package/lib/typescript/src/download/downloadEvents.d.ts +6 -0
  129. package/lib/typescript/src/download/downloadEvents.d.ts.map +1 -0
  130. package/lib/typescript/src/download/downloadTask.d.ts +20 -0
  131. package/lib/typescript/src/download/downloadTask.d.ts.map +1 -0
  132. package/lib/typescript/src/download/ensureModel.d.ts +26 -0
  133. package/lib/typescript/src/download/ensureModel.d.ts.map +1 -0
  134. package/lib/typescript/src/download/index.d.ts +7 -5
  135. package/lib/typescript/src/download/index.d.ts.map +1 -1
  136. package/lib/typescript/src/download/localModels.d.ts +15 -0
  137. package/lib/typescript/src/download/localModels.d.ts.map +1 -0
  138. package/lib/typescript/src/download/modelExtraction.d.ts +36 -0
  139. package/lib/typescript/src/download/modelExtraction.d.ts.map +1 -0
  140. package/lib/typescript/src/download/paths.d.ts +28 -0
  141. package/lib/typescript/src/download/paths.d.ts.map +1 -0
  142. package/lib/typescript/src/download/postDownloadProcessing.d.ts +19 -0
  143. package/lib/typescript/src/download/postDownloadProcessing.d.ts.map +1 -0
  144. package/lib/typescript/src/download/protectedModelKeys.d.ts +6 -0
  145. package/lib/typescript/src/download/protectedModelKeys.d.ts.map +1 -0
  146. package/lib/typescript/src/download/registry.d.ts +14 -0
  147. package/lib/typescript/src/download/registry.d.ts.map +1 -0
  148. package/lib/typescript/src/download/retry.d.ts +15 -0
  149. package/lib/typescript/src/download/retry.d.ts.map +1 -0
  150. package/lib/typescript/src/download/types.d.ts +96 -0
  151. package/lib/typescript/src/download/types.d.ts.map +1 -0
  152. package/lib/typescript/src/download/validation.d.ts +19 -0
  153. package/lib/typescript/src/download/validation.d.ts.map +1 -1
  154. package/lib/typescript/src/extraction/extractTarBz2.d.ts.map +1 -0
  155. package/lib/typescript/src/extraction/extractTarZst.d.ts +14 -0
  156. package/lib/typescript/src/extraction/extractTarZst.d.ts.map +1 -0
  157. package/lib/typescript/src/extraction/index.d.ts +50 -0
  158. package/lib/typescript/src/extraction/index.d.ts.map +1 -0
  159. package/lib/typescript/src/extraction/types.d.ts +60 -0
  160. package/lib/typescript/src/extraction/types.d.ts.map +1 -0
  161. package/lib/typescript/src/index.d.ts +1 -0
  162. package/lib/typescript/src/index.d.ts.map +1 -1
  163. package/lib/typescript/src/licenses.d.ts +10 -0
  164. package/lib/typescript/src/licenses.d.ts.map +1 -0
  165. package/lib/typescript/src/stt/index.d.ts +4 -1
  166. package/lib/typescript/src/stt/index.d.ts.map +1 -1
  167. package/lib/typescript/src/stt/streaming.d.ts.map +1 -1
  168. package/lib/typescript/src/stt/streamingTypes.d.ts +5 -0
  169. package/lib/typescript/src/stt/streamingTypes.d.ts.map +1 -1
  170. package/lib/typescript/src/stt/types.d.ts +3 -1
  171. package/lib/typescript/src/stt/types.d.ts.map +1 -1
  172. package/lib/typescript/src/tts/index.d.ts +3 -1
  173. package/lib/typescript/src/tts/index.d.ts.map +1 -1
  174. package/lib/typescript/src/tts/streaming.d.ts.map +1 -1
  175. package/lib/typescript/src/tts/types.d.ts +6 -5
  176. package/lib/typescript/src/tts/types.d.ts.map +1 -1
  177. package/lib/typescript/src/utils.d.ts +5 -0
  178. package/lib/typescript/src/utils.d.ts.map +1 -1
  179. package/package.json +11 -1
  180. package/scripts/{check-model-csvs.sh → ci/check-model-csvs.sh} +9 -2
  181. package/scripts/ci/collect_all_sherpa_model_streams.sh +101 -0
  182. package/scripts/ci/collect_one_sherpa_release_stream.sh +189 -0
  183. package/scripts/ci/sherpa_asr_model_release_streams.json +21 -0
  184. package/scripts/ci/sherpa_tts_model_release_streams.json +13 -0
  185. package/scripts/ci/update_model_license_csv.sh +765 -0
  186. package/scripts/setup-ios-framework.sh +14 -11
  187. package/scripts/update_commercial_use.js +73 -0
  188. package/src/NativeSherpaOnnx.ts +92 -5
  189. package/src/audio/index.ts +20 -0
  190. package/src/download/ModelDownloadManager.ts +55 -1343
  191. package/src/download/activeModelOperations.ts +38 -0
  192. package/src/download/background-downloader.d.ts +43 -0
  193. package/src/download/bulkPurge.ts +102 -0
  194. package/src/download/checksumPrompt.ts +25 -0
  195. package/src/download/constants.ts +5 -0
  196. package/src/download/downloadEvents.ts +55 -0
  197. package/src/download/downloadTask.ts +497 -0
  198. package/src/download/ensureModel.ts +124 -0
  199. package/src/download/index.ts +19 -2
  200. package/src/download/localModels.ts +234 -0
  201. package/src/download/modelExtraction.ts +244 -0
  202. package/src/download/paths.ts +134 -0
  203. package/src/download/postDownloadProcessing.ts +292 -0
  204. package/src/download/protectedModelKeys.ts +30 -0
  205. package/src/download/registry.ts +404 -0
  206. package/src/download/retry.ts +76 -0
  207. package/src/download/types.ts +120 -0
  208. package/src/download/validation.ts +114 -8
  209. package/src/{download → extraction}/extractTarBz2.ts +3 -1
  210. package/src/extraction/extractTarZst.ts +79 -0
  211. package/src/extraction/index.ts +269 -0
  212. package/src/extraction/types.ts +63 -0
  213. package/src/index.tsx +2 -0
  214. package/src/licenses.ts +100 -0
  215. package/src/stt/index.ts +20 -2
  216. package/src/stt/streaming.ts +3 -0
  217. package/src/stt/streamingTypes.ts +5 -0
  218. package/src/stt/types.ts +3 -1
  219. package/src/tts/index.ts +30 -2
  220. package/src/tts/streaming.ts +10 -0
  221. package/src/tts/types.ts +6 -5
  222. package/src/utils.ts +22 -1
  223. package/third_party/libarchive_prebuilt/ANDROID_RELEASE_TAG +1 -1
  224. package/third_party/libarchive_prebuilt/IOS_RELEASE_TAG +1 -1
  225. package/third_party/sherpa-onnx-prebuilt/ANDROID_RELEASE_TAG +1 -1
  226. package/third_party/sherpa-onnx-prebuilt/IOS_RELEASE_TAG +1 -1
  227. package/android/src/main/cpp/jni/tts/sherpa-onnx-tts-zipvoice-jni.cpp +0 -301
  228. package/android/src/main/java/com/sherpaonnx/ZipvoiceTtsWrapper.kt +0 -187
  229. package/lib/module/download/extractTarBz2.js.map +0 -1
  230. package/lib/typescript/src/download/extractTarBz2.d.ts.map +0 -1
  231. package/scripts/check-qnn-support.sh +0 -78
  232. /package/lib/typescript/src/{download → extraction}/extractTarBz2.d.ts +0 -0
@@ -49,11 +49,9 @@ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeExtractTarBz2(
49
49
  j_progress_callback_global = env->NewGlobalRef(j_progress_callback);
50
50
  }
51
51
 
52
- // Get Promise.resolve and reject methods
52
+ // Get Promise.resolve method
53
53
  jclass promise_class = env->GetObjectClass(j_promise);
54
54
  jmethodID resolve_method = env->GetMethodID(promise_class, "resolve", "(Ljava/lang/Object;)V");
55
- jmethodID reject_method = env->GetMethodID(promise_class, "reject",
56
- "(Ljava/lang/String;Ljava/lang/String;)V");
57
55
 
58
56
  // Get WritableMap from Arguments
59
57
  jclass arguments_class = env->FindClass("com/facebook/react/bridge/Arguments");
@@ -123,15 +121,12 @@ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeExtractTarBz2(
123
121
  env->CallVoidMethod(result_map, put_string_method,
124
122
  env->NewStringUTF("sha256"), env->NewStringUTF(sha256.c_str()));
125
123
  }
126
- env->CallVoidMethod(j_promise, resolve_method, result_map);
127
124
  } else {
128
125
  __android_log_print(ANDROID_LOG_WARN, "SherpaOnnxNative", "[ARCHIVE_ERROR] %s", error_msg.c_str());
129
126
  env->CallVoidMethod(result_map, put_string_method,
130
127
  env->NewStringUTF("reason"), env->NewStringUTF(error_msg.c_str()));
131
- env->CallVoidMethod(j_promise, reject_method,
132
- env->NewStringUTF("ARCHIVE_ERROR"),
133
- env->NewStringUTF(error_msg.c_str()));
134
128
  }
129
+ env->CallVoidMethod(j_promise, resolve_method, result_map);
135
130
 
136
131
  // Clean up global reference
137
132
  if (j_progress_callback_global != nullptr) {
@@ -144,6 +139,270 @@ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeExtractTarBz2(
144
139
  env->DeleteLocalRef(writeable_map_class);
145
140
  }
146
141
 
142
+ extern "C" JNIEXPORT void JNICALL
143
+ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeExtractTarZst(
144
+ JNIEnv* env,
145
+ jobject /* jthis */,
146
+ jstring j_source_path,
147
+ jstring j_target_path,
148
+ jboolean j_force,
149
+ jobject j_progress_callback,
150
+ jobject j_promise) {
151
+ const char* source_path = env->GetStringUTFChars(j_source_path, nullptr);
152
+ const char* target_path = env->GetStringUTFChars(j_target_path, nullptr);
153
+ std::string source_str(source_path);
154
+ std::string target_str(target_path);
155
+ env->ReleaseStringUTFChars(j_source_path, source_path);
156
+ env->ReleaseStringUTFChars(j_target_path, target_path);
157
+
158
+ jmethodID on_progress_method = nullptr;
159
+ jobject j_progress_callback_global = nullptr;
160
+ if (j_progress_callback != nullptr) {
161
+ jclass callback_class = env->GetObjectClass(j_progress_callback);
162
+ on_progress_method = env->GetMethodID(callback_class, "invoke", "(JJD)V");
163
+ env->DeleteLocalRef(callback_class);
164
+ j_progress_callback_global = env->NewGlobalRef(j_progress_callback);
165
+ }
166
+
167
+ jclass promise_class = env->GetObjectClass(j_promise);
168
+ jmethodID resolve_method = env->GetMethodID(promise_class, "resolve", "(Ljava/lang/Object;)V");
169
+
170
+ jclass arguments_class = env->FindClass("com/facebook/react/bridge/Arguments");
171
+ jmethodID create_map_method = env->GetStaticMethodID(
172
+ arguments_class, "createMap", "()Lcom/facebook/react/bridge/WritableMap;");
173
+ jobject result_map = env->CallStaticObjectMethod(arguments_class, create_map_method);
174
+
175
+ jclass writeable_map_class = env->FindClass("com/facebook/react/bridge/WritableMap");
176
+ jmethodID put_boolean_method = env->GetMethodID(
177
+ writeable_map_class, "putBoolean", "(Ljava/lang/String;Z)V");
178
+ jmethodID put_string_method = env->GetMethodID(
179
+ writeable_map_class, "putString", "(Ljava/lang/String;Ljava/lang/String;)V");
180
+
181
+ auto on_progress = [j_progress_callback_global, on_progress_method](
182
+ long long bytes_extracted, long long total_bytes, double percent) {
183
+ if (j_progress_callback_global != nullptr && on_progress_method != nullptr) {
184
+ JNIEnv* callback_env = nullptr;
185
+ bool should_detach = false;
186
+ if (g_vm->GetEnv(reinterpret_cast<void**>(&callback_env), JNI_VERSION_1_6) == JNI_EDETACHED) {
187
+ if (g_vm->AttachCurrentThread(&callback_env, nullptr) == JNI_OK) {
188
+ should_detach = true;
189
+ } else {
190
+ return;
191
+ }
192
+ }
193
+ if (callback_env != nullptr) {
194
+ callback_env->CallVoidMethod(j_progress_callback_global, on_progress_method,
195
+ bytes_extracted, total_bytes, percent);
196
+ if (callback_env->ExceptionCheck()) {
197
+ callback_env->ExceptionClear();
198
+ }
199
+ if (should_detach) {
200
+ g_vm->DetachCurrentThread();
201
+ }
202
+ }
203
+ }
204
+ };
205
+
206
+ std::string error_msg;
207
+ std::string sha256;
208
+ bool success = ArchiveHelper::ExtractTarZst(
209
+ source_str,
210
+ target_str,
211
+ j_force == JNI_TRUE,
212
+ on_progress,
213
+ &error_msg,
214
+ &sha256);
215
+
216
+ env->CallVoidMethod(result_map, put_boolean_method,
217
+ env->NewStringUTF("success"), success ? JNI_TRUE : JNI_FALSE);
218
+
219
+ if (success) {
220
+ env->CallVoidMethod(result_map, put_string_method,
221
+ env->NewStringUTF("path"), env->NewStringUTF(target_str.c_str()));
222
+ if (!sha256.empty()) {
223
+ env->CallVoidMethod(result_map, put_string_method,
224
+ env->NewStringUTF("sha256"), env->NewStringUTF(sha256.c_str()));
225
+ }
226
+ } else {
227
+ __android_log_print(ANDROID_LOG_WARN, "SherpaOnnxNative", "[ARCHIVE_ERROR] %s", error_msg.c_str());
228
+ env->CallVoidMethod(result_map, put_string_method,
229
+ env->NewStringUTF("reason"), env->NewStringUTF(error_msg.c_str()));
230
+ }
231
+ env->CallVoidMethod(j_promise, resolve_method, result_map);
232
+
233
+ if (j_progress_callback_global != nullptr) {
234
+ env->DeleteGlobalRef(j_progress_callback_global);
235
+ }
236
+
237
+ env->DeleteLocalRef(result_map);
238
+ env->DeleteLocalRef(promise_class);
239
+ env->DeleteLocalRef(arguments_class);
240
+ env->DeleteLocalRef(writeable_map_class);
241
+ }
242
+
243
+ namespace {
244
+ struct InputStreamReadContext {
245
+ JNIEnv* env = nullptr;
246
+ jobject stream_global = nullptr;
247
+ jmethodID read_method = nullptr;
248
+ jbyteArray byte_array = nullptr;
249
+ const size_t buffer_size = 64 * 1024;
250
+ };
251
+
252
+ static std::ptrdiff_t JniStreamRead(void* buf, size_t len, void* user_data) {
253
+ auto* ctx = static_cast<InputStreamReadContext*>(user_data);
254
+ if (!ctx || !ctx->env || !ctx->stream_global || !ctx->read_method || !ctx->byte_array) {
255
+ return -1;
256
+ }
257
+ size_t to_read = (len < ctx->buffer_size) ? len : ctx->buffer_size;
258
+ jint n = ctx->env->CallIntMethod(ctx->stream_global, ctx->read_method, ctx->byte_array);
259
+ if (ctx->env->ExceptionCheck()) {
260
+ ctx->env->ExceptionClear();
261
+ return -1;
262
+ }
263
+ if (n <= 0) return 0;
264
+ ctx->env->GetByteArrayRegion(ctx->byte_array, 0, n, static_cast<jbyte*>(buf));
265
+ return static_cast<std::ptrdiff_t>(n);
266
+ }
267
+ } // namespace
268
+
269
+ extern "C" JNIEXPORT void JNICALL
270
+ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeExtractTarZstFromStream(
271
+ JNIEnv* env,
272
+ jobject /* jthis */,
273
+ jobject j_input_stream,
274
+ jstring j_target_path,
275
+ jboolean j_force,
276
+ jobject j_progress_callback,
277
+ jobject j_promise) {
278
+ const char* target_path = env->GetStringUTFChars(j_target_path, nullptr);
279
+ std::string target_str(target_path);
280
+ env->ReleaseStringUTFChars(j_target_path, target_path);
281
+
282
+ jobject stream_global = env->NewGlobalRef(j_input_stream);
283
+ jclass stream_class = env->GetObjectClass(j_input_stream);
284
+ jmethodID read_method = env->GetMethodID(stream_class, "read", "([B)I");
285
+ env->DeleteLocalRef(stream_class);
286
+ if (!read_method) {
287
+ env->DeleteGlobalRef(stream_global);
288
+ jclass promise_class = env->GetObjectClass(j_promise);
289
+ jmethodID reject_method = env->GetMethodID(promise_class, "reject", "(Ljava/lang/String;Ljava/lang/String;)V");
290
+ env->CallVoidMethod(j_promise, reject_method,
291
+ env->NewStringUTF("ARCHIVE_ERROR"),
292
+ env->NewStringUTF("InputStream.read([B)I not found"));
293
+ env->DeleteLocalRef(promise_class);
294
+ return;
295
+ }
296
+
297
+ jbyteArray byte_array = env->NewByteArray(static_cast<jsize>(64 * 1024));
298
+ if (!byte_array) {
299
+ env->DeleteGlobalRef(stream_global);
300
+ return;
301
+ }
302
+
303
+ InputStreamReadContext read_ctx;
304
+ read_ctx.env = env;
305
+ read_ctx.stream_global = stream_global;
306
+ read_ctx.read_method = read_method;
307
+ read_ctx.byte_array = byte_array;
308
+
309
+ jmethodID on_progress_method = nullptr;
310
+ jobject j_progress_callback_global = nullptr;
311
+ if (j_progress_callback != nullptr) {
312
+ jclass callback_class = env->GetObjectClass(j_progress_callback);
313
+ on_progress_method = env->GetMethodID(callback_class, "invoke", "(JJD)V");
314
+ env->DeleteLocalRef(callback_class);
315
+ j_progress_callback_global = env->NewGlobalRef(j_progress_callback);
316
+ }
317
+
318
+ jclass promise_class = env->GetObjectClass(j_promise);
319
+ jmethodID resolve_method = env->GetMethodID(promise_class, "resolve", "(Ljava/lang/Object;)V");
320
+
321
+ jclass arguments_class = env->FindClass("com/facebook/react/bridge/Arguments");
322
+ jmethodID create_map_method = env->GetStaticMethodID(arguments_class, "createMap", "()Lcom/facebook/react/bridge/WritableMap;");
323
+ jobject result_map = env->CallStaticObjectMethod(arguments_class, create_map_method);
324
+
325
+ jclass writeable_map_class = env->FindClass("com/facebook/react/bridge/WritableMap");
326
+ jmethodID put_boolean_method = env->GetMethodID(writeable_map_class, "putBoolean", "(Ljava/lang/String;Z)V");
327
+ jmethodID put_string_method = env->GetMethodID(writeable_map_class, "putString", "(Ljava/lang/String;Ljava/lang/String;)V");
328
+
329
+ auto on_progress = [j_progress_callback_global, on_progress_method](
330
+ long long bytes_extracted, long long total_bytes, double percent) {
331
+ if (j_progress_callback_global != nullptr && on_progress_method != nullptr) {
332
+ JNIEnv* callback_env = nullptr;
333
+ bool should_detach = false;
334
+ if (g_vm->GetEnv(reinterpret_cast<void**>(&callback_env), JNI_VERSION_1_6) == JNI_EDETACHED) {
335
+ if (g_vm->AttachCurrentThread(&callback_env, nullptr) == JNI_OK) {
336
+ should_detach = true;
337
+ } else {
338
+ return;
339
+ }
340
+ }
341
+ if (callback_env != nullptr) {
342
+ callback_env->CallVoidMethod(j_progress_callback_global, on_progress_method,
343
+ bytes_extracted, total_bytes, percent);
344
+ if (callback_env->ExceptionCheck()) {
345
+ callback_env->ExceptionClear();
346
+ }
347
+ if (should_detach) {
348
+ g_vm->DetachCurrentThread();
349
+ }
350
+ }
351
+ }
352
+ };
353
+
354
+ std::string error_msg;
355
+ std::string sha256;
356
+ bool success = ArchiveHelper::ExtractFromStream(
357
+ &JniStreamRead,
358
+ &read_ctx,
359
+ target_str,
360
+ j_force == JNI_TRUE,
361
+ on_progress,
362
+ &error_msg,
363
+ &sha256);
364
+
365
+ env->CallVoidMethod(result_map, put_boolean_method,
366
+ env->NewStringUTF("success"), success ? JNI_TRUE : JNI_FALSE);
367
+
368
+ if (success) {
369
+ env->CallVoidMethod(result_map, put_string_method,
370
+ env->NewStringUTF("path"), env->NewStringUTF(target_str.c_str()));
371
+ if (!sha256.empty()) {
372
+ env->CallVoidMethod(result_map, put_string_method,
373
+ env->NewStringUTF("sha256"), env->NewStringUTF(sha256.c_str()));
374
+ }
375
+ } else {
376
+ __android_log_print(ANDROID_LOG_WARN, "SherpaOnnxNative", "[ARCHIVE_ERROR] %s", error_msg.c_str());
377
+ env->CallVoidMethod(result_map, put_string_method,
378
+ env->NewStringUTF("reason"), env->NewStringUTF(error_msg.c_str()));
379
+ }
380
+ env->CallVoidMethod(j_promise, resolve_method, result_map);
381
+
382
+ env->DeleteGlobalRef(stream_global);
383
+ env->DeleteLocalRef(byte_array);
384
+ if (j_progress_callback_global != nullptr) {
385
+ env->DeleteGlobalRef(j_progress_callback_global);
386
+ }
387
+ env->DeleteLocalRef(result_map);
388
+ env->DeleteLocalRef(promise_class);
389
+ env->DeleteLocalRef(arguments_class);
390
+ env->DeleteLocalRef(writeable_map_class);
391
+ }
392
+
393
+ extern "C" JNIEXPORT void JNICALL
394
+ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeExtractTarBz2FromStream(
395
+ JNIEnv* env,
396
+ jobject jthis,
397
+ jobject j_input_stream,
398
+ jstring j_target_path,
399
+ jboolean j_force,
400
+ jobject j_progress_callback,
401
+ jobject j_promise) {
402
+ Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeExtractTarZstFromStream(
403
+ env, jthis, j_input_stream, j_target_path, j_force, j_progress_callback, j_promise);
404
+ }
405
+
147
406
  extern "C" JNIEXPORT void JNICALL
148
407
  Java_com_sherpaonnx_SherpaOnnxArchiveHelper_nativeCancelExtract(JNIEnv* /* env */, jobject /* jthis */) {
149
408
  ArchiveHelper::Cancel();
@@ -9,6 +9,7 @@
9
9
  #include <jni.h>
10
10
  #include <string>
11
11
  #include <sys/stat.h>
12
+ #include <vector>
12
13
 
13
14
  #define LOG_TAG "AudioConvertJNI"
14
15
  #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
@@ -24,11 +25,14 @@ extern "C" {
24
25
  #include <libswresample/swresample.h>
25
26
  }
26
27
  #include <cstdio>
27
- #include <vector>
28
28
  #endif
29
29
 
30
30
  // Forward declaration — convertToFormat handles all formats including WAV (16 kHz mono).
31
31
  static std::string convertToFormat(const char* inputPath, const char* outputPath, const char* formatHint, int outputSampleRateHz);
32
+ static std::string decodeAudioFileToFloatMono(const char* inputPath,
33
+ int targetSampleRateHz,
34
+ std::vector<float>* outSamples,
35
+ int* outSampleRate);
32
36
 
33
37
  // Convenience: convert any audio to 16 kHz mono WAV via the main convertToFormat pipeline.
34
38
  static std::string convertToWav16kMono(const char* inputPath, const char* outputPath) {
@@ -614,7 +618,8 @@ static std::string convertToFormat(const char* inputPath, const char* outputPath
614
618
  av_packet_unref(pkt);
615
619
  continue;
616
620
  }
617
- int converted = swr_convert(swr, outData, (int)out_nb_samples, (const uint8_t**)frame->data, frame->nb_samples);
621
+ const uint8_t* const* in_data = frame->extended_data ? frame->extended_data : frame->data;
622
+ int converted = swr_convert(swr, outData, (int)out_nb_samples, in_data, frame->nb_samples);
618
623
  if (converted <= 0) {
619
624
  av_freep(&outData[0]);
620
625
  av_freep(&outData);
@@ -701,6 +706,204 @@ static std::string convertToFormat(const char* inputPath, const char* outputPath
701
706
  #endif
702
707
  }
703
708
 
709
+ // Decode any FFmpeg-supported audio to mono float PCM in [-1,1] (clipping not applied) at outSampleRate.
710
+ static std::string decodeAudioFileToFloatMono(const char* inputPath,
711
+ int targetSampleRateHz,
712
+ std::vector<float>* outSamples,
713
+ int* outSampleRate) {
714
+ outSamples->clear();
715
+ *outSampleRate = 0;
716
+ #ifndef HAVE_FFMPEG
717
+ (void)inputPath;
718
+ (void)targetSampleRateHz;
719
+ return std::string("FFmpeg not available. Build prebuilts with third_party/ffmpeg_prebuilt/build_ffmpeg.ps1 or build_ffmpeg.sh.");
720
+ #else
721
+ if (!inputPath) {
722
+ return std::string("inputPath is null");
723
+ }
724
+
725
+ AVFormatContext* inFmt = nullptr;
726
+ if (avformat_open_input(&inFmt, inputPath, nullptr, nullptr) < 0) {
727
+ LOGE("decodeAudioFileToFloatMono: failed to open inputPath=%s", inputPath);
728
+ return std::string("Failed to open input file");
729
+ }
730
+ if (avformat_find_stream_info(inFmt, nullptr) < 0) {
731
+ avformat_close_input(&inFmt);
732
+ return std::string("Failed to find stream info");
733
+ }
734
+
735
+ int audioStreamIndex = -1;
736
+ for (unsigned i = 0; i < inFmt->nb_streams; ++i) {
737
+ if (inFmt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
738
+ audioStreamIndex = (int)i;
739
+ break;
740
+ }
741
+ }
742
+ if (audioStreamIndex < 0) {
743
+ avformat_close_input(&inFmt);
744
+ return std::string("No audio stream found in input");
745
+ }
746
+
747
+ AVStream* inStream = inFmt->streams[audioStreamIndex];
748
+ const AVCodec* decoder = avcodec_find_decoder(inStream->codecpar->codec_id);
749
+ if (!decoder) {
750
+ avformat_close_input(&inFmt);
751
+ return std::string("Unsupported input codec");
752
+ }
753
+
754
+ AVCodecContext* decCtx = avcodec_alloc_context3(decoder);
755
+ if (!decCtx) {
756
+ avformat_close_input(&inFmt);
757
+ return std::string("Failed to allocate decoder context");
758
+ }
759
+ if (avcodec_parameters_to_context(decCtx, inStream->codecpar) < 0) {
760
+ avcodec_free_context(&decCtx);
761
+ avformat_close_input(&inFmt);
762
+ return std::string("Failed to copy codec parameters");
763
+ }
764
+ if (avcodec_open2(decCtx, decoder, nullptr) < 0) {
765
+ avcodec_free_context(&decCtx);
766
+ avformat_close_input(&inFmt);
767
+ return std::string("Failed to open decoder");
768
+ }
769
+
770
+ int in_sr = decCtx->sample_rate;
771
+ if (inStream->codecpar->sample_rate > 0) {
772
+ in_sr = inStream->codecpar->sample_rate;
773
+ }
774
+ if (in_sr <= 0) {
775
+ avcodec_free_context(&decCtx);
776
+ avformat_close_input(&inFmt);
777
+ return std::string("Invalid input sample rate");
778
+ }
779
+
780
+ int out_sr = (targetSampleRateHz > 0) ? targetSampleRateHz : in_sr;
781
+ if (out_sr <= 0) {
782
+ avcodec_free_context(&decCtx);
783
+ avformat_close_input(&inFmt);
784
+ return std::string("Invalid output sample rate");
785
+ }
786
+
787
+ AVChannelLayout in_layout{};
788
+ if (inStream->codecpar->ch_layout.nb_channels > 0) {
789
+ if (av_channel_layout_copy(&in_layout, &inStream->codecpar->ch_layout) < 0) {
790
+ avcodec_free_context(&decCtx);
791
+ avformat_close_input(&inFmt);
792
+ return std::string("Failed to copy input channel layout");
793
+ }
794
+ } else {
795
+ if (av_channel_layout_copy(&in_layout, &decCtx->ch_layout) < 0) {
796
+ avcodec_free_context(&decCtx);
797
+ avformat_close_input(&inFmt);
798
+ return std::string("Failed to get decoder channel layout");
799
+ }
800
+ }
801
+
802
+ AVChannelLayout out_layout = AV_CHANNEL_LAYOUT_MONO;
803
+ SwrContext* swr = nullptr;
804
+ if (swr_alloc_set_opts2(&swr,
805
+ &out_layout,
806
+ AV_SAMPLE_FMT_FLT,
807
+ out_sr,
808
+ &in_layout,
809
+ decCtx->sample_fmt,
810
+ in_sr,
811
+ 0,
812
+ nullptr) < 0 ||
813
+ !swr) {
814
+ av_channel_layout_uninit(&in_layout);
815
+ avcodec_free_context(&decCtx);
816
+ avformat_close_input(&inFmt);
817
+ return std::string("Failed to initialize resampler");
818
+ }
819
+ if (swr_init(swr) < 0) {
820
+ av_channel_layout_uninit(&in_layout);
821
+ swr_free(&swr);
822
+ avcodec_free_context(&decCtx);
823
+ avformat_close_input(&inFmt);
824
+ return std::string("Failed to initialize resampler (swr_init)");
825
+ }
826
+ av_channel_layout_uninit(&in_layout);
827
+
828
+ AVPacket* pkt = av_packet_alloc();
829
+ AVFrame* frame = av_frame_alloc();
830
+ if (!pkt || !frame) {
831
+ if (pkt) av_packet_free(&pkt);
832
+ if (frame) av_frame_free(&frame);
833
+ swr_free(&swr);
834
+ avcodec_free_context(&decCtx);
835
+ avformat_close_input(&inFmt);
836
+ return std::string("Out of memory");
837
+ }
838
+
839
+ auto appendConverted = [&](uint8_t* buf, int nbFloats) {
840
+ if (!buf || nbFloats <= 0) return;
841
+ const float* f = reinterpret_cast<const float*>(buf);
842
+ outSamples->insert(outSamples->end(), f, f + nbFloats);
843
+ };
844
+
845
+ auto convertOneFrame = [&](AVFrame* fr) {
846
+ const uint8_t* const* in_data = fr->extended_data ? fr->extended_data : fr->data;
847
+ int in_sr2 = inStream->codecpar->sample_rate ? inStream->codecpar->sample_rate : decCtx->sample_rate;
848
+ int64_t max_out =
849
+ av_rescale_rnd(swr_get_delay(swr, in_sr2) + (int64_t)fr->nb_samples, out_sr, in_sr2, AV_ROUND_UP);
850
+ if (max_out < 1) max_out = 1;
851
+ uint8_t* out_buf = nullptr;
852
+ if (av_samples_alloc(&out_buf, nullptr, 1, (int)max_out, AV_SAMPLE_FMT_FLT, 0) < 0) {
853
+ return;
854
+ }
855
+ int converted = swr_convert(swr, &out_buf, (int)max_out, in_data, fr->nb_samples);
856
+ if (converted > 0) {
857
+ appendConverted(out_buf, converted);
858
+ }
859
+ av_freep(&out_buf);
860
+ };
861
+
862
+ while (av_read_frame(inFmt, pkt) >= 0) {
863
+ if (pkt->stream_index == audioStreamIndex) {
864
+ if (avcodec_send_packet(decCtx, pkt) == 0) {
865
+ while (avcodec_receive_frame(decCtx, frame) == 0) {
866
+ convertOneFrame(frame);
867
+ av_frame_unref(frame);
868
+ }
869
+ }
870
+ }
871
+ av_packet_unref(pkt);
872
+ }
873
+
874
+ if (avcodec_send_packet(decCtx, nullptr) == 0) {
875
+ while (avcodec_receive_frame(decCtx, frame) == 0) {
876
+ convertOneFrame(frame);
877
+ av_frame_unref(frame);
878
+ }
879
+ }
880
+
881
+ {
882
+ int in_sr2 = inStream->codecpar->sample_rate ? inStream->codecpar->sample_rate : decCtx->sample_rate;
883
+ int tailCap = (int)swr_get_delay(swr, in_sr2) + 4096;
884
+ if (tailCap < 16) tailCap = 16;
885
+ uint8_t* tailData = nullptr;
886
+ if (av_samples_alloc(&tailData, nullptr, 1, tailCap, AV_SAMPLE_FMT_FLT, 0) >= 0) {
887
+ int tailConverted = swr_convert(swr, &tailData, tailCap, nullptr, 0);
888
+ if (tailConverted > 0) {
889
+ appendConverted(tailData, tailConverted);
890
+ }
891
+ av_freep(&tailData);
892
+ }
893
+ }
894
+
895
+ av_packet_free(&pkt);
896
+ av_frame_free(&frame);
897
+ swr_free(&swr);
898
+ avcodec_free_context(&decCtx);
899
+ avformat_close_input(&inFmt);
900
+
901
+ *outSampleRate = out_sr;
902
+ LOGI("decodeAudioFileToFloatMono: samples=%zu sampleRate=%d", outSamples->size(), out_sr);
903
+ return std::string("");
904
+ #endif
905
+ }
906
+
704
907
  extern "C" {
705
908
 
706
909
  // Called from Kotlin: SherpaOnnxModule.nativeConvertAudioToWav16k(inputPath, outputPath) -> Boolean
@@ -759,4 +962,67 @@ Java_com_sherpaonnx_SherpaOnnxModule_nativeConvertAudioToFormat(
759
962
  return env->NewStringUTF(err.c_str());
760
963
  }
761
964
 
965
+ // Returns Object[]: on error [String message]; on success [float[] samples, Integer sampleRate].
966
+ JNIEXPORT jobjectArray JNICALL
967
+ Java_com_sherpaonnx_SherpaOnnxModule_nativeDecodeAudioFileToFloatSamples(JNIEnv* env,
968
+ jobject /* this */,
969
+ jstring inputPath,
970
+ jint targetSampleRateHz) {
971
+ jclass objectClass = env->FindClass("java/lang/Object");
972
+ if (!objectClass) {
973
+ return nullptr;
974
+ }
975
+
976
+ auto makeError = [&](const char* msg) -> jobjectArray {
977
+ jobjectArray ret = env->NewObjectArray(1, objectClass, nullptr);
978
+ if (!ret) return nullptr;
979
+ jstring jmsg = env->NewStringUTF(msg);
980
+ env->SetObjectArrayElement(ret, 0, jmsg);
981
+ env->DeleteLocalRef(jmsg);
982
+ return ret;
983
+ };
984
+
985
+ if (inputPath == nullptr) {
986
+ return makeError("inputPath must be non-null");
987
+ }
988
+ const char* input = env->GetStringUTFChars(inputPath, nullptr);
989
+ if (input == nullptr) {
990
+ return makeError("Failed to get path string");
991
+ }
992
+
993
+ std::vector<float> samples;
994
+ int sampleRate = 0;
995
+ std::string err = decodeAudioFileToFloatMono(input, (int)targetSampleRateHz, &samples, &sampleRate);
996
+ env->ReleaseStringUTFChars(inputPath, input);
997
+
998
+ if (!err.empty()) {
999
+ return makeError(err.c_str());
1000
+ }
1001
+
1002
+ jfloatArray jfloats = env->NewFloatArray((jsize)samples.size());
1003
+ if (!jfloats) {
1004
+ return makeError("Failed to allocate float array");
1005
+ }
1006
+ if (!samples.empty()) {
1007
+ env->SetFloatArrayRegion(jfloats, 0, (jsize)samples.size(), samples.data());
1008
+ }
1009
+
1010
+ jobjectArray ret = env->NewObjectArray(2, objectClass, nullptr);
1011
+ if (!ret) {
1012
+ env->DeleteLocalRef(jfloats);
1013
+ return makeError("Failed to allocate result array");
1014
+ }
1015
+ env->SetObjectArrayElement(ret, 0, jfloats);
1016
+
1017
+ jclass intCls = env->FindClass("java/lang/Integer");
1018
+ jmethodID intCtor = env->GetMethodID(intCls, "<init>", "(I)V");
1019
+ jobject jrate = env->NewObject(intCls, intCtor, sampleRate);
1020
+ env->SetObjectArrayElement(ret, 1, jrate);
1021
+
1022
+ env->DeleteLocalRef(jfloats);
1023
+ env->DeleteLocalRef(jrate);
1024
+ env->DeleteLocalRef(intCls);
1025
+ return ret;
1026
+ }
1027
+
762
1028
  } // extern "C"
@@ -60,8 +60,8 @@ TtsModelKind ParseTtsModelType(const std::string& modelType) {
60
60
  }
61
61
 
62
62
  /** Returns true if the given kind is supported by the current paths and hints (required files present).
63
- * data_dir (espeak-ng-data) is required only for Kitten and Kokoro (sherpa-onnx config Validate());
64
- * VITS, Matcha, Zipvoice use it optionally; Pocket does not use it. */
63
+ * data_dir (espeak-ng-data) is required for Kitten, Kokoro, and Zipvoice (Zipvoice uses MatchaTtsLexicon + espeak).
64
+ * VITS and Matcha use dataDir optionally in this detector; Pocket does not use it. */
65
65
  static bool CapabilitySupportsTtsKind(
66
66
  TtsModelKind kind,
67
67
  bool hasVits,
@@ -128,6 +128,10 @@ static TtsDetectResult DetectTtsModelFromFiles(
128
128
  std::string tokensFile = FindFileByName(files, "tokens.txt");
129
129
  std::vector<LexiconCandidate> lexiconCandidates = FindLexiconCandidates(files, modelDir);
130
130
  std::string dataDirPath = FindDirectoryUnderRoot(files, modelDir, "espeak-ng-data");
131
+ LOGI("DetectTtsModel: modelDir=%s espeak-ng dataDir=%s (empty=%d)",
132
+ modelDir.c_str(),
133
+ dataDirPath.empty() ? "(empty)" : dataDirPath.c_str(),
134
+ (int)dataDirPath.empty());
131
135
  std::string voicesFile = FindFileByName(files, "voices.bin");
132
136
 
133
137
  std::string acousticModel = FindOnnxByAnyToken(files, {"acoustic_model", "acoustic-model"}, std::nullopt);
@@ -55,8 +55,8 @@ static const TtsFieldRequirement kZipvoiceReqs[] = {
55
55
  {"decoder", &TtsModelPaths::decoder, true},
56
56
  {"vocoder", &TtsModelPaths::vocoder, true},
57
57
  {"tokens", &TtsModelPaths::tokens, true},
58
- {"dataDir", &TtsModelPaths::dataDir, false},
59
- {"lexicon", &TtsModelPaths::lexicon, false},
58
+ {"dataDir", &TtsModelPaths::dataDir, true},
59
+ {"lexicon", &TtsModelPaths::lexicon, true},
60
60
  };
61
61
 
62
62
  // ============================================================
@@ -102,6 +102,8 @@ static const char* GetFieldHint(const char* fieldName) {
102
102
  return "Copy espeak-ng-data into the model directory.";
103
103
  if (std::strcmp(fieldName, "tokens") == 0)
104
104
  return "Ensure tokens.txt is present in the model directory.";
105
+ if (std::strcmp(fieldName, "lexicon") == 0)
106
+ return "Add lexicon.txt (or lexicon-<lang>.txt) from the official sherpa-onnx Zipvoice/Matcha release; without it the native engine aborts.";
105
107
  return nullptr;
106
108
  }
107
109