@remotion/whisper-webgpu 4.0.526 → 4.0.528
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/dist/download-whisper-model.d.ts +16 -0
- package/dist/esm/index.mjs +421 -45
- package/dist/import-node-module.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/load-whisper-model.d.ts +6 -3
- package/dist/probe-node-webgpu.d.ts +1 -0
- package/dist/symbol-async-dispose.d.ts +0 -0
- package/dist/transcribe.d.ts +2 -1
- package/package.json +6 -5
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type WhisperWebGpuModel } from './models';
|
|
2
|
+
export type WhisperModelDownloadProgress = {
|
|
3
|
+
file: string | null;
|
|
4
|
+
progress: number;
|
|
5
|
+
loadedBytes: number;
|
|
6
|
+
totalBytes: number;
|
|
7
|
+
};
|
|
8
|
+
export type DownloadWhisperModelOptions = {
|
|
9
|
+
model: WhisperWebGpuModel;
|
|
10
|
+
onProgress?: (progress: WhisperModelDownloadProgress) => void;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
};
|
|
13
|
+
export type DownloadWhisperModelResult = {
|
|
14
|
+
alreadyDownloaded: boolean;
|
|
15
|
+
};
|
|
16
|
+
export declare const downloadWhisperModel: ({ model, onProgress, signal, }: DownloadWhisperModelOptions) => Promise<DownloadWhisperModelResult>;
|
package/dist/esm/index.mjs
CHANGED
|
@@ -14,6 +14,16 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
14
14
|
});
|
|
15
15
|
return to;
|
|
16
16
|
};
|
|
17
|
+
var __export = (target, all) => {
|
|
18
|
+
for (var name in all)
|
|
19
|
+
__defProp(target, name, {
|
|
20
|
+
get: all[name],
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
set: (newValue) => all[name] = () => newValue
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
27
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
18
28
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
19
29
|
}) : x)(function(x) {
|
|
@@ -22,6 +32,159 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
22
32
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
23
33
|
});
|
|
24
34
|
|
|
35
|
+
// src/import-node-module.ts
|
|
36
|
+
var importNodeModule = (specifier) => {
|
|
37
|
+
const dynamicImport = new Function("specifier", "return import(specifier)");
|
|
38
|
+
return dynamicImport(specifier);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/probe-node-webgpu.ts
|
|
42
|
+
var exports_probe_node_webgpu = {};
|
|
43
|
+
__export(exports_probe_node_webgpu, {
|
|
44
|
+
probeNodeWebGpu: () => probeNodeWebGpu
|
|
45
|
+
});
|
|
46
|
+
var webGpuProbeModel, probeNodeWebGpu = async () => {
|
|
47
|
+
const { InferenceSession } = await importNodeModule("onnxruntime-node");
|
|
48
|
+
const session = await InferenceSession.create(webGpuProbeModel, {
|
|
49
|
+
executionProviders: ["webgpu"]
|
|
50
|
+
});
|
|
51
|
+
await session.release();
|
|
52
|
+
};
|
|
53
|
+
var init_probe_node_webgpu = __esm(() => {
|
|
54
|
+
webGpuProbeModel = new Uint8Array([
|
|
55
|
+
8,
|
|
56
|
+
3,
|
|
57
|
+
18,
|
|
58
|
+
6,
|
|
59
|
+
99,
|
|
60
|
+
104,
|
|
61
|
+
101,
|
|
62
|
+
110,
|
|
63
|
+
116,
|
|
64
|
+
97,
|
|
65
|
+
58,
|
|
66
|
+
112,
|
|
67
|
+
10,
|
|
68
|
+
21,
|
|
69
|
+
10,
|
|
70
|
+
1,
|
|
71
|
+
88,
|
|
72
|
+
10,
|
|
73
|
+
1,
|
|
74
|
+
87,
|
|
75
|
+
18,
|
|
76
|
+
1,
|
|
77
|
+
89,
|
|
78
|
+
26,
|
|
79
|
+
5,
|
|
80
|
+
109,
|
|
81
|
+
117,
|
|
82
|
+
108,
|
|
83
|
+
95,
|
|
84
|
+
49,
|
|
85
|
+
34,
|
|
86
|
+
3,
|
|
87
|
+
77,
|
|
88
|
+
117,
|
|
89
|
+
108,
|
|
90
|
+
18,
|
|
91
|
+
8,
|
|
92
|
+
109,
|
|
93
|
+
117,
|
|
94
|
+
108,
|
|
95
|
+
32,
|
|
96
|
+
116,
|
|
97
|
+
101,
|
|
98
|
+
115,
|
|
99
|
+
116,
|
|
100
|
+
42,
|
|
101
|
+
35,
|
|
102
|
+
8,
|
|
103
|
+
3,
|
|
104
|
+
8,
|
|
105
|
+
2,
|
|
106
|
+
16,
|
|
107
|
+
1,
|
|
108
|
+
34,
|
|
109
|
+
24,
|
|
110
|
+
0,
|
|
111
|
+
0,
|
|
112
|
+
128,
|
|
113
|
+
63,
|
|
114
|
+
0,
|
|
115
|
+
0,
|
|
116
|
+
0,
|
|
117
|
+
64,
|
|
118
|
+
0,
|
|
119
|
+
0,
|
|
120
|
+
64,
|
|
121
|
+
64,
|
|
122
|
+
0,
|
|
123
|
+
0,
|
|
124
|
+
128,
|
|
125
|
+
64,
|
|
126
|
+
0,
|
|
127
|
+
0,
|
|
128
|
+
160,
|
|
129
|
+
64,
|
|
130
|
+
0,
|
|
131
|
+
0,
|
|
132
|
+
192,
|
|
133
|
+
64,
|
|
134
|
+
66,
|
|
135
|
+
1,
|
|
136
|
+
87,
|
|
137
|
+
90,
|
|
138
|
+
19,
|
|
139
|
+
10,
|
|
140
|
+
1,
|
|
141
|
+
88,
|
|
142
|
+
18,
|
|
143
|
+
14,
|
|
144
|
+
10,
|
|
145
|
+
12,
|
|
146
|
+
8,
|
|
147
|
+
1,
|
|
148
|
+
18,
|
|
149
|
+
8,
|
|
150
|
+
10,
|
|
151
|
+
2,
|
|
152
|
+
8,
|
|
153
|
+
3,
|
|
154
|
+
10,
|
|
155
|
+
2,
|
|
156
|
+
8,
|
|
157
|
+
2,
|
|
158
|
+
98,
|
|
159
|
+
19,
|
|
160
|
+
10,
|
|
161
|
+
1,
|
|
162
|
+
89,
|
|
163
|
+
18,
|
|
164
|
+
14,
|
|
165
|
+
10,
|
|
166
|
+
12,
|
|
167
|
+
8,
|
|
168
|
+
1,
|
|
169
|
+
18,
|
|
170
|
+
8,
|
|
171
|
+
10,
|
|
172
|
+
2,
|
|
173
|
+
8,
|
|
174
|
+
3,
|
|
175
|
+
10,
|
|
176
|
+
2,
|
|
177
|
+
8,
|
|
178
|
+
2,
|
|
179
|
+
66,
|
|
180
|
+
4,
|
|
181
|
+
10,
|
|
182
|
+
0,
|
|
183
|
+
16,
|
|
184
|
+
7
|
|
185
|
+
]);
|
|
186
|
+
});
|
|
187
|
+
|
|
25
188
|
// src/can-use-whisper-webgpu.ts
|
|
26
189
|
var WhisperWebGpuUnsupportedReason;
|
|
27
190
|
((WhisperWebGpuUnsupportedReason2) => {
|
|
@@ -31,10 +194,24 @@ var WhisperWebGpuUnsupportedReason;
|
|
|
31
194
|
})(WhisperWebGpuUnsupportedReason ||= {});
|
|
32
195
|
var canUseWhisperWebGpu = async () => {
|
|
33
196
|
if (typeof window === "undefined") {
|
|
197
|
+
if (typeof process !== "undefined" && process.release?.name === "node") {
|
|
198
|
+
try {
|
|
199
|
+
const { probeNodeWebGpu: probeNodeWebGpu2 } = await Promise.resolve().then(() => (init_probe_node_webgpu(), exports_probe_node_webgpu));
|
|
200
|
+
await probeNodeWebGpu2();
|
|
201
|
+
return { supported: true };
|
|
202
|
+
} catch (error) {
|
|
203
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
204
|
+
return {
|
|
205
|
+
supported: false,
|
|
206
|
+
reason: "webgpu-unavailable" /* WebGpuUnavailable */,
|
|
207
|
+
detailedReason: `ONNX Runtime could not initialize WebGPU: ${message}`
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
34
211
|
return {
|
|
35
212
|
supported: false,
|
|
36
213
|
reason: "window-undefined" /* WindowUndefined */,
|
|
37
|
-
detailedReason: "
|
|
214
|
+
detailedReason: "WebGPU is not available in this environment."
|
|
38
215
|
};
|
|
39
216
|
}
|
|
40
217
|
if (typeof navigator === "undefined" || !("gpu" in navigator)) {
|
|
@@ -253,6 +430,140 @@ var withRemotionModelHost = async (operation) => {
|
|
|
253
430
|
}
|
|
254
431
|
};
|
|
255
432
|
|
|
433
|
+
// src/download-whisper-model.ts
|
|
434
|
+
var downloadWhisperModel = async ({
|
|
435
|
+
model,
|
|
436
|
+
onProgress,
|
|
437
|
+
signal
|
|
438
|
+
}) => {
|
|
439
|
+
signal?.throwIfAborted();
|
|
440
|
+
const totalBytes = getModelInfo(model).webGpuDownloadSize;
|
|
441
|
+
const hostedModelId = getHostedModelId(model);
|
|
442
|
+
const result = await withRemotionModelHost(async ({ env, ModelRegistry }) => {
|
|
443
|
+
signal?.throwIfAborted();
|
|
444
|
+
const cache = env.useCustomCache ? env.customCache : env.useBrowserCache && typeof caches !== "undefined" ? await caches.open(env.cacheKey) : null;
|
|
445
|
+
const useFileCache = cache === null && env.useFSCache;
|
|
446
|
+
if (cache === null && !useFileCache) {
|
|
447
|
+
throw new Error("A Transformers.js model cache is required to download a Whisper model.");
|
|
448
|
+
}
|
|
449
|
+
const registryOptions = {
|
|
450
|
+
device: "webgpu",
|
|
451
|
+
dtype: getWhisperWebGpuDtype(model)
|
|
452
|
+
};
|
|
453
|
+
const cacheStatus = await ModelRegistry.is_pipeline_cached_files("automatic-speech-recognition", hostedModelId, registryOptions);
|
|
454
|
+
signal?.throwIfAborted();
|
|
455
|
+
if (cacheStatus.allCached) {
|
|
456
|
+
onProgress?.({
|
|
457
|
+
file: null,
|
|
458
|
+
progress: 1,
|
|
459
|
+
loadedBytes: totalBytes,
|
|
460
|
+
totalBytes
|
|
461
|
+
});
|
|
462
|
+
return { alreadyDownloaded: true };
|
|
463
|
+
}
|
|
464
|
+
let loadedBytes = 0;
|
|
465
|
+
const emitProgress = (file) => {
|
|
466
|
+
onProgress?.({
|
|
467
|
+
file,
|
|
468
|
+
progress: Math.min(loadedBytes / totalBytes, 0.99),
|
|
469
|
+
loadedBytes,
|
|
470
|
+
totalBytes
|
|
471
|
+
});
|
|
472
|
+
};
|
|
473
|
+
emitProgress(null);
|
|
474
|
+
for (const { file, cached } of cacheStatus.files) {
|
|
475
|
+
signal?.throwIfAborted();
|
|
476
|
+
if (cached) {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
const url = `${env.remoteHost}${env.remotePathTemplate.replace("{model}", hostedModelId)}${file}`;
|
|
480
|
+
const cacheKey = cache === null ? `${hostedModelId}/${file}` : url;
|
|
481
|
+
const response = await env.fetch(url, { signal });
|
|
482
|
+
if (!response.ok || response.body === null) {
|
|
483
|
+
throw new Error(`Could not download ${url}: HTTP ${response.status}.`);
|
|
484
|
+
}
|
|
485
|
+
const reader = response.body.getReader();
|
|
486
|
+
try {
|
|
487
|
+
if (cache !== null) {
|
|
488
|
+
const stream = new ReadableStream({
|
|
489
|
+
async pull(controller) {
|
|
490
|
+
try {
|
|
491
|
+
signal?.throwIfAborted();
|
|
492
|
+
const { done, value } = await reader.read();
|
|
493
|
+
signal?.throwIfAborted();
|
|
494
|
+
if (done) {
|
|
495
|
+
controller.close();
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
loadedBytes += value.byteLength;
|
|
499
|
+
emitProgress(file);
|
|
500
|
+
signal?.throwIfAborted();
|
|
501
|
+
controller.enqueue(value);
|
|
502
|
+
} catch (error) {
|
|
503
|
+
controller.error(error);
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
cancel: () => reader.cancel()
|
|
507
|
+
});
|
|
508
|
+
await cache.put(cacheKey, new Response(stream, { headers: response.headers }));
|
|
509
|
+
} else {
|
|
510
|
+
const [{ mkdir, open, rename, unlink }, { join, dirname }, { randomUUID }] = await Promise.all([
|
|
511
|
+
importNodeModule("node:fs/promises"),
|
|
512
|
+
importNodeModule("node:path"),
|
|
513
|
+
importNodeModule("node:crypto")
|
|
514
|
+
]);
|
|
515
|
+
if (env.cacheDir === null) {
|
|
516
|
+
throw new Error("Transformers.js env.cacheDir must be set.");
|
|
517
|
+
}
|
|
518
|
+
const destination = join(env.cacheDir, cacheKey);
|
|
519
|
+
const temporary = `${destination}.tmp.${randomUUID()}`;
|
|
520
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
521
|
+
const handle = await open(temporary, "wx");
|
|
522
|
+
try {
|
|
523
|
+
while (true) {
|
|
524
|
+
signal?.throwIfAborted();
|
|
525
|
+
const { done, value } = await reader.read();
|
|
526
|
+
signal?.throwIfAborted();
|
|
527
|
+
if (done) {
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
await handle.writeFile(value);
|
|
531
|
+
loadedBytes += value.byteLength;
|
|
532
|
+
emitProgress(file);
|
|
533
|
+
}
|
|
534
|
+
await handle.close();
|
|
535
|
+
signal?.throwIfAborted();
|
|
536
|
+
await rename(temporary, destination);
|
|
537
|
+
} catch (error) {
|
|
538
|
+
await handle.close();
|
|
539
|
+
await unlink(temporary);
|
|
540
|
+
throw error;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
} finally {
|
|
544
|
+
await reader.cancel().catch(() => {});
|
|
545
|
+
reader.releaseLock();
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
signal?.throwIfAborted();
|
|
549
|
+
if (!await ModelRegistry.is_pipeline_cached("automatic-speech-recognition", hostedModelId, registryOptions)) {
|
|
550
|
+
throw new Error(`The Whisper model "${model}" was not fully cached.`);
|
|
551
|
+
}
|
|
552
|
+
signal?.throwIfAborted();
|
|
553
|
+
onProgress?.({
|
|
554
|
+
file: null,
|
|
555
|
+
progress: 1,
|
|
556
|
+
loadedBytes: totalBytes,
|
|
557
|
+
totalBytes
|
|
558
|
+
});
|
|
559
|
+
return { alreadyDownloaded: false };
|
|
560
|
+
}).catch((error) => {
|
|
561
|
+
signal?.throwIfAborted();
|
|
562
|
+
throw error;
|
|
563
|
+
});
|
|
564
|
+
signal?.throwIfAborted();
|
|
565
|
+
return result;
|
|
566
|
+
};
|
|
256
567
|
// src/is-whisper-model-cached.ts
|
|
257
568
|
var isWhisperModelCached = ({
|
|
258
569
|
model
|
|
@@ -264,14 +575,22 @@ var isWhisperModelCached = ({
|
|
|
264
575
|
});
|
|
265
576
|
});
|
|
266
577
|
};
|
|
578
|
+
// src/symbol-async-dispose.ts
|
|
579
|
+
if (typeof Symbol.asyncDispose !== "symbol") {
|
|
580
|
+
Object.defineProperty(Symbol, "asyncDispose", {
|
|
581
|
+
value: Symbol.for("asyncDispose")
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
267
585
|
// src/load-whisper-model.ts
|
|
268
586
|
var pipelines = new Map;
|
|
269
587
|
var getOrCreateWhisperPipeline = ({
|
|
270
588
|
model,
|
|
271
589
|
onProgress
|
|
272
590
|
}) => {
|
|
273
|
-
|
|
274
|
-
|
|
591
|
+
if (!getModelInfo(model)) {
|
|
592
|
+
throw new Error(`Unsupported Whisper model "${model}".`);
|
|
593
|
+
}
|
|
275
594
|
const existing = pipelines.get(model);
|
|
276
595
|
if (existing) {
|
|
277
596
|
if (onProgress) {
|
|
@@ -299,7 +618,8 @@ var getOrCreateWhisperPipeline = ({
|
|
|
299
618
|
activeTranscriptions: 0,
|
|
300
619
|
onIdle: [],
|
|
301
620
|
progressListeners,
|
|
302
|
-
latestProgress: null
|
|
621
|
+
latestProgress: null,
|
|
622
|
+
retainedLoadHandles: 0
|
|
303
623
|
};
|
|
304
624
|
const emitProgress = (progress) => {
|
|
305
625
|
state.latestProgress = progress;
|
|
@@ -308,43 +628,30 @@ var getOrCreateWhisperPipeline = ({
|
|
|
308
628
|
}
|
|
309
629
|
};
|
|
310
630
|
const loading = Promise.resolve().then(() => {
|
|
311
|
-
return withRemotionModelHost(({ pipeline }) => {
|
|
631
|
+
return withRemotionModelHost(async ({ ModelRegistry, pipeline }) => {
|
|
632
|
+
const hostedModelId = getHostedModelId(model);
|
|
633
|
+
if (!await ModelRegistry.is_pipeline_cached("automatic-speech-recognition", hostedModelId, { device: "webgpu", dtype: getWhisperWebGpuDtype(model) })) {
|
|
634
|
+
throw new Error(`The Whisper model "${model}" is not downloaded. Call downloadWhisperModel() first.`);
|
|
635
|
+
}
|
|
312
636
|
emitProgress({
|
|
313
|
-
status: "
|
|
637
|
+
status: "initializing",
|
|
314
638
|
file: null,
|
|
315
|
-
progress:
|
|
316
|
-
loadedBytes:
|
|
317
|
-
totalBytes
|
|
639
|
+
progress: null,
|
|
640
|
+
loadedBytes: null,
|
|
641
|
+
totalBytes: null
|
|
318
642
|
});
|
|
319
|
-
const hostedModelId = getHostedModelId(model);
|
|
320
|
-
const loadedByFile = new Map;
|
|
321
|
-
let lastProgress = 0;
|
|
322
|
-
let lastLoadedBytes = 0;
|
|
323
643
|
return pipeline("automatic-speech-recognition", hostedModelId, {
|
|
324
644
|
device: "webgpu",
|
|
325
645
|
dtype: getWhisperWebGpuDtype(model),
|
|
326
646
|
progress_callback: (event) => {
|
|
327
647
|
const record = event;
|
|
328
|
-
if (record.status === "progress" && typeof record.file === "string" && typeof record.loaded === "number" && Number.isFinite(record.loaded)) {
|
|
329
|
-
loadedByFile.set(record.file, Math.max(loadedByFile.get(record.file) ?? 0, record.loaded));
|
|
330
|
-
const loadedBytes = [...loadedByFile.values()].reduce((sum, loaded) => sum + loaded, 0);
|
|
331
|
-
lastProgress = Math.max(lastProgress, Math.min(loadedBytes / totalBytes, 0.99));
|
|
332
|
-
lastLoadedBytes = Math.max(lastLoadedBytes, loadedBytes);
|
|
333
|
-
emitProgress({
|
|
334
|
-
status: "loading",
|
|
335
|
-
file: null,
|
|
336
|
-
progress: lastProgress,
|
|
337
|
-
loadedBytes: lastLoadedBytes,
|
|
338
|
-
totalBytes
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
648
|
if (record.status === "ready") {
|
|
342
649
|
emitProgress({
|
|
343
650
|
status: "ready",
|
|
344
651
|
file: null,
|
|
345
652
|
progress: 1,
|
|
346
|
-
loadedBytes:
|
|
347
|
-
totalBytes
|
|
653
|
+
loadedBytes: null,
|
|
654
|
+
totalBytes: null
|
|
348
655
|
});
|
|
349
656
|
}
|
|
350
657
|
}
|
|
@@ -368,26 +675,77 @@ var getOrCreateWhisperPipeline = ({
|
|
|
368
675
|
}
|
|
369
676
|
};
|
|
370
677
|
};
|
|
678
|
+
var disposeWhisperPipelineState = async ({
|
|
679
|
+
model,
|
|
680
|
+
state
|
|
681
|
+
}) => {
|
|
682
|
+
if (pipelines.get(model) !== state) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
pipelines.delete(model);
|
|
686
|
+
const loadedPipeline = await state.loading;
|
|
687
|
+
if (state.activeTranscriptions > 0) {
|
|
688
|
+
await new Promise((resolve) => {
|
|
689
|
+
state.onIdle.push(resolve);
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
await loadedPipeline.dispose();
|
|
693
|
+
};
|
|
371
694
|
var loadWhisperModel = async ({
|
|
372
695
|
model,
|
|
373
|
-
onProgress
|
|
696
|
+
onProgress,
|
|
697
|
+
signal
|
|
374
698
|
}) => {
|
|
699
|
+
signal?.throwIfAborted();
|
|
375
700
|
const { state, alreadyLoaded, unsubscribe } = getOrCreateWhisperPipeline({
|
|
376
701
|
model,
|
|
377
|
-
onProgress
|
|
702
|
+
onProgress: (progress) => {
|
|
703
|
+
if (!signal?.aborted) {
|
|
704
|
+
onProgress?.(progress);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
378
707
|
});
|
|
708
|
+
state.retainedLoadHandles++;
|
|
379
709
|
try {
|
|
380
710
|
await state.loading;
|
|
711
|
+
signal?.throwIfAborted();
|
|
712
|
+
} catch (error) {
|
|
713
|
+
state.retainedLoadHandles--;
|
|
714
|
+
if (state.retainedLoadHandles === 0 && state.activeTranscriptions === 0) {
|
|
715
|
+
await disposeWhisperPipelineState({ model, state }).catch((disposeError) => {
|
|
716
|
+
signal?.throwIfAborted();
|
|
717
|
+
throw disposeError;
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
signal?.throwIfAborted();
|
|
721
|
+
throw error;
|
|
381
722
|
} finally {
|
|
382
723
|
unsubscribe();
|
|
383
724
|
}
|
|
384
|
-
|
|
725
|
+
let released = false;
|
|
726
|
+
const result = { alreadyLoaded };
|
|
727
|
+
Object.defineProperty(result, Symbol.asyncDispose, {
|
|
728
|
+
enumerable: false,
|
|
729
|
+
value: async () => {
|
|
730
|
+
if (released) {
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
released = true;
|
|
734
|
+
state.retainedLoadHandles--;
|
|
735
|
+
if (state.retainedLoadHandles === 0) {
|
|
736
|
+
await disposeWhisperPipelineState({ model, state });
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
return result;
|
|
385
741
|
};
|
|
386
742
|
var withLoadedWhisperPipeline = async ({
|
|
387
743
|
model,
|
|
388
744
|
onProgress,
|
|
745
|
+
signal,
|
|
389
746
|
run
|
|
390
747
|
}) => {
|
|
748
|
+
signal?.throwIfAborted();
|
|
391
749
|
const { state, unsubscribe } = getOrCreateWhisperPipeline({
|
|
392
750
|
model,
|
|
393
751
|
onProgress
|
|
@@ -396,7 +754,11 @@ var withLoadedWhisperPipeline = async ({
|
|
|
396
754
|
try {
|
|
397
755
|
const loaded = await state.loading;
|
|
398
756
|
unsubscribe();
|
|
757
|
+
signal?.throwIfAborted();
|
|
399
758
|
return await run(loaded);
|
|
759
|
+
} catch (error) {
|
|
760
|
+
signal?.throwIfAborted();
|
|
761
|
+
throw error;
|
|
400
762
|
} finally {
|
|
401
763
|
unsubscribe();
|
|
402
764
|
state.activeTranscriptions--;
|
|
@@ -404,6 +766,11 @@ var withLoadedWhisperPipeline = async ({
|
|
|
404
766
|
for (const resolve of state.onIdle.splice(0)) {
|
|
405
767
|
resolve();
|
|
406
768
|
}
|
|
769
|
+
if (signal?.aborted && state.retainedLoadHandles === 0) {
|
|
770
|
+
await disposeWhisperPipelineState({ model, state }).catch(() => {
|
|
771
|
+
signal.throwIfAborted();
|
|
772
|
+
});
|
|
773
|
+
}
|
|
407
774
|
}
|
|
408
775
|
}
|
|
409
776
|
};
|
|
@@ -413,19 +780,8 @@ var disposeWhisperModel = async ({
|
|
|
413
780
|
const matching = [...pipelines.entries()].filter(([loadedModel]) => {
|
|
414
781
|
return model === undefined || loadedModel === model;
|
|
415
782
|
});
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
pipelines.delete(key);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
await Promise.all(matching.map(async ([, state]) => {
|
|
422
|
-
const loadedPipeline = await state.loading;
|
|
423
|
-
if (state.activeTranscriptions > 0) {
|
|
424
|
-
await new Promise((resolve) => {
|
|
425
|
-
state.onIdle.push(resolve);
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
await loadedPipeline.dispose();
|
|
783
|
+
await Promise.all(matching.map(([key, state]) => {
|
|
784
|
+
return disposeWhisperPipelineState({ model: key, state });
|
|
429
785
|
}));
|
|
430
786
|
};
|
|
431
787
|
// src/remove-whisper-model.ts
|
|
@@ -497,8 +853,10 @@ var transcribe = async ({
|
|
|
497
853
|
topK = 50,
|
|
498
854
|
repetitionPenalty = 1,
|
|
499
855
|
noRepeatNgramSize = 0,
|
|
500
|
-
onModelLoadProgress
|
|
856
|
+
onModelLoadProgress,
|
|
857
|
+
signal
|
|
501
858
|
}) => {
|
|
859
|
+
signal?.throwIfAborted();
|
|
502
860
|
if (channelWaveform.length === 0) {
|
|
503
861
|
throw new Error("The audio waveform is empty.");
|
|
504
862
|
}
|
|
@@ -542,7 +900,22 @@ var transcribe = async ({
|
|
|
542
900
|
const output = await withLoadedWhisperPipeline({
|
|
543
901
|
model,
|
|
544
902
|
onProgress: onModelLoadProgress,
|
|
903
|
+
signal: signal ?? null,
|
|
545
904
|
run: async (transcriber) => {
|
|
905
|
+
let logitsProcessor = null;
|
|
906
|
+
if (signal) {
|
|
907
|
+
const { LogitsProcessor, LogitsProcessorList } = await import("@huggingface/transformers");
|
|
908
|
+
|
|
909
|
+
class AbortLogitsProcessor extends LogitsProcessor {
|
|
910
|
+
_call(_inputIds, logits) {
|
|
911
|
+
signal?.throwIfAborted();
|
|
912
|
+
return logits;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
logitsProcessor = new LogitsProcessorList;
|
|
916
|
+
logitsProcessor.push(new AbortLogitsProcessor);
|
|
917
|
+
}
|
|
918
|
+
signal?.throwIfAborted();
|
|
546
919
|
return await transcriber(channelWaveform, {
|
|
547
920
|
return_timestamps: "word",
|
|
548
921
|
chunk_length_s: chunkLengthInSeconds,
|
|
@@ -553,10 +926,12 @@ var transcribe = async ({
|
|
|
553
926
|
top_k: topK,
|
|
554
927
|
repetition_penalty: repetitionPenalty,
|
|
555
928
|
no_repeat_ngram_size: noRepeatNgramSize,
|
|
929
|
+
...logitsProcessor ? { logits_processor: logitsProcessor } : {},
|
|
556
930
|
...multilingual ? { language, task } : {}
|
|
557
931
|
});
|
|
558
932
|
}
|
|
559
933
|
});
|
|
934
|
+
signal?.throwIfAborted();
|
|
560
935
|
if (!output.chunks) {
|
|
561
936
|
throw new Error("The model did not return word-level timestamps. Use one of the timestamped models returned by getAvailableModels().");
|
|
562
937
|
}
|
|
@@ -594,6 +969,7 @@ export {
|
|
|
594
969
|
loadWhisperModel,
|
|
595
970
|
isWhisperModelCached,
|
|
596
971
|
getAvailableModels,
|
|
972
|
+
downloadWhisperModel,
|
|
597
973
|
disposeWhisperModel,
|
|
598
974
|
clearStaleModels,
|
|
599
975
|
canUseWhisperWebGpu,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const importNodeModule: <T>(specifier: string) => Promise<T>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { WhisperWebGpuUnsupportedReason, canUseWhisperWebGpu, } from './can-use-whisper-webgpu';
|
|
2
2
|
export type { CanUseWhisperWebGpuResult } from './can-use-whisper-webgpu';
|
|
3
3
|
export { clearStaleModels } from './clear-stale-models';
|
|
4
|
+
export { downloadWhisperModel } from './download-whisper-model';
|
|
5
|
+
export type { DownloadWhisperModelOptions, DownloadWhisperModelResult, WhisperModelDownloadProgress, } from './download-whisper-model';
|
|
4
6
|
export { isWhisperModelCached } from './is-whisper-model-cached';
|
|
5
7
|
export type { IsWhisperModelCachedOptions } from './is-whisper-model-cached';
|
|
6
8
|
export { disposeWhisperModel, loadWhisperModel } from './load-whisper-model';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import './symbol-async-dispose';
|
|
1
2
|
import { type WhisperWebGpuModel } from './models';
|
|
2
3
|
export type WhisperWebGpuModelLoadProgress = {
|
|
3
4
|
status: string;
|
|
@@ -14,14 +15,16 @@ type LoadedWhisperPipeline = {
|
|
|
14
15
|
export type LoadWhisperModelOptions = {
|
|
15
16
|
model: WhisperWebGpuModel;
|
|
16
17
|
onProgress?: OnWhisperWebGpuModelLoadProgress;
|
|
18
|
+
signal?: AbortSignal;
|
|
17
19
|
};
|
|
18
|
-
export type LoadWhisperModelResult = {
|
|
20
|
+
export type LoadWhisperModelResult = AsyncDisposable & {
|
|
19
21
|
alreadyLoaded: boolean;
|
|
20
22
|
};
|
|
21
|
-
export declare const loadWhisperModel: ({ model, onProgress, }: LoadWhisperModelOptions) => Promise<LoadWhisperModelResult>;
|
|
22
|
-
export declare const withLoadedWhisperPipeline: <ReturnValue>({ model, onProgress, run, }: {
|
|
23
|
+
export declare const loadWhisperModel: ({ model, onProgress, signal, }: LoadWhisperModelOptions) => Promise<LoadWhisperModelResult>;
|
|
24
|
+
export declare const withLoadedWhisperPipeline: <ReturnValue>({ model, onProgress, signal, run, }: {
|
|
23
25
|
model: "base" | "base.en" | "large-v3-turbo" | "medium" | "medium.en" | "small" | "small.en" | "tiny" | "tiny.en";
|
|
24
26
|
onProgress?: OnWhisperWebGpuModelLoadProgress | undefined;
|
|
27
|
+
signal: AbortSignal | null;
|
|
25
28
|
run: (pipeline: LoadedWhisperPipeline) => Promise<ReturnValue>;
|
|
26
29
|
}) => Promise<ReturnValue>;
|
|
27
30
|
export type DisposeWhisperModelOptions = {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const probeNodeWebGpu: () => Promise<void>;
|
|
File without changes
|
package/dist/transcribe.d.ts
CHANGED
|
@@ -25,5 +25,6 @@ export type TranscribeOptions = {
|
|
|
25
25
|
repetitionPenalty?: number;
|
|
26
26
|
noRepeatNgramSize?: number;
|
|
27
27
|
onModelLoadProgress?: OnWhisperWebGpuModelLoadProgress;
|
|
28
|
+
signal?: AbortSignal;
|
|
28
29
|
};
|
|
29
|
-
export declare const transcribe: ({ channelWaveform, model, language, task, chunkLengthInSeconds, strideLengthInSeconds, forceFullSequences, doSample, temperature, topK, repetitionPenalty, noRepeatNgramSize, onModelLoadProgress, }: TranscribeOptions) => Promise<WhisperWebGpuTranscription>;
|
|
30
|
+
export declare const transcribe: ({ channelWaveform, model, language, task, chunkLengthInSeconds, strideLengthInSeconds, forceFullSequences, doSample, temperature, topK, repetitionPenalty, noRepeatNgramSize, onModelLoadProgress, signal, }: TranscribeOptions) => Promise<WhisperWebGpuTranscription>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remotion/whisper-webgpu",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.528",
|
|
4
4
|
"description": "Transcribe audio in the browser using Whisper and WebGPU",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -24,10 +24,11 @@
|
|
|
24
24
|
"url": "https://github.com/remotion-dev/remotion/issues"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@remotion/captions": "4.0.
|
|
27
|
+
"@remotion/captions": "4.0.528",
|
|
28
|
+
"onnxruntime-node": "1.24.3"
|
|
28
29
|
},
|
|
29
30
|
"peerDependencies": {
|
|
30
|
-
"@huggingface/transformers": "4.
|
|
31
|
+
"@huggingface/transformers": "4.2.0"
|
|
31
32
|
},
|
|
32
33
|
"exports": {
|
|
33
34
|
".": {
|
|
@@ -38,8 +39,8 @@
|
|
|
38
39
|
"./package.json": "./package.json"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
|
-
"@huggingface/transformers": "4.
|
|
42
|
-
"@remotion/eslint-config-internal": "4.0.
|
|
42
|
+
"@huggingface/transformers": "4.2.0",
|
|
43
|
+
"@remotion/eslint-config-internal": "4.0.528",
|
|
43
44
|
"@typescript/native-preview": "7.0.0-dev.20260217.1",
|
|
44
45
|
"eslint": "9.19.0"
|
|
45
46
|
},
|