Remotion 마스터 가이드 — React를 이용한 프로그래매틱 비디오 제작
Remotion을 사용하면 순수 React 코드로 시네마틱 비디오를 제작할 수 있습니다. 각 비디오 프레임은 결정론적 상태 (frame) => UI 로 처리되어 끊김이나 프레임 드롭 없이 완벽한 60 FPS 영상을 생성합니다.
1. 컴포지션 아키텍처 및 루트 구성
A. 루트 진입점 (Root.tsx)
import { Composition } from 'remotion';
import { MainExplainer } from './compositions/MainExplainer';
import { VerticalShort } from './compositions/VerticalShort';
export const RemotionRoot: React.FC = () => {
return (
<>
{/* 16:9 가로 시네마틱 익스플레이너 */}
<Composition
id="MainExplainer-1080p"
component={MainExplainer}
durationInFrames={30 * 32} // 32초 @ 30 FPS = 960 프레임
fps={30}
width={1920}
height={1080}
defaultProps={{
title: '세계 빙하 정상이 녹는 미스터리',
author: 'NARA 스튜디오',
themeColor: '#f97316',
}}
/>
{/* 9:16 세로 TikTok / Reels / Shorts */}
<Composition
id="VerticalShort-60fps"
component={VerticalShort}
durationInFrames={60 * 15} // 15초 @ 60 FPS = 900 프레임
fps={60}
width={1080}
height={1920}
/>
</>
);
};
B. 지속 시간 및 FPS 규칙
- 16:9 YouTube / 익스플레이너:
30 fps권장 (렌더링 속도와 부드러움의 균형) 또는 초고해상도 키네틱 타이포그래피의 경우60 fps. - 프레임 지속 시간: 항상
durationInFrames = Math.round(초 * fps)로 계산합니다. - Zero Jitter:
setInterval또는setTimeout을 사용하지 마세요. 항상useCurrentFrame()과useVideoConfig()를 사용하세요.
2. 레이아웃 및 타임라인 기본 요소
AbsoluteFill
장면 레이어의 기본입니다. 자동으로 position: absolute; inset: 0; width: 100%; height: 100%;가 적용됩니다:
import { AbsoluteFill } from 'remotion';
export const SceneBackground: React.FC = () => {
return (
<AbsoluteFill style={{ backgroundColor: '#090812', overflow: 'hidden' }}>
{/* 시각적 콘텐츠 */}
</AbsoluteFill>
);
};
Sequence vs Series
Sequence: 요소를 절대 시간(from및durationInFrames)에 배치합니다. 배경 음악, 워터마크, HUD 오버레이 또는 서로 겹치는 요소에 이상적입니다.Series: 수동으로 프레임 오프셋을 계산할 필요 없이 장면을 자동으로 순차적으로 정렬합니다.
import { Series, Sequence, Audio, staticFile } from 'remotion';
export const VideoStory: React.FC = () => {
return (
<AbsoluteFill>
{/* 비디오 전체 배경 음악 */}
<Audio src={staticFile('audio/ambient_score.mp3')} volume={0.35} />
{/* 자동 장면 순서 */}
<Series>
<Series.Sequence durationInFrames={120}>
<HookScene />
</Series.Sequence>
<Series.Sequence durationInFrames={180}>
<ProblemScene />
</Series.Sequence>
<Series.Sequence durationInFrames={240}>
<SolutionScene />
</Series.Sequence>
<Series.Sequence durationInFrames={150}>
<CallToActionScene />
</Series.Sequence>
</Series>
</AbsoluteFill>
);
};
3. 애니메이션 물리: spring() & interpolate()
A. 자연스러운 스프링 공식 (spring())
spring()을 사용하여 실제 물리적 움직임을 생성하세요 (딱딱한 "PowerPoint" 느낌 제거):
import { spring, useCurrentFrame, useVideoConfig } from 'remotion';
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// 1. 스내피 팝 (텍스트/뱃지가 튀어나오며 나타남)
const popScale = spring({
frame,
fps,
config: { damping: 10, mass: 0.4, stiffness: 180 },
});
// 2. 부드러운 드리프트 (카메라가 부드럽게 미끄러짐)
const smoothSlide = spring({
frame,
fps,
config: { damping: 20, mass: 1.2, stiffness: 60 },
});
// 3. 바운시 드롭 (아이콘/파티클이 튀며 떨어짐)
const bounce = spring({
frame: frame - 10, // 10 프레임 지연
fps,
config: { damping: 8, mass: 0.6, stiffness: 140 },
});
B. 부드러운 값 매핑 (interpolate())
import { interpolate, Easing } from 'remotion';
// 베지어 이징을 사용한 알파 및 회전 전환
const opacity = interpolate(frame, [0, 20], [0, 1], {
extrapolateRight: 'clamp',
});
const rotateY = interpolate(frame, [0, 60], [-45, 0], {
easing: Easing.bezier(0.16, 1, 0.3, 1),
extrapolateRight: 'clamp',
});
4. 키네틱 타이포그래피 및 음성 해설 동기화
A. 단어별 나타내기 (텍스트가 단어별로 등장)
import React from 'react';
import { useCurrentFrame, useVideoConfig, spring, interpolate } from 'remotion';
interface KineticTextProps {
sentence: string;
startFrame?: number;
wordDelay?: number;
}
export const KineticText: React.FC<KineticTextProps> = ({
sentence,
startFrame = 0,
wordDelay = 4, // 단어 간 4프레임 간격
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const words = sentence.split(' ');
return (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '12px', justifyContent: 'center' }}>
{words.map((word, idx) => {
const wordStart = startFrame + idx * wordDelay;
const progress = spring({
frame: frame - wordStart,
fps,
config: { damping: 12, mass: 0.4, stiffness: 160 },
});
const translateY = interpolate(progress, [0, 1], [40, 0]);
const opacity = interpolate(progress, [0, 1], [0, 1]);
return (
<span
key={idx}
style={{
display: 'inline-block',
transform: `translateY(${translateY}px)`,
opacity,
fontSize: '48px',
fontWeight: 800,
color: idx === 0 ? '#f97316' : '#ffffff',
letterSpacing: '-1px',
fontFamily: "'JetBrains Mono', sans-serif",
}}
>
{word}
</span>
);
})}
</div>
);
};
B. 음성 해설 및 SFX 동기화
- 오디오 파일은
public/audio/폴더에 넣으세요. staticFile('audio/file.mp3')를 사용하세요.volume속성을 사용하여 오디오 페이드 인/페이드 아웃 전환을 만듭니다:
<Audio
src={staticFile('audio/naskah_vo.mp3')}
volume={(f) =>
interpolate(f, [0, 15, 880, 900], [0, 1, 1, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
})
}
/>
5. 비동기 에셋 및 사전 로딩 (delayRender)
비디오가 사용자 정의 글꼴, 외부 이미지 또는 API 데이터를 로드할 때 Remotion은 빈 프레임을 렌더링하지 않도록 반드시 알림을 받아야 합니다:
import { delayRender, continueRender } from 'remotion';
import { useEffect, useState } from 'react';
export const DynamicScene: React.FC = () => {
const [handle] = useState(() => delayRender('글꼴 및 에셋 로딩 중'));
useEffect(() => {
document.fonts.ready.then(() => {
continueRender(handle);
});
}, [handle]);
return <AbsoluteFill>...</AbsoluteFill>;
};
6. NARA 모션 그래픽 템플릿 및 레시피
8단계 시네마틱 공식 (PPT 탈출)
- 장면 1: Cold Hook (00-03초) $\rightarrow$ 미스터리한 비주얼 + 큰 키네틱 타이포그래피.
- 장면 2: 문제 제시 (04-07초) $\rightarrow$ 무너지는 얼음 비주얼 / 불타는 붉은 그래픽.
- 장면 3: 심층 데이터 탐구 (08-12초) $\rightarrow$ 통계 숫자가 올라가는 카운팅 (
Math.floor). - 장면 4: 동적 배율 변경 (13-16초) $\rightarrow$ 매크로에서 마이크로로 규모 변화.
- 장면 5: 인간적 영향 (17-20초) $\rightarrow$ 캔버스에서 반응하는 만화 캐릭터.
- 장면 6: 해결책 공개 (21-25초) $\rightarrow$ 밝은 녹색/주황색으로 전환되는 비주얼.
- 장면 7: 행동 단계 (26-29초) $\rightarrow$ 3D로 떠다니는 3개의 카드 포인트.
- 장면 8: 클라이맥스 및 CTA (30-32초) $\rightarrow$ 스튜디오 로고 펄스 + 오디오 우숩 드롭.
7. CLI 렌더링 실행 및 하드웨어 가속
A. 단일 비디오 렌더링 (1080p 60fps)
npx remotion render src/remotion/index.ts MainExplainer-1080p out/output.mp4 --gl=angle
B. 높은 동시성을 사용한 빠른 렌더링
npx remotion render src/remotion/index.ts MainExplainer-1080p out/output.mp4 --concurrency=8 --gl=angle --jpeg-quality=90
C. 중요한 렌더링 플래그
--gl=angle: Windows에서 DirectX GPU 가속 사용 (소프트웨어 래스터라이저보다 훨씬 빠름).--gl=swiftshader: 컨테이너/CI에서 GPU를 사용할 수 없는 경우 소프트웨어 폴백.--concurrency=N: 병렬 렌더링에 사용할 CPU 코어 수.--muted: 비디오만 필요한 경우 오디오 스트림 없이 렌더링.--props='{"title":"New"}': 명령줄 JSON을 통해 동적 매개변수 전달.
8. 문제 해결 가이드
- 검은 화면 / 이미지가 나타나지 않음: 에셋이
public/폴더에 있고staticFile('파일이름')으로 호출되는지 확인하세요. - 렌더링 중 비디오 멈춤:
useMemo의 무한 루프 또는delayRender를 닫지 않는 React 컴포넌트가 있는지 확인하세요. - 오디오 동기화 불량: 컴포지션의
fps값이 음성 해설 초 계산 (초 = 프레임 / fps)과 정확히 일치하는지 확인하세요.