From 8a8005bd7f2375ff0ee99980de5f1ffbfab1cce6 Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Sat, 9 May 2026 23:53:19 +0100 Subject: [PATCH] add: parser srt files --- src/utils/subtitles/parseSrt.ts | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/utils/subtitles/parseSrt.ts diff --git a/src/utils/subtitles/parseSrt.ts b/src/utils/subtitles/parseSrt.ts new file mode 100644 index 0000000..ec58014 --- /dev/null +++ b/src/utils/subtitles/parseSrt.ts @@ -0,0 +1,62 @@ +export interface SubtitleCue { + index: number; + startTime: number; + endTime: number; + text: string; +} + +const SRT_TIME_SEPARATOR = " --> "; +const SRT_TIME_PATTERN = /^(\d{2}):(\d{2}):(\d{2}),(\d{3})$/; + +export function parseSrt(srtContent: string): SubtitleCue[] { + return srtContent + .replace(/^\uFEFF/, "") + .replace(/\r/g, "") + .trim() + .split(/\n{2,}/) + .map(parseSrtBlock) + .filter((cue): cue is SubtitleCue => cue !== null); +} + +function parseSrtBlock(block: string): SubtitleCue | null { + const lines = block + .split("\n") + .map((line) => line.trim()) + .filter(Boolean); + + if (lines.length < 3) return null; + + const index = Number(lines[0]); + if (!Number.isInteger(index)) return null; + + const [start, end] = lines[1]?.split(SRT_TIME_SEPARATOR) ?? []; + if (!start || !end) return null; + + const startTime = parseSrtTime(start); + const endTime = parseSrtTime(end); + if (startTime === null || endTime === null || endTime <= startTime) { + return null; + } + + return { + index, + startTime, + endTime, + text: lines.slice(2).join("\n"), + }; +} + +function parseSrtTime(value: string): number | null { + const match = value.match(SRT_TIME_PATTERN); + if (!match) return null; + + const [, hours, minutes, seconds, milliseconds] = match; + if (!hours || !minutes || !seconds || !milliseconds) return null; + + return ( + Number(hours) * 3600 + + Number(minutes) * 60 + + Number(seconds) + + Number(milliseconds) / 1000 + ); +}