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;
}