HyperFrames — HTML을 소스로 삼아 AI 에이전트가 직접 영상을 만드는 프레임워크

HeyGen이 만든 HyperFrames로 HTML·CSS·GSAP만으로 비디오를 작성하고 Puppeteer+FFmpeg로 렌더링하는 실전 가이드

2026-08-12 · 최초 발행 2026-04-21

HyperFrames는 HeyGen이 만든 HTML 기반 비디오 렌더링 프레임워크로, AI 에이전트가 직접 코드로 영상을 제작할 수 있도록 설계된 도구다. 기존 비디오 편집 GUI 대신 HTML, CSS, GSAP 애니메이션으로 영상을 작성하고, Puppeteer + FFmpeg가 이를 MP4로 렌더링한다. Claude Code, Cursor, Gemini CLI와 네이티브 통합을 지원해 에이전트 우선(Agent-first) 워크플로우를 구현한다.

개요

HyperFrames의 핵심 아이디어는 단순하다. HTML이 비디오의 소스가 되고, 별도의 편집 툴이나 독점 DSL 없이 data-* 속성으로 타이밍을 정의하며 GSAP으로 애니메이션을 작성한다. 이렇게 만든 HTML을 Puppeteer + FFmpeg가 프레임별로 캡처해 MP4로 인코딩하는 구조다.

기존 방식과 비교하면 차이가 뚜렷하다.

기존 방식 HyperFrames
비디오 편집 GUI 사용 HTML + CSS + JS로 작성
독점 포맷으로 재현 어려움 동일 입력 = 동일 출력 (결정론적)
자동화 파이프라인 구축 어려움 CLI 완전 지원, 에이전트 친화적
코드로 관리 불가 Git으로 버전 관리 가능

패키지는 모노레포로 구성되며 각 패키지가 역할을 나눠 갖는다.

hyperframes(모노레포)clinpx hyperframes 명령어core타입, 파서, 린터, 프레임어댑터enginePuppeteer + FFmpeg 캡처엔진producer전체 렌더링 파이프라인 조율studio브라우저 기반 라이브 프리뷰player임베드 가능한 컴포넌트shader-transitionsWebGL 전환 효과

설치 및 환경 설정

시스템 요구사항은 Node.js 22 이상, 그리고 로컬 렌더링 시 FFmpeg다.

FFmpeg는 OS별로 이렇게 설치한다.

# macOS
brew install ffmpeg

# Ubuntu / Debian
sudo apt-get install ffmpeg

# Windows (Chocolatey)
choco install ffmpeg

새 프로젝트는 init 명령 하나로 시작한다.

npx hyperframes init my-video
cd my-video

이 명령은 다음 구조를 생성한다.

my-video/
├── index.html          # 메인 컴포지션
├── assets/             # 미디어 파일 (영상, 이미지, 오디오)
└── compositions/       # 서브 컴포지션 HTML 파일

환경이 제대로 갖춰졌는지는 doctor 명령으로 확인한다.

npx hyperframes doctor

Node.js, FFmpeg, 의존성 설치 상태를 자동으로 진단한다.

핵심 개념

**컴포지션(Composition)**은 data-composition-id를 가진 <div> 요소로, 하나의 비디오 장면 또는 전체 영상에 해당한다.

<div data-composition-id="my-video" data-width="1920" data-height="1080">
  <!-- 클립들 -->
</div>

**클립(Clip)**은 컴포지션 안에 배치되는 개별 요소다. <video>, <audio>, <img>, <div> 모두 클립이 될 수 있다.

<video
  id="clip-1"
  data-start="0"
  data-duration="5"
  data-track-index="0"
  src="hero.mp4"
  muted
  playsinline
></video>
<div id="title-1" data-start="1" data-duration="4" data-track-index="1">
  안녕하세요
</div>

**타임라인(Timeline)**은 GSAP 타임라인으로 애니메이션을 정의한다. 항상 { paused: true }로 시작하고, window.__timelines에 등록해야 한다.

const tl = gsap.timeline({ paused: true });
tl.from(
  "#title-1",
  { y: 50, opacity: 0, duration: 0.6, ease: "power3.out" },
  0.3,
);
window.__timelines["my-video"] = tl;

**트랙(Track)**의 data-track-index는 클립의 레인(lane)을 지정한다. 같은 트랙 인덱스의 클립은 시간이 겹치면 안 되며, 시각적 레이어링은 CSS z-index로 별도 제어한다.

프로젝트 구조

기본 index.html은 이런 형태를 갖춘다.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      body {
        background: #000;
      }
    </style>
  </head>
  <body>
    <!-- 루트 컴포지션: <template> 없이 직접 <body>에 배치 -->
    <div data-composition-id="main" data-width="1920" data-height="1080">
      <!-- 비디오 클립 -->
      <video
        id="bg-video"
        data-start="0"
        data-duration="10"
        data-track-index="0"
        src="assets/background.mp4"
        muted
        playsinline
      ></video>

      <!-- 텍스트 오버레이 -->
      <div id="headline" data-start="1" data-duration="9" data-track-index="1">
        <div class="scene-content">
          <h1 class="title">HyperFrames로 만드는 비디오</h1>
          <p class="subtitle">HTML이 비디오의 소스입니다</p>
        </div>
      </div>

      <style>
        [data-composition-id="main"] {
          position: relative;
          width: 1920px;
          height: 1080px;
          overflow: hidden;
          font-family: "Inter", sans-serif;
        }
        .scene-content {
          display: flex;
          flex-direction: column;
          justify-content: center;
          width: 100%;
          height: 100%;
          padding: 120px 160px;
          gap: 24px;
          box-sizing: border-box;
        }
        .title {
          font-size: 96px;
          font-weight: 700;
          color: #ffffff;
          line-height: 1.1;
        }
        .subtitle {
          font-size: 40px;
          color: #a0a0a0;
        }
      </style>

      <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
      <script>
        window.__timelines = window.__timelines || {};
        const tl = gsap.timeline({ paused: true });

        tl.from(
          "#headline .title",
          { y: 60, opacity: 0, duration: 0.7, ease: "power3.out" },
          1.2,
        );
        tl.from(
          "#headline .subtitle",
          { y: 40, opacity: 0, duration: 0.5, ease: "power2.out" },
          1.5,
        );

        window.__timelines["main"] = tl;
      </script>
    </div>
  </body>
</html>

데이터 속성 레퍼런스

모든 클립에 공통으로 쓰이는 속성은 다음과 같다.

속성 필수 설명 예시
id 필수 고유 식별자 "clip-1"
data-start 필수 시작 시간(초) 또는 클립 ID 참조 "0", "clip-1", "clip-1 + 2"
data-duration img/div/컴포지션은 필수 지속 시간(초). 비디오/오디오는 미디어 길이가 기본값 "5"
data-track-index 필수 트랙 레인 번호 "0", "1", "2"
data-media-start 선택 소스 미디어의 시작 오프셋(초) "3.5"
data-volume 선택 볼륨 (0~1, 기본값 1) "0.7"

컴포지션 요소에만 붙는 속성은 별도다.

속성 필수 설명
data-composition-id 필수 고유 컴포지션 ID
data-width 필수 픽셀 너비
data-height 필수 픽셀 높이
data-composition-src 선택 외부 HTML 파일 경로 (서브 컴포지션)

data-start는 절대 시간뿐 아니라 다른 클립을 기준으로도 지정할 수 있다.

<!-- 절대 시간 -->
<div id="a" data-start="3" ...></div>

<!-- 다른 클립 ID 참조 (해당 클립이 끝나는 시점에 시작) -->
<div id="b" data-start="a" ...></div>

<!-- 오프셋 포함 참조 -->
<div id="c" data-start="a + 1.5" ...></div>
<div id="d" data-start="b - 0.5" ...></div>

컴포지션 구조 작성법

해상도는 용도에 따라 셋 중 하나를 고른다.

용도 크기
가로형 (유튜브, 발표용) 1920 × 1080
세로형 (쇼츠, 릴스, TikTok) 1080 × 1920
정방형 (인스타그램) 1080 × 1080

하나의 컴포지션 안에서 여러 씬은 시간 순서로 배치한다.

<!-- 씬 1: 0~7초 -->
<div id="scene-1" data-start="0" data-duration="7" data-track-index="1">
  <div class="scene-content">
    <h2 class="s1-title">첫 번째 씬</h2>
  </div>
</div>

<!-- 씬 2: 7~14초 -->
<div id="scene-2" data-start="7" data-duration="7" data-track-index="1">
  <div class="scene-content">
    <h2 class="s2-title">두 번째 씬</h2>
  </div>
</div>

미디어 (비디오 / 오디오)

비디오는 반드시 muted playsinline을 포함해야 한다. 오디오는 별도의 <audio> 요소로 분리한다.

<!-- 영상 (무음) -->
<video
  id="el-v"
  data-start="0"
  data-duration="30"
  data-track-index="0"
  src="assets/clip.mp4"
  muted
  playsinline
></video>

<!-- 오디오 (같은 소스 파일에서 분리) -->
<audio
  id="el-a"
  data-start="0"
  data-duration="30"
  data-track-index="2"
  src="assets/clip.mp4"
  data-volume="1"
></audio>

이렇게 분리하는 이유는 프레임워크가 미디어 재생을 완전히 제어해야 하기 때문이다. 비디오 play()/pause()를 직접 호출하는 것은 금지된다.

미디어 트리밍은 data-media-start로 소스의 시작 오프셋을 지정한다.

<!-- 소스 영상의 5초 지점부터 10초간 재생 -->
<video
  id="trimmed"
  data-start="0"
  data-duration="10"
  data-media-start="5"
  data-track-index="0"
  src="assets/long-video.mp4"
  muted
  playsinline
></video>

이미지 클립은 다음과 같이 배치한다.

<img
  id="logo"
  data-start="2"
  data-duration="6"
  data-track-index="2"
  src="assets/logo.png"
  style="position: absolute; top: 40px; right: 80px; width: 200px;"
/>

GSAP 애니메이션

기본 규칙은 다섯 가지다.

  1. 타임라인은 항상 { paused: true } — 프레임워크가 재생을 제어
  2. 반드시 window.__timelines에 등록 — 프레임워크가 이 객체를 읽음
  3. 시각적 속성만 애니메이션opacity, x, y, scale, rotation, color, backgroundColor
  4. visibility, display 애니메이션 금지
  5. repeat: -1 금지 — 무한 반복은 캡처 엔진을 망가뜨림

gsap.from()은 등장 애니메이션에 쓴다. CSS 위치가 최종 위치이고, from()으로 그 위치까지 오는 여정을 정의한다.

// 아래에서 올라오며 등장
tl.from(
  "#title",
  { y: 60, opacity: 0, duration: 0.7, ease: "power3.out" },
  0.3,
);

// 왼쪽에서 슬라이드인
tl.from("#card", { x: -80, opacity: 0, duration: 0.5, ease: "expo.out" }, 0.5);

// 확대되며 등장
tl.from(
  "#badge",
  { scale: 0.7, opacity: 0, duration: 0.4, ease: "back.out(1.7)" },
  0.6,
);

gsap.to()는 퇴장 애니메이션이며, 마지막 씬에서만 허용한다.

// 마지막 씬에서만 퇴장 허용
tl.to(
  "#final-title",
  { opacity: 0, y: -30, duration: 0.5, ease: "power2.in" },
  9.5,
);

반복 애니메이션이 필요하면 repeat: -1 대신 컴포지션 지속 시간을 기반으로 반복 횟수를 계산한다.

const compositionDuration = 15; // 초
const cycleDuration = 2; // 애니메이션 1사이클 길이
const repeatCount = Math.ceil(compositionDuration / cycleDuration) - 1;

tl.to(
  "#pulse",
  {
    scale: 1.1,
    duration: 1,
    ease: "sine.inOut",
    yoyo: true,
    repeat: repeatCount,
  },
  0,
);

여러 씬을 하나의 타임라인에 담으면 이런 구조가 된다.

<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
  window.__timelines = window.__timelines || {};

  const tl = gsap.timeline({ paused: true });

  // 씬 1 등장
  tl.from(
    ".s1-title",
    { y: 50, opacity: 0, duration: 0.7, ease: "power3.out" },
    0.3,
  );
  tl.from(
    ".s1-subtitle",
    { y: 30, opacity: 0, duration: 0.5, ease: "power2.out" },
    0.6,
  );
  tl.from(
    ".s1-stat",
    { scale: 0.8, opacity: 0, duration: 0.4, ease: "back.out(1.7)" },
    0.8,
  );

  // 씬 2 등장 (씬 1이 끝난 후)
  tl.from(
    ".s2-heading",
    { x: -60, opacity: 0, duration: 0.6, ease: "expo.out" },
    7.3,
  );
  tl.from(
    ".s2-body",
    { x: -40, opacity: 0, duration: 0.5, ease: "power2.out" },
    7.6,
  );

  window.__timelines["main"] = tl;
</script>

씬 전환 (Scene Transitions)

전환에는 3가지 절대 규칙이 있다.

  1. 모든 씬 사이에 전환 효과 — 점프컷 금지
  2. 모든 씬에 등장 애니메이션 — 요소가 갑자기 나타나는 것 금지
  3. 전환 전 퇴장 애니메이션 금지 — 전환 효과 자체가 퇴장 역할을 함

전환 전에 퇴장 애니메이션을 넣으면 전환 시점에 빈 화면이 생기는 잘못된 패턴이 된다.

// ❌ 전환 전에 퇴장 애니메이션 → 전환 시점에 빈 화면
tl.to("#s1-title", { opacity: 0, y: -40, duration: 0.4 }, 6.5);
tl.to("#s1-subtitle", { opacity: 0, duration: 0.3 }, 6.7);
// 전환이 빈 프레임에서 시작됨

올바른 방식은 등장 애니메이션만 정의하고 전환 효과가 퇴장을 대신 처리하도록 두는 것이다.

// ✅ 씬 1 등장만 정의 — 전환이 퇴장을 처리
tl.from(
  "#s1-title",
  { y: 50, opacity: 0, duration: 0.7, ease: "power3.out" },
  0.3,
);
tl.from(
  "#s1-subtitle",
  { y: 30, opacity: 0, duration: 0.5, ease: "power2.out" },
  0.6,
);
// 7.2초에 전환 효과가 씬 전체를 가져감

// ✅ 씬 2 등장 — 전환 후 시작
tl.from(
  "#s2-heading",
  { x: -40, opacity: 0, duration: 0.6, ease: "expo.out" },
  8.0,
);

크로스페이드 같은 전환 효과는 별도의 오버레이 클립으로 구현한다.

<!-- 전환 오버레이 클립 -->
<div
  id="trans-1-2"
  data-start="6.5"
  data-duration="1.5"
  data-track-index="10"
  style="position: absolute; inset: 0; background: #000; opacity: 0; z-index: 100;"
></div>
// 페이드 아웃 (씬 1 가리기)
tl.to("#trans-1-2", { opacity: 1, duration: 0.5, ease: "power2.in" }, 6.5);
// 페이드 인 (씬 2 드러내기)
tl.to("#trans-1-2", { opacity: 0, duration: 0.5, ease: "power2.out" }, 7.5);

서브 컴포지션

복잡한 프로젝트는 씬을 별도 HTML 파일로 분리할 수 있다. 서브 컴포지션은 반드시 <template> 태그로 감싸야 한다.

<!-- 서브 컴포지션은 반드시 <template> 태그로 감싸야 한다 -->
<template id="intro-template">
  <div data-composition-id="intro" data-width="1920" data-height="1080">
    <div id="intro-title" data-start="0" data-duration="8" data-track-index="0">
      <div class="scene-content">
        <h1 class="main-title">인트로 씬</h1>
      </div>
    </div>

    <style>
      [data-composition-id="intro"] {
        background: #0a0a0a;
        font-family: "Inter", sans-serif;
      }
      .scene-content {
        display: flex;
        flex-direction: column;
        justify-content: center;
        width: 100%;
        height: 100%;
        padding: 120px 160px;
        box-sizing: border-box;
      }
      .main-title {
        font-size: 120px;
        font-weight: 800;
        color: #ffffff;
      }
    </style>

    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
    <script>
      window.__timelines = window.__timelines || {};
      const tl = gsap.timeline({ paused: true });
      tl.from(
        "[data-composition-id='intro'] .main-title",
        {
          y: 80,
          opacity: 0,
          duration: 0.8,
          ease: "power3.out",
        },
        0.3,
      );
      window.__timelines["intro"] = tl;
    </script>
  </div>
</template>

루트 index.html에서는 이 서브 컴포지션을 다음과 같이 불러온다.

<div
  id="intro-clip"
  data-composition-id="intro"
  data-composition-src="compositions/intro.html"
  data-start="0"
  data-duration="8"
  data-track-index="0"
></div>

루트 컴포지션(index.html)은 <template> 없이 <body>에 직접 배치한다. <template>은 서브 컴포지션에서만 사용한다.

CLI 명령어

개발 워크플로우는 미리보기부터 시작한다.

# 새 프로젝트 생성
npx hyperframes init my-project

# 라이브 프리뷰 시작 (브라우저 자동 열림, 핫 리로드)
npx hyperframes preview

# 특정 포트 지정
npx hyperframes preview --port 3001

검증과 린팅은 렌더링 전 필수 단계다.

# 컴포지션 구조 검증 (타이밍, 속성 오류 감지)
npx hyperframes lint

# WCAG 명암비 감사 + 애니메이션 검사
npx hyperframes validate

# 명암비 검사 건너뜀 (빠른 반복 작업 시)
npx hyperframes validate --no-contrast

렌더링은 다음 명령으로 실행한다.

# MP4로 렌더링
npx hyperframes render

# 출력 파일명 지정
npx hyperframes render --output my-video.mp4

# Docker로 렌더링 (로컬 FFmpeg 불필요)
npx hyperframes render --docker

# 해상도 오버라이드
npx hyperframes render --width 1080 --height 1920

미디어 처리(자막 생성, TTS)도 CLI 안에서 해결된다.

# 오디오 파일에서 자막 생성 (Whisper 기반)
npx hyperframes transcribe assets/voiceover.mp3

# 텍스트를 음성으로 변환 (Kokoro-82M TTS)
npx hyperframes tts "안녕하세요, HyperFrames입니다." --output assets/narration.mp3

# 언어 지정
npx hyperframes tts "Hello, World." --lang en --output assets/en-narration.mp3

전환·오버레이·데이터 시각화 등 50개 넘는 컴포넌트를 카탈로그에서 바로 설치할 수 있다.

# 카탈로그에서 블록 설치 (전환, 오버레이, 데이터 시각화 등 50+종)
npx hyperframes add lower-third
npx hyperframes add progress-bar
npx hyperframes add kinetic-title

환경 진단은 언제든 다시 돌릴 수 있다.

# Node.js, FFmpeg, 의존성 상태 체크
npx hyperframes doctor

비주얼 아이덴티티

HyperFrames는 컴포지션 작성 전에 반드시 비주얼 아이덴티티를 정의하도록 요구한다. DESIGN.md 파일은 다음 구조를 갖춘다.

## Style Prompt

다크 배경에 선명한 색상 대비를 사용하는 모던 테크 스타일.
빠른 모션과 샤프한 타이포그래피가 특징.

## Colors

- `#0a0a0a` — 배경 (Primary Background)
- `#ffffff` — 주 텍스트 (Primary Text)
- `#6366f1` — 강조색 (Accent / Brand)
- `#a1a1aa` — 보조 텍스트 (Secondary Text)
- `#1e1e2e` — 카드/패널 배경 (Surface)

## Typography

- 헤드라인: `Inter` (700~900 weight)
- 본문: `Inter` (400~500 weight)

## What NOT to Do

- 밝은 배경 사용 금지
- 4개 이상의 색상 동시 사용 금지
- 12px 이하 폰트 사용 금지
- 선형 그라디언트를 전체 배경으로 사용 금지 (H.264 밴딩 발생)
- 애니메이션 없이 요소가 등장하는 것 금지

기본 제공되는 비주얼 스타일 프리셋은 8가지다.

스타일 특징
Swiss Pulse 그리드 기반, 극도로 절제된 타이포그래피
Velvet Standard 럭셔리, 깊은 다크 톤, 골드 포인트
Deconstructed 레이어드, 오버래핑, 혼돈미
Maximalist Type 타이포그래피가 주인공, 텍스트로 가득
Data Drift 데이터 시각화 중심, 테크니컬
Soft Signal 페이스텔, 부드러운 그라디언트, 따뜻함
Folk Frequency 핸드드로운 질감, 유기적 형태
Shadow Cut 필름 느와르, 하이 콘트라스트 그림자

레이아웃 작성 원칙

"레이아웃 먼저, 애니메이션 나중" 원칙

잘못된 방식은 요소를 오프스크린 위치에 두고 GSAP으로 최종 위치로 이동시키는 것이다 — 이러면 레이아웃이 추측에 의존하게 된다. 올바른 방식은 두 단계다.

  1. 요소가 가장 잘 보이는 순간(히어로 프레임)을 CSS로 먼저 정의
  2. gsap.from()으로 그 위치까지 들어오는 경로를 정의

CSS 레이아웃 템플릿은 다음처럼 패딩으로 콘텐츠를 안으로 밀어넣는 방식을 쓴다.

/* scene-content는 패딩으로 내용을 안으로 밀어넣음 */
.scene-content {
  display: flex;
  flex-direction: column;
  justify-content: center;
  width: 100%;
  height: 100%;
  padding: 120px 160px; /* 콘텐츠를 프레임 안으로 위치시킴 */
  gap: 24px;
  box-sizing: border-box;
}

하드코딩된 픽셀값과 절대 위치는 다른 해상도에서 깨지므로 금지 패턴이다.

/* ❌ 하드코딩된 픽셀값과 절대 위치 */
.scene-content {
  position: absolute;
  top: 200px;
  left: 160px;
  width: 1920px; /* 다른 해상도에서 깨짐 */
}

렌더링된 비디오에서 가독성을 보장하려면 텍스트 스타일에 최소 크기를 지켜야 한다.

요소 최소 크기
대형 헤드라인 60px 이상
본문 텍스트 20px 이상
데이터 레이블 16px 이상

품질 검사

린팅은 다음 명령으로 실행한다.

npx hyperframes lint

이 명령은 필수 data-* 속성 누락, 같은 트랙에서 클립 시간 겹침, window.__timelines 미등록, repeat: -1 사용을 검사한다.

명암비 검사(WCAG AA)는 다음처럼 실행한다.

npx hyperframes validate

5개 타임스탬프에서 스크린샷을 찍고, 텍스트 요소 뒤의 배경 픽셀을 샘플링해 명암비를 계산한다.

⚠ WCAG AA 명암비 경고 (2건):
  · .subtitle "보조 텍스트" — 2.67:1 (4.5:1 필요, t=3.2s)
  · .caption "캡션" — 3.01:1 (4.5:1 필요, t=7.8s)

기준은 일반 텍스트 4.5:1 이상, 큰 텍스트(24px+ 또는 19px+ 볼드)는 3:1 이상이다.

애니메이션 맵은 별도 스크립트로 생성한다.

node skills/hyperframes/scripts/animation-map.mjs ./my-video \
  --out ./my-video/.hyperframes/anim-map

animation-map.json에서는 각 트윈의 요약 설명과 이동 경로, ASCII 간트 차트(전체 타임라인 시각화), 스태거 간격 감지, 1초 이상 애니메이션 없는 구간(의도적인지 확인 필요), offscreen·collision·invisible 플래그를 확인할 수 있다.

AI 에이전트 통합

HyperFrames는 에이전트 우선(Agent-first) 설계로, Claude Code, Cursor, Gemini CLI와 직접 통합된다. 스킬 설치는 한 줄이면 된다.

npx skills add heygen-com/hyperframes

제공되는 스킬(슬래시 커맨드)은 다음과 같다.

스킬 역할
/hyperframes 컴포지션 작성 전체 가이드 (타이밍, GSAP 규칙, 씬 전환)
/hyperframes-cli CLI 명령어 레퍼런스
/hyperframes-registry 카탈로그 블록 검색 및 설치
/website-to-hyperframes URL → 비디오 변환 파이프라인
/gsap GSAP 애니메이션 API 패턴

에이전트 워크플로우는 자연어 요청에서 시작해 렌더링까지 이어진다.

자연어 요청스킬 로드/hyperframesDESIGN.md확인/생성HTML컴포지션 작성npx hyperframes lint검증npx hyperframes validate품질 검사npx hyperframes renderMP4 출력

실전 예제

예제 1: 간단한 텍스트 애니메이션

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <div data-composition-id="text-demo" data-width="1920" data-height="1080">
      <div
        id="bg"
        data-start="0"
        data-duration="8"
        data-track-index="0"
        style="position:absolute; inset:0; background:#0a0a0a;"
      ></div>

      <div id="content" data-start="0" data-duration="8" data-track-index="1">
        <div class="scene-content">
          <p class="eyebrow">HYPERFRAMES</p>
          <h1 class="headline">비디오의 미래는<br />HTML입니다</h1>
          <p class="body-text">
            코드로 작성하고, 에이전트가 편집하고, 파이프라인이 렌더링합니다.
          </p>
        </div>
      </div>

      <style>
        [data-composition-id="text-demo"] {
          font-family: "Inter", sans-serif;
          position: relative;
          width: 1920px;
          height: 1080px;
          overflow: hidden;
        }
        .scene-content {
          display: flex;
          flex-direction: column;
          justify-content: center;
          width: 100%;
          height: 100%;
          padding: 160px 200px;
          gap: 32px;
          box-sizing: border-box;
        }
        .eyebrow {
          font-size: 24px;
          font-weight: 600;
          letter-spacing: 0.3em;
          color: #6366f1;
          text-transform: uppercase;
        }
        .headline {
          font-size: 110px;
          font-weight: 800;
          color: #ffffff;
          line-height: 1.05;
        }
        .body-text {
          font-size: 36px;
          color: #a1a1aa;
          max-width: 900px;
        }
      </style>

      <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
      <script>
        window.__timelines = window.__timelines || {};
        const tl = gsap.timeline({ paused: true });

        tl.from(
          ".eyebrow",
          { y: 30, opacity: 0, duration: 0.5, ease: "power2.out" },
          0.4,
        );
        tl.from(
          ".headline",
          { y: 70, opacity: 0, duration: 0.8, ease: "power3.out" },
          0.7,
        );
        tl.from(
          ".body-text",
          { y: 40, opacity: 0, duration: 0.6, ease: "power2.out" },
          1.1,
        );

        // 마지막 씬: 페이드 아웃
        tl.to(
          "#content",
          { opacity: 0, duration: 0.6, ease: "power2.in" },
          7.2,
        );

        window.__timelines["text-demo"] = tl;
      </script>
    </div>
  </body>
</html>

예제 2: 비디오 + 자막 오버레이

<div data-composition-id="video-caption" data-width="1920" data-height="1080">
  <!-- 배경 영상 -->
  <video
    id="main-video"
    data-start="0"
    data-duration="20"
    data-track-index="0"
    src="assets/interview.mp4"
    muted
    playsinline
  ></video>

  <!-- 오디오 분리 -->
  <audio
    id="main-audio"
    data-start="0"
    data-duration="20"
    data-track-index="2"
    src="assets/interview.mp4"
    data-volume="1"
  ></audio>

  <!-- 로워 서드 (자막 바) -->
  <div id="lower-third" data-start="2" data-duration="5" data-track-index="3">
    <div class="lower-third-wrap">
      <div class="speaker-name">김철수</div>
      <div class="speaker-title">HyperFrames 개발팀 리드</div>
    </div>
  </div>

  <style>
    [data-composition-id="video-caption"] {
      position: relative;
      width: 1920px;
      height: 1080px;
      overflow: hidden;
      font-family: "Inter", sans-serif;
    }
    .lower-third-wrap {
      position: absolute;
      bottom: 120px;
      left: 120px;
      border-left: 6px solid #6366f1;
      padding-left: 24px;
    }
    .speaker-name {
      font-size: 52px;
      font-weight: 700;
      color: #ffffff;
    }
    .speaker-title {
      font-size: 32px;
      font-weight: 400;
      color: #a1a1aa;
      margin-top: 8px;
    }
  </style>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
  <script>
    window.__timelines = window.__timelines || {};
    const tl = gsap.timeline({ paused: true });

    // 로워 서드 등장
    tl.from(
      "#lower-third .lower-third-wrap",
      { x: -60, opacity: 0, duration: 0.5, ease: "power3.out" },
      2.2,
    );

    // 로워 서드 퇴장 (마지막 요소이므로 허용)
    tl.to(
      "#lower-third .lower-third-wrap",
      { x: -60, opacity: 0, duration: 0.4, ease: "power2.in" },
      6.5,
    );

    window.__timelines["video-caption"] = tl;
  </script>
</div>

자주 하는 실수 (금지 사항)

실수 올바른 방법
window.__timelines 등록 안 함 반드시 window.__timelines["id"] = tl 등록
비디오에서 오디오 사용 muted 비디오 + 별도 <audio> 요소 분리
data-layer 속성 사용 data-track-index 사용
data-end 속성 사용 data-duration 사용
repeat: -1 사용 Math.ceil(duration / cycle) - 1 계산
setTimeout 안에서 타임라인 구성 동기적으로 타임라인 빌드
video.play() / audio.play() 직접 호출 프레임워크에 재생 제어를 맡김
Math.random() 사용 Mulberry32 등 시드 기반 PRNG 사용
전환 전 퇴장 애니메이션 전환이 퇴장을 처리 — 퇴장 트윈 제거
루트 컴포지션에 <template> 사용 <body>에 직접 배치
텍스트에 <br> 강제 줄바꿈 max-width로 자연스러운 줄바꿈
.scene-contentposition: absolute width: 100%; height: 100%; padding: 사용
비디오 요소 치수 애니메이션 래퍼 <div> 애니메이션
후속 씬 요소에 gsap.set() 사용 타임라인 내 tl.set(selector, vars, time) 사용

빠른 참고 카드

# 프로젝트 생성
npx hyperframes init my-video && cd my-video

# 개발
npx hyperframes preview

# 검증
npx hyperframes lint && npx hyperframes validate

# 렌더링
npx hyperframes render --output output.mp4

# AI 스킬 설치
npx skills add heygen-com/hyperframes
// 타임라인 필수 패턴
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
// ... 트윈 추가 ...
window.__timelines["composition-id"] = tl;

마무리

HyperFrames는 HTML을 비디오의 소스로 삼는 독창적인 접근으로, AI 에이전트가 코드로 직접 영상을 제작하는 워크플로우를 현실화한다. data-* 속성 기반의 타임라인 정의, GSAP 애니메이션, Puppeteer + FFmpeg 렌더링 파이프라인이 결합해 결정론적이고 버전 관리 가능한 비디오 제작 환경을 제공한다. 에이전트 친화적 CLI와 슬래시 커맨드 스킬을 활용하면 자연어 요청에서 MP4 출력까지 전 과정을 자동화할 수 있어, 반복적인 영상 콘텐츠 제작 파이프라인 구축에 적합한 프레임워크다.

HyperFramesGSAPPuppeteerFFmpeg에이전트 친화 도구