All files / src/lib subtitle-parser.ts

100% Statements 33/33
82.35% Branches 14/17
100% Functions 3/3
100% Lines 33/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51      1x 16x 16x 16x 16x 16x 16x     1x 8x 8x     1x 8x   8x   8x 10x 10x     9x 9x   8x 8x   8x 8x 10x     7x 7x 7x 7x 7x   7x 7x 7x   8x 8x  
import type { SubtitleCue } from "./types";
 
/** Parse "HH:MM:SS,mmm" or "HH:MM:SS.mmm" into seconds. */
const parseTimestamp = (ts: string): number => {
	const clean = ts.trim().replace(",", ".");
	const parts = clean.split(":");
	if (parts.length !== 3) return 0;
	const [h, m, s] = parts.map(Number);
	return h * 3600 + m * 60 + s;
};
 
/** Strip SRT/HTML tags (<i>, <b>, <font ...>, etc.) from subtitle text. */
const stripTags = (text: string): string => {
	return text.replace(/<[^>]+>/g, "").trim();
};
 
/** Parse SRT file content into an array of SubtitleCue objects. */
export const parseSrt = (content: string): SubtitleCue[] => {
	const cues: SubtitleCue[] = [];
	// Normalise line endings and split into blocks
	const blocks = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim().split(/\n\n+/);
 
	for (const block of blocks) {
		const lines = block.trim().split("\n");
		if (lines.length < 2) continue;
 
		// Find the timestamp line (contains "-->")
		const tsIdx = lines.findIndex((l) => l.includes("-->"));
		if (tsIdx === -1) continue;
 
		const tsParts = lines[tsIdx].split("-->");
		if (tsParts.length !== 2) continue;
 
		const start = parseTimestamp(tsParts[0]);
		const end = parseTimestamp(tsParts[1]);
		if (isNaN(start) || isNaN(end) || end <= start) continue;
 
		// Everything after the timestamp line is the subtitle text
		const text = lines
			.slice(tsIdx + 1)
			.map(stripTags)
			.filter(Boolean)
			.join("\n");
 
		if (!text) continue;
		cues.push({ start, end, text });
	}
 
	return cues;
};