Communitygithub.com

carissadarmafissaleha/remotion

React와 Remotion을 사용한 프로그래매틱 비디오 제작을 위한 종합 마스터 가이드입니다. React, Remotion 4.x, Spring 물리 엔진, GSAP, Tailwind/CSS, CLI 하드웨어 가속 렌더링을 사용하여 16:9 익스플레이너, 9:16 세로 쇼츠/TikTok/Reels와 같은 전문 모션 그래픽 비디오를 설계, 조립, 애니메이션, 오디오 내레이션/VO 동기화 및 렌더링합니다.

remotion란 무엇인가요?

이 스킬은 React와 Remotion을 사용하여 프로그래매틱 방식으로 전문적인 모션 그래픽 비디오를 제작하는 종합 가이드입니다. 사용자는 Spring 물리 기반 애니메이션(`spring()`), 키네틱 타이포그래피, 음성 해설 동기화, 장면 전환(Series/TransitionSeries), 그리고 하드웨어 가속 CLI 렌더링을 포함한 핵심 기술을 배울 수 있습니다. Claude Code, Cursor, Codex와 같은 AI 에이전트 환경에서 실행될 수 있으며, 16:9 익스플레이너나 9:16 세로 쇼츠 등 다양한 형식을 지원합니다. 이 스킬은 Remotion 4.x를 기반으로 하며, `Composition`, `AbsoluteFill`, `Sequence`, `Series`, `spring()`, `interpolate()`, `delayRender`, `Audio`와 같은 핵심 API를 활용하여 비디오의 모든 프레임을 React 컴포넌트로 제어하는 방법을 자세히 설명합니다.

지원 대상~Claude Code~Codex CLI~Cursor✓Antigravity
npx skills add https://github.com/carissadarmafissaleha/office-nara/tree/main/skills/remotion

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

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. 장면 1: Cold Hook (00-03초) $\rightarrow$ 미스터리한 비주얼 + 큰 키네틱 타이포그래피.
  2. 장면 2: 문제 제시 (04-07초) $\rightarrow$ 무너지는 얼음 비주얼 / 불타는 붉은 그래픽.
  3. 장면 3: 심층 데이터 탐구 (08-12초) $\rightarrow$ 통계 숫자가 올라가는 카운팅 (Math.floor).
  4. 장면 4: 동적 배율 변경 (13-16초) $\rightarrow$ 매크로에서 마이크로로 규모 변화.
  5. 장면 5: 인간적 영향 (17-20초) $\rightarrow$ 캔버스에서 반응하는 만화 캐릭터.
  6. 장면 6: 해결책 공개 (21-25초) $\rightarrow$ 밝은 녹색/주황색으로 전환되는 비주얼.
  7. 장면 7: 행동 단계 (26-29초) $\rightarrow$ 3D로 떠다니는 3개의 카드 포인트.
  8. 장면 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. 문제 해결 가이드

  1. 검은 화면 / 이미지가 나타나지 않음: 에셋이 public/ 폴더에 있고 staticFile('파일이름')으로 호출되는지 확인하세요.
  2. 렌더링 중 비디오 멈춤: useMemo의 무한 루프 또는 delayRender를 닫지 않는 React 컴포넌트가 있는지 확인하세요.
  3. 오디오 동기화 불량: 컴포지션의 fps 값이 음성 해설 초 계산 (초 = 프레임 / fps)과 정확히 일치하는지 확인하세요.

관련 스킬