1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude CodeでRust製暗号資産バックテスターのLPとYouTube動画をフルAI生成してみた

1
Posted at

はじめに

crypto-rs-backtesterというOSSを開発しています。Rustコアとpython APIを持つ、ナノ秒精度の決定論的な暗号資産バックテストエンジンです。

このツールを広めるために、以下の2つをAI(Claude Code)だけで作成しました。

  • ランディングページ(LP):

  • YouTube紹介動画: OpenAI TTS音声ナレーション付き、2分16秒

本記事では「どんな指示をAIに出したか」「何が起きたか」「どう解決したか」を技術的に振り返ります。


使用技術スタック

カテゴリ 技術
AIアシスタント Claude Code(Sonnet 4.6)
LP HTML / CSS / Vanilla JS
ホスティング Firebase Hosting
動画フレームワーク Remotion v4
動画言語 TypeScript / React
TTS OpenAI TTS API(tts-1-hd
動画後処理 ffmpeg
ブラウザレンダリング Google Chrome(Headless)

Part 1: ランディングページの作成

AIへの指示

LPの依頼は以下の2点でした:

crypto-rs-backtesterのランディングページをHTMLで作ってください。
わかりやすく、良さが伝わる内容にすること。
デザインはstripe.comをベースにしてください。

Stripeをベースに指定した理由は、Stripeのデザインが「技術系プロダクトのLPとして世界的に高い評価を受けており、以下の特徴を持つ」からです:

  • 情報密度と余白のバランスが絶妙で、コードを扱う開発者向けに読みやすい
  • グラデーション・グロー・グリッド背景を使いながらも品があり、技術的信頼感を演出
  • ヒーローセクションのメトリクス表示(数値で価値を端的に伝える手法)が参考になる
  • コードスニペットをUIの一部として見せるデザインパターンが crypto-rs-backtester に最適

Claude Codeは「Stripeライク」という指示を以下の具体的な実装に落とし込みました:

Stripeの要素 実装された内容
ダークネイビー背景 + グロー #0a0e1a ベース + radial-gradient アクセント
ヒーローの数値バッジ 7.45M ticks/sec / i64 / 3-way timeline の3指標
グリッドライン背景 linear-gradient で1pxグリッドを重ねた装飾
サンセリフ大見出し システムフォントスタック + 太字 + タイトなレタースペース
コードウィンドウ macOS風ドットバー付きのターミナルカード
2カラムHero 左コピー + 右プレビューの分割レイアウト

生成されたLPの構成

Claude CodeはGitHubのREADMEを読み、以下のセクションを自動設計しました:

hero        ← キャッチコピー + メトリクス + コードプレビュー
problem     ← 典型的なバックテスターの3つの欠陥
solution    ← Before/After比較
features    ← 4機能カード
architecture← Python→PyO3→Rustのフロー図
quickstart  ← インストールコマンド + コードタブ
performance ← Criterionベンチマーク数値
roadmap     ← 開発ステータス
CTA         ← pip install / GitHub / Colab

Claude Codeが自律的に行ったこと

  • docs/SPEC.mddocs/PLAN.md を読み込んで技術仕様を把握
  • ベンチマーク結果ファイル(benchmark/auto_tuning_review_fix_20260130.txt)から数値を抽出
  • OGP画像・favicon・robots.txt404.html を自動生成
  • Prism.jsによるコードハイライトの実装
  • Firebase Hostingの設定(firebase.json

こだわりのUIポイント

コードサンプルはタブ切り替え式(Tick / Batch / Arrow)になっており、JavaScriptで実装されています:

tabs.forEach((tab) => {
  tab.addEventListener('click', () => {
    const target = tab.dataset.codeTab;
    tabs.forEach((item) => {
      const isActive = item === tab;
      item.classList.toggle('active', isActive);
      item.setAttribute('aria-selected', String(isActive));
    });
    panels.forEach((panel) => {
      panel.classList.toggle('active', panel.dataset.codePanel === target);
    });
  });
});

アクセシビリティ(aria-selectedaria-expanded)も自動で実装されました。


Part 2: YouTube動画の作成(Remotion)

AIへの指示

RemotionでこのリポジトリのLPで説明した内容を動画にしてください。
わかりやすく、crypto-rs-backtesterの良さが理解できるような内容にすること。

これだけで、Remotionプロジェクトのフルセットアップから8シーンの動画実装まで進めてくれました。

Remotionとは

Remotion はReact + TypeScriptで動画を「コードとして書く」フレームワークです。

// こんな感じでシーンをReactコンポーネントとして書く
const SceneIntro: React.FC = () => {
  const frame = useCurrentFrame(); // 現在フレーム番号
  const { fps } = useVideoConfig();

  const titleProgress = spring({ frame, fps, config: { damping: 18 } });

  return (
    <AbsoluteFill style={{ background: "#0a0e1a" }}>
      <h1 style={{ opacity: titleProgress }}>
        crypto-rs-backtester
      </h1>
    </AbsoluteFill>
  );
};

spring() アニメーション、interpolate() による値補間がネイティブサポートされており、Adobe After EffectsやFinal Cut不要で滑らかなアニメーションが作れます。

プロジェクト構成

video/
├── src/
│   ├── index.ts              ← registerRoot() エントリーポイント
│   ├── Root.tsx              ← Compositionの定義
│   ├── CryptoBacktesterVideo.tsx  ← メイン動画(Sequenceで8シーン結合)
│   ├── Transition.tsx        ← フェードトランジション
│   ├── colors.ts             ← カラーパレット定数
│   └── scenes/
│       ├── SceneIntro.tsx
│       ├── SceneProblem.tsx
│       ├── SceneSolution.tsx
│       ├── SceneFeatures.tsx
│       ├── SceneArchitecture.tsx
│       ├── SceneCode.tsx
│       ├── ScenePerformance.tsx
│       ├── SceneCTA.tsx
│       └── SceneThumbnail.tsx  ← YouTube用サムネイル
├── public/
│   └── audio/               ← OpenAI TTS生成MP3
├── scripts/
│   └── generate-tts.mjs     ← TTS生成スクリプト
└── out/
    ├── crypto-rs-backtester-youtube.mp4
    └── thumbnail.png

シーン設計(8シーン、計2分16秒)

# シーン 内容
1 Intro タイトルアニメ + 3メトリクス 12.6s
2 Problem 3つの欠陥(先読み/float/レイテンシ) 16.1s
3 Solution Before/After コード比較 16.4s
4 Features 4機能カード(2×2グリッド) 20.9s
5 Architecture Python→PyO3→Rustフロー図 18.0s
6 Code タイプライター式コードデモ 13.9s
7 Performance ベンチマーク棒グラフ 19.8s
8 CTA pipインストール + リンク 14.6s

トラブルシューティング:useCurrentFrame()の二重減算バグ

動画作成中に最も重要なバグがありました。

問題

当初、各シーンコンポーネントに startFrame プロップを渡し、内部で引き算していました:

// ❌ バグのあるコード
export const SceneProblem: React.FC<{ startFrame: number }> = ({ startFrame }) => {
  const frame = useCurrentFrame(); // Remotionが自動でSequence相対フレームを返す
  const localFrame = frame - startFrame; // ← 二重減算!
  // ...
};

そして親コンポーネントで:

<Sequence from={110} durationInFrames={170}>
  <SceneProblem startFrame={110} /> {/* startFrame=110を渡す */}
</Sequence>

Remotionの useCurrentFrame() は、<Sequence> 内では自動的に0からカウントし直します。

つまり <Sequence from={110}> の中でフレーム160を再生すると、useCurrentFrame()160 - 110 = 50 を返します。そこからさらに startFrame(110) を引くと -60 になり、アニメーションが計算不能になります。

修正

// ✅ 修正後
export const SceneProblem: React.FC = () => {
  const frame = useCurrentFrame(); // そのまま使えば良い(0からカウント)
  const localFrame = frame; // ← startFrame不要
  // ...
};

Remotionの <Sequence> コンポーネントは useCurrentFrame() のコンテキストを自動的にリセットしてくれるので、各シーンコンポーネントは「自分が0フレームから始まる」として書くだけでよいのです。


Part 3: OpenAI TTSによるナレーション

AIへの指示

OpenAI TTSで作成してください

これだけで以下を自動生成:

  1. 8シーン分のナレーション原稿
  2. OpenAI API呼び出しスクリプト
  3. RemotionへのAudioコンポーネント統合
  4. シーンの尺を音声長に合わせた再調整

TTS生成スクリプト

// scripts/generate-tts.mjs
const response = await fetch("https://api.openai.com/v1/audio/speech", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "tts-1-hd",   // 高品質モデル
    input: narration.text,
    voice: "onyx",       // 深みのある技術系ボイス
    speed: 0.95,         // 少しゆっくり目
    response_format: "mp3",
  }),
});

tts-1-hd + onyx の組み合わせは、技術系プレゼン動画に非常にマッチする深みのある男性ボイスです。

音声をRemotionに組み込む

import { Audio, staticFile, Sequence } from "remotion";

<Sequence from={intro.from} durationInFrames={intro.duration}>
  {/* public/audio/01-intro.mp3 を再生 */}
  <Audio src={staticFile("audio/01-intro.mp3")} />
  <SceneIntro />
</Sequence>

staticFile()public/ ディレクトリからファイルを参照するRemotionのユーティリティです。

シーン尺を音声長に合わせる

音声の正確な長さは ffprobe で計測:

for f in public/audio/*.mp3; do
  dur=$(ffprobe -i "$f" -show_entries format=duration -v quiet -of csv="p=0")
  frames=$(echo "$dur * 30" | bc | cut -d. -f1)
  echo "$(basename $f): ${dur}s → ${frames} frames"
done

結果:

01-intro.mp3:        12.624s → 378 frames
02-problem.mp3:      16.080s → 482 frames
03-solution.mp3:     16.440s → 493 frames
04-features.mp3:     20.856s → 625 frames
05-architecture.mp3: 18.000s → 540 frames
06-code.mp3:         13.944s → 418 frames
07-performance.mp3:  19.776s → 593 frames
08-cta.mp3:          14.568s → 437 frames

各シーンの durationInFrames音声フレーム数 + バッファ(15) + オーバーラップ(15) として設定しました。


Part 4: YouTube向け最適化エクスポート

Remotionレンダリング

npx remotion render src/index.ts CryptoBacktester out/crypto-rs-backtester.mp4 \
  --codec h264 \
  --browser-executable="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"

問題:yuvj420p(full range)

Remotionはデフォルトで yuvj420p(full color range)でレンダリングします。YouTubeはBT.709 limited rangeを期待しており、そのままアップロードすると色が変わる場合があります。

Remotionの --pixel-format フラグでは解決しないため、ffmpegで後処理します:

ffmpeg -y \
  -i out/crypto-rs-backtester.mp4 \
  -c:v libx264 \
  -crf 12 \             # 高品質(低いほど高品質)
  -preset slow \
  -pix_fmt yuv420p \
  -vf "scale=1920:1080,format=yuv420p" \
  -color_range tv \     # limited range
  -colorspace bt709 \   # BT.709カラースペース
  -color_primaries bt709 \
  -color_trc bt709 \
  -c:a aac \
  -b:a 320k \
  -movflags +faststart \ # moovアトムを先頭に(ストリーミング最適化)
  out/crypto-rs-backtester-youtube.mp4
設定 意味
-crf 12 高品質エンコード(YouTube再エンコード前提)
yuv420p 標準カラーレンジ
-colorspace bt709 YouTube標準カラースペース
-movflags +faststart ファイル先頭にメタデータ配置(再生開始高速化)

サムネイル生成

Remotionはスチル画像のレンダリングもサポートしています:

npx remotion still src/index.ts Thumbnail out/thumbnail.png \
  --browser-executable="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
  --frame 0

Thumbnail コンポジションを 1280×720 × durationInFrames=1 で定義し、YouTube推奨サイズのPNGを1コマだけレンダリングします。


AIへの指示のコツ

今回の作業を通じて感じた、Claude Codeへの効果的な指示方法をまとめます。

1. 目的だけ伝えて細部はAIに任せる

# ❌ 細かすぎる指示
「Remotionを使って、1920x1080、30fps、8シーンの動画を作成してください。
シーン1はイントロで120フレームで...」

# ✅ 目的ベースの指示
「RemotionでこのLPの内容を動画にしてください。
わかりやすく、良さが伝わる内容にすること。」

コンテキスト(LPのHTML)が十分にあれば、AIが内容・構成・デザインを自律的に判断します。

2. 参照デザインを一言で指定する

# デザインの方向性を有名サービスで伝える
「デザインはstripe.comをベースにしてください」

「Stripeライク」と伝えるだけで、AIは配色・レイアウト・タイポグラフィ・アニメーションのすべてをその文脈で判断します。「ネイビー系ダークテーマ」「余白多め」「グリッドライン背景」「コードブロックをUIとして見せる」といった設計判断が全自動で行われました。

他にも使えるリファレンス例:

指示 想定される仕上がり
stripe.comをベースに 洗練されたSaaS・開発者向けLP
linear.appをベースに シンプルモダン、ダークUIツール系
vercel.comをベースに デベロッパーツール、ミニマル+インパクト
apple.comをベースに 製品中心、大胆なビジュアル訴求

3. 追加指示は一言でOK

「OpenAI TTSで作成してください」
「YouTube用のビデオにエクスポートできる?」
「サーバーをストップ」

AIは前の会話文脈を保持しているので、短い指示でも意図が伝わります。

4. バグはAIが自己発見・自己修正する

useCurrentFrame() の二重減算バグは、私が指摘したのではなく、AIが自分でスクリーンショットを撮って画面が黒いことに気づき、コードを読み返して原因を特定し、修正しました。

Playwright MCPツールを使ってブラウザ操作→スクリーンショット→解析→修正のループを自律的に回しています。


最終成果物

video/out/
├── crypto-rs-backtester-youtube.mp4  # YouTube用動画 (12MB, 2:16)
├── thumbnail.png                      # サムネイル 1280×720
└── youtube-description.md            # タイトル・説明文・タグ
項目 仕様
解像度 1920×1080 (1080p)
フレームレート 30fps
動画コーデック H.264 High Profile
カラースペース BT.709 (limited range)
音声 AAC 320kbps / 48kHz ステレオ
ナレーター OpenAI TTS tts-1-hd / onyx
総時間 2分16秒(4101フレーム)
ファイルサイズ 12MB

まとめ

作業 所要時間(人間) AIなし想定
LP作成 30分 3〜5日
動画フレーム設計・実装 1時間 1〜2週間
TTS生成・統合 10分 半日
YouTube最適化エクスポート 5分 2〜3時間

Claude Codeは「何を作りたいか」を伝えるだけで、技術選定・実装・デバッグ・最適化まで自律的に進めてくれます。特に以下の点が印象的でした:

  • コンテキスト理解: LPのHTMLを読んで動画の構成を自動設計
  • 自律デバッグ: ブラウザを操作してビジュアル確認→バグ発見→修正
  • 技術的判断: YouTubeのBT.709要件を知っていてffmpegで適切に処理

今後は多言語対応のTTS(日本語版ナレーション)や、A/Bテスト用の複数バリアントの自動生成なども試してみたいと思います。


参考リンク

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?