Remotion 비디오 제작자
Remotion, 즉 React 컴포넌트를 비디오로 렌더링하는 도구를 사용하여 프로그래매틱 비디오를 만듭니다. 이 스킬은 Skill Stack의 시각적 스타일과 설명 콘텐츠 제작 과정에서 얻은 교훈을 인코딩합니다.
목적
독특한 리소그래프에서 영감을 받은 미학으로 Skill Stack 개념을 설명하는 짧은 형식의 비디오 콘텐츠(10-60초)를 제작합니다. 비디오는 코드이므로 버전 관리가 가능하고, 재현 가능하며, 무한히 편집할 수 있습니다.
이 스킬을 사용해야 할 때
- Skill Stack 개념을 설명하는 설명 비디오 제작
- 애니메이션 소셜 콘텐츠 제작 (세로 9:16, 정사각형 1:1, 가로 16:9)
- 기술 워크플로우 또는 점진적 공개 시각화
- 정확한 타이밍과 타이포그래피가 중요한 비디오
다음의 경우에는 적합하지 않습니다:
- 실사 영상 편집 (기존 비디오 도구 사용)
- 복잡한 3D 그래픽 (Remotion은 2D에 중점)
- 빠른 일회성 그래픽 (Figma 또는 Canva 사용)
프로젝트 설정
빠른 시작
# 새 Remotion 프로젝트 생성
npx create-video@latest my-video
# 의존성 설치
cd my-video && npm install
# Google Fonts 추가
npm install @remotion/google-fonts
# 스튜디오 시작
npm run start # http://localhost:3000 열림
파일 구조
my-video/
├── src/
│ ├── Root.tsx # 구성 정의
│ ├── Main.tsx # 메인 비디오 컴포넌트
│ └── components/ # 재사용 가능한 요소
├── package.json
├── remotion.config.ts
└── out/ # 렌더링된 비디오
구성 설정 (Root.tsx)
각 비디오에 대해 항상 여러 종횡비를 만듭니다:
import { Composition, Folder } from "remotion";
import { MyVideo } from "./MyVideo";
export const RemotionRoot = () => (
<>
<Folder name="MyVideo">
<Composition
id="MyVideo-Vertical"
component={MyVideo}
durationInFrames={600} // 30fps에서 20초
fps={30}
width={1080}
height={1920} // 9:16
/>
<Composition
id="MyVideo-Square"
component={MyVideo}
durationInFrames={600}
fps={30}
width={1080}
height={1080} // 1:1
/>
<Composition
id="MyVideo-Landscape"
component={MyVideo}
durationInFrames={600}
fps={30}
width={1920}
height={1080} // 16:9
/>
</Folder>
</>
);
시각 스타일: Skill Stack 리소그래프
색상 팔레트
const C = {
paper: "#FAF8F5", // 따뜻한 오프화이트 배경
coral: "#D4694A", // 강조, CTA, 하이라이트
teal: "#1E4D4D", // 헤더, 선택된 상태
ink: "#1C1C1C", // 본문 텍스트
muted: "#666666", // 보조 텍스트
dimmed: "#AAAAAA", // 삼차, 비활성화
white: "#FFFFFF", // 카드, 컨테이너
};
타이포그래피
import { loadFont as loadInter } from "@remotion/google-fonts/Inter";
import { loadFont as loadPlayfair } from "@remotion/google-fonts/PlayfairDisplay";
import { loadFont as loadJetBrains } from "@remotion/google-fonts/JetBrainsMono";
// 본문 텍스트, UI 레이블
const { fontFamily: inter } = loadInter("normal", {
weights: ["400", "500", "600"],
subsets: ["latin"],
});
// 헤드라인, 인용문, 감성 텍스트
const { fontFamily: playfair } = loadPlayfair("normal", {
weights: ["400", "500", "600", "700"],
subsets: ["latin"],
});
// 코드, 기술 콘텐츠, 스킬 이름
const { fontFamily: mono } = loadJetBrains("normal", {
weights: ["400", "500"],
subsets: ["latin"],
});
글꼴 크기 가이드라인
비디오의 최소 가독 가능 크기:
| 요소 | 정사각형 (1080) | 세로 (1080w) | 가로 (1920w) |
|---|---|---|---|
| 헤드라인 | 64-84px | 64-84px | 72-96px |
| 스킬 이름 | 36-40px | 36-40px | 40-48px |
| 본문 텍스트 | 28-32px | 28-32px | 32-36px |
| 레이블 | 24-28px | 24-28px | 28-32px |
| 주석 | 22-24px | 22-24px | 24-28px |
핵심 교훈: 생각보다 항상 더 크게. 비디오는 세부 사항을 압축합니다.
그레인 오버레이
const GrainOverlay: React.FC = () => {
const frame = useCurrentFrame();
return (
<AbsoluteFill
style={{
pointerEvents: "none",
opacity: 0.04,
mixBlendMode: "multiply",
}}
>
<svg width="100%" height="100%">
<defs>
<filter id="grain">
<feTurbulence
type="fractalNoise"
baseFrequency="0.8"
numOctaves="4"
seed={Math.floor(frame / 4)} // 그레인 애니메이션
/>
</filter>
</defs>
<rect width="100%" height="100%" filter="url(#grain)" />
</svg>
</AbsoluteFill>
);
};
애니메이션 패턴
핵심 도구
import {
useCurrentFrame,
useVideoConfig,
AbsoluteFill,
interpolate,
spring,
Sequence,
} from "remotion";
페이드 인/아웃
const fadeIn = interpolate(frame, [0, 30], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const fadeOut = interpolate(frame, [150, 180], [1, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
// 장면 불투명도 결합
<div style={{ opacity: fadeIn * fadeOut }}>
스프링 애니메이션
const { fps } = useVideoConfig();
// 부드러운 진입
const entrance = spring({
frame: frame - 20, // 20프레임 지연
fps,
config: { damping: 100 }, // 부드럽게, 바운스 없음
});
// 탄력적인 선택
const selectPulse = spring({
frame: frame - 15,
fps,
config: { damping: 12, stiffness: 100 }, // 바운시
});
// 변환에 적용
<div style={{
opacity: interpolate(entrance, [0, 1], [0, 1]),
transform: `translateY(${interpolate(entrance, [0, 1], [20, 0])}px)`,
}}>
타자기 효과
const ScenePrompt: React.FC<{ frame: number }> = ({ frame }) => {
const text = "여기에 프롬프트 텍스트를 입력하세요";
const charsToShow = Math.floor(frame / 2); // 30fps에서 초당 15자
const visibleText = text.slice(0, charsToShow);
const isTyping = charsToShow < text.length;
const cursorBlink = Math.floor(frame / 18) % 2 === 0;
return (
<div>
"{visibleText}
{(isTyping || cursorBlink) && (
<span style={{
backgroundColor: C.coral,
marginLeft: 4,
opacity: cursorBlink ? 1 : 0,
}}>
{"\u00A0"}
</span>
)}
{!isTyping && '"'}
</div>
);
};
계단식 목록 애니메이션
const ITEMS = ["item-1", "item-2", "item-3"];
{ITEMS.map((item, i) => {
const delay = i * 25; // 각 항목 사이 25프레임
const s = spring({
frame: Math.max(0, frame - delay - 20),
fps,
config: { damping: 100 },
});
return (
<div
key={item}
style={{
opacity: interpolate(s, [0, 1], [0, 1]),
transform: `translateY(${interpolate(s, [0, 1], [20, 0])}px)`,
}}
>
{item}
</div>
);
})}
장면 타이밍 가이드라인
지속 시간 계산
- 30fps = 초당 30프레임
- 10초 = 300프레임
- 20초 = 600프레임
페이싱 교훈
너무 빠른 것이 #1 실수입니다. 시청자는 다음을 위해 시간이 필요합니다:
- 장면 변경을 알아차리기
- 텍스트 읽기
- 의미 처리하기
- 다음 변경 준비하기
최소 장면 지속 시간:
| 장면 유형 | 최소 | 편안함 |
|---|---|---|
| 타자기 텍스트 | 3-4초 | 5-6초 |
| 목록 나타나기 | 4-5초 | 6-8초 |
| 선택/하이라이트 | 3-4초 | 5초 |
| 기술 다이어그램 | 4-5초 | 6-8초 |
| 핀치라인/CTA | 2-3초 | 3-4초 |
장면 중첩 패턴
장면은 하드 컷이 아닌 크로스 페이드되어야 합니다:
// 20초 비디오(600프레임)를 위한 장면 타이밍
{frame < 200 && <ScenePrompt frame={frame} />}
{frame >= 160 && frame < 340 && <SceneScan frame={frame - 160} />}
{frame >= 300 && frame < 460 && <SceneSelect frame={frame - 300} />}
{frame >= 420 && frame < 560 && <SceneLoad frame={frame - 420} />}
{frame >= 520 && <ScenePunchline frame={frame - 520} />}
40프레임의 중첩(1.3초)은 fadeIn/fadeOut 보간과 결합되어 부드러운 크로스 페이드를 만듭니다.
워크플로우
1단계: 스토리보드
코딩 전에 다음을 정의하세요:
- 핵심 메시지 - 시청자가 배워야 할 한 가지는 무엇인가요?
- 장면 분석 - 각각 하나의 아이디어를 가진 4-6개의 장면
- 전체 지속 시간 - 소셜용 10-20초, 설명용 30-60초
- 텍스트 콘텐츠 - 각 장면의 정확한 단어
2단계: 기본 구조 만들기
- 적절한 지속 시간으로 구성 만들기
- 플레이스홀더 콘텐츠로 장면 컴포넌트 구축
- 기본 타이밍(장면 범위) 설정
- 모든 장면이 나타나는지 테스트
3단계: 스타일링
- 색상 팔레트 및 글꼴 적용
- 그레인 오버레이 추가
- 글꼴 크기가 읽을 수 있는지 확인
- 휴대폰(세로)과 데스크탑(가로)에서 테스트
4단계: 애니메이션
- 각 장면에 fadeIn/fadeOut 추가
- 진입을 위한 스프링 애니메이션 추가
- 타이밍 미세 조정 - 거의 항상 더 느리게 해야 함
- 전체 재생을 여러 번 테스트
5단계: 렌더링
# 특정 구성 렌더링
npx remotion render MyVideo-Square out/square.mp4
# 모든 형식 렌더링
npx remotion render MyVideo-Vertical out/vertical.mp4
npx remotion render MyVideo-Square out/square.mp4
npx remotion render MyVideo-Landscape out/landscape.mp4
일반적인 실수
❌ 중앙 정렬 문제
문제: 콘텐츠가 왼쪽 상단 모서리에 나타납니다.
해결 방법: position: absolute 대신 flexbox와 함께 AbsoluteFill을 사용하세요.
// ✅ 올바름
<AbsoluteFill style={{
justifyContent: "center",
alignItems: "center",
}}>
// ❌ 틀림
<div style={{ position: "absolute", left: "50%", top: "50%" }}>
❌ 작은 글꼴
문제: 스튜디오에서는 텍스트를 읽을 수 있지만 휴대폰에서는 읽기 어렵습니다.
해결 방법: 본문 텍스트는 최소 28px, 헤드라인은 최소 64px.
❌ 너무 빠름
문제: 텍스트가 사라지기 전에 읽을 수 없습니다.
해결 방법: 초기 지속 시간 추정치를 두 배로 늘리세요. 각 장면은 최소 4-8초가 필요합니다.
❌ 하드 컷
문제: 장면 간 전환이 거칠습니다.
해결 방법: 장면을 30-50프레임 겹치고 fadeIn/fadeOut을 사용하세요.
❌ 종횡비 미지원
문제: 비디오가 다른 플랫폼에서 다르게 보입니다.
해결 방법: 처음부터 세 가지 구성(세로, 정사각형, 가로)을 모두 만드세요.
캡션 통합
Remotion은 일급 캡션 지원을 제공합니다. 비용별 세 가지 옵션:
옵션 1: 기본 Whisper (권장 - 무료)
npm install @remotion/captions @remotion/install-whisper-cpp
npx @remotion/install-whisper-cpp --model large-v3-turbo
import { transcribe } from "@remotion/captions";
const result = await transcribe({
inputPath: "/path/to/audio.mp3",
model: "large-v3-turbo",
language: "en",
});
// result.captions = [{ text: "Hello", startTime: 0, endTime: 0.5 }, ...]
옵션 2: ZapCap API ($0.10/분)
로컬 모델 설정 없이 배치 처리를 위한 옵션입니다.
옵션 3: Submagic API ($0.69/분)
미리 스타일이 지정된 "바이럴" 캡션 템플릿이지만 비용이 많이 듭니다.
전체 워크플로우 세부 정보: references/caption-workflow.md 참조
커뮤니티 리소스
디스코드
Remotion 디스코드 - 6,000명 이상의 회원. 활성화된 #help 및 #showcase 채널. 제작자(Jonny Burger)가 직접 응답합니다.
공부할 주요 템플릿
| 템플릿 | 사용 사례 | 링크 |
|---|---|---|
| TikTok | 세로형 소셜 비디오 | remotion-dev/template-tiktok |
| Audiogram | 팟캐스트 클립 | remotion-dev/template-audiogram |
| GitHub Unwrapped | 데이터 기반 비디오 | remotion-dev/github-unwrapped |
| claude-remotion-kickstart | AI 지원 워크플로우 | Claude + Remotion용 커뮤니티 스타터 |
필수 패키지
# 캡션
npm install @remotion/captions @remotion/install-whisper-cpp
# 그래픽
npm install @remotion/skia # 2D 벡터 그래픽
npm install @remotion/three # Three.js를 사용한 3D
npm install @remotion/lottie # Lottie 애니메이션
# 렌더링
npm install @remotion/lambda # AWS 서버리스 렌더링
npm install @remotion/player # 웹 앱에 임베드
전체 리소스 목록: references/community-resources.md 참조
번들 리소스
references/what-is-a-skill.tsx- "스킬이란 무엇인가" 비디오의 전체 예제 컴포넌트references/caption-workflow.md- 완전한 캡션 통합 가이드 (Whisper, ZapCap, Submagic)references/community-resources.md- 템플릿, 패키지, 학습 경로
관련 스킬
- video-caption-creation - 렌더링된 비디오용 캡션 및 해시태그
- youtube-scriptwriting - 더 긴 설명 비디오 스크립트용
- social-content-creation - 비디오 개념을 정적 게시물로 재사용
버전 기록
-
v1.1 (2026-01-22): 캡션 및 커뮤니티 통합
@remotion/captions를 통한 기본 Whisper 캡션 워크플로우 추가- 캡션 스타일 프리셋 추가 (미니멀, 볼드, 가라오케, 브랜드)
- 커뮤니티 리소스 추가 (디스코드, 템플릿, 패키지)
- 새 참조 파일:
caption-workflow.md,community-resources.md - 비용 비교: Whisper (무료) vs ZapCap ($0.10/분) vs Submagic ($0.69/분)
-
v1.0 (2026-01-21): 초기 스킬 생성
- 리소그래프 시각적 스타일
- 애니메이션 패턴 (스프링, 보간, 타자기)
- 타이밍 가이드라인 ("너무 빠름" 반복에서 배움)
- 장면 중첩 패턴
비디오는 코드입니다. 버전 관리하고, 반복하고, 재사용 가능한 컴포넌트 라이브러리를 구축하세요.