File size: 2,358 Bytes
be07307
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Clipwhit speech-to-text worker: Whisper in the browser with transformers.js (WebGPU when available, else WASM).
// Audio arrives as 16 kHz mono Float32Array; nothing is uploaded. © 2026 CyberMax. All rights reserved.
import { pipeline, WhisperTextStreamer, env } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.3.0';

env.allowLocalModels = false;
let asr = null, loadedKey = null;

async function load(model, device, post) {
  const key = `${model}|${device}`;
  if (asr && loadedKey === key) return asr;
  if (asr) { try { await asr.dispose(); } catch {} asr = null; loadedKey = null; }
  const dtype = device === 'webgpu' ? { encoder_model: 'fp32', decoder_model_merged: 'q4' } : 'q8';
  asr = await pipeline('automatic-speech-recognition', model, {
    device, dtype,
    progress_callback: (p) => post({ type: 'load', file: p.file, loaded: p.loaded, total: p.total }),
  });
  loadedKey = key;
  return asr;
}

self.onmessage = async ({ data }) => {
  if (data?.type !== 'run') return;
  const post = (m) => self.postMessage(m);
  try {
    let device = data.device, p;
    try { p = await load(data.model, device, post); } catch (e) {
      if (device !== 'webgpu') throw e;
      post({ type: 'info', message: 'WebGPU could not start; using the CPU instead.' });
      device = 'wasm';
      p = await load(data.model, device, post);
    }
    post({ type: 'ready', device });
    const english = /\.en$/.test(data.model);
    const opts = { chunk_length_s: 30, stride_length_s: 5, return_timestamps: true, ...(english ? {} : { language: data.language || null, task: 'transcribe' }) };
    let done = 0, out;
    try {
      const time_precision = p.processor.feature_extractor.config.chunk_length / p.model.config.max_source_positions;
      const streamer = new WhisperTextStreamer(p.tokenizer, {
        time_precision, skip_prompt: true,
        callback_function: (text) => post({ type: 'partial', text }),
        on_finalize: () => post({ type: 'chunk', done: ++done }),
      });
      out = await p(data.audio, { ...opts, streamer });
    } catch (e) {
      if (!/stream/i.test(String(e?.message || e))) throw e;
      out = await p(data.audio, opts);
    }
    post({ type: 'done', chunks: out.chunks || [], text: out.text, device });
  } catch (e) {
    post({ type: 'error', message: String(e?.message || e) });
  }
};