remotion-dev/remotion · error · TypeError

Invalid LUT content: expected

Error message

Invalid LUT content: expected ${expectedRows} color rows for LUT_3D_SIZE ${size}, but got ${dataRows}

What it means

parseCubeLut() counts non-header color data rows and requires exactly size^3 rows for the declared LUT_3D_SIZE. If the number of data rows differs (too few or too many), the file is inconsistent with its declared size and a TypeError is thrown naming expected vs actual row counts.

Solutions

  1. Compare the reported got/expected counts: fix LUT_3D_SIZE to match the actual number of color rows, or regenerate the LUT with the correct size
  2. Re-download/re-export the LUT — truncation is the most common cause when got < expected
  3. Remove trailing junk lines or duplicated rows so exactly size^3 data rows remain
  4. Check the file size math: each row is one line of 3 numbers; a 33-size LUT must have 35937 data lines

Example fix

// before (.cube header mismatch)
LUT_3D_SIZE 33
// ...only 4913 data rows (a 17-size cube)
// after
LUT_3D_SIZE 17
// (or regenerate the table with 35937 rows for size 33)
Defensive patterns

Strategy: validation

Validate before calling

const lines = content.split(/\r?\n/)
  .map((l) => l.split('#')[0].trim())
  .filter((l) => l.length > 0);
const sizeLine = lines.find((l) => /^LUT_3D_SIZE\s+\d+$/.test(l));
const size = sizeLine ? Number(sizeLine.split(/\s+/)[1]) : null;
const isData = (l: string) => /^[\d.eE+-]+\s+[\d.eE+-]+\s+[\d.eE+-]+$/.test(l);
if (size !== null) {
  const dataRows = lines.filter(isData).length;
  if (dataRows !== size ** 3) throw new Error(`Expected ${size ** 3} rows, found ${dataRows}`);
}

Try / catch

try {
  lut = parseCubeLut(content);
} catch (e) {
  if (e instanceof TypeError && /color rows for LUT_3D_SIZE/.test(e.message)) {
    // re-download or regenerate the LUT; message states expected vs got
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling parseCubeLut(content) on a truncated .cube file (download cut off), a file where LUT_3D_SIZE was edited without regenerating the data table (e.g. says 33 but contains 17^3=4913 rows), extra garbage lines parsed as data rows, or rows removed/added by hand-editing.

Common situations: Incomplete uploads/downloads, converting a smaller LUT by only editing the size header, files that mix CRLF/LF or contain stray tokens miscounted as data, and LUTs from tools writing fewer rows than declared.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of remotion-dev/remotion@46a3a6bf13 (2026-09-18). Data as JSON: /api/errors/03fab41ad3b6d8f4. Report an issue: GitHub.

Appendix: source

Thrown at packages/effects/src/lut/parse-cube-lut.ts:200

			);
		}
	}

	if (size === null) {
		throw new TypeError('Invalid LUT content: LUT_3D_SIZE is missing');
	}

	for (let channel = 0; channel < 3; channel++) {
		if (domainMin[channel] >= domainMax[channel]) {
			throw new TypeError(
				`Invalid LUT content: DOMAIN_MIN must be smaller than DOMAIN_MAX for every channel`,
			);
		}
	}

	const expectedRows = size ** 3;
	if (dataRows !== expectedRows) {
		throw new TypeError(
			`Invalid LUT content: expected ${expectedRows} color rows for LUT_3D_SIZE ${size}, but got ${dataRows}`,
		);
	}

	const data = new Float32Array(expectedRows * 4);
	let dataIndex = 0;

	for (let index = 0; index < lines.length; index++) {
		const line = stripComment(lines[index]).trim();
		if (line.length === 0) {
			continue;
		}

		const [keyword, ...values] = line.split(/\s+/);
		if (HEADER_DIRECTIVES.has(keyword)) {
			continue;
		}

View on GitHub (pinned to 46a3a6bf13)