vid-sizzle-reel
브랜드 자산과 핵심 메시지로부터 고에너지 스리즈 릴 또는 하이프 비디오를 생성합니다. 파이프라인: HyperFrames HTML 구성(GSAP 타임라인) → headless Chromium → FFmpeg H.264 MP4. Runway, Pika, AI 비디오 API 불필요. Node.js + FFmpeg 외 추가 비용 없음.
vid-product-launch와의 차별점:
vid-product-launch는 서사를 전달합니다 — 하나의 설계된 공개, 다섯 개 섹션, 긴장감 호vid-sizzle-reel는 에너지와 흥분을 만듭니다 — 빠른 컷, 음악 우선, 단일 스토리 비트 없음
중요 규칙 (모든 생성 전에 읽을 것)
-
핵심 메시지를 직접 작성하거나 사용자에게 작성을 요청하세요. 이는 화면에 표시될 정확한 문구입니다. "AI가 알아서 할 것이다"라고 하면 빈 내용이 생성됩니다. 3-5개의 날카로운 문장이 스리즈 릴의 전체 내용입니다.
-
브랜드명은 오직 Land 섹션에만 나타납니다. Cold Open과 Build는 브랜드를 언급하지 않고 긴장감을 조성합니다. Land 섹션이 그 보상입니다.
-
HTML을 작성하기 전에 스크립트 상단에 리듬 패턴을 주석으로 선언하세요. 형식:
// RHYTHM: flash-sequence | Cold[0-5s]: stat | Build[5-40s]: msg(1.5s)|flash|... | Peak[40-55s]: ... | Land[55-60s]: logo+CTA. 모든 타이밍 결정은 이를 기준으로 확인합니다. -
먼저 CSS로 종료 상태 레이아웃을 구축하세요 — GSAP는 아직 사용하지 마세요. 모든 요소를 정적 CSS에서 가장 잘 보이는 위치에 배치하세요. 그런 다음
gsap.from()으로 GSAP 진입을,gsap.to()로 종료를 추가하세요. 애니메이션 시작 상태에 배치하지 마세요. -
모든 시간 기반 요소에
class="clip"을 추가하세요. HyperFrames에 필수입니다. 없으면 플레이어에서 요소가 보이지 않습니다. -
모든 클립에
data-start,data-duration,data-track-index를 추가하세요. 예외 없음. 동일 트랙 클립은 겹칠 수 없습니다 — 다른 트랙 인덱스를 사용하세요. -
Math.random()사용 금지. HyperFrames는 결정론적 구성을 요구합니다. 의사 난수 값에는 시드된 PRNG(mulberry32)를 사용하세요. -
모든 GSAP 타임라인은
{ paused: true }로 시작하고window.__timelines["comp-id"]에 등록됩니다. HyperFrames 플레이어가 재생을 제어합니다 —tl.play()를 호출하지 마세요. -
.scene-content컨테이너에position: absolute를 절대 사용하지 마세요.width:100%; height:100%; padding:Npx; display:flex를 사용하세요.position:absolute는 장식 요소에만 예약하세요. -
음악:
data-track-index가 있는 별도의<audio>요소를 사용하세요. 비디오는muted playsinline이어야 합니다. 절대<video>요소에 오디오를 넣지 마세요. -
HTML을 생성하기 전에
references/cut-patterns.md와references/tone-presets.md를 모두 읽으세요. -
HTML을 채팅에 덤프하지 마세요. 파일로 저장하세요. 요약만 표시하세요.
1단계: 입력 수집
필수:
key_messages— 화면에 표시할 3-5개의 강력한 문장 (사용자가 직접 작성해야 함)
선택적 매개변수 및 기본값:
| 매개변수 | 기본값 | 설명 |
|---|---|---|
| brand_assets | 없음 | 로고 URL/경로, 브랜드 색상(16진수), 주요 스크린샷 |
| tone | energetic | energetic / cinematic / emotional / professional |
| music | 없음 | 파일 경로 또는 BPM/장르 문자열 (예: "128bpm electronic") |
| duration | 60 | 30 / 60 / 90초 |
| aspect_ratio | 9:16 | 9:16 (1080x1920) / 16:9 (1920x1080) |
| cut_style | auto | fast (1-2s) / cinematic (3-5s) — auto는 tone에서 파생 |
| end_card | auto | Land 섹션의 로고 + 태그라인 + CTA URL |
cut_style 자동 기본값: energetic → fast; professional → fast (2s); cinematic → cinematic; emotional → mixed
key_messages가 없으면 정확히 다음과 같이 질문하세요:
"스리즈 릴을 생성하려면 핵심 메시지가 필요합니다 — 화면에 표시될 정확한 문구입니다. 3-5개의 강력한 문장을 알려주세요. 이 비디오를 위해 작성하는 가장 중요한 내용입니다.
예시: '500개 이상의 성장 팀이 사용 중' / '며칠에서 몇 분으로' / 'Claude, Codex, Gemini와 함께 작동'
또한 유용한 정보: 톤 (energetic/cinematic/emotional/professional), 길이 (30/60/90초), 브랜드 색상이나 로고 URL."
key_messages가 있으면 즉시 2단계로 진행하세요.
2단계: HyperFrames 설치
HyperFrames 스킬 설치 (처음 한 번만 — 이미 설치된 경우 건너뛰기):
npx skills add heygen-com/hyperframes
환경 확인:
node --version # >= 22 이어야 함
ffmpeg -version # 있어야 함
프로젝트 스캐폴딩:
npx hyperframes init sizzle-[slug] --example kinetic-type --non-interactive
cd sizzle-[slug]
슬러그: end_card 브랜드명 또는 첫 번째 key_message의 케밥-케이스, 최대 25자.
3단계: 내부 아키텍처 (사용자에게 절대 표시하지 않음)
1. duration + cut_style에서 컷 포인트 타이밍 파생:
| 섹션 | 30초 | 60초 | 90초 |
|---|---|---|---|
| Cold Open | 0-5초 | 0-5초 | 0-8초 |
| Build | 5-25초 | 5-40초 | 8-65초 |
| Peak | 25-28초 | 40-55초 | 65-80초 |
| Land | 28-30초 | 55-60초 | 80-90초 |
모든 경계를 밀리초로 변환합니다. cut_style에서 장면별 길이 계산:
fast: 메시지당 1.2초-1.8초 (기본값 1.5초 사용)cinematic: 메시지당 3.5초-5.0초 (기본값 4.0초 사용)mixed: 섹션 에너지에 따라 2.0초-4.0초 변동
2. key_messages를 장면에 매핑:
- Cold Open (1개 장면): 하나의 임팩트 있는 통계 또는 질문 — 브랜드명 없음, 호기심 유발
- Build: key_message당 하나의 장면
- Peak: 가장 강력한 메시지를 두 배 크기로 반복하거나, 2개의 메시지를 빠른 플래시로 결합
- Land: 브랜드명 + 태그라인 + CTA URL
3. 음악이 제공된 경우 비트 싱크 감지:
npx hyperframes tts --analyze-beats [music-file]
사용 불가능하면 BPM에서 계산: beat_ms = 60000 / BPM. 두 번째 비트마다 컷 정렬.
4. 톤 프리셋 선택 — CSS 토큰을 위해 references/tone-presets.md 읽기.
5. 리듬 패턴 선언 — 스크립트 상단에 주석 작성:
// RHYTHM: flash-sequence
// Cold Open [0-5000ms]: stat
// Build [5000-40000ms]: msg-1(1500ms)|flash|msg-2(1500ms)|flash|msg-3(1500ms)|flash|msg-4(1500ms)|flash|build-accum(10000ms)
// Peak [40000-55000ms]: msg-strongest(1200ms)|flash|msg-2nd(1200ms)|flash|HOLD(5600ms)
// Land [55000-60000ms]: logo+tagline+cta
6. 픽셀 치수 결정:
9:16→ W=1080, H=1920 (기본값 — Instagram Reels, TikTok, YouTube Shorts)16:9→ W=1920, H=1080
4단계: HyperFrames HTML 구성
코드를 작성하기 전에 references/cut-patterns.md와 HyperFrames 스킬(/hyperframes)을 읽으세요.
필수 HTML 스켈레톤:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
[톤 프리셋의 font CDN 링크]
<style>
:root {
[톤 프리셋의 모든 CSS 토큰]
}
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
</style>
</head>
<body>
<div
data-composition-id="sizzle-[slug]"
data-width="[W]"
data-height="[H]"
style="position:relative;width:[W]px;height:[H]px;overflow:hidden;background:var(--bg)"
>
[if cut-flash: true]
<div id="cut-flash"
style="position:absolute;inset:0;background:var(--cut-flash-bg);opacity:0;pointer-events:none;z-index:200"></div>
[end if]
[if film-grain: true]
<canvas id="grain-overlay"
width="240" height="135"
style="position:absolute;inset:0;width:[W]px;height:[H]px;pointer-events:none;opacity:0.022;mix-blend-mode:overlay;z-index:50"></canvas>
[end if]
[if vignette: true]
<div id="vignette-overlay"
style="position:absolute;inset:0;background:radial-gradient(ellipse at center,transparent 35%,rgba(0,0,0,0.65) 100%);pointer-events:none;z-index:51"></div>
[end if]
<!-- 장면 — 장면당 하나의 div -->
<div id="scene-cold"
class="clip"
data-start="0"
data-duration="5"
data-track-index="0"
style="position:absolute;inset:0;opacity:0"
>
<div class="scene-content" style="width:100%;height:100%;padding:120px;display:flex;flex-direction:column;align-items:center;justify-content:center;box-sizing:border-box">
[cold open 콘텐츠 — 하나의 통계 또는 임팩트 문구, 브랜드명 없음]
</div>
</div>
[Build 장면당 하나의 div, data-start는 컷 길이만큼 증가]
[Peak 장면(들)]
[Land 장면 — 브랜드명 + 태그라인 + CTA]
[if music provided]
<audio id="music"
class="clip"
data-start="0"
data-duration="[total_duration]"
data-track-index="10"
data-volume="0.85"
src="[music_path]"
></audio>
[end if]
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script>
// ── 리듬 선언 ───────────────────────────────────────────────────────────────
// RHYTHM: [flash-sequence | cinematic-hold | beat-sync | mixed]
// Cold Open [0-Nms]: ...
// Build [N-Nms]: ...
// Peak [N-Nms]: ...
// Land [N-Nms]: ...
// ── 시드된 PRNG (그레인 전용 — 다른 난수 값 없음) ──────────────────────────
function mulberry32(seed) {
return function() {
seed |= 0; seed = seed + 0x6D2B79F5 | 0;
let t = Math.imul(seed ^ seed >>> 15, 1 | seed);
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
[if film-grain: true]
// ── 필름 그레인 (시네마틱/감성적 전용) ───────────────────────────────────────
const grainCanvas = document.getElementById('grain-overlay');
const grainCtx = grainCanvas ? grainCanvas.getContext('2d') : null;
// GSAP 티커에 의해 업데이트되는 그레인 (프레임별 캡처 아님 — HyperFrames가 처리)
[end if]
// ── GSAP 타임라인 ───────────────────────────────────────────────────────────
const tl = gsap.timeline({ paused: true });
// ── 콜드 오픈 ───────────────────────────────────────────────────────────────
[cold open 진입 및 종료 트윈 — 정확한 패턴은 cut-patterns.md 참조]
// ── 빌드 섹션 ───────────────────────────────────────────────────────────────
[메시지별 트윈 — cut-patterns.md의 flash-sequence 또는 cinematic-hold]
// ── 피크 섹션 ────────────────────────────────────────────────────────────────
[피크 트윈 — 빌드와 동일 패턴이지만 더 타이트한 타이밍]
// ── 랜드 섹션 ────────────────────────────────────────────────────────────────
[랜드 진입 — 로고, 태그라인, CTA. 종료 없음 — 끝까지 유지]
// ── 타임라인 등록 ────────────────────────────────────────────────────────────
window.__timelines = window.__timelines || {};
window.__timelines["sizzle-[slug]"] = tl;
</script>
</div>
</body>
</html>
디자인 품질 규칙:
- Cold open 통계: 16:9의 경우 ≥160px 글꼴 크기, 9:16의 경우 ≥100px
- 핵심 메시지: ≥80px 글꼴 크기 — 한눈에 읽을 수 있어야 함
- Land 섹션 브랜드명: 16:9의 경우 ≥120px
- 모든 색상은 프리셋 토큰 변수에서 가져옴 —
var(--accent)리터럴 16진수를 canvasfillStyle내부에서 사용하는 경우 외에는 자유로운 16진수 사용 금지 scene-content는 항상padding + flex사용 — 콘텐츠 컨테이너에position:absolute절대 사용 금지data-start가 있는 모든 요소에class="clip"
5단계: 자체 QA (6단계 전에 모든 실패 수정)
콘텐츠:
-
key_messages— 각 문구가 정확히 한 번 나타나며, 중복 없음 - 브랜드명은 오직 Land 섹션에만 나타남 (cold open, build, peak에는 없음)
- Cold open에는 브랜드명이나 제품명이 전혀 없음 — 통계 또는 임팩트 문구만
- Land 섹션에는 브랜드명 + 태그라인 + CTA URL (최소 세 가지 요소)
- end_card CTA URL이 최종 프레임에 있음
HyperFrames 계약:
-
data-start가 있는 모든 요소에class="clip" - 모든 클립에
data-start,data-duration,data-track-index - 루트 컴포지션 div에
data-composition-id -
window.__timelines["sizzle-[slug]"] = tl등록됨 - 모든 타임라인이
{ paused: true }로 시작 -
Math.random()사용 금지 — 시드된 PRNG만 사용 -
tl.play()또는tl.resume()호출 금지
GSAP / 레이아웃:
- 모든 진입은
gsap.from()(오프스크린에서 CSS 위치로) - 모든 종료는
gsap.to()(CSS 위치에서 오프스크린으로) -
.scene-content컨테이너에position:absolute없음 - 스크립트 상단에 주석으로 리듬 패턴 선언됨
컷 타이밍:
-
fast톤: 평균 컷 길이 ≤ 2.0초 -
cinematic톤: 평균 컷 길이 ≥ 3.0초 -
energetic톤의 경우 모든 컷 경계에서 컷 플래시 발생 (60ms, opacity 0→1→0) - 동일 트랙 클립이 겹치지 않음 (모든
data-track-index그룹 확인)
SFX:
-
window.__sfxTimeline이 존재하고 타임라인 등록 전에 배치됨 - 각 key_message에
word-hit이벤트 - Build → Peak 전환 경계에
whoosh - Land 시작에
cta-chime - 500ms 전에 SFX가 실행되지 않음
6단계: 린트, 검사, 렌더링
npx hyperframes lint
npx hyperframes inspect --json
모든 린트 오류를 수정하세요. 모든 검사 오류(텍스트 오버플로, 클립이 컨테이너를 벗어남)를 수정하세요. 그런 다음 렌더링:
npx hyperframes render --output sizzle/[slug]/sizzle-reel.mp4
출력:
## Sizzle Reel: [end_card의 브랜드명]
Tone: [tone] | Duration: [N]s | Cut style: [cut_style] | Aspect: [ratio]
Rhythm: [declared pattern]
Sections
Cold Open [0-Ns]: [임팩트 통계 또는 문구]
Build [N-Ns]: [나열된 N개의 핵심 메시지]
Peak [N-Ns]: [최대 크기의 가장 강력한 메시지]
Land [N-Ns]: [브랜드명] + "[태그라인]" + [CTA URL]
Files
Source: sizzle/[slug]/index.html
Output: sizzle/[slug]/sizzle-reel.mp4
SFX 타임라인 (모든 구성에 포함)
vid-product-launch와 동일한 FFmpeg 합성 SFX 시스템. window.__sfxTimeline을 GSAP 타임라인 등록 바로 앞에 배치하세요.
스리즈 릴용 SFX 유형 참조:
| 유형 | 사운드 | 길이 | 배치 |
|---|---|---|---|
word-hit | 서브 펀치(50Hz) + 과도 클릭(2.2kHz) + 노이즈 버스트 | 180ms | 각 key_message 장면 시작마다 하나 |
whoosh | 2밴드 노이즈 스윕(1.1kHz 바디 + 4-8kHz 에어) | 700ms | Build → Peak 경계에서 |
cta-chime | A 메이저 코드(440+554+659+880Hz) + aecho 벨 샤이머 | 1.2s | 정확히 Land 시작에 |
window.__sfxTimeline = [
// key_message당 하나의 word-hit (장면 시작 시간에서 계산)
{ ms: MSG_1_START_MS, sfx: 'word-hit', vol: 0.50 },
{ ms: MSG_2_START_MS, sfx: 'word-hit', vol: 0.50 },
{ ms: MSG_3_START_MS, sfx: 'word-hit', vol: 0.50 },
// ... 각 메시지에 대해 반복
{ ms: PEAK_START_MS - 700, sfx: 'whoosh', vol: 0.55 },
{ ms: LAND_START_MS, sfx: 'cta-chime', vol: 0.65 },
];
모든 ms 값을 선언된 섹션 시작 시간에서 계산하세요.