remotion-dev/remotion · error · Error

Expected timescale and duration in trak box

Error message

Expected timescale and duration in trak box

What it means

Thrown by collectSamplePositionsFromTrak when getTimescaleAndDuration(trakBox) returns null. That helper reads the mdhd (Media Header) box inside the trak to obtain timescale and duration; a null result means mdhd is absent or unparseable, so the parser has no time base for the track's samples. mdhd is mandatory in ISO/IEC 14496-12 for every trak.

Source

Thrown at packages/media-parser/src/containers/iso-base-media/collect-sample-positions-from-trak.ts:50

	if (!stszBox) {
		throw new Error('Expected stsz box in trak box');
	}

	if (!stcoBox) {
		throw new Error('Expected stco box in trak box');
	}

	if (!stscBox) {
		throw new Error('Expected stsc box in trak box');
	}

	if (!sttsBox) {
		throw new Error('Expected stts box in trak box');
	}

	if (!timescaleAndDuration) {
		throw new Error('Expected timescale and duration in trak box');
	}

	const samplePositions = getSamplePositions({
		stcoBox,
		stscBox,
		stszBox,
		stssBox,
		sttsBox,
		cttsBox,
	});

	return samplePositions;
};

View on GitHub (pinned to 78fe4bb3fd)

Solutions

  1. Re-mux with `ffmpeg -i input.mp4 -c copy -movflags faststart out.mp4` to rebuild mdhd and the rest of mdia.
  2. Validate the file with `ffprobe -v error -show_streams`; reject if mdhd/timescale is missing.
  3. Catch the error and report the file as unsupported rather than letting it propagate as an unhandled exception.
  4. If authoring files programmatically, ensure every trak includes an mdhd box with a sane timescale.

Example fix

// before
const info = await parseMedia({ src: file, fields: { dimensions: true, duration: true } });

// after
let info;
try {
  info = await parseMedia({ src: file, fields: { dimensions: true, duration: true } });
} catch (err) {
  if (/timescale and duration/i.test(String(err?.message))) {
    throw new Error('Cannot read track timing: file is corrupt or missing mdhd. Re-mux with ffmpeg.');
  }
  throw err;
}
Defensive patterns

Strategy: try-catch

Validate before calling

import {execFileSync} from 'node:child_process';
function hasTimescale(file: string): boolean {
  try {
    const out = execFileSync('ffprobe', ['-v', 'error', '-show_entries', 'stream=time_base', '-of', 'json', file], {encoding: 'utf8'});
    const data = JSON.parse(out);
    return data.streams?.every((s: any) => !!s.time_base && s.time_base !== '0/0');
  } catch { return false; }
}

Type guard

import type {TrakBox} from './trak/trak';
import {getTimescaleAndDuration} from '../../get-fps';
function trakHasTimescale(trakBox: TrakBox): boolean {
  return getTimescaleAndDuration(trakBox) !== null;
}

Try / catch

try {
  const positions = collectSamplePositionsFromTrak(trakBox);
} catch (err) {
  if (/timescale and duration/i.test(String(err?.message))) {
    return { seekable: false, reason: 'missing-mdhd' };
  }
  throw err;
}

Prevention

When it happens

Trigger: A trak that is missing its mdia/mdhd child, or whose mdhd is present but parsed as the wrong version/type so getTimescaleAndDuration returns null. Triggered during sample position collection for non-fragmented traks.

Common situations: Corrupt files where the moov was partially overwritten. Files from non-conformant encoders that emit trak without mdhd. Truncated moov atoms at EOF. Files surviving an interrupted transfer.

Related errors


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