Framewright (프레임라이트)
당신은 판화 제작자가 판화를 찍듯이 짧은 비디오를 만들 것입니다. 하나의 HTML 파일에 캔버스, '플레이트(plate)'라고 불리는 장면 목록, 그리고 (프레임 번호, 시드, 너비)로부터 온디맨드로 모든 프레임을 그리는 프레임 함수가 있습니다. 푸티지, 이미지 파일, CDN의 어떤 것도 없습니다. 프레임은 헤드리스 Chrome에서 렌더링되고 ffmpeg로 조립됩니다. 사운드는 마지막에 잠긴 장면 길이에 맞춰 합성됩니다.
사용자와 그들의 언어로 대화하십시오. 사용자가 서두르더라도 아래 규칙을 지키십시오. 여기서 모든 지름길은 절약하는 것보다 더 많은 시간을 소모합니다.
SKILL_DIR은 이 파일이 포함된 폴더를 의미합니다 (Claude Code에서는 ${CLAUDE_SKILL_DIR}, 다른 곳에서는 .agents/skills/framewright). 3단계 이후 스크립트는 프로젝트 자체의 scripts/ 폴더에도 위치합니다.
절대 타협할 수 없는 규칙 (Non-negotiables)
- 프레임은 오직 프레임 번호, 시드, 출력 너비에만 의존합니다.
Math.random, 벽시계, CSS 애니메이션,requestAnimationFrame을 시간 소스로 사용하지 마십시오.scripts/check.mjs는 모든 전체 렌더링 전에 이를 증명합니다. - 이미지, 비디오, 폰트, 라이브러리 파일, base64, CDN이 없습니다. 스크립트가 사진에서 추적한 다각형은 데이터이며 괜찮습니다. 시스템 폰트만으로 충분합니다.
- 렌더링된 프레임을 보고 검증하며, 코드를 읽고 상상하는 방식으로 절대 검증하지 않습니다. 어떤 수정사항도 프레임으로 확인합니다.
- 한 번에 한 장면씩 작업합니다. 플레이트를 작성하고, 세 개의 프레임을 촬영하고, 보고, 수정한 후 다음 플레이트로 넘어갑니다.
- 장면 길이는 첫 번째 초안의 비트(beat) 배수입니다. 사운드는 마지막에 작업합니다.
- 전체 비디오의 연락처 시트(contact sheet)가 올바르게 보인 후에만 전체 렌더링을 수행합니다.
frames/,shots/,*.mp4,*.wav또는 사용자의 사진을 절대 커밋하지 마십시오.- 시스템 패키지를 설치하기 전에 물어보십시오. 사용자가 사용할 권한이 있다고 확인하지 않고 실제 사람의 초상을 절대 게시하지 마십시오.
워크플로우 한눈에 보기
| 단계 | 작업 내용 | 사용자와의 체크포인트 |
|---|---|---|
| 0 | 환경 확인 | 설치해야 할 것이 있을 때만 |
| 1 | 브리핑 (Brief) | 설문지, 한 블록 |
| 2 | 콘셉트 (Concepts) | 세 가지 옵션, 하나 선택 |
| 3 | 스토리보드 + 스캐폴드 (Storyboard + Scaffold) | 한 번의 "승인" |
| 4 | 장면별 작업 (Scenes, one by one) | 2~3개 장면마다 연락처 시트 |
| 5 | 자산 (사진, 로고) | 추적된 초상화 미리보기 |
| 6 | 전체 시트, 렌더, 빌드 | 미리보기 MP4 |
| 7 | 사운드 | 재빌드된 MP4 |
| 8 | 전달 및 변형 | 최종 |
일반적인 소요 시간: 브리핑 및 콘셉트 10분, 스캐폴드 10분, 각 장면 1020분, 1080p 비디오 1분당 렌더링 24분, 사운드 15분.
0단계. 환경
bash SKILL_DIR/scripts/doctor.sh # 보고; --json을 추가하면 기계가 읽을 수 있는 요약
bash SKILL_DIR/scripts/doctor.sh --install # 누락된 항목 설치, 각 단계 전에 질문
필수: Node 20+, npm, libx264가 포함된 ffmpeg, 프로젝트 내 Chrome이 있는 Puppeteer.
선택: 사진을 추적할 때만 Python 3과 numpy, scipy, Pillow. doctor가 누락된 도구를 보고하면, 사용자에게 무엇이 어디에 설치될지 알리고 동의를 얻은 후 --install을 실행하십시오 (사용자가 이미 동의한 경우 --yes 추가). 3단계를 doctor 실패 상태에서 시작하지 마십시오.
1단계. 브리핑 (The Brief)
references/questionnaire.md의 설문지를 실행하십시오. 구조화된 질문 도구가 있을 경우 그것을 사용하십시오 (Claude Code: AskUserQuestion, 한 번 호출에 최대 4개 질문, 각 질문에 2~4개 옵션, 권장 옵션을 먼저 배치하고 표시). 그러한 도구가 없으면 질문을 번호 목록과 알파벳 옵션, 각 질문별 기본값과 함께 인쇄하고 1b 2a 3c와 같은 간결한 답변을 허용하십시오. 항상 다음 순서로 질문하십시오:
- 메시지: 시청자가 이해하거나 느껴야 할 것을 한 문장으로. 화면에 표시되어야 하는 이름, 날짜, 링크, 정확한 문구.
- 재생 위치 및 형식: 16:9, 9:16, 1:1, 4:5.
- 길이: 10
15, 2025, 3040, 4560초. - 분위기: 장난기, 무표정, 따뜻함, 장엄함, 기술적, 어두움.
- 스타일: 제안하도록 함 (기본값) 또는
references/styles.md의 패밀리. - 자산: 사진, 로고, 스크린샷. 파일은 프로젝트 폴더에 배치되어야 합니다. 채팅에 붙여넣은 이미지는 저장할 수 없습니다. 실존 인물에 대한 권리에 대해 질문하십시오.
- 사운드: 합성, 없음, 또는 사용자 트랙을 위한 공간 (BPM을 물음).
- 결과물 및 품질: 초안 또는 최종, 추가 컷 (세로, GIF, 포스터 프레임, 시드 변형), MP4의 첫 번째 프레임으로 커버 (피드에서 미리보기로 표시됨).
사용자가 "알아서 결정하라"고 하면 기본값을 선택하고, 어떤 기본값을 선택했는지 말한 후 진행하십시오.
2단계. 콘셉트 (Concepts)
코드 작성 전에 정확히 세 개의 콘셉트를 제안하십시오. 세 가지 관점에서 구성하십시오: 직역 (주제 보여주기), 은유 (상황을 나타내는 시각 시스템: 기다림에 대한 TV 테스트 카드, 계획에 대한 청사진, 프로세스에 대한 터미널), 장르 패러디 (뉴스 속보, 아케이드 어트랙트 모드, 안전 카드, 일기 예보, 텔레텍스트). 각 콘셉트에 대해 다음을 제공하십시오: 이름, 한 문장 로그라인, 시각 시스템 및 팔레트, 순서대로 35개의 핵심 장면, 결말, 사운드 스케치, 적합한 이유, 주요 위험. 간결한 표를 제시하고 하나를 추천한 후 사용자가 선택하도록 하십시오. 방법과 작업 예제는 16)는 페인팅 필름으로 구축됩니다 (references/questionnaire.md에 있습니다. 하나의 여정을 컷 없이 (절대 떠나지 않는 선, 세부 사항에서 풍경으로의 비행) 진행하는 콘셉트는 하나의 연속된 세계로 구축됩니다 (references/world.md); 해당 행에 그렇게 명시하십시오. 장면이 구축되고 변경되는 방식이 달라지기 때문입니다. 하나의 뷰가 그려지고, 채색되고, 생명을 얻는 콘셉트 (연필 그림이 생생한 유화가 되는, 스타일 13references/painting.md); 해당 행에도 명시하십시오: 플레이트는 하나의 그림 위에서의 시간의 장이며, 장면은 그 작업입니다.
3단계. 스토리보드 및 스캐폴드 (Storyboard and scaffold)
storyboard.md를 채우십시오 (템플릿은 assets/storyboard.md): 플레이트당 한 행, 마디 단위 길이, 비트 그리드 (30fps, 120 BPM: 비트 15프레임, 마디 60), 주요 이벤트의 초, 총합. 총합은 합의된 길이와 같아야 합니다. 나중에 장면이 추가되면 다른 장면이 한 마디를 양보합니다. 표를 보여주고 한 번의 승인을 요청하십시오.
그런 다음 스캐폴드:
bash SKILL_DIR/scripts/init.sh # index.html, scripts/, audio.mjs, storyboard.md, package.json
bash SKILL_DIR/scripts/init.sh . --world # 동일하지만 index.html은 원-월드 스켈레톤 (컷 없음)
bash SKILL_DIR/scripts/init.sh . --painting # 페인팅 스켈레톤; audio.mjs와 nature.mjs가 자연 사운드 생성
npm install # puppeteer (Chrome은 캐시에서 제공되거나 한 번 다운로드됨)
node scripts/look.mjs shot 0,30,60 1200 7
이미지 보기 도구로 PNG를 열어 확인하십시오. 스켈레톤의 데모 플레이트를 건드리기 전에 렌더링해야 합니다. 그런 다음 데모 플레이트를 첫 번째 장면으로 교체하십시오. 페인팅 필름에서 프레임 0은 빈 종이입니다: 대신 30,82,200 (그림, 색상 도착, 생생한 그림)을 촬영하고, 페인팅 키트는 그대로 두고 데모의 씬 블록을 교체하십시오.
4단계. 장면 (Scenes)
첫 번째 장면 전에 references/guide.md를 한 번 읽으십시오. 필수 사항:
plate('name', {len: 2*BAR}, (S, R) => { ... }).S.t는 진행도 0..1,S.i는 로컬 프레임,S.f는 글로벌 프레임,S.g는 논리 좌표 (짧은 쪽 1080)의 2D 컨텍스트입니다.R은 전체 플레이트에 대해 안정적인 생성기입니다;S.b는 3프레임마다 리시드되고 생생한 라인을 제공합니다;S.nz는 노이즈를 위해 매 프레임 변경됩니다. 시드는 플레이트 이름에서 파생됩니다.- 모든 지오메트리는 논리 단위이며,
CX, CY를 통해 중앙 정렬됩니다. 중요한 콘텐츠는 프레임의 중앙 92% 내에 유지하십시오; 9:16에서는 상단 15%와 하단 20% 밖에도 유지하십시오 (플랫폼의 버튼과 캡션이 있는 곳). - 플레이트 내부의 시간은 비트 단위:
Math.floor(S.i/BEAT),span(S.i, a, b),ease.out(...). 장면 내부에 전역 프레임 번호를 하드코딩하지 마십시오. 컷을 넘어 계속되는 것은 다른 플레이트의 시간을 이름으로 사용합니다 (at('plate', beats)); 사운드가 맞춰야 할 순간은CUES에 들어가고cue('name')으로 읽습니다. - 헬퍼는 플레이트 블록 위에 위치하며, 플레이트 사이에 두지 마십시오.
- 전환은 엔진이 플레이트 가장자리에서 그립니다 (
cutIn/cutOut플래그); 장면은 이에 대해 알 필요가 없습니다. - 월드 필름 (
--world)에는 컷이 없습니다. 플레이트는build(W, {pen, b, R})빌더이며, 하나의 타임라인에 추가합니다:b(x)는 플레이트의 비트 x이고, 펜은 이전 플레이트가 끝난 곳에서 선을 계속 이으며,camKey는 하나의 카메라를 이동시키고,title은 텍스트를 추가합니다. 첫 번째 빌더 전에references/world.md를 읽으십시오. - 페인팅 필름 (
--painting)은 세 가지 매체로 하나의 정지 그림을 그립니다. 모든 플레이트는drawPainting(S)를 호출합니다; 뷰는SCENE객체와 그 레이어 (/* ===== scene ===== */와/* ===== end scene ===== */사이)에 있으며, 위의 페인팅 키트는 그대로 유지됩니다. 첫 번째 레이어 전에references/painting.md를 읽으십시오. 먼저 드로잉만 검토한 다음, 리빌, 생명 (references/painting.md, 섹션 13)을 검토하십시오.
장면별: 작성하고, 첫 번째 프레임, 중간 프레임, 끝에서 5프레임 전 프레임을 촬영하고, 보고, 수정하고, 다시 확인하십시오. 2~3개 장면마다:
node scripts/look.mjs sheet 24 480 7 shots/sheet.png
FROM=240 TO=480 node scripts/look.mjs sheet 12 480 7 shots/sheet-b.png # 한 섹션, 더 조밀하게
시트에 대한 체크리스트, 각 항목을 눈으로 확인: 모든 셀이 1.5초 안에 스틸로 읽힘; 텍스트가 잘리지 않음; 인접한 선과 패널이 닿지 않음; 한 플레이트의 인접한 두 셀이 동일하게 보이지 않음; 컷이 계획된 곳에 있으며 장면의 첫 번째 프레임을 먹지 않음; 중요한 것이 바깥쪽 8%에 없음; 색상이 탁하지 않음; 가는 선이 부서지지 않음; 주요 이벤트가 계획된 초에 있음; 마지막 프레임이 종료처럼 보임; 같은 위치에 두 개의 제목이 함께 화면에 나타나지 않음. "1.5초"에 실패한 장면은 꾸며지지 않고 재설계됩니다. 빈 장면은 더 큰 객체나 더 가까운 카메라를 얻으며, 결코 더 많은 디테일을 얻지 않습니다; 바쁜 클로즈업은 주제 주변의 장식을 어둡게 합니다. 월드 필름에서는 각 플레이트의 마지막 프레임을 다음 플레이트의 첫 번째 프레임 옆에 촬영하십시오: 아무것도 튀어서는 안 됩니다.
이 시점에서 시트를 사용자에게 두 문장의 상태와 함께 보내십시오. 코드를 설명하지 마십시오.
5단계. 자산 (Assets)
사진은 픽셀이 아닌 포스터화된 다각형이 됩니다:
bash scripts/portrait.sh photo.jpg --levels 0.14,0.28,0.42,0.56,0.70,0.84 --height 900
node scripts/look.mjs shot <초상화 장면의 프레임> 1200 7
shots/portrait_preview.png와 렌더링된 프레임을 확인하십시오. references/photo.md에 따라 레벨, 블러, 크롭 및 배경 임계값을 조정하십시오. 파일이 아직 폴더에 없는 동안에는 스켈레톤에 포함된 합성 플레이스홀더를 사용하여 장면을 구축하고 그렇게 말하십시오.
6단계. 전체 시트, 렌더, 빌드 (Full sheet, render, build)
node scripts/look.mjs sheet 24 480 7 shots/sheet.png # 확인, 수정, 반복
node scripts/check.mjs # 결정론 및 자체 포함 여부 확인, OK가 나와야 함
node scripts/render.mjs frames 7 1920 5 # dir, seed, width (생략: 1920 풍경, 1080 세로), tabs
bash scripts/build.sh out.mp4 # frames (+ track.wav) -> mp4
체크는 모든 플레이트의 다섯 프레임을 일곱 가지 순서 (새 탭의 첫 프레임, 순방향, 역방향, 섞기, 이전 프레임 직후, 다른 너비 후, 렌더 탭이 탐색하는 방식으로 필름을 훑은 후)로 렌더링하고 동일한 픽셀을 요구합니다. 또한 소스에서 Math.random, Date 및 performance.now를 스캔하고, 페이지가 파일을 요청하면 실패하며, 비트 그리드와 예상 렌더 시간을 출력합니다. 렌더링 전에 모든 FAIL을 수정하십시오; make.sh는 체크를 실행하고 실패 시 중지됩니다. 렌더 순서에 따라 변경되는 프레임은 프레임 이후에도 지속되는 상태 (지우지 않고 그려진 풀링된 캔버스, 플레이트가 변경하는 전역 변수, 모든 입력 없이 키가 지정된 캐시)를 가리킵니다.
프레임이 아닌 파일에서 검증: ffprobe 프레임 수가 look.mjs info의 총계와 일치, 지속 시간이 프레임 수를 30으로 나눈 값과 일치, MP4에서 만든 타일이 시트와 일치:
ffmpeg -i out.mp4 -vf "select='not(mod(n\,50))',scale=480:-1,tile=6x4" -frames:v 1 shots/out_sheet.png
첫 번째 MP4가 존재하는 즉시 플레이스홀더가 있더라도 사용자에게 보내고, 무엇이 플레이스홀더인지 말하십시오.
7단계. 사운드 (Sound)
scripts/export-curves.mjs는 페이지에서 curves.json을 작성합니다: 플레이트 시작, 큐, 그리고 월드 필름의 경우 모든 프레임에 대한 선頭的 속도 및 화면 위치, 페인팅 필름의 경우 공개된 공유, 생명, gust 및 줌. audio.mjs는 이 타임라인을 사용하므로 그림에서 결코 벗어나지 않습니다 (RISO.curves()가 없는 페이지는 look.mjs info의 시작을 수동으로 T에 복사해야 함). references/audio.md의 큐 맵을 사용하여 플레이트당 하나의 이벤트 블록을 작성하십시오; 그림이 표시하는 것에 맞춰지는 히트는 플레이트에서 복사한 비트 번호가 아닌 페이지 큐 cue('name') (CUES 또는 월드 필름의 W.cues)에서 시간을 가져옵니다; 월드 필름에서는 선 또는 캐릭터에 커브를 따르는 목소리 (follow(), references/audio.md, 섹션 7)를 부여하십시오. 페인팅이나 풍경은 비트 대신 자연 분위기를 얻습니다: 바람, 물, 바스락거림 및 새 소리를 nature.mjs에서 가져오며, --painting이 assets/audio-nature.mjs (references/audio.md, 섹션 8, 자연 분위기)에서 복사하는 audio.mjs에 의해 큐와 페이지의 gust에 맞춰집니다. 그런 다음:
node scripts/export-curves.mjs curves.json
node audio.mjs track.wav
ffmpeg -i track.wav -filter_complex "showwavespic=s=1800x300:split_channels=1" -frames:v 1 shots/wave.png
bash scripts/build.sh out.mp4
듣기는 불가능하므로 파형을 읽으십시오: 활성 장면 내 의도치 않은 침묵 없음, 장면이 천장에 고정되지 않음. 장면 길이가 변경될 때마다 트랙을 다시 생성하십시오.
8단계. 전달 (Delivery)
out.mp4 (1920×1080, H.264, AAC, 시끄러운 스타일에서 초당 약 0.7MB)를 전달하고 재빌드 방법을 알리십시오: bash scripts/make.sh [photo.jpg] [seed] [width]. 제안하되 강요하지 마십시오: 다른 시드 (동일한 플레이트의 다른 인상), 세로 컷 (AR=9:16 node scripts/render.mjs frames-v 7 1080 5, 그런 다음 AR=9:16으로 시트 확인; 하단 제목은 높이의 70%에 위치하며, TITLE_Y, 플랫폼 버튼과 겹치지 않음; 포스터 PNG (node scripts/render.mjs posters 7 1920 1), 더 짧은 컷 (마디 단위로 제거, init.sh의 --trailer LEN), GIF: 기본 palettegen, paletteuse.
MP4가 이미 프로젝트에 있고 사용자가 다시 묻지 않으면 마지막 빌드 명령을 반복하지 마십시오. 빌드에 대한 질문에는 bash scripts/make.sh를 인용하고 차이점을 설명하십시오. 결과물을 설명할 때는 렌더링된 shots/ 파일을 확인하고 명확하지 않은 부분 (시드가 변경한 것과 스타일이 일정한 것)을 지적하십시오.
더 이상 할 일이 없으면 "Framewright done"을 출력하고 추가 작업 (커밋이 있는 저장소, README.md를 위한 MP4 링크, 시트에 타임스탬프가 있는 경우 storyboard.md의 타임라인, 또는 완전히 격리된 다른 문서로서의 README.md)이 있으면 완료를 지연시키지 말고 제안하십시오.