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 makeBaseMediaTrack when getTimescaleAndDuration(trakBox) returns null during track construction. That helper reads mdhd (Media Header) for timescale and duration; null means mdhd is missing or unparseable, so the track has no time base. Without timescale the parser cannot convert sample timestamps to seconds and refuses to build the track.

Source

Thrown at packages/media-parser/src/containers/iso-base-media/make-track.ts:58

export const makeBaseMediaTrack = (
	trakBox: TrakBox,
	startTimeInSeconds: number,
):
	| MediaParserVideoTrack
	| MediaParserAudioTrack
	| MediaParserOtherTrack
	| null => {
	const tkhdBox = getTkhdBox(trakBox);

	const videoDescriptors = getVideoDescriptors(trakBox);
	const timescaleAndDuration = getTimescaleAndDuration(trakBox);

	if (!tkhdBox) {
		throw new Error('Expected tkhd box in trak box');
	}

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

	if (trakBoxContainsAudio(trakBox)) {
		const numberOfChannels = getNumberOfChannelsFromTrak(trakBox);
		if (numberOfChannels === null) {
			throw new Error('Could not find number of channels');
		}

		const sampleRate = getSampleRate(trakBox);
		if (sampleRate === null) {
			throw new Error('Could not find sample rate');
		}

		const {codecString, description} = getAudioCodecStringFromTrak(trakBox);
		const codecPrivate =
			getCodecPrivateFromTrak(trakBox) ?? description ?? null;
		const codecEnum = getAudioCodecFromTrack(trakBox);

View on GitHub (pinned to 78fe4bb3fd)

Solutions

  1. Re-mux with ffmpeg `-c copy -movflags faststart` to rebuild mdhd and the mdia subtree.
  2. Validate with `ffprobe -show_streams -show_entries stream=time_base` and reject files with no time base.
  3. Catch the error at the parse boundary and surface a 'corrupt file' message.
  4. If authoring files, ensure every trak's mdia contains an mdhd box.

Example fix

// before
await parseMedia({ src: file });

// after
try {
  await parseMedia({ src: file });
} catch (err) {
  if (/timescale and duration/i.test(String(err?.message))) {
    throw new Error('MP4 track is missing timing info (mdhd). The file is corrupt; re-mux it with ffmpeg.');
  }
  throw err;
}
Defensive patterns

Strategy: try-catch

Validate before calling

import {execFileSync} from 'node:child_process';
function trackHasTiming(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 trakHasTiming(trakBox: TrakBox): boolean {
  return getTimescaleAndDuration(trakBox) !== null;
}

Try / catch

try {
  await parseMedia({ src: file });
} catch (err) {
  if (/timescale and duration/i.test(String(err?.message))) {
    throw new Error('MP4 track is missing timing info (mdhd). The file is corrupt; re-mux with ffmpeg -movflags faststart.');
  }
  throw err;
}

Prevention

When it happens

Trigger: Reached for every trak during track construction. Fires when mdhd is absent from the mdia subtree, or when mdhd was misparsed (wrong version/flags) so getTimescaleAndDuration returns null. Distinct from the sample-positions version because it blocks all track creation, not just seeking.

Common situations: Corrupt moov where mdia is incomplete. Files from non-conformant encoders. Truncated downloads where the moov tail is cut. Muxers that emit trak without mdhd (spec violation).

Related errors


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