コードでショート動画を作る
画面は時間だけで決まる:ページは window.render(t) を公開し、秒数を渡すとその瞬間の画面を描画する。レンダリングスクリプトはヘッドレスChromeでフレームごとにこれを呼び出してスクリーンショットを撮り、ffmpeg がスクリーンショットと音声を合成して mp4 にする。画面を変えるにはコードを変更し、再レンダリングするだけ。
この手法は xilo がXに投稿した記事(2026-09-29、https://x.com/xilo2991/status/2104912748794589515)に基づいている。本スキルはそれにナレーション同期、サムネイル、静止画チェック、スクリプトと画面描画のルールを追加したもの。
準備
Node 20+, uv, ffmpeg およびローカルの Google Chrome が必要。デフォルトフォントは macOS の宋体、苹方、SF Mono。他のOSでは brand.json の fonts をローカルのフォントに置き換える。
- 本スキルの
template/全体を新しいプロジェクトディレクトリにコピー。 - 新しいディレクトリで
npm installを実行(playwright-coreのみインストール)。 brand.jsonを編集:seriesはサムネイル左上のシリーズ名、handleはサムネイルとエンディングの署名、avatarはアバター画像の相対パス(nullも可)、colorsとfontsは配色とフォント。- サンプルを実行:
uv run tools/voice.py demo # ナレーション、BGM、タイムライン -> episodes/01-demo/build/
node tools/render.mjs demo --stills auto # 各カット末尾の静止画
node tools/render.mjs demo # 動画全体レンダリング -> episodes/01-demo/out/video.mp4 と cover.png
sh tools/covers.sh demo # 4:3 および 3:4 サムネイル
sh tools/sheet.sh demo # サムネイル+各カット末フレームを結合したコンタクトシート build/sheet.png
コマンドにはスラッグのみを指定。スクリプトは自動で episodes/<エピソード番号>-<slug>/ を探す。ナレーションは台本テキストをMicrosoftのオンラインTTSサービスに送信。
プロジェクト構成
brand.json: ブランド設定。エンジンは変更しない。src/lib.js: 描画ツール。window.Eに配置。src/run.js: タイムラインスケジューラ。フレーム0はサムネイルで、0.3秒停止後最初のカットにパンイン。カットは横並びでパンで接続。字幕とプログレスバーもここで描画。src/stage.html: レンダリングページ。tools/voice.py: 行ごとに音声合成。単語ごとのタイムスタンプで区切り位置を決定、BGMと効果音を合成、ラウドネスを-14 LUFSに正規化。合成が不完全な場合はリトライ、3回失敗でエラー。tools/render.mjs: 4ページを並列でフレーム取得、ffmpegでエンコード、30fps。tools/covers.sh、tools/cover-vertical.mjs: 4:3 横サムネイル(16:9 サムネイルの上下に背景色を追加)と独立レイアウトの3:4 縦サムネイル。tools/sheet.sh: 完成動画からサムネイルと各カット最終フレームを抽出し、コンタクトシートに結合。episodes/<エピソード番号>-<slug>/: 各エピソードのディレクトリ。script.json、shots.js、生成されたbuild/とout/を含む。
エピソード作成の流れ
順番通りに実行。前のステップが完了しなければ次に進まない。
内容と事実は自分で準備し、本スキルはスクリプト作成から開始。
- スクリプト:
episodes/<エピソード番号>-<slug>/を作成し、script.jsonを記述。エピソード番号はcover.noにも記述。 - 画面: 同じディレクトリに
shots.jsを記述。カット数はナレーションの行数と一致。 - ナレーション:
uv run tools/voice.py <slug>。台本、音声色、サムネイルの文章を変更した場合は再実行。 - 静止画チェック: 下記「静止画チェック」参照。
- 完成:
node tools/render.mjs <slug>、続けてcovers.shとsheet.shを実行。コンタクトシートを確認。 - 納品: 本ドキュメントの最終セクション参照。
エピソード番号変更時は、ディレクトリ接頭辞と cover.no を同時に変更し、ナレーションとレンダリングを再実行。サムネイルのテキストのみ変更する場合は再ナレーション不要:script.json の cover を編集、build/timeline.js の cover も同様に変更、レンダリングと covers.sh を再実行。
script.json
{
"title": "この動画はコードで描かれています",
"cover": { "no": "01", "lines": ["この動画は", "コードで描かれています"], "hot": 1, "tag": "編集ソフトも動画モデルも不要" },
"voice": "zh-CN-YunxiNeural",
"rate": "+6%",
"lines": [["あなたが見るすべてのフレームが", "コードで描かれている。"], ["ページは一つの関数だけを公開する:", "時間を与えると、", "その瞬間の画面を描画する。"]]
}
- 1行が1カット。行内の各セグメントが画面上のビート。セグメントは
[読み上げテキスト, 字幕テキスト]と記述可能:「パーセント七十五」と読み、「75%」と表示。英語用語の発音が不正確な場合もこの方法で調整。 - サムネイル:
linesは2行以内、hotは主色を使用する行、tagは具体的な数字や結論を含むキャッチコピー、noは2桁のエピソード番号。サムネイルタイトル、プラットフォームタイトル、最初のナレーションは同じ内容を伝える。
スクリプトの書き方
- 縦スクロールで視聴されるショート動画では、75~90秒、8~9カットが適切。
+6%の速度で約毎秒4.8文字(ポーズ含む)、ナレーションは約380~430文字(漢字+英単語)。超過したら、まず文字を削ってからナレーションを生成。 - 最初の2秒で今期最も魅力的なものを配置。 スクリプト作成前に考える:今期の本当の魅力は何か?それは通常、直感に反する数字、具体的な結果、または視聴者が直面している問題。テンプレートではナレーションは0.5秒後に開始(
tools/voice.pyのHEAD)、2秒までに8~9文字しか読めないため、0番目のセグメントは短く、最初の数文字でその魅力を伝える。1番目のカットでは1.2秒以内にそれを大きな文字で表示し、サムネイルのtagも同じ内容に。この魅力は視聴者の言葉で表現し、トピックキーワードを含める(「推論強度を最大にしないで」、その後に「HighからMaxに上げると、コストは2.9倍、スコアは0.6ポイントしか増えない」)。前置きは不要、自己紹介も不要、「今日は~について話しましょう」も不要、「Xとは?」というだけの見出しも不要、「同じA、同じB」のような情報のない前置きも不要。これは少数の動画からの経験則であり、仮説として扱う。 - 以下の順序を出発点として、内容に応じて調整。
- チュートリアル系:最終結果 → ステップバイステップ → よくある落とし穴 → 一言でまとめ。
- 製品紹介:解決する課題 → メイン機能(機能ごとに1カット) → 始め方 → 一言でまとめ。
- 概念解説:結論と数字 → 従来の方法や誤解 → 問題点 → 新しい方法 → 具体的な仕組み → 実測データや公式データ → 一言でまとめ。
- 研究結果:結論と数字 → 一般的な誤解 → 実験方法 → 結果 → 今後の研究 → 理由 → 一言でまとめ。
- まとめは、まず結論や具体的な行動を一言で伝え、次に視聴者が自分の経験で答えられる質問(「普段どの設定にしてる?」)でコメントを誘導。質問は固定コメントと連動。これも仮説として検証。制限事項だけを伝えるカットは作らない。データの制約はデータカットの出典注釈に記載。
- 「次回は~」「前回は~」とは言わない。公開順が変わる可能性があるため。他のエピソードを参照する場合は「~を扱ったエピソード」とする。
- 原文に誤差範囲が記載されている場合、誤差内の差は「ほぼ変わらない」と表現。自分で導き出した数字は納品時に弱い証拠として明記。
- ナレーションにURLは入れない。「○○のサイトを見て」「○○で検索」は言わない(プラットフォームはこれらを外部サイトへの誘導とみなす。video-publishスキル参照)。
画面の描き方
shots.js は即時関数で、最後に window.SHOTS = [...] を記述。各カットは (c, k) => {}:
cは1920×1080のCanvas 2Dコンテキスト。k.at(i, delay, dur)は、i番目のセグメントが開始からdelay秒後、dur秒間で0から1にイーズアウトする進捗を返す。k.rawは同じ線形進捗。k.tはカット内の経過秒数、k.dはこの行のナレーション長、k.segsは各セグメントのカット開始からの秒数。- 描画ツールは
window.E:title(カットタイトル+ブランドストローク)、txt、measure、rise(フェードイン+移動)、pop(ポップイン)、rrect、path(進捗に応じて折れ線グラフ)、arrow、chip(ラベルブロック)、code(タイプライターコードブロック)、stroke(ブランドストローク)、mark(アバター)、sign(エンディングクレジット)、fmt、色C、フォントF。まずsrc/lib.jsの関数シグネチャを読んでから使用。足りないツールは自分のエピソードのshots.jsに記述し、個別のカットのためにlib.jsやrun.jsを変更しない。
ルール:
- 各セグメントは画面上の新しい要素に対応し、2~4秒ごとに変化が必要。1カットで1つのことを、図を使って説明:フロー矢印、数量ドットマトリクス、比較バー、コードブロック、テーブル。画面一杯のテキストは避ける。
- 左揃え、非対称。暗い背景に中央揃えの大きな文字は使わない。すべての要素を同時にフェードインさせない。発光効果は使わない。
- メインアクセントカラーは「この画面で見てほしい数字」を強調するために使用。良い悪いを示すものではない。アクセントカラーは1画面に最大1~2箇所。
- スマホを縦持ちで横画面を見ることを想定してフォントサイズを決定:
txtは56未満のフォントサイズを引き上げ(24を指定すると34px、30を指定すると38pxに)。情報を伝える文字は指定値30以上。出典注釈は24も可。幅の見積もりは引き上げ後のフォントサイズで計算。等幅フォントの各文字はフォントサイズの約0.6倍。 - レイアウト境界:字幕エリアはy932以下、1行約30全角文字。サムネイルのキャッチコピーは約14全角文字でアバターに接触。カットは1.025倍のスローズームイン。端の要素は20px以上のマージン。指定値24の注釈は1行約48全角文字。
- 宋体Blackには「÷」がない。「×」は大きなバツとして描画される:演算子は苹方で描画するか、「割る」「倍」と記述。200pxの大見出しで英文字のディセンダーが次の行に食い込む可能性。
- 0番目のセグメントが非常に短い場合、カット開始時に空画面ができやすい:図の骨格(軸、ヘッダー、輪郭)を0番目のセグメントで配置し、データは後続のセグメントで出現させる。
- 実測データに基づかない図には「図解」と明記。原文のチャートから読み取った数字には、注釈に「原文チャートから読み取り」と記述。データカットの下部に出典注釈を記述。名称のみ(「出典:○○公式ドキュメント」)、ドメインやURLは不要。
- 各カットで
E.titleを使用してタイトルを表示。最後のカットではE.signでクレジット。
静止画チェック
node tools/render.mjs <slug> --stills auto で各カットの最終フレームを出力。--stills 0,t1,t2,… で指定秒数のフレームを出力(0はサムネイル)。中間フレームは各行開始後約0.4秒と各セグメント後約1秒で取得。時間は該当エピソードの build/timeline.js から読み取る。--stills を実行するたびに前のバッチはクリアされるので、分割して確認するか、時間をまとめて1つのコマンドに指定。フレーム数が多い場合はffmpegでコンタクトシートに結合。サムネイルと最終フレームは原寸で確認。
フレームごとに確認:
- テキストがカード、コードブロック、テーブル列からはみ出していないか、字幕エリアやプログレスバーに隠れていないか。
- 右揃えの数字と左側のラベルが接触していないか。座標軸ラベルとグラフのラベルが重なっていないか。
- カットの最終フレームが最も情報が完全なフレームか。未配置の要素がないか。
- サムネイルの2行タイトルが小さくなりすぎていないか。キャッチコピーが幅を超えていないか。
- 2~3秒間新しい要素がない空画面がないか、特にカット開始時。
問題を見つけたら修正。既知のはみ出しを残したまま完成動画を出力しない。画面のみの変更は直接レンダリングを再実行。
既知の制限
- edge-tts はMicrosoftの非公式APIを使用。バージョン7.2.8は2026年10月から403エラーを返す(rany2/edge-tts #490)。
tools/voice.pyは修正PR #491のコミットに固定。公式版リリース後はedge-tts==<新版>に戻す。再度403が発生した場合はアップストリームのIssueを確認。 - エージェントはナレーションを聞けない:英語用語の発音と速度は人間が確認。合成中の途切れは
voice.pyの長さチェックで防止。 - モーションブラーなし。音楽ビートに合わせたカット編集なし。
納品時の説明事項
- ナレーションは未確認。
- 静止画チェックの結果(残っている既知の問題を含む)。
- 完成動画、3種類のサムネイル、コンタクトシートのパスおよび動画の長さ。