@remotion/whisper-web 4.0.302
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/.turbo/turbo-make.log +6 -0
- package/LICENSE.md +49 -0
- package/README.md +18 -0
- package/build-wasm.ts +117 -0
- package/bundle.ts +15 -0
- package/dist/can-use-whisper-web.d.ts +18 -0
- package/dist/can-use-whisper-web.js +80 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +225 -0
- package/dist/db/delete-object.d.ts +3 -0
- package/dist/db/delete-object.js +13 -0
- package/dist/db/get-object-from-db.d.ts +10 -0
- package/dist/db/get-object-from-db.js +27 -0
- package/dist/db/open-db.d.ts +1 -0
- package/dist/db/open-db.js +53 -0
- package/dist/db/put-object.d.ts +4 -0
- package/dist/db/put-object.js +18 -0
- package/dist/delete-model.d.ts +2 -0
- package/dist/delete-model.js +6 -0
- package/dist/download-model.d.ts +5 -0
- package/dist/download-model.js +32 -0
- package/dist/download-whisper-model.d.ts +15 -0
- package/dist/download-whisper-model.js +48 -0
- package/dist/esm/index.mjs +652 -0
- package/dist/get-loaded-models.d.ts +2 -0
- package/dist/get-loaded-models.js +17 -0
- package/dist/get-model-url.d.ts +9 -0
- package/dist/get-model-url.js +10 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +18 -0
- package/dist/load-mod/load-mod.d.ts +2 -0
- package/dist/load-mod/load-mod.js +6 -0
- package/dist/log.d.ts +10 -0
- package/dist/log.js +33 -0
- package/dist/mod.d.ts +6 -0
- package/dist/mod.js +1 -0
- package/dist/print-handler.d.ts +9 -0
- package/dist/print-handler.js +25 -0
- package/dist/resample-to-16khz.d.ts +8 -0
- package/dist/resample-to-16khz.js +66 -0
- package/dist/result.d.ts +53 -0
- package/dist/result.js +1 -0
- package/dist/simulate-progress.d.ts +9 -0
- package/dist/simulate-progress.js +53 -0
- package/dist/transcribe.d.ts +18 -0
- package/dist/transcribe.js +97 -0
- package/dist/transcription-speed.d.ts +3 -0
- package/dist/transcription-speed.js +13 -0
- package/emscripten.cpp +303 -0
- package/eslint.config.mjs +5 -0
- package/main.d.ts +46 -0
- package/main.js +3 -0
- package/package.json +52 -0
- package/src/can-use-whisper-web.ts +103 -0
- package/src/constants.ts +232 -0
- package/src/db/delete-object.ts +16 -0
- package/src/db/get-object-from-db.ts +43 -0
- package/src/db/open-db.ts +62 -0
- package/src/db/put-object.ts +27 -0
- package/src/delete-model.ts +8 -0
- package/src/download-model.ts +52 -0
- package/src/download-whisper-model.ts +86 -0
- package/src/get-loaded-models.ts +22 -0
- package/src/get-model-url.ts +13 -0
- package/src/index.module.ts +9 -0
- package/src/index.ts +72 -0
- package/src/load-mod/load-mod.ts +11 -0
- package/src/log.ts +41 -0
- package/src/mod.ts +13 -0
- package/src/print-handler.ts +39 -0
- package/src/resample-to-16khz.ts +105 -0
- package/src/result.ts +59 -0
- package/src/simulate-progress.ts +74 -0
- package/src/transcribe.ts +184 -0
- package/src/transcription-speed.ts +21 -0
- package/tsconfig.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/worker.js +3 -0
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"repository": {
|
|
3
|
+
"url": "https://github.com/remotion-dev/remotion/tree/main/packages/whisper-web"
|
|
4
|
+
},
|
|
5
|
+
"name": "@remotion/whisper-web",
|
|
6
|
+
"version": "4.0.302",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"author": "Hunain Ahmed <junaidhunain6@gmail.com>",
|
|
10
|
+
"license": "UNLICENSED",
|
|
11
|
+
"dependencies": {},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"eslint": "9.19.0",
|
|
14
|
+
"@remotion/eslint-config-internal": "4.0.302"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"exports": {
|
|
20
|
+
"./package.json": "./package.json",
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"require": "./dist/index.js",
|
|
24
|
+
"module": "./dist/esm/index.mjs",
|
|
25
|
+
"import": "./dist/esm/index.mjs"
|
|
26
|
+
},
|
|
27
|
+
"./main": {
|
|
28
|
+
"require": "./main.js",
|
|
29
|
+
"module": "./main.js",
|
|
30
|
+
"import": "./main.js"
|
|
31
|
+
},
|
|
32
|
+
"./worker": {
|
|
33
|
+
"require": "./worker.js",
|
|
34
|
+
"module": "./worker.js",
|
|
35
|
+
"import": "./worker.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"remotion",
|
|
40
|
+
"openai",
|
|
41
|
+
"whisper",
|
|
42
|
+
"wasm"
|
|
43
|
+
],
|
|
44
|
+
"homepage": "https://www.remotion.dev/docs/whisper-web",
|
|
45
|
+
"description": "Helpers for using Whisper.cpp in browser using WASM",
|
|
46
|
+
"scripts": {
|
|
47
|
+
"formatting": "prettier src --check",
|
|
48
|
+
"test": "bun test src",
|
|
49
|
+
"lint": "eslint src",
|
|
50
|
+
"make": "tsc -d && bun --env-file=../.env.bundle bundle.ts"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type {WhisperWebModel} from './constants';
|
|
2
|
+
import {SIZES} from './constants';
|
|
3
|
+
|
|
4
|
+
export enum WhisperWebUnsupportedReason {
|
|
5
|
+
WindowUndefined = 'window-undefined',
|
|
6
|
+
IndexedDbUnavailable = 'indexed-db-unavailable',
|
|
7
|
+
NavigatorStorageUnavailable = 'navigator-storage-unavailable',
|
|
8
|
+
StorageEstimationApiUnavailable = 'storage-estimation-api-unavailable',
|
|
9
|
+
QuotaUndefined = 'quota-undefined',
|
|
10
|
+
UsageUndefined = 'usage-undefined',
|
|
11
|
+
NotEnoughSpace = 'not-enough-space',
|
|
12
|
+
ErrorEstimatingStorage = 'error-estimating-storage',
|
|
13
|
+
NotCrossOriginIsolated = 'not-cross-origin-isolated',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CanUseWhisperWebResult {
|
|
17
|
+
supported: boolean;
|
|
18
|
+
reason?: WhisperWebUnsupportedReason;
|
|
19
|
+
detailedReason?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const canUseWhisperWeb = async (
|
|
23
|
+
model: WhisperWebModel,
|
|
24
|
+
): Promise<CanUseWhisperWebResult> => {
|
|
25
|
+
if (typeof window === 'undefined') {
|
|
26
|
+
return {
|
|
27
|
+
supported: false,
|
|
28
|
+
reason: WhisperWebUnsupportedReason.WindowUndefined,
|
|
29
|
+
detailedReason:
|
|
30
|
+
'`window` is not defined. This module can only be used in a browser environment.',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!window.crossOriginIsolated) {
|
|
35
|
+
return {
|
|
36
|
+
supported: false,
|
|
37
|
+
reason: WhisperWebUnsupportedReason.NotCrossOriginIsolated,
|
|
38
|
+
detailedReason:
|
|
39
|
+
'The document is not cross-origin isolated (window.crossOriginIsolated = false). This prevents the usage of SharedArrayBuffer, which is required by `@remotion/whisper-web`. Make sure the document is served with the HTTP header `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp`: https://developer.mozilla.org/en-US/docs/Web/API/Window/crossOriginIsolated',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!window.indexedDB) {
|
|
44
|
+
return {
|
|
45
|
+
supported: false,
|
|
46
|
+
reason: WhisperWebUnsupportedReason.IndexedDbUnavailable,
|
|
47
|
+
detailedReason: 'IndexedDB is not available in this environment.',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!navigator?.storage || !navigator?.storage.estimate) {
|
|
52
|
+
return {
|
|
53
|
+
supported: false,
|
|
54
|
+
reason: WhisperWebUnsupportedReason.NavigatorStorageUnavailable,
|
|
55
|
+
detailedReason:
|
|
56
|
+
'`navigator.storage.estimate()` API is not available in this environment.',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const estimate = await navigator.storage.estimate();
|
|
62
|
+
|
|
63
|
+
if (estimate.quota === undefined) {
|
|
64
|
+
return {
|
|
65
|
+
supported: false,
|
|
66
|
+
reason: WhisperWebUnsupportedReason.QuotaUndefined,
|
|
67
|
+
detailedReason:
|
|
68
|
+
'navigator.storage.estimate() API returned undefined quota.',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (estimate.usage === undefined) {
|
|
73
|
+
return {
|
|
74
|
+
supported: false,
|
|
75
|
+
reason: WhisperWebUnsupportedReason.UsageUndefined,
|
|
76
|
+
detailedReason:
|
|
77
|
+
'navigator.storage.estimate() API returned undefined usage.',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const remaining = estimate.quota - estimate.usage;
|
|
82
|
+
const modelSize = SIZES[model];
|
|
83
|
+
|
|
84
|
+
if (remaining < modelSize) {
|
|
85
|
+
return {
|
|
86
|
+
supported: false,
|
|
87
|
+
reason: WhisperWebUnsupportedReason.NotEnoughSpace,
|
|
88
|
+
detailedReason: `Not enough space to download the model. Required: ${modelSize} bytes, Available: ${remaining} bytes.`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
93
|
+
return {
|
|
94
|
+
supported: false,
|
|
95
|
+
reason: WhisperWebUnsupportedReason.ErrorEstimatingStorage,
|
|
96
|
+
detailedReason: `Error estimating storage: ${errorMessage}`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
supported: true,
|
|
102
|
+
};
|
|
103
|
+
};
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
export const DB_NAME = 'whisper-web';
|
|
2
|
+
export const DB_VERSION = 1;
|
|
3
|
+
export const DB_OBJECT_STORE_NAME = 'models';
|
|
4
|
+
|
|
5
|
+
export const MODELS = [
|
|
6
|
+
'tiny',
|
|
7
|
+
'tiny.en',
|
|
8
|
+
'base',
|
|
9
|
+
'base.en',
|
|
10
|
+
'small',
|
|
11
|
+
'small.en',
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
export type WhisperWebModel = (typeof MODELS)[number];
|
|
15
|
+
|
|
16
|
+
export const LANGUAGES = [
|
|
17
|
+
// whisper allows passing 'auto' to auto-detect the language
|
|
18
|
+
'auto',
|
|
19
|
+
// short codes
|
|
20
|
+
'en',
|
|
21
|
+
'zh',
|
|
22
|
+
'de',
|
|
23
|
+
'es',
|
|
24
|
+
'ru',
|
|
25
|
+
'ko',
|
|
26
|
+
'fr',
|
|
27
|
+
'ja',
|
|
28
|
+
'pt',
|
|
29
|
+
'tr',
|
|
30
|
+
'pl',
|
|
31
|
+
'ca',
|
|
32
|
+
'nl',
|
|
33
|
+
'ar',
|
|
34
|
+
'sv',
|
|
35
|
+
'it',
|
|
36
|
+
'id',
|
|
37
|
+
'hi',
|
|
38
|
+
'fi',
|
|
39
|
+
'vi',
|
|
40
|
+
'he',
|
|
41
|
+
'uk',
|
|
42
|
+
'el',
|
|
43
|
+
'ms',
|
|
44
|
+
'cs',
|
|
45
|
+
'ro',
|
|
46
|
+
'da',
|
|
47
|
+
'hu',
|
|
48
|
+
'ta',
|
|
49
|
+
'no',
|
|
50
|
+
'th',
|
|
51
|
+
'ur',
|
|
52
|
+
'hr',
|
|
53
|
+
'bg',
|
|
54
|
+
'lt',
|
|
55
|
+
'la',
|
|
56
|
+
'mi',
|
|
57
|
+
'ml',
|
|
58
|
+
'cy',
|
|
59
|
+
'sk',
|
|
60
|
+
'te',
|
|
61
|
+
'fa',
|
|
62
|
+
'lv',
|
|
63
|
+
'bn',
|
|
64
|
+
'sr',
|
|
65
|
+
'az',
|
|
66
|
+
'sl',
|
|
67
|
+
'kn',
|
|
68
|
+
'et',
|
|
69
|
+
'mk',
|
|
70
|
+
'br',
|
|
71
|
+
'eu',
|
|
72
|
+
'is',
|
|
73
|
+
'hy',
|
|
74
|
+
'ne',
|
|
75
|
+
'mn',
|
|
76
|
+
'bs',
|
|
77
|
+
'kk',
|
|
78
|
+
'sq',
|
|
79
|
+
'sw',
|
|
80
|
+
'gl',
|
|
81
|
+
'mr',
|
|
82
|
+
'pa',
|
|
83
|
+
'si',
|
|
84
|
+
'km',
|
|
85
|
+
'sn',
|
|
86
|
+
'yo',
|
|
87
|
+
'so',
|
|
88
|
+
'af',
|
|
89
|
+
'oc',
|
|
90
|
+
'ka',
|
|
91
|
+
'be',
|
|
92
|
+
'tg',
|
|
93
|
+
'sd',
|
|
94
|
+
'gu',
|
|
95
|
+
'am',
|
|
96
|
+
'yi',
|
|
97
|
+
'lo',
|
|
98
|
+
'uz',
|
|
99
|
+
'fo',
|
|
100
|
+
'ht',
|
|
101
|
+
'ps',
|
|
102
|
+
'tk',
|
|
103
|
+
'nn',
|
|
104
|
+
'mt',
|
|
105
|
+
'sa',
|
|
106
|
+
'lb',
|
|
107
|
+
'my',
|
|
108
|
+
'bo',
|
|
109
|
+
'tl',
|
|
110
|
+
'mg',
|
|
111
|
+
'as',
|
|
112
|
+
'tt',
|
|
113
|
+
'haw',
|
|
114
|
+
'ln',
|
|
115
|
+
'ha',
|
|
116
|
+
'ba',
|
|
117
|
+
'jw',
|
|
118
|
+
'su',
|
|
119
|
+
'yue',
|
|
120
|
+
// aliases
|
|
121
|
+
'english',
|
|
122
|
+
'chinese',
|
|
123
|
+
'german',
|
|
124
|
+
'spanish',
|
|
125
|
+
'russian',
|
|
126
|
+
'korean',
|
|
127
|
+
'french',
|
|
128
|
+
'japanese',
|
|
129
|
+
'portuguese',
|
|
130
|
+
'turkish',
|
|
131
|
+
'polish',
|
|
132
|
+
'catalan',
|
|
133
|
+
'dutch',
|
|
134
|
+
'arabic',
|
|
135
|
+
'swedish',
|
|
136
|
+
'italian',
|
|
137
|
+
'indonesian',
|
|
138
|
+
'hindi',
|
|
139
|
+
'finnish',
|
|
140
|
+
'vietnamese',
|
|
141
|
+
'hebrew',
|
|
142
|
+
'ukrainian',
|
|
143
|
+
'greek',
|
|
144
|
+
'malay',
|
|
145
|
+
'czech',
|
|
146
|
+
'romanian',
|
|
147
|
+
'danish',
|
|
148
|
+
'hungarian',
|
|
149
|
+
'tamil',
|
|
150
|
+
'norwegian',
|
|
151
|
+
'thai',
|
|
152
|
+
'urdu',
|
|
153
|
+
'croatian',
|
|
154
|
+
'bulgarian',
|
|
155
|
+
'lithuanian',
|
|
156
|
+
'latin',
|
|
157
|
+
'maori',
|
|
158
|
+
'malayalam',
|
|
159
|
+
'welsh',
|
|
160
|
+
'slovak',
|
|
161
|
+
'telugu',
|
|
162
|
+
'persian',
|
|
163
|
+
'latvian',
|
|
164
|
+
'bengali',
|
|
165
|
+
'serbian',
|
|
166
|
+
'azerbaijani',
|
|
167
|
+
'slovenian',
|
|
168
|
+
'kannada',
|
|
169
|
+
'estonian',
|
|
170
|
+
'macedonian',
|
|
171
|
+
'breton',
|
|
172
|
+
'basque',
|
|
173
|
+
'icelandic',
|
|
174
|
+
'armenian',
|
|
175
|
+
'nepali',
|
|
176
|
+
'mongolian',
|
|
177
|
+
'bosnian',
|
|
178
|
+
'kazakh',
|
|
179
|
+
'albanian',
|
|
180
|
+
'swahili',
|
|
181
|
+
'galician',
|
|
182
|
+
'marathi',
|
|
183
|
+
'punjabi',
|
|
184
|
+
'sinhala',
|
|
185
|
+
'khmer',
|
|
186
|
+
'shona',
|
|
187
|
+
'yoruba',
|
|
188
|
+
'somali',
|
|
189
|
+
'afrikaans',
|
|
190
|
+
'occitan',
|
|
191
|
+
'georgian',
|
|
192
|
+
'belarusian',
|
|
193
|
+
'tajik',
|
|
194
|
+
'sindhi',
|
|
195
|
+
'gujarati',
|
|
196
|
+
'amharic',
|
|
197
|
+
'yiddish',
|
|
198
|
+
'lao',
|
|
199
|
+
'uzbek',
|
|
200
|
+
'faroese',
|
|
201
|
+
'haitian creole',
|
|
202
|
+
'pashto',
|
|
203
|
+
'turkmen',
|
|
204
|
+
'nynorsk',
|
|
205
|
+
'maltese',
|
|
206
|
+
'sanskrit',
|
|
207
|
+
'luxembourgish',
|
|
208
|
+
'myanmar',
|
|
209
|
+
'tibetan',
|
|
210
|
+
'tagalog',
|
|
211
|
+
'malagasy',
|
|
212
|
+
'assamese',
|
|
213
|
+
'tatar',
|
|
214
|
+
'hawaiian',
|
|
215
|
+
'lingala',
|
|
216
|
+
'hausa',
|
|
217
|
+
'bashkir',
|
|
218
|
+
'javanese',
|
|
219
|
+
'sundanese',
|
|
220
|
+
'cantonese',
|
|
221
|
+
] as const;
|
|
222
|
+
|
|
223
|
+
export type WhisperWebLanguage = (typeof LANGUAGES)[number];
|
|
224
|
+
|
|
225
|
+
export const SIZES: {[key in WhisperWebModel]: number} = {
|
|
226
|
+
tiny: 77691713,
|
|
227
|
+
'tiny.en': 77704715,
|
|
228
|
+
base: 147951465,
|
|
229
|
+
'base.en': 147964211,
|
|
230
|
+
small: 487601967,
|
|
231
|
+
'small.en': 487614201,
|
|
232
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {openDb} from './open-db';
|
|
2
|
+
|
|
3
|
+
export const deleteObject = async ({key}: {key: string}) => {
|
|
4
|
+
const objectStore = await openDb('readwrite');
|
|
5
|
+
|
|
6
|
+
return new Promise<void>((resolve, reject) => {
|
|
7
|
+
const request = objectStore.delete(key);
|
|
8
|
+
request.onsuccess = () => {
|
|
9
|
+
resolve();
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
request.onerror = () => {
|
|
13
|
+
reject(request.error);
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {openDb} from './open-db';
|
|
2
|
+
|
|
3
|
+
export const getObjectFromObjectStore = ({
|
|
4
|
+
objectStore,
|
|
5
|
+
key,
|
|
6
|
+
}: {
|
|
7
|
+
objectStore: IDBObjectStore;
|
|
8
|
+
key: string;
|
|
9
|
+
}) => {
|
|
10
|
+
return new Promise<Uint8Array>((resolve, reject) => {
|
|
11
|
+
const request = objectStore.get(key);
|
|
12
|
+
request.onsuccess = () => {
|
|
13
|
+
resolve(request.result as Uint8Array);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
request.onerror = () => {
|
|
17
|
+
reject(request.error);
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const getKeysFromObjectStore = ({
|
|
23
|
+
objectStore,
|
|
24
|
+
}: {
|
|
25
|
+
objectStore: IDBObjectStore;
|
|
26
|
+
}) => {
|
|
27
|
+
return new Promise<IDBValidKey[]>((resolve, reject) => {
|
|
28
|
+
const request = objectStore.getAllKeys();
|
|
29
|
+
request.onsuccess = () => {
|
|
30
|
+
resolve(request.result as IDBValidKey[]);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
request.onerror = () => {
|
|
34
|
+
reject(request.error);
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const getObject = async ({key}: {key: string}) => {
|
|
40
|
+
const objectStore = await openDb('readonly');
|
|
41
|
+
|
|
42
|
+
return getObjectFromObjectStore({objectStore, key});
|
|
43
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {DB_NAME, DB_OBJECT_STORE_NAME, DB_VERSION} from '../constants';
|
|
2
|
+
|
|
3
|
+
export const openDb = (transactionMode: IDBTransactionMode) => {
|
|
4
|
+
return new Promise<IDBObjectStore>((resolve, reject) => {
|
|
5
|
+
const rq = indexedDB.open(DB_NAME, DB_VERSION);
|
|
6
|
+
|
|
7
|
+
rq.onupgradeneeded = (event) => {
|
|
8
|
+
try {
|
|
9
|
+
const db = rq.result;
|
|
10
|
+
if (event.oldVersion < DB_VERSION) {
|
|
11
|
+
db.createObjectStore(DB_OBJECT_STORE_NAME, {autoIncrement: false});
|
|
12
|
+
} else {
|
|
13
|
+
const {transaction} = event.currentTarget as IDBOpenDBRequest;
|
|
14
|
+
if (!transaction) {
|
|
15
|
+
throw new Error('No transaction available during upgrade');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const objectStore = transaction.objectStore(DB_OBJECT_STORE_NAME);
|
|
19
|
+
if (!objectStore) {
|
|
20
|
+
throw new Error('Could not access object store during upgrade');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
objectStore.clear();
|
|
24
|
+
}
|
|
25
|
+
} catch (err) {
|
|
26
|
+
reject(new Error(`Failed to upgrade database: ${err}`));
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
rq.onsuccess = () => {
|
|
31
|
+
try {
|
|
32
|
+
const db = rq.result;
|
|
33
|
+
const transaction = db.transaction(
|
|
34
|
+
[DB_OBJECT_STORE_NAME],
|
|
35
|
+
transactionMode,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
transaction.onerror = () => {
|
|
39
|
+
reject(new Error('Transaction failed'));
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
transaction.onabort = () => {
|
|
43
|
+
reject(new Error('Transaction aborted'));
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const objectStore = transaction.objectStore(DB_OBJECT_STORE_NAME);
|
|
47
|
+
resolve(objectStore);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
reject(new Error(`Failed to open database: ${err}`));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
rq.onerror = () => {
|
|
54
|
+
const error = rq.error?.message ?? 'Unknown error';
|
|
55
|
+
reject(new Error(`Failed to open IndexedDB: ${error}`));
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
rq.onblocked = () => {
|
|
59
|
+
reject(new Error('Database is blocked by another connection'));
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {openDb} from './open-db';
|
|
2
|
+
|
|
3
|
+
export const putObject = async ({
|
|
4
|
+
key,
|
|
5
|
+
value,
|
|
6
|
+
}: {
|
|
7
|
+
key: string;
|
|
8
|
+
value: Uint8Array;
|
|
9
|
+
}) => {
|
|
10
|
+
const objectStore = await openDb('readwrite');
|
|
11
|
+
|
|
12
|
+
return new Promise<void>((resolve, reject) => {
|
|
13
|
+
try {
|
|
14
|
+
const putRq = objectStore.put(value, key);
|
|
15
|
+
|
|
16
|
+
putRq.onsuccess = () => {
|
|
17
|
+
resolve();
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
putRq.onerror = () => {
|
|
21
|
+
reject(new Error(`Failed to store "${key}" in IndexedDB`));
|
|
22
|
+
};
|
|
23
|
+
} catch (e) {
|
|
24
|
+
reject(new Error(`Failed to store "${key}" in IndexedDB: ${e}`));
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type {WhisperWebModel} from './constants';
|
|
2
|
+
import {deleteObject} from './db/delete-object';
|
|
3
|
+
import {getModelUrl} from './get-model-url';
|
|
4
|
+
|
|
5
|
+
export const deleteModel = async (model: WhisperWebModel) => {
|
|
6
|
+
const url = getModelUrl(model);
|
|
7
|
+
await deleteObject({key: url});
|
|
8
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const fetchRemote = async ({
|
|
2
|
+
url,
|
|
3
|
+
onProgress,
|
|
4
|
+
expectedLength,
|
|
5
|
+
}: {
|
|
6
|
+
url: string;
|
|
7
|
+
onProgress: (progress: number) => void;
|
|
8
|
+
expectedLength: number;
|
|
9
|
+
}) => {
|
|
10
|
+
// start the fetch
|
|
11
|
+
const response = await fetch(url, {method: 'get'});
|
|
12
|
+
if (!response.ok || !response.body) {
|
|
13
|
+
throw new Error(`failed to fetch: ${url}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const contentLength = response.headers.get('content-length') as string;
|
|
17
|
+
// hugging face servers do include this header
|
|
18
|
+
const total = parseInt(contentLength, 10);
|
|
19
|
+
if (total !== expectedLength) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Content-Length header is ${total} for ${url} but expected ${expectedLength}`,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const reader = response.body.getReader();
|
|
26
|
+
|
|
27
|
+
const chunks: Uint8Array[] = [];
|
|
28
|
+
let receivedLength = 0;
|
|
29
|
+
|
|
30
|
+
while (true) {
|
|
31
|
+
const {done, value} = await reader.read();
|
|
32
|
+
|
|
33
|
+
if (done) {
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
chunks.push(value);
|
|
38
|
+
receivedLength += value.length;
|
|
39
|
+
|
|
40
|
+
onProgress(receivedLength);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let position = 0;
|
|
44
|
+
const chunksAll = new Uint8Array(receivedLength);
|
|
45
|
+
|
|
46
|
+
for (const chunk of chunks) {
|
|
47
|
+
chunksAll.set(chunk, position);
|
|
48
|
+
position += chunk.length;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return chunksAll;
|
|
52
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {canUseWhisperWeb} from './can-use-whisper-web';
|
|
2
|
+
import {MODELS, SIZES, type WhisperWebModel} from './constants';
|
|
3
|
+
import {getObject} from './db/get-object-from-db';
|
|
4
|
+
import {putObject} from './db/put-object';
|
|
5
|
+
import {fetchRemote} from './download-model';
|
|
6
|
+
import {getModelUrl} from './get-model-url';
|
|
7
|
+
|
|
8
|
+
export type DownloadWhisperModelProgress = {
|
|
9
|
+
downloadedBytes: number;
|
|
10
|
+
totalBytes: number;
|
|
11
|
+
progress: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type DownloadWhisperModelOnProgress = (
|
|
15
|
+
progress: DownloadWhisperModelProgress,
|
|
16
|
+
) => void;
|
|
17
|
+
|
|
18
|
+
export interface DownloadWhisperModelParams {
|
|
19
|
+
model: WhisperWebModel;
|
|
20
|
+
onProgress: DownloadWhisperModelOnProgress;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type DownloadWhisperModelResult = {
|
|
24
|
+
alreadyDownloaded: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const downloadWhisperModel = async ({
|
|
28
|
+
model,
|
|
29
|
+
onProgress,
|
|
30
|
+
}: DownloadWhisperModelParams): Promise<DownloadWhisperModelResult> => {
|
|
31
|
+
if (!model || !MODELS.includes(model)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Invalid model name: ${model}. Supported models: ${MODELS.join(', ')}.`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const usabilityCheck = await canUseWhisperWeb(model);
|
|
38
|
+
|
|
39
|
+
if (!usabilityCheck.supported) {
|
|
40
|
+
return Promise.reject(
|
|
41
|
+
new Error(
|
|
42
|
+
`Whisper.wasm is not supported in this environment. Reason: ${usabilityCheck.detailedReason}`,
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const url = getModelUrl(model);
|
|
48
|
+
const modelSize = SIZES[model];
|
|
49
|
+
|
|
50
|
+
const existingModel = await getObject({key: url});
|
|
51
|
+
if (existingModel) {
|
|
52
|
+
onProgress({
|
|
53
|
+
downloadedBytes: modelSize,
|
|
54
|
+
totalBytes: modelSize,
|
|
55
|
+
progress: 1,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
alreadyDownloaded: true,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const data = await fetchRemote({
|
|
64
|
+
url,
|
|
65
|
+
onProgress: (bytes) => {
|
|
66
|
+
onProgress({
|
|
67
|
+
downloadedBytes: bytes,
|
|
68
|
+
progress: bytes / modelSize,
|
|
69
|
+
totalBytes: modelSize,
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
expectedLength: modelSize,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
onProgress({
|
|
76
|
+
downloadedBytes: modelSize,
|
|
77
|
+
totalBytes: modelSize,
|
|
78
|
+
progress: 1,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
await putObject({key: url, value: data});
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
alreadyDownloaded: false,
|
|
85
|
+
};
|
|
86
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type {WhisperWebModel} from './constants';
|
|
2
|
+
import {MODELS} from './constants';
|
|
3
|
+
import {getKeysFromObjectStore} from './db/get-object-from-db';
|
|
4
|
+
import {openDb} from './db/open-db';
|
|
5
|
+
import {getModelUrl} from './get-model-url';
|
|
6
|
+
|
|
7
|
+
export const getLoadedModels = async (): Promise<WhisperWebModel[]> => {
|
|
8
|
+
const objectStore = await openDb('readonly');
|
|
9
|
+
const loadedModels: WhisperWebModel[] = [];
|
|
10
|
+
|
|
11
|
+
const result = await getKeysFromObjectStore({
|
|
12
|
+
objectStore,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
for (const model of MODELS) {
|
|
16
|
+
if (result.includes(getModelUrl(model))) {
|
|
17
|
+
loadedModels.push(model);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return loadedModels;
|
|
22
|
+
};
|