{"record":{"id":"1bf8d07d1601965f","repo":"remotion-dev/remotion","slug":"path-endms-must-not-be-earlier-than-startms","errorCode":null,"errorMessage":"${path}.endMs must not be earlier than startMs.","messagePattern":"(.+?)\\.endMs must not be earlier than startMs\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/studio/src/components/parse-caption-file.ts","lineNumber":55,"sourceCode":"\t\tconst path = `captions[${index}]`;\n\t\tif (!isObject(caption)) {\n\t\t\tthrow new Error(`${path} must be an object.`);\n\t\t}\n\n\t\tif (typeof caption.text !== 'string') {\n\t\t\tthrow new Error(`${path}.text must be a string.`);\n\t\t}\n\n\t\tif (!isFiniteNumber(caption.startMs) || caption.startMs < 0) {\n\t\t\tthrow new Error(`${path}.startMs must be a finite, non-negative number.`);\n\t\t}\n\n\t\tif (!isFiniteNumber(caption.endMs) || caption.endMs < 0) {\n\t\t\tthrow new Error(`${path}.endMs must be a finite, non-negative number.`);\n\t\t}\n\n\t\tif (caption.endMs < caption.startMs) {\n\t\t\tthrow new Error(`${path}.endMs must not be earlier than startMs.`);\n\t\t}\n\n\t\tif (caption.timestampMs !== null && !isFiniteNumber(caption.timestampMs)) {\n\t\t\tthrow new Error(`${path}.timestampMs must be a finite number or null.`);\n\t\t}\n\n\t\tif (caption.confidence !== null && !isFiniteNumber(caption.confidence)) {\n\t\t\tthrow new Error(`${path}.confidence must be a finite number or null.`);\n\t\t}\n\n\t\tif (\n\t\t\ttypeof caption.confidence === 'number' &&\n\t\t\t(caption.confidence < 0 || caption.confidence > 1)\n\t\t) {\n\t\t\tthrow new Error(`${path}.confidence must be between 0 and 1.`);\n\t\t}\n\n\t\tif (","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/remotion-dev/remotion/blob/b2f4e34732f3c6c222ea029413e22dc402218cd7/packages/studio/src/components/parse-caption-file.ts#L37-L73","documentation":"parseCaptionFile enforces chronological caption timing: if a caption's endMs is earlier than its startMs, the file is rejected. This catches inverted or corrupt timing ranges in the loaded captions JSON and reports which indexed caption is at fault.","triggerScenarios":"captions[i] has endMs < startMs, e.g. {\"startMs\": 2000, \"endMs\": 1000}, commonly after sorting mistakes or ms/seconds unit mixing.","commonSituations":"Hand-editing timestamps and swapping values; one caption in seconds and the next in milliseconds; transcription drift producing inverted ranges.","solutions":["Swap or correct startMs/endMs so endMs >= startMs","Re-sort/recompute timings in the exporting tool","Convert all times to the same unit (milliseconds)"],"exampleFix":"// before\n{\"text\": \"hi\", \"startMs\": 2000, \"endMs\": 1000}\n// after\n{\"text\": \"hi\", \"startMs\": 1000, \"endMs\": 2000}","handlingStrategy":"validation","validationCode":"for (const [i, c] of parsed.entries()) {\n  if ((c as any).endMs < (c as any).startMs) {\n    throw new Error(`captions[${i}]: endMs must be >= startMs`);\n  }\n}","typeGuard":"const hasValidRange = (c: {startMs: number; endMs: number}): boolean =>\n  c.endMs >= c.startMs;","tryCatchPattern":"try {\n  const captions = parseCaptionFile({fileName, contents});\n} catch (e) {\n  // fix the inverted range in the reported element and retry\n}","preventionTips":["Clamp endMs = Math.max(endMs, startMs) in the export pipeline","Avoid mixing units (seconds vs ms) across cues","Check timings after any manual timestamp edits"],"tags":["json","validation","captions"],"backgroundTag":"value-out-of-range","analyzedSha":"b2f4e34732f3c6c222ea029413e22dc402218cd7","analyzedAt":"2026-09-09T13:27:51.281Z","contentChangedAt":"2026-09-09T13:27:51.281Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}