videolabs: 비디오 만들기 (터미널 강좌 스타일)
모든 파일은 단일 레포지토리 /opt/opus/videolabs (github.com/bigzaqui/videolabs, 공개: 자격 증명을 절대 커밋하지 말 것; 커밋 전에 scripts/check-secrets.sh 실행)에 위치합니다. 이 스킬의 소스는 해당 레포지토리의 skills/videolabs_create-video/ 디렉토리에 있으며, scripts/setup.sh에 의해 ~/.claude/skills로 심볼릭 링크됩니다. 설정값(ElevenLabs 및 Cloudflare 키, 렌더 호스트)은 git에서 무시되는 레포지토리 루트의 .env 파일에 있으며, .env.example 파일을 참조하세요.
레포지토리 구성: 일반 엔진 (Remotion + ElevenLabs 도구 모음, npm ci 실행)과 labs/ 디렉토리 아래 각 실습실별 폴더입니다. "kafka - in a nutshell" (26개 레슨 / 18분), "ceph - first steps" (14개 레슨 / 11.6분) 및 이전 "ceph in a nutshell" (41개 레슨 / 30분; 스크립트와 비주얼은 카드, 비교, 게이지, 트리, 인시던트, 상태 및 사이트 예시로 reference/에 보관)에 사용됩니다. 두 현재 실습실 모두 실습형입니다: 모든 명령은 실제 실습실에서 먼저 실행되었습니다 ("실제 실습실" 참조).
/opt/opus/videolabs/
engine/ 일반 도구 모음: engine/cli.mjs <lab> <command>, src/ (Remotion), scripts/
labs/<slug>/
lab.config.json 카탈로그 항목 (제목, 설명, 태그) + 외형 (브랜드, 로고, 상태 표시줄, 도구 이름, 샘플 컷)
script/script.md 내레이션, 장면은 ### 제목으로 구분
visuals.ts 각 장면이 보여줄 내용 + 큐 오버라이드
lab/ create-lab.sh, destroy-lab.sh, cap.sh, captures/, lab-meta.json
audio/raw/ ElevenLabs 출력 (유료; git에 커밋됨)
out/ data/ public/voiceover/ 생성됨, git에서 무시됨
site/ scripts/publish-all.sh state/ 사이트 및 게시 (스킬 videolabs_publish)
script.md ──cli scenes──▶ data/scenes.json
└─ cli tts (ElevenLabs 단어 타이밍) ─▶ audio/raw ──pace──▶ public/voiceover/* + data/{durations,captions}.json
visuals.ts + engine/src/cues.ts (reveal = 단어가 발화되는 시점) ─▶ Remotion ──cli render──▶ out/<slug>.mp4
cli player ─▶ out/player/{chapters.json,lab.json} ──videolabs_publish──▶ videolabs.pages.dev
모든 명령어: node engine/cli.mjs <lab> <scenes|tts|pace|cues|stills|typecheck|studio|sample|render|player>, 추가로 list와 new <slug> (레포지토리 README.md에 명령어 표가 있습니다). 실습실 폴더만 강좌마다 다릅니다. 엔진 코드를 실습실에 복사하지 마십시오.
워크플로우 (사용자와 개요 및 스크립트를 먼저 합의; 이후 모든 작업은 허가 없이 실행)
사용자의 사전 승인: 스크립트가 합의되면, 각 단계마다 허가를 구하지 않고 유료 ElevenLabs tts, 큐 감사, 렌더 호스트에서 렌더링, 플레이어/포스터 생성, 실습실 해체를 실행합니다. 이후 결과와 새 길이를 보고합니다. 이 워크플로우를 벗어나는 경우(예: 실습실이 생성하지 않은 항목에 대한 파괴적 작업, 사용자가 요청하지 않은 사이트 게시)에만 질문을 위해 멈춥니다.
- 실습형 주제? 먼저 실제 실습실을 구축하고 실행합니다 (아래 "실제 실습실" 참조). 명령 출력을 기억에서 절대 작성하지 마십시오: ceph-first-steps 스크립트의 첫 번째 초안은 실제 클러스터에서 실행한 후 열 군데에서 틀렸습니다. 그런 다음 실제 발생한 내용을 바탕으로 스크립트를 작성합니다.
- 스크립트 우선, 비디오는 나중에. 개요(모듈 → 레슨, 각 레슨의 도표 포함)를 제안하고 합의를 얻은 후
labs/<slug>/script/script.md를 작성합니다. 텍스트 변경은 오디오 재생성보다 훨씬 저렴합니다. 속도 1.0으로 표시하고 일시 정지 포함 약 160 wpm: 1,500 단어 ≈ 모듈 카드 포함 9.5분; 1.08 ≈ 168 wpm.tts직후Paced … total X min줄로 실제 길이를 확인하고 렌더링 전에 조정합니다. 버전별 명령어의 경우 현재 릴리스를 확인합니다(문서 및 프로젝트의 다운로드/이미지 태그). 형식 (engine/scripts/build-scenes.mjs에서 구문 분석):
모듈의 마지막 레슨을 "Module takeaway: …"로 끝냅니다 – takeaway 단어는 요약 창을 트리거합니다.## Module 1 — Core Architecture ### S02 · RADOS **Visual:** 디자이너를 위한 참고 사항 **Commands:** `ceph -s` `ceph osd tree` (선택 사항, 화면에만 표시) **Narration:** 한 단락, 말하기 위해 작성됨. 명령어는 발음대로 철자 ("ceph dash s"). node engine/cli.mjs new <slug>는labs/<slug>/(설정, 세 개 장면 스크립트, visuals.ts)의 기본 구조를 생성합니다;lab.config.json(카탈로그용 제목, 설명, 태그; 외형용brand,user,logo,statusbar,statusCmd,networkLabels,session,tools,sample)을 채우고node engine/cli.mjs <slug> scenes를 실행합니다.- 시각 자료
labs/<slug>/visuals.ts(engine/src/types.ts의 타입,@engine/theme의colors), 장면 ID당 하나의 항목. 도표 종류 (engine/src/components/Diagrams.tsx):topology: 하나 또는 두 개의 네트워크 라인에 호스트(이름, IP, 데몬 칩, 디스크/복제본), 선택적 상태 라인(statusCmd레이블, 설정의 기본값) 및flows(호스트 간 이동하는 블록,cue부터until까지). 모든 호스트/데몬/디스크/상태는cue(발화될 때 나타남) 및changes: [{cue, state, label}](디스크empty|osd|leader|idle|down|destroyed|new, 호스트ok|spare|down|maint)를 가질 수 있습니다. 큐가 없는 요소는 처음부터 표시됩니다. 헬퍼(host(n),built(n))를 사용하여 실습실 상태를 구축하여 도표가 레슨별로 성장합니다.session: 사실적인 셸; 각cmd는 발화될 때 자체 입력되고,out줄이 출력됩니다; 가장 최근 행을 유지합니다.host는 줄의 전체 프롬프트를 설정합니다(설정의session.prompt기본값). 그 위에 선택적 compacttopology가 있습니다. 하단 명령어 표시줄은 session 씬에서 숨겨집니다. 기타 종류:title(fastfetch 카드;user, 추가rows),layers,cards,flow,tree,compare,gauge,terminal(콜아웃이 있는 명령 출력),incident(증상/확인/조치),checklist,states,sites; 장면당 선택적takeaway및commandCues. 모든out및symptom에 대해lab/captures/의 실제 출력을 사용합니다.
- 오디오 비용 지불 전에 시각 자료 검토:
node engine/cli.mjs <slug> stills(장면당 60%에서 한 프레임; 특정 순간은S04,S11 --at=12.5) 및 ffmpegxstack으로 2×2 연락처 시트로 타일링하여 확인합니다. 오버플로, 줄 바꿈, 빈 레이아웃을 확인합니다. - 음성: 레포지토리 루트
.env에ELEVENLABS_API_KEY/ELEVENLABS_VOICE_ID, 그런 다음node engine/cli.mjs <slug> tts(직접 실행, 승인 필요 없음; 기존 것은 건너뜀;--only=S03,S04 --force로 내레이션이 변경된 장면만 재실행하여 크레딧 절약). 자동으로pace를 실행합니다. 스크립트가 길어졌을 때 새 길이를 사용자에게 알립니다.labs/<slug>/audio/를 커밋합니다 (유료 출력). - 동기화 감사:
node engine/cli.mjs <slug> cues(명령어를 변경한 후에는commandCues항목도 변경:dmesg칩이 여전히 "smartctl"에 큐되어 있으면 조용한 불일치) — 모든 요소와 모든 명령어 줄을 드러내는 단어를 나열합니다 (MISS= 이웃 간 분산). 잘못된 일치는 큐 오버라이드로 수정합니다 (아래 참조). 내레이션 또는 음성 변경 후에 다시 실행합니다. - 미리보기:
node engine/cli.mjs <slug> sample(렌더 서버에서 약 1분),ffmpeg -ss <t> -frames:v 1로 스팟 체크,volumedetect로 레벨 확인 (클리핑 없음: 최대 < -1 dB). - 전체 강좌 렌더링 (원격, 아래 참조), 그런 다음
node engine/cli.mjs <slug> player, 포스터labs/<slug>/out/spot.jpg생성, 실습실 VM 파괴, videolabs_publish 스킬로 전체 사이트 게시.
사용자가 승인한 디자인 규칙
- 외형: Omarchy/Hyprland 데스크탑, Tokyo Night 팔레트 (
src/theme.ts;rados,mon,cephfs같은 시맨틱 이름은 단지 팔레트 슬롯). 워크스페이스 0–7의 모듈이 있는 Waybar 스트립, 상태 (HEALTH_WARN문제 해결 모듈에), 레슨 카운터07/41. 하나의 타일드 창, 2px 정사각형 테두리, 반경/광택/그라데이션 없음. 각 레슨은 타이핑되는 robbyrussell 프롬프트➜ module-1-core-architecture git:(s03) cat the-daemons.md로 시작하고, 이어서# Heading이 나타납니다. 참고 사항은// comments로, 명령어는❯ cmd줄로, 요약은 플로팅 창cat sre-takeaway.md로, 모듈 구분선은cd module-N-…+ 큰 제목으로, 오프너는 fastfetch로 렌더링됩니다. 모든 텍스트는 JetBrains Mono; Inter는 자막에만 사용됩니다. 사용자가 싫어했던 "일반적인" 어두운 그라데이션 외형을 대체했습니다. - 동기화 공개: 시간별로 지연시키지 않습니다. 각 요소는 내레이터가 해당 이름을 처음 말할 때 나타납니다 (
engine/src/cues.ts: 가장 긴 의미 있는 토큰, 접두사 형태소 분석, 이전 큐에서 앞으로 검색). 실습실의visuals.ts에서 오버라이드:cue: "=managers"정확한 발화 단어 (하이픈/구두점 무시:"=write-ahead"),cue: null= 단어 없음, 보간.pointCues(비교면),noteCue(트리),linkCue(사이트),cues(제목 배지),commandCues(명령어 줄, 장면별).cueLabels()의 레이블 순서는engine/src/components/Visuals.tsx의 렌더러가 사용하는 인덱스 순서와 일치해야 합니다 (topology/session은topologyKeys/sessionKeys에서 키 기반 순회 사용).- 하나의 전방 커서가 장면의 모든 레이블을 순회하므로, 나중 레이블이 일반적인 단어("three", "recovery", "add")를 재사용하면 이후 발생에 위치할 수 있습니다. 해당 순간 근처에서 한 번 발생하는 단어를 선호하고("=repeat", "=begins"), 항상
cues감사 결과를 읽습니다.
- 자막: 평이한 구문, 문장/절 끝에서 분리, 한 줄 ≤ 64자, 단어별 가라오케 강조 없음 ("피곤함").
- 호흡:
engine/scripts/pace.mjs는 원시 ElevenLabs 오디오에. ? !후 250ms,: ;후 120ms를 삽입하고 단어 타이밍을 이동합니다. 컷 포인트는 ffmpegsilencedetect(엄격 -38 dB/40 ms, 그 다음 -30 dB/20 ms)에서 오거나, 가장 조용한 5 ms 창에서 오며, 8 ms 페이드 포함 — 절대 ElevenLabs 단어 경계만으로 결정하지 않습니다 (단어 내부에 위치).SENTENCE_PAUSE/CLAUSE_PAUSE를 조정하고,node engine/cli.mjs <slug> pace를 다시 실행합니다 (무료; 원시 오디오는audio/raw/에 보관됨). 페이싱은 ffmpeg 버전에 따라 다릅니다 (동일한 Kafka 오디오에 대해 7.1.5 및 8.0.1이 각각 17.9분 및 17.8분 제공): 렌더링하는 곳에서 페이싱하거나data/및public/voiceover/를 렌더 호스트로 복사합니다. - 전환:
@remotion/transitionsTransitionSeries를 통한 15프레임 크로스페이드; 무음 리드인(0.4초)/테일(0.8초)만 겹칩니다.engine/src/timeline.ts는 오버랩을 뺍니다;engine/scripts/player.mjs및stills.mjs는 해당 상수를 미러링합니다 — 동기화 상태를 유지하세요. - 사용자가 선택한 음성: ElevenLabs "Mark"
v3p1kjzUvro6S76qmYmH(음성 라이브러리;GET /v1/voices/{id}가 404를 반환하지만 TTS에는 작동),eleven_multilingual_v2, 속도 1.08,with-timestamps엔드포인트, 연속성을 위한previous_text/next_text. 전체 실행 전에 항상 한 문장 TTS 호출로 음성 ID를 확인하십시오 (이전.env의 ID는 잘렸습니다).
강좌별 설정 (labs//lab.config.json)
Waybar의 워크스페이스와 fastfetch 모듈/레슨 카운트는 장면을 자동으로 따릅니다. 강좌별 다른 모든 것은 설정의 데이터이며 코드가 아닙니다: brand (Waybar 이름), user (제목 카드의 프롬프트), logo (블록 문자 5줄), statusbar (ok/warn 텍스트, right 텍스트, warnModule = 경고를 표시하는 모듈), statusCmd 및 networkLabels (topology 장면의 기본값), session.prompt/session.title (터미널 세션), tools (명령어 줄 자동 큐잉 시 건너뛸 명령어 단어), sample (Sample 컷의 장면 ID), 그리고 카탈로그용 title, description, tags. 다른 외형의 새 실습실은 엔진 변경이 필요 없습니다. 새 도표 종류 또는 상태는 엔진 변경(engine/src/types.ts, components, cues.ts)이 필요하며 모든 실습실에 혜택을 줍니다.
적용된 Remotion 관행 (remotion-* 스킬에서 가져옴)
@remotion/google-fonts를 통한 글꼴 (시스템 글꼴에 의존하지 마십시오); @remotion/media의 <Audio>는 from과 함께 사용; TransitionSeries; calculateMetadata는 오디오 지속 시간에서 컴포지션 크기를 조정; interpolate/Easing.bezier 및 translate로 애니메이션 (CSS 전환 없음). 의도적으로 채택하지 않음: Interactive.create/연결된 컴포지션 (Studio에서 수동 편집에만 유용), 큰 소셜 비디오 텍스트 최소값, 밈 SFX.
렌더링
VIDEOLABS_RENDER_HOST(렌더 및 실습실 서버의 ssh 대상, 키는 이미 설정됨)는 레포지토리 루트.env에서 가져옵니다: 아래 명령어 전에set -a; . /opt/opus/videolabs/.env; set +a를 실행하고, 해당 변수를 커밋하는 모든 항목에 리터럴 주소 대신 작성합니다. 동일한 호스트가 실습실 VM을 실행합니다.TMPDIR=/var/tmp는 CLI에 의해 설정됩니다: 이 호스트에서/tmp는 작은 RAM tmpfs이며 긴 오디오 믹스가 마지막에 "No space left on device" 오류로 실패합니다.- 로컬 박스 (4 vCPU) ≈ 30분 비디오당 약 45분. 원격 렌더 서버 $VIDEOLABS_RENDER_HOST (Ubuntu 26.04, 24 스레드, 31 GB RAM, RTX 3090 Ti, KVM; Node 22, ffmpeg, apt의 Chromium 라이브러리)는 11.6분 ceph 강좌를 3분, 18분 kafka 강좌를 5분에 동시성 12–16으로 렌더링합니다. 레포지토리는 두 머신 모두
/opt/opus/videolabs에 있습니다. 흐름:(검토한 것과 동일한 타이밍을 유지하려면 서버에서rsync -az --delete --exclude node_modules --exclude out --exclude .env --exclude .git --exclude site --exclude state ./ $VIDEOLABS_RENDER_HOST:/opt/opus/videolabs/ # from /opt/opus/videolabs ssh $VIDEOLABS_RENDER_HOST 'cd /opt/opus/videolabs/engine && npm ci && cd .. && node engine/cli.mjs <slug> scenes && node engine/cli.mjs <slug> pace && node engine/cli.mjs <slug> render --concurrency=12 --log=error' rsync -a $VIDEOLABS_RENDER_HOST:/opt/opus/videolabs/labs/<slug>/out/<slug>.mp4 labs/<slug>/out/ && node engine/cli.mjs <slug> playerscenes/pace를 실행하는 대신labs/<slug>/data및public/voiceover를 rsync하십시오.) 절대.env를 복사하지 마십시오. 긴 렌더는 분리 실행(ssh -f -n으로 시작한nohup bash script &)하고time의real라인에 Monitor로 대기하십시오; 로그에서 "error"를 grep하지 마십시오 (명령어 줄에--log=error가 있습니다). Waybar/도표 코드가 변경되면 두 실습실 모두 확인하십시오 (stills), 둘이 공유하므로. - 엔진 변경 확인: 이전 코드와 새 코드로 Sample 컷을 렌더링하고
ffmpeg -i a.mp4 -i b.mp4 -lavfi ssim -f null -로 비교;scenes/durations/captions.json및cues감사가 재생성되어야 합니다.
…
[번역을 위해 잘림]