remotion-dev/remotion · error · Error

Read past end of file

Error message

Read past end of file

What it means

Thrown during the second fetch performed by getMoovAtom (when moov sits at the end of the file and the parser must range-request the tail) if iterator.counter.getOffset() + endOfMdat exceeds state.contentLength. This means the parser consumed more bytes than the file is supposed to contain — typically because contentLength was misreported or the actual response body is larger/structured differently than declared.

Source

Thrown at packages/media-parser/src/containers/iso-base-media/get-moov-atom.ts:124

			logLevel: state.logLevel,
			onlyIfMoovAtomExpected: {
				tracks: tracksState,
				isoState: null,
				movieTimeScaleState: state.iso.movieTimeScale,
				onAudioTrack,
				onVideoTrack,
				registerVideoSampleCallback: () => Promise.resolve(),
				registerAudioSampleCallback: () => Promise.resolve(),
			},
			onlyIfMdatAtomExpected: null,
			contentLength: state.contentLength - endOfMdat,
		});
		if (box.type === 'box') {
			boxes.push(box.box);
		}

		if (iterator.counter.getOffset() + endOfMdat > state.contentLength) {
			throw new Error('Read past end of file');
		}

		if (iterator.counter.getOffset() + endOfMdat === state.contentLength) {
			break;
		}
	}

	const moov = boxes.find((b) => b.type === 'moov-box');
	if (!moov) {
		throw new Error('No moov box found');
	}

	Log.verbose(
		state.logLevel,
		`Finished fetching moov atom in ${Date.now() - start}ms`,
	);

	return moov;

View on GitHub (pinned to 78fe4bb3fd)

Solutions

  1. Ensure the readerInterface honors the requested byte range and the source reports a stable Content-Length.
  2. Pre-fetch the file size with a HEAD request and pass an accurate contentLength to parseMedia.
  3. If the source is a growing file, snapshot it to a stable URL before parsing.
  4. Implement a readerInterface that clamps reads to contentLength to avoid overshoot.

Example fix

// before
await parseMedia({ src: growingUrl, readerInterface: customReader });

// after
// Snapshot the current size and use a byte-serving endpoint
const head = await fetch(growingUrl, { method: 'HEAD' });
const size = Number(head.headers.get('content-length'));
await parseMedia({
  src: `${growingUrl}?snapshot=${size}`,
  readerInterface: rangeServingReader,
});
Defensive patterns

Strategy: validation

Validate before calling

// Ensure contentLength is accurate and the reader honors ranges
async function verifyStableSize(url: string): Promise<number> {
  const h1 = await fetch(url, { method: 'HEAD' });
  const size1 = Number(h1.headers.get('content-length'));
  const h2 = await fetch(url, { method: 'HEAD' });
  const size2 = Number(h2.headers.get('content-length'));
  if (!Number.isFinite(size1) || size1 !== size2) throw new Error('Source size is unstable; snapshot before parsing');
  return size1;
}

Try / catch

try {
  await parseMedia({ src, fields: { duration: true } });
} catch (err) {
  if (/Read past end of file/i.test(String(err?.message))) {
    throw new Error('Source reported an inconsistent size. Snapshot the file or fix Content-Length and retry.');
  }
  throw err;
}

Prevention

When it happens

Trigger: Reached after moov is not at the head and the parser re-fetches from endOfMdat onward. If the server returns more bytes than the original Content-Length (e.g. the source grew between requests, a proxy injected content, or contentLength was misreported by the caller) the offset will overshoot. Also reachable if endOfMdat is computed wrong, double-counting bytes.

Common situations: Live or append-only MP4s that grow between the HEAD and the range request. Misreported contentLength by a custom readerInterface. Proxies that decompress or transform the body. Range request that ignores Range headers and returns the whole file.

Related errors


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