/pitch-deck-video
실행 중인 로컬 웹 앱에서 나레이션이 포함된 제품 데모 비디오를 생성합니다. 편집은 파생되며, 수동 타이밍이 전혀 없습니다. 따라서 프론트엔드가 변경되면 다시 녹화하기만 하면 컷이 자동으로 따라옵니다 — 프레임 번호를 수정할 필요가 없습니다.
실제 발생하는 상황, 즉 UI가 계속 움직이는 동안 비디오가 필요한 경우를 위해 제작되었습니다.
사용법
/pitch-deck-video # 현재 디렉토리의 앱을 촬영합니다
/pitch-deck-video <path-to-app> # 특정 앱을 촬영합니다
/pitch-deck-video --rebuild # 기존 비디오 프로젝트를 다시 녹화하고 재편집합니다
/pitch-deck-video --script # 나레이션만 변경되었습니다; 마지막 테이크를 재사용합니다
/pitch-deck-video --render # 카드/캡션만 변경되었습니다; 다시 렌더링합니다
구조
네 개의 생성된 파일이 모든 것을 제어합니다. 첫 번째 파일만 사람이 작성합니다.
| 파일 | 내용 | 작성 주체 |
|---|---|---|
shots.json | 말할 내용, 음성, 음악, 테마, 브라우저 동작 | 사용자 |
src/vo-durations.json | 각 대사가 실제로 소요되는 시간 | scripts/tts.mjs |
capture/marks.json | 각 샷이 테이크에서 시작된 시간과 찾아야 할 위치 | scripts/record.mjs |
src/frames.json | 테이크에서 생성된 프레임 수 | scripts/frames.mjs |
src/timeline.ts는 이 파일들로부터 전체 편집을 도출합니다. 장면 길이는 측정된 나레이션 오디오를 기반으로 합니다. 푸티지는 이에 맞춰 늘어납니다. 카메라 움직임은 녹화 중 실시간으로 측정된 요소 경계 상자에서 비롯되므로, 이동된 버튼은 샷을 깨뜨리는 대신 샷을 이동시킵니다.
워크플로우
1 · 스캐폴딩
이 스킬의 template/을 앱 옆에 <app-name>-video/로 복사한 후 npm install을 실행합니다. 템플릿에는 시드 JSON이 포함되어 있어 첫 녹화 전에도 타입 검사와 렌더링이 가능합니다.
하나의 프로젝트를 위해 ~/.claude/skills/pitch-deck-video/template/ 아래의 파일을 절대 편집하지 마세요 — 이것은 공유 템플릿입니다. 복사본을 편집하세요.
2 · 먼저 저장된 로그인 정보 확인
앱에 인증이 필요한 경우, 저장소가 자체 e2e 테스트를 위해 이미 보관하고 있는 Playwright 저장 상태(.auth/*.json, storageState, playwright/.auth/)를 찾으세요. app.storageState를 해당 파일로 지정하면 카메라에 로그인 과정 없이 로그인된 상태로 녹화가 시작됩니다. 그러한 파일이 없을 경우에만 로그인 샷을 구축하세요 — 그리고 {"type": {"sel": "…", "env": "DEMO_PASSWORD"}}를 사용하여 비밀번호가 shots.json에 기록되지 않도록 하세요.
3 · 한 글자도 쓰기 전에 앱을 익히세요
앱의 개발 서버를 시작하고 모든 라우트를 1920×1080으로 스크린샷한 후 이미지를 읽으세요. 소스 코드에서 나레이션을 작성하지 마세요. 심사위원이 보게 될 내용에 대해 작성하는 것이며, 스크린샷만이 그것을 알려줍니다.
// 일회성 스크립트; Playwright는 템플릿과 함께 제공됩니다
for (const r of routes) {
await p.goto(base + r, { waitUntil: 'networkidle' });
await p.waitForTimeout(700);
await p.screenshot({ path: `/tmp/shots/${r}.png` });
console.log(r, await p.evaluate(() => document.documentElement.scrollHeight));
}
스크롤 높이를 기록하세요: 1080을 초과하는 모든 것은 scroll 액션이 필요하며, 그렇지 않으면 카메라에 절대 나타나지 않습니다.
4 · shots.json 작성
template/shots.example.json에서 시작하세요. 모든 필드와 액션은 references/shots-reference.md를 참조하세요.
theme.colors를 앱 자체 디자인 토큰에서 가져와 카드와 푸티지가 하나의 작품처럼 읽히도록 하세요. cards.arch.nodes를 실제 아키텍처(어느 박스가 모델 호출이고 어느 것이 결정적 코드인지)로 채우세요. 이 부분이 데모와 단순 래퍼를 구분짓는 요소이며, 심사위원들이 찾는 부분입니다.
중요한 나레이션 규칙:
- 12–16 비트, 총 1:30–1:50. 2:00 제한 아래에서 10초의 여유를 남겨두세요.
- 비트당 하나의 아이디어. 아이디어가 끝나면 비트도 끝납니다.
- 숫자를 말하세요. "6명이 점수를 받았고, Priya가 승리합니다"가 "누군가를 선택합니다"보다 낫습니다.
- UI를 나레이션하지 마세요. "여기서 승인을 클릭합니다"가 아니라 그것이 의미하는 바를 말하세요.
- 제품 이름이 나오기 전에 사용자의 언어로 문제를 제시하세요.
- 화면에 남기고 싶은 한 문장으로 마무리하세요.
5 · 빌드
APP_DIR=/path/to/app npm run all
나레이션 → 녹화 → 프레임 → 렌더링. app.url을 서빙하는 것이 없으면 개발 서버를 시작하며, 앱 자체의 dev 스크립트를 읽어 해당 프레임워크가 포트를 어떻게 사용하는지 파악합니다. 특별한 것이 필요하면 app.devCommand를 설정하세요. 일반적인 전체 재빌드: 약 2분.
shots.json은 먼저 검증되므로, 중복된 id나 알 수 없는 액션은 2분 후에 혼란스러운 비디오를 생성하는 대신 2초 안에 실패합니다.
6 · 직접 보고 QA — 선택 사항이 아닙니다
npm run qa # 샷당 하나의 스틸 이미지, 연속 시트로 쌓임
npm run qa 3 # 샷당 세 개, 전환을 확인하려는 경우
그런 다음 시트를 이미지로 읽으세요. 비디오를 스크러빙하는 것은 느리고 많은 것을 놓칠 수 있습니다; 연속 시트는 몇 초면 되고 정말 중요한 실패를 정확히 잡아냅니다.
확인 사항: capture/marks.json → warnings가 비어 있는지 (선택자를 찾을 수 없는 것은 충돌이 아닌 경고입니다 — 여전히 비디오가 생성되며, 무엇이 깨졌는지 목록이 제공됩니다). 그런 다음: 각 캡션이 그것을 뒷받침하는 프레임에 위치하는가? 빈 프레임이나 흰색 프레임은 없는가? 작은 텍스트가 읽을 수 있는가, 아니면 focus가 필요한가?
references/troubleshooting.md에는 이 파이프라인이 실제로 겪었던 모든 실패와 각각의 수정 방법이 있습니다. 처음부터 디버깅하기 전에 읽어보세요.
UI 변경 후 재편집
이것이 이 모든 것의 핵심입니다.
APP_DIR=/path/to/app npm run all
대부분의 변경 사항은 다른 것이 필요하지 않습니다 — 이동된 요소, 스타일이 변경된 페이지, 새로운 복사본. 여전히 판단이 필요한 것:
- 페이지가 삭제되거나 이름이 변경됨 → 해당 샷을 다시 지정하거나 교체하세요.
- 기능이 다시 작성됨 → 해당 비트의 나레이션은 더 이상 존재하지 않는 것에 대한 주장이 됩니다. 대사를 다시 작성하세요.
- 선택자가 실제로 사라짐 → 경고 목록을 확인하고 거기서 수정하세요.
나레이션은 라인별로 캐시되므로, 편집된 라인만 다시 합성됩니다.
음성
음성은 완성된 비디오의 가장 큰 품질 차이이며, 기본값은 확실히 좋지 않습니다. 첫 번째 빌드 전에 미리 솔직하게 말하세요.
tts.provider: "minimax"— 좋습니다. 사용자에게 MiniMax 키가 어디에 있는지 물어보고tts.envFile을 해당 파일로 지정하세요. 음성 ID는 MiniMax의 시스템 음성 목록에서 가져옵니다; 음성의 언어 태그는 제한이 아닌 힌트입니다(우크라이나어 태그가 붙은 음성은languageBoost: "English"로 영어를 잘 읽습니다).tts.provider: "say"— macOS의 내장 합성기이며, 레거시 음성만 일반적으로 설치되어 있습니다. 로봇 같고 제품 이름을 잘못 발음하며, 보는 사람이 누구나 알 수 있습니다. 타이밍 확인용으로는 사용 가능하지만, 심사위원이나 고객이 볼 것에는 약합니다.- 인간 녹음이 둘 다를 능가합니다.
<id>.wav파일을public/vo/에 넣고npm run script를 실행하면; 지속 시간이 다시 측정되고 전체 비디오가 다른 편집 없이 스스로 재타이밍됩니다.
키가 없으면 어쨌든 비디오를 빌드하세요 — 하지만 음성이 약한 부분이며, 키나 마이크로 10분이면 해결된다는 것을 사용자에게 미리 알리세요. 완성된 것처럼 say 음성 해설을 조용히 제공하지 마세요.
MiniMax는 API에 연결할 수 없는 경우 라인별로 say로 대체되므로, 실행이 완전히 실패하지는 않습니다.
API 키를 프로젝트에 복사하지 마세요. scripts/tts.mjs는 환경 변수 MINIMAX_API_KEY / MINIMAX_GROUP_ID를 읽거나, 그렇지 않으면 tts.envFile이 지정한 파일에서 구문 분석합니다. 사용자에게 키가 어디에 있는지 물어보고 envFile을 그곳으로 지정하세요.
인간 녹음은 어떤 합성기보다 뛰어납니다: <id>.wav 파일을 public/vo/에 넣고 npm run script를 실행하세요 — 파일을 다시 측정하고 전체 비디오를 재타이밍합니다.
음악
scripts/music.mjs는 music.url에 지정된 트랙을 yt-dlp로 가져와 현재 비디오 길이에 맞게 배경 음악을 자릅니다. 게시하기 전에 사용자에게 트랙의 라이선스를 확인하라고 알리세요 — 라이브러리 트랙은 일반적으로 설명에 출처 표시를 하면 무료이고, 그렇지 않으면 유료입니다. 제목과 URL은 public/music/SOURCE.txt에 저장되어 출처를 쉽게 복사할 수 있습니다.
하지 말아야 할 것
- 타이밍을 수동으로 조정하지 마세요. 비트가 너무 빠르게 느껴진다면, 나레이션이 샷에 비해 너무 긴 것입니다 — 타임라인이 아닌
shots.json을 수정하세요. <Sequence>안에<Screen />을 넣지 마세요. 설계상 절대 시간을 읽습니다; Sequence 내부의useCurrentFrame()은 Sequence-로컬 프레임을 반환합니다. 이로 인해 이전에 비디오가 깨진 적이 있으며, 증상(푸티지가 음성과 미묘하게 동기화되지 않음)을 발견하기 어렵습니다.- 설정 시간이 카메라에 찍히도록 두지 마세요. 탐색, 데이터 로딩, 배너 닫기는 모두 샷의 캡처 창 내에서 발생하며 나레이션 아래로 늘어납니다. 설정 단계 후에
{"cut": true}를 넣으면; 푸티지가 그 지점부터 시작됩니다. - 스텁된 것은 촬영하지 마세요. 버튼이 "백엔드 연결"을 반환한다면, 그 샷을 제외하고 연결이 완료되면 추가해야 할 샷을 사용자에게 알리세요.
- 경고 목록을 확인하지 않고 출시하지 마세요.
- 실행 간에 변동하는 숫자를 나레이션하지 말고, 이 계정의 버전이 비어 있을 때 화면이 가득 찼다고 설명하지 마세요. 하나의 거짓 주장을 발견한 심사위원은 나머지도 신뢰하지 않게 됩니다.