← Back to Home

How to Calculate Scene Duration with Cross-Fade in Remotion

Updated January 14, 2026
remotionscene durationcross-fadeframe timingaudio sync

Calculating Scene Duration with Cross-Fade in Remotion

Scene duration in Remotion must account for cross-fade transitions between scenes.

Timing Constants

const FPS = 30;
const CROSS_FADE_FRAMES = 30;  // 1 second

Scene Duration Formula

sceneDurationInFrames = audioFrames + CROSS_FADE_FRAMES

Complete Calculation

function calculateSceneDuration(pcmBuffer: Buffer): {
  duration: number;
  durationInFrames: number;
} {
  // Calculate audio duration in frames
  const audioBytesPerSecond = 24000 * 1 * 2;  // sampleRate × channels × bytesPerSample
  const audioSeconds = pcmBuffer.length / audioBytesPerSecond;
  const audioFrames = Math.round(audioSeconds * 30);

  // Add cross-fade frames
  const durationInFrames = audioFrames + 30;
  const duration = durationInFrames / 30;

  return { duration, durationInFrames };
}

Timeline with Cross-Fades

Scene 1 (400 frames total, 370 frames audio):
  Frames 0-370:    Audio plays, full opacity
  Frames 370-400:  Fade out (cross-fade zone)

Scene 2 (500 frames total, 470 frames audio):
  Frames 370-400:  Fade in (cross-fade with Scene 1)
  Frames 400-470:  Audio plays, full opacity
  Frames 470-500:  Fade out (cross-fade zone)

Scene 3 (560 frames total, 530 frames audio):
  Frames 470-500:  Fade in (cross-fade with Scene 2)
  Frames 500-530:  Audio plays, full opacity
  Frames 530-560:  Fade out

Total Duration Calculation

function calculateTotalDuration(scenes: Array<{ durationInFrames: number }>): number {
  if (scenes.length === 0) return 0;

  // First scene: full duration
  let total = scenes[0].durationInFrames;

  // Subsequent scenes: subtract overlap
  for (let i = 1; i < scenes.length; i++) {
    total += scenes[i].durationInFrames - 30;  // Subtract cross-fade overlap
  }

  return total;
}

Example

const scenes = [
  { durationInFrames: 400 },  // 0-400
  { durationInFrames: 500 },  // 400-870 (30-frame overlap)
  { durationInFrames: 560 },  // 870-1400 (30-frame overlap)
];

const total = calculateTotalDuration(scenes);
// total = 400 + (500-30) + (560-30) = 1400 frames

Sequence Timing

let currentFrame = 0;

scenes.forEach((scene, index) => {
  // Scene starts at currentFrame
  console.log(`Scene ${index + 1}: starts at frame ${currentFrame}`);

  // Next scene starts after subtracting overlap
  currentFrame += scene.durationInFrames - 30;
});

Opacity Calculation

function calculateSceneOpacity(
  sceneFrame: number,
  sceneDuration: number,
  fadeInFrames: number = 30,
  fadeOutFrames: number = 30
): number {
  if (sceneFrame < fadeInFrames) {
    // Fade in
    return sceneFrame / fadeInFrames;
  } else if (sceneFrame > sceneDuration - fadeOutFrames) {
    // Fade out
    return (sceneDuration - sceneFrame) / fadeOutFrames;
  }
  // Full opacity
  return 1;
}