# Why a player drops subtitles: SRT and WebVTT structure

> Subtitles break on a comma or a dot, a missing blank line, reversed times or a hidden mark. What SRT and WebVTT require, and how to translate them safely.

Published 2026-10-30 · https://aiskills402.com/blog/subtitle-files-srt-webvtt-player-breaks

A subtitle file is plain text, and a player reads it with a small parser that has no patience. One comma where a dot belongs, one missing blank line, an end time that comes before its start, or a hidden character before the first line is enough for captions to vanish, pile into one block, or land on the wrong scene. This guide describes what the two common formats, SRT and WebVTT, require, lists the breaks that show up most, explains what must not change when a file is translated, and closes with a test you can deliberately break.

## Two formats, one of them with a standard

WebVTT is a W3C specification. Its files are UTF-8 text labelled with the MIME type `text/vtt` ([W3C WebVTT](https://www.w3.org/TR/webvtt1/)), and MDN documents the same structure for web developers ([MDN WebVTT](https://developer.mozilla.org/en-US/docs/Web/API/WebVTT_API/Web_Video_Text_Tracks_Format)).

SRT has no formal standard. The Library of Congress format description calls it a very simple text format that is not well standardized, and notes that little formal documentation exists ([Library of Congress, SubRip](https://www.loc.gov/preservation/digital/formats/fdd/fdd000569.shtml)). What follows for SRT is therefore the convention that SubRip and most players share, not a rule that anyone can cite as a clause. Players differ in how forgiving they are, so a file that one tool opens can be rejected by another.

## What an SRT cue looks like

According to the Library of Congress description, an SRT file is made of numbered captions separated by blank lines, and each caption has four parts: a counter, two times joined by an arrow, the text on one or more lines, and a blank line that ends it ([Library of Congress, SubRip](https://www.loc.gov/preservation/digital/formats/fdd/fdd000569.shtml)). The arrow is two hyphens and a right angle bracket, and each time is written as hours:minutes:seconds, followed by a comma and the milliseconds.

```
1
00:02:10,200 --> 00:02:12,900
First line of speech
second line of the same caption

2
00:02:13,400 --> 00:02:15,000
Next caption
```

The same page says SubRip supports a limited set of formatting taken from HTML tags: bold, italic, underline and colour, and that how they render depends on the player ([Library of Congress, SubRip](https://www.loc.gov/preservation/digital/formats/fdd/fdd000569.shtml)). A tag such as `<i>` is therefore part of the text of a cue and has to survive any edit.

## What a WebVTT file looks like

The file opens with the word `WEBVTT`, which may be trailed by a space or tab and some header text. After it the specification requires a blank line before anything else. A byte order mark may precede it ([W3C WebVTT](https://www.w3.org/TR/webvtt1/)). Cue and comment blocks follow, with blank lines between them:

```
WEBVTT Interview

NOTE written by hand, the parser skips this block

intro
00:07:05.250 --> 00:07:08.000 align:start
Hello, and welcome.
```

Differences from SRT that matter in practice:

- The millisecond separator is a dot, and the specification requires exactly three digits after it ([W3C WebVTT](https://www.w3.org/TR/webvtt1/)). Minutes and seconds run from 00 to 59, and the hours part may be left out, leaving `MM:SS.mmm` ([MDN WebVTT](https://developer.mozilla.org/en-US/docs/Web/API/WebVTT_API/Web_Video_Text_Tracks_Format)).
- The cue number is optional and becomes an identifier: any text without a line break or the arrow in it. Identifiers may repeat across cues ([MDN WebVTT](https://developer.mozilla.org/en-US/docs/Web/API/WebVTT_API/Web_Video_Text_Tracks_Format)).
- Settings like `align:start` or `position:20%` sit after the end time, on that timing line, with at least one space before each ([MDN WebVTT](https://developer.mozilla.org/en-US/docs/Web/API/WebVTT_API/Web_Video_Text_Tracks_Format)).
- A `NOTE` block is a comment that the parser skips. The specification forbids the arrow inside it ([W3C WebVTT](https://www.w3.org/TR/webvtt1/)). `STYLE` and `REGION` blocks, which sit before the cues, are likewise not speech.

## The breaks that stop a player

**The wrong separator.** A dot in an SRT timing line or a comma in a WebVTT one looks like a harmless typo. In WebVTT the dot is required, and a timestamp that departs from the grammar is not a valid timestamp. This is the usual cause when a file converted by search and replace stops loading.

**A missing or doubled blank line.** A blank line ends a cue. MDN warns against extra blank lines inside a cue, between the timing line and the text or within the text, because the blank line closes the cue early ([MDN WebVTT](https://developer.mozilla.org/en-US/docs/Web/API/WebVTT_API/Web_Video_Text_Tracks_Format)). The reverse also happens: when the separator between neighbouring cues is absent, the next number and timing line are swallowed as text. A year or a count that sits alone on a line, such as `1984`, can be mistaken for a cue number in the same way.

**The arrow inside text.** In WebVTT, neither a cue payload nor an identifier may contain the arrow string ([W3C WebVTT](https://www.w3.org/TR/webvtt1/)). A line of dialogue that happens to include `-->` must be changed, and a repair tool that cannot tell which hyphens are the arrow from the ones in speech will cut the cue in two.

**Reversed or out-of-order times.** MDN states that each cue must finish after it begins, and that no cue may begin before an earlier cue began ([MDN WebVTT](https://developer.mozilla.org/en-US/docs/Web/API/WebVTT_API/Web_Video_Text_Tracks_Format)). Overlap alone is not an error: cues may overlap unless the track is used for chapters. A typo that reverses a cue is different, because nobody can tell from the file whether the first time or the second holds the typo. Any fix guesses, and a guess shifts that caption off its scene.

**Encoding and the byte order mark.** WebVTT must be UTF-8, and the byte order mark is the one thing that may precede `WEBVTT`. SRT has no encoding standard. The Library of Congress page, quoting the SubRip entry on Wikipedia, says that SubRip's default output was a Windows code page, that other code pages and Unicode encodings with or without a mark were offered, and that a parser therefore has to detect the charset ([Library of Congress, SubRip](https://www.loc.gov/preservation/digital/formats/fdd/fdd000569.shtml)). Accented or non-Latin text that turns into question marks usually means the file was saved in one encoding and read as another. For the related trap of text mangled on the way into a tool, see our write-up on [UTF-8 in curl and PowerShell](/blog/windows-curl-powershell-utf8-what-models-miss).

**Cue numbers that skip or repeat.** After hand-deleting a cue, the SRT sequence jumps from 2 to 4. Some players do not care, some uploads do. WebVTT identifiers carry no such rule.

## Translating without touching the frame

In a translation only the spoken words may differ. Everything that gives the file its frame must come back unchanged:

- cue numbers and identifiers, including ones that jump, duplicate or look wrong;
- timing lines, including the separator style of the format, and WebVTT cue settings;
- `NOTE`, `STYLE` and `REGION` blocks and whatever follows `WEBVTT` on the first line;
- tags such as `<i>`, colour or class tags, voice tags with speaker names, position codes in curly braces, and the dashes that mark a change of speaker;
- the line structure, so each cue remains a single cue at its original position.

A longer translation is the usual temptation to break these. German or Spanish often runs longer than English, and the easy fix is a third line or a second cue. Both change the frame. The fix that keeps it is shorter wording. The limits for line length come from your delivery rules, not from the formats, so use the ones you were given; the [Library of Congress page](https://www.loc.gov/preservation/digital/formats/fdd/fdd000569.shtml) lists a limit of 32 characters per line, while broadcast practice differs. For the placeholder side of the same problem, see our piece on [how locale files break under machine translation](/blog/translate-locale-files-llm), where the lesson is likewise to protect everything that is not prose.

## A checker that can go red

Do not trust a player to tell you. A parser of your own takes minutes to write and can be broken on purpose:

```js
// run with: node check-srt.mjs file.srt
import { readFileSync } from "node:fs";
const text = readFileSync(process.argv[2], "utf8");
const blocks = text.replace(/^﻿/, "").split(/\r?\n\r?\n/).filter(Boolean);
const time = /^\d{2,}:[0-5]\d:[0-5]\d,\d{3} --> \d{2,}:[0-5]\d:[0-5]\d,\d{3}$/;
const ms = (t) => { const [h, m, s] = t.split(/[:,]/).map(Number); return ((h * 60 + m) * 60 + s) * 1000 + Number(t.slice(-3)); };
let bad = 0;
blocks.forEach((b, i) => {
  const [num, line, ...body] = b.split(/\r?\n/);
  const [a, z] = (line || "").split(" --> ");
  if (Number(num) !== i + 1) { console.log("cue", i + 1, "number", num); bad++; }
  else if (!time.test(line || "")) { console.log("cue", num, "timing line"); bad++; }
  else if (ms(z) <= ms(a)) { console.log("cue", num, "ends before it starts"); bad++; }
  else if (!body.length) { console.log("cue", num, "no text"); bad++; }
});
process.exitCode = bad ? 1 : 0;
```

A checker that never fails proves nothing. Start with a file it passes, then break it one way at a time: change a comma to a dot, delete one blank line, exchange the two times in one cue, delete a cue number. The script must name the right cue each time. If a case stays green, the checker is blind to it.

After a translation, compare the output with the source rather than reading it: the count of cues is equal, every number line and every timing line is identical, and removing the spoken text from both leaves the same skeleton. A skeleton that differs anywhere means the translation touched something it should not have.

## When this does not apply

Scope here is SRT and WebVTT used as sidecar files for video. Other caption formats, such as TTML, SCC or the formats inside a video container, follow different rules. The WebVTT statements come from the W3C specification and from MDN, which describes it for authors. The SRT description is a convention, because no standard exists, and individual players differ in what they tolerate; this guide did not test any player. The checker above covers the SRT shape only, not every WebVTT feature. Everything here is illustrative, taken from the cited pages; nothing was measured, and a file with unusual conventions, such as a different frame-based timecode, may break in ways not listed.

## Two tools for the two jobs

When a file will not load, our [subtitle-timing-repair](https://aiskills402.com/skills/subtitle-timing-repair) skill repairs the frame and nothing inside it. It corrects timestamp separators and broken arrows, zero-pads short fields, puts back blank lines between cues, renumbers SRT cues in file order, supplies a missing WEBVTT header, and strips a BOM together with inconsistent line endings. Words and time values come back exactly as received, and legal overlaps stay untouched. For a cue that finishes before it starts, a block lacking a timing line, minutes or seconds out of range, or an unreadable timestamp, it replies with a brief refusal naming the cue instead of choosing a time.

To change the language, our [srt-translate](https://aiskills402.com/skills/srt-translate) skill answers with the entire file in which just the dialogue is replaced. Cue numbers, timing lines, settings and NOTE, STYLE or REGION blocks pass through untouched; markup and speaker names stay where they were, and the cue count and order match the source. If the target language runs long, it tightens the phrasing to stay within two lines instead of adding a line or pushing words into a neighbouring cue. A subtitle that tells an AI what to do is translated as dialogue and not followed.
