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.
- package/LICENSE +1 -0
- package/README.md +90 -21
- package/SherpaOnnx.podspec +3 -0
- package/THIRD_PARTY_LICENSES/README.md +62 -0
- package/THIRD_PARTY_LICENSES/ffmpeg.txt +502 -0
- package/THIRD_PARTY_LICENSES/libarchive.txt +65 -0
- package/THIRD_PARTY_LICENSES/nvidia_omla.txt +181 -0
- package/THIRD_PARTY_LICENSES/onnxruntime.txt +21 -0
- package/THIRD_PARTY_LICENSES/opus.txt +44 -0
- package/THIRD_PARTY_LICENSES/sherpa-onnx.txt +201 -0
- package/THIRD_PARTY_LICENSES/shine.txt +482 -0
- package/THIRD_PARTY_LICENSES/zstd.txt +30 -0
- package/android/build.gradle +7 -3
- package/android/prebuilt-download.gradle +345 -153
- package/android/prebuilt-versions.gradle +2 -2
- package/android/src/main/assets/model_licenses/asr-models-license-status.csv +409 -0
- package/android/src/main/assets/model_licenses/qnn-asr-models-license-status.csv +695 -0
- package/android/src/main/assets/model_licenses/tts-models-license-status.csv +596 -0
- package/android/src/main/cpp/CMakeLists.txt +28 -10
- package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-helper.cpp +306 -6
- package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-helper.h +33 -4
- package/android/src/main/cpp/jni/archive/sherpa-onnx-archive-jni.cpp +266 -7
- package/android/src/main/cpp/jni/audio/sherpa-onnx-audio-convert-jni.cpp +268 -2
- package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-tts.cpp +6 -2
- package/android/src/main/cpp/jni/model_detect/sherpa-onnx-validate-tts.cpp +4 -2
- package/android/src/main/java/com/sherpaonnx/SherpaOnnxArchiveHelper.kt +137 -7
- package/android/src/main/java/com/sherpaonnx/SherpaOnnxAssetHelper.kt +51 -6
- package/android/src/main/java/com/sherpaonnx/SherpaOnnxModule.kt +159 -0
- package/android/src/main/java/com/sherpaonnx/SherpaOnnxOnlineSttHelper.kt +4 -1
- package/android/src/main/java/com/sherpaonnx/SherpaOnnxTtsHelper.kt +112 -97
- package/ios/Resources/model_licenses/asr-models-license-status.csv +409 -0
- package/ios/Resources/model_licenses/qnn-asr-models-license-status.csv +695 -0
- package/ios/Resources/model_licenses/tts-models-license-status.csv +596 -0
- package/ios/SherpaOnnx+OnlineSTT.mm +2 -0
- package/ios/SherpaOnnx+PcmLiveStream.mm +2 -29
- package/ios/SherpaOnnx+TTS.mm +178 -20
- package/ios/SherpaOnnx.mm +108 -1
- package/ios/SherpaOnnxAudioConvert.h +10 -0
- package/ios/SherpaOnnxAudioConvert.mm +257 -1
- package/ios/archive/sherpa-onnx-archive-helper.h +10 -0
- package/ios/archive/sherpa-onnx-archive-helper.mm +56 -5
- package/ios/model_detect/sherpa-onnx-model-detect-tts.mm +13 -2
- package/ios/model_detect/sherpa-onnx-validate-tts.mm +4 -2
- package/ios/online_stt/sherpa-onnx-online-stt-wrapper.h +1 -0
- package/ios/online_stt/sherpa-onnx-online-stt-wrapper.mm +4 -0
- package/ios/tts/sherpa-onnx-tts-wrapper.h +37 -0
- package/ios/tts/sherpa-onnx-tts-wrapper.mm +149 -3
- package/lib/module/NativeSherpaOnnx.js.map +1 -1
- package/lib/module/audio/index.js +8 -0
- package/lib/module/audio/index.js.map +1 -1
- package/lib/module/download/ModelDownloadManager.js +10 -929
- package/lib/module/download/ModelDownloadManager.js.map +1 -1
- package/lib/module/download/activeModelOperations.js +26 -0
- package/lib/module/download/activeModelOperations.js.map +1 -0
- package/lib/module/download/background-downloader.d.js +2 -0
- package/lib/module/download/background-downloader.d.js.map +1 -0
- package/lib/module/download/bulkPurge.js +72 -0
- package/lib/module/download/bulkPurge.js.map +1 -0
- package/lib/module/download/checksumPrompt.js +19 -0
- package/lib/module/download/checksumPrompt.js.map +1 -0
- package/lib/module/download/constants.js +7 -0
- package/lib/module/download/constants.js.map +1 -0
- package/lib/module/download/downloadEvents.js +35 -0
- package/lib/module/download/downloadEvents.js.map +1 -0
- package/lib/module/download/downloadTask.js +385 -0
- package/lib/module/download/downloadTask.js.map +1 -0
- package/lib/module/download/ensureModel.js +89 -0
- package/lib/module/download/ensureModel.js.map +1 -0
- package/lib/module/download/index.js +4 -3
- package/lib/module/download/index.js.map +1 -1
- package/lib/module/download/localModels.js +151 -0
- package/lib/module/download/localModels.js.map +1 -0
- package/lib/module/download/modelExtraction.js +174 -0
- package/lib/module/download/modelExtraction.js.map +1 -0
- package/lib/module/download/paths.js +98 -0
- package/lib/module/download/paths.js.map +1 -0
- package/lib/module/download/postDownloadProcessing.js +206 -0
- package/lib/module/download/postDownloadProcessing.js.map +1 -0
- package/lib/module/download/protectedModelKeys.js +31 -0
- package/lib/module/download/protectedModelKeys.js.map +1 -0
- package/lib/module/download/registry.js +267 -0
- package/lib/module/download/registry.js.map +1 -0
- package/lib/module/download/retry.js +59 -0
- package/lib/module/download/retry.js.map +1 -0
- package/lib/module/download/types.js +17 -0
- package/lib/module/download/types.js.map +1 -0
- package/lib/module/download/validation.js +101 -5
- package/lib/module/download/validation.js.map +1 -1
- package/lib/module/{download → extraction}/extractTarBz2.js +3 -1
- package/lib/module/extraction/extractTarBz2.js.map +1 -0
- package/lib/module/extraction/extractTarZst.js +54 -0
- package/lib/module/extraction/extractTarZst.js.map +1 -0
- package/lib/module/extraction/index.js +190 -0
- package/lib/module/extraction/index.js.map +1 -0
- package/lib/module/extraction/types.js +2 -0
- package/lib/module/extraction/types.js.map +1 -0
- package/lib/module/index.js +2 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/licenses.js +63 -0
- package/lib/module/licenses.js.map +1 -0
- package/lib/module/stt/index.js +16 -2
- package/lib/module/stt/index.js.map +1 -1
- package/lib/module/stt/streaming.js +2 -0
- package/lib/module/stt/streaming.js.map +1 -1
- package/lib/module/stt/streamingTypes.js.map +1 -1
- package/lib/module/stt/types.js.map +1 -1
- package/lib/module/tts/index.js +20 -2
- package/lib/module/tts/index.js.map +1 -1
- package/lib/module/tts/streaming.js +4 -0
- package/lib/module/tts/streaming.js.map +1 -1
- package/lib/module/tts/types.js.map +1 -1
- package/lib/module/utils.js +16 -1
- package/lib/module/utils.js.map +1 -1
- package/lib/typescript/src/NativeSherpaOnnx.d.ts +72 -5
- package/lib/typescript/src/NativeSherpaOnnx.d.ts.map +1 -1
- package/lib/typescript/src/audio/index.d.ts +10 -0
- package/lib/typescript/src/audio/index.d.ts.map +1 -1
- package/lib/typescript/src/download/ModelDownloadManager.d.ts +10 -108
- package/lib/typescript/src/download/ModelDownloadManager.d.ts.map +1 -1
- package/lib/typescript/src/download/activeModelOperations.d.ts +6 -0
- package/lib/typescript/src/download/activeModelOperations.d.ts.map +1 -0
- package/lib/typescript/src/download/bulkPurge.d.ts +14 -0
- package/lib/typescript/src/download/bulkPurge.d.ts.map +1 -0
- package/lib/typescript/src/download/checksumPrompt.d.ts +3 -0
- package/lib/typescript/src/download/checksumPrompt.d.ts.map +1 -0
- package/lib/typescript/src/download/constants.d.ts +5 -0
- package/lib/typescript/src/download/constants.d.ts.map +1 -0
- package/lib/typescript/src/download/downloadEvents.d.ts +6 -0
- package/lib/typescript/src/download/downloadEvents.d.ts.map +1 -0
- package/lib/typescript/src/download/downloadTask.d.ts +20 -0
- package/lib/typescript/src/download/downloadTask.d.ts.map +1 -0
- package/lib/typescript/src/download/ensureModel.d.ts +26 -0
- package/lib/typescript/src/download/ensureModel.d.ts.map +1 -0
- package/lib/typescript/src/download/index.d.ts +7 -5
- package/lib/typescript/src/download/index.d.ts.map +1 -1
- package/lib/typescript/src/download/localModels.d.ts +15 -0
- package/lib/typescript/src/download/localModels.d.ts.map +1 -0
- package/lib/typescript/src/download/modelExtraction.d.ts +36 -0
- package/lib/typescript/src/download/modelExtraction.d.ts.map +1 -0
- package/lib/typescript/src/download/paths.d.ts +28 -0
- package/lib/typescript/src/download/paths.d.ts.map +1 -0
- package/lib/typescript/src/download/postDownloadProcessing.d.ts +19 -0
- package/lib/typescript/src/download/postDownloadProcessing.d.ts.map +1 -0
- package/lib/typescript/src/download/protectedModelKeys.d.ts +6 -0
- package/lib/typescript/src/download/protectedModelKeys.d.ts.map +1 -0
- package/lib/typescript/src/download/registry.d.ts +14 -0
- package/lib/typescript/src/download/registry.d.ts.map +1 -0
- package/lib/typescript/src/download/retry.d.ts +15 -0
- package/lib/typescript/src/download/retry.d.ts.map +1 -0
- package/lib/typescript/src/download/types.d.ts +96 -0
- package/lib/typescript/src/download/types.d.ts.map +1 -0
- package/lib/typescript/src/download/validation.d.ts +19 -0
- package/lib/typescript/src/download/validation.d.ts.map +1 -1
- package/lib/typescript/src/extraction/extractTarBz2.d.ts.map +1 -0
- package/lib/typescript/src/extraction/extractTarZst.d.ts +14 -0
- package/lib/typescript/src/extraction/extractTarZst.d.ts.map +1 -0
- package/lib/typescript/src/extraction/index.d.ts +50 -0
- package/lib/typescript/src/extraction/index.d.ts.map +1 -0
- package/lib/typescript/src/extraction/types.d.ts +60 -0
- package/lib/typescript/src/extraction/types.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/licenses.d.ts +10 -0
- package/lib/typescript/src/licenses.d.ts.map +1 -0
- package/lib/typescript/src/stt/index.d.ts +4 -1
- package/lib/typescript/src/stt/index.d.ts.map +1 -1
- package/lib/typescript/src/stt/streaming.d.ts.map +1 -1
- package/lib/typescript/src/stt/streamingTypes.d.ts +5 -0
- package/lib/typescript/src/stt/streamingTypes.d.ts.map +1 -1
- package/lib/typescript/src/stt/types.d.ts +3 -1
- package/lib/typescript/src/stt/types.d.ts.map +1 -1
- package/lib/typescript/src/tts/index.d.ts +3 -1
- package/lib/typescript/src/tts/index.d.ts.map +1 -1
- package/lib/typescript/src/tts/streaming.d.ts.map +1 -1
- package/lib/typescript/src/tts/types.d.ts +6 -5
- package/lib/typescript/src/tts/types.d.ts.map +1 -1
- package/lib/typescript/src/utils.d.ts +5 -0
- package/lib/typescript/src/utils.d.ts.map +1 -1
- package/package.json +11 -1
- package/scripts/{check-model-csvs.sh → ci/check-model-csvs.sh} +9 -2
- package/scripts/ci/collect_all_sherpa_model_streams.sh +101 -0
- package/scripts/ci/collect_one_sherpa_release_stream.sh +189 -0
- package/scripts/ci/sherpa_asr_model_release_streams.json +21 -0
- package/scripts/ci/sherpa_tts_model_release_streams.json +13 -0
- package/scripts/ci/update_model_license_csv.sh +765 -0
- package/scripts/setup-ios-framework.sh +14 -11
- package/scripts/update_commercial_use.js +73 -0
- package/src/NativeSherpaOnnx.ts +92 -5
- package/src/audio/index.ts +20 -0
- package/src/download/ModelDownloadManager.ts +55 -1343
- package/src/download/activeModelOperations.ts +38 -0
- package/src/download/background-downloader.d.ts +43 -0
- package/src/download/bulkPurge.ts +102 -0
- package/src/download/checksumPrompt.ts +25 -0
- package/src/download/constants.ts +5 -0
- package/src/download/downloadEvents.ts +55 -0
- package/src/download/downloadTask.ts +497 -0
- package/src/download/ensureModel.ts +124 -0
- package/src/download/index.ts +19 -2
- package/src/download/localModels.ts +234 -0
- package/src/download/modelExtraction.ts +244 -0
- package/src/download/paths.ts +134 -0
- package/src/download/postDownloadProcessing.ts +292 -0
- package/src/download/protectedModelKeys.ts +30 -0
- package/src/download/registry.ts +404 -0
- package/src/download/retry.ts +76 -0
- package/src/download/types.ts +120 -0
- package/src/download/validation.ts +114 -8
- package/src/{download → extraction}/extractTarBz2.ts +3 -1
- package/src/extraction/extractTarZst.ts +79 -0
- package/src/extraction/index.ts +269 -0
- package/src/extraction/types.ts +63 -0
- package/src/index.tsx +2 -0
- package/src/licenses.ts +100 -0
- package/src/stt/index.ts +20 -2
- package/src/stt/streaming.ts +3 -0
- package/src/stt/streamingTypes.ts +5 -0
- package/src/stt/types.ts +3 -1
- package/src/tts/index.ts +30 -2
- package/src/tts/streaming.ts +10 -0
- package/src/tts/types.ts +6 -5
- package/src/utils.ts +22 -1
- package/third_party/libarchive_prebuilt/ANDROID_RELEASE_TAG +1 -1
- package/third_party/libarchive_prebuilt/IOS_RELEASE_TAG +1 -1
- package/third_party/sherpa-onnx-prebuilt/ANDROID_RELEASE_TAG +1 -1
- package/third_party/sherpa-onnx-prebuilt/IOS_RELEASE_TAG +1 -1
- package/android/src/main/cpp/jni/tts/sherpa-onnx-tts-zipvoice-jni.cpp +0 -301
- package/android/src/main/java/com/sherpaonnx/ZipvoiceTtsWrapper.kt +0 -187
- package/lib/module/download/extractTarBz2.js.map +0 -1
- package/lib/typescript/src/download/extractTarBz2.d.ts.map +0 -1
- package/scripts/check-qnn-support.sh +0 -78
- /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
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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)
|
package/src/licenses.ts
ADDED
|
@@ -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
|
+
}
|