code-video: 대규모 언어 모델이 코드를 작성하여 과학/교육 비디오를 프레임별로 그리기
화면은 비디오 모델로 생성되거나 촬영된 것이 아닙니다. 모델이 웹 페이지(Canvas / SVG / Three.js / WebGL)를 작성하고,
페이지는 __render(t)를 노출하며, 스크립트가 프레임 번호별로 스크린샷을 찍은 다음 ffmpeg로 mp4로 인코딩합니다.
장점은 재현 가능, 수정 가능, 텍스트와 숫자가 절대적으로 정확, 생성 비용이 0이라는 점입니다.
전체 프로세스는 세 단계로 구성됩니다: 스타일 선택 → 프롬프트 상세 작성 → 프레임 추출로 먼저 확인하고 수정한 후 전체 비디오 렌더링.
0. 준비 (첫 사용 시)
cd <스킬 디렉터리>
npm install # playwright-core 설치
npx playwright-core install chromium-headless-shell # 또는 CHROME=/path/to/chrome 설정
# ffmpeg / ffprobe가 PATH에 있어야 함; 중문 폰트는 Noto Sans CJK 권장 (fonts-noto-cjk / noto-fonts-cjk)
pip install edge-tts numpy scipy # 선택 사항: 나레이션, 자막, 배경 음악 합성
1. 스타일 선택: 스타일은 주제에 따라 결정
먼저 주제가 어떤 범주에 속하는지 확인한 후 references/styles/로 이동하여 해당 스타일 파일을 읽습니다 (각 파일에는 다음이 포함됨: 어떤 주제에 적합한지,
간결 프롬프트, 전체 프롬프트, 제작 노트, 사실 근거, 예제 소스 코드는 examples/<name>/에 있음).
| 주제 | 먼저 시도할 스타일 | 대체 가능한 스타일 |
|---|---|---|
| 고사, 역사 이야기 | 그림자극 piying | 픽셀 pixel, 만화 comic |
| 저연령 과학, 자연 성장 | 그림책 picturebook | 플랫 MG flatmg |
| 문자, 기물, 왕조 | 한자 진화 hanzi | 수묵화 ink |
| 경로, 사절, 이주 | 고대 지도 oldmap | 등각 프로세스 isometric |
| 기계 구조, 제품 분해 | 3D 분해 explode3d | 화이트보드 whiteboard |
| 공정, 생산 라인 | 등각 프로세스 isometric | 플랫 MG flatmg |
| 크기, 깊이, 거리 | 축척 scale | 데이터 정보 그래픽 infographic |
| 시대, 규모 | 데이터 정보 그래픽 infographic | 축척 scale |
| 정리, 공식 | 수학 시각화 math | 선화 lineart |
| 단일 원리, 인과 관계 | 화이트보드 whiteboard | 선화 lineart |
| 슬로건, 오프닝/클로징 | 키네틱 타입 kinetic | — |
전체 16가지 스타일은 references/styles/README.md를 참조하세요. 사용자가 특정 스타일을 지정하면 해당 파일을 직접 읽습니다.
2. 프롬프트 상세 작성: 한 문장으로 스타일만 정해진 경우
"10초짜리 그림자극 하나 만들어 줘" 같은 한 문장 프롬프트는 모델이 스타일만 알 뿐, 무엇을 연기할지, 어떤 숫자를 사용할지, 각 섹션의 길이가 얼마인지 알 수 없습니다. 아래 뼈대에 따라 네 가지 사항을 명확히 작성하세요 (스타일 파일의 전체 프롬프트는 모두 이 구조를 따릅니다):
Canvas로 프레임별로 1920×1080, 30fps, 약 12초 분량의 '스타일' 과학 애니메이션을 그립니다: 색상, 질감, 폰트를 각각 어떻게 지정할지.
주제는 "……"입니다: 몇 개의 장면으로 구성되며, 각 장면에서 무엇을 연기하고 화면에 어떤 글자를 표시할지, 마지막에 한 문장으로 요약합니다.
사용할 숫자: ……, 출처는 notes.txt에 기록합니다; 확실하지 않은 숫자는 정확하게 맞추지 마십시오.
각 프레임은 시간 t에 의해서만 결정됩니다(타이머 사용 금지, 난수는 고정 시드 사용).
먼저 4-6프레임을 추출하여 자막이 충분히 큰지, 레이블이 겹치거나 화면을 벗어나는지 확인하고 수정한 후 전체 렌더링을 진행합니다.
또한 주제에서 물리/사실 규칙이 잘못 그려질 수 있는 부분을 보충합니다. 예: 조충의 코끼리 무게 재기 이야기에서 배의 흘수선은 적재량에 따라 계산되어야 합니다. 그렇지 않으면 표시와 수위선이 맞지 않습니다. 이 규칙을 작성하지 않으면 모델이 그릴 때 오류가 발생합니다.
3. 페이지 작성: 반드시 준수해야 하는 페이지 계약
각 비디오는 하나의 디렉터리 <work>/<name>/을 가지며, 최소한 index.html이 있어야 하고 prompt.txt(사용된 프롬프트)
및 notes.txt(숫자와 사실의 출처)도 함께 작성하는 것이 좋습니다. templates/canvas/index.html에서 시작할 수 있습니다.
- 페이지는 1920×1080 고정,
overflow:hidden. - 다음 줄이 반드시 있어야 합니다(render.sh에서 정규식으로 읽음):
const FPS = 30, DUR = 12; window.__info = { fps: FPS, duration: DUR, frames: Math.round(FPS * DUR) };window.__render = t => { ...; return { seg: '<장면 이름>', tau: t.toFixed(2) }; }각 프레임은 t에 의해서만 결정됨: CSS animation/transition, setTimeout, requestAnimationFrame,Date.now(),Math.random()사용 금지; 난수는 고정 시드 사용(예: mulberry32); 입자, 떨림, 물리 효과는 모두 t에서 직접 계산 가능해야 하며, 프레임 간 상태가 누적되지 않아야 합니다.- 폰트와 리소스가 로드된 후
window.__ready = true설정 (WebGL 페이지는 첫 프레임 렌더링 후 true 설정). - 외부 네트워크에 접근하지 않음 (프레임 추출 스크립트가 모든 외부 요청을 차단). 폰트는 시스템에 설치된 폰트 사용.
- 모바일에서도 읽을 수 있어야 함: 본문 레이블 ≥ 36px, 제목 ≥ 64px, 왼쪽 아래 페이지 하단 작은 글자는 선택 사항.
- Three.js 페이지:
scripts/fetch_three.sh <name>으로<name>/vendor/에 다운로드한 후<script type="importmap">{ "imports": { "three": "./vendor/three.module.js", "three/addons/": "./vendor/jsm/" } }</script>; renderer는preserveDrawingBuffer: true로 설정하고,__render(t)에서renderer.render()(또는 composer)를 동기적으로 호출합니다.
자세한 내용과 일반적인 문제는 references/page-contract.md를 참조하세요.
4. 먼저 프레임 추출하여 모델에게 보여주고 수정한 후 전체 비디오 렌더링
scripts/render.sh <work>/<name> 30,120,210,300 # 이 프레임들만 추출 -> <work>/_preview/<name>/
이미지 뷰어로 각 이미지를 확인하고 다음 체크리스트로 문제를 찾은 후 수정하고 다시 추출합니다 (최소 한 번 반복):
- 텍스트: 충분히 큰지, 겹침, 화면 밖으로 나감, 가려짐 여부; 한글이 모두 실제 폰트로 표시되는지 (네모 상자 없음).
- 사실: 화면의 숫자, 연도, 지명, 공식이 notes.txt와 일치하는지; 개략적인 장면에는 "개략도" 표시.
- 구성: 주제가 충분히 큰지 (원경 주인공은 최소 화면 높이의 1/4), 화면이 너무 비어 있지 않은지, 색상이 주제에 맞는지.
- 동작: 주요 동작이 실제로 발생했는지 (인접한 두 프레임 확인), 전환이 갑작스럽지 않은지.
- WebGL: 검은 블록/얼룩, 깜빡임이 없는지.
문제가 없으면 전체 비디오를 렌더링합니다:
scripts/render.sh <work>/<name> # -> <work>/<name>.mp4
N=8 scripts/render.sh <work>/<name> # 병렬 프레임 추출 프로세스 수 (기본값 min(6, 코어 수/2))
GPU_ANGLE=vulkan scripts/render.sh ... # WebGL은 GPU 사용 (더 빠르지만 연속 프레임 추출 시 검은 얼룩 발생 가능, 먼저 프레임 추출로 확인)
코덱 자동 선택: NVENC 사용 가능하면 h264_nvenc, 그렇지 않으면 libx264.
프레임 추출은 주로 CPU를 사용; 시간이 많이 소요되는 부분은 주로 "모델이 페이지 작성, 스크린샷 확인, 코드 수정"이며, 렌더링 자체는 아닙니다.
5. 선택 사항: 나레이션, 자막, 배경 음악, 미리 보기 이미지
# 나레이션 + 자막 (기본 무료 edge-tts; 전체 나레이션을 한 번에 합성하여 음성이 일관됨)
python3 scripts/tts.py script.txt -o out/narration --voice zh-CN-YunyangNeural --rate -5%
# 배경 음악: 직접 합성(저작권 문제 없음) 또는 로열티 프리 음악 라이브러리 사용 (references/narration.md 참조)
python3 scripts/chiptune.py out/bgm.wav 30 # 8비트 칩뮤직
python3 scripts/beat.py out/beat.wav --bpm 120 --beats 20 # 박자 맞춤 비트, 프레임에 맞춤
# 합성: 음악이 자동으로 음성을 피하고, 볼륨을 -16 LUFS로 표준화; --burn은 자막을 화면에 새김
scripts/mux.sh out/<name>.mp4 out/<name>.final.mp4 --voice out/narration.mp3 --music out/bgm.wav --srt out/narration.srt --burn
# 미리 보기 GIF / 커버 프레임
scripts/gif.sh out/<name>.mp4 2.5 4 # 2.5초부터 4초까지
scripts/gif.sh --poster out/<name>.mp4 6.0 # 한 프레임을 jpg로 추출
자막을 새길 경우, 페이지 하단 1/5를 비우고 별도의 자막 막대를 그리지 마세요 (또는 SUB_MARGIN=60으로 자막을 높임);
볼륨 목표는 -16 LUFS이며, 음성 피크가 높을 때는 약간 낮아질 수 있음 (최고 피크 제한 -1.5 dBTP).
오디오 선택, 자체 TTS 연동, 박자 맞춤 방법은 references/narration.md를 참조하세요.
6. 여러 비디오를 한 번에 제작 (여러 스타일 비교, 시리즈 비디오)
각 비디오는 하나의 디렉터리와 하나의 프롬프트를 가지며, 서로 독립적입니다. 여러 하위 에이전트(sub-agent)에 분산하여 병렬로 페이지를 작성하고 자가 점검할 수 있으며, 메인 에이전트는 최종 통합 렌더링, 연결 및 검사만 담당합니다. 각 하위 에이전트에 대한 지침에는 다음을 명확히 작성합니다: 페이지 계약, 디렉터리 규칙, 프레임 추출 자가 점검 체크리스트, 사실은 notes.txt에 기록, 완료 후 시간과 자체 평가 보고.
시작하기 전에 명확히 할 사항
- 사실 기반 숫자는 실제로 검증 가능해야 합니다; 뉴스/신규 카테고리 주제를 다룰 때는 공개된 정보만 그리고 모서리에 "개략도"라고 표시합니다.
- 예제의 주제 숫자를 일반적인 사실로 재사용하지 마십시오; 주제가 바뀌면 다시 확인합니다.
- 모델이 생성한 배경 음악(예: MusicGen 가중치)은 대부분 비상업적 라이선스입니다; 상업적 배포 시에는 직접 합성하거나 명시적으로 라이선스가 부여된 음악을 사용하십시오.