MakeSubs
Drop a video and get speaker-aware multilingual subtitles, without opening a desktop editor.
Drop a video and get speaker-aware multilingual subtitles, without opening a desktop editor.
MakeSubs turns uploaded videos into subtitles automatically. Drop an MKV file, watch it play back in the browser, and receive a timed caption file when processing finishes — with separate speakers labeled when the audio supports it.
The product is still a prototype, but the pipeline is clear: do as much as possible on the client, send only the audio that matters to the server, and run a speech stack tuned for messy real-world footage rather than clean podcasts.
Subtitle workflows usually assume you already have a clean audio file and a desktop tool like Subtitle Edit or Aegisub. For most people that is too much friction — and for MKV rips, TV captures, or clips with background music, the audio is nowhere near clean enough for off-the-shelf transcription.
Whisper is excellent at multilingual speech, but it hallucinates when music and effects leak into the input. Long files need chunking, speakers need separating, and you still want to see the video while you wait. MakeSubs is built around that full chain, not just “run Whisper on the upload.”
The intended flow is straightforward from the outside:
Behind that UI sits speaker diarization, voice-activity chunking, and per-chunk language detection so captions stay readable on dialogue-heavy material with multiple voices and languages.
Browsers cannot assume every upload is a simple MP4 with AAC audio. The prototype focuses on Matroska because that is what shows up in the wild — multiple tracks, AC-3 audio, long runtimes.
On drop, the client streams the file through an EBML parser and builds a map of tracks and audio clusters without uploading the whole video first. You see container metadata, codec details, and progress while the file is read in chunks.
The video track is remuxed for playback with mp4box.js, so the user can scrub the source footage while subtitles are generated. Keeping playback client-side avoids storing full video files on the server when only audio is needed for ASR.
Audio is pulled out of the container in the browser — including AC-3 payloads decoded through a WebAssembly build of liba52. The prototype proved that cluster-level extraction works; the product path sends a normalized audio stream to the backend rather than the raw MKV.
Heavy ML stays on the server. The processing chain is sequential, with each stage feeding the next:
Incoming audio passes through Mel-Band RoFormer vocal separation (via audio-separator). TV rips and clips with background music leave music in the stem that Whisper will confidently mishear; isolating vocals first materially reduces garbage captions.
Before separation and transcription, Silero VAD finds speech regions and splits on natural pauses. Chunks are merged to a minimum duration so the separator and ASR models always receive enough context, but never more than a safe maximum per pass. This keeps GPU memory predictable on hour-long files.
Each chunk is transcribed with faster-whisper using multilingual mode and automatic language detection per segment. Timestamps from chunk boundaries are shifted back onto the full timeline so the final SRT is continuous.
pyannote speaker diarization runs on the cleaned vocal stem to identify who spoke when. Segments are aligned with Whisper output so subtitles can prefix lines with speaker labels — useful for interviews, dubbing sessions, and multi-character scenes where a single block of text is not enough.
A light post-processing pass trims silence, merges overlapping cues, and normalizes line breaks. The result is returned to the client as standard SRT, ready to preview alongside the mp4box.js player or export for editing elsewhere.
Browser Backend
─────── ───────
MKV drop
→ probe tracks (EBML)
→ extract + play video (mp4box.js)
→ extract audio ──────→ voice separation (RoFormer)
VAD chunking (Silero)
diarization (pyannote)
transcribe (Whisper)
←──── SRT + speaker labels
preview / downloadThe split is intentional: the client owns container parsing and preview; the server owns GPU inference. Video never has to touch the backend unless you later add features that require it.
Early experiments in the repo validated the hardest client-side pieces — parsing multi-gigabyte MKV files in the browser, walking cluster boundaries, and decoding AC-3 audio through WASM without sending the full file upstream. Separate Python scripts exercised the server-side chain: RoFormer separation with VAD-guided chunking, then faster-whisper with per-chunk language detection.
The remaining work is wiring those pieces into one product: a job API, progress streaming back to the UI, mp4box.js remux for playback, and pyannote integrated into the same pipeline as Whisper.
Subtitle generation is not a single model problem. The quality ceiling is set long before Whisper runs — by how you split the file, how cleanly you isolate speech, and whether you know there are multiple speakers.
Client-side container parsing is worth the complexity. Users already have the file; reading it locally gives instant feedback and keeps privacy-sensitive footage off a server until they explicitly submit audio for processing.
Client
Backend