When I changed my YouTube Shorts titles to name both games in the comparison, the view counts moved. Named-vs-named drove 162–373 views per video; stripping the names out of the same template collapsed it to 77, and naming nobody at all died at 5. That data was enough to start making corresponding changes to the thumbnail design — the visual and the title need to work together or neither works well.
Here are the four rules I've now hardcoded in the thumbnail generation pipeline after running 19 game-comparison Shorts.
Rule 1: Lead with the big number, not the game title
The original thumbnail was one ffmpeg drawtext call: the video title, wrapped to 24 characters a line, centered at fontsize 72 over a dimmed and vignetted still — the game's Steam art if the spec resolved one, otherwise a frame 40% into the video. So the subject came first and the statistical result arrived at the tail of a sentence, if it survived the wrap at all.
For a 3-second impression on a Shorts feed, this ordering is backwards. The viewer doesn't know to care about the game until they see the result. They need a reason to stop scrolling before they'll register who the comparison involves.
PR #37 made build_thumb.py the primary path for Shorts whenever the spec carries a number. It draws the number first — top left, in the accent color, starting at 132px and shrinking until it fits on one line — then the headline underneath in white, then an accent bar. The game art becomes the blurred, dimmed, full-bleed background rather than the subject line. The implicit reading order is now "RESULT → explanation" rather than "subject → result."
I don't have A/B split data on this specific change yet — it shipped this week. But the title-naming data consistently showed specificity is the click trigger. The number is the most specific element on the thumbnail. It should be the first thing the viewer reads.
Rule 2: Wire the cover_image explicitly, because the fallback is silent
The thumbnail generator takes a cover_image field from the YouTube script JSON. When I added the Hades vs Starfield Short to the queue, I omitted the field, assuming the generator would fall back to a sensible default.
It did fall back — that's the problem. cover_prep.py resolves a missing cover_image through game_data.game.appid to the Steam header art, and if that fails, compose.sh drops to the background photo's first frame and then to a gradient; thumbnail.sh independently falls back to a frame 40% into the video. Nothing errors. What you get for a two-game comparison is a picture of one game — a perfectly valid thumbnail that doesn't communicate the thing the video is about.
PR #36 added two fixes: an SOP for how the background image is selected and sourced (plan-included generation only, no text baked in, empty safe bands at top and bottom), and explicit wiring of cover_image in the Hades queue entry — a David-vs-Goliath composition that overrides the single-game Steam header. The SOP was drafted in a code-review session and committed directly to the video generation runbook.
The rule: the routing contract calls cover_image optional, and for a comparison spec that's a trap. If you're generating thumbnails programmatically, treat a silent fallback chain as a failure mode of its own — check what the fallback actually produced, because it will never tell you it fired.
Rule 3: Export at 16:9, not 9:16, for the preview card
YouTube Shorts play vertically at 9:16, so the obvious choice is to generate vertical thumbnails. The problem is where discovery actually happens.
Thumbnail preview cards in YouTube search results, the main feed, and embed contexts display horizontally. A 9:16 thumbnail that YouTube crops to a 16:9 preview card loses the top and bottom — which is exactly where I'd placed the game title and the result number. The center crop shows only the background art.
My current export is 1280×720 (16:9). YouTube scales it for the Shorts player by adding sidebars. The data in the preview card — where the viewer decides whether to click — is preserved in full.
This rule depends on where your Shorts discovery is actually happening. If most of your traffic comes from within the Shorts vertical feed (the swipe-up interface), 9:16 makes sense and my rule doesn't apply. At my follower count, feed cards are the dominant discovery surface, so the horizontal export wins.
Rule 4: Hardcode the layout constants, don't prompt for them
PR #36 brought an image model into the pipeline for the first time, to generate the background art. The obvious next step would have been to let it lay out the whole thumbnail — describe the design in the prompt and let it render text too. The SOP rules that out in one line: the title and the big number are never baked into the AI image. The model produces a text-free background with empty safe bands; the pipeline composites the text.
Prompting treats layout as a creative decision the model makes fresh each time. For automation, that's the wrong model. I want an identical layout across every thumbnail in a series, with only the game names and numbers changing.
So the constraints live in the Python generation script, not in prose:
W, H = 1280, 720
margin, y = 72, 84 # text block origin — left third, near the top
size = 132 # BIG NUMBER; shrinks by 6 until it fits one line, floor 60
f = _font(78) # headline, wrapped, max 3 lines
These aren't configurable parameters. They're invariants. The model doesn't pick them. The inputs the generator accepts are --number, --title, --subtitle, --art or --appid, and --accent — which the workflow fills from the spec's cover_hook (or its first stat panel), cover_title, cover_accent, and game_data.game.appid.
The same logic applied earlier on the directive side: once a decision is settled, move it from a prose instruction into code. A prose instruction can be skimmed past or interpreted loosely. A constant cannot.
Part of an ongoing 6-month experiment running three AI-curated directory sites. The technical claims here are real; this article was AI-assisted.
Top comments (0)