Parsing Video Captions: Building a Secure Client-Side ASS to SRT Converter
DEV Community

Parsing Video Captions: Building a Secure Client-Side ASS to SRT Converter

Format Specifications: ASS vs. SRT

Before writing the parser, we must analyze the structural patterns of both formats.

1. ASS (Advanced SubStation Alpha)

ASS is a multi-section, text-based format containing comprehensive styling dictionaries and scripting headers. The actual dialogue events are stored under the [Events] block:

[Events]
Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:01:12.45,0:01:15.90,Default,,0,0,0,,Welcome to our latest {\b1}video production tutorial{\b0}!

2. SRT (SubRip)

SRT is a basic flat-file structure containing sequential numbering, start/end timestamps separated by the --> token, and unstyled text blocks:

1
00:01:12,450 --> 00:01:15,900
Welcome to our latest video production tutorial!

Under the Hood: The Interpolation and Sanitation Pipeline

To translate ASS into SRT, our client-side engine performs two main operations:

1. Timestamp Conversion

ASS tracks timing in centiseconds (1/100 of a second), whereas SRT relies on milliseconds (1/1000 of a second). The converter implements this conversion:

Milliseconds (ms) = Centiseconds (CS) ร— 10

Hours, minutes, and seconds are padded with leading zeros to maintain the standard double-digit layout required by the SRT specification.

2. Tag Sanitation

We use regular expressions to strip out formatting overrides (like {\b1}, {\pos(100,200)}, or font details) to ensure universal hardware compatibility:

  • Styling Tag Removal: Removes everything within curly brackets: text.replace(/\{[^}]*\}/g, '')
  • Line Break Normalization: Translates alignment breaks into standard Unix line breaks: text.replace(/\\N|\\n/gi, '\n')

JavaScript Parser Implementation

Below is the clean, self-contained JavaScript utility that splits, validates, and decodes the raw inputs:

/**
 * Converts a raw ASS subtitle string into clean SRT format.
 * @param {string} assInput - The raw ASS text file content
 * @returns {string} The converted SRT output
 */
function convertAssToSrt(assInput) {
  if (!assInput) return '';
  const lines = assInput.split('\n');
  let srtContent = [];
  let subtitleNumber = 1;
  let inEventsSection = false;
  for (const line of lines) {
    const trimmedLine = line.trim();
    if (trimmedLine === '[Events]') {
      inEventsSection = true;
      continue;
    }
    if (trimmedLine.startsWith('[') && trimmedLine !== '[Events]') {
      inEventsSection = false;
    }
    if (inEventsSection && trimmedLine.startsWith('Dialogue:')) {
      const parts = trimmedLine.split(',');
      if (parts.length < 10) continue;
      const assStartTime = parts[1].trim();
      const assEndTime = parts[2].trim();
      // Recombine the remaining elements in case dialogue contains commas
      const assText = parts.slice(9).join(',');
      const srtStartTime = formatAssTimeToSrt(assStartTime);
      const srtEndTime = formatAssTimeToSrt(assEndTime);
      const cleanText = assText
        .replace(/\{[^}]*\}/g, '')
        .replace(/\\N|\\n/gi, '\n')
        .trim();
      if (cleanText) {
        srtContent.push(subtitleNumber.toString());
        srtContent.push(`${srtStartTime} --> ${srtEndTime}`);
        srtContent.push(cleanText);
        srtContent.push(''); // Empty line separator
        subtitleNumber++;
      }
    }
  }
  if (srtContent.length === 0) {
    throw new Error("No dialogue events discovered. Ensure your subtitle contains an [Events] section.");
  }
  return srtContent.join('\n').trim();
}

/**
 * Translates ASS time format (H:MM:SS.CS) to SRT format (HH:MM:SS,ms)
 */
function formatAssTimeToSrt(assTime) {
  const parts = assTime.split(':');
  let h = parseInt(parts[0], 10);
  let m = parseInt(parts[1], 10);
  let s_cs = parseFloat(parts[2]);
  let s = Math.floor(s_cs);
  let cs = Math.round((s_cs - s) * 100);
  let ms = cs * 10;
  h = h.toString().padStart(2, '0');
  m = m.toString().padStart(2, '0');
  s = s.toString().padStart(2, '0');
  ms = ms.toString().padStart(3, '0');
  return `${h}:${m}:${s},${ms}`;
}

Client-Side Security and Privacy

Many free online converters process your subtitle files on remote servers. When translating unreleased films, private lectures, or company product videos, this network exposure introduces unnecessary data risks. By executing the regex cleaning and timing interpolation entirely on the client side:

  • No Database Logging: Your raw text strings remain strictly within your local browser memory and are flushed instantly when the page is reset.
  • Universal Offline Support: Once the tool page is loaded, the converter operates without any internet connectivity.

Try the Live Utility

I have integrated this client-side transcoder directly into my online toolbox. If you need a private, rapid way to flatten your caption files and remove styling tags for universal hardware compatibility, feel free to use it:

๐Ÿ‘‰ Live Link: Online ASS to SRT Subtitle Converter - Vo Viet Hoang

Let's Connect!

How do you handle subtitle standardization and localized video distributions in your development pipeline? Do you write command-line scripts or use browser utilities? Let me know in the comments section below!

Happy coding! ๐Ÿš€

Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.