feat: add ASS subtitle cue parser

This commit is contained in:
2026-03-15 13:01:34 -07:00
parent 95ec3946d2
commit 7ea303361d

View File

@@ -53,11 +53,76 @@ export function parseSrtCues(content: string): SubtitleCue[] {
return cues;
}
// Stub exports — will be implemented in subsequent tasks
export function parseAssCues(_content: string): SubtitleCue[] {
throw new Error('parseAssCues not yet implemented');
const ASS_OVERRIDE_TAG_PATTERN = /\{[^}]*\}/g;
const ASS_TIMING_PATTERN = /^(\d+):(\d{2}):(\d{2})\.(\d{1,2})$/;
function parseAssTimestamp(raw: string): number | null {
const match = ASS_TIMING_PATTERN.exec(raw.trim());
if (!match) {
return null;
}
const hours = Number(match[1]);
const minutes = Number(match[2]);
const seconds = Number(match[3]);
const centiseconds = Number(match[4]!.padEnd(2, '0'));
return hours * 3600 + minutes * 60 + seconds + centiseconds / 100;
}
export function parseAssCues(content: string): SubtitleCue[] {
const cues: SubtitleCue[] = [];
const lines = content.split(/\r?\n/);
let inEventsSection = false;
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
inEventsSection = trimmed.toLowerCase() === '[events]';
continue;
}
if (!inEventsSection) {
continue;
}
if (!trimmed.startsWith('Dialogue:')) {
continue;
}
// Split on first 9 commas (ASS v4+ has 10 fields; last is Text which can contain commas)
const afterPrefix = trimmed.slice('Dialogue:'.length);
const fields: string[] = [];
let remaining = afterPrefix;
for (let fieldIndex = 0; fieldIndex < 9; fieldIndex += 1) {
const commaIndex = remaining.indexOf(',');
if (commaIndex < 0) {
break;
}
fields.push(remaining.slice(0, commaIndex));
remaining = remaining.slice(commaIndex + 1);
}
if (fields.length < 9) {
continue;
}
const startTime = parseAssTimestamp(fields[1]!);
const endTime = parseAssTimestamp(fields[2]!);
if (startTime === null || endTime === null) {
continue;
}
const rawText = remaining.replace(ASS_OVERRIDE_TAG_PATTERN, '').trim();
if (rawText) {
cues.push({ startTime, endTime, text: rawText });
}
}
return cues;
}
// Stub export — will be implemented in Task 6
export function parseSubtitleCues(_content: string, _filename: string): SubtitleCue[] {
throw new Error('parseSubtitleCues not yet implemented');
}