react-native-sherpa-onnx 0.3.2 → 0.3.4

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 (83) hide show
  1. package/README.md +84 -77
  2. package/SherpaOnnx.podspec +79 -45
  3. package/android/build.gradle +8 -2
  4. package/android/prebuilt-download.gradle +70 -16
  5. package/android/prebuilt-versions.gradle +14 -6
  6. package/android/src/main/cpp/CMakeLists.txt +2 -0
  7. package/android/src/main/cpp/jni/audio/sherpa-onnx-audio-convert-jni.cpp +202 -328
  8. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-detect-jni-common.cpp +22 -0
  9. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-detect-jni-common.h +2 -0
  10. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-helper.cpp +96 -142
  11. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-helper.h +40 -4
  12. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-stt.cpp +774 -316
  13. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-tts.cpp +208 -122
  14. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect.h +92 -0
  15. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-stt-wrapper.cpp +3 -0
  16. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-tts-wrapper.cpp +14 -2
  17. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-validate-stt.cpp +229 -0
  18. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-validate-stt.h +38 -0
  19. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-validate-tts.cpp +144 -0
  20. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-validate-tts.h +38 -0
  21. package/android/src/main/cpp/jni/module/sherpa-onnx-module-jni.cpp +1 -1
  22. package/android/src/main/java/com/sherpaonnx/SherpaOnnxModule.kt +157 -11
  23. package/android/src/main/java/com/sherpaonnx/SherpaOnnxPcmCapture.kt +150 -0
  24. package/android/src/main/java/com/sherpaonnx/SherpaOnnxSttHelper.kt +75 -24
  25. package/android/src/main/java/com/sherpaonnx/SherpaOnnxTtsHelper.kt +52 -1
  26. package/ios/SherpaOnnx+PcmLiveStream.mm +288 -0
  27. package/ios/SherpaOnnx+STT.mm +2 -0
  28. package/ios/SherpaOnnx+TTS.mm +17 -0
  29. package/ios/SherpaOnnx.mm +27 -3
  30. package/ios/SherpaOnnxAudioConvert.h +28 -0
  31. package/ios/SherpaOnnxAudioConvert.mm +698 -0
  32. package/ios/archive/sherpa-onnx-archive-helper.mm +12 -0
  33. package/ios/model_detect/sherpa-onnx-model-detect-helper.h +37 -3
  34. package/ios/model_detect/sherpa-onnx-model-detect-helper.mm +80 -45
  35. package/ios/model_detect/sherpa-onnx-model-detect-stt.mm +629 -267
  36. package/ios/model_detect/sherpa-onnx-model-detect-tts.mm +148 -56
  37. package/ios/model_detect/sherpa-onnx-model-detect.h +72 -0
  38. package/ios/model_detect/sherpa-onnx-validate-stt.h +38 -0
  39. package/ios/model_detect/sherpa-onnx-validate-stt.mm +229 -0
  40. package/ios/model_detect/sherpa-onnx-validate-tts.h +38 -0
  41. package/ios/model_detect/sherpa-onnx-validate-tts.mm +144 -0
  42. package/ios/stt/sherpa-onnx-stt-wrapper.mm +4 -0
  43. package/lib/module/NativeSherpaOnnx.js.map +1 -1
  44. package/lib/module/audio/index.js +55 -1
  45. package/lib/module/audio/index.js.map +1 -1
  46. package/lib/module/download/ModelDownloadManager.js +14 -0
  47. package/lib/module/download/ModelDownloadManager.js.map +1 -1
  48. package/lib/module/index.js +10 -0
  49. package/lib/module/index.js.map +1 -1
  50. package/lib/module/stt/streaming.js +6 -3
  51. package/lib/module/stt/streaming.js.map +1 -1
  52. package/lib/module/tts/index.js +13 -1
  53. package/lib/module/tts/index.js.map +1 -1
  54. package/lib/typescript/src/NativeSherpaOnnx.d.ts +32 -3
  55. package/lib/typescript/src/NativeSherpaOnnx.d.ts.map +1 -1
  56. package/lib/typescript/src/audio/index.d.ts +20 -1
  57. package/lib/typescript/src/audio/index.d.ts.map +1 -1
  58. package/lib/typescript/src/download/ModelDownloadManager.d.ts +2 -1
  59. package/lib/typescript/src/download/ModelDownloadManager.d.ts.map +1 -1
  60. package/lib/typescript/src/index.d.ts +10 -0
  61. package/lib/typescript/src/index.d.ts.map +1 -1
  62. package/lib/typescript/src/stt/streaming.d.ts.map +1 -1
  63. package/lib/typescript/src/stt/streamingTypes.d.ts +1 -1
  64. package/lib/typescript/src/stt/streamingTypes.d.ts.map +1 -1
  65. package/lib/typescript/src/tts/index.d.ts +12 -1
  66. package/lib/typescript/src/tts/index.d.ts.map +1 -1
  67. package/package.json +6 -1
  68. package/scripts/check-model-csvs.sh +72 -0
  69. package/scripts/setup-ios-framework.sh +272 -191
  70. package/src/NativeSherpaOnnx.ts +37 -3
  71. package/src/audio/index.ts +84 -1
  72. package/src/download/ModelDownloadManager.ts +19 -0
  73. package/src/index.tsx +15 -0
  74. package/src/stt/streaming.ts +10 -5
  75. package/src/stt/streamingTypes.ts +1 -1
  76. package/src/tts/index.ts +25 -1
  77. package/third_party/ffmpeg_prebuilt/ANDROID_RELEASE_TAG +1 -1
  78. package/third_party/libarchive_prebuilt/ANDROID_RELEASE_TAG +1 -1
  79. package/third_party/libarchive_prebuilt/IOS_RELEASE_TAG +1 -1
  80. package/third_party/sherpa-onnx-prebuilt/ANDROID_RELEASE_TAG +1 -1
  81. package/third_party/sherpa-onnx-prebuilt/IOS_RELEASE_TAG +1 -1
  82. package/ios/scripts/patch-libarchive-includes.sh +0 -61
  83. package/ios/scripts/setup-ios-libarchive.sh +0 -98
@@ -6,8 +6,10 @@
6
6
  */
7
7
 
8
8
  #import "sherpa-onnx-archive-helper.h"
9
+ #ifdef HAVE_LIBARCHIVE
9
10
  #import <archive.h>
10
11
  #import <archive_entry.h>
12
+ #endif
11
13
  #import <CommonCrypto/CommonCrypto.h>
12
14
  #include <array>
13
15
  #include <atomic>
@@ -17,6 +19,7 @@
17
19
  static std::atomic_bool g_cancelExtract(false);
18
20
 
19
21
  namespace {
22
+ #ifdef HAVE_LIBARCHIVE
20
23
  struct ArchiveReadContext {
21
24
  FILE* file = nullptr;
22
25
  std::array<unsigned char, 64 * 1024> buffer{};
@@ -66,6 +69,7 @@ static void DrainRemainingAndClose(ArchiveReadContext* ctx) {
66
69
  fclose(ctx->file);
67
70
  ctx->file = nullptr;
68
71
  }
72
+ #endif
69
73
 
70
74
  static NSString* HexStringFromDigest(const unsigned char* digest, size_t size) {
71
75
  static const char* kHex = "0123456789abcdef";
@@ -122,7 +126,11 @@ static NSString* ComputeFileSha256(NSString* filePath, NSError** error) {
122
126
 
123
127
  + (void)cancelExtractTarBz2
124
128
  {
129
+ #ifdef HAVE_LIBARCHIVE
125
130
  g_cancelExtract.store(true);
131
+ #else
132
+ // feature disabled
133
+ #endif
126
134
  }
127
135
 
128
136
  - (NSDictionary *)extractTarBz2:(NSString *)sourcePath
@@ -130,6 +138,9 @@ static NSString* ComputeFileSha256(NSString* filePath, NSError** error) {
130
138
  force:(BOOL)force
131
139
  progress:(SherpaOnnxArchiveProgressBlock)progress
132
140
  {
141
+ #ifndef HAVE_LIBARCHIVE
142
+ return @{ @"success": @NO, @"reason": @"libarchive is disabled in this build. Rebuild without SHERPA_ONNX_DISABLE_LIBARCHIVE=1." };
143
+ #else
133
144
  g_cancelExtract.store(false);
134
145
  NSFileManager *fileManager = [NSFileManager defaultManager];
135
146
 
@@ -285,6 +296,7 @@ static NSString* ComputeFileSha256(NSString* filePath, NSError** error) {
285
296
  NSString *sha256Hex = HexStringFromDigest(digest, CC_SHA256_DIGEST_LENGTH);
286
297
 
287
298
  return @{ @"success": @YES, @"path": targetPath, @"sha256": sha256Hex ?: @"" };
299
+ #endif
288
300
  }
289
301
 
290
302
  - (NSString *)computeFileSha256:(NSString *)filePath
@@ -21,12 +21,11 @@ std::vector<std::string> ListDirectories(const std::string& path);
21
21
  std::vector<FileEntry> ListFiles(const std::string& path);
22
22
  std::vector<FileEntry> ListFilesRecursive(const std::string& path, int maxDepth);
23
23
  std::string ToLower(std::string value);
24
- std::string ResolveTokenizerDir(const std::string& modelDir);
25
24
 
26
- std::string FindFileByName(const std::string& baseDir, const std::string& fileName, int maxDepth);
25
+ /** Find file in \p files whose name equals \p fileName (case-insensitive). Uses file tree only, no filesystem. */
26
+ std::string FindFileByName(const std::vector<FileEntry>& files, const std::string& fileName);
27
27
  /** Find file whose name equals or ends with suffix (e.g. tokens.txt, tiny-tokens.txt) in a pre-built file list. */
28
28
  std::string FindFileEndingWith(const std::vector<FileEntry>& files, const std::string& suffix);
29
- std::string FindDirectoryByName(const std::string& baseDir, const std::string& dirName, int maxDepth);
30
29
 
31
30
  std::string FindOnnxByToken(
32
31
  const std::vector<FileEntry>& files,
@@ -38,6 +37,13 @@ std::string FindOnnxByAnyToken(
38
37
  const std::vector<std::string>& tokens,
39
38
  const std::optional<bool>& preferInt8
40
39
  );
40
+ /** Like FindOnnxByAnyToken but skips any file whose nameLower contains any of \p excludeInName. */
41
+ std::string FindOnnxByAnyTokenExcluding(
42
+ const std::vector<FileEntry>& files,
43
+ const std::vector<std::string>& tokens,
44
+ const std::vector<std::string>& excludeInName,
45
+ const std::optional<bool>& preferInt8
46
+ );
41
47
  std::string FindLargestOnnxExcludingTokens(
42
48
  const std::vector<FileEntry>& files,
43
49
  const std::vector<std::string>& excludeTokens
@@ -46,6 +52,34 @@ std::string FindLargestOnnxExcludingTokens(
46
52
  /** Returns true if \p word appears in \p haystack as a standalone token (surrounded by separators: / - _ . space). */
47
53
  bool ContainsWord(const std::string& haystack, const std::string& word);
48
54
 
55
+ /**
56
+ * Find a directory with the given name anywhere under \p rootDir in the file tree.
57
+ * Searches \p files for any path that starts with \p rootDir and contains "/dirName/".
58
+ * Returns the full path to that directory (e.g. rootDir/inner/dirName) or empty if not found.
59
+ * Used e.g. to find espeak-ng-data in modelDir or in modelDir/inner-model-dir/.
60
+ */
61
+ std::string FindDirectoryUnderRoot(
62
+ const std::vector<FileEntry>& files,
63
+ const std::string& rootDir,
64
+ const std::string& dirName
65
+ );
66
+
67
+ /** Lexicon file with optional language id for multi-lang TTS (e.g. Kokoro). */
68
+ struct LexiconCandidate {
69
+ std::string path; /**< Full path to the lexicon file */
70
+ std::string languageId; /**< From filename: "default" for lexicon.txt, else e.g. "us-en", "zh" from lexicon-us-en.txt, lexicon-zh.txt */
71
+ };
72
+
73
+ /**
74
+ * Find all lexicon files under \p rootDir: exact "lexicon.txt" and any "lexicon-*.txt".
75
+ * Returns a list of LexiconCandidate (path + languageId), ordered: lexicon.txt first (as "default"),
76
+ * then lexicon-*.txt alphabetically by language id. Used for multi-language Kokoro/Kitten TTS.
77
+ */
78
+ std::vector<LexiconCandidate> FindLexiconCandidates(
79
+ const std::vector<FileEntry>& files,
80
+ const std::string& rootDir
81
+ );
82
+
49
83
  } // namespace model_detect
50
84
  } // namespace sherpaonnx
51
85
 
@@ -30,12 +30,16 @@ bool ContainsToken(const std::string& value, const std::string& token) {
30
30
  return value.find(token) != std::string::npos;
31
31
  }
32
32
 
33
+ static bool IsOnnxOrOrtFile(const FileEntry& entry) {
34
+ return EndsWith(entry.nameLower, ".onnx") || EndsWith(entry.nameLower, ".ort");
35
+ }
36
+
33
37
  std::string ChooseLargest(const std::vector<FileEntry>& files,
34
38
  const std::vector<std::string>& excludeTokens, bool onlyInt8, bool onlyNonInt8) {
35
39
  std::string chosen;
36
40
  std::uint64_t bestSize = 0;
37
41
  for (const auto& entry : files) {
38
- if (!EndsWith(entry.nameLower, ".onnx")) continue;
42
+ if (!IsOnnxOrOrtFile(entry)) continue;
39
43
  bool hasExcluded = false;
40
44
  for (const auto& token : excludeTokens) {
41
45
  if (ContainsToken(entry.nameLower, token)) { hasExcluded = true; break; }
@@ -115,7 +119,7 @@ std::string FindOnnxByToken(const std::vector<FileEntry>& files,
115
119
  std::string tokenLower = ToLower(token);
116
120
  std::vector<FileEntry> matches;
117
121
  for (const auto& entry : files) {
118
- if (!EndsWith(entry.nameLower, ".onnx")) continue;
122
+ if (!IsOnnxOrOrtFile(entry)) continue;
119
123
  if (ContainsToken(entry.nameLower, tokenLower)) matches.push_back(entry);
120
124
  }
121
125
  if (matches.empty()) return "";
@@ -136,6 +140,37 @@ std::string FindOnnxByAnyToken(const std::vector<FileEntry>& files,
136
140
  return "";
137
141
  }
138
142
 
143
+ std::string FindOnnxByAnyTokenExcluding(const std::vector<FileEntry>& files,
144
+ const std::vector<std::string>& tokens, const std::vector<std::string>& excludeInName,
145
+ const std::optional<bool>& preferInt8) {
146
+ for (const auto& token : tokens) {
147
+ std::string tokenLower = ToLower(token);
148
+ std::vector<FileEntry> matches;
149
+ for (const auto& entry : files) {
150
+ if (!IsOnnxOrOrtFile(entry)) continue;
151
+ if (!ContainsToken(entry.nameLower, tokenLower)) continue;
152
+ bool excluded = false;
153
+ for (const auto& ex : excludeInName) {
154
+ std::string exLower = ToLower(ex);
155
+ if (ContainsToken(entry.nameLower, exLower)) {
156
+ excluded = true;
157
+ break;
158
+ }
159
+ }
160
+ if (!excluded) matches.push_back(entry);
161
+ }
162
+ if (matches.empty()) continue;
163
+ std::vector<std::string> emptyTokens;
164
+ bool wantInt8 = preferInt8.has_value() && preferInt8.value();
165
+ bool wantNonInt8 = preferInt8.has_value() && !preferInt8.value();
166
+ std::string chosen = ChooseLargest(matches, emptyTokens, wantInt8, wantNonInt8);
167
+ if (!chosen.empty()) return chosen;
168
+ chosen = ChooseLargest(matches, emptyTokens, false, false);
169
+ if (!chosen.empty()) return chosen;
170
+ }
171
+ return "";
172
+ }
173
+
139
174
  std::string FindFileEndingWith(const std::vector<FileEntry>& files, const std::string& suffix) {
140
175
  std::string targetSuffix = ToLower(suffix);
141
176
  for (const auto& entry : files) {
@@ -147,9 +182,8 @@ std::string FindFileEndingWith(const std::vector<FileEntry>& files, const std::s
147
182
  return "";
148
183
  }
149
184
 
150
- std::string FindFileByName(const std::string& baseDir, const std::string& fileName, int maxDepth) {
185
+ std::string FindFileByName(const std::vector<FileEntry>& files, const std::string& fileName) {
151
186
  std::string target = ToLower(fileName);
152
- auto files = ListFilesRecursive(baseDir, maxDepth);
153
187
  for (const auto& entry : files) {
154
188
  if (entry.nameLower == target) return entry.path;
155
189
  }
@@ -172,54 +206,55 @@ bool ContainsWord(const std::string& haystack, const std::string& word) {
172
206
  return false;
173
207
  }
174
208
 
175
- std::string FindDirectoryByName(const std::string& baseDir, const std::string& dirName, int maxDepth) {
176
- std::string target = ToLower(dirName);
177
- std::vector<std::string> toVisit = ListDirectories(baseDir);
178
- int depth = 0;
179
- while (!toVisit.empty() && depth <= maxDepth) {
180
- std::vector<std::string> next;
181
- for (const auto& dir : toVisit) {
182
- std::string name = fs::path(dir).filename().string();
183
- if (ToLower(name) == target) return dir;
184
- if (depth < maxDepth) {
185
- auto nested = ListDirectories(dir);
186
- next.insert(next.end(), nested.begin(), nested.end());
187
- }
209
+ std::string FindDirectoryUnderRoot(
210
+ const std::vector<FileEntry>& files,
211
+ const std::string& rootDir,
212
+ const std::string& dirName
213
+ ) {
214
+ if (dirName.empty()) return "";
215
+ const std::string needle = "/" + dirName + "/";
216
+ const size_t dirPathLen = 1 + dirName.size();
217
+ for (const auto& entry : files) {
218
+ if (entry.path.size() < rootDir.size() + needle.size()) continue;
219
+ if (entry.path.compare(0, rootDir.size(), rootDir) != 0) continue;
220
+ size_t pos = entry.path.find(needle, rootDir.size());
221
+ if (pos != std::string::npos) {
222
+ return entry.path.substr(0, pos + dirPathLen);
188
223
  }
189
- toVisit.swap(next);
190
- depth += 1;
191
224
  }
192
225
  return "";
193
226
  }
194
227
 
195
- std::string ResolveTokenizerDir(const std::string& modelDir) {
196
- std::string vocabInMain = modelDir + "/vocab.json";
197
- if (FileExists(vocabInMain)) {
198
- return modelDir;
199
- }
200
-
201
- try {
202
- for (const auto& entry : fs::directory_iterator(modelDir)) {
203
- if (entry.is_directory()) {
204
- std::string dirName = entry.path().filename().string();
205
- std::string dirNameLower = ToLower(dirName);
206
- if (dirNameLower.find("qwen3") != std::string::npos) {
207
- std::string vocabPath = entry.path().string() + "/vocab.json";
208
- if (FileExists(vocabPath)) {
209
- return entry.path().string();
210
- }
211
- }
212
- }
228
+ std::vector<LexiconCandidate> FindLexiconCandidates(
229
+ const std::vector<FileEntry>& files,
230
+ const std::string& rootDir
231
+ ) {
232
+ std::vector<LexiconCandidate> candidates;
233
+ const size_t rootLen = rootDir.size();
234
+ for (const auto& entry : files) {
235
+ if (entry.path.size() <= rootLen) continue;
236
+ if (rootLen > 0) {
237
+ if (entry.path.compare(0, rootLen, rootDir) != 0) continue;
238
+ // Enforce path boundary: if rootDir doesn't end with '/', require '/' after it
239
+ if (rootDir.back() != '/' && entry.path[rootLen] != '/') continue;
240
+ }
241
+ const std::string& baseLower = entry.nameLower;
242
+ if (baseLower == "lexicon.txt") {
243
+ candidates.push_back({entry.path, "default"});
244
+ } else if (baseLower.size() > 12 &&
245
+ baseLower.compare(0, 8, "lexicon-") == 0 &&
246
+ baseLower.compare(baseLower.size() - 4, 4, ".txt") == 0) {
247
+ std::string languageId = baseLower.substr(8, baseLower.size() - 12);
248
+ candidates.push_back({entry.path, languageId});
213
249
  }
214
- } catch (const std::exception&) {
215
- }
216
-
217
- std::string commonPath = modelDir + "/Qwen3-0.6B";
218
- if (FileExists(commonPath + "/vocab.json")) {
219
- return commonPath;
220
250
  }
221
-
222
- return "";
251
+ std::sort(candidates.begin(), candidates.end(), [](const LexiconCandidate& a, const LexiconCandidate& b) {
252
+ if (a.languageId == b.languageId) return a.path < b.path;
253
+ if (a.languageId == "default") return true;
254
+ if (b.languageId == "default") return false;
255
+ return a.languageId < b.languageId;
256
+ });
257
+ return candidates;
223
258
  }
224
259
 
225
260
  } // namespace model_detect