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
@@ -3,6 +3,7 @@ import {
3
3
  stat,
4
4
  exists,
5
5
  readDir,
6
+ unlink,
6
7
  type ReadDirResItemT,
7
8
  } from '@dr.pogodin/react-native-fs';
8
9
  import SherpaOnnx from '../NativeSherpaOnnx';
@@ -25,6 +26,37 @@ export class ValidationResult {
25
26
  }
26
27
  }
27
28
 
29
+ /**
30
+ * Delete a directory and all contents. No-op if the path is missing.
31
+ * Best-effort: continues on per-entry errors (permissions, race).
32
+ */
33
+ export async function removeDirectoryRecursive(dirPath: string): Promise<void> {
34
+ if (!(await exists(dirPath))) return;
35
+ let entries: ReadDirResItemT[];
36
+ try {
37
+ entries = await readDir(dirPath);
38
+ } catch {
39
+ return;
40
+ }
41
+ for (const entry of entries) {
42
+ const childPath = `${dirPath}/${entry.name}`.replace(/\/+/g, '/');
43
+ try {
44
+ if (entry.isDirectory()) {
45
+ await removeDirectoryRecursive(childPath);
46
+ } else {
47
+ await unlink(childPath);
48
+ }
49
+ } catch {
50
+ // ignore
51
+ }
52
+ }
53
+ try {
54
+ await unlink(dirPath);
55
+ } catch {
56
+ // ignore
57
+ }
58
+ }
59
+
28
60
  /**
29
61
  * Parse checksum.txt format into a Map of filename -> hash
30
62
  * Expected format:
@@ -148,12 +180,15 @@ export async function validateExtractedFiles(
148
180
 
149
181
  for (const entry of entries) {
150
182
  if (entry.isDirectory()) {
151
- const nested = await collectFilesRecursive(
152
- entry.path,
153
- depth + 1,
154
- maxDepth
155
- );
156
- files.push(...nested);
183
+ const subPath = entry.path;
184
+ if (subPath != null) {
185
+ const nested = await collectFilesRecursive(
186
+ subPath,
187
+ depth + 1,
188
+ maxDepth
189
+ );
190
+ files.push(...nested);
191
+ }
157
192
  } else {
158
193
  files.push(entry);
159
194
  }
@@ -175,13 +210,13 @@ export async function validateExtractedFiles(
175
210
  }
176
211
 
177
212
  let hasModelLikeFiles = actualFiles.some((file) =>
178
- isModelLikeFile(file.name)
213
+ isModelLikeFile(file.name ?? '')
179
214
  );
180
215
 
181
216
  if (!hasModelLikeFiles) {
182
217
  const nestedFiles = await collectFilesRecursive(modelDir);
183
218
  hasModelLikeFiles = nestedFiles.some((file) =>
184
- isModelLikeFile(file.name)
219
+ isModelLikeFile(file.name ?? '')
185
220
  );
186
221
  }
187
222
 
@@ -203,6 +238,77 @@ export async function validateExtractedFiles(
203
238
  }
204
239
  }
205
240
 
241
+ /** True if the file is a native sherpa-onnx model file (e.g. encoder.onnx). Excludes our metadata (.ready, manifest.json). */
242
+ function isNativeModelFileName(name: string): boolean {
243
+ const lower = name.toLowerCase();
244
+ return lower.endsWith('.onnx') || lower.endsWith('.bin');
245
+ }
246
+
247
+ /**
248
+ * Check if a directory contains native model files (.onnx or .bin) at the top level or one level of subdirectories.
249
+ * Used to find the actual model dir; ignores our metadata (manifest.json, .ready).
250
+ */
251
+ async function dirContainsModelFiles(dir: string): Promise<boolean> {
252
+ const entries = await readDir(dir);
253
+ const files = entries.filter((e) => !e.isDirectory());
254
+ if (files.some((f) => isNativeModelFileName(f.name ?? ''))) return true;
255
+ const subdirs = entries.filter((e) => e.isDirectory());
256
+ for (const sub of subdirs) {
257
+ const subPath = sub?.path;
258
+ if (subPath == null) continue;
259
+ const subEntries = await readDir(subPath);
260
+ const subFiles = subEntries.filter((e) => !e.isDirectory());
261
+ if (subFiles.some((f) => isNativeModelFileName(f.name ?? ''))) return true;
262
+ }
263
+ return false;
264
+ }
265
+
266
+ /**
267
+ * Resolve the directory that actually contains model files.
268
+ * After extracting a tarball, model files often end up in a single top-level subdirectory
269
+ * (e.g. installDir/modelId/encoder.onnx). Native APIs expect the path to the folder
270
+ * that directly contains encoder.onnx, decoder.onnx, etc.
271
+ *
272
+ * - If installDir itself contains native model files (.onnx/.bin), returns installDir.
273
+ * - If installDir has exactly one subdirectory that contains native model files, returns that subdirectory path.
274
+ * (Ignores our metadata: .ready, manifest.json.) This can produce paths like
275
+ * .../tts/vits-piper-de_DE-thorsten-medium-int8/vits-piper-de_DE-thorsten-medium-int8 when the
276
+ * archive extracts a top-level folder with the same name as the model id; that is intentional.
277
+ * - Otherwise returns installDir unchanged.
278
+ */
279
+ export async function resolveActualModelDir(
280
+ installDir: string
281
+ ): Promise<string> {
282
+ try {
283
+ const dirExists = await exists(installDir);
284
+ if (!dirExists) return installDir;
285
+
286
+ const entries = await readDir(installDir);
287
+ const topLevelFiles = entries.filter((e) => !e.isDirectory());
288
+ if (topLevelFiles.some((f) => isNativeModelFileName(f.name ?? ''))) {
289
+ return installDir;
290
+ }
291
+
292
+ const subdirs = entries.filter((e) => e.isDirectory());
293
+ const firstSubdir = subdirs[0];
294
+ const singleSubdir = subdirs.length === 1 ? firstSubdir : undefined;
295
+
296
+ if (singleSubdir != null) {
297
+ const candidatePath = singleSubdir.path;
298
+ if (
299
+ candidatePath != null &&
300
+ (await dirContainsModelFiles(candidatePath))
301
+ ) {
302
+ return candidatePath;
303
+ }
304
+ }
305
+
306
+ return installDir;
307
+ } catch {
308
+ return installDir;
309
+ }
310
+ }
311
+
206
312
  /**
207
313
  * Get available disk space (in bytes)
208
314
  * This is a simplified version. For accurate values on Android/iOS, use native modules.
@@ -47,7 +47,9 @@ export async function extractTarBz2(
47
47
  if (signal) {
48
48
  const onAbort = () => {
49
49
  try {
50
- SherpaOnnx.cancelExtractTarBz2();
50
+ // Use per-path cancel so aborting this extraction doesn't affect
51
+ // other extractions that may be running in parallel.
52
+ SherpaOnnx.cancelExtractBySourcePath(sourcePath);
51
53
  } catch {
52
54
  // Ignore cancel errors to avoid crashing on abort.
53
55
  }
@@ -0,0 +1,79 @@
1
+ import { DeviceEventEmitter } from 'react-native';
2
+ import SherpaOnnx from '../NativeSherpaOnnx';
3
+
4
+ export type ExtractProgressEvent = {
5
+ bytes: number;
6
+ totalBytes: number;
7
+ percent: number;
8
+ };
9
+
10
+ type ExtractResult = {
11
+ success: boolean;
12
+ path?: string;
13
+ sha256?: string;
14
+ reason?: string;
15
+ };
16
+
17
+ export async function extractTarZst(
18
+ sourcePath: string,
19
+ targetPath: string,
20
+ force = true,
21
+ onProgress?: (event: ExtractProgressEvent) => void,
22
+ signal?: AbortSignal
23
+ ): Promise<ExtractResult> {
24
+ let subscription: { remove: () => void } | null = null;
25
+ let removeAbortListener: (() => void) | null = null;
26
+
27
+ if (signal?.aborted) {
28
+ const abortError = new Error('Extraction aborted');
29
+ abortError.name = 'AbortError';
30
+ throw abortError;
31
+ }
32
+
33
+ if (onProgress) {
34
+ subscription = DeviceEventEmitter.addListener(
35
+ 'extractTarZstProgress',
36
+ (event: ExtractProgressEvent & { sourcePath?: string }) => {
37
+ if (event.sourcePath != null && event.sourcePath !== sourcePath) {
38
+ return;
39
+ }
40
+ const safePercent = Math.max(0, Math.min(100, event.percent));
41
+ onProgress({ ...event, percent: safePercent });
42
+ }
43
+ );
44
+ }
45
+
46
+ if (signal) {
47
+ const onAbort = () => {
48
+ try {
49
+ // Use per-path cancel so aborting this extraction doesn't affect
50
+ // other extractions that may be running in parallel.
51
+ SherpaOnnx.cancelExtractBySourcePath(sourcePath);
52
+ } catch {
53
+ // Ignore cancel errors to avoid crashing on abort.
54
+ }
55
+ };
56
+ signal.addEventListener('abort', onAbort);
57
+ removeAbortListener = () => signal.removeEventListener('abort', onAbort);
58
+ }
59
+
60
+ try {
61
+ const result = await SherpaOnnx.extractTarZst(
62
+ sourcePath,
63
+ targetPath,
64
+ force
65
+ );
66
+ if (!result.success) {
67
+ const message = result.reason || 'Extraction failed';
68
+ const error = new Error(message);
69
+ if (signal?.aborted || /cancel/i.test(message)) {
70
+ error.name = 'AbortError';
71
+ }
72
+ throw error;
73
+ }
74
+ return result;
75
+ } finally {
76
+ subscription?.remove();
77
+ removeAbortListener?.();
78
+ }
79
+ }
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Extraction subpath: list and extract compressed model archives (.tar.zst / .tar.bz2).
3
+ *
4
+ * Three entry points:
5
+ * - getBundledArchives(packName) – Android PAD packs (STORAGE_FILES or APK_ASSETS)
6
+ * - listBundledArchives(dirPath) – any filesystem directory (cross-platform)
7
+ * - extractArchive(archive, target) – unified extraction (auto-selects path or asset-stream)
8
+ *
9
+ * After extraction, use listModelsAtPath / autoModelPath from the main package.
10
+ */
11
+
12
+ import { DeviceEventEmitter, Platform } from 'react-native';
13
+ import { readDir, stat, exists } from '@dr.pogodin/react-native-fs';
14
+ import SherpaOnnx from '../NativeSherpaOnnx';
15
+ import { extractTarZst } from './extractTarZst';
16
+ import { extractTarBz2 } from './extractTarBz2';
17
+ import type {
18
+ BundledArchive,
19
+ ExtractArchiveOptions,
20
+ ExtractResult,
21
+ ExtractProgressEvent,
22
+ } from './types';
23
+
24
+ export type {
25
+ BundledArchive,
26
+ ExtractArchiveOptions,
27
+ ExtractResult,
28
+ ExtractProgressEvent,
29
+ } from './types';
30
+
31
+ // ── Constants & helpers ───────────────────────────────────────────
32
+
33
+ const TAR_ZST = '.tar.zst';
34
+ const TAR_BZ2 = '.tar.bz2';
35
+
36
+ function formatFromFilename(name: string): 'tar.zst' | 'tar.bz2' | null {
37
+ if (name.endsWith(TAR_ZST)) return 'tar.zst';
38
+ if (name.endsWith(TAR_BZ2)) return 'tar.bz2';
39
+ return null;
40
+ }
41
+
42
+ function modelIdFromFilename(filename: string): string {
43
+ if (filename.endsWith(TAR_ZST)) return filename.slice(0, -TAR_ZST.length);
44
+ if (filename.endsWith(TAR_BZ2)) return filename.slice(0, -TAR_BZ2.length);
45
+ return filename;
46
+ }
47
+
48
+ /**
49
+ * Scan a filesystem directory for .tar.zst / .tar.bz2 entries.
50
+ * Shared by getBundledArchives (STORAGE_FILES) and listBundledArchives.
51
+ */
52
+ async function scanDirectoryForArchives(
53
+ directoryPath: string
54
+ ): Promise<BundledArchive[]> {
55
+ const dirExists = await exists(directoryPath);
56
+ if (!dirExists) return [];
57
+
58
+ const entries = await readDir(directoryPath);
59
+ const archives: BundledArchive[] = [];
60
+
61
+ for (const entry of entries) {
62
+ const format = formatFromFilename(entry.name);
63
+ if (!format || !entry.isFile()) continue;
64
+
65
+ let fileSize = 0;
66
+ try {
67
+ const s = await stat(entry.path);
68
+ fileSize = s.size ?? 0;
69
+ } catch {
70
+ // stat may fail on some filesystems; fileSize stays 0
71
+ }
72
+
73
+ archives.push({
74
+ modelId: modelIdFromFilename(entry.name),
75
+ archivePath: entry.path,
76
+ format,
77
+ fileSize,
78
+ });
79
+ }
80
+
81
+ return archives;
82
+ }
83
+
84
+ // ── Public API ────────────────────────────────────────────────────
85
+
86
+ /**
87
+ * List compressed archives delivered via a **Play Asset Delivery** pack.
88
+ *
89
+ * - **STORAGE_FILES** packs: scans the pack directory on the filesystem.
90
+ * - **APK_ASSETS** packs: queries the Android AssetManager for embedded archive paths.
91
+ * Archives returned with `fromAsset: true` are extracted by streaming from the APK
92
+ * (no temp copy needed).
93
+ * - **iOS / unavailable pack**: returns `null`.
94
+ *
95
+ * @param packName Name of the PAD asset pack (e.g. `"sherpa_models"`)
96
+ */
97
+ export async function getBundledArchives(
98
+ packName: string
99
+ ): Promise<BundledArchive[] | null> {
100
+ if (Platform.OS !== 'android') {
101
+ return null;
102
+ }
103
+
104
+ const packPath = await SherpaOnnx.getAssetPackPath(packName);
105
+
106
+ if (packPath != null && packPath.length > 0) {
107
+ const archives = await scanDirectoryForArchives(packPath);
108
+ return archives.length > 0 ? archives : null;
109
+ }
110
+
111
+ const assetPaths = await SherpaOnnx.listBundledArchiveAssetPaths(packName);
112
+ if (assetPaths.length === 0) return null;
113
+
114
+ return assetPaths.map((archivePath) => {
115
+ const filename = archivePath.split('/').pop() ?? archivePath;
116
+ const format = formatFromFilename(filename) ?? 'tar.zst';
117
+ return {
118
+ modelId: modelIdFromFilename(filename),
119
+ archivePath,
120
+ format,
121
+ fromAsset: true,
122
+ };
123
+ });
124
+ }
125
+
126
+ /**
127
+ * List `.tar.zst` and `.tar.bz2` archives in a filesystem directory.
128
+ *
129
+ * Works on **all platforms** — use for:
130
+ * - iOS main-bundle archives (`MainBundlePath + '/models'`)
131
+ * - Archives downloaded to the documents directory
132
+ * - Any other folder containing compressed model archives
133
+ *
134
+ * @param directoryPath Absolute path to the directory to scan
135
+ */
136
+ export async function listBundledArchives(
137
+ directoryPath: string
138
+ ): Promise<BundledArchive[]> {
139
+ return scanDirectoryForArchives(directoryPath);
140
+ }
141
+
142
+ /**
143
+ * Extract a single archive to the target directory.
144
+ *
145
+ * Handles both source types transparently:
146
+ * - **Filesystem archives** (from `listBundledArchives` or PAD STORAGE_FILES) —
147
+ * uses the regular path-based extraction.
148
+ * - **APK asset archives** (`fromAsset: true`, from PAD APK_ASSETS) —
149
+ * streams directly from the APK without copying the archive to disk first.
150
+ *
151
+ * @param archive Descriptor returned by `getBundledArchives` or `listBundledArchives`
152
+ * @param targetPath Directory to extract into (e.g. `DocumentDirectoryPath + '/models'`)
153
+ * @param options `force` (default `true`), `onProgress`, `signal` (AbortSignal)
154
+ */
155
+ export async function extractArchive(
156
+ archive: BundledArchive,
157
+ targetPath: string,
158
+ options?: ExtractArchiveOptions
159
+ ): Promise<ExtractResult> {
160
+ const force = options?.force !== false;
161
+ const onProgress = options?.onProgress;
162
+ const signal = options?.signal;
163
+
164
+ if (signal?.aborted) {
165
+ const err = new Error('Extraction aborted');
166
+ err.name = 'AbortError';
167
+ throw err;
168
+ }
169
+
170
+ const useAssetStream =
171
+ Platform.OS === 'android' &&
172
+ (archive.fromAsset === true ||
173
+ archive.archivePath.startsWith('asset_packs/'));
174
+
175
+ if (useAssetStream) {
176
+ return extractFromAsset(archive, targetPath, force, onProgress, signal);
177
+ }
178
+
179
+ if (archive.format === 'tar.zst') {
180
+ return extractTarZst(
181
+ archive.archivePath,
182
+ targetPath,
183
+ force,
184
+ onProgress,
185
+ signal
186
+ );
187
+ }
188
+ return extractTarBz2(
189
+ archive.archivePath,
190
+ targetPath,
191
+ force,
192
+ onProgress,
193
+ signal
194
+ );
195
+ }
196
+
197
+ // ── Internal: asset-stream extraction (Android APK_ASSETS) ───────
198
+
199
+ async function extractFromAsset(
200
+ archive: BundledArchive,
201
+ targetPath: string,
202
+ force: boolean,
203
+ onProgress?: (event: ExtractProgressEvent) => void,
204
+ signal?: AbortSignal
205
+ ): Promise<ExtractResult> {
206
+ const eventName =
207
+ archive.format === 'tar.zst'
208
+ ? 'extractTarZstProgress'
209
+ : 'extractTarBz2Progress';
210
+
211
+ let subscription: { remove: () => void } | null = null;
212
+ let removeAbortListener: (() => void) | null = null;
213
+
214
+ if (onProgress) {
215
+ subscription = DeviceEventEmitter.addListener(
216
+ eventName,
217
+ (event: ExtractProgressEvent & { sourcePath?: string }) => {
218
+ if (
219
+ event.sourcePath != null &&
220
+ event.sourcePath !== archive.archivePath
221
+ ) {
222
+ return;
223
+ }
224
+ const safePercent = Math.max(0, Math.min(100, event.percent));
225
+ onProgress({ ...event, percent: safePercent });
226
+ }
227
+ );
228
+ }
229
+
230
+ if (signal) {
231
+ const onAbort = () => {
232
+ try {
233
+ SherpaOnnx.cancelExtractBySourcePath(archive.archivePath);
234
+ } catch {
235
+ // ignore
236
+ }
237
+ };
238
+ signal.addEventListener('abort', onAbort);
239
+ removeAbortListener = () => signal.removeEventListener('abort', onAbort);
240
+ }
241
+
242
+ try {
243
+ const result =
244
+ archive.format === 'tar.zst'
245
+ ? await SherpaOnnx.extractTarZstFromAsset(
246
+ archive.archivePath,
247
+ targetPath,
248
+ force
249
+ )
250
+ : await SherpaOnnx.extractTarBz2FromAsset(
251
+ archive.archivePath,
252
+ targetPath,
253
+ force
254
+ );
255
+
256
+ if (!result.success) {
257
+ const message = result.reason ?? 'Extraction failed';
258
+ const error = new Error(message);
259
+ if (signal?.aborted || /cancel/i.test(message)) {
260
+ error.name = 'AbortError';
261
+ }
262
+ throw error;
263
+ }
264
+ return result;
265
+ } finally {
266
+ subscription?.remove();
267
+ removeAbortListener?.();
268
+ }
269
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Types for the extraction subpath.
3
+ *
4
+ * A BundledArchive describes a compressed model archive (.tar.zst or .tar.bz2)
5
+ * that can come from two distinct sources:
6
+ *
7
+ * 1. **Filesystem** — a regular file on disk (PAD STORAGE_FILES, iOS bundle,
8
+ * downloaded archive, etc.). `fromAsset` is absent or `false`.
9
+ * 2. **Android APK asset** — embedded in the APK via PAD APK_ASSETS.
10
+ * `fromAsset` is `true`; extraction streams directly from the APK.
11
+ *
12
+ * The consumer does not need to distinguish between the two:
13
+ * `extractArchive()` handles both transparently.
14
+ */
15
+
16
+ /** Describes one compressed model archive. */
17
+ export type BundledArchive = {
18
+ /** Identifier derived from the archive filename (filename minus the extension). */
19
+ modelId: string;
20
+ /**
21
+ * Path to the archive.
22
+ * - Filesystem archives: absolute path (e.g. `/data/.../models/whisper-tiny.tar.zst`).
23
+ * - APK assets: asset path (e.g. `asset_packs/sherpa_models/assets/whisper-tiny.tar.zst`).
24
+ */
25
+ archivePath: string;
26
+ /** Compression format. */
27
+ format: 'tar.zst' | 'tar.bz2';
28
+ /** File size in bytes (available for filesystem archives; 0 or absent for APK assets). */
29
+ fileSize?: number;
30
+ /** `true` when the archive lives inside the APK (APK_ASSETS). Absent for filesystem archives. */
31
+ fromAsset?: boolean;
32
+ };
33
+
34
+ /** Progress event emitted during extraction. */
35
+ export type ExtractProgressEvent = {
36
+ /** Bytes extracted so far. */
37
+ bytes: number;
38
+ /** Total bytes of the archive (may be 0 when unknown). */
39
+ totalBytes: number;
40
+ /** Progress percentage 0–100. */
41
+ percent: number;
42
+ };
43
+
44
+ /** Result returned by `extractArchive`. */
45
+ export type ExtractResult = {
46
+ success: boolean;
47
+ /** Absolute path to the extracted directory (on success). */
48
+ path?: string;
49
+ /** SHA-256 hex digest of the source archive (when available). */
50
+ sha256?: string;
51
+ /** Error description (on failure). */
52
+ reason?: string;
53
+ };
54
+
55
+ /** Options for `extractArchive`. */
56
+ export type ExtractArchiveOptions = {
57
+ /** Overwrite existing files. Defaults to `true`. */
58
+ force?: boolean;
59
+ /** Callback for extraction progress. */
60
+ onProgress?: (event: ExtractProgressEvent) => void;
61
+ /** AbortSignal to cancel the extraction. */
62
+ signal?: AbortSignal;
63
+ };
package/src/index.tsx CHANGED
@@ -18,10 +18,12 @@ export {
18
18
 
19
19
  export { copyFileToContentUri } from './tts';
20
20
 
21
+ export { getModelLicenses, type ModelLicense } from './licenses';
21
22
  // Note: Feature-specific exports are available via subpath imports:
22
23
  // - import { createSTT, createStreamingSTT, ... } from 'react-native-sherpa-onnx/stt'
23
24
  // - import { createTTS, ... } from 'react-native-sherpa-onnx/tts'
24
25
  // - import { ... } from 'react-native-sherpa-onnx/download'
26
+ // - import { getBundledArchives, listBundledArchives, extractArchive } from 'react-native-sherpa-onnx/extraction'
25
27
  // - import { ... } from 'react-native-sherpa-onnx/vad' (planned)
26
28
  // - import { ... } from 'react-native-sherpa-onnx/diarization' (planned)
27
29
  // - import { ... } from 'react-native-sherpa-onnx/enhancement' (planned)
@@ -0,0 +1,100 @@
1
+ import SherpaOnnx from './NativeSherpaOnnx';
2
+
3
+ export interface ModelLicense {
4
+ asset_name: string;
5
+ license_type: string;
6
+ commercial_use: 'yes' | 'no' | 'conditional' | 'restricted' | 'unknown';
7
+ confidence: string;
8
+ detection_source: string;
9
+ license_file: string;
10
+ }
11
+
12
+ export async function getModelLicenses(): Promise<ModelLicense[]> {
13
+ const asrPath = 'model_licenses/asr-models-license-status.csv';
14
+ const qnnPath = 'model_licenses/qnn-asr-models-license-status.csv';
15
+ const ttsPath = 'model_licenses/tts-models-license-status.csv';
16
+
17
+ const results = await Promise.allSettled([
18
+ SherpaOnnx.readAssetFileAsUtf8(asrPath),
19
+ SherpaOnnx.readAssetFileAsUtf8(qnnPath),
20
+ SherpaOnnx.readAssetFileAsUtf8(ttsPath),
21
+ ]);
22
+
23
+ const [asrResult, qnnResult, ttsResult] = results;
24
+
25
+ const licenses: ModelLicense[] = [];
26
+
27
+ if (asrResult.status === 'fulfilled') {
28
+ licenses.push(...parseCsv(asrResult.value));
29
+ } else {
30
+ console.warn(
31
+ `[SherpaOnnx] Failed to load ASR model licenses: ${asrResult.reason}`
32
+ );
33
+ }
34
+
35
+ if (qnnResult.status === 'fulfilled') {
36
+ licenses.push(...parseCsv(qnnResult.value));
37
+ } else {
38
+ console.warn(
39
+ `[SherpaOnnx] Failed to load QNN model licenses: ${qnnResult.reason}`
40
+ );
41
+ }
42
+
43
+ if (ttsResult.status === 'fulfilled') {
44
+ licenses.push(...parseCsv(ttsResult.value));
45
+ } else {
46
+ console.warn(
47
+ `[SherpaOnnx] Failed to load TTS model licenses: ${ttsResult.reason}`
48
+ );
49
+ }
50
+
51
+ return licenses;
52
+ }
53
+
54
+ function parseCsv(csvString: string): ModelLicense[] {
55
+ const lines = csvString.split(/\r?\n/);
56
+ if (lines.length === 0) {
57
+ return [];
58
+ }
59
+
60
+ // The first line is the header
61
+ const headerLine = lines[0];
62
+ if (!headerLine) return [];
63
+
64
+ const headers = headerLine.split(',').map((h) => h.trim());
65
+
66
+ const results: ModelLicense[] = [];
67
+
68
+ for (let i = 1; i < lines.length; i++) {
69
+ const line = lines[i];
70
+ if (!line || line.trim() === '') continue;
71
+
72
+ // The CSV has 6 columns: asset_name, license_type, commercial_use,
73
+ // confidence, detection_source, license_file. The last column
74
+ // (license_file) may itself contain commas (e.g. URLs with query strings),
75
+ // so split into at most 6 parts and join any excess back into the last field.
76
+ const COLUMN_COUNT = 6;
77
+ const rawParts = line.split(',');
78
+ const parts =
79
+ rawParts.length <= COLUMN_COUNT
80
+ ? rawParts
81
+ : [
82
+ ...rawParts.slice(0, COLUMN_COUNT - 1),
83
+ rawParts.slice(COLUMN_COUNT - 1).join(','),
84
+ ];
85
+
86
+ const entry: Record<string, string> = {};
87
+ for (let j = 0; j < headers.length; j++) {
88
+ const header = headers[j];
89
+ if (header) {
90
+ entry[header] = (parts[j] ?? '').trim();
91
+ }
92
+ }
93
+
94
+ if (entry['asset_name']) {
95
+ results.push(entry as unknown as ModelLicense);
96
+ }
97
+ }
98
+
99
+ return results;
100
+ }