@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
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
7
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
8
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
9
|
+
for (let key of __getOwnPropNames(mod))
|
|
10
|
+
if (!__hasOwnProp.call(to, key))
|
|
11
|
+
__defProp(to, key, {
|
|
12
|
+
get: () => mod[key],
|
|
13
|
+
enumerable: true
|
|
14
|
+
});
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
18
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
19
|
+
}) : x)(function(x) {
|
|
20
|
+
if (typeof require !== "undefined")
|
|
21
|
+
return require.apply(this, arguments);
|
|
22
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// src/constants.ts
|
|
26
|
+
var DB_NAME = "whisper-web";
|
|
27
|
+
var DB_VERSION = 1;
|
|
28
|
+
var DB_OBJECT_STORE_NAME = "models";
|
|
29
|
+
var MODELS = [
|
|
30
|
+
"tiny",
|
|
31
|
+
"tiny.en",
|
|
32
|
+
"base",
|
|
33
|
+
"base.en",
|
|
34
|
+
"small",
|
|
35
|
+
"small.en"
|
|
36
|
+
];
|
|
37
|
+
var SIZES = {
|
|
38
|
+
tiny: 77691713,
|
|
39
|
+
"tiny.en": 77704715,
|
|
40
|
+
base: 147951465,
|
|
41
|
+
"base.en": 147964211,
|
|
42
|
+
small: 487601967,
|
|
43
|
+
"small.en": 487614201
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/db/open-db.ts
|
|
47
|
+
var openDb = (transactionMode) => {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const rq = indexedDB.open(DB_NAME, DB_VERSION);
|
|
50
|
+
rq.onupgradeneeded = (event) => {
|
|
51
|
+
try {
|
|
52
|
+
const db = rq.result;
|
|
53
|
+
if (event.oldVersion < DB_VERSION) {
|
|
54
|
+
db.createObjectStore(DB_OBJECT_STORE_NAME, { autoIncrement: false });
|
|
55
|
+
} else {
|
|
56
|
+
const { transaction } = event.currentTarget;
|
|
57
|
+
if (!transaction) {
|
|
58
|
+
throw new Error("No transaction available during upgrade");
|
|
59
|
+
}
|
|
60
|
+
const objectStore = transaction.objectStore(DB_OBJECT_STORE_NAME);
|
|
61
|
+
if (!objectStore) {
|
|
62
|
+
throw new Error("Could not access object store during upgrade");
|
|
63
|
+
}
|
|
64
|
+
objectStore.clear();
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
reject(new Error(`Failed to upgrade database: ${err}`));
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
rq.onsuccess = () => {
|
|
71
|
+
try {
|
|
72
|
+
const db = rq.result;
|
|
73
|
+
const transaction = db.transaction([DB_OBJECT_STORE_NAME], transactionMode);
|
|
74
|
+
transaction.onerror = () => {
|
|
75
|
+
reject(new Error("Transaction failed"));
|
|
76
|
+
};
|
|
77
|
+
transaction.onabort = () => {
|
|
78
|
+
reject(new Error("Transaction aborted"));
|
|
79
|
+
};
|
|
80
|
+
const objectStore = transaction.objectStore(DB_OBJECT_STORE_NAME);
|
|
81
|
+
resolve(objectStore);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
reject(new Error(`Failed to open database: ${err}`));
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
rq.onerror = () => {
|
|
87
|
+
const error = rq.error?.message ?? "Unknown error";
|
|
88
|
+
reject(new Error(`Failed to open IndexedDB: ${error}`));
|
|
89
|
+
};
|
|
90
|
+
rq.onblocked = () => {
|
|
91
|
+
reject(new Error("Database is blocked by another connection"));
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/db/delete-object.ts
|
|
97
|
+
var deleteObject = async ({ key }) => {
|
|
98
|
+
const objectStore = await openDb("readwrite");
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
const request = objectStore.delete(key);
|
|
101
|
+
request.onsuccess = () => {
|
|
102
|
+
resolve();
|
|
103
|
+
};
|
|
104
|
+
request.onerror = () => {
|
|
105
|
+
reject(request.error);
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// src/get-model-url.ts
|
|
111
|
+
var getModelUrl = (model) => {
|
|
112
|
+
return `https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-${model}.bin`;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// src/delete-model.ts
|
|
116
|
+
var deleteModel = async (model) => {
|
|
117
|
+
const url = getModelUrl(model);
|
|
118
|
+
await deleteObject({ key: url });
|
|
119
|
+
};
|
|
120
|
+
// src/can-use-whisper-web.ts
|
|
121
|
+
var canUseWhisperWeb = async (model) => {
|
|
122
|
+
if (typeof window === "undefined") {
|
|
123
|
+
return {
|
|
124
|
+
supported: false,
|
|
125
|
+
reason: "window-undefined" /* WindowUndefined */,
|
|
126
|
+
detailedReason: "`window` is not defined. This module can only be used in a browser environment."
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (!window.crossOriginIsolated) {
|
|
130
|
+
return {
|
|
131
|
+
supported: false,
|
|
132
|
+
reason: "not-cross-origin-isolated" /* NotCrossOriginIsolated */,
|
|
133
|
+
detailedReason: "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"
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (!window.indexedDB) {
|
|
137
|
+
return {
|
|
138
|
+
supported: false,
|
|
139
|
+
reason: "indexed-db-unavailable" /* IndexedDbUnavailable */,
|
|
140
|
+
detailedReason: "IndexedDB is not available in this environment."
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (!navigator?.storage || !navigator?.storage.estimate) {
|
|
144
|
+
return {
|
|
145
|
+
supported: false,
|
|
146
|
+
reason: "navigator-storage-unavailable" /* NavigatorStorageUnavailable */,
|
|
147
|
+
detailedReason: "`navigator.storage.estimate()` API is not available in this environment."
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const estimate = await navigator.storage.estimate();
|
|
152
|
+
if (estimate.quota === undefined) {
|
|
153
|
+
return {
|
|
154
|
+
supported: false,
|
|
155
|
+
reason: "quota-undefined" /* QuotaUndefined */,
|
|
156
|
+
detailedReason: "navigator.storage.estimate() API returned undefined quota."
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (estimate.usage === undefined) {
|
|
160
|
+
return {
|
|
161
|
+
supported: false,
|
|
162
|
+
reason: "usage-undefined" /* UsageUndefined */,
|
|
163
|
+
detailedReason: "navigator.storage.estimate() API returned undefined usage."
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const remaining = estimate.quota - estimate.usage;
|
|
167
|
+
const modelSize = SIZES[model];
|
|
168
|
+
if (remaining < modelSize) {
|
|
169
|
+
return {
|
|
170
|
+
supported: false,
|
|
171
|
+
reason: "not-enough-space" /* NotEnoughSpace */,
|
|
172
|
+
detailedReason: `Not enough space to download the model. Required: ${modelSize} bytes, Available: ${remaining} bytes.`
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
177
|
+
return {
|
|
178
|
+
supported: false,
|
|
179
|
+
reason: "error-estimating-storage" /* ErrorEstimatingStorage */,
|
|
180
|
+
detailedReason: `Error estimating storage: ${errorMessage}`
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
supported: true
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// src/db/get-object-from-db.ts
|
|
189
|
+
var getObjectFromObjectStore = ({
|
|
190
|
+
objectStore,
|
|
191
|
+
key
|
|
192
|
+
}) => {
|
|
193
|
+
return new Promise((resolve, reject) => {
|
|
194
|
+
const request = objectStore.get(key);
|
|
195
|
+
request.onsuccess = () => {
|
|
196
|
+
resolve(request.result);
|
|
197
|
+
};
|
|
198
|
+
request.onerror = () => {
|
|
199
|
+
reject(request.error);
|
|
200
|
+
};
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
var getKeysFromObjectStore = ({
|
|
204
|
+
objectStore
|
|
205
|
+
}) => {
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
const request = objectStore.getAllKeys();
|
|
208
|
+
request.onsuccess = () => {
|
|
209
|
+
resolve(request.result);
|
|
210
|
+
};
|
|
211
|
+
request.onerror = () => {
|
|
212
|
+
reject(request.error);
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
};
|
|
216
|
+
var getObject = async ({ key }) => {
|
|
217
|
+
const objectStore = await openDb("readonly");
|
|
218
|
+
return getObjectFromObjectStore({ objectStore, key });
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// src/db/put-object.ts
|
|
222
|
+
var putObject = async ({
|
|
223
|
+
key,
|
|
224
|
+
value
|
|
225
|
+
}) => {
|
|
226
|
+
const objectStore = await openDb("readwrite");
|
|
227
|
+
return new Promise((resolve, reject) => {
|
|
228
|
+
try {
|
|
229
|
+
const putRq = objectStore.put(value, key);
|
|
230
|
+
putRq.onsuccess = () => {
|
|
231
|
+
resolve();
|
|
232
|
+
};
|
|
233
|
+
putRq.onerror = () => {
|
|
234
|
+
reject(new Error(`Failed to store "${key}" in IndexedDB`));
|
|
235
|
+
};
|
|
236
|
+
} catch (e) {
|
|
237
|
+
reject(new Error(`Failed to store "${key}" in IndexedDB: ${e}`));
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// src/download-model.ts
|
|
243
|
+
var fetchRemote = async ({
|
|
244
|
+
url,
|
|
245
|
+
onProgress,
|
|
246
|
+
expectedLength
|
|
247
|
+
}) => {
|
|
248
|
+
const response = await fetch(url, { method: "get" });
|
|
249
|
+
if (!response.ok || !response.body) {
|
|
250
|
+
throw new Error(`failed to fetch: ${url}`);
|
|
251
|
+
}
|
|
252
|
+
const contentLength = response.headers.get("content-length");
|
|
253
|
+
const total = parseInt(contentLength, 10);
|
|
254
|
+
if (total !== expectedLength) {
|
|
255
|
+
throw new Error(`Content-Length header is ${total} for ${url} but expected ${expectedLength}`);
|
|
256
|
+
}
|
|
257
|
+
const reader = response.body.getReader();
|
|
258
|
+
const chunks = [];
|
|
259
|
+
let receivedLength = 0;
|
|
260
|
+
while (true) {
|
|
261
|
+
const { done, value } = await reader.read();
|
|
262
|
+
if (done) {
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
chunks.push(value);
|
|
266
|
+
receivedLength += value.length;
|
|
267
|
+
onProgress(receivedLength);
|
|
268
|
+
}
|
|
269
|
+
let position = 0;
|
|
270
|
+
const chunksAll = new Uint8Array(receivedLength);
|
|
271
|
+
for (const chunk of chunks) {
|
|
272
|
+
chunksAll.set(chunk, position);
|
|
273
|
+
position += chunk.length;
|
|
274
|
+
}
|
|
275
|
+
return chunksAll;
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// src/download-whisper-model.ts
|
|
279
|
+
var downloadWhisperModel = async ({
|
|
280
|
+
model,
|
|
281
|
+
onProgress
|
|
282
|
+
}) => {
|
|
283
|
+
if (!model || !MODELS.includes(model)) {
|
|
284
|
+
throw new Error(`Invalid model name: ${model}. Supported models: ${MODELS.join(", ")}.`);
|
|
285
|
+
}
|
|
286
|
+
const usabilityCheck = await canUseWhisperWeb(model);
|
|
287
|
+
if (!usabilityCheck.supported) {
|
|
288
|
+
return Promise.reject(new Error(`Whisper.wasm is not supported in this environment. Reason: ${usabilityCheck.detailedReason}`));
|
|
289
|
+
}
|
|
290
|
+
const url = getModelUrl(model);
|
|
291
|
+
const modelSize = SIZES[model];
|
|
292
|
+
const existingModel = await getObject({ key: url });
|
|
293
|
+
if (existingModel) {
|
|
294
|
+
onProgress({
|
|
295
|
+
downloadedBytes: modelSize,
|
|
296
|
+
totalBytes: modelSize,
|
|
297
|
+
progress: 1
|
|
298
|
+
});
|
|
299
|
+
return {
|
|
300
|
+
alreadyDownloaded: true
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
const data = await fetchRemote({
|
|
304
|
+
url,
|
|
305
|
+
onProgress: (bytes) => {
|
|
306
|
+
onProgress({
|
|
307
|
+
downloadedBytes: bytes,
|
|
308
|
+
progress: bytes / modelSize,
|
|
309
|
+
totalBytes: modelSize
|
|
310
|
+
});
|
|
311
|
+
},
|
|
312
|
+
expectedLength: modelSize
|
|
313
|
+
});
|
|
314
|
+
onProgress({
|
|
315
|
+
downloadedBytes: modelSize,
|
|
316
|
+
totalBytes: modelSize,
|
|
317
|
+
progress: 1
|
|
318
|
+
});
|
|
319
|
+
await putObject({ key: url, value: data });
|
|
320
|
+
return {
|
|
321
|
+
alreadyDownloaded: false
|
|
322
|
+
};
|
|
323
|
+
};
|
|
324
|
+
// src/get-loaded-models.ts
|
|
325
|
+
var getLoadedModels = async () => {
|
|
326
|
+
const objectStore = await openDb("readonly");
|
|
327
|
+
const loadedModels = [];
|
|
328
|
+
const result = await getKeysFromObjectStore({
|
|
329
|
+
objectStore
|
|
330
|
+
});
|
|
331
|
+
for (const model of MODELS) {
|
|
332
|
+
if (result.includes(getModelUrl(model))) {
|
|
333
|
+
loadedModels.push(model);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return loadedModels;
|
|
337
|
+
};
|
|
338
|
+
// src/log.ts
|
|
339
|
+
var logLevels = ["trace", "verbose", "info", "warn", "error"];
|
|
340
|
+
var getNumberForLogLevel = (level) => {
|
|
341
|
+
return logLevels.indexOf(level);
|
|
342
|
+
};
|
|
343
|
+
var isEqualOrBelowLogLevel = (currentLevel, level) => {
|
|
344
|
+
return getNumberForLogLevel(currentLevel) <= getNumberForLogLevel(level);
|
|
345
|
+
};
|
|
346
|
+
var Log = {
|
|
347
|
+
trace: (logLevel, ...args) => {
|
|
348
|
+
if (isEqualOrBelowLogLevel(logLevel, "trace")) {
|
|
349
|
+
return console.log(...args);
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
verbose: (logLevel, ...args) => {
|
|
353
|
+
if (isEqualOrBelowLogLevel(logLevel, "verbose")) {
|
|
354
|
+
return console.log(...args);
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
info: (logLevel, ...args) => {
|
|
358
|
+
if (isEqualOrBelowLogLevel(logLevel, "info")) {
|
|
359
|
+
return console.log(...args);
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
warn: (logLevel, ...args) => {
|
|
363
|
+
if (isEqualOrBelowLogLevel(logLevel, "warn")) {
|
|
364
|
+
return console.warn(...args);
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
error: (...args) => {
|
|
368
|
+
return console.error(...args);
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// src/resample-to-16khz.ts
|
|
373
|
+
var EXPECTED_SAMPLE_RATE = 16000;
|
|
374
|
+
var context;
|
|
375
|
+
var getAudioContext = () => {
|
|
376
|
+
if (!context) {
|
|
377
|
+
context = new AudioContext({
|
|
378
|
+
sampleRate: EXPECTED_SAMPLE_RATE
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
return context;
|
|
382
|
+
};
|
|
383
|
+
var audioDecoder = async (audioBuffer) => {
|
|
384
|
+
const offlineContext = new OfflineAudioContext(audioBuffer.numberOfChannels, audioBuffer.length, audioBuffer.sampleRate);
|
|
385
|
+
const source = offlineContext.createBufferSource();
|
|
386
|
+
source.buffer = audioBuffer;
|
|
387
|
+
source.connect(offlineContext.destination);
|
|
388
|
+
source.start(0);
|
|
389
|
+
const renderedBuffer = await offlineContext.startRendering();
|
|
390
|
+
return renderedBuffer.getChannelData(0);
|
|
391
|
+
};
|
|
392
|
+
var resampleTo16Khz = async ({
|
|
393
|
+
file,
|
|
394
|
+
onProgress,
|
|
395
|
+
logLevel = "info"
|
|
396
|
+
}) => {
|
|
397
|
+
Log.info(logLevel, `Starting resampling for file, size: ${file.size}`);
|
|
398
|
+
onProgress?.(0);
|
|
399
|
+
if (typeof window === "undefined") {
|
|
400
|
+
Log.error(logLevel, "Window object not found. Resampling can only be done in a browser environment.");
|
|
401
|
+
throw new Error("Window object not found. Resampling requires a browser environment.");
|
|
402
|
+
}
|
|
403
|
+
if (!file) {
|
|
404
|
+
Log.error(logLevel, "File is empty.");
|
|
405
|
+
throw new Error("File is empty");
|
|
406
|
+
}
|
|
407
|
+
const innerContext = getAudioContext();
|
|
408
|
+
const reader = new FileReader;
|
|
409
|
+
return new Promise((resolve, reject) => {
|
|
410
|
+
reader.onprogress = (event) => {
|
|
411
|
+
if (event.lengthComputable) {
|
|
412
|
+
const percentage = event.loaded / event.total * 0.5;
|
|
413
|
+
onProgress?.(Math.min(0.5, percentage));
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
reader.onload = async () => {
|
|
417
|
+
try {
|
|
418
|
+
Log.info(logLevel, "File reading complete. Decoding audio data...");
|
|
419
|
+
onProgress?.(0.5);
|
|
420
|
+
const buffer = new Uint8Array(reader.result);
|
|
421
|
+
const audioBuffer = await innerContext.decodeAudioData(buffer.buffer);
|
|
422
|
+
Log.info(logLevel, "Audio decoding complete. Starting rendering...");
|
|
423
|
+
onProgress?.(0.75);
|
|
424
|
+
const processedAudio = await audioDecoder(audioBuffer);
|
|
425
|
+
Log.info(logLevel, "Audio resampling and processing complete.");
|
|
426
|
+
onProgress?.(1);
|
|
427
|
+
resolve(processedAudio);
|
|
428
|
+
} catch (error) {
|
|
429
|
+
Log.error(logLevel, "Error during audio processing:", error);
|
|
430
|
+
reject(error);
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
reader.onerror = () => {
|
|
434
|
+
Log.error(logLevel, "File reading failed.");
|
|
435
|
+
reject(new Error("File reading failed"));
|
|
436
|
+
};
|
|
437
|
+
reader.readAsArrayBuffer(file);
|
|
438
|
+
});
|
|
439
|
+
};
|
|
440
|
+
// src/load-mod/load-mod.ts
|
|
441
|
+
var loadMod = async () => {
|
|
442
|
+
const Mod = await import("../../main.js");
|
|
443
|
+
return Mod.default;
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
// src/print-handler.ts
|
|
447
|
+
var RESULT_TOKEN = "remotion_final:";
|
|
448
|
+
var PROGRESS_TOKEN = "remotion_progress:";
|
|
449
|
+
var UPDATE_TOKEN = "remotion_update:";
|
|
450
|
+
var BUSY_TOKEN = "remotion_busy:";
|
|
451
|
+
var printHandler = ({
|
|
452
|
+
onProgress,
|
|
453
|
+
onDone,
|
|
454
|
+
onBusy,
|
|
455
|
+
onUpdate,
|
|
456
|
+
logLevel
|
|
457
|
+
}) => {
|
|
458
|
+
return (text) => {
|
|
459
|
+
Log.verbose(logLevel, text);
|
|
460
|
+
if (text.startsWith(PROGRESS_TOKEN)) {
|
|
461
|
+
const value = parseInt(text.slice(PROGRESS_TOKEN.length), 10);
|
|
462
|
+
onProgress(value);
|
|
463
|
+
} else if (text.startsWith(RESULT_TOKEN)) {
|
|
464
|
+
const json = JSON.parse(text.slice(RESULT_TOKEN.length));
|
|
465
|
+
onDone(json);
|
|
466
|
+
} else if (text.startsWith(UPDATE_TOKEN)) {
|
|
467
|
+
const json = JSON.parse(text.slice(UPDATE_TOKEN.length));
|
|
468
|
+
onUpdate(json);
|
|
469
|
+
} else if (text.startsWith(BUSY_TOKEN)) {
|
|
470
|
+
onBusy();
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// src/transcription-speed.ts
|
|
476
|
+
var storeActualTranscriptionSpeed = (speed) => {
|
|
477
|
+
window.localStorage.setItem("remotion-whisper-web-transcription-speed", speed.toString());
|
|
478
|
+
};
|
|
479
|
+
var DEFAULT_ASSUMED_SPEED = 1;
|
|
480
|
+
var NEW_PROGRESS_EVENT_EVERY_N_SECONDS = 30;
|
|
481
|
+
var getActualTranscriptionSpeedInMilliseconds = () => {
|
|
482
|
+
const speed = window.localStorage.getItem("remotion-whisper-web-transcription-speed");
|
|
483
|
+
if (!speed) {
|
|
484
|
+
return DEFAULT_ASSUMED_SPEED * NEW_PROGRESS_EVENT_EVERY_N_SECONDS * 1000;
|
|
485
|
+
}
|
|
486
|
+
return parseFloat(speed);
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
// src/simulate-progress.ts
|
|
490
|
+
var simulateProgress = ({
|
|
491
|
+
audioDurationInSeconds,
|
|
492
|
+
onProgress
|
|
493
|
+
}) => {
|
|
494
|
+
let progress = 0;
|
|
495
|
+
const progressSteps = audioDurationInSeconds / NEW_PROGRESS_EVENT_EVERY_N_SECONDS;
|
|
496
|
+
let progressStepsReceived = 0;
|
|
497
|
+
let timer = null;
|
|
498
|
+
let lastTimerStart = null;
|
|
499
|
+
const start = () => {
|
|
500
|
+
const speed = getActualTranscriptionSpeedInMilliseconds();
|
|
501
|
+
let iterations = 0;
|
|
502
|
+
lastTimerStart = Date.now();
|
|
503
|
+
timer = setInterval(() => {
|
|
504
|
+
progress += 1 / NEW_PROGRESS_EVENT_EVERY_N_SECONDS / (progressSteps + 1);
|
|
505
|
+
progress = Math.min(progress, 0.99);
|
|
506
|
+
onProgress(progress);
|
|
507
|
+
iterations += 1;
|
|
508
|
+
if (iterations > NEW_PROGRESS_EVENT_EVERY_N_SECONDS - 1 && timer) {
|
|
509
|
+
clearInterval(timer);
|
|
510
|
+
timer = null;
|
|
511
|
+
}
|
|
512
|
+
}, speed / NEW_PROGRESS_EVENT_EVERY_N_SECONDS);
|
|
513
|
+
};
|
|
514
|
+
return {
|
|
515
|
+
start,
|
|
516
|
+
progressStepReceived: () => {
|
|
517
|
+
progressStepsReceived += 1;
|
|
518
|
+
progress = progressStepsReceived / progressSteps;
|
|
519
|
+
if (timer) {
|
|
520
|
+
clearInterval(timer);
|
|
521
|
+
timer = null;
|
|
522
|
+
}
|
|
523
|
+
if (lastTimerStart) {
|
|
524
|
+
const timeToProcessChunk = Date.now() - (lastTimerStart ?? Date.now());
|
|
525
|
+
storeActualTranscriptionSpeed(timeToProcessChunk);
|
|
526
|
+
}
|
|
527
|
+
start();
|
|
528
|
+
},
|
|
529
|
+
onDone: () => {
|
|
530
|
+
if (timer) {
|
|
531
|
+
clearInterval(timer);
|
|
532
|
+
timer = null;
|
|
533
|
+
}
|
|
534
|
+
progress = 1;
|
|
535
|
+
onProgress(1);
|
|
536
|
+
},
|
|
537
|
+
abort: () => {
|
|
538
|
+
if (timer) {
|
|
539
|
+
clearInterval(timer);
|
|
540
|
+
timer = null;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
// src/transcribe.ts
|
|
547
|
+
var MAX_THREADS_ALLOWED = 16;
|
|
548
|
+
var DEFAULT_THREADS = 4;
|
|
549
|
+
var withResolvers = function() {
|
|
550
|
+
let resolve;
|
|
551
|
+
let reject;
|
|
552
|
+
const promise = new Promise((res, rej) => {
|
|
553
|
+
resolve = res;
|
|
554
|
+
reject = rej;
|
|
555
|
+
});
|
|
556
|
+
return { promise, resolve, reject };
|
|
557
|
+
};
|
|
558
|
+
var storeFS = (mod, fname, buf) => {
|
|
559
|
+
try {
|
|
560
|
+
mod.FS_unlink(fname);
|
|
561
|
+
} catch {}
|
|
562
|
+
mod.FS_createDataFile("/", fname, buf, true, true, undefined);
|
|
563
|
+
};
|
|
564
|
+
var transcribe = async ({
|
|
565
|
+
channelWaveform,
|
|
566
|
+
model,
|
|
567
|
+
language = "auto",
|
|
568
|
+
onProgress,
|
|
569
|
+
threads,
|
|
570
|
+
onTranscriptionChunk,
|
|
571
|
+
logLevel = "info"
|
|
572
|
+
}) => {
|
|
573
|
+
if (!channelWaveform || channelWaveform.length === 0) {
|
|
574
|
+
Log.error(logLevel, "No audio data provided or audio data is empty.");
|
|
575
|
+
throw new Error("No audio data provided or audio data is empty.");
|
|
576
|
+
}
|
|
577
|
+
Log.info(logLevel, `Starting transcription with model: ${model}, language: ${language}, threads: ${threads ?? DEFAULT_THREADS}`);
|
|
578
|
+
if ((threads ?? DEFAULT_THREADS) > MAX_THREADS_ALLOWED) {
|
|
579
|
+
Log.warn(logLevel, `Thread limit exceeded: Used ${threads ?? DEFAULT_THREADS}, max ${MAX_THREADS_ALLOWED} allowed.`);
|
|
580
|
+
return Promise.reject(new Error(`Thread limit exceeded: max ${MAX_THREADS_ALLOWED} allowed.`));
|
|
581
|
+
}
|
|
582
|
+
const audioDurationInSeconds = channelWaveform.length / EXPECTED_SAMPLE_RATE;
|
|
583
|
+
const {
|
|
584
|
+
abort: abortProgress,
|
|
585
|
+
onDone: onProgressDone,
|
|
586
|
+
progressStepReceived,
|
|
587
|
+
start: startProgress
|
|
588
|
+
} = simulateProgress({
|
|
589
|
+
audioDurationInSeconds,
|
|
590
|
+
onProgress: (p) => {
|
|
591
|
+
onProgress?.(p);
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
const {
|
|
595
|
+
promise,
|
|
596
|
+
resolve: _resolve,
|
|
597
|
+
reject: _reject
|
|
598
|
+
} = withResolvers();
|
|
599
|
+
const resolve = (value) => {
|
|
600
|
+
_resolve(value);
|
|
601
|
+
abortProgress();
|
|
602
|
+
Log.info(logLevel, "Transcription completed successfully.");
|
|
603
|
+
};
|
|
604
|
+
const reject = (reason) => {
|
|
605
|
+
_reject(reason);
|
|
606
|
+
abortProgress();
|
|
607
|
+
Log.error("Transcription failed:", reason);
|
|
608
|
+
};
|
|
609
|
+
const handler = printHandler({
|
|
610
|
+
logLevel,
|
|
611
|
+
onProgress: (p) => {
|
|
612
|
+
if (p === 0) {
|
|
613
|
+
startProgress();
|
|
614
|
+
} else if (p === 100) {
|
|
615
|
+
onProgressDone();
|
|
616
|
+
} else {
|
|
617
|
+
progressStepReceived();
|
|
618
|
+
}
|
|
619
|
+
},
|
|
620
|
+
onDone: resolve,
|
|
621
|
+
onBusy: () => {
|
|
622
|
+
reject(new Error("Another transcription is already in progress"));
|
|
623
|
+
},
|
|
624
|
+
onUpdate: (json) => {
|
|
625
|
+
onTranscriptionChunk?.(json.transcription);
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
window.remotion_wasm_moduleOverrides = {
|
|
629
|
+
print: handler,
|
|
630
|
+
printErr: handler
|
|
631
|
+
};
|
|
632
|
+
const Mod = await loadMod();
|
|
633
|
+
delete window.remotion_wasm_moduleOverrides;
|
|
634
|
+
const url = getModelUrl(model);
|
|
635
|
+
const result = await getObject({ key: url });
|
|
636
|
+
if (!result) {
|
|
637
|
+
throw new Error(`Model ${model} is not loaded. Call downloadWhisperModel() first.`);
|
|
638
|
+
}
|
|
639
|
+
Log.info(logLevel, `Model ${model} loaded successfully.`);
|
|
640
|
+
const fileName = `${model}.bin`;
|
|
641
|
+
storeFS(Mod, fileName, result);
|
|
642
|
+
Log.info(logLevel, "Starting main transcription process...");
|
|
643
|
+
Mod.full_default(fileName, channelWaveform, model, language, threads ?? DEFAULT_THREADS, false);
|
|
644
|
+
return promise;
|
|
645
|
+
};
|
|
646
|
+
export {
|
|
647
|
+
transcribe,
|
|
648
|
+
resampleTo16Khz,
|
|
649
|
+
getLoadedModels,
|
|
650
|
+
downloadWhisperModel,
|
|
651
|
+
deleteModel
|
|
652
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MODELS } from './constants';
|
|
2
|
+
import { getKeysFromObjectStore } from './db/get-object-from-db';
|
|
3
|
+
import { openDb } from './db/open-db';
|
|
4
|
+
import { getModelUrl } from './get-model-url';
|
|
5
|
+
export const getLoadedModels = async () => {
|
|
6
|
+
const objectStore = await openDb('readonly');
|
|
7
|
+
const loadedModels = [];
|
|
8
|
+
const result = await getKeysFromObjectStore({
|
|
9
|
+
objectStore,
|
|
10
|
+
});
|
|
11
|
+
for (const model of MODELS) {
|
|
12
|
+
if (result.includes(getModelUrl(model))) {
|
|
13
|
+
loadedModels.push(model);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return loadedModels;
|
|
17
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const getModelUrl = (model) => {
|
|
2
|
+
return `https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-${model}.bin`;
|
|
3
|
+
};
|
|
4
|
+
export const sizes = {
|
|
5
|
+
tiny: 74000000,
|
|
6
|
+
base: 244000000,
|
|
7
|
+
small: 769000000,
|
|
8
|
+
medium: 1550000000,
|
|
9
|
+
large: 3050000000,
|
|
10
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CanUseWhisperWebResult, canUseWhisperWeb as originalCanUseWhisperWeb, WhisperWebUnsupportedReason } from './can-use-whisper-web';
|
|
2
|
+
import type { WhisperWebLanguage, WhisperWebModel } from './constants';
|
|
3
|
+
import type { deleteModel as originalDeleteModel } from './delete-model';
|
|
4
|
+
import type { DownloadWhisperModelOnProgress, DownloadWhisperModelParams, DownloadWhisperModelProgress, DownloadWhisperModelResult, downloadWhisperModel as originalDownloadWhisperModel } from './download-whisper-model';
|
|
5
|
+
import type { getLoadedModels as originalGetLoadedModels } from './get-loaded-models';
|
|
6
|
+
import type { resampleTo16Khz as originalResampleTo16Khz, ResampleTo16KhzParams } from './resample-to-16khz';
|
|
7
|
+
import type { transcribe as originalTranscribe, TranscribeParams } from './transcribe';
|
|
8
|
+
export declare const transcribe: typeof originalTranscribe;
|
|
9
|
+
export declare const downloadWhisperModel: typeof originalDownloadWhisperModel;
|
|
10
|
+
export declare const getLoadedModels: typeof originalGetLoadedModels;
|
|
11
|
+
export declare const deleteModel: typeof originalDeleteModel;
|
|
12
|
+
export declare const canUseWhisperWeb: typeof originalCanUseWhisperWeb;
|
|
13
|
+
export declare const resampleTo16Khz: typeof originalResampleTo16Khz;
|
|
14
|
+
export type { CanUseWhisperWebResult, DownloadWhisperModelOnProgress, DownloadWhisperModelParams, DownloadWhisperModelProgress, DownloadWhisperModelResult, ResampleTo16KhzParams, TranscribeParams, WhisperWebLanguage, WhisperWebModel, WhisperWebUnsupportedReason, };
|