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 ナレーション、### 見出しごとに1シーン
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 スクリプトの最初のドラフトは、実際のクラスターで実行したところ、10 か所間違っていました。次に、実際に起こったことからスクリプトを書きます。
- スクリプトが先、ビデオは後。 アウトライン(モジュール → レッスン、各レッスンに計画された図を含む)を提案し、合意を得てから
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:** 朗読用に書かれた1つの段落。コマンドは話し言葉として綴る("ceph dash s" のように)。 node engine/cli.mjs new <slug>でlabs/<slug>/(設定、3シーンのスクリプト、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、colorsは@engine/themeから)にあり、シーンIDごとに1エントリです。図の種類(engine/src/components/Diagrams.tsx):topology: 1つまたは2つのネットワークライン上のホスト(名前、IP、デーモンチップ、ディスク/レプリカ)、オプションのステータスライン(statusCmdラベル、デフォルトは設定から)、およびフロー(ホスト間を移動するブロック、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)。その上にオプションのコンパクトなtopology。下部のコマンドバーはセッションシーンでは非表示になります。 その他の種類: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%のフレームを1枚。S04,S11 --at=12.5で特定の瞬間)。そして、ffmpeg のxstackを使用して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のモジュール、ステータス(トラブルシューティングモジュールのHEALTH_WARN)、レッスンカウンター07/41を備えた Waybar ストリップ。1つのタイルドウィンドウ、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でキー指定されたトラバーサルを使用)でレンダラーが使用するインデックス順序と一致している必要があります。- 1つの前方カーソルがシーンのすべてのラベルを実行するため、共通の単語(「three」、「recovery」、「add」)を再利用する後のラベルは、後の出現箇所に着地する可能性があります。瞬間の近くで1回だけ出現する単語を優先し("=repeat"、"=begins")、常に
cues監査を読みます。
- 字幕: プレーンなフレーズ。文/節の終わりで分割。1行 ≤ 64 文字。単語ごとのカラオケ強調表示なし(「疲れる」)。
- 呼吸:
engine/scripts/pace.mjsは、生の ElevenLabs オーディオに. ? !の後に 250 ms、: ;の後に 120 ms を挿入し、ワードタイミングをシフトします。カットポイントは ffmpeg のsilencedetect(厳密: -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(Voice Library。GET /v1/voices/{id}は 404 を返しますが TTS では動作します)、eleven_multilingual_v2、速度 1.08、with-timestampsエンドポイント、継続性のためのprevious_text/next_text。フル実行の前に、常に 1 文の 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(サンプルカットのシーン 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、rsync、apt からの Chromium ライブラリ)は、11.6 分の ceph コースを 3 分で、18 分の kafka コースを同時実行数 12~16 で 5 分でレンダリングします。リポジトリは両方のマシンの
/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/ # /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をコピーしないでください。長時間のレンダリングはデタッチして実行し(nohup bash script &をssh -f -nで開始)、timeのreal行を Monitor で待機します。ログから「error」を grep しないでください(コマンドラインに--log=errorがあります)。Waybar/図のコードが変更された場合は、両方のラボを確認します(stills)。共有されているためです。 - エンジンの変更を検証: 古いコードと新しいコードでサンプルカットをレンダリングし、
ffmpeg -i a.mp4 -i b.mp4 -lavfi ssim -f null -で比較します。scenes/durations/captions.jsonとcues監査を再生成する必要があります。