Skip to content

thumbnail_generation Lambda Overview

Triggered by messages on the SQS thumbnail-generation queue, this Lambda produces a thumbnail by extracting the frame at an AI-selected moment of the race video.

The image is extracted, not generated

The thumbnail is not drawn by a generative model. An agent (thumbnail_generation_agent) analyses the race video, race result and course image to choose the best moment (seconds from the start of the video), and ffmpeg extracts the frame at that position.


Trigger

Sender When
App Lambda (odds_poc_app) On a blog thumbnail request (POST /v1/blogs/{blog_id}/thumbnail_generate)

The payload carries exactly one of article_id / blog_id (supplying both or neither is an error). The article path is implemented, but the App Lambda currently enqueues only blog thumbnails.


SQS event payload (input)

{
  "blog_id": "string"
}
Field Type Required Notes
article_id string โ–ณ For articles; mutually exclusive with blog_id
blog_id string โ–ณ For blogs; mutually exclusive with article_id

Processing flow

  1. Receive the SQS event, parse and validate the payload
  2. Connect to DocumentDB
  3. Resolve the target (blog / article) and its race from race_id
  4. Download the race video ({racing_type}/video/{race_id}.mp4) to /tmp and read its duration with ffprobe
  5. Ask thumbnail_generation_agent for the best moment in seconds
    • Input: race video (S3 URI), race result, course image (horse racing only)
    • On failure, fall back to 90 % of the duration (near the finish)
  6. Clamp the timestamp into the video range and extract the frame with ffmpeg
  7. Upload the JPEG to S3 and upsert the media record
  8. (Blogs only) Insert the thumbnail into the body HTML just below the "10-second summary"
flowchart TD
    AppLambda([App Lambda]) -->|SQS| Start
    Start[Receive SQS trigger] --> DB[Connect to DocumentDB]
    DB --> Target[Resolve blog / article and its race]
    Target --> Download[Download race video to /tmp]
    Download --> Duration[Read duration with ffprobe]
    Duration --> Choose[thumbnail_generation_agent picks the best second]
    Choose -->|failure| Fallback[Fall back to 90% of the duration]
    Choose -->|success| Clamp
    Fallback --> Clamp[Clamp into the video range]
    Clamp --> Extract[Extract the frame with ffmpeg]
    Extract --> Upload[Upload to S3]
    Upload --> Media[Upsert the media record]
    Media -->|blogs| Insert[Insert the thumbnail into body_html]

Storage and naming

Target S3 key media.name
Article {racing_type}/thumbnail/{race_id}.jpg thumbnail_{race_id}.jpg
Blog {racing_type}/thumbnail/blog_{blog_id}.jpg thumbnail_blog_{blog_id}.jpg

media.name has a unique index, so an existing record is updated in place (upsert). This keeps regeneration idempotent when, for example, SQS redelivers the message.

Public URL

The S3 bucket is private (Block Public Access), so media.s3_url stores a CloudFront URL (CLOUDFRONT_DOMAIN environment variable) that browsers can reach. Environments without it fall back to the direct S3 URL.


Embedding into body HTML (blogs only)

The following img tag is inserted just below the "10-second summary" section of the blog body.

<img class="nb-thumbnail" src="{CloudFront URL}" alt="ใƒฌใƒผใ‚นใฎใ‚ตใƒ ใƒใ‚คใƒซ็”ปๅƒ"
     style="max-width:100%;height:auto;border-radius:8px"/>

The insertion point is decided in this order:

  1. If a thumbnail (<img class="nb-thumbnail">) already exists, replace it (idempotent regeneration)
  2. Right after the "10-second summary" section (<section class="nb-summary-section">) if present
  3. Right before the body region (<div class="nb-body") if present
  4. Otherwise append to the end

The markup matches blog_builder's nb-thumbnail, which extracts and carries it over on rebuild.


AWS services and libraries

Service / library Use
Amazon SQS Trigger
Amazon S3 Fetching the race video and course image; storing the thumbnail
Amazon CloudFront Public delivery of the stored thumbnail
Amazon Bedrock Choosing the thumbnail moment (Nova Pro)
Amazon DocumentDB Reading/writing blog, article and media
LangGraph Building and running the thumbnail selection agent
ffmpeg / ffprobe Reading the duration and extracting the frame