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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
import type { ParseSubtitleResult, SubtitleCue } from "./types";
const TIMESTAMP_SEPARATOR = /\s*-->\s*/;
const TIMESTAMP_PATTERN =
/^(\d{2}:\d{2}:\d{2}[,.]\d{1,3})\s*-->\s*(\d{2}:\d{2}:\d{2}[,.]\d{1,3})/;
export function parseSrt({ input }: { input: string }): ParseSubtitleResult {
const normalized = input.replace(/\r\n?/g, "\n").trim();
if (!normalized) {
return {
captions: [],
skippedCueCount: 0,
warnings: [],
};
}
const blocks = normalized.split(/\n{2,}/);
const cues: SubtitleCue[] = [];
let skippedCueCount = 0;
for (const block of blocks) {
const lines = block
.split("\n")
.map((line) => line.trim())
.filter((line) => line.length > 0);
if (lines.length < 2) {
skippedCueCount += 1;
continue;
}
const timestampIndex = TIMESTAMP_SEPARATOR.test(lines[0]) ? 0 : 1;
const timestampLine = lines[timestampIndex];
if (!timestampLine || !TIMESTAMP_PATTERN.test(timestampLine)) {
skippedCueCount += 1;
continue;
}
const textLines = lines.slice(timestampIndex + 1);
const text = textLines.join("\n").trim();
if (!text) {
skippedCueCount += 1;
continue;
}
const [rawStart, rawEnd] = timestampLine.split(TIMESTAMP_SEPARATOR);
if (!rawStart || !rawEnd) {
skippedCueCount += 1;
continue;
}
const startTime = parseSrtTimestamp({ input: rawStart });
const endTime = parseSrtTimestamp({ input: rawEnd });
const duration = endTime - startTime;
if (
!Number.isFinite(startTime) ||
!Number.isFinite(endTime) ||
duration <= 0
) {
skippedCueCount += 1;
continue;
}
cues.push({
text,
startTime,
duration,
});
}
return {
captions: cues,
skippedCueCount,
warnings: [],
};
}
function parseSrtTimestamp({ input }: { input: string }): number {
const normalized = input.trim().replace(",", ".");
const match = normalized.match(/^(\d{2}):(\d{2}):(\d{2})\.(\d{1,3})$/);
if (!match) {
return Number.NaN;
}
const [, hours, minutes, seconds, milliseconds] = match;
const parsedHours = Number.parseInt(hours, 10);
const parsedMinutes = Number.parseInt(minutes, 10);
const parsedSeconds = Number.parseInt(seconds, 10);
const parsedMilliseconds = Number.parseInt(milliseconds.padEnd(3, "0"), 10);
return (
parsedHours * 3600 +
parsedMinutes * 60 +
parsedSeconds +
parsedMilliseconds / 1000
);
}
|