コンテンツにスキップ

thumbnail_generation Lambda 概要

SQS(thumbnail-generation キュー)のメッセージをトリガーに、レース動画から AI が選定した瞬間の フレームを切り出してサムネイル画像を作る Lambda 関数です。

画像を生成するのではなく、動画から切り出す

サムネイルは生成 AI が描画するのではありません。AI(thumbnail_generation_agent)が レース動画・レース結果・コース画像を解析して「サムネイルに最適な瞬間(動画先頭からの経過秒数)」を 選定し、その位置のフレームを ffmpeg で切り出します。


トリガー

送信元 タイミング
App Lambda(odds_poc_app) ブログのサムネイル生成リクエスト時(POST /v1/blogs/{blog_id}/thumbnail_generate)

ペイロードは article_id / blog_id のどちらか一方を指定します(両方指定・両方未指定はエラー)。 記事向けの生成経路も実装されていますが、現在 App Lambda が投入するのはブログ向けのみです。


SQS イベントペイロード(受信)

{
  "blog_id": "string"
}
フィールド データ型 必須 備考
article_id string △ 記事向け。blog_id と排他
blog_id string △ ブログ向け。article_id と排他

処理フロー

  1. SQS イベントを受信し、ペイロードをパース・バリデーションする
  2. DocumentDB へ接続する
  3. 対象(blog / article)とその race_id からレースを特定する
  4. レース動画({racing_type}/video/{race_id}.mp4)を /tmp へダウンロードし、ffprobe で動画長を得る
  5. thumbnail_generation_agent に「最適な瞬間(秒)」を選定させる
    • 入力: レース動画(S3 URI)・レース結果・コース画像(競馬のみ)
    • 失敗した場合は動画長の 90%(ゴール付近)へフォールバックする
  6. タイムスタンプを動画範囲内へクランプし、ffmpeg でフレームを切り出す
  7. JPEG を S3 へアップロードし、media レコードを upsert する
  8. (ブログの場合)本文 HTML の「10秒サマリー」直下へサムネイル画像を挿入する
flowchart TD
    AppLambda([App Lambda]) -->|SQS| Start
    Start[SQS トリガー受信] --> DB[DocumentDB へ接続]
    DB --> Target[blog / article とレースを特定]
    Target --> Download[レース動画を /tmp へダウンロード]
    Download --> Duration[ffprobe で動画長を取得]
    Duration --> Choose[thumbnail_generation_agent が最適な秒数を選定]
    Choose -->|失敗| Fallback[動画長の 90% にフォールバック]
    Choose -->|成功| Clamp
    Fallback --> Clamp[動画範囲内へクランプ]
    Clamp --> Extract[ffmpeg でフレームを切り出し]
    Extract --> Upload[S3 へアップロード]
    Upload --> Media[media レコードを upsert]
    Media -->|ブログの場合| Insert[body_html へサムネイルを挿入]

保存先と命名規則

対象 S3 キー media.name
記事 {racing_type}/thumbnail/{race_id}.jpg thumbnail_{race_id}.jpg
ブログ {racing_type}/thumbnail/blog_{blog_id}.jpg thumbnail_blog_{blog_id}.jpg

media.name はユニークインデックスのため、既存レコードがあれば上書き更新(upsert)します。 SQS 再配信などで同じレースのサムネイルを再生成した場合の冪等性を担保するためです。

公開 URL

S3 バケットは非公開(Block Public Access)のため、ブラウザから参照できる CloudFront 経由の URL (CLOUDFRONT_DOMAIN 環境変数)を media.s3_url に保存します。 未設定の環境向けに S3 の直 URL へフォールバックします。


本文 HTML への埋め込み(ブログのみ)

ブログ本文の「10秒サマリー」セクション直下へ、次の img タグを挿入します。

<img class="nb-thumbnail" src="{CloudFront URL}" alt="レースのサムネイル画像"
     style="max-width:100%;height:auto;border-radius:8px"/>

挿入位置の決定は次の順で行います。

  1. 既にサムネイル(<img class="nb-thumbnail">)があれば置き換える(再生成の冪等性)
  2. 「10秒サマリー」セクション(<section class="nb-summary-section">)があればその直後
  3. 本文領域(<div class="nb-body")があればその直前
  4. いずれも無ければ末尾に追記

このマークアップは blog_builder 側の nb-thumbnail と揃えており、 再ビルド時には blog_builder が既存 body_html から抽出して引き継ぎます。


使用 AWS サービス・ライブラリ

サービス / ライブラリ 用途
Amazon SQS トリガー受信
Amazon S3 レース動画・コース画像の取得、サムネイルの保存
Amazon CloudFront 保存したサムネイルの公開配信
Amazon Bedrock サムネイル位置の選定(Nova Pro)
Amazon DocumentDB blog / article / media の読み書き
LangGraph サムネイル選定エージェントの構築・実行
ffmpeg / ffprobe 動画長の取得とフレーム抽出