Kuboyage

・ AI / Claude Code / 個人開発

Claude Codeで20秒のサイト紹介動画を作った|HTMLから動画にするHyperFramesの手順 🎬

Claude Codeで20秒のサイト紹介動画を作った|HTMLから動画にするHyperFramesの手順 🎬

この記事について 📖

対象読者: 自分のサイトやプロダクトの紹介動画を、動画編集ソフトを使わずに作りたい方。AIに動画を作らせてみたけれど「それっぽいだけ」で終わった方

この記事で扱うこと: Kuboyageのトップページに置いた20秒の紹介動画を、Claude CodeとHyperFramesで作った手順です。使った道具から場面の作り方、音の付け方、仕上がりの確かめ方、Webページへの埋め込み方まで順に解説します

はじめに 👋

こんにちは!H×Hのセンリツ大好きエンジニアです!✌️

Kuboyageのトップページ、自己紹介のすぐ上に「20秒でわかる Kuboyage」という動画を置きました。サイトが何をしている場所なのかを、文章を読む前に20秒で伝えるための動画です。

この動画は、動画編集ソフトを一度も開かずに作りました。Claude Codeに場面をHTMLとして書かせ、HyperFramesというフレームワークでMP4に書き出しています。

きっかけは、X(旧Twitter)で読んだOpus 5.5でモーションデザインをする講座の記事(@0xMovez)です。「ワンプロンプトで動画ができた」という投稿の裏側には、長い指示書と、自分の出力を見直す仕組みがある。この記事の主張はそこにありました。今回はその考え方を、自分のサイトの紹介動画で試しています。

※この記事はAIと2人3脚で執筆されています

この記事はおよそ5,500字なので、11 分で読めます!

目次 📚

  1. 完成した動画
  2. 使った道具
  3. 作り方の全体像
  4. 手順1:サイトを取り込んで、絵コンテを書く
  5. 手順2:5つの場面を並列で作る
  6. 手順3:BGMと効果音を付ける
  7. 手順4:スクショで見直して直す
  8. 手順5:サイトに埋め込む
  9. まとめ

完成した動画 🎥

「20秒・1920×1080・ナレーションなし。音楽と効果音だけで進みます。」

動画そのものはKuboyageのトップページで再生できます。流れは次の5場面です。

20秒の紹介動画から6コマを抜き出したもの。あと少し、こうだったら。と入力される場面、波に浮かぶ船、毎日に、小さな余白を。の見出し、6つのプロダクトのアイコン、作って、書く。とブログ記事3枚、Kuboyageのロゴ

場面長さ画面で起きること
1. つかみ3.5秒「あと少し、こうだったら。」が1文字ずつ入力され、消えた後に波の上へ船が浮かぶ
2. 伝えたいこと3.5秒サイトの見出し「毎日に、小さな余白を。」が出て、「余白」の字間が広がる
3. プロダクト5.5秒6つのプロダクトのアイコンが並び、iOSアプリ・自動リサーチ・Webツールの分類が付く
4. ブログ4秒「作って、書く。」と一緒に、最近の記事3本のカードが並ぶ
5. 締め3.5秒Kuboyageのロゴ、自分の似顔絵、kuboyage.devのURLで止まる

場面をまたいで、1本の細い波線が続いています。サイトのヒーローにある波をそのまま動画のモチーフにしたもので、5つの場面を1本の作品としてつなぐ役目をしています。

使った道具 🧰

「動画を作るのはブラウザ、音を作るのはローカルのモデルとコードです。」

役割使ったもの
指示を出して作業を進めるClaude Code(Opus 5.5)
HTMLを動画にするHyperFrames([email protected] に固定)
アニメーションGSAP
BGMACE-Step 1.5(Macのローカルで動く音楽生成モデル)
効果音Node.jsで波形を直接計算して作ったWAV
音の合成と書き出しffmpeg

HyperFramesは、HTMLで書いた場面を動画にするフレームワークです。ページを「時刻を指定すると、その瞬間の絵が決まる」形で書いておくと、ヘッドレスのChromeが1コマずつ撮ってMP4にまとめます。要素に data-start と data-duration を書き、アニメーションはGSAPの止めたタイムラインとして登録します。

<div id="root" data-composition-id="main" data-duration="20"
     data-width="1920" data-height="1080">
  <div data-composition-id="01-hook"
       data-composition-src="compositions/frames/01-hook.html"
       data-start="0" data-duration="3.5"></div>
  <!-- 02〜05 も同じ形で並べる -->
  <audio data-start="0" data-duration="20" src="assets/audio/mix.m4a"></audio>
</div>

大事なのは、どの時刻を指定しても同じ絵になることです。Math.random() やタイマーを使わないので、同じHTMLからは毎回同じ動画が出てきます。1か所直したら、その場面だけ見直して書き出し直せばすみます。

作り方の全体像 🗺️

「いきなりHTMLを書かせず、先に決めごとを文書にしました。」

作業は次の順に進めました。

  1. サイトを取り込む: kuboyage.devの画面のスクショ、色、フォント、画像を集めます
  2. 決めごとを書く: 誰に何を伝える動画か、長さ、画面の比率を BRIEF.md に書きます
  3. 見た目の規則を決める: 色・文字・カードの形を frame.md にまとめます
  4. 絵コンテを書く: 場面ごとに、何秒に何が出るかを STORYBOARD.md に書きます
  5. 場面を作る: 5つの場面を別々のサブエージェントに1つずつ作らせます
  6. 音を付ける: BGMを生成し、効果音をコードで作って混ぜます
  7. 見直す: 場面ごとのスクショを並べて、崩れを直してから書き出します

参考にした講座の記事では、全体を「プロンプトは動画の1割で、残りの9割は仕組み」と表現していました。実際にやってみて、手間の大半は4〜7の準備と見直しに使っています。

手順1:サイトを取り込んで、絵コンテを書く 📝

「動画の素材は、サイトにある本物だけを使いました。」

HyperFramesには、URLからサイトの素材を集めるコマンドがあります。

npx hyperframes init videos/kuboyage-promo --non-interactive --example=blank
cd videos/kuboyage-promo
npx hyperframes capture "https://kuboyage.dev" -o ./capture --json

これで、スクロール位置ごとのスクショ、ページの文章、色の変数、フォント名、画像がまとまって手に入ります。Kuboyageの場合は、メインの青 #0284C5、丸ゴシックのM PLUS Rounded 1c、船のロゴ、各プロダクトのアイコンがここで揃いました。

見た目の規則は、HyperFramesに入っているデザインの型を1つ選び、サイトの色とフォントに置き換えて作ります。今回は落ち着いた型(blue-professional)を選びました。ただし、置き換えた結果の「補助の文字色」が淡い水色になってしまい、白地では読めない濃さでした。ここだけ手で濃いグレー #475569 に直しています。

絵コンテは、場面ごとに秒単位で書きます。たとえば1場面目はこうです。

## Frame 1 — あと少し、こうだったら
- duration: 3.5s
Scene 1 (0.0–1.6s): 左上にキャレットが出て「あと少し、」「こうだったら。」を1文字ずつ入力する
Scene 2 (1.6–2.5s): 画面の下のほうに、青い波線が左から右へ描かれる
Scene 3 (2.5–3.5s): 入力した文字が縮んで消え、同時に船が波から浮かび上がる

講座の記事が「ありがちな見た目」として挙げていたものは、絵コンテの冒頭で禁止しました。

  • グラデーションの背景の真ん中にタイトルを置く
  • すべての要素をフェードインで出す
  • 画面の四隅にラベルや枠線を置く

代わりに「2〜4秒ごとに新しいことが起きる」「動きはばねのように止まる」を全場面の約束にしています。

手順2:5つの場面を並列で作る 🧩

「1場面を1人に任せ、5人に同時に作ってもらいました。」

絵コンテができたら、場面ごとの指示書を自動で作ります。指示書には、その場面の絵コンテ、使う動きの型、見た目の規則だけが入ります。これをClaude Codeのサブエージェント5体に1つずつ渡し、同時に作らせました。

各サブエージェントが書くのは、自分の場面のHTMLファイル1つだけです。書き終えたら、仮のプロジェクトで検査コマンドを通し、途中の数コマをスクショにして自分で目で見てから返ってきます。5場面とも、指示してから2〜5分ほどで揃いました。

並列で作ると、場面どうしのつなぎ目がずれます。今回は2か所で起きました。

  • 波線の形: 1場面目の波は1周期340px、2場面目は480pxで描かれていました。切り替わった瞬間に波の形が変わって見えるので、2場面目を1場面目の式に合わせました
  • 四隅のロゴ: 2場面目の担当が、左上にロゴの小さなバッジを置いていました。「四隅に何も置かない」という約束に反するので外しました

場面をまたいで続く要素は、位置と形の数値を両方の担当に渡しておくべきでした。次に作るときは、絵コンテの段階で「波線は y=691px、振幅16px、周期340px」のように書いておきます。

手順3:BGMと効果音を付ける 🎵

「BGMはローカルで生成し、効果音はコードで作りました。」

BGMはACE-Step 1.5で作りました。Macのローカルで動くので、生成のたびの課金はありません。指示は英語で書き、テンポは120BPM、歌なしにしています。

{
  "prompt": "clean airy chill house for a stylish personal brand intro, soft felt piano chords, gentle marimba plucks, light four-on-the-floor kick ... instrumental, no vocals",
  "lyrics": "[Instrumental]",
  "audio_duration": 24,
  "bpm": 120
}

生成した曲をそのまま使うと、場面の切り替わりが拍からずれます。そこで、低音だけを取り出してキックの位置を測りました。テンポは狙いどおり120BPMで、最初のキックは曲の頭から0.12秒後にありました。曲の頭を0.1秒削って、キックが動画の0秒、0.5秒、1秒…にほぼ乗るようにしています。場面の長さを3.5秒・5.5秒のように0.5秒刻みにしたのは、この拍に合わせるためです。

効果音は、講座の記事にならって音源ファイルを探さずにコードで作りました。

const V = {
  tick:   [0.03, t => Math.sin(2 * Math.PI * 2600 * t) * Math.exp(-t * 160) * 0.18],
  pop:    [0.18, t => Math.sin(2 * Math.PI * (520 + 700 * t) * t) * Math.exp(-t * 24) * 0.32],
  thump:  [0.6,  t => Math.sin(2 * Math.PI * (95 - 55 * t) * t) * Math.exp(-t * 7) * 0.55],
  // whoosh(風切り音)は、決まった種から作るノイズを山なりに鳴らす
};

文字が1つ入力されるたびに tick、船が浮かぶときに pop、ロゴが決まるときに thump を鳴らします。鳴らす時刻は場面のHTMLに書いたアニメーションの時刻から拾いました。最後にBGMと効果音をffmpegで混ぜ、全体の音量は −16 LUFS にそろえています。

手順4:スクショで見直して直す 🔍

「書き出す前に、全場面を並べて厳しめに見ました。」

HyperFramesには、指定した時刻のスクショを撮って1枚に並べるコマンドがあります。

npx hyperframes lint
npx hyperframes check
npx hyperframes snapshot --at 0.9,1.9,2.9,4.3,6.8,10.9,14.4,19.4

check は、文字の重なり、はみ出し、色のコントラストを自動で調べます。ただ、自動の検査に通っても見た目が良いとは限りません。並べたスクショを見て、次の2つを直しました。

  • ブログの場面が小さい: カードが画面の上半分に寄っていて、下の3分の1が空いていました。記事タイトルも25pxで、スマホで再生すると読めない大きさです。見出しを1行にまとめ、カード3枚を画面幅いっぱいに横並びにして、タイトルを30pxに上げました
  • 補足の文字が小さい: 動画はスマホだと幅375px前後で再生されるので、1920pxの画面で36pxの文字は7px程度になります。2場面目の補足文を44pxに、締めの場面の文字を40pxに上げました

check は、傾けたカードの中の2行の文字を「重なっている」と判定しました。スクショで見ると重なっていなかったので、ここは直していません。自動の検査の結果は、最後にスクショと突き合わせて判断しています。

見直しが終わったら書き出しです。20秒の動画は約34秒で書き出され、ファイルは5.7MBでした。

npx hyperframes render --quality high --output renders/video.mp4

手順5:サイトに埋め込む 🌐

「自動再生はせず、最初のコマに締めの画面を出しています。」

サイトはReactで作っていて、Cloudflare Pagesで配信しています。動画は public/ に置き、<video> 要素で埋め込みました。

<video
  className="mx-auto aspect-video w-full max-w-4xl rounded-2xl"
  src="/home/intro-video-v1.mp4"
  poster="/home/intro-video-poster-v1.jpg"
  controls
  playsInline
  preload="metadata"
/>

埋め込むときに気を付けたのは3点です。

  • 再生までの待ち時間: ffmpegの -movflags +faststart で、再生に必要な情報をファイルの先頭に移しました。ダウンロードが終わる前に再生を始められます
  • ファイル名: このサイトの public/ のファイルは、1年間キャッシュされる設定で配信しています。同じ名前のまま中身を差し替えると、見に来た人には古い動画が出続けます。そのため名前に -v1 を付け、差し替えるときは名前を変えます
  • iPhoneでの再生: iPhoneのSafariは、動画を少しずつ取りに行く「Range」という要求を使います。これに部分的な応答(206)が返らないと再生できないので、公開後に curl -H 'Range: bytes=0-1' で206が返ることを確かめました

poster には動画の最後のコマ(ロゴとURL)を使いました。再生前でも、サイトの名前とURLが目に入ります。

まとめ 🎊

  • サイトの紹介動画は、HTMLで場面を書き、HyperFramesで動画にすれば作れる
  • 先に決めごと・見た目の規則・秒単位の絵コンテを文書にしてから作らせた
  • 5つの場面をサブエージェントに並列で作らせると早いが、場面のつなぎ目はずれる
  • BGMはローカル生成、効果音はコードで作り、拍に合わせて場面を切り替えた
  • 書き出す前にスクショを並べて見直し、スマホで読めない小ささを直した

「AIに動画を作ってもらう」と聞くと1回の指示で完成するように思えますが、実際に時間を使ったのは、決めごとを書くことと、出てきたものを見直すことでした。逆に言えば、その2つさえ用意すれば、動画編集ソフトを触らなくても自分のサイトに合った動画が作れます。

同じHyperFramesで、ほかのプロダクトの紹介動画も作りました。ペットのアルバムアプリPetdearと、朝の技術ニュースのResearch Deskです。それぞれのページで再生できます。ほかのプロダクトはプロダクト一覧にまとめているので、よければ覗いてみてください。

参考リンク 🔗

参考にした記事

使った道具

ブログの記事一覧へ