{"slug": "i-built-a-free-whisper-transcription-site-that-runs-in-the-browser-5-things-that", "title": "I built a free Whisper transcription site that runs in the browser: 5 things that broke", "summary": "A developer built Quietscribe, a free static site hosted on Cloudflare Pages that runs OpenAI's Whisper models (tiny, base, small) entirely in the browser via Web Workers, producing transcripts, SRT/VTT subtitles and burned-in captions without uploading files by default. The build required fixes for language detection (running a single decode step after <|startoftranscript|> and argmaxing over language tokens), Safari tab crashes on iPhone (a sessionStorage crash flag that steps down model size), repetition loops (no_repeat_ngram_size 4, repetition_penalty 1.15), and MP4 codec issues (explicit avc1 strings with WebM fallback). An opt-in Cloud mode sends ~60-second chunks to a Cloudflare Worker running @cf/openai/whisper-large-v3-turbo, with translation handled by @cf/meta/m2m100-1.2b since the turbo model cannot translate.", "body_md": "I'm a YouTube creator, and I got tired of subtitle tools that upload my footage, cap free minutes or stamp a watermark on the video. So I built **[Quietscribe](https://quietscribe.pages.dev)**: OpenAI's Whisper running directly in the browser. You get transcripts, SRT/VTT subtitles and even burned-in captions, and the file never leaves your device.\n\nIt's a static site on Cloudflare Pages: no backend for the default mode and zero hosting cost. Here are the five things that broke along the way, in case you're building something similar.\n\n`Xenova/whisper-tiny` / `base` / `small` in a module Web Worker`COOP: same-origin` + `COEP: credentialless` headers (via Cloudflare Pages `_headers`) so `crossOriginIsolated` is true and WASM can use threads`MediaRecorder` + WebAudio for burning captions into the video (MP4 where the browser supports H.264)\nMy first Urdu test came back as confident English gibberish. It turned out that when you don't pass a language to a multilingual Whisper model, the library falls back to English with only a console warning:\n\n```\n// TODO: Implement language detection\nconsole.warn('No language specified - defaulting to English (en).');\n```\n\nThe fix is to do what Whisper does internally. Take the first ~30 seconds of speech, run a single decoding step after `<|startoftranscript|>`, and pick the language token with the highest score:\n\n``` js\nlet scores = null;\nconst capture = (ids, logits) => { if (!scores) scores = logits.data.slice(); return logits; };\nawait asr.model.generate({\n  ...(await asr.processor(clip)),\n  decoder_input_ids: [cfg.decoder_start_token_id],\n  max_new_tokens: 1,\n  logits_processor: [capture],\n});\n// then argmax over cfg.lang_to_id\n```\n\nOn iPhone, the model download reached 100% and then the whole page reloaded. There was no error. Safari kills tabs that use too much memory. What fixed it:\n\n`whisper-tiny`\n`sessionStorage`. If the page loads and that flag is still there, the last run crashed, so the site steps down a model size and tells the user why.\n`tiny` and `base` sometimes get stuck repeating one word forever, especially in lower-resource languages. Two layers helped:\n\n`no_repeat_ngram_size: 4, repetition_penalty: 1.15`\nFor Urdu, the honest answer is that small on-device models aren't good enough, and the large ones don't fit on a phone. So I added an **opt-in Cloud mode**. The browser decodes the audio to 16 kHz mono, cuts it into ~60-second pieces at the quietest point, and sends each piece to a Cloudflare Worker running `@cf/openai/whisper-large-v3-turbo` on Workers AI. Nothing is stored. The free tier covers roughly 200 audio minutes a day.\n\nOne gotcha: **whisper-large-v3-turbo can't translate.** `task: 'translate'` just returns the original language. So the Worker transcribes first, then translates each segment with `@cf/meta/m2m100-1.2b`.\n\nBeing honest about this mattered. The site says \"private by default\". It used to say \"never uploaded\", which stopped being true the day Cloud mode shipped.\n\n`MediaRecorder` with plain `video/mp4` produced VP9-in-MP4 in some Chromium builds, which many players reject. Requesting explicit `avc1` codec strings first, and falling back to WebM, fixed it. Long captions also need to shrink to fit (down to ~65% size) before wrapping, otherwise you get single orphan words on a second line.\n\nWith a `share_target` in the web manifest and a tiny service worker that stores the shared file in Cache Storage and redirects to `/?shared=1`, Android users can tap **Share → Quietscribe** from their gallery or WhatsApp. The file still never leaves the device.\n\nIt's free, with no sign-up. There's a one-tap sample if you want to see it run without uploading anything: **[quietscribe.pages.dev](https://quietscribe.pages.dev)**. I'd love feedback on speed on your hardware and accuracy in your language.\n\n*Written by the AI agent that helped build Quietscribe, on behalf of its maker.*", "url": "https://wpnews.pro/news/i-built-a-free-whisper-transcription-site-that-runs-in-the-browser-5-things-that", "canonical_source": "https://dev.to/quietscribe/i-built-a-free-whisper-transcription-site-that-runs-in-the-browser-5-things-that-broke-59o6", "published_at": "2026-11-01 17:12:47+00:00", "updated_at": "2026-11-01 17:22:21.284131+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-tools", "large-language-models", "developer-tools"], "entities": ["Quietscribe", "OpenAI", "Whisper", "Cloudflare Pages", "Cloudflare Workers AI", "Xenova", "Safari", "whisper-large-v3-turbo"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/i-built-a-free-whisper-transcription-site-that-runs-in-the-browser-5-things-that", "markdown": "https://wpnews.pro/news/i-built-a-free-whisper-transcription-site-that-runs-in-the-browser-5-things-that.md", "text": "https://wpnews.pro/news/i-built-a-free-whisper-transcription-site-that-runs-in-the-browser-5-things-that.txt", "jsonld": "https://wpnews.pro/news/i-built-a-free-whisper-transcription-site-that-runs-in-the-browser-5-things-that.jsonld"}}