Remotion: React 코드로 영상 만들기

Remotion: React 코드로 영상 만들기

영상 편집이라고 하면 보통 프리미어 프로나 파이널 컷 같은 전문 편집 도구를 떠올리게 되는데요. 만약 React 컴포넌트를 작성하듯이 코드로 영상을 만들 수 있다면 어떨까요?

Remotion은 바로 그런 발상에서 출발한 프레임워크입니다. React와 TypeScript로 영상의 모든 요소를 코드로 제어하고 최종적으로 MP4 파일로 렌더링할 수 있게 해주죠. GitHub 스타 38,000개에 월간 설치 수 90만 건을 넘길 정도로 개발자들 사이에서 관심을 많이 받고 있습니다.

이번 포스팅에서는 Remotion의 핵심 개념부터 실전 활용법까지 차근차근 살펴보겠습니다.

영상은 시간의 함수

Remotion의 기본 아이디어는 간단합니다. 영상은 시간에 따른 이미지의 연속이라는 거예요.

우리가 매일 보는 영상은 결국 초당 30장 혹은 60장의 이미지가 빠르게 넘어가면서 움직이는 것처럼 보이는 것일 뿐입니다. Remotion은 이 원리를 그대로 활용해서 매 프레임마다 React 컴포넌트를 렌더링하고 이를 이어붙여 영상을 만듭니다.

모든 Remotion 영상에는 네 가지 필수 속성이 있습니다.

<Composition
  id="MyVideo"
  component={MyVideo}
  width={1920}
  height={1080}
  durationInFrames={300}
  fps={30}
/>

widthheight는 영상의 해상도를, fps는 초당 프레임 수를, durationInFrames는 전체 프레임 수를 나타냅니다. 위 설정이라면 1920x1080 해상도에 30fps로 10초짜리(300 / 30 = 10) 영상이 되겠네요.

프로젝트 시작하기

Remotion 프로젝트를 만드는 건 아주 간단합니다.

bun create video

이 명령어를 실행하면 템플릿 선택 화면이 나오는데, 기본(Hello World) 템플릿을 선택하면 됩니다. 프로젝트가 생성되면 바로 개발 서버를 띄울 수 있습니다.

bun run dev

브라우저에서 Remotion Studio가 열리면서 영상을 실시간으로 미리 보고 편집할 수 있는 환경이 갖춰집니다. 타임라인 UI에서 재생 헤드를 이동하거나 각 컴포지션을 선택해서 확인할 수 있어요.

useCurrentFrame과 useVideoConfig

Remotion에서 가장 중요한 훅 두 가지가 있는데요. useCurrentFrame()은 현재 프레임 번호를 반환하고 useVideoConfig()는 영상의 설정 정보를 가져옵니다.

import { useCurrentFrame, useVideoConfig } from "remotion";

const MyVideo = () => {
  const frame = useCurrentFrame();
  const { fps, durationInFrames, width, height } = useVideoConfig();

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        fontSize: 60,
        width: "100%",
        height: "100%",
        backgroundColor: "white",
      }}
    >
      현재 프레임: {frame} / {durationInFrames}
    </div>
  );
};

프레임 번호는 0부터 시작해서 durationInFrames - 1까지 증가합니다. 이 프레임 값을 기반으로 화면에 표시할 내용을 결정하면 자연스럽게 시간에 따라 변화하는 영상이 만들어집니다.

interpolate()로 애니메이션 만들기

프레임 번호를 직접 계산해서 스타일 값을 지정할 수도 있지만 Remotion이 제공하는 interpolate() 함수를 쓰면 훨씬 편합니다.

interpolate()는 입력 범위의 값을 출력 범위의 값으로 매핑해주는 함수입니다.

import { useCurrentFrame, interpolate } from "remotion";

const FadeIn = () => {
  const frame = useCurrentFrame();

  const opacity = interpolate(frame, [0, 30], [0, 1], {
    extrapolateRight: "clamp",
  });

  return (
    <div
      style={{
        opacity,
        fontSize: 80,
        textAlign: "center",
        paddingTop: 200,
      }}
    >
      안녕하세요! 👋
    </div>
  );
};

프레임 0에서 30까지 opacity가 0에서 1로 서서히 변합니다. extrapolateRight: "clamp" 옵션은 프레임 30 이후에도 값이 1을 넘지 않도록 고정시켜주는 역할을 합니다.

위치 이동도 같은 방식으로 처리할 수 있어요.

const SlideIn = () => {
  const frame = useCurrentFrame();

  const translateY = interpolate(frame, [0, 30], [50, 0], {
    extrapolateRight: "clamp",
  });

  const opacity = interpolate(frame, [0, 30], [0, 1], {
    extrapolateRight: "clamp",
  });

  return (
    <div
      style={{
        transform: `translateY(${translateY}px)`,
        opacity,
        fontSize: 60,
      }}
    >
      아래에서 위로 올라옵니다
    </div>
  );
};

이렇게 interpolate()를 여러 번 사용해서 투명도와 위치를 동시에 변화시키면 꽤 자연스러운 등장 애니메이션이 됩니다.

spring()으로 자연스러운 움직임

interpolate()가 선형적인 변화를 만든다면 spring()은 물리 기반의 탄성 있는 움직임을 만들어줍니다. 스프링 애니메이션은 0에서 1까지 변하면서 자연스러운 오버슈트(살짝 넘어갔다 돌아오는 효과)를 포함합니다.

import { useCurrentFrame, useVideoConfig, spring } from "remotion";

const BounceIn = () => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  const scale = spring({
    fps,
    frame,
    config: {
      damping: 10,
      stiffness: 100,
    },
  });

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        height: "100%",
      }}
    >
      <div
        style={{
          transform: `scale(${scale})`,
          fontSize: 100,
        }}
      >
        🚀
      </div>
    </div>
  );
};

damping은 감쇠력으로 높을수록 빨리 멈추고, stiffness는 탄성으로 높을수록 빠르게 움직입니다. 버튼이 톡 튀어나오거나 로고가 등장하는 장면에 사용하면 딱이죠.

한 가지 꼭 기억해야 할 점이 있는데요. 항상 useCurrentFrame()을 통해 애니메이션을 처리해야 합니다. CSS transition이나 requestAnimationFrame 같은 일반적인 애니메이션 방식을 쓰면 렌더링할 때 프레임이 깜빡이는 문제가 생길 수 있어요.

Sequence로 타임라인 구성

실제 영상은 여러 장면이 순서대로 이어져 있는데요. Remotion의 <Sequence> 컴포넌트를 사용하면 이런 타임라인 구성을 깔끔하게 할 수 있습니다.

import { Sequence, AbsoluteFill } from "remotion";

const MyTrailer = () => {
  return (
    <AbsoluteFill>
      <Sequence durationInFrames={60}>
        <Intro />
      </Sequence>
      <Sequence from={60} durationInFrames={120}>
        <MainContent />
      </Sequence>
      <Sequence from={180}>
        <Outro />
      </Sequence>
    </AbsoluteFill>
  );
};

from은 해당 시퀀스가 시작되는 프레임을, durationInFrames는 표시되는 기간을 지정합니다. 여기서 아주 편리한 점은 각 시퀀스 안의 자식 컴포넌트가 useCurrentFrame()을 호출하면 시퀀스 시작 시점을 기준으로 0부터 시작하는 프레임 값을 받는다는 거예요.

그래서 <MainContent /> 컴포넌트는 자기가 전체 영상의 어디에 배치되었는지 신경 쓸 필요가 없습니다. 항상 0부터 시작하는 프레임으로 동작하니까 재사용하기도 편하죠.

시퀀스를 중첩해서 사용할 수도 있고 from에 음수 값을 주면 앞부분을 잘라낼 수도 있습니다. Remotion Studio에서는 각 시퀀스가 타임라인에 name 속성으로 표시되어서 복잡한 영상도 시각적으로 관리할 수 있어요.

오디오와 멀티미디어

영상에 오디오를 추가하는 것도 React 컴포넌트를 사용하면 됩니다.

import { AbsoluteFill, Html5Audio, staticFile, interpolate } from "remotion";

const WithMusic = () => {
  return (
    <AbsoluteFill>
      <Html5Audio
        src={staticFile("background-music.mp3")}
        volume={(f) =>
          interpolate(f, [0, 30], [0, 0.5], {
            extrapolateRight: "clamp",
          })
        }
      />
      <MyVideoContent />
    </AbsoluteFill>
  );
};

volume 속성에 프레임별 볼륨을 계산하는 함수를 넘기면 페이드인/페이드아웃 효과를 쉽게 구현할 수 있습니다. playbackRate로 재생 속도를 조절하거나 loop 속성으로 반복 재생도 가능하고요.

Player로 웹에 임베드

Remotion으로 만든 영상을 꼭 MP4로 내보내야만 하는 건 아닙니다. @remotion/player 패키지의 <Player> 컴포넌트를 사용하면 일반 React 앱에서 바로 재생할 수 있습니다.

import { Player } from "@remotion/player";
import { MyVideo } from "./remotion/MyVideo";

const App = () => {
  return (
    <Player
      component={MyVideo}
      durationInFrames={300}
      compositionWidth={1920}
      compositionHeight={1080}
      fps={30}
      controls
      loop
      style={{ width: "100%" }}
    />
  );
};

Next.js나 Vite 같은 기존 React 프로젝트에 바로 통합할 수 있어서 사용자에게 인터랙티브한 영상 경험을 제공하기 좋습니다. ref를 통해 play(), pause(), seekTo() 같은 메서드로 재생을 제어할 수도 있고 play, pause, ended 같은 이벤트 구독도 됩니다.

데이터 기반 영상 생성

Remotion이 정말 빛나는 부분은 데이터를 기반으로 영상을 자동 생성할 수 있다는 점입니다. React 컴포넌트에 props를 전달하듯이 영상에도 파라미터를 넘길 수 있거든요.

type VideoProps = {
  userName: string;
  stats: {
    commits: number;
    pullRequests: number;
    reviews: number;
  };
};

const YearInReview: React.FC<VideoProps> = ({ userName, stats }) => {
  const frame = useCurrentFrame();

  return (
    <AbsoluteFill style={{ backgroundColor: "#0d1117" }}>
      <Sequence durationInFrames={90}>
        <Title text={`${userName}의 2025년 회고`} />
      </Sequence>
      <Sequence from={90} durationInFrames={120}>
        <StatsAnimation stats={stats} />
      </Sequence>
      <Sequence from={210}>
        <Outro />
      </Sequence>
    </AbsoluteFill>
  );
};

GitHub Wrapped 같은 연말 회고 영상이나 사용자마다 다른 데이터를 보여주는 개인화 영상을 대량으로 만들어낼 수 있죠. API에서 데이터를 가져와서 수천 개의 맞춤 영상을 자동으로 생성하는 식의 워크플로우도 가능합니다.

렌더링과 내보내기

영상이 완성되면 CLI로 간단하게 렌더링할 수 있습니다.

bunx remotion render MyVideo out/video.mp4

컴포지션 이름을 지정하지 않으면 선택 화면이 나타납니다. MP4 외에도 다양한 출력 형식을 지원합니다.

  • GIF 형식으로 내보내기
  • 오디오만 추출하기
  • --sequence 플래그로 이미지 시퀀스 만들기
  • 투명 배경 영상 만들기

대규모 렌더링이 필요하다면 Remotion Lambda를 활용할 수 있습니다. AWS Lambda 위에서 영상을 여러 청크로 나눠 병렬로 처리하기 때문에 렌더링 속도가 훨씬 빨라져요.

작동 방식을 간단히 살펴볼게요. 먼저 Remotion 프로젝트를 S3 버킷에 배포합니다. 그러면 Lambda 함수가 여러 워커를 동시에 띄워서 각각 영상의 일부분을 렌더링하고, 모든 조각이 완성되면 하나로 합쳐서 최종 영상을 S3에 저장합니다. 사용한 만큼만 비용을 내면 되니 가성비도 괜찮고요.

실전 활용 사례

Remotion이 특히 잘 맞는 시나리오를 몇 가지 소개할게요.

먼저 음악 시각화 영상입니다. 오디오 파형 데이터를 분석해서 비트에 맞춰 그래픽 요소를 움직이는 뮤직 비주얼라이저를 코드로 만들 수 있어요.

자막 영상 제작에도 쓸모가 많습니다. Remotion Recorder라는 도구를 사용하면 Whisper.cpp 기반으로 자동 자막을 생성하고 단어 단위 타이밍까지 세밀하게 조절할 수 있습니다. 트위터, 유튜브, 틱톡 같은 플랫폼별 비율에 맞춰 영상 포맷도 자동으로 조정해주고요.

스크린캐스트 제작도 가능합니다. 화면 녹화 위에 애니메이션 텍스트나 하이라이트를 코드로 추가해서 깔끔한 튜토리얼 영상을 만들 수 있죠.

그리고 앞서 살펴본 것처럼 데이터를 기반으로 수백, 수천 개의 맞춤형 영상을 자동 생성하는 시나리오에서 Remotion은 다른 도구로는 쉽게 대체하기 어렵습니다.

라이선스

Remotion은 오픈소스이긴 하지만 상업적으로 사용할 때는 라이선스 구매가 필요합니다.

개인이나 소규모 팀(직원 3명 이하)은 무료로 쓸 수 있고 교육 목적이나 비영리 프로젝트도 무료입니다. 그 이상 규모에서 상업적으로 쓰려면 회사 라이선스가 필요한데 사용량 기반 가격 정책을 따르고 있어요. 엔터프라이즈 플랜은 월 $500부터 시작합니다.

자세한 내용은 Remotion 라이선스 페이지에서 확인할 수 있어요.

마치며

Remotion은 영상 제작이라는 작업을 React 개발자에게 익숙한 방식으로 풀어낸 프레임워크입니다.

useCurrentFrame()interpolate()로 프레임 단위 애니메이션을 만들고 <Sequence>로 타임라인을 구성하며 props를 통해 데이터 기반의 동적 영상을 생성할 수 있어요. 거기에 <Player>로 웹 임베딩, Remotion Lambda로 대규모 서버사이드 렌더링까지 지원하니 영상이 필요한 거의 모든 시나리오에 대응이 가능합니다.

데이터 기반으로 영상을 대량 생산해야 하거나 기존 React 프로젝트에 영상 기능을 통합해야 하는 상황이라면 Remotion을 한 번 시도해보세요.

터미널에서 FFmpeg을 직접 사용하여 영상을 변환하고 편집하는 방법이 궁금하다면 FFmpeg 사용법을 참고해보세요. Remotion에 대해서 더 궁금하신 분들은 공식 문서를 참고하시면 됩니다.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord