subtitle‑tools
Retime, clean & convert captions locally — subtitles never leave your device.
1 · Load a file
2 · Pick a transform
One click runs four safe passes at broadcast-typical defaults — trim overlaps, open a minimum gap, hold a readable min/max duration, then re-wrap long lines. Every change is listed below, and anything that can’t be fully fixed (a cue too short to reach the minimum, a word longer than the line) is shown honestly as a residual — never forced into an overlap or dropped. The fixed file is downloadable.
Collapses consecutive cues whose text is identical — the stutter auto-generated and rolling captions leave behind — into one cue that spans from the first duplicate’s start to the last duplicate’s end. Only back-to-back repeats are merged; a line that genuinely recurs later in the file is kept. Text is left exactly as authored (only leading/trailing whitespace is ignored for the match) and cues are renumbered. The result tells you how many cues were removed.
Loads the file above as a second language and folds it onto the primary track (the file in box 1) — each secondary line is stacked under the primary cue it overlaps most, giving one dual-language caption file. Timings and cue count come from the primary track; a secondary line that overlaps no primary cue is dropped (reported in the count). Both files stay SRT/VTT and never leave your device. Leave this empty to use any other transform normally.
e.g. 2.5 pushes every cue 2.5 s later; -1.5 pulls them 1.5 s earlier. Times below zero clamp to 00:00:00.
Use when a track is in sync up to a point (an ad-break re-insert, a trimmed scene) then drifts. Cues starting before the boundary are left exactly as authored; a cue whose start is exactly the boundary is shifted. Time as ss, mm:ss.mmm, or hh:mm:ss.mmm.
Give two moments you can eyeball in the video. Each: where the line is now → where it should be. Times as ss, mm:ss.mmm, or hh:mm:ss.mmm.
Fixes linear drift from a frame-rate mismatch (e.g. a 23.976 fps track played at 25 fps runs progressively fast). Every timestamp is scaled by from ÷ to. Same source & target is an exact no-op.
Format is auto-detected from the file’s header/extension. Force LRC here if you dropped a start-only lyrics file without a .lrc extension — LRC carries no end times, so each cue’s end is synthesized as the next line’s start (capped at 8 s; the last line gets exactly 8 s).
MicroDVD (.sub) stores each cue as frame numbers, not clock time, so the fps it was authored at is needed to place cues on the timeline — it is never guessed. Common rates: 23.976, 24, 25, 29.97, 30. A wrong value stretches or squeezes every timestamp proportionally.
SRT→VTT→SRT is byte-for-byte lossless, and inline tags like <i> survive both ways — but VTT cue settings do not: a cue written 00:00:01.000 --> 00:00:03.000 align:start position:10% comes back without align or position, because SRT has nowhere to put them. SBV, TTML/DFXP and LRC are all both read and written. ASS/SSA and MicroDVD are read-only inputs: they convert to SRT/VTT/SBV/TTML/LRC, but their styling (ASS positions/colours/fonts) can't survive a plain-text caption and is honestly dropped — words, timing and line breaks are kept. Writing TTML emits a minimal, valid <tt> document (clock timing, one <p> per cue, <br/> line breaks); it carries no regions/colours because a plain cue has none. Writing LRC is lossy: LRC lyric files hold only a start stamp per line, so every cue’s end time is dropped — you’ll see an honest warning above the result when LRC is the target. MicroDVD isn't offered as an output because there is no writer for it here.
Pulls each cue’s end earlier so at least this many milliseconds of blank screen precede the next cue — killing the flicker when one caption hides and the next appears on the same frame. ~2 frames (80–125 ms) is a common broadcast floor. Cue starts are never moved, and no cue is shrunk below a 1-second on-screen minimum. Read that floor literally: a cue that is already under a second gets pushed out to one second, which on a tight track can leave it ending after the next cue starts — that pair is reported with its real, negative gap rather than silently accepted. Running Fix all instead clamps such an overlap straight back out, so use Fix all when you want gaps opened and never an overlap introduced.
Holds every cue on-screen for a readable window. A cue shorter than the minimum has its end pushed later (its start never moves); a cue longer than the maximum has its end pulled in. 1000–7000 ms is a common range (min ≥ ~1 s to be legible, max so a caption doesn’t linger). A short cue is never extended onto the next cue’s start: any cue that can’t reach the minimum without colliding is stretched only as far as legal and listed honestly in the result rather than forced.
Re-wraps every cue’s text at word boundaries so each line fits the character limit, and when two lines are needed it splits them to keep the lines even. Existing breaks are normalized, then rebuilt from scratch. Timings are untouched and no text is ever dropped: a single word longer than the limit is kept whole on its own line, and text that genuinely needs more than the max lines is packed onto the last line — both cases are reported in the result.
One-click SDH / hearing-impaired cleanup that edits inside each cue — a line mixing a tag with real dialogue (“[door slams] Who’s there?”) keeps the dialogue; only the annotation is stripped. Speaker-label removal is deliberately conservative: it only touches an ALL-CAPS word (or words) followed by a colon at the very start of a line, so a mixed-case name like Dr. Smith: is never eaten. Timings are never touched; a cue left completely empty (e.g. a bare ♪ line) is dropped and the rest renumbered.
Timings are never touched — only cue text. SDH removal is opt-in and conservative: a cue that mixes dialogue with a bracketed tag keeps all its words; only cues that are purely a sound description or a bare all-caps label are dropped. Cues left empty after cleaning are removed and the rest renumbered.
Divides any cue whose text is longer than the character limit or whose duration exceeds the maximum into two or more shorter cues, splitting the text at word boundaries and sharing the original time span in proportion (a 100 ms gap is opened between the parts). This is different from “Split into two files” — that cuts the whole track at a clock time; this reshapes over-long cues. A cue that can’t be split cleanly (a single unbreakable word, or no split that keeps the gap without a zero-duration part) is left exactly as authored and listed below — never mangled or dropped.
Rewrites SRT cue numbers to 1…N in document order, fixing gaps or out-of-order indexes. For VTT this is an honest no-op — WebVTT identifiers are optional and left untouched.
Produces two files. A cue is kept whole in the part its start falls in — a cue is never cut mid-line; a straddling cue stays in part 1 with its full duration. A cue starting exactly at the split point goes to part 2. Time as ss, mm:ss.mmm, or hh:mm:ss.mmm.
Strips every timecode, index and formatting tag, leaving just the spoken words as plain text you can paste into notes or a search index. Deduping removes the echo rolling/auto-generated captions leave behind; a line that genuinely recurs later is kept.
Flags cues that show too briefly for their text. 17 CPS is a common Latin-script ceiling; use a lower value (~9) for CJK, which reads slower per character. Line breaks are not counted; spaces and punctuation are. This is a read-only report — nothing to download.
One read-only pass that lints every cue for the timing and readability defects professional QC flags: reading speed over the CPS ceiling, a gap below the minimum, a true overlap with the next cue, a duration under the minimum or over the maximum, a line past the character limit, and a cue needing more than the max lines. Each finding is shown per cue with its measured value vs the threshold below — nothing is changed and there is nothing to download. Defaults are broadcast-typical; tune any threshold above.
Result
Your subtitles never leave the device. Every parse, retime and convert runs in this page; the only third-party request is a cookieless pageview count from stats.dankdev.com, which records the URL and never your file. Timing math comes from the golden-tested core.js loaded beside this page.
What this tool does — and what it refuses to do
What it is for. A caption file that runs a second and a half late, was
written for the wrong frame rate, is cluttered with [DOOR SLAMS], or
simply will not open in your player. Drop the file in, pick one of the 19 transforms
— grouped as one-click cleanup, retime, transform text/format, and extract &
analyse — then read the result and copy or download it. It reads SubRip (.srt),
WebVTT (.vtt), ASS/SSA, MicroDVD (.sub), YouTube SBV, TTML/DFXP and LRC lyrics, and
writes SRT, VTT, SBV, TTML and LRC. Nothing is uploaded: the file is read by the
browser on your own machine, and every transform runs inside this page.
A worked example: the track that slides
A fixed offset cures captions that are late by the same amount from beginning to
end. It does nothing for a track that starts in sync and slips further behind as the
film runs — the signature of a file authored at one frame rate and played back at
another. That is what Two-anchor linear resync is for. Find one line you can
see in the picture near the start and one near the end, and type where each one is
now and where it should be. Leave anchor A at 00:00:04.000 → 00:00:04.000 and move
anchor B from 01:00:00.000 to 01:00:03.600, and the tool solves
scale = (newB − newA) / (oldB − oldA), here 1.0010011, and
offset = newA − scale × oldA, here −4.0044 ms, then applies
t' = round(scale × t + offset) to every cue start and end. A cue sitting
at 00:30:00.000 comes back at 00:30:01.798 — 1.8 s of the eventual 3.6 s error,
because it is halfway through the file. If you already know the two frame rates,
Frame-rate resync takes them directly and scales every timestamp by
from ÷ to instead.
Honest limits
ASS/SSA and MicroDVD are read-only here: they convert out to SRT, VTT, SBV, TTML
or LRC, but nothing writes them back, and ASS positions, colours and fonts cannot
survive a plain-text caption, so they are dropped while the words, timing and line
breaks are kept. SRT → VTT → SRT is byte-for-byte lossless and inline tags survive
both ways, but VTT cue settings do not: a cue written with
align:start position:10% comes back without them, because SRT has
nowhere to put them. Writing LRC drops every cue's end time, since an LRC line
carries only a start stamp — you get an honest warning above the result when LRC is
the target. Reading LRC has the mirror problem: each line's end is synthesised as the
next line's start, capped at 8 seconds, and the last line gets exactly 8 seconds, so
a lyric sheet with a 19-second instrumental break yields an 8-second cue and 11
seconds of blank screen rather than a guess. MicroDVD stores frame numbers rather
than clock time, so its frame rate is asked for and never inferred; a wrong value
stretches or squeezes every timestamp proportionally.
What it refuses to do
It will not invent or discard your words to make a threshold come out clean. A cue
too short to reach the minimum display duration without colliding with the next one
is stretched only as far as is legal and then listed as a residual, never forced into
an overlap and never dropped. Rebalancing keeps a word longer than the line limit
whole on its own line instead of cutting it, and flags that cue. Splitting into two
files cuts between cues and never mid-cue: a cue straddling the split point stays
whole in part 1. Stripping speaker labels only touches ALL-CAPS words followed by a
colon at the very start of a line, so Dr. Smith: is left alone, and a
line mixing a sound tag with real dialogue keeps the dialogue. The reading-speed
(CPS) and quality-control passes change nothing at all — they are reports, showing
each finding per cue as a measured value against the threshold you set, with nothing
to download.