REEL-FROM-SCRIPT · v7은(는) 무엇을 하나요?
직접 작성하지 않은 대본과, 리버스 엔지니어링해야 하는 참조 영상, 그리고 이전 대화 기록이 없는 새로운 채팅에서 예술 감독이 작업한 듯한 깨지지 않는 9:16 릴을 제작하는 스킬입니다.
v5에서는 레이아웃을 도입하고 디자인을 하드코딩했습니다. v6에서는 모든 숫자를 PARAMS.json으로 옮기고 렌더링 전에 검증기를 배치했습니다. v7은 실제 릴 두 개가 v6에게 가르쳐준 것입니다 — 6색 필드 순환, 커버 포스터, 장면별 카드 애니메이션, 연속적인 장면 범위, 적응형 와이프, 오디오 함정, 그리고 잘못된 결과물을 넘겨주지 않는 단일 ./build.sh + scripts/qc.py.
약속: 사람이 편집하는 유일한 파일은
PARAMS.json입니다. '아니요'라고 말할 수 있는 유일한 것은prepare.py/qc.py입니다. 그 외 모든 것은 생성되며, 생성된 모든 것은 하나의 명령어로 재현 가능합니다.
0 · 처음이라면: 처음 10분
export PATH=/home/user/node22/bin:$PATH # Node >= 22
cd <project> && ./build.sh --from render # 또는 §3을 처음부터 따라가세요
그런 다음, 다음 순서대로 읽으세요: §1 (룩이 어떻게 구축되는지) → §3 (파이프라인) → §6 (디자인 플레이북) → §9 (실패 카탈로그). 레이아웃을 직접 만들지 마세요: 뼈대가 바로 디자인입니다.
절대 하지 말아야 할 네 가지: 길이를 직접 입력하지 마세요(VO를 측정하세요), 16진수나 픽셀을 컴포지션에 붙여넣지 마세요(PARAMS.json을 변경하세요), index.html을 직접 작성하지 마세요(생성하세요), 연속 사진을 보지 않고 배포하지 마세요.
1 · 출력물이 디자인되어 보이는 이유 (일곱 가지 법칙)
이것들은 이 시리즈의 모든 좋은 프레임 뒤에 있는 실제 결정들입니다. 이것들은 규칙이지, 취향이 아닙니다.
1. 하나의 앵커 그리드, 하단 고정. Kicker → hero → body → chip → presenter. 하단 절반은 절대 움직이지 않습니다: 칩은 항상 같은 y에 위치하고, 프리젠터는 항상 같은 위치에서 프레임에 의해 잘립니다. 상단 절반만 재구성됩니다. 이것이 9개의 다른 장면이 하나의 영화로 읽히는 이유입니다.
2. 히어로는 문장이 아니라 단어입니다. 1–3개 단어, 프레임 너비의 ~17–25%로 설정된 좁은 디스플레이 서체, 좁은 줄 간격(0.88). 전체 문장은 칩(3–6개 단어, 음소거된 시청자가 읽는 것)에 들어가고, 세부 정보는 카드에 들어갑니다. 3단어로 말할 수 없다면, 비트는 두 비트입니다.
3. 모든 비트는 다른 배경을 가집니다. 6개의 필드(크림 / 적갈색 / 거의 검은색 / 세이지 / 네이비 / 라벤더)가 연속된 두 개가 일치하지 않도록 순환됩니다. 색상 변화 자체가 전환의 또 다른 절반입니다. prepare.py는 두 장면이 연속으로 필드를 공유하면 빌드를 실패시킵니다.
4. 증거, 장식이 아닙니다. 카드는 무언가를 보여주기 위해 존재합니다: 파일 목록, diff, 브라우저, 업로드, 미터기. 비트에 아티팩트가 없으면 type-led이며 카드가 없습니다. 두 개의 행이 있는 카드는 깨져 보입니다. 4~6개 행은 의도적으로 보입니다(또는 모형이 카드의 ≥ 55%를 채움).
5. 첫 번째 프레임은 포스터입니다. 장면 1은 t=0에서 구성됩니다(data-cover="1"): 키커, 히어로, 카드 및 캐릭터가 이미 화면에 있고 칩은 0.65초에 나타납니다. 빈 필드에서 시작하는 릴은 스크롤을 잃습니다.
6. 모션은 변환(transform)만 사용하며, 일찍 끝납니다. 등장은 0.24–0.34초, power2/3/4.out이며, 비트의 전반부에 끝납니다. 텍스트 콘텐츠에 애니메이션을 적용하지 말고, 숫자를 트윈(tween)하지 말고, 1.5초 장면이 0.6초를 와이프에 소비하게 하지 마세요. 히어로의 느린 드리프트는 마지막 초를 살아있게 유지합니다.
7. 사운드는 구두점이지, 배경 음악이 아닙니다. 초당 ≤ 1 큐: 모든 컷에 팝, UI에 키 틱, 가장 큰 두 번의 전환에 라이저, 공개에 차임, CTA에 알림/핑. 베드는 −16 LUFS VO 아래 0.15; 마스터 −14 LUFS / ≤ −1 dBTP.
2 · 계약: 고정된 것과 사용자의 몫
불변 — 분위기로 편집 불가, 검증됨
| 불변 | 적용자 |
|---|---|
| 모든 텍스트/배경 쌍 읽기 가능 | WCAG ≥ 4.5:1 (10개 렌더링 쌍 + 6개 필드 쌍) |
| 히어로는 히어로이며 항상 맞음 | ≥ 120px, 파생된 크기, --hero-band 내 --hero-lines에 맞아야 함 |
| 히어로가 키커를 넘어서지 않고, 카드에 닿지 않음 | 파생된 밴드 + --gap, 엄격한 실패 |
| 본문 텍스트를 휴대폰에서 읽을 수 있음 | ≥ 24px 및 ≥ bodyMin |
| 플랫폼 UI 아래에 아무것도 숨지 않음 | 칩 하단 ≤ 안전 영역 하단, ≥ 55% 사용 가능 |
| 칩이 카드와 절대 충돌하지 않음 | cardTop + cardHmax ≤ chipTop |
| 프리젠터가 프레임에 의해 잘리고, 절대 칩 위로 오지 않음 | presenterTop + h > 1.0, presenterTop ≥ chipBottom |
| 연속된 두 장면이 필드를 공유하지 않음 | 모든 컴포지션에서 data-field 읽음 |
| 프레임 0은 커버 | data-cover + 스냅샷 게이트 |
| 모든 장면에 다른 포즈 | 장면당 하나의 포즈 파일, 리뷰 시 확인 |
| 결과물이 재생 가능하고 충분히 큼 | qc.py: 해상도, fps, 프레임, LUFS, 트루 피크, 무음 구간 |
변수 — 진정으로 사용자의 것: 팔레트 값(6개 필드 + 4개 페이퍼 톤, 대비 허용) · 형식(9:16, 4:5, 1:1, 720p) · 안전 영역 인세트 · 바닥 내의 모든 그리드 및 타입 비율 · 장면별 아키타입 · 장면 수 · 단어 · 지속 시간 · 포즈 세트 · 큐 시트 · 디스크상의 폰트.
검증기가 확인할 수 없는 레이아웃을 원한다면, 뼈대를 변경 하고 검사를 추가해야 합니다. 그것이 자유의 대가이며, 출력물이 나쁠 수 없는 이유입니다.
3 · 파이프라인 (순서는 협상 불가)
| # | 단계 | 명령어 | 이유 |
|---|---|---|---|
| 0 | 참조 영상 리버스 엔지니어링 | §4 | 측정하지 않은 룩을 맞출 수 없음 |
| 1 | 사실 | — | 늦게 도착하는 주장은 재녹음을 강요함 |
| 2 | 스크립트 → 섹션 | assets/vo/sections.json | 비트당 하나의 아이디어, 1.2–9초 |
| 3 | VO 먼저 | python3 scripts/process_vo.py | timeline.json이 시계이며; 그림은 그것에 맞춰 잘림 |
| 4 | 룩 | PARAMS.json | 팔레트, 형식, 그리드, 타입 바닥 |
| 5 | 게이트 | python3 scripts/prepare.py | 대비 / 타입 / 충돌 / 필드 리듬 실패 시 중단 |
| 6 | 장면 | ./scenes.sh | 비트당 하나의 new_scene.py 호출, 토큰 주입 |
| 7 | 루트 | python3 scripts/build_index.py | 연속적인 범위, 적응형 와이프, 수동으로 입력한 index.html 없음 |
| 8 | 린트 | npx --yes [email protected] lint | 0 에러는 게이트 |
| 9 | 확인 | npx … snapshot --at "0,1.5,4,…" | "맞지만 못생긴" 것을 잡는 유일한 단계 |
| 10 | 렌더링 | npx … render --output renders/_raw.mp4 | 1080×1920에서 32초에 약 1분 45초, 소프트웨어 GL |
| 11 | 믹싱 | python3 scripts/mix_audio.py | VO + 배경 + 큐 → renders/master.wav (−14 LUFS) |
| 12 | 먹스 + 배포 | build.sh 6–7단계 | mp4 + .srt + 썸네일, 이후 qc.py |
./build.sh는 3 → 12단계를 한 번에 실행합니다. ./build.sh --from render는 VO가 이미 측정된 경우 VO 단계를 건너뜁니다.
3단계 상세 — VO는 시계입니다
# 섹션당 하나의 클립, assets/vo/에 vo1.mp3 … voN.mp3 이름으로 저장
python3 scripts/process_vo.py
무음을 자르고, script_target이 설정된 경우에만 atempo를 적용하고, 각 테이크를 −16 LUFS로 라우드노름(loudnorm)한 다음, assets/vo/timeline.json을 작성합니다: 섹션별 시작/지속 시간/종료 + root_duration. 다운스트림에서는 이 파일에 없는 지속 시간을 알 수 없습니다.
데바나가리 문자로 say를 쓰고 영어 기술 용어는 라틴 문자로 쓰세요(resume, job description, ATS score, DM) — hi-IN 음성이 운율을 제대로 맞추면서도 영어 단어를 발음합니다. caption은 .srt가 보여주는 내용입니다.
7단계 상세 — 루트는 기계적입니다
build_index.py는 compositions/s*.html 파일을 숫자 순서로 찾고, 측정된 범위를 읽어 연속적으로 만듭니다(0 → 컷 → 컷 → … → 루트, 따라서 VO에 0.30초 간격이 있어도 검은 구멍이 나타나지 않음), 도착하는 장면의 색상으로 각 컷 0.36초 전에 와이프를 실행하고, 오디오 테일까지 마지막 장면을 유지합니다. 와이프는 짧은 장면 길이의 34–62%로 단축됩니다 — 1.4초 장면은 0.62초 와이프를 가질 수 없습니다.
11단계 상세 — 두 가지 오디오 함정
alimiter는level=true가 기본값입니다: 자동으로 레벨을 천장까지 다시 올리므로, "리미터"가 컴프레서 + 메이크업 게인이 되어 −14 LUFS를 목표로 할 때 믹스가 −12.5 LUFS에 도달합니다. 항상level=false로 설정하세요.loudnorm을 하나의 선형 패스로 사용하면 오버슛이 발생합니다.ebur128로 측정하고, 정확히target − measured만큼 이동한 다음, 제한하세요. 검증 가능한 산술이 추측하는 필터보다 낫습니다.
4 · 0단계 — 참조 영상 리버스 엔지니어링
브리핑이 "이걸 보고 따라 해"라면, 측정하세요. 10분, 5개의 명령어:
ffprobe -v error -show_entries format=duration:stream=width,height,r_frame_rate,codec_type -of default=nw=1 ref.mp4
ffmpeg -i ref.mp4 -vf "fps=1,scale=400:-1,tile=6x6" -frames:v 1 sheet.jpg # 전체 영화를 하나의 이미지로
python3 - <<'P' # 초당 필드 팔레트
from PIL import Image; import glob
for f in sorted(glob.glob("frames/f_*.jpg"))[::2]:
im = Image.open(f).convert("RGB"); w,h = im.size
print(f[-7:-4], im.getpixel((3,3)), im.getpixel((w-4,3)))
P
ffmpeg -i ref.mp4 -vn -ac 1 -ar 16000 -c:a pcm_s16le audio.wav # 스크립트용
이것으로부터 읽어내세요: 지속 시간과 컷 리듬(각 비트가 얼마나 긴지), 필드 팔레트(중간이 아닌 모서리 샘플), 타입 동작(대문자 응축? 단어별 빌드? 하이라이트 상자?), 레이아웃(캐릭터 위치, 텍스트 블록 위치), UI 아티팩트(어떤 모형), 사운드(큐 밀도). 이 여섯 가지 숫자가 전체 아트 디렉션입니다.
브리핑이 참조 영상은 주었지만 스크립트는 주지 않았다면, 참조 영상 자체가 스크립트입니다: 오디오를 전사하고(faster-whisper, int8, base 모델 → 32초 힌글리시에 약 7초), 프레임에서 화면상의 카피를 읽으세요. 카피를 다시 쓰지 마세요 — 시리즈에는 목소리가 있습니다.
번역하세요, 복사하지 마세요. 여기서 참조 영상은 1280×720이었고; 결과물은 1080×1920이었습니다. 동일한 팔레트, 동일한 타입, 동일한 모형 — 그러나 히어로 밴드, 칩 앵커 및 프리젠터 크롭은 비율로 다시 파생되었습니다. 9:16에서 참조 영상의 8–10 단어 블록은 24px 본문 바닥 아래로 떨어지므로, 카피는 다르게 분할됩니다: 히어로 1–3 단어, 칩은 문장, 카드는 세부 정보.
5 · 파일
PARAMS.json 유일한 디자인 파일
tokens.css 생성된 영수증 (편집 금지)
index.html build_index.py에 의해 생성됨 (편집 금지)
SKILL.md · COMPONENTS.md · README.md
build.sh 전체 파이프라인
scenes.sh 모든 장면, 재현 가능하게
components/ `--card-body @…`로 전달할 수 있는 7개의 카드 본문
compositions/
_scene-base.html.tmpl THE 뼈대 — 리터럴 16진수 0, 리터럴 픽셀 0
wipe.html 유일한 전환
sN-*.html new_scene.py에 의해 생성됨
_examples/ 세 개의 작업된 장면 (마운트되지 않음: build_index는 compositions/s*.html만 읽음)
scripts/
process_vo.py VO → timeline.json
prepare.py 게이트: 바닥 + 토큰 주입
new_scene.py 기본 템플릿 → 올바른 장면
build_index.py 타임라인 + 장면 → 루트
mix_audio.py VO + 배경 + 큐 → master.wav
make_deliverables.py .srt + 썸네일
qc.py 하나의 명령어, 10개 게이트에 대해 PASS/FAIL
cutout.py · gen_sfx.py 자산
assets/{fonts,presenter,vo,sfx,music}/
6 · 디자인 플레이북 (실제로 선택할 것)
비트 → 아키타입. 증거 → card-led. 숫자 → stat-led(주행 거리계, --digits). 진술, 전환, 브랜드 카드 → type-led. 훅과 CTA는 거의 항상 card-led입니다. 둘 다 아티팩트가 필요하기 때문입니다(문제를 증명하는 파일 목록; CTA를 증명하는 댓글 스레드).
비트 → 필드. 따뜻함과 차가움을 번갈아 사용하세요; 절대 반복하지 마세요. 6개 필드에 대한 실용적인 순환: accent → ground → ink → secondary → tertiary → quaternary. 어두운 필드(ink, tertiary)는 프리젠터를 사라지게 만들므로, 그곳에서는 후광(halo)이 0.20 → 0.42로 갑니다(뼈대가 data-field에서 이를 수행합니다); 드라마가 도움이 되는 도구 공개 및 "AI가 작업을 수행" 비트에 사용하세요.
비트 → 포즈. 동일한 캐릭터, 동일한 복장, 장면당 하나의 포즈, 문장의 감정과 일치: 어깨 으쓱(질문) · 얼굴 감싸기(문제) · 제시(제안) · 턱(궁금함) · 기억(회상) · 엄지(승리) · 가리키기(단계) · 아이디어(아하) · CTA(요청). 기본 이미지에서 누락된 포즈를 생성하고 cutout.py로 매트를 만드세요; 흰색 바닥 규칙과 중간 허벅지 크롭을 유지하여 깔끔하게 잘리도록 하세요.
화면상의 단어. 히어로: 감정적인 동사 또는 숫자 (SAME RESUME?, CUSTOMIZE KARO, TAILOR, ATS SCORE). 칩: 음소거된 시청자가 필요한 문장 (har company mein bhej rahe ho?). 카드 제목: 아티팩트가 무엇인지 (applied / 12 companies). 카드 메타: 작고 사실적인 사실 (this week, matched 3 of 7).
비트가 디자인하기에 너무 짧을 때 (1–2초). 장면을 유지하고, 두 번째 레이어를 버리세요: 히어로 + 칩 + 프리젠터, type-led, 하나의 공개. 카드와 계단식 목록을 1.4초에 쑤셔 넣지 마세요 — 결함처럼 읽힙니다.
비트가 길 때 (8–9초, 보통 CTA). 세 번의 공개를 1.8–2초 간격으로 배치하고(댓글 → DM → 링크), 느린 드리프트가 꼬리를 운반하게 하세요. 8초 정적 프레임은 "싸구려처럼 보이는" 가장 흔한 원인입니다.
전환. 하나의 와이프, 0.34–0.62초, 색상 = 도착 장면의 필드, 방향 교대. 다른 전환 유형은 없습니다: 일관성이 8개의 컷을 작가의 손길이 닿은 것으로 만듭니다.
마지막 초. 마지막 프레임은 핸들과 사이트가 있는 디자인된 프레임이어야 합니다 — 거기서 멈추는 시청자가 행동할 수 있어야 합니다.
7 · 오디오 플레이북
| 순간 | 큐 | 게인 |
|---|---|---|
| 모든 컷 | pop | 0.50 |
| 가장 큰 두 번의 전환 | riser (컷 0.3초 전) | 0.28–0.30 |
| UI 요소가 착지할 때 | click, key | 0.38–0.45 |
| 목록이 찍힐 때 | key ×N, 0.12초 간격 | 0.40 |
| 공개 / 점수 | chime | 0.36–0.42 |
| CTA | notify, 1.7초 후 ping | 0.32–0.40 |
| 잘못됨 / 거부됨 | error (드물게 사용 — 한 번만 코미디로 읽힘) | 0.30 |
큐 오프셋은 섹션 기준입니다(assets/sfx/cues.json에서 "vo7+1.68"형식). 따라서 재녹음된 VO가 디자인을 무효화하지 않습니다. 큐를 단어의 첫 음절에 절대 배치하지 마세요; 틱은 20–60ms *
… (원문 잘림)