HyperFrames 탐구 1 — URL 하나가 MP4가 되기까지, 영화 제작소의 7단계
OpenCut 시리즈에 이어 두 번째 오픈소스 탐구입니다. 이번 대상은 HyperFrames — 유저가 /website-to-hyperframes <url>이라고 치면 그 웹사이트의 홍보 영상 MP4가 나오는 도구입니다. OpenCut이 "사람이 손으로 편집하는 브라우저 비디오 에디터"라면, HyperFrames는 **"AI 에이전트가 영상을 만드는 파이프라인"**입니다.
이 시스템은 영화 제작소에 빗대면 정확히 이해됩니다. 로케이션 헌팅을 하고, 대본과 콘티를 쓰고, 성우를 녹음하고, 촬영하고, 시사회를 거쳐 최종본을 뽑는 — 그 순서 그대로입니다. 이번 편에서는 URL 입력부터 MP4가 나오기까지를 단계별로 추적합니다.
시작: 스킬이 파이프라인을 가르친다
유저가 URL을 주는 순간, Claude Code의 스킬 시스템이 발동합니다.
.claude/skills/website-to-hyperframes/
├── SKILL.md # 스킬의 entry point (트리거 + 7단계 메소드)
└── references/
├── step-1-capture.md # 각 단계 상세 가이드
├── step-2-design.md
└── ...
SKILL.md의 frontmatter가 트리거("capture", "URL", "video" 등)를 정의하고, 발동되면 본문의 7단계 메소드가 컨텍스트에 주입됩니다. 각 단계의 상세 가이드는 해당 단계에 도달했을 때 Read 도구로 lazy-load합니다. 컨텍스트 부담 없이 깊은 가이드를 제공하는 패턴이죠. 에이전트 연동의 속사정은 2편에서 자세히 다룹니다.
1단계: 캡처 — 로케이션 헌팅
npx hyperframes capture <URL>이 실행되면 Puppeteer가 헤드리스 Chrome(1920×1080)을 띄워 사이트를 측정합니다.
- URL 방문 →
networkidle대기 - JS 인젝션으로 데이터 추출 —
document.fonts에서 폰트 목록,getComputedStyle()로 상위 컬러(tokens.json), 헤딩·문단 텍스트(visible-text.txt), 이미지·SVG·비디오 메타데이터(assets-catalog.json) - 스크롤하며 뷰포트 단위 스크린샷 ~20장 (30% 오버랩)
- 이미지·폰트 에셋 다운로드
- (옵션) Gemini Vision API로 각 에셋 한 줄 설명 생성
AGENTS.md+CLAUDE.md자동 생성 — 다음 단계의 에이전트가 읽을 요약 인덱스
capture/
├── screenshots/ (스크롤 PNG)
├── assets/ (이미지·SVG·폰트)
├── extracted/ (tokens.json, visible-text.txt, ...)
├── AGENTS.md (에이전트용 요약)
└── CLAUDE.md (Claude용 요약)
마지막 항목이 이 프로젝트다운 부분입니다. 캡처 결과물에 **"이걸 읽을 에이전트를 위한 안내문"**까지 함께 생성합니다.
2~4단계: 문서 — 의사결정을 압축한다
여기서는 CLI가 돌지 않습니다. 순수하게 Claude가 캡처 데이터를 읽고 세 개의 마크다운을 씁니다. 영화로 치면 제작 바이블, 대본, 콘티입니다.
| 파일 | 역할 | 입력 | 출력 |
|---|---|---|---|
DESIGN.md |
브랜드 치트시트 | tokens.json + 스크린샷 |
6섹션 (~90줄) |
SCRIPT.md |
내레이션 대본 | visible-text.txt + DESIGN.md |
비트(Beat)별 한 줄씩 |
STORYBOARD.md |
비트별 연출 노트 | DESIGN.md + SCRIPT.md + 에셋 |
비트별 visual/animation/transition/SFX |
이 세 문서가 이후 모든 단계의 single source of truth가 됩니다. 즉흥 연출은 없습니다 — 색과 폰트는 DESIGN.md에서, 타이밍은 SCRIPT.md에서, 장면 구성은 STORYBOARD.md에서 나옵니다.
5단계: 음성 — 타이밍을 측정한다
hyperframes tts narration.txt -v af_nova -o narration.wav
hyperframes transcribe narration.wav
TTS는 Node.js가 Python 프로세스를 스폰해 kokoro-onnx(~80MB 모델, ONNX Runtime CPU 추론)로 24kHz WAV를 만들고, transcribe는 FFmpeg로 16kHz mono 변환 후 whisper.cpp(small.en)가 단어별 [start, end] 타임스탬프를 뽑아 transcript.json으로 저장합니다.
이 단계의 진짜 의미는 **"비트별 길이가 명확한 숫자로 확정된다"**는 것입니다. "Beat 1은 02.7초, Beat 2는 2.76.3초" — 이후 모든 애니메이션이 이 숫자에 맞춰 코딩됩니다. 음성을 먼저 만들고 영상을 거기에 맞추는, 순서가 뒤집힌 제작 방식이죠.
6단계: 컴포지션 — HTML이 곧 비디오 소스코드
Claude가 index.html과 compositions/beat-*.html을 작성합니다.
<!-- 루트 index.html -->
<audio id="narration" data-start="0" data-duration="22"></audio>
<div id="beat-1" data-composition-src="compositions/beat-1.html"
data-start="0" data-duration="2.69"></div>
<div id="beat-2" data-composition-src="compositions/beat-2.html"
data-start="2.7" data-duration="3.59"></div>
<script>
window.__timelines["root"] = gsap.timeline({ paused: true });
</script>
data-start, data-duration 속성이 타임라인이고, 각 비트 HTML은 자신의 <style> + <script> + GSAP 타임라인을 <template> 안에 등록합니다. 별도의 프로젝트 파일 포맷이 없습니다. HTML 자체가 영상의 데이터 모델입니다.
7단계: 검증 — 시사회는 싼 것부터
렌더링은 비쌉니다(5~10분). 그래서 검증은 가장 가벼운 것부터 단계적으로 돕니다.
| 도구 | 소요 | 하는 일 |
|---|---|---|
lint |
1초 | 정적 분석 — GSAP·CSS transform 충돌, 무한 repeat 등 100여 개 규칙 |
validate |
30초 | 헤드리스 Chrome에서 5개 시점 seek — 콘솔 에러, 404/CORS, 텍스트 배경 픽셀 샘플링으로 WCAG 대비 계산 |
snapshot |
1분 | 각 비트 중간 지점을 PNG로 캡처 — 육안 검사용 |
preview |
즉시 | Vite 서버 + <hyperframes-player> 웹 컴포넌트로 브라우저에서 재생/스크럽 |
render |
5~10분 | 최종 승인 후에만 실행 |
8단계: 렌더링 — 결정적 캡처 루프
hyperframes render의 심장부는 이 루프입니다.
for (let frame = 0; frame < fps * duration; frame++) {
const t = frame / fps; // 30fps × 22s = 660 프레임
masterTl.seek(t); // GSAP 타임라인을 t로 스크럽
await renderAllPendingPaints(); // 페인트 완료 대기
await videoElements.forEach(seekToMediaTime); // <video>도 currentTime 강제 설정
const png = await page.screenshot({ type: 'png' });
}
시간이 흐르는 것을 기다리는 게 아니라, 타임라인을 프레임 단위로 강제로 옮기며 한 장씩 찍습니다. 그다음 FFmpeg가 오디오 트랙들(data-start/data-volume 적용해 믹스)과 프레임들을 합쳐 MP4로 인코딩합니다.
이게 성립하려면 결정성(determinism) — 같은 HTML + 같은 시각 t → 반드시 같은 픽셀 — 이 필요합니다. 그래서 Date.now()와 Math.random()은 금지, GSAP 타임라인은 paused: true 필수, 비디오는 직접 .play() 금지(프레임워크가 매 프레임 currentTime을 설정)라는 규칙이 강제됩니다. 이 결정성 이야기는 3편(테스트)과 4편(렌더 엔진)의 주인공이 됩니다.
전체 구조와 제작 철학
USER: /website-to-hyperframes <url>
↓
[준비 — 전체 시간의 60%]
① CAPTURE "사이트를 측정한다" (Puppeteer)
② DOCUMENTS "의사결정을 압축한다" (DESIGN/SCRIPT/STORYBOARD.md)
③ VOICE "타이밍을 측정한다" (Kokoro TTS + whisper.cpp)
↓
[실행 — 30%]
④ COMPOSITION "실제로 만든다" (HTML + GSAP)
↓
[검증 — 10%]
⑤ VALIDATE & DELIVER (lint → validate → snapshot → preview → render)
↓
🎬 renders/output.mp4
패키지 구조도 이 흐름을 그대로 반영합니다 — capture(측정), core(런타임·타임라인 합성), engine(헤드리스 Chrome 캡처), producer(렌더 파이프라인), player/studio(미리보기), shader-transitions(WebGL 트랜지션).
철학은 한 문장입니다. "준비를 길게 한 만큼, 실행이 짧고 정확해진다." 전체 시간의 60%를 측정과 문서화에 쓰는 이유는, 가장 비싼 작업(렌더링과 재작업)을 가장 싸게 만들기 위해서입니다.
더 깊이: "브라우저를 렌더 팜으로" 계보 — Remotion과의 비교
"헤드리스 브라우저로 프레임을 찍어 FFmpeg로 인코딩한다"는 아이디어 자체는 HyperFrames의 발명이 아닙니다. Remotion이 같은 구조로 유명하죠 — React 컴포넌트가 useCurrentFrame()으로 현재 프레임을 받아 화면을 그리면, 렌더러가 프레임을 순회하며 캡처합니다.
차이는 누가 쓰라고 만든 도구인가에 있습니다.
- Remotion — 개발자가 React로 영상을 프로그래밍하는 도구. 컴포넌트, props, TypeScript가 인터페이스
- HyperFrames — AI 에이전트가 순수 HTML +
data-*속성으로 영상을 작성하는 도구. 마크다운 문서와 검증 CLI가 인터페이스
React 추상화 없이 순수 HTML을 고집한 것, 그리고 파이프라인의 60%를 "에이전트가 읽을 문서 만들기"에 배정한 것 — 이 선택들은 전부 "저자가 사람이 아니라 LLM"이라는 전제에서 나옵니다. 같은 렌더링 기술 위에서 인터페이스 설계가 사용자에 따라 어떻게 갈라지는지 보여주는 좋은 대조입니다.
한 줄 요약 — HyperFrames는 "측정(60%) → 코딩(30%) → 검증(10%)"의 영화 제작 파이프라인으로, HTML을 비디오 소스코드 삼아 헤드리스 Chrome이 결정적으로 매 프레임을 렌더하고 FFmpeg가 MP4로 인코딩하는 시스템이다.
스스로 점검할 질문
- 음성(TTS)을 영상보다 먼저 만드는 순서가 왜 합리적인지 설명할 수 있나요?
- 검증 단계가 lint(1초) → validate(30초) → snapshot(1분) → render(5~10분) 순인 이유는 무엇인가요?
Date.now()와Math.random()이 금지되는 이유를 "결정성" 관점에서 설명할 수 있나요?
참고 링크