clipwhit / asr-worker.js
CyberMax-tools's picture
Deploy clipwhit
be07307 verified
Raw History Blame Contribute Delete
2.36 kB
// 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) });
}
};