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?

More than 1 year has passed since last update.

【Next.js 15 × GPT-5 nano】動画から自動でエンティティ抽出&SNS最適化する次世代プラットフォームを作った

1
Last updated at Posted at 2025-10-22

はじめに

動画コンテンツが増え続ける現代、「この動画には何が映っているのか?」「どのSNSに投稿すべきか?」を自動で判断できたら便利ですよね。

そこで、OpenAIの最新モデルGPT-5 nanoとGPT-4o mini Transcribeを活用した動画分析プラットフォームを開発しました。

🎬 GitHubリポジトリ: https://github.com/nobu-suzuki345/video-intelligence-platform

何ができるのか?

  1. 動画から「物」を見つける - 人物、物体、場所を自動検出
  2. 音声をテキスト化 - 高精度な書き起こし
  3. どのSNSに向いているか評価 - X/Twitter、YouTube、TikTok、Instagram、LinkedInの5つのSNSでスコアリング
  4. 動画をベクトル化 - 類似動画検索の基盤を構築

システム概要

アーキテクチャ

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)

動画の各フレームから、人物・物体・場所を自動検出します。

実装のポイント:

lib/ai/vision.ts
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)

最新の音声認識モデルで高精度な書き起こしを実現。

lib/ai/transcribe.ts
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点で評価します。

lib/ai/sns-optimization.ts
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: 文脈理解に基づく検索

これらすべてがベクトル検索を基盤としています。

つまり、動画コンテンツを「検索可能」にするには、ベクトル化が必須です。

lib/ai/embeddings.ts
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の不安定な動作

解決策:

lib/ai/vision.ts
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();
}

設定:

lib/ai/config.ts
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を使用したモダンなインターフェース:

main.png

アップロード画面

  • 進捗表示をリアルタイムで表示
  • 対応形式: MP4(最大50MB、1分まで)

1Video Intelligence Platform Analysis Results.png

SNS適合度スコア表示

5つのSNS(TikTok、Instagram、YouTube、X/Twitter、LinkedIn)のスコアが一目で確認できます。
おすすめ度に応じて色分けされており、どのSNSに投稿すべきか即座に判断できます。

2Video AI Analysis Process.png

エンティティ一覧

検出された視覚エンティティが信頼度とともに一覧表示されます。
人物(PERSON)、物体(OBJECT)、場所(LOCATION)がそれぞれ分類されます。
3SNS Score and Entity Extraction.png

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]);
}

データベース不要のシンプル実装

ファイルベースストレージで実装しています。

lib/storage/file-storage.ts
// ストレージ構造
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

動画をアップロード

  1. http://localhost:3000 を開く
  2. MP4動画を選択(最大50MB、1分以内)
  3. 「分析を開始」をクリック
  4. 約30-40秒待つ
  5. 結果を確認!

なぜ今、ベクトル検索なのか?

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%(直列処理)

技術的な学び

  1. GPT-5 nanoの制約を理解

    • temperatureは使えない
    • response_formatは不安定
    • max_completion_tokensは大きめに
  2. 直列処理の重要性

    • 並列処理は高速だが不安定
    • 0.5秒の待機でレート制限を回避
  3. プロンプト設計

    • シンプルなプロンプトほど成功率が高い
    • 具体的な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/)からお気軽にどうぞ!


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?