구성주의 해설 영상 · 워크플로우
하나의 시간축, 세 개의 하위 단계가 모두 여기서 파생됩니다.
① 원고 ② TTS 축 설정 ③ BGM 변주 ④ 최종 영상
텍스트 ──→ 해설 음성(시간축) ──→ LMMS 음악(계층) ──→ Remotion 화면 + 음성 트랙 통합 + 썸네일
│ ▲
└──── 계층 경계 시간 지점 ────┘
- 화면 프레임 수는 TTS의 자연스러운 속도에 따라 결정됩니다 (반대로 속도를 압축하지 않음).
- BGM의 변주점은 해설의 계층 전환 지점에 맞춥니다.
- 최종 영상은 해설 음성을 기준으로 정렬되며, BGM은 배경으로만 사용됩니다.
0. 시작하기: 먼저 경로 계약을 읽으세요 (이 단계는 생략할 수 없습니다)
이 스킬은 자체적으로 도구를 찾지 않으며, 모든 경로는 환경 설정 스킬이 작성한 계약에서 가져옵니다:
$VW_CONTRACT (기본값 ~/.config/ideaform/toolchain.json)
CONTRACT="${VW_CONTRACT:-$HOME/.config/ideaform/toolchain.json}"
source "$(dirname "$CONTRACT")/env.sh" # VW_NODE / VW_VENV / VW_FFMPEG / … 내보내기
작업 시작 전에 반드시 확인해야 할 세 가지:
- 계약 파일이 존재합니까? 없으면 → 사용자에게 먼저
ideaform-setup스킬을 실행하도록 안내합니다. schema_version이 이 스킬이 인식하는 버전입니까? 일치하지 않으면 → 환경 설정 스킬을 다시 실행하도록 요청합니다.verified_at이null입니까? 그렇다면 → "환경이 초기 검증을 통과하지 못했습니다" 사용자에게 경고하고, 먼저 수정한 후 작업을 진행합니다. 또한tools.lmms.stkdir_ok는 반드시true여야 합니다. 그렇지 않으면 BGM이 전체적으로 무음이 되며 오류도 발생하지 않습니다.
주요 필드 빠른 참조:
| 용도 | 계약 필드 |
|---|---|
| TTS 스크립트 실행 (venv 내 edge-tts) | tools.venv.python |
| 오디오/비디오 합성 | tools.ffmpeg.path / tools.ffprobe.path |
| 화면 렌더링 | tools.remotion.project / tools.remotion.browser.resolved |
| BGM 렌더링 | tools.lmms.apprun |
| 임시 파일 저장 (대용량 디스크 필수) | tmpdir |
⚠️ 계약의
tmpdir은 선택 사항이 아닙니다. 로컬/tmp는 10MB에 불과하며, Remotion 패키징 / pip 패키지 설치 시 이를 사용하므로ENOSPC오류가 발생합니다. 모든 Remotion 명령에는TMPDIR을 포함시켜야 합니다.
⚠️ 경로는 절대
resolve(심볼릭 링크 해석)하지 마세요. 관리형 툴체인 경로에는 심볼릭 링크가 포함되어 있으며, venv 인터프리터를 해석하면 venv 컨텍스트가 손실되어 모든 패키지를 가져올 수 없습니다. 자세한 내용은 저장소 루트의docs/03-경로-계약.md를 참조하세요.
1. 4단계 개요
| 단계 | 수행 작업 | 입력 | 출력 (작업 공간에 저장) | 참조 |
|---|---|---|---|---|
| ① 원고 | 사용자 입력을 계층화된 해설 원고로 변환 | 사용자 입력 | script.md + 대사.txt (계층 표시 완료) | references/stage1-script.md |
| ② TTS 축 설정 | 자연스러운 속도 측정 → 시간축 생성 → 음성 트랙 출력 | 대사.txt | plan.json / timeline.ts / voice.wav | references/stage2-tts.md |
| ③ BGM 변주 | 계층 경계 → 변주 계획 → LMMS 음악 트랙 출력 | plan.json + 계층 표시 | bgm-plan.json / bgm.wav | references/stage3-bgm.md |
| ④ 최종 영상 | Remotion으로 화면 배치 + 트랙 통합 + 렌더링 + 썸네일 | timeline.ts + 오디오 트랙 + bgm-plan.json | final.mp4 + cover.png | references/stage4-render.md · references/cover.md |
스타일 통일은 references/constructivist-style.md가 관리합니다.
화면과 썸네일이 동일한 색상 팔레트 / 폰트 / 그리드를 사용하므로 자연스럽게 일관성을 유지합니다.
2. 1단계 · 원고 (계층화는 필수)
사용자가 제공하는 것은 입력(주제 / 자료 / 구상)입니다. 결과물은:
script.md—— 전체 원고, 논의 계층을 명시적으로 표시대사.txt—— 한 줄에 한 문장의 구술 대사, TTS에서 사용
계층화는 보기 좋으라고 하는 것이 아니라, 3단계의 입력입니다. 계층 표시가 없으면 BGM 변주는 기준점을 잡을 수 없습니다.
## 계층 1 · 도입
이것은 《원고가 시간축이다》의 첫 번째 계층입니다.
해설 음성이야말로 전체 파이프라인의 시간축입니다.
## 계층 2 · 전개
화면 프레임 수는 이것에 의해 결정되며, 그 반대가 아닙니다.
...
- 한 문장 = 하나의 의미 단위, 두 가지 내용을 한 문장에 넣지 마세요 (TTS는 문장 단위로 축을 정하므로, 두 내용을 넣으면 슬롯이 길어집니다).
- 한 문장은 8–30자를 권장합니다. 너무 길면 문장을 나누고, 너무 짧으면 합칩니다.
- 문장 부호는 마침표/물음표로 끝내 TTS의 문장 구분을 돕습니다.
자세한 내용은 references/stage1-script.md를 참조하세요.
3. 2단계 · TTS 축 설정 (순서는 철칙)
"$VW_VENV/bin/python" skills/ideaform/scripts/make-tts-track.py \
--lines 대사.txt --fps 30 --audio \
--plan-out out/plan.json --ts-out out/timeline.ts
먼저 음성을 녹음하고, 그 다음 축을 설정합니다. 시간축은 TTS의 실제 속도에서 생성됩니다:
① 대사 작성
② make-tts-track.py --lines 대사.txt --fps 30 --audio ← 자연스러운 속도 측정, 시간축 생성
③ 출력된 TOTAL_FRAMES를 사용하여 Remotion composition 작성 ← 프레임 수가 이에 따라 결정됨
④ 그런 다음 렌더링
반례의 대가 (실제 사례): 먼저 스토리보드에 따라 43개의 타임코드를 고정하고, TTS 자연 속도가 총 212.90초인데 비디오는 174.80초에 불과하여 → 1.218배 강제 압축을 해야 했습니다. 이 타협은 충분히 피할 수 있었습니다.
같은 스크립트로 반대 작업도 가능합니다 (이미 SRT가 있고 타임코드가 고정된 경우 두 단계 속도 조정으로 적응), 하지만 그것은 보완일 뿐 최선의 방법은 아닙니다. 자세한 내용은 references/stage2-tts.md를 참조하세요.
⚠️ 사용자에게 "최종 영상이 몇 분인지" 묻는 것을 금지합니다. 길이 = 속도 × 대사량, 즉 출력이지 입력이 아닙니다. 작업 시작 시 묻거나 "표준판 X분"과 같은 옵션을 제공해서는 안 됩니다. 「이 원고 기준으로 실제 측정 결과 X분 Y초입니다」라고만 보고합니다. 사용자가 더 길거나/짧게 원하면 → 변경해야 할 것은 원고이지, 시간이 아닙니다. (2026-10-15 계화편에서 겪음, E16 참고)
출력: plan.json의 각 cue에는 start/end/dur/frameStart/frameEnd가 포함됩니다.
timeline.ts는 바로 cp하여 Remotion 프로젝트의 src/timeline.ts로 사용할 수 있습니다.
4. 3단계 · BGM 변주 (이 워크플로우의 핵심)
요점: 논의가 새로운 계층에 진입할 때마다 음악의 형태가 바뀝니다. 단, 곡 자체는 바뀌지 않습니다.
# ① 원고의 계층을 cue 번호에 매핑하고, 각 계층의 서사 기능을 표시합니다
"$VW_VENV/bin/python" skills/ideaform/scripts/bgm-plan.py \
--plan out/plan.json \
--layers "1:present,4:develop,6:resolve" \
--key "A minor" --out out/bgm-plan.json
# ② bgm-plan.json에 따라 LMMS 프로젝트를 생성하고 렌더링합니다 (electronic-music-production의 composer.py 사용)
# 계약의 tools.lmms.apprun으로 렌더링합니다. bpm 및 timeline lp1pos는 mmp_hints에 따라 작성해야 합니다.
여섯 가지 서사 기능 (각 계층에서 하나를 선택):
| 기능 | 의도 | 변경 사항 |
|---|---|---|
present | 제시·도입 | 동기만 노출, 단성부, 여백 |
develop | 전개·심화 | +베이스 +리듬, 동기 시퀀싱 |
intensify | 강조·충돌 | 전성부, 옥타브 상승, 차용 화음, 강도 증가 |
contrast | 대조·전환 | 볼륨 증가 없이 질감 변경: 레이어 제거, 음색 변경, 조성 전환 |
resolve | 수렴·회귀 | 간소화, 본조 회귀, 동기 증대 |
sustain | 지속·전환 | 변경 없음 (계층이 변경되지 않았는데 변주하면 대비가 희석됨) |
철칙:
- 정체성 요소는 변경 불가 —— 동기 세포, 조성 중심, 기본 속도. 변경되면 변주가 아니라 곡이 바뀌는 것입니다.
- 각 구간은 최소 1개의 형태 요소를 변경해야 합니다.
bgm-plan.md에는 각 구간의 「변주 증거」가 나열됩니다. 인접한 두 구간의 증거가 비어 있으면 계층 표시가 누락된 것입니다. - 변주 지점은 해설의 계층 경계에 정확히 맞춰야 합니다 (
--lead-in을 사용하여 음악이 약간 앞서 시작하도록 할 수 있음).
전체 방법 및 LMMS 측 구현은 references/stage3-bgm.md를 참조하세요.
5. 4단계 · 최종 영상
5.1 화면: 해설 음성을 시간축으로
cp out/timeline.ts <remotion-프로젝트>/src/timeline.ts # 프레임 수/씬 경계를 모두 CUES에서 도출
cd <remotion-프로젝트>
export TMPDIR="$VW_TMPDIR" # ★ 필수
npx remotion studio # 먼저 사용자에게 미리보기 제공
화면 씬의 경계는 프레임 번호를 직접 입력하지 말고, CUES에서 도출하세요:
export const SCENES = {
s1: { from: 0, dur: CUES[2].start },
s2: { from: CUES[2].start, dur: CUES[4].start - CUES[2].start },
} as const;
구조식 / 신호 경로 / 메커니즘 다이어그램 → 모두 결정론적으로 생성하며, 절대 AI 이미지 생성을 사용하지 마세요.
AI가 화학 구조를 그리면 반드시 환각 결합을 만들어 내며, 오류를 발견할 수 없습니다——이러한 자료는 재현 가능하고 검증 가능해야 합니다.
scripts/gen-structures.py 사용: SMILES → RDKit 결정론적 묘사 → 프레임 기반 Remotion 컴포넌트
python3 scripts/gen-structures.py --tsv structures.tsv --out structures
# → svg/*.svg + components/*.tsx (자체 애니메이션 포함) + manifest.json
# → 분자식 오프라인 검증 (RDKit CalcMolFormula), 불일치 시 0이 아닌 종료 코드 반환, `&&`로 직접 연결 가능
- 색상은 RDKit 공식 옵션(
useBWAtomPalette/singleColourBonds/setSymbolColour)을 사용하고, 정규식으로 SVG의 색상을 후처리하지 마세요——이는 반복 작업이며 RDKit 버전 업데이트 시 깨집니다 (오답노트 E20 참고). - 생성된 컴포넌트는 CSS 변수
--mol-dash/--mol-text를 사용하여 선 애니메이션을 구동하므로, 외부에서 반드시color를 제공해야 합니다. - 그리드 다이어그램 / 골격 강조 / 3D 형태 / 단백질-리간드 상호작용 다이어그램 등 더 광범위한 분자 시각화가 필요하면, 먼저 기존 스킬을 찾아보세요 (§7 참조), 비디오 파이프라인에 억지로 포함하지 마세요.
5.2 오디오 트랙 통합 (두 가지 방법, 기본 A)
A. Remotion 통합 (권장) —— composition에서 <Audio>를 사용하면 렌더링된 mp4에 자동으로 오디오 트랙이 포함됩니다:
<Audio src={staticFile("voice.wav")} />
<Audio src={staticFile("bgm.wav")} volume={0.1694} />
volume 값은 임의로 정하지 말고, 실제 RMS를 측정하여 역산합니다:
bash scripts/mix-av.sh --plan-only --voice voice.wav --bgm bgm.wav --drop 9
# 해설 RMS = -23.46 dBFS BGM RMS = -17.04 dBFS
# → BGM에 -15.42 dB 적용 → volume = 0.1694
B. 후반 합성 (비디오가 이미 렌더링되었거나 / 음성을 다시 녹음해야 하는 경우):
bash scripts/mix-av.sh --video silent.mp4 --voice voice.wav --bgm bgm.wav --out final.mp4
⚠️ 방법 B는 반드시 명시적으로
-map 0:v:0 -map 1:a:0을 사용해야 합니다. Remotion이 출력하는 mp4에는 무음 AAC 트랙이 포함되어 있습니다.-map을 추가하지 않으면 ffmpeg가 자동으로 이 트랙을 선택하여 → 최종 파일의 종료 코드는 0, 길이는 정확, 재생은 가능하지만 소리가 없습니다.mix-av.sh에는 이-map이 이미 내장되어 있습니다.
5.3 렌더링 및 썸네일
export TMPDIR="$VW_TMPDIR"
npx remotion render <CompositionId> out/video.mp4 --concurrency=3
npx remotion still Cover out/cover.png --props='{...}' # 썸네일: 정적 composition, 애니메이션 사용 금지
- 긴 영상 분할 렌더링: 7분 영상을 한 번에 렌더링하면 메모리 부족으로 시간 초과가 발생할 수 있습니다.
--frames=A-B로 분할 →concat -c copy무손실 병합. - 썸네일은
durationInFrames={1}인 정적 composition을 사용하고, 컴포넌트 내에서useCurrentFrame을 사용하지 마세요 (still 시 항상 0이므로 애니메이션 초기값으로 인해 요소가 보이지 않을 수 있습니다). - 썸네일 레이아웃은
references/cover.md를, 스타일 규칙은references/constructivist-style.md를 참조하세요.
6. 눈 없는 검증: 각 단계마다 검증기를 통과해야 합니다
모델은 이미지를 볼 수 없고, 소리를 들을 수 없습니다. 따라서 "렌더링 성공" ≠ "화면 맞음, 소리 맞음"입니다. 각 단계는 수치로 실제 측정하며, 기준을 통과하지 못하면 전달을 거부합니다:
# 오디오 자체 점검 (TTS 트랙, BGM): 피크/RMS/클리핑/길이 편차
# 화면 자체 점검: 픽셀 색상 / 여백 순도 / 텍스트 픽셀 개수
# --layout은 반드시 올바르게 선택: cover=썸네일 레이아웃; scene=비디오 씬 (기본값).
# 샘플링 스트립은 레이아웃에 따라 결정되며, 잘못 사용하면 올바른 썸네일도 실패 판정을 받을 수 있습니다 (05-문제 해결 D2 참고).
python3 scripts/verify-video.py cover out/cover.png --layout cover
# 최종 영상 자체 점검: 프레임 수 / 길이 / 오디오 트랙 비트레이트 (무음 트랙 방지)
# ffprobe 위치 순서: VW_FFPROBE → 경로 계약 toolchain.json → 시스템
VW_FFPROBE="$VW_FFPROBE" python3 scripts/verify-video.py video out/final.mp4 --frames 732 --fps 30
스크립트는 0이 아닌 종료 코드로 실패를 나타내며, && 파이프라인에 직접 연결할 수 있습니다.
자주 사용되는 판정 기준 빠른 참조:
| 대상 | 판정 기준 | 임계값 |
|---|---|---|
| 오디오 | 무음 아님 | 피크 > 1000 및 RMS > 100 |
| 오디오 | 클리핑 없음 | 전체 스케일 샘플 < 0.01% |
| 오디오 | 길이 정렬 | 계획과의 편차 < 50 ms |
| 썸네일 | 크기 | 1920×1080 |
| 썸네일 | 텍스트 렌더링 | 텍스트 영역 잉크 픽셀 > 300 |
| 썸네일 | 축선 텍스트 미침 | 제목 영역 내 빨간색 픽셀 < 60 |
| 최종 영상 | 프레임 수 | 계획 값과 정확히 일치 |
| 최종 영상 | 오디오 트랙 무음 아님 | 비트레이트 > 64000 bps |
| BGM | 배경 레벨 | 나레이션보다 8–12 dB 낮음 |
7. 이 스킬과 함께 사용할 수 있는 다른 기능
| 필요한 것 | 사용할 것 |
|---|---|
| 환경 탐지 / 보완 설치 / 계약 | ideaform-setup (일회성) |
| 새로운 오픈 소스 도구의 기존 스킬 (가져오기: GitHub 수집 → 보안 검사 → 저장) | ideaform-setup의 scripts/harvest-skills.py <도구명> |
| 화학 구조식 / 경로 다이어그램의 결정론적 그래픽 | 이 스킬의 scripts/gen-structures.py (RDKit → SVG → Remotion 컴포넌트) |
| 더 광범위한 분자 시각화 (그리드 다이어그램 / 골격 강조 / 3D 형태 / 단백질-리간드) | 외부 기존 스킬: synthetic-sciences/openscience의 molecule-visualization (Apache-2.0), ghutchis/chem-skill (MIT) |
LMMS 프로젝트 형식, 플러그인 매개변수, eldata 엔벨로프 | electronic-music-production + 그 references/05 |
| 작곡법 (화성, 동기, 무청각 작곡, 창발) | electronic-music-production의 references/06–10 |
| Remotion 문법 및 렌더링 세부 사항 | remotion-best-practices |
| 픽셀 수준 눈 없는 검증의 전체 방법 | headless-visual-verify |
| WSL 환경 함정 (/mnt, gh 가져오기, 오디오 경로) | wsl-pitfalls |
8. 전달 목록
한 편의 비디오가 완성되면 작업 공간에는 다음이 있어야 합니다:
<작업 공간>/
├── script.md 계층화 원고
├── 대사.txt 구술 대사
├── out/
│ ├── plan.json 시간축 (각 cue의 초/프레임)
│ ├── timeline.ts Remotion에 바로 사용 가능한 시간축
│ ├── voice.wav 해설 음성 마스터
│ ├── bgm-plan.json 변주 계획 + bgm-plan.md (변주 증거)
│ ├── bgm.wav BGM 트랙
│ ├── video.mp4 무음 화면 (또는 오디오가 통합된 최종 영상)
│ ├── final.mp4 ★ 최종 영상
│ └── cover.png ★ 썸네일
└── .tts/ TTS 캐시 (유지, 재실행 시 몇 초만 소요)
전달 전에 반드시 섹션 6의 모든 검증을 실행하고, 어떤 항목이 통과했고 어떤 항목이 테스트되지 않았는지 정확히 보고해야 합니다.
9. 자체 진화: 문제가 발생하면 기록하세요
이 워크플로우는 변경 가능한 복합 스킬입니다——사용하면서 생산 리듬에 더 잘 맞도록 발전해야 합니다.
- 문제가 발생하여 수정한 직후 → 저장소 루트의
docs/06-오답노트.md에 기록합니다 (6열 형식, 번호 순차적으로 증가). ⚠️skills/ideaform/아래에 별도의docs/를 만들지 마세요——중복 장부가 되며 원본과 번호가 충돌합니다 (2026-10-15에 겪음, E19 참고). - 특정 문제가 스크립트/문서로 영구적으로 수정된 경우 (우회가 아님) → 해당 항목의 상태를
완치로 변경하고 수정 방법을 명시합니다. - 동일한 유형의 문제가 두 번째로 발생하면 → 세 번째 항목을 기록하지 말고, 방어선을 스크립트에 직접 작성합니다 (검증기 / 기본값 / 계약).
- 새로운 오픈 소스 도구 도입 → 먼저 수집한 후 작업 시작:
harvest-skills.py <도구명>으로 커뮤니티의 성숙한 스킬을 찾습니다 (분기 보호가 있어 로컬에서 수정한 내용을 덮어쓰지 않습니다); 적합한 것을 찾지 못한 경우에만 직접 작성합니다. 수집한 스킬에서 문제가 발생해도 동일하게 기록합니다. - 세션 종료 전에 확인: "수정했지만 기록하지 않은 것"이 있는지 확인합니다.