product-video — 코드로 만드는 제품 소개 영상
제품 화면을 HTML로 다시 만들고, 움직임을 '시간만으로 결정'되도록 작성하여, 헤드리스 브라우저에서 1프레임씩 촬영해 동영상으로 만듭니다. 음악과 효과음도 브라우저 내에서 합성합니다. 사용하는 것은 Node.js·ffmpeg·Chromium뿐입니다.
이 스킬에는 바로 실행할 수 있는 템플릿(assets/template/)이 포함되어 있습니다. 촬영·음향·검품 도구와 30초짜리 샘플 동영상이 준비되어 있으므로, 메커니즘을 처음부터 만들지 않고 템플릿을 복제하여 내용물만 교체하면 됩니다.
중요한 전제: 스스로는 보거나 들을 수 없음
동영상을 재생하여 확인할 수 없습니다. 따라서 이 작업의 품질은 정지 화면을 출력하여 직접 눈으로 확인하는 횟수와 숫자로 측정하는 검품에 의해 결정됩니다. 코드만 작성하고 '완성되었을 것이다'라고 진행하면 글자 잘림·겹침·일시적인 어긋남이 반드시 남습니다. 한 장면을 작성할 때마다 나란히 놓고 확인한 후 수정하고, 다음으로 진행해야 합니다. 소리는 들을 수 없으므로 측정된 수치를 제시하고, 청감 확인은 사람에게 요청합니다.
지켜야 할 사항 (이유 포함)
- 시각은 시간으로만 결정합니다. 움직임은
tl.*로 '몇 초에' 쌓거나,onFrame((t) => …)으로 t에서 직접 계산합니다. CSS의 animation/transition,setTimeout,Math.random(), 현재 시간은 사용하지 않습니다. 어느 시간으로 이동해도 같은 화면이 되기 때문에, 병렬로 촬영할 수 있고 일부만 다시 촬영할 수 있습니다. - 작성했으면 확인합니다. 한 장면을 작성할 때마다
--sheet로 나열하고, 이미지를 열어 확인합니다. 장면 전환은 0.1~0.2초 단위로 확인합니다. 확인하지 않고 다음 장면으로 진행하지 않습니다. - 문자가 있는 부품을 천천히 움직이지 않습니다. 브라우저는 문자의 위치를 픽셀에 맞추기 위해, 수 초에 걸쳐 조금씩 움직이면 문자가 미세하게 떨립니다. 컨테이너나 제목은 약 1초 정도로 움직임을 마치고, 정지된 화면 안에서 내용물을 움직입니다. 천천히 움직여도 되는 것은 배경의 빛뿐입니다.
- 등장은
tl.in, 제거는tl.out/tl.hide를 사용합니다.tl.fromTo는 '시작 시간 이전에는 원래 값 그대로'이므로, 등장에 사용하면 시작 전에 잠시 보여 버립니다. - 제품 화면의 CSS는, 화면의 루트 클래스 아래에 작성합니다. (
.ui-chat .row와 같이.).row.title과 같은 짧은 이름을 그대로 사용하면 템플릿 부품이나 다른 화면과 충돌하여 알아채기 어려운 깨짐이 발생합니다. - 읽을 수 있는 크기로 만듭니다. 동영상은 스마트폰에서도 시청됩니다. 제목은 68px 이상, 컨테이너 안의 본문은 화면상에서 18px 이상(글꼴 크기 × 배율)이어야 합니다. 작은 화면은 컨테이너 자체를 크게 배치하거나, 보여주고 싶은 곳으로 확대합니다.
- 사실을 왜곡하지 않습니다. 자료로 확인할 수 없는 것은 넣지 않습니다. 할 수 없는 것을 '할 수 있다'고 보여주지 않습니다. 미확인 수치, 타사와의 비교, 추천 문구는 넣지 않습니다. 타사의 로고는 그리지 않고, 이름은 문자로 작성합니다. 마지막 카드에, 화면이 재구성된 것임을 작게 표시합니다(
brand.fine). - 장면의 경계와 큰 움직임은 마디의 처음에 배치합니다. 로고가 맞춰지는 순간, 장면 전환, 마지막 카드는 곡의 비트(
hit: true)와 같은 시간으로 합니다. 이렇게만 해도 전체가 정리되어 보입니다. - 볼륨과 음색은 변경하지 않습니다.
kit/audio.js의 값은 측정하여 균형을 맞춰 놓았습니다. 들을 수 없는 상태에서 변경하면 깨집니다. 변경하는 것은 곡의 구간(plan.js)과 효과음의 신호뿐입니다. - 측정하지 않은 것을 '확인했습니다'라고 작성하지 않습니다. 소리는 듣지 않았다고 보고에 반드시 작성합니다.
진행 방법
각 단계의 끝에 확인이 있습니다. 확인을 건너뛰고 앞으로 진행하지 않습니다(나중에 수정하는 것이 더 비용이 많이 듭니다).
긴 작업이 됩니다. 도중에 맥락이 압축되어 세부 사항을 잊은 경우, 이 파일과 해당 단계의 references/를 다시 읽습니다. 결정한 구성(장면 표)은 src/js/plan.js의 시작 부분에 주석으로 남겨 두면, 중단되어도 이어서 시작할 수 있습니다.
0. 준비
node "${CLAUDE_SKILL_DIR}/scripts/new.mjs" video # 템플릿을 video/에 복제합니다 (위치는 요청에 맞춤)
cd video && npm install --ignore-scripts
node render.mjs --sheet 0:29:2 # 샘플 동영상 목록이 out/sheet.png에 출력되면 준비 완료
${CLAUDE_SKILL_DIR}은 이 SKILL.md가 있는 폴더입니다. 문자 그대로 보일 때는, 이 파일이 있는 폴더의 절대 경로로 대체하여 읽습니다.
브라우저가 없다고 하면 npx playwright install chromium을 실행합니다. 그것이 불가능한 환경이면, 설치된 Chrome이나 Edge를 사용합니다(환경 변수 CHROME_CHANNEL=chrome 또는 CHROME_CHANNEL=msedge를 추가하여 실행). ffmpeg가 없으면 설치해 달라고 요청합니다.
먼저, 샘플의 src/js/scenes.js와 출력된 out/sheet.png를 확인합니다. 부품의 사용법은 설명보다 샘플이 더 빠릅니다.
1. 조사하기
동영상에 포함할 사실은 모두 출처가 있어야 합니다. README·문서·디자인 규칙(색상, 글꼴, 여백)·실제 화면의 문구를 읽고, 다음을 정리합니다.
- 누구의, 어떤 고민을, 어떻게 해결하는 제품인가 (한 문장으로)
- 보여주고 싶은 기능과 그 근거 (어떤 자료의 어디인지)
- 화면에 표시되는 실제 문구 (버튼명, 상태 표시, 알림 문장). 지어낸 문구는 실제 제품을 아는 사람에게 바로 들통납니다
- 실제 화면의 모양. 스크린샷이나 화면 모크업이 있으면 열어서 보고, 배치·색상·여백·글자 크기를 파악합니다. 없으면 UI 코드(컴포넌트, 스타일, 디자인 토큰)에서 읽어냅니다
- 대응 환경·제약·전제 (과장하지 않기 위해)
요청에 명시되지 않은 경우, 다음은 스스로 결정하여 진행하고 보고에 작성합니다: 분량(60~90초, 짧은 요청이면 30초), 언어(제품 화면과 자료의 언어), 밝은 장면과 어두운 장면의 구분 사용. 브랜드의 색상·로고·글꼴이 자료에서 확인되지 않을 때만 요청자에게 문의합니다.
2. 구성 결정하기
장면 구성을 표로 만든 후에 제작을 시작합니다. 구성의 유형, 제목 작성법, 시간 결정 방법은 references/direction.md에 있습니다. 반드시 읽습니다.
- 전달할 것은 1개. 장면별 제목도 1개. 기능 목록으로 만들지 않음
- 시작은 제품이 아닌 '고민'으로 시작
- 장면의 경계는 마디의 처음에 배치 (120 BPM이면 2초 단위)
src/js/plan.js의T(장면 시작 시간)와music(곡 구간)을 이 표에 따라 작성
결정한 구성은 요청자에게 간략히 보여줍니다. 답변을 기다리지 않고 제작을 시작해도 좋습니다(방향이 다르면 빨리 알 수 있음).
3. 브랜드와 화면 준비하기
src/js/brand.js: 이름, 한마디, 색상, 글꼴, 로고. 값은 제품의 실제 정보에서 가져옴src/css/app.css와scenes.js의 HTML: 제품 화면의 재구성. 색상·크기·문구를 실제 제품에 맞춤- 움직이기 전에, 정지된 상태에서
--shot으로 출력하여 실제 제품과 비교. 여기서 비슷하지 않으면 움직여도 비슷해지지 않음
화면은 스크린샷을 붙여넣지 않고 HTML로 다시 만듭니다. 부품별로 움직일 수 있고, 확대해도 선명하며, 색상을 장면에 맞춰 전환할 수 있기 때문입니다.
4. 장면을 하나씩 만들기
scenes.js에 장면별 함수를 작성합니다. 부품과 작성 방법은 references/kit.md에 있습니다. 작성하기 전에 읽습니다.
1장면마다 다음을 반복합니다.
node render.mjs --sheet 14:26:1 # 해당 장면을 1초 간격으로 나열 → out/sheet.png를 열어 확인
node render.mjs --shot 16.6,22.5 # 신경 쓰이는 시간을 등배로 확인 → out/shots/
node render.mjs --sheet 13.4:14.6:0.1 --cols 7 --w 420 # 장면 전환은 0.1~0.2초 단위로 확인
node render.mjs --lint # 글자 잘림·겹침·너무 작은 글자를 기계로 찾음
확인 시 관점: 글자가 잘리지 않았는가, 겹치지 않았는가, 읽을 수 있는 크기인가, 여백이 치우치지 않았는가, 무엇을 봐야 하는지 한눈에 알 수 있는가, 이전 장면의 부품이 남아 있지 않은가.
기준으로, 1장면당 3회는 확인합니다(작성 직후, 수정 후, 전후 전환). 목록에서 작아서 잘 보이지 않는 부분은 --shot으로 등배로 확인합니다.
5. 음향 추가하기
곡은 plan.js의 구간 배열에서 자동으로 편곡됩니다. 효과음은 장면을 구성할 때 cue("종류", 시간)으로 쌓은 것이 재생됩니다. 자세한 내용은 references/audio.md를 참조.
node render.mjs --audio # out/audio-master.wav. 볼륨과 피크가 표시됨
6. 전체 미리보기 및 검품
node render.mjs # 등배로 전체 (30초 동영상이면 수분). out/video.mp4
node tools/qa.mjs # 형식·볼륨·1프레임 어긋남·프레임 드롭·완전 검정·무음
node render.mjs --sheet 0:90:1.5 --cols 6 # 전체를 한눈에 확인 (초 수는 동영상 길이에 맞춤)
✗를 모두 수정합니다. 검품 결과 읽는 방법과 자주 발생하는 불량 수정 방법은 references/qa.md를 참조.
여기서부터가 마무리 작업입니다. 처음 통과한 버전은 대개 '깨지지는 않았지만, 아쉽다'입니다. 전체 목록을 보고 수정하고 싶은 부분을 5개 이상 기록한 후 수정합니다. 찾아야 할 것:
- 2초 이상 아무 일도 일어나지 않는 시간
- 크게 비어 있는 여백, 한쪽으로 치우친 그림, 컨테이너 안의 공백 (스마트폰의 아래 절반이 하얀 경우 등)
- 무엇이 무엇을 발생시켰는지 알기 어려운 순간 (누름 → 변화의 대응이 보이지 않음)
- 실제 제품과 다른 문구·색상·배치
5개를 찾을 수 없을 때는 확인 간격이 너무 성긴 것입니다. 1초 간격, 신경 쓰이는 장면은 0.25초 간격으로 다시 확인합니다.
7. 본편 출력하기
node render.mjs --scale 2 --keep-frames # 2배로 촬영 후 축소 (등배의 3~4배 시간). 백그라운드에서 실행
node tools/qa.mjs
node tools/extras.mjs web # 10MB 미만의 경량 버전 (채팅이나 GitHub에 첨부 가능)
node tools/extras.mjs poster 15.9 # 제목용 정지 이미지 (내용이 가장 잘 전달되는 시간)
2배로 촬영하는 이유는 윤곽을 부드럽게 하고 글자의 떨림을 절반으로 줄이기 위해서입니다. 도중에 멈추면 --resume --reuse-audio를 추가하여 이어서 촬영합니다. 일부만 수정한 경우, 해당 구간의 프레임(out/frames/의 해당 번호)을 삭제하고 --resume하면, 그 부분만 다시 촬영할 수 있습니다.
8. 보고하기
다음을 간결하고 솔직하게 작성합니다(양식은 references/qa.md의 마지막 부분).
- 완성된 것 (파일 위치, 길이, 해상도)과 구성 요약
- 확인한 것 (검품 결과)과 확인되지 않은 것. 소리는 듣지 않았으므로, '수치상으로는 기준 내. 청감 확인 부탁드립니다'라고 반드시 작성
- 스스로 결정한 것 (길이, 언어, 색상 처리 등)과 자료에서 확인할 수 없어 넣지 않은 것
- 화면이 재구성된 것임. 수정하고 싶을 때의 요청 방법
수정을 요청받았을 때
이 템플릿으로 만든 동영상을 수정할 때는 위의 절차를 처음부터 다시 하지 않습니다.
references/kit.md와 수정할 부분의 코드를 읽습니다. 제목이나 화면 문구는scenes.js, 마지막 카드의 글자는brand.js, 시간표와 곡은plan.js, 화면의 모양은app.css- 수정합니다
- 수정한 장면과 그 전후 전환을
--shot/--sheet로 확인합니다.--lint도 실행합니다 - 장면 길이나 곡 구간을 변경한 경우,
node render.mjs --audio에서 경고가 나오지 않는지도 확인합니다 - 동영상 파일까지 요청되었다면 절차 6~7로 출력합니다. 요청되지 않았다면, 어디를 확인했는지 보고하고 출력할지 여부를 문의합니다
node render.mjs를 옵션 없이 실행하면 전체 출력이 시작됩니다(수분 이상 소요). 확인만 할 때는 반드시 --shot --sheet --lint 중 하나를 추가합니다. 사용법은 node render.mjs --help.
완성도 기준
제출하기 전에 스스로 확인합니다.
- 소리를 꺼도 제목만으로 이야기가 통함
- 처음 3초 안에 '자신의 이야기'라고 생각할 수 있는 고민이 제시됨
- 어떤 장면에서도 봐야 할 곳이 하나로 정해져 있음 (동시에 2곳을 움직이지 않음)
- 제품을 아는 사람이 보고, 화면과 문구에 위화감이 없음
--lint와tools/qa.mjs에✗가 없음- 장면 전환을 0.1~0.2초 단위로 모두 확인함
참고 자료
| 파일 | 내용 | 언제 읽을까 |
|---|---|---|
references/direction.md | 구성 유형, 시간표, 제목 작성법, 화면 배치, 움직임 | 구성 결정 전 (필수) |
references/kit.md | 템플릿 파일의 역할과 엔진·부품 사용법 | 장면 작성 전 (필수) |
references/audio.md | 곡 구간, 효과음 신호 목록, 측정 방법 | 음향 추가 시 |
references/qa.md | 검품 결과 읽기, 자주 발생하는 불량과 수정법, 보고 양식 | 미리보기 및 본편 출력 시 |
assets/template/src/js/scenes.js | 샘플 동영상. 부품 사용법의 실제 예 | 처음에 |