@remotion/whisper-webgpu 4.0.522 → 4.0.524

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.
@@ -67,8 +67,6 @@ var canUseWhisperWebGpu = async () => {
67
67
  }
68
68
  return { supported: true };
69
69
  };
70
- // src/clear-stale-models.ts
71
- var clearStaleModels = () => Promise.resolve();
72
70
  // src/models.ts
73
71
  var WHISPER_WEBGPU_MODELS = [
74
72
  "tiny",
@@ -87,6 +85,7 @@ var MODEL_INFO = {
87
85
  modelId: "onnx-community/whisper-tiny_timestamped",
88
86
  parameters: 39000000,
89
87
  multilingual: true,
88
+ supportsTranslation: true,
90
89
  webGpuDownloadSize: 119699015
91
90
  },
92
91
  "tiny.en": {
@@ -94,6 +93,7 @@ var MODEL_INFO = {
94
93
  modelId: "onnx-community/whisper-tiny.en_timestamped",
95
94
  parameters: 39000000,
96
95
  multilingual: false,
96
+ supportsTranslation: false,
97
97
  webGpuDownloadSize: 119697479
98
98
  },
99
99
  base: {
@@ -101,6 +101,7 @@ var MODEL_INFO = {
101
101
  modelId: "onnx-community/whisper-base_timestamped",
102
102
  parameters: 74000000,
103
103
  multilingual: true,
104
+ supportsTranslation: true,
104
105
  webGpuDownloadSize: 206190057
105
106
  },
106
107
  "base.en": {
@@ -108,6 +109,7 @@ var MODEL_INFO = {
108
109
  modelId: "onnx-community/whisper-base.en_timestamped",
109
110
  parameters: 74000000,
110
111
  multilingual: false,
112
+ supportsTranslation: false,
111
113
  webGpuDownloadSize: 206188009
112
114
  },
113
115
  small: {
@@ -115,6 +117,7 @@ var MODEL_INFO = {
115
117
  modelId: "onnx-community/whisper-small_timestamped",
116
118
  parameters: 244000000,
117
119
  multilingual: true,
120
+ supportsTranslation: true,
118
121
  webGpuDownloadSize: 586213010
119
122
  },
120
123
  "small.en": {
@@ -122,6 +125,7 @@ var MODEL_INFO = {
122
125
  modelId: "onnx-community/whisper-small.en_timestamped",
123
126
  parameters: 244000000,
124
127
  multilingual: false,
128
+ supportsTranslation: false,
125
129
  webGpuDownloadSize: 586209938
126
130
  },
127
131
  medium: {
@@ -129,6 +133,7 @@ var MODEL_INFO = {
129
133
  modelId: "onnx-community/whisper-medium_timestamped",
130
134
  parameters: 769000000,
131
135
  multilingual: true,
136
+ supportsTranslation: true,
132
137
  webGpuDownloadSize: 1698508143
133
138
  },
134
139
  "medium.en": {
@@ -136,6 +141,7 @@ var MODEL_INFO = {
136
141
  modelId: "onnx-community/whisper-medium.en_timestamped",
137
142
  parameters: 769000000,
138
143
  multilingual: false,
144
+ supportsTranslation: false,
139
145
  webGpuDownloadSize: 1698504047
140
146
  },
141
147
  "large-v3-turbo": {
@@ -143,20 +149,121 @@ var MODEL_INFO = {
143
149
  modelId: "onnx-community/whisper-large-v3-turbo_timestamped",
144
150
  parameters: 809000000,
145
151
  multilingual: true,
146
- webGpuDownloadSize: 2882584170
152
+ supportsTranslation: false,
153
+ webGpuDownloadSize: 1608611679
147
154
  }
148
155
  };
156
+ var HOSTED_MODEL_IDS = {
157
+ tiny: "whisper-tiny_timestamped-v1",
158
+ "tiny.en": "whisper-tiny.en_timestamped-v1",
159
+ base: "whisper-base_timestamped-v1",
160
+ "base.en": "whisper-base.en_timestamped-v1",
161
+ small: "whisper-small_timestamped-v1",
162
+ "small.en": "whisper-small.en_timestamped-v1",
163
+ medium: "whisper-medium_timestamped-v1",
164
+ "medium.en": "whisper-medium.en_timestamped-v1",
165
+ "large-v3-turbo": "whisper-large-v3-turbo_timestamped-v1"
166
+ };
149
167
  var getAvailableModels = () => {
150
168
  return WHISPER_WEBGPU_MODELS.map((model) => ({ ...MODEL_INFO[model] }));
151
169
  };
152
170
  var getModelInfo = (model) => {
153
171
  return MODEL_INFO[model];
154
172
  };
155
- var WHISPER_WEBGPU_DTYPE = {
173
+ var getHostedModelId = (model) => {
174
+ return HOSTED_MODEL_IDS[model];
175
+ };
176
+ var DEFAULT_WHISPER_WEBGPU_DTYPE = {
156
177
  encoder_model: "fp32",
157
178
  decoder_model_merged: "q4"
158
179
  };
180
+ var LARGE_V3_TURBO_WHISPER_WEBGPU_DTYPE = {
181
+ encoder_model: "fp16",
182
+ decoder_model_merged: "q4"
183
+ };
184
+ var getWhisperWebGpuDtype = (model) => {
185
+ return model === "large-v3-turbo" ? LARGE_V3_TURBO_WHISPER_WEBGPU_DTYPE : DEFAULT_WHISPER_WEBGPU_DTYPE;
186
+ };
159
187
 
188
+ // src/clear-stale-models.ts
189
+ var legacyModelUrlPrefixes = getAvailableModels().map(({ modelId }) => `https://huggingface.co/${modelId}/resolve/main/`);
190
+ var clearStaleModels = async () => {
191
+ if (typeof caches === "undefined") {
192
+ return;
193
+ }
194
+ const { env } = await import("@huggingface/transformers");
195
+ let cache;
196
+ let requests;
197
+ try {
198
+ cache = await caches.open(env.cacheKey);
199
+ requests = await cache.keys();
200
+ } catch {
201
+ return;
202
+ }
203
+ await Promise.all(requests.map((request) => {
204
+ if (legacyModelUrlPrefixes.some((prefix) => request.url.startsWith(prefix))) {
205
+ return cache.delete(request);
206
+ }
207
+ return Promise.resolve(false);
208
+ }));
209
+ };
210
+ // src/with-remotion-model-host.ts
211
+ var REMOTION_MODEL_HOST = "https://remotion.media/";
212
+ var REMOTION_MODEL_PATH_TEMPLATE = "models/{model}/";
213
+ var REMOTION_MODEL_HOST_STATE = Symbol.for("@remotion/whisper-webgpu/model-host-state");
214
+ var withRemotionModelHost = async (operation) => {
215
+ const transformers = await import("@huggingface/transformers");
216
+ const { env } = transformers;
217
+ const environmentWithState = env;
218
+ let state = environmentWithState[REMOTION_MODEL_HOST_STATE];
219
+ if (!state) {
220
+ state = {
221
+ activeOperations: 0,
222
+ previousRemoteConfiguration: null
223
+ };
224
+ Object.defineProperty(environmentWithState, REMOTION_MODEL_HOST_STATE, {
225
+ value: state
226
+ });
227
+ }
228
+ if (state.activeOperations === 0) {
229
+ state.previousRemoteConfiguration = {
230
+ remoteHost: env.remoteHost,
231
+ remotePathTemplate: env.remotePathTemplate
232
+ };
233
+ env.remoteHost = REMOTION_MODEL_HOST;
234
+ env.remotePathTemplate = REMOTION_MODEL_PATH_TEMPLATE;
235
+ }
236
+ state.activeOperations++;
237
+ try {
238
+ return await operation(transformers);
239
+ } finally {
240
+ state.activeOperations--;
241
+ if (state.activeOperations === 0) {
242
+ const configurationToRestore = state.previousRemoteConfiguration;
243
+ state.previousRemoteConfiguration = null;
244
+ if (configurationToRestore) {
245
+ if (env.remoteHost === REMOTION_MODEL_HOST) {
246
+ env.remoteHost = configurationToRestore.remoteHost;
247
+ }
248
+ if (env.remotePathTemplate === REMOTION_MODEL_PATH_TEMPLATE) {
249
+ env.remotePathTemplate = configurationToRestore.remotePathTemplate;
250
+ }
251
+ }
252
+ }
253
+ }
254
+ };
255
+
256
+ // src/is-whisper-model-cached.ts
257
+ var isWhisperModelCached = ({
258
+ model
259
+ }) => {
260
+ return withRemotionModelHost(({ ModelRegistry }) => {
261
+ return ModelRegistry.is_pipeline_cached("automatic-speech-recognition", getHostedModelId(model), {
262
+ device: "webgpu",
263
+ dtype: getWhisperWebGpuDtype(model)
264
+ });
265
+ });
266
+ };
160
267
  // src/load-whisper-model.ts
161
268
  var pipelines = new Map;
162
269
  var getOrCreateWhisperPipeline = ({
@@ -167,81 +274,112 @@ var getOrCreateWhisperPipeline = ({
167
274
  const totalBytes = modelInfo.webGpuDownloadSize;
168
275
  const existing = pipelines.get(model);
169
276
  if (existing) {
170
- return { state: existing, alreadyLoaded: true, totalBytes };
171
- }
172
- const loading = Promise.resolve().then(async () => {
173
- const { pipeline } = await import("@huggingface/transformers");
174
- onProgress?.({
175
- status: "loading",
176
- file: null,
177
- progress: 0,
178
- loadedBytes: 0,
179
- totalBytes
180
- });
181
- const { modelId } = modelInfo;
182
- const loadedByFile = new Map;
183
- let lastProgress = 0;
184
- let lastLoadedBytes = 0;
185
- return pipeline("automatic-speech-recognition", modelId, {
186
- device: "webgpu",
187
- dtype: WHISPER_WEBGPU_DTYPE,
188
- progress_callback: onProgress ? (event) => {
189
- const record = event;
190
- if (record.status === "progress" && typeof record.file === "string" && typeof record.loaded === "number" && Number.isFinite(record.loaded)) {
191
- loadedByFile.set(record.file, Math.max(loadedByFile.get(record.file) ?? 0, record.loaded));
192
- const loadedBytes = [...loadedByFile.values()].reduce((sum, loaded) => sum + loaded, 0);
193
- lastProgress = Math.max(lastProgress, Math.min(loadedBytes / totalBytes, 0.99));
194
- lastLoadedBytes = Math.max(lastLoadedBytes, loadedBytes);
195
- onProgress({
196
- status: "loading",
197
- file: null,
198
- progress: lastProgress,
199
- loadedBytes: lastLoadedBytes,
200
- totalBytes
201
- });
202
- }
203
- if (record.status === "ready") {
204
- onProgress({
205
- status: "ready",
206
- file: null,
207
- progress: 1,
208
- loadedBytes: Math.max(lastLoadedBytes, totalBytes),
209
- totalBytes
210
- });
277
+ if (onProgress) {
278
+ existing.progressListeners.add(onProgress);
279
+ if (existing.latestProgress) {
280
+ onProgress(existing.latestProgress);
281
+ }
282
+ }
283
+ return {
284
+ state: existing,
285
+ alreadyLoaded: true,
286
+ unsubscribe: () => {
287
+ if (onProgress) {
288
+ existing.progressListeners.delete(onProgress);
211
289
  }
212
- } : undefined
213
- });
214
- });
290
+ }
291
+ };
292
+ }
293
+ const progressListeners = new Set;
294
+ if (onProgress) {
295
+ progressListeners.add(onProgress);
296
+ }
215
297
  const state = {
216
- loading,
298
+ loading: Promise.resolve(null),
217
299
  activeTranscriptions: 0,
218
- onIdle: []
300
+ onIdle: [],
301
+ progressListeners,
302
+ latestProgress: null
303
+ };
304
+ const emitProgress = (progress) => {
305
+ state.latestProgress = progress;
306
+ for (const listener of state.progressListeners) {
307
+ listener(progress);
308
+ }
219
309
  };
310
+ const loading = Promise.resolve().then(() => {
311
+ return withRemotionModelHost(({ pipeline }) => {
312
+ emitProgress({
313
+ status: "loading",
314
+ file: null,
315
+ progress: 0,
316
+ loadedBytes: 0,
317
+ totalBytes
318
+ });
319
+ const hostedModelId = getHostedModelId(model);
320
+ const loadedByFile = new Map;
321
+ let lastProgress = 0;
322
+ let lastLoadedBytes = 0;
323
+ return pipeline("automatic-speech-recognition", hostedModelId, {
324
+ device: "webgpu",
325
+ dtype: getWhisperWebGpuDtype(model),
326
+ progress_callback: (event) => {
327
+ 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
+ if (record.status === "ready") {
342
+ emitProgress({
343
+ status: "ready",
344
+ file: null,
345
+ progress: 1,
346
+ loadedBytes: Math.max(lastLoadedBytes, totalBytes),
347
+ totalBytes
348
+ });
349
+ }
350
+ }
351
+ });
352
+ });
353
+ });
354
+ state.loading = loading;
220
355
  pipelines.set(model, state);
221
356
  loading.catch(() => {
222
357
  if (pipelines.get(model) === state) {
223
358
  pipelines.delete(model);
224
359
  }
225
360
  });
226
- return { state, alreadyLoaded: false, totalBytes };
361
+ return {
362
+ state,
363
+ alreadyLoaded: false,
364
+ unsubscribe: () => {
365
+ if (onProgress) {
366
+ state.progressListeners.delete(onProgress);
367
+ }
368
+ }
369
+ };
227
370
  };
228
371
  var loadWhisperModel = async ({
229
372
  model,
230
373
  onProgress
231
374
  }) => {
232
- const { state, alreadyLoaded, totalBytes } = getOrCreateWhisperPipeline({
375
+ const { state, alreadyLoaded, unsubscribe } = getOrCreateWhisperPipeline({
233
376
  model,
234
377
  onProgress
235
378
  });
236
- await state.loading;
237
- if (alreadyLoaded) {
238
- onProgress?.({
239
- status: "ready",
240
- file: null,
241
- progress: 1,
242
- loadedBytes: totalBytes,
243
- totalBytes
244
- });
379
+ try {
380
+ await state.loading;
381
+ } finally {
382
+ unsubscribe();
245
383
  }
246
384
  return { alreadyLoaded };
247
385
  };
@@ -250,24 +388,17 @@ var withLoadedWhisperPipeline = async ({
250
388
  onProgress,
251
389
  run
252
390
  }) => {
253
- const { state, alreadyLoaded, totalBytes } = getOrCreateWhisperPipeline({
391
+ const { state, unsubscribe } = getOrCreateWhisperPipeline({
254
392
  model,
255
393
  onProgress
256
394
  });
257
395
  state.activeTranscriptions++;
258
396
  try {
259
397
  const loaded = await state.loading;
260
- if (alreadyLoaded) {
261
- onProgress?.({
262
- status: "ready",
263
- file: null,
264
- progress: 1,
265
- loadedBytes: totalBytes,
266
- totalBytes
267
- });
268
- }
398
+ unsubscribe();
269
399
  return await run(loaded);
270
400
  } finally {
401
+ unsubscribe();
271
402
  state.activeTranscriptions--;
272
403
  if (state.activeTranscriptions === 0) {
273
404
  for (const resolve of state.onIdle.splice(0)) {
@@ -297,15 +428,16 @@ var disposeWhisperModel = async ({
297
428
  await loadedPipeline.dispose();
298
429
  }));
299
430
  };
300
- // src/is-whisper-model-cached.ts
301
- var isWhisperModelCached = async ({
431
+ // src/remove-whisper-model.ts
432
+ var removeWhisperModel = async ({
302
433
  model
303
434
  }) => {
304
- const { ModelRegistry } = await import("@huggingface/transformers");
305
- const { modelId } = getModelInfo(model);
306
- return ModelRegistry.is_pipeline_cached("automatic-speech-recognition", modelId, {
307
- device: "webgpu",
308
- dtype: WHISPER_WEBGPU_DTYPE
435
+ await disposeWhisperModel({ model });
436
+ await withRemotionModelHost(({ ModelRegistry }) => {
437
+ return ModelRegistry.clear_pipeline_cache("automatic-speech-recognition", getHostedModelId(model), {
438
+ device: "webgpu",
439
+ dtype: getWhisperWebGpuDtype(model)
440
+ });
309
441
  });
310
442
  };
311
443
  // src/resample-to-16khz.ts
@@ -356,8 +488,15 @@ var transcribe = async ({
356
488
  channelWaveform,
357
489
  model,
358
490
  language,
491
+ task = "transcribe",
359
492
  chunkLengthInSeconds = 30,
360
493
  strideLengthInSeconds = 5,
494
+ forceFullSequences = false,
495
+ doSample = false,
496
+ temperature = 1,
497
+ topK = 50,
498
+ repetitionPenalty = 1,
499
+ noRepeatNgramSize = 0,
361
500
  onModelLoadProgress
362
501
  }) => {
363
502
  if (channelWaveform.length === 0) {
@@ -369,7 +508,31 @@ var transcribe = async ({
369
508
  if (!Number.isFinite(strideLengthInSeconds) || strideLengthInSeconds < 0 || strideLengthInSeconds * 2 >= chunkLengthInSeconds) {
370
509
  throw new Error("strideLengthInSeconds must be a finite, non-negative number and less than half of chunkLengthInSeconds.");
371
510
  }
372
- const { multilingual } = getModelInfo(model);
511
+ if (typeof forceFullSequences !== "boolean") {
512
+ throw new TypeError("forceFullSequences must be a boolean.");
513
+ }
514
+ if (typeof doSample !== "boolean") {
515
+ throw new TypeError("doSample must be a boolean.");
516
+ }
517
+ if (!Number.isFinite(temperature) || temperature <= 0) {
518
+ throw new TypeError("temperature must be a finite number greater than 0.");
519
+ }
520
+ if (!Number.isInteger(topK) || topK < 0) {
521
+ throw new TypeError("topK must be a non-negative integer.");
522
+ }
523
+ if (!Number.isFinite(repetitionPenalty) || repetitionPenalty <= 0) {
524
+ throw new TypeError("repetitionPenalty must be a finite number greater than 0.");
525
+ }
526
+ if (!Number.isInteger(noRepeatNgramSize) || noRepeatNgramSize < 0) {
527
+ throw new TypeError("noRepeatNgramSize must be a non-negative integer.");
528
+ }
529
+ if (task !== "transcribe" && task !== "translate") {
530
+ throw new TypeError('task must be either "transcribe" or "translate".');
531
+ }
532
+ const { multilingual, supportsTranslation } = getModelInfo(model);
533
+ if (task === "translate" && !supportsTranslation) {
534
+ throw new Error(`The model "${model}" does not support translation.`);
535
+ }
373
536
  if (multilingual && (language === undefined || language === "auto")) {
374
537
  throw new Error(`The language option is required for the multilingual model "${model}" because automatic language detection is not supported.`);
375
538
  }
@@ -384,7 +547,13 @@ var transcribe = async ({
384
547
  return_timestamps: "word",
385
548
  chunk_length_s: chunkLengthInSeconds,
386
549
  stride_length_s: strideLengthInSeconds,
387
- ...multilingual ? { language } : {}
550
+ force_full_sequences: forceFullSequences,
551
+ do_sample: doSample,
552
+ temperature,
553
+ top_k: topK,
554
+ repetition_penalty: repetitionPenalty,
555
+ no_repeat_ngram_size: noRepeatNgramSize,
556
+ ...multilingual ? { language, task } : {}
388
557
  });
389
558
  }
390
559
  });
@@ -421,6 +590,7 @@ export {
421
590
  transcribe,
422
591
  toCaptions,
423
592
  resampleTo16Khz,
593
+ removeWhisperModel,
424
594
  loadWhisperModel,
425
595
  isWhisperModelCached,
426
596
  getAvailableModels,