remotion-dev/remotion · error · Error

Unsupported CTTS version ${version}

Error message

Unsupported CTTS version ${version}

What it means

The CTTS (Composition Time to Sample) box declares a version byte other than 0 or 1. Version 0 stores unsigned offsets and version 1 stores signed offsets; both are the only standardized versions. Any other value means the box is malformed or the parser is misaligned (read the wrong byte as the version).

Source

Thrown at packages/media-parser/src/containers/iso-base-media/stsd/ctts.ts:28

	type: 'ctts-box';
	version: number;
	flags: number[];
	entryCount: number;
	entries: CttsEntry[];
}

export const parseCtts = ({
	iterator,
	offset,
	size,
}: {
	iterator: BufferIterator;
	offset: number;
	size: number;
}): CttsBox => {
	const version = iterator.getUint8();
	if (version !== 0 && version !== 1) {
		throw new Error(`Unsupported CTTS version ${version}`);
	}

	const flags = iterator.getSlice(3);
	const entryCount = iterator.getUint32();

	const entries: CttsEntry[] = [];

	for (let i = 0; i < entryCount; i++) {
		const sampleCount = iterator.getUint32();

		// V1 = signed, V0 = unsigned
		// however some files are buggy

		// Let's do the same thing as mp4box
		// https://github.com/gpac/mp4box.js/blob/c6cc468145bc5b031b866446111f29c8b620dbe6/src/parsing/ctts.js#L2
		const sampleOffset = iterator.getInt32();

		entries.push({

View on GitHub (pinned to 78fe4bb3fd)

Solutions

  1. Validate the file with mp4box/ffprobe: `ffprobe -v error -show_format input.mp4` — if ffprobe also errors, the file is corrupt.
  2. Check whether an earlier box in the same moov/traf threw a size-mismatch error; cursor desync is the most common root cause.
  3. Re-mux the file with `ffmpeg -i input.mp4 -c copy -movflags +faststart remuxed.mp4` to normalize box layout.
  4. If the source is a live stream or fragmented MP4, ensure the moof/traf chain is complete and not truncated mid-flight.

Example fix

// before: corrupted moov produces CTTS version 0xff
ffmpeg -i corrupt.mp4 -c copy fixed.mp4
// after: re-mux regates a well-formed CTTS v0/v1
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate the CTTS box version byte before parsing
function isValidCttsVersion(versionByte: number): boolean {
  return versionByte === 0 || versionByte === 1;
}
// Read the version byte at the box's data offset and check before invoking parseMedia

Type guard

function isSupportedCttsVersion(v: number): v is 0 | 1 {
  return v === 0 || v === 1;
}

Try / catch

try {
  await parseMedia({src, fields: {dimensions: true}});
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Unsupported CTTS version')) {
    // file likely corrupt or cursor-desynced; re-mux or reject
  } else throw err;
}

Prevention

When it happens

Trigger: Calling parseCtts on a CTTS box where the first byte (version) is not 0 or 1. This usually means the iterator is reading garbage because an earlier box was parsed with the wrong size, pushing the cursor into the middle of unrelated data.

Common situations: Corrupt or truncated MP4 files, files produced by non-conformant encoders, or files where a preceding box parser consumed too many/few bytes and desynchronized the cursor. Rarely a genuinely novel version.

Related errors


AI-assisted analysis of remotion-dev/remotion@78fe4bb3fd (2026-08-12). Data as JSON: /api/errors/457d975040357bd5. Report an issue: GitHub.