← Back to the app

How Subomatic works

Subomatic re-times an out-of-sync subtitle automatically — no manual anchor points. It lines the subtitle up with when speech happens, either in a reference subtitle you already trust or in the audio of the video itself. Everything below runs locally, in your browser.

The pipeline

  1. Decode (audio mode). Your media file is decoded by the browser's native WebAudio engine into a mono PCM signal. This is the same optimized (often hardware-accelerated) decoder the browser uses to play video, so any format it can play, Subomatic can read — and the file never leaves the page.
  2. Detect speech. A voice-activity detector turns that waveform into a sparse set of speech intervals — the spans of time where someone is actually talking. (In reference mode this step is skipped: the reference subtitle's own cue timings are the intervals.)
  3. Align. The engine treats both sides — the reference intervals and your subtitle's lines — as sets of time intervals, and searches for the timing correction that makes them overlap as much as possible.
  4. Rewrite. The chosen correction is applied to the cue timestamps and the subtitle is serialized back to the same format it came in. Only the timing changes — text, styling, positioning and karaoke all round-trip untouched.

Written in Rust, compiled to WebAssembly, running in your browser

There is no server. The whole thing is a single pure-Rust core — the subtitle parsers/serializers (SubRip .srt, WebVTT .vtt, MicroDVD .sub, ASS/SSA), the voice-activity detector, the alignment engine and the timing warp — with zero platform or audio dependencies and #![forbid(unsafe_code)]. Because it's dependency-free, the exact same code compiles two ways: to a native command-line tool, and to WebAssembly (a ~120 KB .wasm module) for the web.

In the browser, a thin JavaScript shell wires up the file pickers and calls two functions exported from the WASM module (sync_to_reference and sync_to_audio); the PCM is handed across as a Float32Array with no copying or serialization. Decoding deliberately lives outside the Rust core — the browser does it with WebAudio — which is exactly what keeps the core small, portable and WASM-clean. The page is just static files (HTML + CSS + JS + the .wasm) served from GitHub Pages, so once it has loaded there is no backend to talk to and nothing to upload.

Why it's reliable

Why it's fast

The alignment, in a little more detail

Each input is a set of half-open time intervals in milliseconds. For a candidate shift δ, a line's score is the total overlap between the line moved by δ and the (merged, non-overlapping) reference intervals. A dynamic program assigns every line an offset from the grid to maximize the summed overlap, minus a split penalty charged whenever two neighbouring lines take different offsets. With the penalty set to infinity (the default in the web app) every line shares one offset — a pure global shift; lower it and the program is free to break the file into a few independently-shifted segments to absorb an ad-break or a re-cut. Wrapped around that is the frame-rate scan: the same solve is run for each common play-rate ratio, and the highest-scoring combination of stretch + offset wins.