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)
| 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
- Receive the SQS event, parse and validate the payload
- Connect to DocumentDB
- Resolve the target (
blog/article) and its race fromrace_id - Download the race video (
{racing_type}/video/{race_id}.mp4) to/tmpand read its duration withffprobe - Ask
thumbnail_generation_agentfor 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)
- Clamp the timestamp into the video range and extract the frame with
ffmpeg - Upload the JPEG to S3 and upsert the
mediarecord - (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:
- If a thumbnail (
<img class="nb-thumbnail">) already exists, replace it (idempotent regeneration) - Right after the "10-second summary" section (
<section class="nb-summary-section">) if present - Right before the body region (
<div class="nb-body") if present - 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 |