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
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import {
|
|
2
|
+
exists,
|
|
3
|
+
readFile,
|
|
4
|
+
mkdir,
|
|
5
|
+
writeFile,
|
|
6
|
+
} from '@dr.pogodin/react-native-fs';
|
|
7
|
+
import { ModelCategory } from './types';
|
|
8
|
+
import type {
|
|
9
|
+
ModelMetaBase,
|
|
10
|
+
ModelArchiveExt,
|
|
11
|
+
CachePayload,
|
|
12
|
+
CacheStatus,
|
|
13
|
+
TtsModelType,
|
|
14
|
+
Quantization,
|
|
15
|
+
SizeTier,
|
|
16
|
+
TtsModelMeta,
|
|
17
|
+
} from './types';
|
|
18
|
+
import {
|
|
19
|
+
CACHE_TTL_MINUTES,
|
|
20
|
+
MODEL_ARCHIVE_EXT,
|
|
21
|
+
MODEL_ONNX_EXT,
|
|
22
|
+
} from './constants';
|
|
23
|
+
import {
|
|
24
|
+
getCacheDir,
|
|
25
|
+
getCachePath,
|
|
26
|
+
getArchiveFilename,
|
|
27
|
+
getReleaseUrl,
|
|
28
|
+
CATEGORY_CONFIG,
|
|
29
|
+
} from './paths';
|
|
30
|
+
import { parseChecksumFile } from './validation';
|
|
31
|
+
import { retryWithBackoff } from './retry';
|
|
32
|
+
import { emitModelsListUpdated } from './downloadEvents';
|
|
33
|
+
|
|
34
|
+
const memoryCacheByCategory: Partial<
|
|
35
|
+
Record<ModelCategory, CachePayload<ModelMetaBase>>
|
|
36
|
+
> = {};
|
|
37
|
+
|
|
38
|
+
const checksumCacheByCategory: Partial<
|
|
39
|
+
Record<ModelCategory, Map<string, string>>
|
|
40
|
+
> = {};
|
|
41
|
+
|
|
42
|
+
export async function fetchChecksumsFromRelease(
|
|
43
|
+
category: ModelCategory
|
|
44
|
+
): Promise<Map<string, string>> {
|
|
45
|
+
if (category === ModelCategory.Qnn) {
|
|
46
|
+
return new Map<string, string>();
|
|
47
|
+
}
|
|
48
|
+
if (checksumCacheByCategory[category]) {
|
|
49
|
+
return checksumCacheByCategory[category]!;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const checksums = await retryWithBackoff(
|
|
53
|
+
async () => {
|
|
54
|
+
const response = await fetch(
|
|
55
|
+
`https://github.com/k2-fsa/sherpa-onnx/releases/download/${CATEGORY_CONFIG[category].tag}/checksum.txt`
|
|
56
|
+
);
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Failed to fetch checksum.txt for ${category}: ${response.status}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const content = await response.text();
|
|
63
|
+
return parseChecksumFile(content);
|
|
64
|
+
},
|
|
65
|
+
{ maxRetries: 3, initialDelayMs: 1000 }
|
|
66
|
+
);
|
|
67
|
+
checksumCacheByCategory[category] = checksums;
|
|
68
|
+
return checksums;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.warn(
|
|
71
|
+
`SherpaOnnxChecksum: Error fetching checksums for ${category}:`,
|
|
72
|
+
error
|
|
73
|
+
);
|
|
74
|
+
return new Map();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function toTitleCase(value: string): string {
|
|
79
|
+
return value
|
|
80
|
+
.split(/[-_\s]+/g)
|
|
81
|
+
.filter(Boolean)
|
|
82
|
+
.map((token) => token[0]!.toUpperCase() + token.slice(1))
|
|
83
|
+
.join(' ');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function deriveDisplayName(id: string): string {
|
|
87
|
+
const cleaned = id.replace(/^sherpa-onnx-/, '');
|
|
88
|
+
return toTitleCase(cleaned);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function deriveType(id: string): TtsModelType {
|
|
92
|
+
const lower = id.toLowerCase();
|
|
93
|
+
if (lower.includes('vits')) return 'vits';
|
|
94
|
+
if (lower.includes('kokoro')) return 'kokoro';
|
|
95
|
+
if (lower.includes('matcha')) return 'matcha';
|
|
96
|
+
if (lower.includes('kitten')) return 'kitten';
|
|
97
|
+
if (lower.includes('pocket')) return 'pocket';
|
|
98
|
+
if (lower.includes('zipvoice')) return 'zipvoice';
|
|
99
|
+
return 'unknown';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function deriveQuantization(id: string): Quantization {
|
|
103
|
+
const lower = id.toLowerCase();
|
|
104
|
+
if (lower.includes('int8') && lower.includes('quant')) {
|
|
105
|
+
return 'int8-quantized';
|
|
106
|
+
}
|
|
107
|
+
if (lower.includes('int8')) return 'int8';
|
|
108
|
+
if (lower.includes('fp16')) return 'fp16';
|
|
109
|
+
return 'unknown';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function deriveSizeTier(id: string): SizeTier {
|
|
113
|
+
const lower = id.toLowerCase();
|
|
114
|
+
if (lower.includes('tiny')) return 'tiny';
|
|
115
|
+
if (lower.includes('small')) return 'small';
|
|
116
|
+
if (lower.includes('medium')) return 'medium';
|
|
117
|
+
if (lower.includes('large')) return 'large';
|
|
118
|
+
if (lower.includes('low')) return 'small';
|
|
119
|
+
return 'unknown';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function deriveLanguages(id: string): string[] {
|
|
123
|
+
const tokens = id.split(/[-_]+/g);
|
|
124
|
+
const languages = new Set<string>();
|
|
125
|
+
for (const token of tokens) {
|
|
126
|
+
if (/^[a-z]{2}$/.test(token)) {
|
|
127
|
+
languages.add(token);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (/^[a-z]{2}[A-Z]{2}$/.test(token)) {
|
|
131
|
+
languages.add(token.slice(0, 2).toLowerCase());
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (/^[a-z]{2}-[A-Z]{2}$/.test(token)) {
|
|
135
|
+
languages.add(token.slice(0, 2).toLowerCase());
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return Array.from(languages);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function getAssetExtension(name: string): ModelArchiveExt | null {
|
|
142
|
+
if (name.endsWith(MODEL_ARCHIVE_EXT)) return 'tar.bz2';
|
|
143
|
+
if (name.endsWith(MODEL_ONNX_EXT)) return 'onnx';
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function stripAssetExtension(name: string, ext: ModelArchiveExt): string {
|
|
148
|
+
const suffix = `.${ext}`;
|
|
149
|
+
return name.endsWith(suffix) ? name.slice(0, -suffix.length) : name;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function isAssetSupportedForCategory(
|
|
153
|
+
category: ModelCategory,
|
|
154
|
+
name: string,
|
|
155
|
+
ext: ModelArchiveExt
|
|
156
|
+
): boolean {
|
|
157
|
+
const lower = name.toLowerCase();
|
|
158
|
+
switch (category) {
|
|
159
|
+
case ModelCategory.Tts:
|
|
160
|
+
return ext === 'tar.bz2';
|
|
161
|
+
case ModelCategory.Stt:
|
|
162
|
+
return ext === 'tar.bz2' && !lower.includes('vad');
|
|
163
|
+
case ModelCategory.Vad:
|
|
164
|
+
return ext === 'onnx' && lower.includes('vad');
|
|
165
|
+
case ModelCategory.Diarization:
|
|
166
|
+
return ext === 'tar.bz2';
|
|
167
|
+
case ModelCategory.Enhancement:
|
|
168
|
+
return ext === 'onnx';
|
|
169
|
+
case ModelCategory.Separation:
|
|
170
|
+
return ext === 'tar.bz2' || ext === 'onnx';
|
|
171
|
+
case ModelCategory.Qnn:
|
|
172
|
+
return (
|
|
173
|
+
ext === 'tar.bz2' &&
|
|
174
|
+
lower.includes('sherpa-onnx-qnn') &&
|
|
175
|
+
lower.includes('binary') &&
|
|
176
|
+
lower.includes('seconds')
|
|
177
|
+
);
|
|
178
|
+
default:
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function parseDigestSha256(value?: string | null): string | undefined {
|
|
184
|
+
if (!value) return undefined;
|
|
185
|
+
const match = value.match(/^sha256:([a-f0-9]{64})$/i);
|
|
186
|
+
return match?.[1]?.toLowerCase();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function toTtsModelMeta(
|
|
190
|
+
asset: {
|
|
191
|
+
name: string;
|
|
192
|
+
size: number;
|
|
193
|
+
browser_download_url: string;
|
|
194
|
+
digest?: string | null;
|
|
195
|
+
},
|
|
196
|
+
archiveExt: ModelArchiveExt
|
|
197
|
+
): TtsModelMeta | null {
|
|
198
|
+
if (archiveExt !== 'tar.bz2') return null;
|
|
199
|
+
const id = stripAssetExtension(asset.name, archiveExt);
|
|
200
|
+
const type = deriveType(id);
|
|
201
|
+
if (type === 'unknown') {
|
|
202
|
+
console.warn('SherpaOnnxModelList: Unsupported model', id);
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
id,
|
|
206
|
+
displayName: deriveDisplayName(id),
|
|
207
|
+
type,
|
|
208
|
+
languages: deriveLanguages(id),
|
|
209
|
+
quantization: deriveQuantization(id),
|
|
210
|
+
sizeTier: deriveSizeTier(id),
|
|
211
|
+
downloadUrl: asset.browser_download_url,
|
|
212
|
+
archiveExt,
|
|
213
|
+
bytes: asset.size,
|
|
214
|
+
sha256: parseDigestSha256(asset.digest),
|
|
215
|
+
category: ModelCategory.Tts,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function toGenericModelMeta(
|
|
220
|
+
category: ModelCategory,
|
|
221
|
+
asset: {
|
|
222
|
+
name: string;
|
|
223
|
+
size: number;
|
|
224
|
+
browser_download_url: string;
|
|
225
|
+
digest?: string | null;
|
|
226
|
+
},
|
|
227
|
+
archiveExt: ModelArchiveExt
|
|
228
|
+
): ModelMetaBase | null {
|
|
229
|
+
const id = stripAssetExtension(asset.name, archiveExt);
|
|
230
|
+
return {
|
|
231
|
+
id,
|
|
232
|
+
displayName: deriveDisplayName(id),
|
|
233
|
+
downloadUrl: asset.browser_download_url,
|
|
234
|
+
archiveExt,
|
|
235
|
+
bytes: asset.size,
|
|
236
|
+
sha256: parseDigestSha256(asset.digest),
|
|
237
|
+
category,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function toModelMeta(
|
|
242
|
+
category: ModelCategory,
|
|
243
|
+
asset: {
|
|
244
|
+
name: string;
|
|
245
|
+
size: number;
|
|
246
|
+
browser_download_url: string;
|
|
247
|
+
digest?: string | null;
|
|
248
|
+
}
|
|
249
|
+
): ModelMetaBase | null {
|
|
250
|
+
const archiveExt = getAssetExtension(asset.name);
|
|
251
|
+
if (!archiveExt) return null;
|
|
252
|
+
if (!isAssetSupportedForCategory(category, asset.name, archiveExt)) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
if (category === ModelCategory.Tts) {
|
|
256
|
+
return toTtsModelMeta(asset, archiveExt);
|
|
257
|
+
}
|
|
258
|
+
return toGenericModelMeta(category, asset, archiveExt);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function loadCacheFromDisk<T extends ModelMetaBase>(
|
|
262
|
+
category: ModelCategory
|
|
263
|
+
): Promise<CachePayload<T> | null> {
|
|
264
|
+
const memoryCache = memoryCacheByCategory[category] as
|
|
265
|
+
| CachePayload<T>
|
|
266
|
+
| undefined;
|
|
267
|
+
if (memoryCache) return memoryCache;
|
|
268
|
+
const cachePath = getCachePath(category);
|
|
269
|
+
const existsResult = await exists(cachePath);
|
|
270
|
+
if (!existsResult) return null;
|
|
271
|
+
const raw = await readFile(cachePath, 'utf8');
|
|
272
|
+
const parsed = JSON.parse(raw) as CachePayload<T>;
|
|
273
|
+
memoryCacheByCategory[category] = parsed as CachePayload<ModelMetaBase>;
|
|
274
|
+
return parsed;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function saveCache<T extends ModelMetaBase>(
|
|
278
|
+
category: ModelCategory,
|
|
279
|
+
payload: CachePayload<T>
|
|
280
|
+
): Promise<void> {
|
|
281
|
+
await mkdir(getCacheDir());
|
|
282
|
+
await writeFile(getCachePath(category), JSON.stringify(payload), 'utf8');
|
|
283
|
+
memoryCacheByCategory[category] = payload as CachePayload<ModelMetaBase>;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function isCacheFresh<T extends ModelMetaBase>(
|
|
287
|
+
payload: CachePayload<T>,
|
|
288
|
+
ttlMinutes: number
|
|
289
|
+
): boolean {
|
|
290
|
+
const updated = new Date(payload.lastUpdated).getTime();
|
|
291
|
+
if (!updated) return false;
|
|
292
|
+
const ageMs = Date.now() - updated;
|
|
293
|
+
return ageMs < ttlMinutes * 60 * 1000;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export async function listModelsByCategory<T extends ModelMetaBase>(
|
|
297
|
+
category: ModelCategory
|
|
298
|
+
): Promise<T[]> {
|
|
299
|
+
const cache = await loadCacheFromDisk<T>(category);
|
|
300
|
+
return cache?.models ?? [];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export async function refreshModelsByCategory<T extends ModelMetaBase>(
|
|
304
|
+
category: ModelCategory,
|
|
305
|
+
options?: {
|
|
306
|
+
forceRefresh?: boolean;
|
|
307
|
+
cacheTtlMinutes?: number;
|
|
308
|
+
maxRetries?: number;
|
|
309
|
+
signal?: AbortSignal;
|
|
310
|
+
}
|
|
311
|
+
): Promise<T[]> {
|
|
312
|
+
const ttl = options?.cacheTtlMinutes ?? CACHE_TTL_MINUTES;
|
|
313
|
+
const cached = await loadCacheFromDisk<T>(category);
|
|
314
|
+
|
|
315
|
+
if (!options?.forceRefresh && cached && isCacheFresh(cached, ttl)) {
|
|
316
|
+
return cached.models;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
const body = await retryWithBackoff(
|
|
321
|
+
async () => {
|
|
322
|
+
const response = await fetch(getReleaseUrl(category));
|
|
323
|
+
if (!response.ok) {
|
|
324
|
+
throw new Error(`Failed to fetch models: ${response.status}`);
|
|
325
|
+
}
|
|
326
|
+
return response.json();
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
maxRetries: options?.maxRetries ?? 3,
|
|
330
|
+
initialDelayMs: 1000,
|
|
331
|
+
signal: options?.signal,
|
|
332
|
+
}
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
const assets = Array.isArray(body?.assets) ? body.assets : [];
|
|
336
|
+
const models: T[] = assets
|
|
337
|
+
.map(
|
|
338
|
+
(asset: {
|
|
339
|
+
name: string;
|
|
340
|
+
size: number;
|
|
341
|
+
browser_download_url: string;
|
|
342
|
+
digest?: string | null;
|
|
343
|
+
}) =>
|
|
344
|
+
toModelMeta(category, {
|
|
345
|
+
name: asset.name,
|
|
346
|
+
size: asset.size,
|
|
347
|
+
browser_download_url: asset.browser_download_url,
|
|
348
|
+
digest: asset.digest,
|
|
349
|
+
})
|
|
350
|
+
)
|
|
351
|
+
.filter((model: ModelMetaBase | null): model is T => Boolean(model));
|
|
352
|
+
|
|
353
|
+
const checksums = await fetchChecksumsFromRelease(category);
|
|
354
|
+
for (const model of models) {
|
|
355
|
+
const archiveFilename = getArchiveFilename(model.id, model.archiveExt);
|
|
356
|
+
const sha256 = checksums.get(archiveFilename);
|
|
357
|
+
if (sha256) {
|
|
358
|
+
model.sha256 = sha256;
|
|
359
|
+
} else if (model.sha256) {
|
|
360
|
+
model.sha256 = model.sha256.toLowerCase();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const payload: CachePayload<T> = {
|
|
365
|
+
lastUpdated: new Date().toISOString(),
|
|
366
|
+
models,
|
|
367
|
+
};
|
|
368
|
+
await saveCache(category, payload);
|
|
369
|
+
emitModelsListUpdated(category, models as ModelMetaBase[]);
|
|
370
|
+
return models;
|
|
371
|
+
} catch (error) {
|
|
372
|
+
if (cached) {
|
|
373
|
+
console.warn(
|
|
374
|
+
`Failed to refresh models for ${category}, using cached data:`,
|
|
375
|
+
error
|
|
376
|
+
);
|
|
377
|
+
return cached.models;
|
|
378
|
+
}
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export async function getModelsCacheStatusByCategory(
|
|
384
|
+
category: ModelCategory
|
|
385
|
+
): Promise<CacheStatus> {
|
|
386
|
+
const cached = await loadCacheFromDisk(category);
|
|
387
|
+
if (!cached) {
|
|
388
|
+
return { lastUpdated: null, source: 'cache' };
|
|
389
|
+
}
|
|
390
|
+
return { lastUpdated: cached.lastUpdated, source: 'cache' };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export async function getModelByIdByCategory<T extends ModelMetaBase>(
|
|
394
|
+
category: ModelCategory,
|
|
395
|
+
id: string
|
|
396
|
+
): Promise<T | null> {
|
|
397
|
+
const models = await listModelsByCategory<T>(category);
|
|
398
|
+
return models.find((model) => model.id === id) ?? null;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function clearMemoryCacheForCategory(category: ModelCategory): void {
|
|
402
|
+
delete memoryCacheByCategory[category];
|
|
403
|
+
delete checksumCacheByCategory[category];
|
|
404
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry helper with exponential backoff
|
|
3
|
+
* @param fn - The async function to retry
|
|
4
|
+
* @param options - Retry configuration
|
|
5
|
+
* @returns The result of the function
|
|
6
|
+
* @throws The last error if all retries fail or AbortError if aborted
|
|
7
|
+
*/
|
|
8
|
+
export async function retryWithBackoff<T>(
|
|
9
|
+
fn: () => Promise<T>,
|
|
10
|
+
options: {
|
|
11
|
+
maxRetries?: number;
|
|
12
|
+
initialDelayMs?: number;
|
|
13
|
+
maxDelayMs?: number;
|
|
14
|
+
backoffFactor?: number;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
} = {}
|
|
17
|
+
): Promise<T> {
|
|
18
|
+
const maxRetries = options.maxRetries ?? 3;
|
|
19
|
+
const initialDelayMs = options.initialDelayMs ?? 1000;
|
|
20
|
+
const maxDelayMs = options.maxDelayMs ?? 10000;
|
|
21
|
+
const backoffFactor = options.backoffFactor ?? 2;
|
|
22
|
+
|
|
23
|
+
let lastError: Error | undefined;
|
|
24
|
+
|
|
25
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
26
|
+
if (options.signal?.aborted) {
|
|
27
|
+
const abortError = new Error('Operation aborted');
|
|
28
|
+
abortError.name = 'AbortError';
|
|
29
|
+
throw abortError;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
return await fn();
|
|
34
|
+
} catch (error) {
|
|
35
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
36
|
+
|
|
37
|
+
// Don't retry on abort
|
|
38
|
+
if (lastError.name === 'AbortError' || options.signal?.aborted) {
|
|
39
|
+
throw lastError;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// If this was the last attempt, throw the error
|
|
43
|
+
if (attempt === maxRetries) {
|
|
44
|
+
throw lastError;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Calculate delay with exponential backoff
|
|
48
|
+
const delayMs = Math.min(
|
|
49
|
+
initialDelayMs * Math.pow(backoffFactor, attempt),
|
|
50
|
+
maxDelayMs
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
console.warn(
|
|
54
|
+
`Retry attempt ${attempt + 1}/${maxRetries} after ${delayMs}ms due to:`,
|
|
55
|
+
lastError.message
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Wait before retrying (abort-aware: cancel the delay if signal fires)
|
|
59
|
+
await new Promise<void>((resolve, reject) => {
|
|
60
|
+
const timer = setTimeout(resolve, delayMs);
|
|
61
|
+
if (options.signal) {
|
|
62
|
+
const onAbort = () => {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
options.signal!.removeEventListener('abort', onAbort);
|
|
65
|
+
const abortErr = new Error('Operation aborted');
|
|
66
|
+
abortErr.name = 'AbortError';
|
|
67
|
+
reject(abortErr);
|
|
68
|
+
};
|
|
69
|
+
options.signal.addEventListener('abort', onAbort);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
throw lastError ?? new Error('Retry failed with no error');
|
|
76
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { TTSModelType } from '../tts/types';
|
|
2
|
+
|
|
3
|
+
export enum ModelCategory {
|
|
4
|
+
Tts = 'tts',
|
|
5
|
+
Stt = 'stt',
|
|
6
|
+
Vad = 'vad',
|
|
7
|
+
Diarization = 'diarization',
|
|
8
|
+
Enhancement = 'enhancement',
|
|
9
|
+
Separation = 'separation',
|
|
10
|
+
Qnn = 'qnn',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** TTS model type for meta; 'unknown' when id could not be classified. */
|
|
14
|
+
export type TtsModelType = TTSModelType | 'unknown';
|
|
15
|
+
|
|
16
|
+
export type Quantization = 'fp16' | 'int8' | 'int8-quantized' | 'unknown';
|
|
17
|
+
|
|
18
|
+
export type SizeTier = 'tiny' | 'small' | 'medium' | 'large' | 'unknown';
|
|
19
|
+
|
|
20
|
+
export type ModelArchiveExt = 'tar.bz2' | 'onnx';
|
|
21
|
+
|
|
22
|
+
export type ModelMetaBase = {
|
|
23
|
+
id: string;
|
|
24
|
+
displayName: string;
|
|
25
|
+
downloadUrl: string;
|
|
26
|
+
archiveExt: ModelArchiveExt;
|
|
27
|
+
bytes: number;
|
|
28
|
+
sha256?: string;
|
|
29
|
+
category: ModelCategory;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type TtsModelMeta = ModelMetaBase & {
|
|
33
|
+
type: TtsModelType;
|
|
34
|
+
languages: string[];
|
|
35
|
+
quantization: Quantization;
|
|
36
|
+
sizeTier: SizeTier;
|
|
37
|
+
category: ModelCategory.Tts;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type DownloadProgress = {
|
|
41
|
+
bytesDownloaded: number;
|
|
42
|
+
totalBytes: number;
|
|
43
|
+
percent: number;
|
|
44
|
+
phase?: 'downloading' | 'extracting';
|
|
45
|
+
speed?: number;
|
|
46
|
+
eta?: number;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type DownloadResult = {
|
|
50
|
+
modelId: string;
|
|
51
|
+
localPath: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type DownloadState = {
|
|
55
|
+
modelId: string;
|
|
56
|
+
category: ModelCategory;
|
|
57
|
+
phase: 'downloading' | 'extracting';
|
|
58
|
+
startedAt: string;
|
|
59
|
+
archivePath: string;
|
|
60
|
+
model: ModelMetaBase;
|
|
61
|
+
bytesDownloaded?: number;
|
|
62
|
+
totalBytes?: number;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** State for an in-progress or interrupted model extraction (archive --> model dir). */
|
|
66
|
+
export type ExtractionState = {
|
|
67
|
+
modelId: string;
|
|
68
|
+
category: ModelCategory;
|
|
69
|
+
phase: 'extracting';
|
|
70
|
+
startedAt: string;
|
|
71
|
+
archivePath: string;
|
|
72
|
+
modelDir: string;
|
|
73
|
+
model: ModelMetaBase;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type DownloadProgressListener = (
|
|
77
|
+
category: ModelCategory,
|
|
78
|
+
modelId: string,
|
|
79
|
+
progress: DownloadProgress
|
|
80
|
+
) => void;
|
|
81
|
+
|
|
82
|
+
export type ModelsListUpdatedListener = (
|
|
83
|
+
category: ModelCategory,
|
|
84
|
+
models: ModelMetaBase[]
|
|
85
|
+
) => void;
|
|
86
|
+
|
|
87
|
+
export type ModelManifest<T extends ModelMetaBase = ModelMetaBase> = {
|
|
88
|
+
downloadedAt: string;
|
|
89
|
+
lastUsed?: string;
|
|
90
|
+
model: T;
|
|
91
|
+
sizeOnDisk?: number;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type ModelWithMetadata<T extends ModelMetaBase = ModelMetaBase> = {
|
|
95
|
+
model: T;
|
|
96
|
+
downloadedAt: string;
|
|
97
|
+
lastUsed: string | null;
|
|
98
|
+
sizeOnDisk?: number;
|
|
99
|
+
status: 'ready' | 'downloading' | 'extracting' | 'failed';
|
|
100
|
+
progress?: number;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export type ChecksumIssue = {
|
|
104
|
+
category: ModelCategory;
|
|
105
|
+
id: string;
|
|
106
|
+
archivePath: string;
|
|
107
|
+
expected?: string;
|
|
108
|
+
message: string;
|
|
109
|
+
reason: 'CHECKSUM_FAILED' | 'CHECKSUM_MISMATCH';
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export type CachePayload<T extends ModelMetaBase> = {
|
|
113
|
+
lastUpdated: string;
|
|
114
|
+
models: T[];
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export type CacheStatus = {
|
|
118
|
+
lastUpdated: string | null;
|
|
119
|
+
source: 'cache' | 'remote';
|
|
120
|
+
};
|