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
@@ -0,0 +1,234 @@
1
+ import { Platform } from 'react-native';
2
+ import {
3
+ DocumentDirectoryPath,
4
+ exists,
5
+ readFile,
6
+ readDir,
7
+ unlink,
8
+ writeFile,
9
+ } from '@dr.pogodin/react-native-fs';
10
+ import type {
11
+ ModelCategory,
12
+ ModelMetaBase,
13
+ ModelManifest,
14
+ ModelWithMetadata,
15
+ } from './types';
16
+ import {
17
+ getCachePath,
18
+ getModelsBaseDir,
19
+ getModelDir,
20
+ getManifestPath,
21
+ getNativeAssetExtractedModelDir,
22
+ getReadyMarkerPath,
23
+ getTarArchivePath,
24
+ getOnnxPath,
25
+ } from './paths';
26
+ import { resolveActualModelDir, removeDirectoryRecursive } from './validation';
27
+ import { emitModelsListUpdated } from './downloadEvents';
28
+ import { clearMemoryCacheForCategory } from './registry';
29
+
30
+ export async function listDownloadedModelsByCategory<T extends ModelMetaBase>(
31
+ category: ModelCategory
32
+ ): Promise<T[]> {
33
+ const baseDir = getModelsBaseDir(category);
34
+ const existsResult = await exists(baseDir);
35
+ if (!existsResult) return [];
36
+
37
+ const entries = await readDir(baseDir);
38
+ const models: T[] = [];
39
+
40
+ for (const entry of entries) {
41
+ if (!entry.isDirectory()) continue;
42
+ const manifestPath = getManifestPath(category, entry.name);
43
+ const manifestExists = await exists(manifestPath);
44
+ if (!manifestExists) continue;
45
+ try {
46
+ const raw = await readFile(manifestPath, 'utf8');
47
+ const manifest = JSON.parse(raw) as ModelManifest<T>;
48
+ if (manifest.model) {
49
+ models.push(manifest.model);
50
+ }
51
+ } catch {
52
+ // ignore invalid manifest
53
+ }
54
+ }
55
+
56
+ return models;
57
+ }
58
+
59
+ export async function isModelDownloadedByCategory(
60
+ category: ModelCategory,
61
+ id: string
62
+ ): Promise<boolean> {
63
+ const readyPath = getReadyMarkerPath(category, id);
64
+ return exists(readyPath);
65
+ }
66
+
67
+ export async function getLocalModelPathByCategory(
68
+ category: ModelCategory,
69
+ id: string
70
+ ): Promise<string | null> {
71
+ const ready = await isModelDownloadedByCategory(category, id);
72
+ if (!ready) return null;
73
+
74
+ await updateModelLastUsed(category, id);
75
+
76
+ const installDir = getModelDir(category, id);
77
+ return resolveActualModelDir(installDir);
78
+ }
79
+
80
+ export async function updateModelLastUsed(
81
+ category: ModelCategory,
82
+ id: string
83
+ ): Promise<void> {
84
+ const manifestPath = getManifestPath(category, id);
85
+ const existsResult = await exists(manifestPath);
86
+ if (!existsResult) return;
87
+
88
+ try {
89
+ const raw = await readFile(manifestPath, 'utf8');
90
+ const manifest = JSON.parse(raw) as ModelManifest;
91
+ manifest.lastUsed = new Date().toISOString();
92
+ await writeFile(manifestPath, JSON.stringify(manifest), 'utf8');
93
+ } catch (error) {
94
+ console.warn(`Failed to update lastUsed for ${category}:${id}:`, error);
95
+ }
96
+ }
97
+
98
+ export async function listDownloadedModelsWithMetadata<T extends ModelMetaBase>(
99
+ category: ModelCategory
100
+ ): Promise<ModelWithMetadata<T>[]> {
101
+ const baseDir = getModelsBaseDir(category);
102
+ const existsResult = await exists(baseDir);
103
+ if (!existsResult) return [];
104
+
105
+ const entries = await readDir(baseDir);
106
+ const modelsWithMetadata: ModelWithMetadata<T>[] = [];
107
+
108
+ for (const entry of entries) {
109
+ if (!entry.isDirectory()) continue;
110
+
111
+ const manifestPath = getManifestPath(category, entry.name);
112
+ const manifestExists = await exists(manifestPath);
113
+
114
+ if (manifestExists) {
115
+ try {
116
+ const raw = await readFile(manifestPath, 'utf8');
117
+ const manifest = JSON.parse(raw) as ModelManifest<T>;
118
+ if (manifest.model) {
119
+ modelsWithMetadata.push({
120
+ model: manifest.model,
121
+ downloadedAt: manifest.downloadedAt,
122
+ lastUsed: manifest.lastUsed ?? null,
123
+ sizeOnDisk: manifest.sizeOnDisk ?? entry.size,
124
+ status: 'ready',
125
+ });
126
+ }
127
+ } catch (error) {
128
+ console.warn(
129
+ `Failed to read manifest for ${category}:${entry.name}:`,
130
+ error
131
+ );
132
+ }
133
+ }
134
+ }
135
+
136
+ return modelsWithMetadata;
137
+ }
138
+
139
+ export async function cleanupLeastRecentlyUsed(
140
+ category: ModelCategory,
141
+ options?: {
142
+ targetBytes?: number;
143
+ maxModelsToDelete?: number;
144
+ keepCount?: number;
145
+ }
146
+ ): Promise<string[]> {
147
+ const modelsWithMetadata = await listDownloadedModelsWithMetadata(category);
148
+
149
+ if (modelsWithMetadata.length === 0) {
150
+ return [];
151
+ }
152
+
153
+ const keepCount = options?.keepCount ?? 1;
154
+ if (modelsWithMetadata.length <= keepCount) {
155
+ return [];
156
+ }
157
+
158
+ const sorted = modelsWithMetadata.sort((a, b) => {
159
+ const aTime = a.lastUsed ?? a.downloadedAt;
160
+ const bTime = b.lastUsed ?? b.downloadedAt;
161
+ return new Date(aTime).getTime() - new Date(bTime).getTime();
162
+ });
163
+
164
+ const deletedIds: string[] = [];
165
+ let bytesFreed = 0;
166
+ const targetBytes = options?.targetBytes ?? 0;
167
+ const maxToDelete = options?.maxModelsToDelete ?? sorted.length - keepCount;
168
+
169
+ for (let i = 0; i < sorted.length - keepCount && i < maxToDelete; i++) {
170
+ const item = sorted[i];
171
+ if (!item) continue;
172
+
173
+ try {
174
+ await deleteModelByCategory(category, item.model.id);
175
+ deletedIds.push(item.model.id);
176
+ bytesFreed += item.sizeOnDisk ?? 0;
177
+
178
+ console.log(
179
+ `[LRU Cleanup] Deleted ${category}:${item.model.id} (freed ${
180
+ (item.sizeOnDisk ?? 0) / 1024 / 1024
181
+ } MB)`
182
+ );
183
+
184
+ if (targetBytes > 0 && bytesFreed >= targetBytes) {
185
+ break;
186
+ }
187
+ } catch (error) {
188
+ console.warn(
189
+ `[LRU Cleanup] Failed to delete ${category}:${item.model.id}:`,
190
+ error
191
+ );
192
+ }
193
+ }
194
+
195
+ return deletedIds;
196
+ }
197
+
198
+ export async function deleteModelByCategory(
199
+ category: ModelCategory,
200
+ id: string
201
+ ): Promise<void> {
202
+ const modelDir = getModelDir(category, id);
203
+ const tarPath = getTarArchivePath(category, id);
204
+ const onnxPath = getOnnxPath(category, id);
205
+ if (await exists(modelDir)) {
206
+ await unlink(modelDir);
207
+ }
208
+ if (await exists(tarPath)) {
209
+ await unlink(tarPath);
210
+ }
211
+ if (await exists(onnxPath)) {
212
+ await unlink(onnxPath);
213
+ }
214
+ await removeDirectoryRecursive(getNativeAssetExtractedModelDir(id));
215
+ const list = await listDownloadedModelsByCategory<ModelMetaBase>(category);
216
+ emitModelsListUpdated(category, list);
217
+ }
218
+
219
+ export async function clearModelCacheByCategory(
220
+ category: ModelCategory
221
+ ): Promise<void> {
222
+ const cachePath = getCachePath(category);
223
+ if (await exists(cachePath)) {
224
+ await unlink(cachePath);
225
+ }
226
+ clearMemoryCacheForCategory(category);
227
+ }
228
+
229
+ export async function getDownloadStorageBase(): Promise<string> {
230
+ if (Platform.OS === 'ios') {
231
+ return DocumentDirectoryPath;
232
+ }
233
+ return DocumentDirectoryPath;
234
+ }
@@ -0,0 +1,244 @@
1
+ import {
2
+ exists,
3
+ readFile,
4
+ readDir,
5
+ mkdir,
6
+ stat,
7
+ unlink,
8
+ } from '@dr.pogodin/react-native-fs';
9
+ import type {
10
+ ModelCategory,
11
+ ModelMetaBase,
12
+ ChecksumIssue,
13
+ DownloadProgress,
14
+ ExtractionState,
15
+ } from './types';
16
+ import type { DownloadResult } from './types';
17
+ import {
18
+ getModelsBaseDir,
19
+ getModelDir,
20
+ getArchivePath,
21
+ getReadyMarkerPath,
22
+ getExtractionStatePath,
23
+ getNativeAssetExtractedModelDir,
24
+ } from './paths';
25
+ import { runPostDownloadProcessing } from './postDownloadProcessing';
26
+ import { getModelByIdByCategory } from './registry';
27
+ import {
28
+ listDownloadedModelsByCategory,
29
+ getLocalModelPathByCategory,
30
+ } from './localModels';
31
+ import { resolveActualModelDir, removeDirectoryRecursive } from './validation';
32
+
33
+ const EXTRACTION_STATE_PREFIX = '.extraction-state-';
34
+ const EXTRACTION_STATE_SUFFIX = '.json';
35
+
36
+ /**
37
+ * Start extraction for a model: archive must already exist (e.g. after download or from PAD).
38
+ * Writes extraction state so that if the app crashes, extraction can be resumed via
39
+ * getIncompleteExtractions + resumeExtraction.
40
+ * Use signal to abort (pause) extraction.
41
+ */
42
+ export async function extractModelByCategory<T extends ModelMetaBase>(
43
+ category: ModelCategory,
44
+ id: string,
45
+ opts?: {
46
+ onProgress?: (progress: DownloadProgress) => void;
47
+ signal?: AbortSignal;
48
+ onChecksumIssue?: (issue: ChecksumIssue) => Promise<boolean>;
49
+ deleteArchiveAfterExtract?: boolean;
50
+ }
51
+ ): Promise<DownloadResult> {
52
+ const model = await getModelByIdByCategory<T>(category, id);
53
+ if (!model) {
54
+ throw new Error(`Unknown model id: ${id}`);
55
+ }
56
+ if (model.archiveExt !== 'tar.bz2') {
57
+ throw new Error(
58
+ `Model ${id} is not a tar.bz2 archive; extraction is only for archived models.`
59
+ );
60
+ }
61
+
62
+ const downloadPath = getArchivePath(category, id, model.archiveExt);
63
+ const modelDir = getModelDir(category, id);
64
+ const statePath = getExtractionStatePath(category, id);
65
+
66
+ const archiveExists = await exists(downloadPath);
67
+ if (!archiveExists) {
68
+ throw new Error(
69
+ `Archive not found at ${downloadPath}. Download the model first or ensure the archive is present.`
70
+ );
71
+ }
72
+ try {
73
+ const archiveStat = await stat(downloadPath);
74
+ if (model.bytes > 0 && archiveStat.size < model.bytes) {
75
+ throw new Error(
76
+ `Archive is truncated (${archiveStat.size}/${model.bytes} bytes). Re-download or replace the file.`
77
+ );
78
+ }
79
+ } catch (statErr) {
80
+ if (statErr instanceof Error) throw statErr;
81
+ throw new Error('Failed to read archive size.');
82
+ }
83
+
84
+ await mkdir(getModelsBaseDir(category));
85
+
86
+ return runPostDownloadProcessing({
87
+ category,
88
+ id,
89
+ model,
90
+ downloadPath,
91
+ modelDir,
92
+ isArchive: true,
93
+ statePath,
94
+ signal: opts?.signal,
95
+ onChecksumIssue: opts?.onChecksumIssue,
96
+ deleteArchiveAfterExtract: opts?.deleteArchiveAfterExtract,
97
+ onProgress: opts?.onProgress,
98
+ getDownloadedList: () =>
99
+ listDownloadedModelsByCategory<ModelMetaBase>(category),
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Returns models in the given category that have incomplete extractions (e.g. after app
105
+ * crash during extraction). Use with resumeExtraction to continue.
106
+ */
107
+ export async function getIncompleteExtractions(
108
+ category: ModelCategory
109
+ ): Promise<ExtractionState[]> {
110
+ const baseDir = getModelsBaseDir(category);
111
+ const baseExists = await exists(baseDir);
112
+ if (!baseExists) return [];
113
+
114
+ let entries: { name: string; isDirectory: () => boolean }[];
115
+ try {
116
+ entries = await readDir(baseDir);
117
+ } catch {
118
+ return [];
119
+ }
120
+
121
+ const results: ExtractionState[] = [];
122
+ for (const entry of entries) {
123
+ const name = entry.name;
124
+ if (
125
+ !name.startsWith(EXTRACTION_STATE_PREFIX) ||
126
+ !name.endsWith(EXTRACTION_STATE_SUFFIX)
127
+ ) {
128
+ continue;
129
+ }
130
+ const modelId = name.slice(
131
+ EXTRACTION_STATE_PREFIX.length,
132
+ name.length - EXTRACTION_STATE_SUFFIX.length
133
+ );
134
+ const statePath = getExtractionStatePath(category, modelId);
135
+ let state: ExtractionState;
136
+ try {
137
+ const raw = await readFile(statePath, 'utf8');
138
+ state = JSON.parse(raw) as ExtractionState;
139
+ } catch {
140
+ continue;
141
+ }
142
+ const readyPath = getReadyMarkerPath(category, modelId);
143
+ if (await exists(readyPath)) continue;
144
+ try {
145
+ const archiveExists = await exists(state.archivePath);
146
+ if (!archiveExists) continue;
147
+ const st = await stat(state.archivePath);
148
+ if (
149
+ state.model.bytes > 0 &&
150
+ st.size != null &&
151
+ st.size < state.model.bytes
152
+ ) {
153
+ continue;
154
+ }
155
+ } catch {
156
+ continue;
157
+ }
158
+ results.push(state);
159
+ }
160
+ return results;
161
+ }
162
+
163
+ /**
164
+ * Resume an incomplete extraction (e.g. after app restart). Use getIncompleteExtractions
165
+ * to discover items to resume. Runs extraction from the start (archive is overwritten into
166
+ * model dir with force).
167
+ */
168
+ export async function resumeExtraction<T extends ModelMetaBase>(
169
+ category: ModelCategory,
170
+ id: string,
171
+ opts?: {
172
+ onProgress?: (progress: DownloadProgress) => void;
173
+ signal?: AbortSignal;
174
+ onChecksumIssue?: (issue: ChecksumIssue) => Promise<boolean>;
175
+ deleteArchiveAfterExtract?: boolean;
176
+ }
177
+ ): Promise<DownloadResult> {
178
+ const statePath = getExtractionStatePath(category, id);
179
+ const stateExists = await exists(statePath);
180
+ if (!stateExists) {
181
+ return extractModelByCategory<T>(category, id, opts);
182
+ }
183
+ let state: ExtractionState;
184
+ try {
185
+ const raw = await readFile(statePath, 'utf8');
186
+ state = JSON.parse(raw) as ExtractionState;
187
+ } catch {
188
+ return extractModelByCategory<T>(category, id, opts);
189
+ }
190
+ if (state.modelId !== id || state.category !== category) {
191
+ return extractModelByCategory<T>(category, id, opts);
192
+ }
193
+ const readyPath = getReadyMarkerPath(category, id);
194
+ if (await exists(readyPath)) {
195
+ try {
196
+ await unlink(statePath);
197
+ } catch {
198
+ // non-fatal
199
+ }
200
+ const localPath =
201
+ (await getLocalModelPathByCategory(category, id)) ??
202
+ (await resolveActualModelDir(state.modelDir));
203
+ return { modelId: id, localPath };
204
+ }
205
+
206
+ return runPostDownloadProcessing({
207
+ category: state.category,
208
+ id: state.modelId,
209
+ model: state.model,
210
+ downloadPath: state.archivePath,
211
+ modelDir: state.modelDir,
212
+ isArchive: true,
213
+ statePath,
214
+ signal: opts?.signal,
215
+ onChecksumIssue: opts?.onChecksumIssue,
216
+ deleteArchiveAfterExtract: opts?.deleteArchiveAfterExtract,
217
+ onProgress: opts?.onProgress,
218
+ getDownloadedList: () =>
219
+ listDownloadedModelsByCategory<ModelMetaBase>(category),
220
+ });
221
+ }
222
+
223
+ /**
224
+ * Cancel/delete an incomplete extraction: removes extraction state and partial model dir.
225
+ * Does not delete the archive so the user can retry extraction later.
226
+ */
227
+ export async function deleteIncompleteExtraction(
228
+ category: ModelCategory,
229
+ id: string
230
+ ): Promise<void> {
231
+ const statePath = getExtractionStatePath(category, id);
232
+ try {
233
+ if (await exists(statePath)) await unlink(statePath);
234
+ } catch {
235
+ // non-fatal
236
+ }
237
+ const modelDir = getModelDir(category, id);
238
+ try {
239
+ if (await exists(modelDir)) await unlink(modelDir);
240
+ } catch {
241
+ // non-fatal
242
+ }
243
+ await removeDirectoryRecursive(getNativeAssetExtractedModelDir(id));
244
+ }
@@ -0,0 +1,134 @@
1
+ import { DocumentDirectoryPath } from '@dr.pogodin/react-native-fs';
2
+ import { ModelCategory } from './types';
3
+ import type { ModelArchiveExt } from './types';
4
+ import { RELEASE_API_BASE } from './constants';
5
+
6
+ export const CATEGORY_CONFIG: Record<
7
+ ModelCategory,
8
+ { tag: string; cacheFile: string; baseDir: string }
9
+ > = {
10
+ [ModelCategory.Tts]: {
11
+ tag: 'tts-models',
12
+ cacheFile: 'tts-models.json',
13
+ baseDir: `${DocumentDirectoryPath}/sherpa-onnx/models/tts`,
14
+ },
15
+ [ModelCategory.Stt]: {
16
+ tag: 'asr-models',
17
+ cacheFile: 'asr-models.json',
18
+ baseDir: `${DocumentDirectoryPath}/sherpa-onnx/models/stt`,
19
+ },
20
+ [ModelCategory.Vad]: {
21
+ tag: 'asr-models',
22
+ cacheFile: 'vad-models.json',
23
+ baseDir: `${DocumentDirectoryPath}/sherpa-onnx/models/vad`,
24
+ },
25
+ [ModelCategory.Diarization]: {
26
+ tag: 'speaker-segmentation-models',
27
+ cacheFile: 'diarization-models.json',
28
+ baseDir: `${DocumentDirectoryPath}/sherpa-onnx/models/diarization`,
29
+ },
30
+ [ModelCategory.Enhancement]: {
31
+ tag: 'speech-enhancement-models',
32
+ cacheFile: 'enhancement-models.json',
33
+ baseDir: `${DocumentDirectoryPath}/sherpa-onnx/models/enhancement`,
34
+ },
35
+ [ModelCategory.Separation]: {
36
+ tag: 'source-separation-models',
37
+ cacheFile: 'separation-models.json',
38
+ baseDir: `${DocumentDirectoryPath}/sherpa-onnx/models/separation`,
39
+ },
40
+ [ModelCategory.Qnn]: {
41
+ tag: 'asr-models-qnn-binary',
42
+ cacheFile: 'qnn-models.json',
43
+ baseDir: `${DocumentDirectoryPath}/sherpa-onnx/models/qnn`,
44
+ },
45
+ };
46
+
47
+ export function getCacheDir(): string {
48
+ return `${DocumentDirectoryPath}/sherpa-onnx/cache`;
49
+ }
50
+
51
+ export function getCachePath(category: ModelCategory): string {
52
+ return `${getCacheDir()}/${CATEGORY_CONFIG[category].cacheFile}`;
53
+ }
54
+
55
+ export function getModelsBaseDir(category: ModelCategory): string {
56
+ return CATEGORY_CONFIG[category].baseDir;
57
+ }
58
+
59
+ export function getModelDir(category: ModelCategory, modelId: string): string {
60
+ return `${getModelsBaseDir(category)}/${modelId}`;
61
+ }
62
+
63
+ export function getArchiveFilename(
64
+ modelId: string,
65
+ archiveExt: ModelArchiveExt
66
+ ): string {
67
+ return `${modelId}.${archiveExt}`;
68
+ }
69
+
70
+ export function getArchivePath(
71
+ category: ModelCategory,
72
+ modelId: string,
73
+ archiveExt: ModelArchiveExt
74
+ ): string {
75
+ const filename = getArchiveFilename(modelId, archiveExt);
76
+ if (archiveExt === 'onnx') {
77
+ return `${getModelDir(category, modelId)}/${filename}`;
78
+ }
79
+ return `${getModelsBaseDir(category)}/${filename}`;
80
+ }
81
+
82
+ export function getTarArchivePath(
83
+ category: ModelCategory,
84
+ modelId: string
85
+ ): string {
86
+ return getArchivePath(category, modelId, 'tar.bz2');
87
+ }
88
+
89
+ export function getOnnxPath(category: ModelCategory, modelId: string): string {
90
+ return getArchivePath(category, modelId, 'onnx');
91
+ }
92
+
93
+ export function getReadyMarkerPath(
94
+ category: ModelCategory,
95
+ modelId: string
96
+ ): string {
97
+ return `${getModelDir(category, modelId)}/.ready`;
98
+ }
99
+
100
+ export function getManifestPath(
101
+ category: ModelCategory,
102
+ modelId: string
103
+ ): string {
104
+ return `${getModelDir(category, modelId)}/manifest.json`;
105
+ }
106
+
107
+ export function getDownloadStatePath(
108
+ category: ModelCategory,
109
+ modelId: string
110
+ ): string {
111
+ return `${getModelsBaseDir(category)}/.download-state-${modelId}.json`;
112
+ }
113
+
114
+ /** Path to extraction state file; used to detect and resume incomplete extractions after app restart. */
115
+ export function getExtractionStatePath(
116
+ category: ModelCategory,
117
+ modelId: string
118
+ ): string {
119
+ return `${getModelsBaseDir(category)}/.extraction-state-${modelId}.json`;
120
+ }
121
+
122
+ /**
123
+ * Directory where native `resolveAssetPath` materializes a bundled model folder
124
+ * (`DocumentDirectoryPath/models/{modelId}` — Android internal `files/models/...`).
125
+ * Separate from {@link getModelDir}. Remove on delete so empty dirs do not break detection.
126
+ */
127
+ export function getNativeAssetExtractedModelDir(modelId: string): string {
128
+ const safeId = modelId.replace(/[/\\]/g, '');
129
+ return `${DocumentDirectoryPath}/models/${safeId}`.replace(/\/+/g, '/');
130
+ }
131
+
132
+ export function getReleaseUrl(category: ModelCategory): string {
133
+ return `${RELEASE_API_BASE}/${CATEGORY_CONFIG[category].tag}`;
134
+ }