html-to-video · コードでフレームごとに動画を作る
これは何か
AI による動画生成ではありません。コードで1フレームずつ描画する方式です:
HTML/Canvas を書き、frame(t) の純関数としてシーンを表現
→ ヘッドレスブラウザが各フレーム t へ seek、スクリーンショット
→ ffmpeg で H.264 MP4 にエンコード
字幕 / カメラワーク / トランジションはすべてコード内で制御。コードを変更して再レンダリングするだけ
利点:確定的(同じ入力=同じ出力)、フレーム単位で制御可能、日本語の文字が完璧、費用ゼロ。 最適な用途:解説アニメーション、動的な図解、プロダクトデモ、データ可視化アニメーション、動画のイントロ。 不向き:実写風 / 雰囲気のある映像(そういうのは VideoGen で)。
何ができるか(assets/templates/ にある 11 個のテンプレートは、文字を変えるだけで使えます):
| テンプレート | 用途 | 出力尺 |
|---|---|---|
explainer.html | マルチシーン解説動画:イントロ / 人物+吹き出し / 要点 / 棒比較 / エンディング。シーンにトランジション付き、JSON のショットテーブルを渡せます。横画面で要点がちょうど3つの場合は自動で3カラムに、縦画面(1080×1920)には上下レイアウトが適用されます | --duration=17.6 |
title-card.html | 動画のタイトルカード:見出しが1文字ずつポップイン+サブタイトル | --duration=6 |
bar-chart.html | 棒グラフ / ランキング:棒が順番に成長、数値も同期してスクロール | --duration=6 |
line-chart.html | 折れ線グラフのトレンド:線が伸び、データポイントがハイライトされ、最終点にラベル表示 | --duration=8 |
number-counter.html | 大きな数字 / KPI:0から目標値までカウントアップ、増減ラベル付き | --duration=7 |
timeline.html | タイムライン / フローチャート:中心軸が伸び、ノードが順にハイライト | --duration=7 |
vertical-card.html | 縦画面 9:16 カード:大きな見出しと大きな数字、TikTok/Shorts/Reels 向け | --duration=6 --width=1080 --height=1920 |
compare.html | ビフォーアフター比較:左右2カラムで比較、結論の帯で締め | --duration=7 |
flow-steps.html | フローチャートアニメーション:ステップが順にハイライト、現在のステップが強調表示 | --duration=7 |
subtitle-card.html | フレーズごとの解説カード:1フレーズずつ表示、下部にプログレスバー | --duration=8 + --srt |
vertical-subtitle.html | 縦画面字幕カード:キーワードがハイライト、9:16 ネイティブレイアウト | --duration=8 --width=1080 --height=1920 + --srt |
各テンプレートは単一ファイルで完結しており、ファイル上部にある CFG 設定ブロックの文章やデータを変更するだけ ―
Canvas がわからなくても動画を作れます。11 テンプレートすべてがテーマ切り替えに対応しています(「配色の変更」セクション参照)。
別のものを作りたい場合は、assets/animation-skeleton.html を参照してゼロから始めてください。
「単なるアニメーション」ではなく「1本の動画」を作る:assets/lib/scenes.js を使って複数のシーン+トランジションを記述。
さらに、ショットテーブルは --story=shots.json で外部化できます ― LLM に JSON を書かせる方が、数百行の Canvas コードを書かせるより信頼できます。
詳しくは下記「マルチシーンとトランジション」セクションを参照してください。
サイズ:テンプレートはビューポートに追従、手動変更不要
11 テンプレートすべてが window.innerWidth/innerHeight でキャンバスを設定するため、どのサイズにしても画像が切れることはありません:
# 横画面 1080p(デフォルト)
bash assets/render.sh title-card.html --duration=6 --fps=60
# 縦画面 9:16
bash assets/render.sh title-card.html --duration=6 --fps=60 --width=1080 --height=1920
# 720p / 4K も同様
bash assets/render.sh title-card.html --duration=6 --fps=60 --width=1280 --height=720
テンプレート内部では3段階のスケーリングを使用して、デザインカンプを実際のキャンバスに合わせます:
| 変数 | 意味 | 1920×1080 | 1080×1920 |
|---|---|---|---|
SX = W/1920 | 横方向のサイズ、左右マージン | 1 | 0.5625 |
SY = H/1080 | 縦方向のサイズ、行間 | 1 | 1.7778 |
SF = √(SX·SY) | フォントサイズ、線幅、角丸、ブラー | 1 | 1 |
フォントサイズは幅ではなく面積に比例(SF)―― 縦画面にしても文字が小さくなりません。レイアウトが展開されるだけです。
1920×1080 では 3 変数すべてが 1 になるため、デフォルト出力は旧バージョンとピクセル単位で一致します(実測 PSNR = inf)。
自分で書いたアニメーションもこれに従ってください。さもないと --width/--height を変更したときに、右下が静かに切り取られます:
const cv = document.getElementById('stage');
cv.width = window.innerWidth; // ← 1920 とハードコードしない
cv.height = window.innerHeight; // ← 1080 とハードコードしない
レンダラーは現在、キャンバスサイズを能動的に測定し、ビューポートより小さい場合は明確に警告するようになりました(以前のように半分だけの画像を静かに出力することはありません)。
まず依存関係をインストール(一度だけ)
レンダラーには Node ワークスペース(playwright + フルバージョンの ffmpeg を含む)が必要です。1コマンドで完了:
bash scripts/install-deps.sh # ワークスペースはデフォルト ~/.workbuddy/binaries/node/workspace
bash scripts/install-deps.sh /path/to/workspace # または自分でパスを指定
自動でお使いのマシンのブラウザキャッシュを検出し、適合する playwright のバージョンを選び、フルバージョンの ffmpeg をインストールします。
そしてワークスペースのパスを ~/.workbuddy/.html2video-env に書き込みます。その後、render.sh / smoke.sh / qa.sh がすぐに使えるようになります。
手動でインストールすることもできます(ワークスペースに playwright + @ffmpeg-installer/ffmpeg が必要です)。その後、自分で設定ファイルを書きます:
# ~/.workbuddy/.html2video-env
WB_NODE_WORKSPACE="<あなたの node ワークスペースの絶対パス>"
環境変数を使って一時的に指定することもできます:
export WB_NODE_WORKSPACE="<パス>"。 パスを${VAR:-デフォルト値}に書かないでください ― ユーザー名にアポストロフィ(O'Brienなど)が含まれていると、 bash がそれをクォートの開始とみなしてbad substitutionエラーを起こします。
4つのハードな制約(経験済み、繰り返さないで)
-
Playwright のバージョンはブラウザキャッシュと一致している必要があります。
playwright-core@Xは、自分がbrowsers.jsonで宣言している chromium ビルド番号のみを認識します。 一致しないと "Executable doesn't exist" が発生します。→ バージョンを推測せず、
scripts/install-deps.shを使ってください。これはscripts/detect-env.jsを実行してキャッシュを検出し、自動的にバージョンを選択します。 単独で検出する場合:node scripts/detect-env.js(RECOMMEND_PW=...を出力)。 既知のマッピング:build 1148→1.49.1、1155→1.50.1、1169→1.52.0、1187→1.55.0、1208→1.58.0。注意:ネットワーク環境によっては、chromium のダウンロードが繰り返し中断されることがあります(キャッシュディレクトリに中途半端な不完全なディレクトリが残る)。 その場合は、アップグレードするよりも既存のキャッシュを再利用することを優先してください ― これこそが検出スクリプトの存在理由です。
-
Playwright に付属する ffmpeg は使えません。
ms-playwright/ffmpeg-*/ffmpeg-win64.exeは--disable-everythingでコンパイルされているため、 VP8 エンコード + webm コンテナしかサポートしておらず、H.264、GIF、PNG デコードすら有効になっていません。 必ず@ffmpeg-installer/ffmpegのフルバージョンを使用してください。 -
ESM は
NODE_PATHを認識しません。.mjsでimport 'playwright'を使うと必ずERR_MODULE_NOT_FOUNDになります。 → 補助スクリプトはすべて CJS(.cjs/require) で書いてください。 -
PATH は信頼できません。 特に Git Bash / Windows では、PATH に追加された Windows 形式のパスが変換によって壊れる可能性があります。 → ffmpeg を PATH 経由で渡さず、
FFMPEG環境変数を使用してください(レンダラーは対応済み)。
出力手順
ステップ 0(一番簡単な方法):テンプレートを選んで文字を変える
cp assets/templates/bar-chart.html my-video.html
# my-video.html を開き、上部の CFG にある title / data / note だけを変更
bash assets/render.sh my-video.html --duration=6 --fps=60
テンプレートには字幕セーフゾーンの考慮があります:テキストは画面下部 15% に配置されません(字幕を焼き付けると隠れてしまうため)。 自分でレイアウトを作る場合もこれに従ってください。
ステップ 1:アニメーション HTML を書く(テンプレートで不十分な場合)
assets/animation-skeleton.html を骨組みとして使用します。コア契約(レンダラーはこれに依存して動作します):
// これら2つを必ず公開する必要があります
window.__seek = (t) => { /* 時刻 t のシーンを完全に再描画 */ };
window.__ready = true; // 準備完了シグナル
// レンダラーは window.__seekRender = true を注入します。
// その場合、【自己駆動クロックをフリーズ】し、外部の __seek 呼び出しだけでフレームを進める必要があります(決定性を保証するため)
if (window.__seekRender) { renderAt(0); window.__ready = true; }
else { /* ブラウザプレビュー:requestAnimationFrame による自己駆動ループ */ }
鉄則:各フレームの描画 = 時刻 t の純関数。 「前のフレーム」の状態に依存しないでください。
Math.random() / Date.now() / performance.now() を使ってはいけません ― 同じ入力から異なる出力になり、
QA の「決定性」チェックが赤くなり、複数ワーカーによる並行レンダリングでそれぞれ異なる結果になります。
キャンバスが唯一の出力です:<canvas width=1920 height=1080>、CSS は left:0; top:0、
body は margin:0。レンダラーは clip:{x:0,y:0,width:W,height:H} で左上隅を切り取るため、キャンバスはビューポートを完全に埋める必要があり、中央揃えではいけません。
(レンダラーは DOM/CSS/SVG アニメーションもサポートしています。__seek を公開するだけで OK ― ただし、DOM アニメーションはフレームごとのリフローが遅いため、純粋な Canvas の方が一般的に安定していて高速です。)
禁止されているクラス名(レンダラーが注入する CSS によって無条件に非表示になり、要素が見えなくなります):
.no-record .progress .progress-bar .counter .tCur .phases .phase-label .phase .replay .masthead .kicker .title .footer
また、position:fixed/sticky の要素に <button> を追加しないでください。ヒューリスティックルールによって再生コントロールと見なされて非表示になります。
(特定の要素を完成動画に表示したい場合は、data-record-keep="true" を追加するとヒューリスティックによる非表示を回避できます。)
日本語フォント:'"Microsoft YaHei", "PingFang SC", system-ui, sans-serif';等幅は 'ui-monospace, Consolas, monospace'。
ステップ 2:スモークテスト(飛ばさないで)
まず数フレームだけキャプチャして、コントラクトが準備できていること、コンソールエラーがないこと、映像が壊れていないことを確認してから、全量レンダリングに進みます:
bash assets/smoke.sh animation.html
# SMOKE_T="0,1,2" SMOKE_W=1280 SMOKE_H=720 bash assets/smoke.sh animation.html
さらに高速なイテレーション方法:短い区間だけレンダリングすると、数秒で動きを確認できます。
bash assets/render.sh animation.html --from=3 --to=5 --fps=30 # 3秒~5秒だけレンダリング
bash assets/render.sh animation.html --stills=1,3,5 # 3枚の静止画 PNG だけ出力
ステップ 3:フルレンダリング
bash assets/render.sh animation.html --duration=12 --fps=60 --width=1920 --height=1080 --concurrency=4
よく使うパラメータ:
| パラメータ | 説明 |
|---|---|
--duration / --from / --to | 尺 / 特定の区間のみレンダリング(秒)。範囲を指定すると出力ファイル名が ファイル名.開始-終了s.mp4 になり、完成品を上書きしません |
--stills=0.5,2.0 | 指定した時刻の PNG のみ出力、エンコードは行いません |
--fps / --width / --height | フレームレートと解像度 |
--preset=<名前> | プラットフォームに応じたプリセットサイズ:douyin / tiktok / vertical(1080×1920)、xiaohongshu(1080×1440)、instagram(1080×1350)、square(1080×1080)、bilibili / youtube / landscape(1920×1080) |
--presets=a,b,c | 1コマンドですべてのアスペクト比を出力。各アスペクト比ごとに1つずつ完成動画が作られます(ファイル名に自動で -アスペクト比名 が付加)。クロスプラットフォーム公開に最適 |
--theme=<名前> | テーマを変更(midnight / paper / neon)。テンプレート内の CFG.theme を上書きするため、ファイルを変更する必要はありません。名前を間違えると明確に警告され、midnight にフォールバックします |
--out=<ファイル> | 出力先のパスを指定(デフォルトは HTML と同じディレクトリ、同名)。1つの HTML から複数の配色を出力する場合に使用。後からレンダリングしたものが前のものを上書きするのを防ぎます。上位ディレクトリが存在しない場合は自動的に作成されます |
--json | stdout に結果の JSON を1行だけ出力(output / duration / frames / warnings など)。人間向けのログはすべて stderr へ。Agent が解析するために使用 |
--concurrency | 並列ワーカー数(各ワーカーは1つの Chromium ページを使用。メモリに注意)。auto を指定すると CPU とメモリに基づいて自動選択 |
--audio=<file> | エンコード後に自動で音声をミックス(ビデオストリームは直接 copy、再エンコードしません)。--duration が指定されていない場合、自動的に音声の長さに合わせて出力されます(整数フレームに切り上げ、音声の末尾が切れないようにします) |
--audio-fit=trim|pad | 音声と動画の長さが一致しない場合の処理:trim は音声トラックに合わせて終了(デフォルト)/pad は無音を追加して動画の長さを保つ |
--gop=<秒> | キーフレーム間隔。例:--gop=1 は毎秒キーフレームを配置し、後で秒単位でのシークを容易にします |
--keep-frames | 成功時もフレームごとの PNG を保持(CRF を変更して再エンコードする際に再キャプチャする必要がありません) |
--tmpdir=<ディレクトリ> | フレームごとの PNG を保存するディスクの場所(デフォルトは HTML と同じ場所)。1080p 1分間で約 2.4 GB になるため、システムディスクに空きがない場合は別のディスクを指定してください |
--resume | 前回のレンダリングが中断された場合、続きからレンダリング。すでにレンダリング済みのフレームは再利用します(パラメータが前回と異なる場合は拒否されます。下記参照) |
--alpha | 透明背景で出力(ProRes 4444 .mov)。実写や AI 生成の映像に重ねるために使用。下記参照 |
--srt=<file> | 外部字幕タイムライン(SRT / VTT / JSON)。window.__cues に注入されます。下記参照。同義パラメータ --cues |
--cue-log=<file> | frame,t,cue_index,cue_start,cue_end の CSV を出力。字幕が正確にタイミングが合っているかを確認するために使用 |
--story=<file> | ショットテーブル JSON。window.__story に注入。台本全体を差し替えます(「マルチシーンとトランジション」参照) |
--scene-log=<file> | シーンタイムライン CSV を出力。ショットテーブルの総尺と --duration が一致しない場合は警告されます |
--gif[=fps,幅] | 出力後に GIF も生成(デフォルト 15fps / 幅 960)。2段階パレット最適化。再レンダリングは行いません |
--poster=<秒> | 出力後にサムネイル静止画 .jpg を生成。サイトやSNS投稿用 |
--batch=<file.json> | ジョブのリストを順番にレンダリングし、最後にサマリーテーブルを出力。個別の失敗ですでにレンダリング済みのものが影響を受けることはありませんが、全体としては非ゼロで終了します |
--headed | ブラウザウィンドウを表示してレンダリング(デバッグ用。並列数は自動で1に低下、ウィンドウが大量に表示されるのを防ぐ) |
--allow-errors | ページのエラーを失敗とみなさない(デフォルト:エラーがあると非ゼロで終了) |
--keep-chrome | 再生コントロールを非表示にしない(デバッグ用) |
--readytimeout=8 | __seek の準備が整うまでの待機時間(秒)。大きなフォントや大量のデータで起動が遅い場合は調整可能 |
--no-serve | file:// でページを読み込む(デフォルトはローカル http サーバー。下記参照) |
ESM / fetch を使用するプロジェクトでもそのままレンダリングできます。 レンダラーはデフォルトで 127.0.0.1 にバインドされたローカル http サーバーを起動してページを読み込みます ―
file:// では <script type="module"> や fetch() が CORS でブロックされるため、Vite/esbuild で構成されたアニメーションや
ローカル JSON を読み込むプロジェクトは動作しません。http 経由であればこれらの問題は発生しません。レンダリング開始時にこのアドレスが表示されるので、トラブルシューティング時にブラウザに直接貼り付けてコンソールを確認するのが最も簡単です。
サーバーが起動できない場合は自動的に file:// にフォールバックし、その旨が通知されます(警告のみ、失敗とはみなされません)。
- 出力は HTML と同じ名前の
.mp4ファイル - フレーム数 =
fps × duration。12s@60fps = 720 フレーム、実測約 6〜7 分(concurrency=4) - エンコードに約 1 分(crf 18 / preset medium)
レンダリング終了時にページエラーがあると非ゼロで終了します(ただし完成動画は通常どおり出力されるので、確認に使用できます)。
レンダリング中に中断された場合、最初からやり直さないで
長い動画(60fps × 30s = 1800 フレーム)の 80% まで進んでクラッシュした場合、最初からやり直すのは最も労力がかかります。--resume を追加すれば続きからレンダリングできます:
bash assets/render.sh animation.html --fps=60 --duration=30 --resume
# ▸ resume: 1432 フレームを再利用、あと 368 フレームをレンダリング
3つのルール。すべて、「続きからレンダリングした動画が一回でレンダリングした動画と完全に同一であること」を保証するためのものです:
- フレームは最初に
.partとして書き込まれ、その後リネームされます。 強制終了されたフレームが「完了済み」と偽装する中途半端な PNG を残すことはありません。そのため、再利用されるフレームは本当に描画が完了したものだけです。 - パラメータが変更された場合は即座に拒否されます。 前回 30fps、今回 60fps だと、2つのバッチのフレームが混ざってエンコードされ、動画の前後でフレームレートが一致せず、エラーも報告されません ―
そのため、
fps / サイズ / 範囲 / レンダリングモードのいずれかが異なる場合は終了し、「前回 vs 今回」を表示します。自分で削除するかどうかを決めてください。 - フレーム番号は連続している必要があります。 1枚でも欠けると ffmpeg が静かに早めに終了し、動画が不自然に短くなります。そのため、エンコード前に
[開始, 終了)の範囲で各フレーム番号を確認し、 欠けがあれば報告し、--resumeを追加するよう促します。
一時ディレクトリは HTML の隣にある .seek-tmp-resume/ に固定されます(--resume がない場合でも、一時ディレクトリは使い捨てで、並行レンダリングが互いに干渉することはありません)。
レンダリング完了後に自動的に削除されます。途中で保持したい場合は --keep-frames を指定してください。
これは意図的な設計です:ページエラーはしばしば「全面真っ黒 / 画面の半分が欠けている」ことを意味します。以前のバージョンでは、成功したと報告し続け、
後でフレームを確認するまで気づかないことがありました ― 現在はエンコードの前にエラーを表示し、最後に失敗と判定します。
透明出力:テキストレイヤーを他の映像に重ねる
AI 生成の背景 + コード生成のテキストレイヤーは、現在最もコスト効率の良い組み合わせです(モデルは文字を描画するときに必ずぼやけますが、コードはピクセル単位で正確に文字を描画します)。
--alpha はこの用途のために存在します:
bash assets/render.sh animation.html --duration=6 --fps=30 --alpha
# → animation.mov(ProRes 4444 / yuva444p10le、背景透明)
テンプレート側で1つ対応が必要です:window.__transparent が真の場合、背景レイヤーの描画をスキップします。
現在、title-card、compare、flow-steps、subtitle-card、vertical-subtitle、
explainer の6つはすでにこの処理が実装されており、--alpha を追加するだけで透明効果が得られます。
残りの5つは全面グラフ(棒グラフ / 折れ線 / 大きな数字 / タイムライン / 縦型カード)で、そもそも背景色が必要なため、付けても不透明な動画しか得られません。
自分で書いたアニメーションはこれに従ってください:
const TRANSPARENT = !!window.__transparent;
function drawFrame(t) {
g.clearRect(0, 0, W, H);
if (!TRANSPARENT) {
// 背景色 / 全画面グラデーション / 光の斑点 ― これらはすべてスキップ
g.fillStyle = '#080A10';
g.fillRect(0, 0, W, H);
}
// テキスト、図形は通常どおり描画
}
全画面の半透明の光の暈しも一緒にスキップしてください。 たとえ不透明度が 5% でも、実写に重ねるとエリア全体が白っぽくなり、 編集者が最初に感じるのは「この素材、汚い」です。実測:背景色だけをスキップして光の斑点を残した場合、四隅のアルファ値は 12(0 ではない)。 すべてスキップした場合、四隅のアルファ値は 0、不透明なピクセルはわずか 3.7%(すべてテキスト)。
サイズのトレードオフ(960×540 / 2s / 20 フレーム実測):
| 出力 | サイズ | 用途 |
|---|---|---|
H.264 .mp4 | 118 KB | 完成動画としてそのまま配信 |
ProRes 4444 .mov | 760 KB(6.5倍) | 編集ソフトにドラッグしてオーバーレイ |
ProRes のみ。VP9 はありません。 --pix_fmt yuva420p + -auto-alt-ref 0 を試しましたが、ファイルサイズは 7.8KB
から 16.8KB に増加しました(アルファデータが書き込まれました)が、Chromium で再生するとフレーム全体のアルファが 0 になりました ― テキストさえも透明になりました。
lossless、固定ビットレート、-lag-in-frames 0 も同様でした。自分で正しいことを証明できない機能は、オプションとしてリリースしません。
小さなサイズの透明素材が必要な場合は、ProRes を編集ソフトにインポートしてから webm に書き出すのが、そちら側の仕事です。
音声と動画の同期:先に音声ありき、その長さに合わせて動画の尺を決める
実際のワークフローは「文章を書く → ナレーションを録る → 映像を合わせる」であり、最初に 6 秒と決めてから無理にナレーションを詰め込むことではありません。
そのため、--audio を指定して --duration を指定しない場合、動画は音声の長さに合わせて出力されます:
bash assets/render.sh title-card.html --audio=voice.m4a --story=shots.json
# ▸ audio: 音声 12.48s、--duration 未指定 → 動画は音声の長さに合わせる(12.5s、整数フレームに切り上げ)
- 整数フレームに切り上げ(四捨五入ではない):半フレームでも最後の文字の語尾が削られてしまいます。
- 明示的に
--durationを指定し、音声との差が 0.35 秒を超える場合は明確に警告されます。差の大きさと、どちらに影響があるかが示されます。 旧バージョンでは、ここで静かに短い動画が作成されていました ― 映像の後半が完全に消え、ログには✓ Doneとだけ表示されていました。 --audio-fit=pad:音声が映像より短い場合、無音を追加して映像全体を保持します(デフォルトのtrimは音声トラックに合わせて終了)。
注意:trim の場合、完成動画は音声の秒数と正確には一致しません。ビデオストリームは -c:v copy で直接コピーされるため、
ffmpeg はフレームパケットの境界でしか終了できません。実測では 0.3〜0.5 秒余分に残ります。ミリ秒単位で揃えるには再エンコードが必要ですが、
それは画質を精度と引き換えにするものであり、価値がありません ― 動画の最後に数フレーム余分にあっても、通常はわかりません。
一稿多発:1コマンドですべてのプラットフォームのアスペクト比を出力
同じ動画を抖音(Douyin)、小红书(Xiaohongshu)、Bilibili に投稿する場合、編集ソフトではアスペクト比ごとにシーケンスを作成し、それぞれ書き出すたびに監視する必要があります。 しかし、ここのレイアウトはそもそもパラメータ化されています。もう一度レンダリングするのは、ループを1回回すだけのことです:
bash assets/render.sh bar-chart.html --duration=8 \
--presets=douyin,xiaohongshu,square --out=dist/chart.mp4
# → dist/chart-douyin.mp4 1080×1920
# dist/chart-xiaohongshu.mp4 1080×1440
# dist/chart-square.mp4 1080×1080
| プリセット | サイズ | 対象プラットフォーム |
|---|---|---|
douyin / tiktok / vertical | 1080×1920 | 抖音、快手、WeChat Video、Reels、Shorts |
xiaohongshu / xhs | 1080×1440 | 小红书 3:4 |
instagram / ig | 1080×1350 | IG フィード 4:5 |
square | 1080×1080 | 正方形 1:1 |
bilibili / youtube / landscape | 1920×1080 | 横画面 16:9 |
- 単一アスペクト比の場合は
--preset=xiaohongshu。複数の場合は--presets=a,b(最低2つ。1つだけの場合は--presetを使用)。 - プリセット名を間違えると即座に失敗し、使用可能な名前のリストが表示されます ― 1920×1080 で間違ったアスペクト比の動画を密かにレンダリングすることはありません。
- レイアウトが耐えられるかどうかは別問題:テンプレートはビューポートに追従しますが、9:16 と 16:9 では情報密度が異なり、
3:4 ではフォントサイズが大きすぎる / コンテンツが詰まりすぎる可能性があるため、自分で確認する必要があります。
--presetsはサイズが正しいことだけを保証し、構図が見栄えすることを保証しません。 - 縦画面の下部の空白は意図的なものです、レイアウトミスではありません:プラットフォームのプログレスバーやボタンがそこに重なるため、 テンプレートは一律で字幕焼き付け用に 15% のセーフゾーンを確保しています。
--stillsとは併用できません(一方は複数の動画を出力、もう一方は静止画のみを出力)。--batchともネストできません。
Agent 向け:--json
このツールの主な呼び出し元は AI Agent です。人間は ✓ Done: xxx.mp4 (1.2 MB · 30fps · 12s) を読めますが、
Agent は正規表現で推測するしかありません ― パスにスペース、日本語、カッコが含まれているとクラッシュします。--json は結果を構造化します:
bash assets/render.sh title-card.html --duration=6 --json
{"ok":true,"exitCode":0,"input":"…/title-card.html","output":"…/title-card.mp4",
"width":1920,"height":1080,"fps":60,"duration":6,"frames":360,"bytes":2411724,
"gif":null,"poster":null,"warnings":[],"mode":"video","theme":null,"preset":null}
- stdout にはこの1行だけ。進捗状況や人間向けのログはすべて stderr に出力されます ―
JSON.parse(stdout)を直接呼び出すだけです。 warningsは配列です:パラメータの競合、音声と動画の長さの不一致、認識できないテーマ名などがここに入ります。空配列=クリーン。durationは完成動画の実際の長さ(ffmpeg から読み取ったもの)であり、計画値ではありません。--presetsを使用した場合、outputは配列になり、presets: [{name,width,height,output,ok,bytes}]が追加で付属します。
字幕を外部タイムラインと同期:ナレーションが変わっても、字幕は追従
字幕の文章をテンプレートの CFG.lines に書くのが最も簡単ですが、ナレーションが必要になると、タイミングを音声に合わせる必要があります ―
毎回文章を変えるたびに手動で秒数を数え直すわけにはいきません。そのため、字幕ファイルを直接読み込むことができます:
bash assets/render.sh subtitle-card.html --duration=8 --srt=voice.srt --cue-log=cues.csv
- 対応フォーマット:SRT(
HH:MM:SS,mmm)、WebVTT(MM:SS.mmmも可)、JSON([{start,end,text}])。 - 一度解析され、
window.__cuesに注入されます。テンプレートは現在のフレームのtを使って、表示すべき該当行を自分で検索します。--srtを使用する場合、テンプレートは**__cuesを優先して読み取り、なければCFG.linesにフォールバック**するため、同じテンプレートで2つの使用方法が両立します。 --cue-logはframe,t,cue_index,cue_start,cue_endの CSV を出力します。「字幕のタイミングが合っているか」を 計算可能な数値に変換します:各行の最初のフレームの時刻から SRT の開始時刻を引いたものが誤差です。 実測:4行 / 80 フレームで、各行の誤差は 0 ms。フレームカバレッジ数は理論値と完全に一致しました。
テンプレート側は、たった2行で対応できます:
const cues = (window.__cues && window.__cues.length) ? window.__cues : CFG.lines;
const cur = cues.find(c => t >= c.start && t < c.end) || cues[cues.length - 1];
字幕は下部 15% に入れないでください。 そこは各プラットフォームのプログレスバー / いいねボタンエリアです。書いても無駄になります。
qa.js は「字幕が領域に入っています」と警告します。新しい4つのテンプレートの縦画面レイアウトは、このセーフゾーンを確保しています。
配色の変更:1つのフィールドを変えるだけで、Canvas コードには触れない
assets/templates/themes.js には3つのカラーパレットが定義されており、11テンプレートすべてが対応しています。2つの切り替え方法:
# ① コマンドラインで変更(推奨):ファイルを変更せずに、1回のレンダリングで1つのテーマ
bash assets/render.sh bar-chart.html --duration=6 --theme=paper --out=bar-paper.mp4
# ② ファイルを変更:テンプレート上部の CFG に直接指定
const CFG = {
theme: 'paper', // midnight(デフォルト、ダーク)| paper(ライト)| neon(黒紫ネオン)
...
};
コマンドラインが CFG.theme より優先されます(レンダラーは window.__h2vTheme を注入し、テンプレートは
h2vTheme(window.__h2vTheme || CFG.theme) で読み取ります)。したがって、同じ HTML から3つの配色を一括出力できます:
[{"html":"chart.html","out":"dist/chart-midnight.mp4","theme":"midnight"},
{"html":"chart.html","out":"dist/chart-paper.mp4","theme":"paper"},
{"html":"chart.html","out":"dist/chart-neon.mp4","theme":"neon"}]
bash assets/render.sh --batch=jobs.json
→ chart-midnight.mp4 / chart-paper.mp4 / chart-neon.mp4
各テーマのスクリーンショットは `assets/docs/screenshots/` にあります。