はじめに
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.mdやdocs/PLAN.mdを読み込んで技術仕様を把握 - ベンチマーク結果ファイル(
benchmark/auto_tuning_review_fix_20260130.txt)から数値を抽出 - OGP画像・favicon・
robots.txt・404.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-selected、aria-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で作成してください
これだけで以下を自動生成:
- 8シーン分のナレーション原稿
- OpenAI API呼び出しスクリプト
- RemotionへのAudioコンポーネント統合
- シーンの尺を音声長に合わせた再調整
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テスト用の複数バリアントの自動生成なども試してみたいと思います。