쇼릴 모션그래픽 영상 제작
목표는 "AI가 만든 슬라이드 동영상"이 아니라 이력서에 붙일 만한 모션 디자이너의 쇼릴이다. 첫 3초에 강한 시각 사건을 두고, 한 화면에 메시지 하나만 전하고, 화면의 변화를 나레이션 단어와 음악 히트에 프레임 단위로 맞춘다.
이 스킬은 실제 제작 13건(인물 프로필·기업 홍보·행사·교육·티저·음악 기반 등)에서 쓴 방법과 그때 겪은 문제의 해결책을 스크립트와 문서로 정리한 것이다. 같은 실수를 반복하지 않도록 각 단계의 스크립트를 그대로 쓴다.
기본값
사용자가 정하지 않은 항목은 아래 값으로 진행한다. 사용자가 바꾸면 그 값을 따른다.
| 항목 | 기본값 |
|---|---|
| 길이·규격 | 60초, 16:9 1920×1080, 30fps |
| 분위기 | 웅장한 시네마틱(어두운 배경, 따뜻한 흰 글자, 금색 강조, 레터박스·필름 그레인) |
| 나레이션 | ElevenLabs Marcus ibCGc01503OQd2R6i1n1(깊은 바리톤 한국어), eleven_multilingual_v2, stability 0.45 · similarity 0.8 · style 0.4 |
| 음악 | ElevenLabs Music(유료 플랜). 쓸 수 없으면 synth_music.py 합성 음악 |
| 이미지 | 필요한 장면만 codex-image로 생성. 실존 인물의 얼굴은 생성하지 않고 실제 사진만 쓴다 |
| 산출물 | 마스터 MP4, 공유용 MP4, 포스터 JPG, 자막 SRT, 제작노트 |
| 음량 | -14 LUFS, true peak -1.5 dBTP |
작업 위치
모든 작업 파일은 프로젝트 폴더 아래에 만든다. /tmp, /private/tmp, 세션 scratchpad에는 아무것도 저장하지 않는다. 세션이 끝나면 임시 폴더는 지워지고, 재생성 명령·검수 시트·생성 이미지·나레이션 테이크를 다시 쓸 수 없게 된다. 검수용 스틸과 시트는 <프로젝트>/qa/, 중간 렌더는 <프로젝트>/out/에 둔다.
프로젝트 폴더는 브리프에서 정한다. 사용자가 지정하지 않으면 현재 작업 폴더 아래에 영문 소문자 이름(./<주제-slug>/)으로 만든다.
진행 방식
사용자 확인은 1단계 기획 브리프에서 한 번만 받는다. 브리프가 확정되면 질문하지 않고 납품까지 진행한다. 진행 중 판단이 필요한 일(보이스가 막힘, 음악 API 불가, 사실 확인이 안 되는 내용)은 아래 대체 경로로 처리하고 납품 보고에 적는다. 사용자가 "묻지 말고 바로"라고 하면 브리프도 기본값으로 채워 요약만 보여 주고 진행한다.
0 환경 점검 → 1 기획 브리프(사용자 확인) → 2 조사·자료 → 3 스토리보드·원고
→ 4 오디오 먼저(나레이션·음악·효과음 → 타임라인) → 5 이미지 → 6 장면 제작(스타일 프레임 먼저)
→ 7 초안 렌더·검수 반복 → 8 최종 렌더·믹스·마감 → 9 자동 검수·납품 보고
오래 걸리는 작업(조사, TTS, 음악 생성, 이미지 생성, 최종 렌더)은 Bash run_in_background나 서브에이전트로 돌리고 그동안 다음 단계를 진행한다. sleep으로 기다리지 않는다. 완료 알림을 받으면 이어 간다.
스크립트 경로는 이 스킬 폴더 기준이다(~/.claude/skills/showreel/scripts/). 아래에서 S=~/.claude/skills/showreel/scripts로 줄여 쓴다.
0. 환경 점검
python3 $S/preflight.py
결과의 "쓸 수 있는 경로"를 브리프에 반영한다.
- codex CLI나 이미지 스킬이 없을 때: codex-image·photoreal·codex-cli 스킬은 https://github.com/revfactory/skills 에 있다. 출력된 설치 명령을 사용자에게 안내한다. 설치하지 않겠다면 타이포·그래픽·사용자 제공 사진으로 구성한다. 사용자가 "realphoto"라고 부르는 스킬의 실제 이름은
photoreal이다. - ElevenLabs 키가 없을 때: 나레이션을 원하면
preflight.py가 출력하는 키 발급 절차를 그대로 안내한다. 키를 받기 전까지는 음악·자막 버전으로 진행할 수 있다고 함께 알린다. - 기본 보이스가 막힐 때(Free 플랜은 라이브러리 보이스를 쓸 수 없다):
python3 $S/eleven.py voices --lang ko로 쓸 수 있는 보이스를 고르거나 플랜 업그레이드를 안내한다.
1. 기획 브리프 (사용자 확인)
references/brief.md를 읽고 질문한다. 요청에 이미 있는 항목은 다시 묻지 않는다. AskUserQuestion은 한 번에 질문 네 개까지라서 최대 두 번 묻는다.
확인할 항목:
- 주제와 목적: 누구·무엇을, 누구에게 보여 줄 영상인가
- 분위기·테마
- 길이와 화면비
- 이미지: AI 생성 / 실제 사진·자료 / 둘 다 / 타이포·그래픽만
- 나레이션 유무와 보이스
- 꼭 들어갈 정보: 이름·직함·날짜·장소·URL·CTA
- 자료 위치: 사진 폴더, 로고, 참고 URL
답을 받으면 <프로젝트>/brief.md를 쓰고, 요약(장면 구성안 한 줄, 길이, 무드, 보이스, 예상 시간·크레딧)을 보여 준 뒤 이대로 진행할지 한 번 확인한다.
2. 조사와 자료
먼저 프로젝트를 만든다. 폴더 구조, 엔진 템플릿, 한글 폰트, 렌더러 의존성이 한 번에 준비된다.
python3 $S/new_project.py <프로젝트> --ratio 16:9 --duration 60
인물·기업·행사·제품이 주제면 조사를 서브에이전트에 백그라운드로 맡긴다. 프롬프트 형식은 references/brief.md의 "조사 요청"에 있다.
- 결과는
research/research.md에 남긴다. 사실마다 출처 URL과 확실도(확인/단일 출처/미확인)를 적는다. - 영상에 넣는 숫자와 사실은 출처가 있는 것만 쓴다. 사용자가 준 정보가 조사 결과와 다르면 조사 결과를 따르고, 납품 보고의 "요청과 다르게 반영한 것"에 적는다.
- 실제 사진은 사용자 제공 자료, 공식 사이트, 보도 사진에서 고른다. 출처를
research/photos.md에 적는다. 보도 사진은 납품 보고에서 "공개 전 사용 허락 확인 필요"로 표시한다. - 브랜드 색은 공식 사이트 CSS나 로고에서 뽑는다. 로고는 그 회사의 영상일 때만 공식 파일을 쓰고, 다른 회사 로고는 그리지 않는다.
3. 스토리보드와 나레이션 원고
references/story.md를 읽는다. 용도별 구성 표, 길이별 나레이션 분량, 카피 규칙, 발음 표기 규칙이 있다.
storyboard.md: 장면 표(시간·화면 카피·나레이션·화면 설계·전환·효과음·출처)script.json: 나레이션 줄, 효과음, 음악. 형식은narrate.py맨 위 설명을 따른다.- 화면 표기(
text)와 발음 표기(tts)를 나눠 쓴다. 숫자·연도·약어·영문은 한글로 읽히게 적는다. - 장면 전환과 맞출 단어는
cues로 이름을 붙인다.
- 화면 표기(
- 나레이션 분량은 60초에 240~255자(공백·문장부호 제외)다. 처음부터 이 범위로 쓴다. 넘치면 화면이 나레이션을 따라가지 못한다.
- 나레이션이 없는 영상도
script.json을 쓴다:"lines": []로 두고scenes·sfx·music만 채운다.narrate.py는 건너뛰고timeline.py부터 실행한다. 자막(SRT)은 나오지 않는다. - 날짜가 나오면 요일을 달력으로 대조한다(
python3 -c "import datetime;print(datetime.date(2026,11,20).strftime('%A'))"). 요청한 날짜와 요일이 맞지 않은 사례가 있었다. 맞지 않으면 요일을 빼고 보고에 적는다.
4. 오디오를 먼저 만든다
화면의 시각은 나레이션 단어와 음악 히트에서 정해지므로 오디오를 먼저 확정한다. 자세한 기준은 references/audio.md에 있다.
python3 $S/narrate.py <프로젝트>/script.json --stt # 나레이션 + 발음 검증. ✗ 줄은 고쳐서 다시
python3 $S/music_tools.py plan <프로젝트>/audio/music/spec.json # 음악 스펙 → ElevenLabs 구성 플랜
python3 $S/eleven.py music <프로젝트>/audio/music/music.mp3 --plan <프로젝트>/audio/music/plan.json
# 402(유료 플랜 필요)면: python3 $S/synth_music.py music <프로젝트>/audio/music/spec.json <프로젝트>/audio/music/music.wav
python3 $S/music_tools.py analyze <프로젝트>/audio/music/music.mp3 # 템포·히트 → 장면 경계를 히트에 맞춘다
python3 $S/eleven.py sfx-kit <프로젝트>/audio/sfx --names impact,whoosh,riser,logo_hit # 또는 synth_music.py sfx
python3 $S/timeline.py <프로젝트>/script.json --music-analysis audio/music/analysis.json
- 음악 섹션은 4마디 단위로 잡는다(120 BPM이면 8초). ElevenLabs Music은 그 단위로 곡을 만들기 때문에, 드롭이 원하는 시각보다 늦게 나오면
music_tools.py splice로 빌드업 끝 몇 박 뒤에 드롭 구간을 이어 붙여 당긴다.
timeline.py가 timeline.json(믹서·검수용)과 reel/timeline.js(화면용 큐·단어 시각·소리 크기 곡선)를 만든다. 겹침·길이 초과·큐 누락이 있으면 ✗로 알려 주므로, 모두 고친 뒤 장면을 만든다. 나레이션 문구나 시각을 바꿀 때는 script.json만 고치고 narrate.py(바뀐 줄만 다시 생성)와 timeline.py를 다시 실행한다.
5. 이미지
references/images.md를 읽는다.
- 생성:
~/.claude/skills/codex-image/scripts/codex_imagegen_batch.sh <프로젝트>/assets/gen "프롬프트::이름.png" …(5장씩 병렬, 장당 약 1분) - 사람이 나오는 사진풍 이미지:
photoreal스킬로 프롬프트를 만든다. - 실제 대상을 닮게 만들 때:
$S/codex_ref_image.sh로 참조 사진을 붙인다. - 후처리:
python3 $S/prep_images.py <프로젝트>/assets/gen --out <프로젝트>/assets/img [--cutout]. 레터박스 제거, 확대, 누끼를 한다. - 확인:
prep_images.py --sheet로 시트를 만들어 Read로 본다. 손가락·글자·로고가 섞인 이미지는 다시 만든다.
6. 장면 제작
references/engine.md(엔진 API·장면 작성법·기법 코드)와 references/design.md(무드 프리셋·한글 타이포·모션 규칙)를 읽고 reel/scenes.js를 쓴다. 장면마다 다른 표현을 쓰려면 references/techniques.md(아이소메트릭 도시, 한글 자모 조립, 가변 굵기 물결, 유리 활자, 비트 그리드, 데이터 맥박, 3D 기기 UI, 입자 글자, 매치 컷 등 22종)에서 고른다. 같은 기법만 반복하면 쇼릴이 단조로워진다.
- 엔진 규칙: 화면은 시간 t의 순수 함수다.
Math.random,Date.now, CSS transition·animation, 이전 프레임 상태에 기대는 코드를 쓰지 않는다. 렌더러가 임의 시각으로 이동하고 여러 워커가 나눠 그리기 때문이다. - 시각은 숫자를 직접 쓰지 말고
REEL.cue('이름')로 타임라인 큐를 쓴다. 그래야 나레이션을 다시 만들어도 화면이 따라온다. - 하드 컷·상태 전환은 큐보다 0.5프레임 앞에 둔다(
cue(x) - 0.5/fps). 모션 블러 셔터가 프레임 앞뒤에 걸쳐 있어, 그래야 큐 프레임에 새 상태가 정확히 보인다. - 0초 프레임부터 화면이 차 있고 움직여야 한다. 페이드 인으로 시작하지 않는다.
- 색은 무드 프리셋보다 주제의 브랜드 색(공식 사이트 CSS·로고)을 우선한다. 프리셋은 브랜드 색이 없을 때의 기본값이다.
- 역동성은 설계 단계에서 넣는다(
design.md의 "역동성 규칙"). 스킬 없이 만든 영상과 비교했을 때 이 스킬의 결과가 덜 역동적으로 보인 원인은 다섯 가지였다.- 한 구도를 4
8초씩 두었다. 정보 구간은 0.51박마다 새 사건을 넣고, 구도는 2마디 안에 바꾼다. 장면 경계는REEL.bar(n)으로 마디에 놓는다. - 큰 타격이 컷 하나로 끝났다. 큰 히트마다
FX.impact+FX.camera흔들림 + 1.3배·흐림에서 착지하는 글자를 함께 쓴다. - 퇴장이 투명도 페이드였다.
REEL.inout으로 위로 밀리며 줄고 흐려지게 한다. - 검은 배경 위 글자만 있었다.
REEL.config({ push: 0.03 }),FX.floor,FX.dust로 장면 뒤를 늘 움직인다. - 전환이 대부분 컷이었다.
FX.camera({ whips }),FX.wipe로 화면 전체가 움직이는 전환을 섞는다.
- 한 구도를 4
- 스타일 프레임 먼저: 대표 장면 2~3개를 만들고 스틸 시트로 확인한 뒤 나머지를 만든다.
- 3분 넘는 영상은 챕터마다
scenes_chNN.js로 나누고, 서브에이전트에 챕터를 나눠 맡길 수 있다(references/engine.md의 "긴 영상").
7. 초안 렌더와 검수 반복
node $S/render.mjs --dir <프로젝트>/reel --every 0.5 --outdir <프로젝트>/qa/stills # 스틸(장면 하나를 만들 때마다)
python3 $S/contact_sheet.py --dir <프로젝트>/qa/stills --out <프로젝트>/qa/sheet_stills.png --timeline <프로젝트>/timeline.json
node $S/render.mjs --dir <프로젝트>/reel --out <프로젝트>/out/draft.mp4 --scale 0.5 # 초안(60초에 약 30초)
python3 $S/contact_sheet.py --video <프로젝트>/out/draft.mp4 --every 1 --out <프로젝트>/qa/sheet_draft.png
python3 $S/motion_check.py <프로젝트>/out/draft.mp4 --timeline <프로젝트>/timeline.json # 멈춘 구간·큰 움직임 부족을 시각과 함께 알려 준다
python3 $S/contact_sheet.py --video <프로젝트>/out/draft.mp4 --around <장면 경계 초들> --frames 3 --out <프로젝트>/qa/sheet_cuts.png
motion_check.py가 △를 내면 그 시각의 장면에 사건을 더하고 다시 초안을 뽑는다(기준: 20초 이하 멈춘 화면 8%·큰 움직임 15, 60초 이하 20%·7, 그보다 길면 35%·6). 만든 시트는 반드시 Read로 열어 직접 본다. 렌더한 프레임을 보지 않고 완료라고 하지 않는다. references/qa.md의 점검표로 고치고 2~3회 반복한다. 렌더러가 종료 코드 2로 끝나면 페이지 오류가 난 것이니 로그의 오류부터 고친다.
8. 최종 렌더·믹스·마감
node $S/render.mjs --dir <프로젝트>/reel --out <프로젝트>/out/video.mp4 --sub 4 --format png # 모션 블러. 60초에 약 3~5분, 백그라운드로
python3 $S/mix.py <프로젝트>/timeline.json # 나레이션은 리버브 없이 둔다(웅장한 톤도 마찬가지)
python3 $S/finish.py --video <프로젝트>/out/video.mp4 --audio <프로젝트>/audio/mix.wav \
--name "<제목>_<길이>s" --outdir <프로젝트>/out --timeline <프로젝트>/timeline.json
나레이션에 리버브를 넣지 않는다. 웅장한 느낌은 음악·효과음이 맡는다. 2.4초 홀을 --hall 0.12로 걸었던 영상에서 "나레이션에 에코가 너무 많다"는 지적을 받았다. 공간감이 꼭 필요할 때만 --hall 0.04~0.08을 쓰고, mix.py가 출력하는 "말 끝 잔향"가 ✓(-32dB 이하)인지 확인한다.
mix.py가 줄마다 "나레이션 대 배경" dB를 보여 준다. ✗(6dB 미만)인 줄은 근처 효과음을 줄이거나 핵심 단어에서 0.2~0.3초 비켜 놓고 다시 믹스한다. 오디오만 고칠 때는 영상을 다시 렌더하지 않고 mix.py와 finish.py만 다시 돌린다.
9. 자동 검수와 납품 보고
python3 $S/qa_check.py <프로젝트>/out/<이름>.mp4 --timeline <프로젝트>/timeline.json --script <프로젝트>/script.json --stt
규격, 음량, 너무 어두운 구간, 섬광, 멈춘 화면, 완성 믹스의 받아쓰기(숫자 오인식 포함)를 검사하고 qa/qa_report.md와 최종 시트를 만든다. "고칠 것"을 모두 처리하고 최종 시트를 Read로 본 뒤 납품한다. STT에서 한 번만 틀린 줄은 한 번 더 돌려 두 번 다 틀릴 때 고친다.
납품 보고와 제작노트.md 형식은 references/qa.md의 "납품"에 있다. 다음을 반드시 넣는다.
- 산출물의 절대 경로 표
- 장면 구성표
- 요청과 다르게 반영한 것
- 공개 전 확인할 것: 사진 사용 허락, 단일 출처 사실, 시점이 지나면 틀리는 정보, AI 생성 인물이 가상이라는 점
- 재생성 명령
- 사운드를 직접 들어 봐 달라는 요청. Claude는 소리를 들을 수 없고 수치로만 확인했다.
지킬 것
- 역동성: 같은 구도를 2마디 넘게 두지 않고, 퇴장도 움직임으로 처리하고, 장면 뒤에 움직이는 면을 깐다. 큰 타격은
FX.impact와 흔들림을 함께 쓴다. 규칙과 도구는design.md의 "역동성 규칙"에 있고, 초안마다motion_check.py로 확인한다. - 실존 인물의 얼굴·몸은 이미지로 생성하지 않는다. 생성 이미지는 얼굴이 보이지 않는 환경·사물·뒷모습 컷으로 쓴다.
- 영상의 숫자·직함·연도는 조사 출처가 있는 것만 쓴다. 연출용 가상 수치를 쓰면 화면이나 보고에 그렇다고 밝힌다.
- 음악 프롬프트에 아티스트·작곡가·곡 이름을 넣지 않는다. 저작권 필터에 걸려 400으로 거부된다.
- ElevenLabs 크레딧을 아낀다. 바뀐 줄만 다시 만들고,
takes는 고유명사·숫자 줄에만 쓴다. - 섬광은 어두운 배경에서 opacity 0.15 이하로 쓰고, 1초에 3번을 넘기지 않는다(광과민성).
자주 생기는 문제
| 증상 | 조치 |
|---|---|
| 나레이션이 목표 길이를 넘김 | 원고를 줄인다(60초 = 220~255자, 짧은 줄이 많으면 더 적게). 그래도 넘치면 speed 1.05까지만 올린다 |
| 영상이 멈춰 보이거나 밋밋함 | motion_check.py가 알려 준 시각에 사건을 더한다. 장면 뒤 FX.floor·FX.dust, 큰 히트에 FX.impact+흔들림, 퇴장은 inout, 컷 대신 휩·와이프 |
| 이름·숫자를 잘못 읽음(이훈희→이후니, 2026→2025) | 발음 표기 수정 → 쉼표로 끊기("이공, 이육") → takes 3~5 → 모델 eleven_v3로 그 줄만 교체 |
| 나레이션에 에코·울림이 많음 | --hall 없이 mix.py와 finish.py만 다시 돌린다(영상 재렌더 불필요). 리버브를 남길 때는 mix.py의 "말 끝 잔향"가 ✓일 때까지 --hall을 낮춘다 |
| 완성 믹스에서 말이 묻힘 | 타격음을 핵심 단어에서 0.2gain_db +2--duck 0.8 |
| 오류 JSON이 mp3로 저장됨 | eleven.py가 막는다. 직접 curl을 쓰지 말고 스크립트를 쓴다 |
| Python에서 ElevenLabs 호출 시 SSL 오류 | 검증을 끄지 않는다. eleven.py는 curl을 쓰므로 이 문제가 없다 |
| 화면 글꼴이 기본 고딕으로 나옴 | 렌더 로그의 "폰트" 줄 확인, reel/fonts/fonts.css에 그 굵기가 있는지 확인 |
| 장면 경계에서 빈 화면·잔상 | contact_sheet.py --around로 ±3프레임을 보고 퇴장을 컷 0.1초 전에 끝낸다 |
| 음악이 플랜의 섹션 길이와 다름 | 정상이다(대개 4마디 단위로 생성됨). music_tools.py analyze로 히트를 읽고 장면 경계를 히트에 맞추거나, splice로 드롭을 원하는 시각에 옮긴다 |
| 세로(9:16) 영상의 HUD·자막이 앱 UI에 가림 | fx.js가 세로 영상이면 위 250px·아래 420px을 자동으로 피한다. 직접 그린 요소도 이 영역을 비운다 |
| 영상이 yuvj420p·풀레인지로 나옴 | render.mjs와 finish.py를 거치면 yuv420p·tv로 나온다. 다른 도구로 인코딩하지 않는다 |
참조
| 파일 | 언제 읽나 |
|---|---|
references/brief.md | 1단계: 질문 세트, 브리프 양식, 조사 요청 프롬프트, 시간·크레딧 추정 |
references/story.md | 3단계: 용도·길이별 구성, 나레이션 분량, 카피·발음 표기 규칙, 원고 양식 |
references/audio.md | 4·8단계: 보이스·모델·설정, ElevenLabs 키·플랜, 음악·효과음, 믹스 기준 |
references/images.md | 5단계: 이미지 생성·실사 사진·누끼·후처리 |
references/design.md | 6단계: 무드 프리셋, 한글 타이포, 모션·전환 규칙, 레이아웃 |
references/engine.md | 6단계: 엔진 API, 장면 작성법, 기법별 코드, 긴 영상 분업 |
references/techniques.md | 6단계: 표현 기법 카탈로그 22종(코드 포함)과 싱크·첫 프레임·섬광·결정성 규칙 |
references/qa.md | 7·9단계: 검수 점검표, 시트 읽는 법, 납품 보고·제작노트 양식 |