Test files › Subtitles and captions › valid.vtt
Sample WebVTT file (valid)
A valid WebVTT file with the required signature, a NOTE comment, a named cue and cue settings.
↓ Download valid.vtt| Filename | valid.vtt |
| Format | WebVTT |
| Size | 399 bytes (399 bytes) |
| MIME type | text/vtt; charset=utf-8 |
| SHA-256 | 301323cac0aa86219c7f17917e84c18b5e0c8792d431d855798e50c5326be685 |
What this file catchesBaseline WebVTT parsing in HTML5 <track>. Uses a FULL STOP before the milliseconds (00:00:01.000), the opposite of SRT, and carries cue settings that a naive line splitter will treat as text.
Handle it in code
Each snippet below was run against this exact file with Python 3.14.4 and Node 24.15.0 when this page was built, and the output shown is what it printed.
Python
SRT puts a comma before the milliseconds and WebVTT a full stop, and WebVTT files must start with WEBVTT. Players differ on the rest: overlapping cues are legal in WebVTT, but many SRT players show only one of them.
import re
TIME = r"(?:(\d+):)?(\d{2}):(\d{2})([.,])(\d{3})"
CUE = re.compile(TIME + r"\s+-->\s+" + TIME)
def ms(h, m, s, sep, frac):
return ((int(h or 0) * 60 + int(m)) * 60 + int(s)) * 1000 + int(frac)
name = "valid.vtt"
vtt = name.lower().endswith(".vtt")
with open(name, encoding="utf-8-sig") as f:
text = f.read()
problems, cues = [], []
if vtt and not text.startswith("WEBVTT"):
problems.append("line 1: no WEBVTT signature")
for n, line in enumerate(text.splitlines(), 1):
m = CUE.search(line)
if not m:
continue
g = m.groups()
right = "." if vtt else ","
if g[3] != right or g[8] != right:
problems.append(f"line {n}: {'WebVTT' if vtt else 'SRT'} needs {right!r} before the milliseconds")
start, end = ms(*g[:5]), ms(*g[5:])
if end < start:
problems.append(f"line {n}: the cue ends before it starts")
elif end == start:
problems.append(f"line {n}: zero length, so it never shows")
if cues:
prev_start, prev_end = cues[-1]
if start < prev_start:
problems.append(f"line {n}: starts before the previous cue (out of order)")
overlap = min(end, prev_end) - max(start, prev_start)
if overlap > 0:
problems.append(f"line {n}: overlaps the previous cue by {overlap} ms")
cues.append((start, end))
print(f"{len(cues)} cues:", "valid" if not problems else f"{len(problems)} problem{'s' * (len(problems) != 1)}")
for p in problems:
print(" " + p)Run against this file, it prints: 4 cues: valid
Related guides and tools
Download & verify
curl -fsSL -o "valid.vtt" "https://files.hexaqa.com/subtitle/valid.vtt"
curl -fsSL "https://files.hexaqa.com/subtitle/valid.vtt" | sha256sum
# expect: 301323cac0aa86219c7f17917e84c18b5e0c8792d431d855798e50c5326be685