records whatever's playing on this machine, transcribes it locally — no upload, no cloud, no server-side code
Click any paragraph to edit it. The title field doubles as a picker — type or select an existing transcript's title to load it. Edits save automatically — .srt/.vtt exports stay tied to the original transcription.
Nothing transcribed yet. Pick a device, hit start, then stop when you're done.
No saved transcripts yet.
Paste an existing transcript, or upload a .txt/.srt/.vtt file — .srt/.vtt keep their real timing, plain text gets grouped by sentence instead.
Pick "Monitor of ..." as the capture device to transcribe whatever's already playing on this machine — it's the system's own speaker output routed back in as an input, not a physical microphone. A regular microphone works too, e.g. for dictating notes directly.
System audio (a "Monitor of ..." source)
|
v
Web Audio API - an AudioWorklet resamples it to 16kHz mono PCM
|
v
A Web Worker - transformers.js loads Whisper and runs inference there,
off the main thread, so the UI never freezes
|
v
Word-level timestamps -> sentences -> paragraphs (grouped by pause length)
|
v
Transcript UI - editable, exports to .txt / .srt / .vtt, saved locally
const stream = await navigator.mediaDevices.getUserMedia({
audio: { deviceId: { exact: monitorDeviceId } }
});
const ctx = new AudioContext({ sampleRate: 16000 }); // browser resamples for you
await ctx.audioWorklet.addModule('pcm-processor.js');
const source = ctx.createMediaStreamSource(stream);
const worklet = new AudioWorkletNode(ctx, 'pcm-capture-processor');
source.connect(worklet); // not connected to destination -- we're reading, not playing
// whisper-worker.js -- runs inside a Web Worker
import { pipeline } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2';
const transcribe = await pipeline('automatic-speech-recognition', 'Xenova/whisper-base.en');
const result = await transcribe(pcmFloat32Array, {
return_timestamps: 'word',
chunk_length_s: 30,
});
function sentencesToParagraphs(sentences, pauseThreshold = 1.2) {
const paragraphs = [];
let current = [];
for (const s of sentences) {
const prev = current[current.length - 1];
if (prev && s.start - prev.end > pauseThreshold) {
paragraphs.push(current);
current = [];
}
current.push(s);
}
if (current.length) paragraphs.push(current);
return paragraphs;
}
Transcription is powered by OpenAI's Whisper speech-recognition model, running via ONNX builds published by Xenova on Hugging Face, executed entirely in-browser using transformers.js.