はじめに
動画コンテンツが増え続ける現代、「この動画には何が映っているのか?」「どのSNSに投稿すべきか?」を自動で判断できたら便利ですよね。
そこで、OpenAIの最新モデルGPT-5 nanoとGPT-4o mini Transcribeを活用した動画分析プラットフォームを開発しました。
🎬 GitHubリポジトリ: https://github.com/nobu-suzuki345/video-intelligence-platform
何ができるのか?
- 動画から「物」を見つける - 人物、物体、場所を自動検出
- 音声をテキスト化 - 高精度な書き起こし
- どのSNSに向いているか評価 - X/Twitter、YouTube、TikTok、Instagram、LinkedInの5つのSNSでスコアリング
- 動画をベクトル化 - 類似動画検索の基盤を構築
システム概要
アーキテクチャ
MP4動画
↓
① フレーム抽出(10秒ごと、最大6枚)← FFmpeg
↓
② 画像分析 → エンティティ検出 ← GPT-5 nano (Vision)
↓
③ 音声抽出 → 書き起こし ← GPT-4o mini Transcribe
↓
④ SNS適合度評価 ← GPT-5 nano
↓
⑤ ベクトル埋め込み ← text-embedding-3-small
↓
結果表示(Next.js UI)
技術スタック
| カテゴリ | 技術 |
|---|---|
| フレームワーク | Next.js 15 (App Router) |
| 言語 | TypeScript 5.6 |
| AI(音声) | GPT-4o mini Transcribe |
| AI(画像・評価) | GPT-5 nano |
| AI(ベクトル化) | text-embedding-3-small |
| 動画処理 | FFmpeg |
| UI | shadcn/ui + Tailwind CSS |
| ストレージ | ファイルベース(DB不要) |
実装した機能
1. 視覚エンティティ抽出(GPT-5 nano)
動画の各フレームから、人物・物体・場所を自動検出します。
実装のポイント:
export async function analyzeFrame(
framePath: string,
frameIndex: number,
timestamp: number
): Promise<VisualEntity[]> {
// 画像をBase64エンコード
const imageBuffer = fs.readFileSync(framePath);
const base64Image = imageBuffer.toString('base64');
const dataUrl = `data:image/jpeg;base64,${base64Image}`;
// GPT-5 nano で画像分析
const response = await openai.chat.completions.create({
model: 'gpt-5-nano',
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: 'この画像から見えるエンティティを抽出してJSON形式で返してください。',
},
{
type: 'image_url',
image_url: { url: dataUrl },
},
],
},
],
max_completion_tokens: 4000, // ⚠️ 重要!
});
return parseEntities(response);
}
ハマったポイント① - トークン制限
最初は max_completion_tokens: 1000 にしていたところ、すべて空のレスポンスが返ってきました。
finishReason: 'length' // トークン不足で途中終了
hasContent: false // 結果なし
解決策: 4000 に増やすことで安定動作
2. 音声書き起こし(GPT-4o mini Transcribe)
最新の音声認識モデルで高精度な書き起こしを実現。
export async function transcribeAudio(audioPath: string): Promise<Transcription> {
const audioFile = fs.createReadStream(audioPath);
const response = await openai.audio.transcriptions.create({
file: audioFile,
model: 'gpt-4o-mini-transcribe',
language: 'ja',
response_format: 'json', // ⚠️ verbose_jsonは非対応
});
return {
fullText: response.text,
segments: [{
text: response.text,
start: 0,
end: 0,
}],
language: 'ja',
duration: 0,
};
}
ハマったポイント② - response_format
gpt-4o-mini-transcribe は verbose_json をサポートしていませんでした。
Error: 400 response_format 'verbose_json' is not compatible with model 'gpt-4o-mini-transcribe-api-ev3'
解決策: json に変更
3. SNS適合度スコア生成(GPT-5 nano)
各SNSの特性に基づいて、投稿適合度を0-100点で評価します。
export async function generateSnsOptimization(input: OptimizationInput) {
const response = await openai.chat.completions.create({
model: 'gpt-5-nano',
messages: [{
role: 'user',
content: `動画長さ: ${Math.floor(input.duration)}秒
内容: ${input.transcription.fullText.substring(0, 100)}
各SNSの適合スコア(0-100)を評価してください。
評価基準:
- X/Twitter: 短尺動画適合、感情訴求、バズり可能性
- LinkedIn: ビジネス価値、プロフェッショナル度
- YouTube Shorts: エンタメ性
- TikTok: トレンド性、若年層向け
- Instagram Reels: ビジュアル美
JSONのみ返す(説明不要):
{"twitter_score":75,"linkedin_score":40,"youtube_score":80,"tiktok_score":85,"instagram_score":82}`,
}],
max_completion_tokens: 4000,
});
// スコアを抽出
const parsed = JSON.parse(content);
return {
twitter: parsed.twitter_score,
linkedin: parsed.linkedin_score,
youtube: parsed.youtube_score,
tiktok: parsed.tiktok_score,
instagram: parsed.instagram_score,
};
}
ハマったポイント③ - プロンプト設計
当初、複雑な指示を含めていましたが、GPT-5 nanoが空のレスポンスを返していました。
解決策:
- プロンプトを超シンプルに(約70%削減)
- 具体的なJSON例を示す
- 「説明不要」と明記
4. ベクトル埋め込み(text-embedding-3-small)
動画内容を1536次元のベクトルに変換し、次世代AI検索に対応します。
なぜベクトル化が重要なのか?
従来の検索(キーワードベース)の限界:
検索: "公園 女性"
結果: テキストに"公園"と"女性"が含まれる動画のみ
問題: 同じ意味でも別の言葉(例: "野外 少女")は検索できない
次世代AI検索(ベクトルベース)の威力:
検索: "公園 女性"
結果: 意味的に類似する動画すべて
→ "野外 少女"、"屋外 女の子"、"草原 人物" なども検出
AI検索時代の到来:
2024年以降、検索システムは急速に進化しています:
- Google AI Overviews: 検索結果にAI生成の要約を表示
- ChatGPT Search: 対話形式でWeb検索
- Perplexity AI: ソース付きAI検索エンジン
- Claude AI: 文脈理解に基づく検索
これらすべてがベクトル検索を基盤としています。
つまり、動画コンテンツを「検索可能」にするには、ベクトル化が必須です。
export async function generateVideoEmbedding(
result: VideoAnalysisResult
): Promise<VideoEmbedding> {
// 動画の内容を要約テキストに変換
const textSummary = buildVideoSummary(result);
// ベクトル化
const response = await openai.embeddings.create({
model: 'text-embedding-3-small',
input: textSummary,
encoding_format: 'float',
});
return {
model: 'text-embedding-3-small',
dimensions: 1536,
vector: response.data[0].embedding,
textSummary,
generatedAt: new Date().toISOString(),
};
}
// コサイン類似度で類似動画を検索
export function calculateCosineSimilarity(
vector1: number[],
vector2: number[]
): number {
let dotProduct = 0;
let norm1 = 0;
let norm2 = 0;
for (let i = 0; i < vector1.length; i++) {
dotProduct += vector1[i] * vector2[i];
norm1 += vector1[i] * vector1[i];
norm2 += vector2[i] * vector2[i];
}
return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
}
活用例:
// 2つの動画の類似度を計算
const similarity = calculateCosineSimilarity(
video1.embedding.vector,
video2.embedding.vector
);
console.log(`類似度: ${(similarity * 100).toFixed(1)}%`);
// 出力: 類似度: 87.5%
直列処理で安定性を実現
当初は並列処理(20フレーム同時分析)を試みましたが、成功率が5%程度でした。
問題:
- APIレート制限
- GPT-5 nanoの不安定な動作
解決策:
export async function analyzeFrames(
framePaths: Array<{ path: string; index: number; timestamp: number }>,
sequential: boolean = true
): Promise<VisualEntity[]> {
console.log(`[Vision] Analyzing ${framePaths.length} frames (${sequential ? '直列処理' : '並列処理'})...`);
let results: VisualEntity[][];
if (sequential) {
// 直列処理: 1つずつ順番に実行(安定)
results = [];
for (const frame of framePaths) {
const entities = await analyzeFrame(frame.path, frame.index, frame.timestamp);
results.push(entities);
// レート制限を避けるため、少し待つ
if (entities.length > 0) {
await new Promise(resolve => setTimeout(resolve, 500));
}
}
} else {
// 並列処理(高速だが不安定)
results = await Promise.all(
framePaths.map((frame) => analyzeFrame(frame.path, frame.index, frame.timestamp))
);
}
return results.flat();
}
設定:
frameExtraction: {
fps: 1 / 10, // 10秒ごとに1フレーム
maxFrames: 6, // 最大6フレーム(1分動画対応)
sequential: true, // 直列処理
}
結果: 成功率が5% → 80-100%に改善!
GPT-5 nanoの特性と対策
開発を通じて分かった、GPT-5 nanoの特性をまとめます。
わかったこと
| 機能 | 対応状況 | 備考 |
|---|---|---|
| Vision(画像分析) | ✅ 対応 | max_completion_tokens: 4000必須 |
| 短いJSON生成 | ✅ 対応 | スコア5個程度ならOK |
| 長文テキスト生成 | ❌ 不安定 | 空のレスポンスが返る |
| temperature | ❌ 非対応 | デフォルト値(1)のみ |
| response_format | ❌ 不安定 | json_objectで空になる |
対策
// ❌ これはダメ
{
model: 'gpt-5-nano',
max_completion_tokens: 1000, // 少なすぎる
temperature: 0.3, // エラー
response_format: { type: 'json_object' }, // 空になる
}
// ✅ これならOK
{
model: 'gpt-5-nano',
max_completion_tokens: 4000, // 十分な値
// temperature は指定しない
// response_format は指定しない
}
実際の分析結果
テスト動画(サンプル)
20 Seconds Under the Stars.mp4
- 長さ: 20秒
- 内容: 女性がバルコニーで夜空を眺める静かなシーン
- ファイルサイズ: 3.2MB
分析結果
上記のスクリーンショットの通り、以下の情報が抽出されました:
✅ 視覚エンティティ(10個検出)
- 少女(PERSON, 信頼度92%)
- ブーツ(OBJECT, 信頼度95%)
- マフラー(OBJECT, 信頼度93%)
- 草地の野原(LOCATION, 信頼度92%)
- セーター(OBJECT, 信頼度90%)
- 帽子(OBJECT, 信頼度88%)
- 女性(PERSON, 信頼度87%)
- スカート(OBJECT, 信頼度85%)
...
✅ 音声書き起こし
「ふと立ち止まったこの瞬間も、遠くのようにあっという間に過ぎていく、
たった20秒の一瞬の物語、結末は自分で描き出すんだ。」
✅ SNS適合度スコア
TikTok: 88点(おすすめ)
Instagram: 85点(おすすめ)
YouTube: 82点(標準)
X/Twitter: 78点(標準)
LinkedIn: 42点(参考)
✅ ベクトル埋め込み
{
"model": "text-embedding-3-small",
"dimensions": 1536,
"vector": [-0.0123, 0.0234, -0.0087, ...],
"stats": {
"min": -0.0234,
"max": 0.0456,
"mean": 0.0012,
"std": 0.0089
}
}
処理時間: 約35秒
コスト: 約¥4円
コスト試算
1分動画の場合
| 処理 | モデル | コスト |
|---|---|---|
| 音声書き起こし | gpt-4o-mini-transcribe | ~¥0.5 |
| フレーム分析(6枚) | gpt-5-nano | ~¥2.0 |
| SNS適合度評価 | gpt-5-nano | ~¥0.5 |
| ベクトル埋め込み | text-embedding-3-small | ~¥0.003 |
| 合計 | 約¥3-5円 |
従来のWhisper + GPT-4oと比較して約50%コスト削減!
UI/UX
メイン画面
shadcn/uiを使用したモダンなインターフェース:
アップロード画面
- 進捗表示をリアルタイムで表示
- 対応形式: MP4(最大50MB、1分まで)
SNS適合度スコア表示
5つのSNS(TikTok、Instagram、YouTube、X/Twitter、LinkedIn)のスコアが一目で確認できます。
おすすめ度に応じて色分けされており、どのSNSに投稿すべきか即座に判断できます。
エンティティ一覧
検出された視覚エンティティが信頼度とともに一覧表示されます。
人物(PERSON)、物体(OBJECT)、場所(LOCATION)がそれぞれ分類されます。

4つのタブで結果を整理:
- エンティティ: 視覚エンティティの詳細
- 書き起こし: 音声テキスト全文
- ベクトル: 1536次元の埋め込みベクトル情報
- 詳細: メタデータと処理情報
苦労した点と解決策
1. GPT-5 nanoのトークン制限
問題: すべてのフレームで空のレスポンス
finishReason: 'length'
hasContent: false
原因: max_completion_tokens: 1000 では不足
解決: 4000 に増やす → 成功率100%に
デバッグログで特定:
console.log('[Vision Debug] Frame 0 - Full Response:', {
hasChoices: !!response.choices,
hasContent: !!response.choices?.[0]?.message?.content,
finishReason: response.choices?.[0]?.finish_reason,
});
// 出力:
// finishReason: 'length' ← これで原因特定!
2. 並列処理の不安定性
問題: 20フレーム並列処理で1フレームしか成功しない(成功率5%)
原因:
- APIレート制限
- 同時リクエストが多すぎる
解決: 直列処理 + 0.5秒待機
for (const frame of framePaths) {
const entities = await analyzeFrame(frame.path, frame.index, frame.timestamp);
results.push(entities);
// レート制限を避けるため待機
if (entities.length > 0) {
await new Promise(resolve => setTimeout(resolve, 500));
}
}
結果: 成功率が5% → 80-100%に改善!
3. FFmpegの音声抽出エラー
一部の動画で音声抽出に失敗する問題が発生。
エラー内容:
Error: ffmpeg exited with code 4294967274:
Error opening output file audio.wav
解決: エラーハンドリングで継続
try {
await extractAudio(videoPath, audioPath);
transcription = await transcribeAudio(audioPath);
} catch (audioError) {
console.warn('Audio extraction failed, continuing without audio');
transcription = {
fullText: '(音声なし、または音声抽出に失敗しました)',
segments: [],
language: 'ja',
duration: 0,
};
}
音声なし動画でもエンティティ抽出は継続されます。
4. response_formatの罠
問題: response_format: { type: 'json_object' } を指定すると空のレスポンス
// ❌ これは動かない
const response = await openai.chat.completions.create({
model: 'gpt-5-nano',
response_format: { type: 'json_object' },
});
// 結果: hasContent: false
解決: response_formatを削除 + 手動でJSONを抽出
const content = response.choices[0]?.message?.content;
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (jsonMatch) {
const result = JSON.parse(jsonMatch[0]);
}
データベース不要のシンプル実装
ファイルベースストレージで実装しています。
// ストレージ構造
uploads/
/{videoId}/
├── video.mp4 // 元動画
├── audio.wav // 抽出音声
├── frames/ // 抽出フレーム
│ ├── frame_0001.jpg
│ └── frame_0002.jpg
├── metadata.json // メタデータ
└── analysis.json // 分析結果
メリット:
- ✅ セットアップが超簡単(
npm install && npm run dev) - ✅ データベース設定不要
- ✅ デプロイが楽
- ✅ 読者が試しやすい
デメリット:
- 大量の動画には不向き(100動画程度まで推奨)
制限事項
開発中に判明したGPT-5 nanoの制約:
1. temperature パラメータが使えない
// ❌ エラーになる
{
model: 'gpt-5-nano',
temperature: 0.3,
}
// Error: 'temperature' does not support 0.3 with this model.
// Only the default (1) value is supported.
// ✅ temperatureを指定しない
{
model: 'gpt-5-nano',
// temperature は省略(デフォルト1が使われる)
}
2. max_tokens ではなく max_completion_tokens
// ❌ エラーになる
max_tokens: 1000
// ✅ 正しい
max_completion_tokens: 4000
3. 長文生成は不安定
- スコア5個: ✅ OK
- 投稿文案3個 + スコア5個: ❌ 空のレスポンス
対策: 出力を最小限に絞る
パフォーマンス最適化
フレーム数の最適化
| 設定 | フレーム数 | 処理時間 | 成功率 | コスト |
|---|---|---|---|---|
| 3秒ごと・並列 | 20枚 | 10秒 | 5% | ¥10 |
| 5秒ごと・並列 | 12枚 | 8秒 | 20% | ¥6 |
| 10秒ごと・直列 | 6枚 | 35秒 | 95% | ¥3 |
結論: 10秒ごと・直列処理が最もバランスが良い
実装の工夫
1. エラーハンドリング
// 品質評価が失敗してもシステムは継続
let evaluation;
try {
evaluation = await evaluateVideo({ ... });
} catch (evalError) {
console.warn('Evaluation failed, skipping:', evalError);
evaluation = null;
}
// 結果はnullでも処理続行
const result: VideoAnalysisResult = {
metadata,
transcription,
visualEntities,
qualityScore: evaluation?.qualityScore || { overall: 0, ... },
improvements: evaluation?.improvements || [],
// ...
};
2. 信頼度フィルタリング
// 信頼度が低いエンティティを除外
const visualEntities = entities
.filter((entity: any) => (entity.confidence || 0) >= 0.7)
.map((entity: any) => ({
type: normalizeEntityType(entity.type),
name: entity.name,
confidence: entity.confidence,
}));
3. プログレス表示
this.reportProgress({
stage: 'analyzing_frames',
progress: 55,
message: 'フレームを分析中...',
});
// フロントエンドでリアルタイム表示
<Progress value={progress.progress} />
実際の使い方
セットアップ
# リポジトリをクローン
git clone https://github.com/nobu-suzuki345/video-intelligence-platform.git
cd video-intelligence-platform
# 依存関係をインストール
npm install
# 環境変数を設定
cp .env.local.example .env.local
# .env.local に OpenAI API Key を設定
# FFmpegをインストール(Windows)
winget install --id=Gyan.FFmpeg -e
# 開発サーバー起動
npm run dev
動画をアップロード
- http://localhost:3000 を開く
- MP4動画を選択(最大50MB、1分以内)
- 「分析を開始」をクリック
- 約30-40秒待つ
- 結果を確認!
なぜ今、ベクトル検索なのか?
AI検索時代のパラダイムシフト
2025年、検索は「キーワード」から「意味理解」へ進化しています。
従来の検索(キーワードマッチング)
ユーザー: "公園の動画を探したい"
システム: テキストに「公園」という文字列を検索
結果: タイトルやメタデータに"公園"がある動画のみ
問題点:
- ❌ 同義語に対応できない("公園" ≠ "野外")
- ❌ 動画の内容を理解していない
- ❌ 視覚的な要素を検索できない
次世代AI検索(セマンティック検索)
ユーザー: "公園の動画を探したい"
システム: 意味的に類似するベクトルを検索
結果: 公園、野外、草原、ピクニック、自然の中... など
視覚的に似た動画も含む
メリット:
- ✅ 意味で検索できる
- ✅ 同義語・類義語に自動対応
- ✅ 動画の実際の内容で検索
AI検索システムの普及
現在、主要なプラットフォームでベクトル検索が標準化されつつあります:
| システム | ベクトル検索 | 用途 |
|---|---|---|
| ChatGPT | ✅ 対応 | RAG、ファイル検索 |
| Google AI Overviews | ✅ 対応 | 検索結果の要約 |
| Perplexity AI | ✅ 対応 | AI検索エンジン |
| Claude AI | ✅ 対応 | コンテキスト理解 |
| YouTube | 🔄 導入中 | 動画推薦システム |
つまり、動画をベクトル化しておくことで、これら次世代AI検索システムに対応できます。
本プラットフォームのベクトル化の価値
1. 将来的な類似動画検索の基盤
// すでに実装済みの類似度計算
const similarity = calculateCosineSimilarity(
video1.embedding.vector,
video2.embedding.vector
);
// 将来的にベクトルDBと統合
// → Pinecone、Qdrant、pgvectorなどで高速検索
2. AI検索エンジンへの対応
動画がベクトル化されていれば:
- ChatGPT検索で「公園の動画」と検索 → 自動でヒット
- Perplexityで「感動的な短編動画」と検索 → 意味的にマッチ
- Google AI Overviewsで要約表示 → 関連動画として表示
3. レコメンデーションシステム
// 視聴した動画に基づいて似た動画を推薦
const recommendations = recommendSimilarVideos(
watchedVideo,
allVideos,
5 // トップ5を推薦
);
このプロジェクトの先進性
多くの動画プラットフォームはまだキーワード検索に依存しています。
しかし、本プラットフォームは:
- ✅ 動画を自動でベクトル化
- ✅ 意味的な検索に対応
- ✅ AI検索時代に備えた設計
今からベクトル化しておくことで、将来的なAI検索の波に乗れます。
制限事項と今後の課題
現在の制限
- 対応形式: MP4のみ
- ファイルサイズ: 最大50MB
- 動画の長さ: 最大1分
- 音声エンティティ: 現在未対応(GPT-5 nanoの制約)
今後の展望(ベクトル検索を軸に)
- 類似動画検索機能: ベクトルDBを活用したセマンティック検索
- AI検索エンジン統合: ChatGPT Search、Perplexityへの対応
- ベクトルDB対応: Pinecone、Qdrant、pgvectorとの統合
- バッチ処理: 大量動画のベクトル化
- ハイブリッド検索: キーワード + ベクトルの組み合わせ
まとめ
OpenAIの最新モデル(GPT-4o mini Transcribe、GPT-5 nano)を使って、動画分析プラットフォームを構築しました。
開発のポイント
- ✅ GPT-5 nanoは
max_completion_tokens: 4000で安定動作 - ✅ 直列処理で成功率を大幅改善(5% → 95%)
- ✅ シンプルなプロンプトが重要
- ✅ ファイルベースでDB不要(セットアップ簡単)
- ✅ エラーハンドリングで堅牢性を確保
パフォーマンス
- コスト: 1分動画あたり約¥3-5円
- 処理時間: 約30-40秒
- 成功率: 80-100%(直列処理)
技術的な学び
-
GPT-5 nanoの制約を理解
- temperatureは使えない
- response_formatは不安定
- max_completion_tokensは大きめに
-
直列処理の重要性
- 並列処理は高速だが不安定
- 0.5秒の待機でレート制限を回避
-
プロンプト設計
- シンプルなプロンプトほど成功率が高い
- 具体的なJSON例を示す
GitHubリポジトリ & デモ
ソースコード
GitHub: https://github.com/nobu-suzuki345/video-intelligence-platform
ぜひスターをお願いします!⭐
デモ動画 & サンプル
実際の動作を見る:
https://snamo.jp/resources/
- 📹 デモ動画: システム全体の動作フロー(20秒)
- 📸 スクリーンショット: 詳細な画面キャプチャ
フィードバックやコントリビューション歓迎です。
参考リンク
おわりに
動画分析とSNS最適化を組み合わせた、実用的なプラットフォームを開発できました。
特にGPT-5 nanoの制約を理解し、それに適したアーキテクチャ設計をすることで、低コスト・高精度なシステムを実現できました。
ぜひ試してみてください! 🚀
質問やフィードバックがあれば、GitHubのIssueまたは私のWebsite(https://snamo.jp/)からお気軽にどうぞ!


