DEV Community

hammi
hammi

Posted on

I wrote an SRT-to-WebVTT converter in ~35 lines because players lie about subtitle support

i'm building RKMovies, a 0.91 MB Android media client. the streams come with subtitle files, and the internet almost always hands you SRT. the problem: the <video> element only renders WebVTT properly, and SRT is not WebVTT.

the differences that actually bite:

  • timestamps. SRT uses 00:00:01,000. WebVTT demands 00:00:01.000. one comma and the whole cue silently disappears.
  • cue indexes. SRT blocks start with a numeric counter line. WebVTT treats that as a cue id, which is fine, but sloppy files skip or repeat numbers.
  • markup. SRT files carry HTML-ish tags (<i>, <font color=...>) and ASS override tags ({\an8}, {\pos(...)}). browsers render some of it, choke on the rest, and the override tags show up as literal text.
  • BOM and \r\n. half the files start with a UTF-8 BOM and use Windows line endings.

so captionVtt() does four things: strip the BOM and \r, split into blocks on blank lines, drop the counter line, convert commas in the timing line to dots, and clean the text lines (strip tags, decode entities). about 35 lines total:

t = t.replace(/\r/g, "").replace(/^/, "");
const out = [];
for (const b of t.split(/\n{2,}/)) {
  const lines = b.split("\n").filter(l => l.trim() !== "");
  if (!lines.length) continue;
  if (/^\d+$/.test(lines[0].trim())) lines.shift();   // cue counter
  const tl = lines.shift();
  if (!tl || tl.indexOf("-->") < 0) continue;           // timing line
  const text = lines.map(clean).filter(l => l.length);  // strip tags/entities
  if (!text.length) continue;
  out.push(tl.replace(/,/g, ".") + "\n" + text.join("\n"));
}
return "WEBVTT\n\n" + out.join("\n\n") + "\n";
Enter fullscreen mode Exit fullscreen mode

the annoying part was not the parsing. it's that every file is broken in its own way. some have no index lines, some have NOTE blocks, some put cue settings after the timestamp (00:00:01.000 --> 00:00:04.000 position:10%). you end up writing a tolerant parser, not a correct one.

if you're embedding subs, always emit WebVTT and validate with a real <track> element in the browser. devtools will happily show you a comma and not warn you.

i'm the developer of RKMovies. the Android APK is 0.91 MB with hardware decode, multi-audio tracks and zero tracking, and there's a Windows build too.

Top comments (0)