Skip to content

๐ŸŽจ Bites YouTube Thumbnail Pipeline

Status: โœ… Complete โ€” 733/733 thumbnails rendered and uploaded (100%)
Built: April 14-17, 2026
Location: C:\ssClawy\aguidetocloud-revamp\scripts\thumbnail-generator\
Channel: "A Guide to Cloud & AI โ€” Bites" (@AGuideToCloud, channel ID: UCg8OCdG1yeiSPFDyqQDxHnw)


Overview

Complete YouTube thumbnail rebrand pipeline for the Bites channel (733 videos). Uses HTML/CSS glassmorphism templates rendered via Playwright screenshots โ€” same proven approach as the OG Image Pipeline but adapted for YouTube thumbnails at 1280ร—720 PNG.

The system produces branded thumbnails with:

  • Dark saturated backgrounds with gradient orbs (16 unique colour palettes)
  • Frosted glass text panel (left side) with bold titles and subtitles
  • Large product logo hero (right side) floating on the coloured background
  • Series capsule ("Part X of Y") for multi-part content
  • Category pill for instant identification
  • Channel logo footer

Design System โ€” F2 Split Glass (Dark)

Template Evolution

Version Name Why Rejected
A Clean Card Centred card too symmetrical, boring
B Split Panel Right panel too busy with rings
C Bold Banner Full-bleed, no glassmorphism
D Glass Bold Edge-to-edge glass hid the colour
E Glass Hero Pastel backgrounds โ€” all looked the same
F2 Split Glass (LOCKED) Dark saturated + frosted panel = winner

F2 Template Specs

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ [Part 3 of 20]          โ”‚    โ”‚                    โ”‚  โ”‚
โ”‚  โ”‚                         โ”‚    โ”‚    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚  โ”‚
โ”‚  โ”‚ [CATEGORY PILL]         โ”‚    โ”‚    โ”‚  PRODUCT โ”‚    โ”‚  โ”‚
โ”‚  โ”‚                         โ”‚    โ”‚    โ”‚   LOGO   โ”‚    โ”‚  โ”‚
โ”‚  โ”‚ MASSIVE                 โ”‚    โ”‚    โ”‚  (240px) โ”‚    โ”‚  โ”‚
โ”‚  โ”‚ TITLE                   โ”‚    โ”‚    โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚  โ”‚
โ”‚  โ”‚ (120px)                 โ”‚    โ”‚         โ”‚[badge]   โ”‚  โ”‚
โ”‚  โ”‚                         โ”‚    โ”‚    โ—‹ glow rings โ—‹  โ”‚  โ”‚
โ”‚  โ”‚ Subtitle (34px bold)    โ”‚    โ”‚                    โ”‚  โ”‚
โ”‚  โ”‚                         โ”‚    โ”‚                    โ”‚  โ”‚
โ”‚  โ”‚ ๐Ÿชท A Guide to Cloud     โ”‚    โ”‚                    โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚  โ† Frosted glass (35% white) โ†’  โ† Coloured background โ†’ โ”‚
โ”‚         740px                         480px               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                        1280 ร— 720 PNG

Key Design Rules

  1. Series-level colour distinction โ€” every distinct series gets its OWN palette. AZ-900 โ‰  MS-900 โ‰  AZ-104. Never reuse the same palette for adjacent series in the timeline.
  2. Exam code = BIG title (120px) โ€” students search for "AZ-900", not "Azure Fundamentals". The exam code is always the massive hero text for cert/exam content.
  3. Product logo > emoji โ€” the product logo (Azure, Teams, M365, etc.) is always the 240px hero visual. Emoji is the small 80px contextual badge overlapping the bottom-right.
  4. No Bites badge โ€” removed per feedback. Channel name is already visible on YouTube.
  5. Title sizing tiers:
    • โ‰ค16 chars โ†’ 120px (exam codes like "MS-900")
    • 17-30 chars โ†’ 84px (short titles)
    • 31+ chars โ†’ 64px (longer multi-line titles)
  6. Subtitle: 34px, font-weight 700, darker accent colour

16 Dark Saturated Palettes

Each palette has: accent, accent_light, accent_shadow, text_dark, subtitle_color, channel_name_color, bg_base, bg_tint, and 3 orb colours.

# Name Accent Background Series Examples
1 azure-blue #2563EB #1E3A8A Azure Labs, PL-900
2 indigo #4338CA #312E81 AVD Series, Copilot Agents, DP-900
3 purple #7C3AED #4C1D95 MS-900, M365 Agents
4 green #059669 #064E3B AZ-900, Azure Interview
5 teal #0891B2 #164E63 MS-700, Teams Premium
6 amber #D97706 #78350F AZ-400, Windows 365, M365 Interview
7 rose #E11D48 #881337 MS-500, Prompt Eng Beginner
8 orange #EA580C #7C2D12 AZ-104, App Service, Azure DevOps
9 pink #DB2777 #831843 AZ-304, Prompt Eng (Dog theme)
10 sky #0284C7 #0C4A6E AZ-303, Azure OpenAI, AI-900 Q&A
11 red #DC2626 #7F1D1D SC-900, Purview
12 fuchsia #A21CAF #701A75 AZ-204, Copilot Studio
13 lime #65A30D #365314 AZ-500, SC-100 Q&A
14 emerald #047857 #064E3B (available)
15 violet #8B5CF6 #5B21B6 (available)

Rule: When adding a new series, pick a palette that contrasts with its chronological neighbours. Check render_batch.py PALETTES dict for full colour specs.


File Map

scripts/thumbnail-generator/
โ”œโ”€โ”€ template-f2-split-glass.html        # LOCKED production template (DO NOT create new without approval)
โ”œโ”€โ”€ template-f2-circular-logos.html      # Circular AI logos variant (unused โ€” too complex)
โ”œโ”€โ”€ template-yt-banner.html             # YouTube channel banner template (2560ร—1440)
โ”œโ”€โ”€ template-a/b/c/d/e-*.html           # Earlier iterations (archived, can delete)
โ”‚
โ”œโ”€โ”€ render_batch.py                     # Manual batch renderer (edit BATCH array + palettes)
โ”œโ”€โ”€ render_exam_batch.py                # Generic exam series renderer (CLI args)
โ”œโ”€โ”€ render_nonexam.py                   # Auto-renders all non-exam series
โ”œโ”€โ”€ render_mockups.py                   # Design mockup renderer
โ”œโ”€โ”€ render_banners.py                   # YouTube channel banner renderer
โ”œโ”€โ”€ render_az900.py                     # AZ-900 specific renderer (standalone)
โ”‚
โ”œโ”€โ”€ upload_daily.py                     # Simple daily uploader (configurable limit)
โ”œโ”€โ”€ upload_overnight.py                 # Long-running uploader (60s delays, auto-retry)
โ”œโ”€โ”€ upload_persistent.py                # Hourly check + auto-upload when quota available
โ”‚
โ”œโ”€โ”€ bites_all_videos.json               # All 733 Bites channel videos (chronological)
โ”œโ”€โ”€ uploaded_ids.json                   # Upload progress tracker (717 IDs)
โ”œโ”€โ”€ *_ids.json                          # Per-batch video ID lists (15 files)
โ”‚
โ”œโ”€โ”€ fonts/
โ”‚   โ””โ”€โ”€ InterVariable.woff2            # Shared with OG generator
โ”‚
โ”œโ”€โ”€ badges/                             # 32 files โ€” cert badges + product logos
โ”‚   โ”œโ”€โ”€ az-900-badge.svg ... sc-900-badge.svg    # 13 exam-level badges
โ”‚   โ”œโ”€โ”€ microsoft-certified-*.svg                 # 4 level badges (Fundamentals/Associate/Expert/Specialty)
โ”‚   โ”œโ”€โ”€ azure-logo.svg                            # Azure "A" icon
โ”‚   โ”œโ”€โ”€ teams-logo.svg                            # Teams icon
โ”‚   โ”œโ”€โ”€ copilot-logo.svg                          # Copilot sparkle (custom SVG)
โ”‚   โ”œโ”€โ”€ microsoft-365-logo.svg                    # M365 icon
โ”‚   โ”œโ”€โ”€ windows-365-logo.svg                      # W365 icon (custom SVG)
โ”‚   โ”œโ”€โ”€ sharepoint-logo.svg, excel-logo.svg       # Office app icons
โ”‚   โ”œโ”€โ”€ word-logo.svg, outlook-logo.svg           # Office app icons
โ”‚   โ””โ”€โ”€ chatgpt-icon.svg, gemini-icon.svg, claude-logo.svg  # AI vendor icons (unused)
โ”‚
โ”œโ”€โ”€ mockups/                            # Design mockups and banner previews
โ”‚   โ”œโ”€โ”€ f2-split-glass_*.png            # Template F2 mockups (3 categories)
โ”‚   โ”œโ”€โ”€ banner-main-*.png               # Main channel banner options (A/B/C)
โ”‚   โ”œโ”€โ”€ banner-bites-*.png              # Bites channel banner options (A/B/C)
โ”‚   โ””โ”€โ”€ prompt-eng-*.png                # Prompt engineering mockups
โ”‚
โ””โ”€โ”€ output/                             # 715 rendered PNGs (~278 MB)
    โ””โ”€โ”€ {video-id}.png                  # Named by YouTube video ID

Commands Reference

Render Thumbnails

# Render a specific exam series (auto-extracts topics from video titles)
python scripts/thumbnail-generator/render_exam_batch.py --exam AZ-104 --palette orange --badge az-104-badge.svg

# Render all non-exam series (auto-detects series from title patterns)
python scripts/thumbnail-generator/render_nonexam.py

# Render design mockups for testing
python scripts/thumbnail-generator/render_mockups.py

# Render YouTube channel banners
python scripts/thumbnail-generator/render_banners.py

Upload Thumbnails

# Simple upload with progress tracking
python scripts/thumbnail-generator/upload_daily.py

# Long-running overnight upload (60s delays, auto-retry on rate limit + quota)
python scripts/thumbnail-generator/upload_overnight.py

# Persistent uploader โ€” checks hourly, auto-uploads when quota available
python scripts/thumbnail-generator/upload_persistent.py

# Check upload progress
Get-Content scripts/thumbnail-generator/upload_log.txt -Tail 5
$u = (Get-Content scripts/thumbnail-generator/uploaded_ids.json | ConvertFrom-Json).Count; "Uploaded: $u"

For New Bites Videos

When Sutheesh publishes a new Bites video:

  1. Exam series video: Use render_exam_batch.py:

    python render_exam_batch.py --exam AZ-305 --palette sky --badge az-305-badge.svg
    

  2. Non-exam video: Edit render_batch.py BATCH array:

    BATCH = [{
        "id": "VIDEO_ID_HERE",
        "title": "Big Title",
        "subtitle": "Descriptive subtitle",
        "category": "CATEGORY",
        "series": "Part 1 of 5",
        "palette": "teal",
        "hero": "badge:azure-logo.svg",
        "badge": "emoji:1f680",
    }]
    

  3. Upload single thumbnail:

    python upload_overnight.py  # picks up any un-uploaded PNGs
    


YouTube API Learnings

Rate Limits (Hard-Won Knowledge)

Limit Value Behaviour
Daily quota 10,000 units/day thumbnails.set = 50 units = max ~200 uploads/day
Quota reset Midnight Pacific (8 PM NZT) Sharp reset, not rolling
Burst rate limit ~20-50 rapid uploads "Too many thumbnails recently" โ€” separate from quota
Burst cooldown 10-30 min rolling window Cleared by waiting, not by quota reset
Optimal speed 60-second gaps Avoids BOTH burst limit and quota waste
Dangerous speed 18-28 seconds Triggers burst limit after ~20 uploads

Key Technical Facts

  • OAuth tokens auto-refresh via google.oauth2.credentials.Credentials โ€” no manual intervention needed for long-running scripts
  • Brand channel access: Bites is a brand channel under the same Google account as Main. Use channels.list(id: 'UCg8OCdG1yeiSPFDyqQDxHnw') not mine: true
  • MCP server authenticates to Main channel only. For Bites API calls, use the YouTube Data API directly
  • Playwright page.set_content() blocks file:// URIs. MUST use write-temp-file + page.goto(tmp.as_uri()) pattern
  • Python emoji in file redirect causes UnicodeEncodeError (cp1252). Use plain ASCII in log output
  • Windows Start-Process paths โ€” use full absolute backslash paths, not relative or forward-slash

YouTube Channel Banners

Also built during this session โ€” two channel banners at 2560ร—1440 using the same glassmorphism system.

Channel Hero Text Subtext Style
Main (@susanthsutheesh) Your AI & Cloud Learning Hub Hands-on tutorials by a Microsoft engineer Dark (#0f0f1a)
Bites (@AGuideToCloud) Azure ยท M365 ยท AI โ€” One Bite at a Time Short tutorials that get straight to the point Magenta tint (#1a0a1e)

Banner safe zone: 1546 ร— 423px centred (visible on all devices including mobile).

Template: template-yt-banner.html โ€” frosted glass card centred in safe zone, gradient orbs behind.


Architecture Diagram

flowchart TD
    A[bites_all_videos.json<br/>733 videos] --> B{Series Type?}
    B -->|Exam series| C[render_exam_batch.py<br/>--exam CODE --palette NAME]
    B -->|Non-exam| D[render_nonexam.py<br/>Auto-detects series]
    B -->|Manual| E[render_batch.py<br/>Edit BATCH array]

    C --> F[template-f2-split-glass.html<br/>+ palette + badge + emoji]
    D --> F
    E --> F

    F --> G[Playwright Screenshot<br/>1280ร—720 PNG]
    G --> H[output/{video-id}.png<br/>715 files, 278MB]

    H --> I[upload_overnight.py<br/>60s delays, auto-retry]
    I --> J[YouTube Data API<br/>thumbnails.set]
    J --> K[uploaded_ids.json<br/>Progress tracker]

Timeline

Date Milestone
Apr 14, 2:15 PM Session started โ€” design brief
Apr 14, 2:30 PM Templates A, B, C designed and rejected
Apr 14, 2:45 PM Template D (Glass Bold) โ€” user requested glassmorphism
Apr 14, 3:00 PM Template E (Glass Hero) โ€” bigger text, product logos
Apr 14, 3:30 PM Template F2 (Split Glass) โ€” approved design!
Apr 14, 3:45 PM Dark saturated palettes โ€” locked
Apr 14, 3:55 PM Exam code = 120px title โ€” locked
Apr 14, 4:10 PM All 12 exam series rendered (577 thumbnails)
Apr 14, 4:35 PM All 154 non-exam videos rendered (total: 731)
Apr 14, 4:45 PM Upload pipeline built, first uploads started
Apr 14, 8:00 PM 268 uploaded โ€” hit daily quota
Apr 15, 8:00 PM Quota reset โ€” 468 uploaded
Apr 15, 8:10 PM YouTube banners designed and uploaded
Apr 16, 8:00 PM Quota reset โ€” 628 uploaded
Apr 16, 9:30 PM 717/717 uploaded โ€” COMPLETE! ๐ŸŽ‰

Maintenance Guide

Adding New Cert Badges

  1. Download from Microsoft Learn: https://learn.microsoft.com/en-us/media/learn/certification/badges/microsoft-certified-{level}-badge.svg
  2. Copy to badges/{exam-code}-badge.svg
  3. Available levels: fundamentals, associate, expert, specialty

Adding New Product Logos

  1. Check badges/ folder for existing logos
  2. For missing logos, create clean SVGs (see copilot-logo.svg and windows-365-logo.svg for examples)
  3. Downloaded logos available from Microsoft Learn: https://learn.microsoft.com/en-us/media/logos/logo-{product}.svg

Adding New Palettes

All palettes are defined in render_batch.py PALETTES dict. To add a new one:

  1. Pick a Tailwind colour (e.g., cyan-700 for accent, cyan-900 for bg_base)
  2. Set orb opacities at 0.50-0.60 for strong visibility
  3. Ensure โ‰ฅ60ยฐ hue distance from adjacent series in timeline

Re-rendering All Thumbnails

If the template changes:

# Re-render exam series (run each)
python render_exam_batch.py --exam MS-900 --palette purple --badge ms-900-badge.svg
# ... repeat for each exam

# Re-render non-exam
python render_nonexam.py

# Re-upload all (clears uploaded_ids.json first)
Remove-Item uploaded_ids.json
python upload_overnight.py

Lessons Learned

  1. Pastel โ‰  distinct. Light/pastel backgrounds all look the same at YouTube thumbnail size. Dark saturated backgrounds create instant visual distinction between series.
  2. Edge-to-edge glass hides colour. The frosted glass panel should cover ~60% of the thumbnail, leaving 40% exposed colour for differentiation.
  3. Product logos > emojis for recognition. Students instantly recognise the Azure "A" or Teams icon. Emoji works for context but not as the primary visual.
  4. Exam code is the hero. Students search "AZ-900" not "Azure Fundamentals". Make the code massive (120px).
  5. 60-second upload delay is the sweet spot. Faster triggers YouTube's burst rate limit. Slower wastes time. 60s gives reliable ~200/day throughput.
  6. YouTube API quota is 10,000 units/day. Plan for ~200 thumbnail uploads per day maximum. Multi-day uploads are normal for large channels.
  7. Series-level, not category-level colours. AZ-900 and MS-900 are both "certification" but need completely different palettes for visual distinction.
  8. Batch-by-batch with user guidance is better than full automation. The user provides the right icon/logo per series and approves mockups before upload.
  9. Circular AI logos looked bad. Hand-drawn SVGs of ChatGPT/Gemini/Claude don't look authentic. Keep it simple โ€” use a brain emoji instead.
  10. page.set_content() doesn't work for local assets. Always use temp file + page.goto() for Playwright rendering with local fonts/images.
  11. ๐Ÿ”ด Underscore video IDs get skipped. YouTube video IDs starting with _ (e.g., _DTSnEGAGUI, __FbFNY1Zzo) are prone to being missed by sorting and glob patterns. 18 out of 733 were missed in the initial upload. ALWAYS run a post-upload reconciliation check.
  12. ๐Ÿ”ด ALWAYS reconcile after bulk uploads. Compare channel video list โ†’ rendered PNGs โ†’ uploaded IDs. Never trust "Done: X uploaded, 0 remaining" โ€” verify against the source of truth (bites_all_videos.json).

๐Ÿ”ด MANDATORY: Post-Upload QA Checklist

After ANY bulk thumbnail upload, run this reconciliation before declaring "done":

# 1. Count all three sources
$channel = (Get-Content scripts\thumbnail-generator\bites_all_videos.json | ConvertFrom-Json).Count
$rendered = (Get-ChildItem scripts\thumbnail-generator\output -Filter "*.png").Count
$uploaded = (Get-Content scripts\thumbnail-generator\uploaded_ids.json | ConvertFrom-Json).Count
Write-Host "Channel: $channel | Rendered: $rendered | Uploaded: $uploaded"

# 2. Find gaps โ€” videos on channel without uploaded thumbnail
$channelIds = (Get-Content scripts\thumbnail-generator\bites_all_videos.json | ConvertFrom-Json).id
$uploadedIds = Get-Content scripts\thumbnail-generator\uploaded_ids.json | ConvertFrom-Json
$missing = $channelIds | Where-Object { $_ -notin $uploadedIds }
Write-Host "MISSING: $($missing.Count)"
$missing | ForEach-Object { Write-Host "  $_" }

# 3. If any missing โ†’ render + upload
# python scripts\thumbnail-generator\fix_missing.py

The check passes when: Channel count = Uploaded count and Missing = 0.

Root cause of the April 2026 miss: Video IDs starting with _ were sorted differently by Python's sorted() vs the order they appeared in render scripts. The render scripts generated PNGs by video ID, but the upload script iterated a separate ID list that had different ordering โ€” causing 18 underscore-prefixed IDs to fall through the cracks.

Prevention: The fix_missing.py script directly compares the channel video list against uploaded IDs, bypassing all intermediate ID lists. Run it as the final QA step after any bulk operation.