@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.
Files changed (78) hide show
  1. package/.turbo/turbo-make.log +6 -0
  2. package/LICENSE.md +49 -0
  3. package/README.md +18 -0
  4. package/build-wasm.ts +117 -0
  5. package/bundle.ts +15 -0
  6. package/dist/can-use-whisper-web.d.ts +18 -0
  7. package/dist/can-use-whisper-web.js +80 -0
  8. package/dist/constants.d.ts +10 -0
  9. package/dist/constants.js +225 -0
  10. package/dist/db/delete-object.d.ts +3 -0
  11. package/dist/db/delete-object.js +13 -0
  12. package/dist/db/get-object-from-db.d.ts +10 -0
  13. package/dist/db/get-object-from-db.js +27 -0
  14. package/dist/db/open-db.d.ts +1 -0
  15. package/dist/db/open-db.js +53 -0
  16. package/dist/db/put-object.d.ts +4 -0
  17. package/dist/db/put-object.js +18 -0
  18. package/dist/delete-model.d.ts +2 -0
  19. package/dist/delete-model.js +6 -0
  20. package/dist/download-model.d.ts +5 -0
  21. package/dist/download-model.js +32 -0
  22. package/dist/download-whisper-model.d.ts +15 -0
  23. package/dist/download-whisper-model.js +48 -0
  24. package/dist/esm/index.mjs +652 -0
  25. package/dist/get-loaded-models.d.ts +2 -0
  26. package/dist/get-loaded-models.js +17 -0
  27. package/dist/get-model-url.d.ts +9 -0
  28. package/dist/get-model-url.js +10 -0
  29. package/dist/index.d.ts +14 -0
  30. package/dist/index.js +18 -0
  31. package/dist/load-mod/load-mod.d.ts +2 -0
  32. package/dist/load-mod/load-mod.js +6 -0
  33. package/dist/log.d.ts +10 -0
  34. package/dist/log.js +33 -0
  35. package/dist/mod.d.ts +6 -0
  36. package/dist/mod.js +1 -0
  37. package/dist/print-handler.d.ts +9 -0
  38. package/dist/print-handler.js +25 -0
  39. package/dist/resample-to-16khz.d.ts +8 -0
  40. package/dist/resample-to-16khz.js +66 -0
  41. package/dist/result.d.ts +53 -0
  42. package/dist/result.js +1 -0
  43. package/dist/simulate-progress.d.ts +9 -0
  44. package/dist/simulate-progress.js +53 -0
  45. package/dist/transcribe.d.ts +18 -0
  46. package/dist/transcribe.js +97 -0
  47. package/dist/transcription-speed.d.ts +3 -0
  48. package/dist/transcription-speed.js +13 -0
  49. package/emscripten.cpp +303 -0
  50. package/eslint.config.mjs +5 -0
  51. package/main.d.ts +46 -0
  52. package/main.js +3 -0
  53. package/package.json +52 -0
  54. package/src/can-use-whisper-web.ts +103 -0
  55. package/src/constants.ts +232 -0
  56. package/src/db/delete-object.ts +16 -0
  57. package/src/db/get-object-from-db.ts +43 -0
  58. package/src/db/open-db.ts +62 -0
  59. package/src/db/put-object.ts +27 -0
  60. package/src/delete-model.ts +8 -0
  61. package/src/download-model.ts +52 -0
  62. package/src/download-whisper-model.ts +86 -0
  63. package/src/get-loaded-models.ts +22 -0
  64. package/src/get-model-url.ts +13 -0
  65. package/src/index.module.ts +9 -0
  66. package/src/index.ts +72 -0
  67. package/src/load-mod/load-mod.ts +11 -0
  68. package/src/log.ts +41 -0
  69. package/src/mod.ts +13 -0
  70. package/src/print-handler.ts +39 -0
  71. package/src/resample-to-16khz.ts +105 -0
  72. package/src/result.ts +59 -0
  73. package/src/simulate-progress.ts +74 -0
  74. package/src/transcribe.ts +184 -0
  75. package/src/transcription-speed.ts +21 -0
  76. package/tsconfig.json +11 -0
  77. package/tsconfig.tsbuildinfo +1 -0
  78. 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,2 @@
1
+ import type { WhisperWebModel } from './constants';
2
+ export declare const getLoadedModels: () => Promise<WhisperWebModel[]>;
@@ -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,9 @@
1
+ import type { WhisperWebModel } from './constants';
2
+ export declare const getModelUrl: (model: WhisperWebModel) => string;
3
+ export declare const sizes: {
4
+ tiny: number;
5
+ base: number;
6
+ small: number;
7
+ medium: number;
8
+ large: number;
9
+ };
@@ -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
+ };
@@ -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, };