Velxio 프로모 비디오 파이프라인
세 단계로 구성되며, 모든 작업은 이 저장소(velxio_docs; 개발 박스의 /home/dave/velxio_docs)에서 이루어집니다:
- 녹화 (Record) —
scripts/promo-record.mjs가 스테이징 환경의 실제 제품을 처음부터 끝까지(빈 프로젝트 → ESP32 선택 → 220R + LED 추가 → 저항 회전 → 핀 클릭으로 배선 → 스케치 작성 → 실행 → LED 깜빡임) 구동합니다. 보이는 커서와 클릭 ripple이 삽입됩니다. 각 동작은{t, x, y, label}형태로scripts/promo-events.json에 기록되며, 원본 비디오는scripts/promo-raw.webm에 저장됩니다. - 비트 그리드 (Beat grid) —
scripts/detect-beats.py(librosa)가 사운드트랙을 분석하여promo/src/beats.json(BPM, 비트, 킥, 스네어)을 생성합니다. - 후편집 (Post) —
promo/는 Remotion 프로젝트입니다.prepare.mjs가 녹화본을 복사하고, 로딩 전 모달 부분을 자르고, 컴파일 대기 시간을 제거한 후src/data.json을 생성합니다.src/Promo.tsx가 카메라 워크(각 비트 중심의 줌인, 각 펀치는 0.34초 이내의 가장 가까운 음악 비트에 SNAPPED됨), 자막 캡슐, SOLID 인트로 카드, velxio.dev 아웃트로, 그리고 페이드 인/아웃이 적용된 음악 트랙을 렌더링합니다.
명령어
cd /home/dave/velxio_docs
VELXIO_SHOTS_EMAIL='[email protected]' VELXIO_SHOTS_PASSWORD='<see memory vstaging-test-user>' \
node scripts/promo-record.mjs # 녹화 (~2-4분, 컴파일 시간 포함)
python3 scripts/detect-beats.py promo/public/soundtrack.mp3 --fps 30 \
--out promo/src/beats.json # 트랙이 변경될 때만 실행
cd promo && node prepare.mjs
CHROME=$(ls -d $HOME/.cache/ms-playwright/chromium-*/chrome-linux*/chrome | sort | tail -1)
npx remotion render src/index.ts promo out/velxio-demo.mp4 --codec h264 --browser-executable "$CHROME"
GitHub에서 볼 수 있도록 게시된 복사본: media/velxio-demo.mp4 (커밋됨; 덮어쓰기 + 푸시로 동일한 URL 업데이트). promo/out/은 gitignore에 포함됩니다.
검토 절차 (절대 건너뛰지 마세요)
비트 시점에 promo/src/data.json의 ct 필드를 기준으로 프레임을 샘플링하세요. 시간을 추측하지 마십시오. 비트는 테이크마다 이동합니다:
ffmpeg -y -ss <ct> -i out/velxio-demo.mp4 -vframes 1 /tmp/beat.png
확인할 사항: 인트로가 읽을 수 있는지, 각 줌이 대상물을 자르지 않고 보여주는지, 자막이 화면과 일치하는지, 커서가 중요한 부분을 가리지 않는지, 최종 LED가 켜져 있는지.
갤러리 데모 비디오 (기본 모드)
갤러리 예제별 짧은 비디오 하나씩 — 열고, 실행하고, 작동하는 모습을 촬영합니다. 분할 인트로 카드(왼쪽에 로고 + 제목 + 보드, 오른쪽에 전체 회로), 하단 서드, 아웃트로 및 사운드트랙이 포함됩니다.
node scripts/gallery-scan.mjs # 406개 예제 인덱스 새로고침
node scripts/gallery-record.mjs [slug ...] # 1080p 테이크 (완료된 것은 건너뜀)
node scripts/gallery-bbox.mjs [slug ...] # 회로 위치 측정
node scripts/gallery-render.mjs [slug ...] # 테이크당 하나의 mp4 생성
- 선별된 데모 세트는
gallery-record.mjs상단의GALLERY에 있습니다; slug는scripts/gallery-index.json에서 가져옵니다(썸네일 파일명 = slug). - 스틸 이미지는
deviceScaleFactor: 2로 캡처되며 회로 bbox로 크롭됩니다: 1080p 프레임 절반을 채울 만큼 선명합니다. 작은 회로를 확대하기 위해 앱을 줌하지 마십시오 — 캔버스 앵커가 중앙을 기준으로 줌하여 회로가 화면 밖으로 벗어납니다; 대신 보기를 재설정하십시오. - bbox 패스는 예제마다 새로운 컨텍스트가 필요합니다: 두 번째 클라이언트 측 에디터 로드는 캔버스를 비워 두고 아무것도 측정하지 않습니다.
- 보드가 없는 예제(Digital / Analog 카테고리)는 컴파일되지 않습니다: Run은 비활성화된 상태로 유지되며 SPICE 엔진이 처음부터 구동합니다.
- 백엔드-QEMU 보드(STM32, Raspberry Pi)는 "in-browser" 콘솔 라인을 출력하지 않습니다. STM32는 스테이징 환경에서 전혀 실행되지 않습니다(libqemu-arm이 선택적 자산으로 배포되지 않음) — 3초 만에 컴파일되지만 시작되지 않습니다.
- 원본 테이크(
promo/public/gallery/*.webm, 각각 ~3MB)는 gitignore에 포함됩니다; 회로 스틸과 테이크별 JSON은 커밋되므로 재렌더링 시 테이크만 다시 가져오면 됩니다.
음악
promo/public/soundtrack.mp3 = Mixkit "House Vibez" by Lily J
(assets.mixkit.co/music/745/745.mp3), Mixkit Stock Music Free License —
상업적 사용 가능, 저작자 표시 불필요. 123 BPM. 파일을 교체하고 detect-beats.py를 다시 실행하여 트랙을 변경하십시오; 라이선스 노트는 promo/public/ATTRIBUTION.md에 최신 상태로 유지하십시오.
트랙은 약 8초의 인트로로 시작됩니다: Promo.tsx의 AUDIO_START는 첫 번째 감지된 비트이므로 오디오가 그 지점부터 시작되고 구성 t=0은 다운비트에 맞춰집니다. 볼륨은 0.55로 고정되며 0.9초 페이드인과 아웃트로 위의 2.2초 페이드아웃이 적용됩니다.
스타일 규칙 (David의 리뷰에서 — 유지할 것)
- 비디오는 보드-템플릿 모달이 이미 열려 있고 AI 채팅이 이미 최소화된 상태로 에디터에서 시작합니다. 홈 페이지, 로딩, 또는 보이는 최소화 클릭이 없어야 합니다(리코더가 시작 전에 프로그래밍 방식으로 처리; prepare.mjs가
start마크까지 트림). - 인트로 제목은 SOLID 다크 카드 위에 — UI 위에 반투명하게 절대 표시하지 않음.
- 줌은 대상물을 중앙에 두어야 함: translate+scale로 가장자리 클램프 적용(
Promo.tsx), transformOrigin-at-point 사용 금지(대상물이 테두리에 위치하여 잘림, 예: ESP32 카드). - 부품 추가 비트는 짧은 유지(~0.5초)를 사용하여 줌이 제때 해제되어 부품이 어디에 놓였는지 볼 수 있게 함.
- 코드 비트는 전체 에디터 창을 중앙에 배치(fx/fy 재정의 357,280) — 코드가 작성되는 동안 잘린 에디터를 표시하지 않음.
- Run을 클릭한 후에는 커서를 다른 곳(리코더가 700,620으로 이동)에 배치하여 버튼 스피너가 보이도록 함; Run 비트는 컴파일/실행 클러스터를 중간 정도 배율(1.8)로 줌.
- 저항기를 수평으로 회전시켜 배치(수직 기본값은 카메라에서 자체 와이어와 겹침).
- 포인터는 macOS 스타일의 화살표여야 하며 점이 아니어야 함. 핫스팟 계산(saas-product-demo 스킬에서 가져옴): 끝부분이 24x24 viewBox의 (4,3)에 위치하므로 렌더링 너비 W에서 요소는 (-4W/24, -3W/24)만큼 오프셋됨 — 이 계산 없이 모든 클릭이 약 6-8px 정도 빗나감.
- 줌 펀치는 음악 비트에 맞춰짐(위의 음악 참조); 펀치가 클릭에서 ~0.3초 이상 벗어나지 않도록 함.
- 클릭 시 모달이 닫히는 비트(choose-blank, add-*)는
lead: 0.45가 필요함: 피크가 클릭 전에 와야 프레임에 카드가 아직 열려 있는 상태가 표시되고, 릴리즈 시 부품이 어디에 놓였는지 드러남. 클릭 자체에 피크가 맞춰지면 이미 닫힌 대화상자에 줌이 머물게 됨.
트랩 (각각 테이크 실패를 유발 — 모두 스크립트에서 처리됨)
/는 로그인 상태에서도 마케팅 랜딩 페이지이며, 에디터는/editor입니다. 먼저 페이지 내에서 fetch POST/api/auth/login으로 로그인하십시오.- 새
/editor에서는 "Start a new project" 오버레이가 자동으로 열리며(.new-project-overlay), 외부의 모든 포인터 클릭을 가로챕니다. 먼저 이와 상호작용하거나 프로그래밍 방식으로 클릭을 전달하십시오. - 피커의 검색 입력은 이전 쿼리를 유지합니다 — 입력 전에 Ctrl+A + Backspace로 지우지 않으면 "esp32led"처럼 검색합니다.
- 피커 카드:
.component-picker-modal .card-name에 대해 정확한 제목 일치(일반 텍스트 엔진은 모달 뒤의 Monaco 토큰을 찾음). 결과는 디바운스됩니다 — 최대 5초까지 폴링하십시오. 카드 이름은 정확합니다("Resistor 220 Ω"에는 U+2126 스타일 문자가 포함됨, "Resistor"가 아님;/components-metadata.json확인). - 보드는 본체에서 드래그하지 않음 — 피커가 놓은 위치(캔버스 오른쪽 가장자리 근처)에 보드를 두고 작은 부품을 라이브 boundingBox 기준 상대적으로, 공간이 있는 왼쪽에 배치하십시오.
- 핀 클릭: 앱 자체의
[data-pin-overlay="true"][title=<pin>]div를 사용하되, 호스트 요소에 가장 가까운 후보를 선택하십시오(제목 충돌: 보드 핀 "2"와 저항 핀 "2").pinInfo오프셋에서 계산하면 회전된 부품에서 깨집니다. ESP32 핀은 '2','4','GND'로 명명됩니다(D2 아님). - 각 와이어가 제대로 연결되었는지
path[stroke="#1a1a1a"](모든 와이어의 외곽선 경로; 와이어에는 클래스/데이터 속성 없음)를 세어 확인하십시오. 실패 시 Escape + 재클릭으로 한 번 재시도하십시오. - 회로 검증기는 직렬 저항이 없는 LED에 대해 Run을 차단합니다(3.9e12 A 오류). 데모는 GPIO2 -> 220R -> LED A, GND -> LED C를 연결해야 합니다. "Run anyway"가 필요했던 테이크를 절대 제공하지 마십시오.
- 빈 템플릿은 기본 스케치를 제공합니다 — 화면에 보이는 주석을 입력하기 전에 Ctrl+A를 누르고,
monaco.editor.getModels()[0].setValue(...)를 사용하여 전체 스케치를 설정하십시오(리터럴 타이핑은 Monaco의 자동 닫기 괄호와 충돌). - "Enjoying Velxio? / Star on GitHub" 토스트 메시지는 약 5분 이상 지속되는 테이크를 방해합니다 — 주입된 클리너가 매초 이를 제거합니다.
- Playwright는 OS 커서를 기록하지 않습니다: 주입된
#vx-cursordiv + mousedown ripple(context.addInitScript)이 보이는 포인터 역할을 합니다. - 컴파일 대기는 실제 시간이 소요됩니다(워밍업 30초 - 콜드 5분): 리코더가
run과sim-started를 기록하고 prepare.mjs가 해당 구간을 제거합니다. 시뮬레이션 시작은 "in-browser" 콘솔 라인으로 감지됩니다; "guest crashed" = 중단. - Remotion은
--browser-executable을 통해 Playwright의 Chromium으로 렌더링합니다(이 박스에 번들 브라우저가 다운로드되어 있지 않음).
참고 저장소 (2026-08-15 검토)
동일한 장르의 두 Claude-Code 스킬로, 다른 접근 방식을 사용합니다: 스크린샷/개략적 UI에서 합성 커서로 영화를 구성합니다 — 우리의 것은 실제 제품을 녹화합니다. 스킬을 향상시킬 때 포크하십시오:
github.com/noamdorr/saas-product-demo-video— Remotion에서 20-45초 비트 동기화 마케팅 필름. 가져올 것: 사운드트랙 비트 동기화(librosadetect-beats.py: 스네어에서 장면 전환, 킥에서 장면 내 이벤트 — 우리 비디오는 무음이므로 음악 추가 및 줌 비트 정렬이 가능한 가장 큰 업그레이드), 커서 핫스팟 수학(SVG 화살표 끝은 24박스의 4,3에 위치 — 점 커서를 화살표로 바꾸는 경우 중요), 타이핑 예산 확인, 반복 플레이북.github.com/Vincentwei1021/video-shotcraft— 데모 소스 + 갤러리(vincentwei1021.github.io/video-shotcraft), 승인된 템플릿(Ink Press), 2.5D 카메라 움직임, 사운드 디자인 및 세 가지 운영 모드(템플릿 / 자율 / 공동 제작)가 포함된 152개의 샷 레시피 카드. 가져올 것: 연출적 샷 어휘와 최종 검토 체크리스트; 본격적인 시네마틱 필름이 필요할 경우 플러그인으로 설치할 수도 있음.
관련
- 스크린샷 파이프라인:
scripts/shots.mjs(동일 저장소) — 장면은 주제가 아닌 경우 AI 패널이 최소화된 상태. - GIF: 세그먼트 녹화(Playwright recordVideo) 후 ffmpeg two-pass 팔레트로 변환; 줌+크롭 패턴은
scripts/weather-media.mjs참조(요소의 boundingBox에서 크롭 영역 가져오기). - 스테이징 테스트 사용자: memory
vstaging-test-user(s3-tester, pro). - 고장난 쇼케이스 프로젝트(날씨 스테이션)는
project/docs-portal-2026-08/STATUS.md(velxio-prod)에서 추적됨.