parseSrt crashes with unhandled TypeError on leading blank lines, whitespace, or comments
Describe the bug
Describe the bug
parseSrt throws an unhandled TypeError: Cannot read properties of undefined (reading 'text') whenever an SRT file contains leading blank lines, whitespace, or metadata headers before the first numbered cue.
In packages/captions/src/parse-srt.ts:
} else if (line?.trim() === '') {
(captions[captions.length - 1] as Caption).text = (
captions[captions.length - 1] as Caption
).text.trim();
} else {
(captions[captions.length - 1] as Caption).text += line + '\n';
}If the file starts with an empty line or comment, captions.length is 0. Accessing captions[-1] produces undefined, and reading undefined.text crashes immediately.
Furthermore, Windows CRLF (\r\n) line breaks are not normalized, leaking trailing \r into subtitle strings, and timestamps formatted with decimal periods (00:00:01.500, standard in WebVTT and Whisper) throw Invalid timestamp in toSeconds.
Steps To Reproduce
Steps To Reproduce
- Call
parseSrtwith an SRT string containing a leading newline:
import { parseSrt } from '@remotion/captions';
const input = '\n1\n00:00:00,000 --> 00:00:02,000\nHello world\n';
parseSrt({ input });- Uncaught
TypeError:
TypeError: Cannot read properties of undefined (reading 'text')
at parseSrt (packages/captions/src/parse-srt.ts:63:39)Expected behavior
Expected behavior
parseSrt should gracefully ignore leading/trailing blank lines, normalize CRLF line breaks, and support both , and . decimal millisecond separators without crashing.
Suggested Fix
--- a/packages/captions/src/parse-srt.ts
+++ b/packages/captions/src/parse-srt.ts
@@ -14,7 +14,7 @@ function toSeconds(time: string) {
throw new Error(`Invalid timestamp:${time}`);
}
- const [seconds, millis] = third.split(',');
+ const [seconds, millis] = third.trim().split(/[,.]/);
if (!seconds) {
throw new Error(`Invalid timestamp:${time}`);
}
@@ -40,16 +40,21 @@ export type ParseSrtOutput = {
};
export const parseSrt = ({input}: ParseSrtInput): ParseSrtOutput => {
- const inputLines = input.split('\n');
+ const normalized = input
+ .replace(/^\uFEFF/, '')
+ .replace(/\r\n/g, '\n')
+ .replace(/\r/g, '\n');
+ const inputLines = normalized.split('\n');
const captions: Caption[] = [];
for (let i = 0; i < inputLines.length; i++) {
const line = inputLines[i];
const nextLine = inputLines[i + 1];
- if (line?.match(/([0-9]+)/) && nextLine?.includes(' --> ')) {
+ if (line?.match(/^\s*\d+\s*$/) && nextLine?.includes(' --> ')) {
const nextLineSplit = nextLine.split(' --> ');
- const start = toSeconds(nextLineSplit[0] as string);
- const end = toSeconds(nextLineSplit[1] as string);
+ const start = toSeconds((nextLineSplit[0] as string).trim());
+ const end = toSeconds((nextLineSplit[1] as string).trim());
captions.push({
text: '',
startMs: start * 1000,
@@ -60,9 +65,11 @@ export const parseSrt = ({input}: ParseSrtInput): ParseSrtOutput => {
} else if (line?.includes(' --> ')) {
continue;
} else if (line?.trim() === '') {
- (captions[captions.length - 1] as Caption).text = (
- captions[captions.length - 1] as Caption
- ).text.trim();
+ if (captions.length > 0) {
+ (captions[captions.length - 1] as Caption).text = (
+ captions[captions.length - 1] as Caption
+ ).text.trim();
+ }
} else if (captions.length > 0) {
(captions[captions.length - 1] as Caption).text += line + '\n';
}
}Packages
@remotion/captions
Additional Context
No response
Source: refinedev/refine