Remotion (프로그래매틱 비디오)
Remotion은 React 컴포넌트를 프레임 단위로 렌더링합니다. 각 프레임은 프레임 번호에 대한 순수 함수이므로 애니메이션은 결정적이며 다시 렌더링할 수 있습니다.
Remotion의 API는 주요 버전 간에 변경됩니다. 설치된 버전(
npm ls remotion)과 공식 문서(remotion.dev)를 읽고 확실하지 않은 API를 사용하기 전에 확인하세요. 기업의 상업적 사용을 위해 Remotion의 라이선스 조건을 확인하세요.
설정
npx create-video@latest # 빈 템플릿(TypeScript) 선택
cd my-video && npm install
npx remotion studio # 실시간 미리보기
npx remotion render MyComp out/video.mp4
npx remotion still MyComp out/thumb.png --frame=45
개념 모델
useCurrentFrame()은 현재 프레임을 반환하며, 0부터 시작합니다.useVideoConfig()은fps,width,height,durationInFrames를 제공합니다.- 아무것도 자동으로 애니메이션되지 않습니다. 스타일을 프레임에서 계산합니다.
- CSS 전환,
setTimeout,Math.random()또는Date.now()를 애니메이션에 절대 사용하지 마세요. 이들은 렌더링 결정성을 깨뜨립니다. 대신 remotion의random('seed')를 사용하세요.
컴포지션
// src/Root.tsx
import {Composition} from 'remotion';
import {Explainer} from './Explainer';
export const RemotionRoot = () => (
<Composition
id="Explainer"
component={Explainer}
durationInFrames={30 * 20} // 30fps에서 20초
fps={30}
width={1920}
height={1080}
defaultProps={{title: 'Hello'}}
/>
);
애니메이션 프리미티브
import {AbsoluteFill, Sequence, interpolate, spring, useCurrentFrame, useVideoConfig} from 'remotion';
export const Title: React.FC<{text: string}> = ({text}) => {
const frame = useCurrentFrame();
const {fps} = useVideoConfig();
const opacity = interpolate(frame, [0, 15], [0, 1], {extrapolateRight: 'clamp'});
const scale = spring({frame, fps, config: {damping: 200}}); // 부드럽고 튕김 없음
return (
<AbsoluteFill style={{justifyContent: 'center', alignItems: 'center'}}>
<h1 style={{opacity, transform: `scale(${0.9 + 0.1 * scale})`, fontSize: 96}}>{text}</h1>
</AbsoluteFill>
);
};
export const Explainer = () => (
<AbsoluteFill style={{backgroundColor: '#0b1020', color: 'white'}}>
<Sequence from={0} durationInFrames={90}><Title text="Intro" /></Sequence>
<Sequence from={90} durationInFrames={120}><Title text="핵심 내용" /></Sequence>
</AbsoluteFill>
);
값이 범위를 벗어나지 않도록 하려면 항상 interpolate에 extrapolateLeft/Right: 'clamp'를 전달하세요.
미디어
import {staticFile, Audio, OffthreadVideo, Img} from 'remotion';
<Audio src={staticFile('voiceover.mp3')} />
<Audio src={staticFile('music.mp3')} volume={0.12} />
<OffthreadVideo src={staticFile('demo.mp4')} />
<Img src={staticFile('logo.png')} />
에셋은 public/ 디렉토리에 넣고 staticFile()로 참조하세요. <img> 대신 <Img>를 사용하여 렌더링이 이미지를 기다리도록 하세요. 최신 Remotion 버전은 업데이트된 미디어 컴포넌트를 제공하므로 설치된 버전의 문서를 확인하세요.
장면을 내레이션에 동기화
장면 길이를 하드코딩하지 마세요. 오디오에서 파생시키세요.
- 장면별 음성 파일(
voiceover-tts스킬)을 생성합니다. ffprobe로 각 길이를 측정하고scenes.json을{id, audio, seconds}형식으로 작성합니다.- 컴포지션에서
Math.ceil((seconds + 0.5) * fps)로 변환하고 누적 오프셋으로<Sequence>를 배치합니다. <Composition>에서calculateMetadata를 사용하여 해당 데이터로 총durationInFrames를 설정합니다.
음성이 다시 녹음되면 비디오가 자동으로 재조정됩니다.
전환
@remotion/transitions를 설치하고 fade(), slide(), wipe()와 함께 TransitionSeries를 사용하세요. 전환은 두 장면을 겹치므로 전체 길이가 전환 지속 시간만큼 줄어듭니다. 30fps에서 전환은 12~20프레임으로 유지하세요.
글꼴 및 브랜드
@remotion/google-fonts 또는 @remotion/fonts로 글꼴을 로드하여 첫 번째 프레임 이전에 준비되도록 하세요. 색상, 글꼴 및 로고를 하나의 brand.ts 파일에 유지하여 리브랜딩을 한 파일 변경으로 처리할 수 있게 하세요.
성능 및 렌더링
- 스튜디오에서 미리보기, CLI에서 렌더링합니다.
--concurrency=50%는 CPU 사용량을 제한합니다.--codec=h264가 기본값입니다. 편집 연계를 위해--codec=prores를 사용하세요.- 무거운 블러, 그림자 및 큰 SVG 필터는 느립니다. 이러한 경우 미리 렌더링된 에셋을 선호하세요.
- 소스 푸티지에는
<video>대신<OffthreadVideo>를 사용하여 프레임 정확한 추출을 얻으세요. - 빠른 초안을 위해
--scale=0.5와--frames=0-90범위를 사용하세요.
일반적인 실수
| 실수 | 해결 방법 |
|---|---|
| 렌더링 시 깜빡임 또는 이미지 누락 | <Img>, staticFile 및 비동기 데이터를 위한 delayRender 사용 |
| 미리보기와 렌더링 간 애니메이션 차이 | 비결정적 코드(시간, 난수) 제거 |
| 다른 로케일에서 텍스트 오버플로우 | 명시적 너비 설정, 가장 긴 문자열 테스트 |
| 전환 추가 후 비디오가 너무 길어짐 | 총 프레임에서 전환 프레임 빼기 |
| 렌더링에서 오디오가 들리지 않음 | 파일 경로와 <Audio>가 활성 <Sequence> 내부에 있는지 확인 |
타이밍 및 이징 선택을 위해 motion-design-craft와 함께 사용하세요.