๐จ 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¶
- 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.
- 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.
- 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.
- No Bites badge โ removed per feedback. Channel name is already visible on YouTube.
- Title sizing tiers:
- โค16 chars โ 120px (exam codes like "MS-900")
- 17-30 chars โ 84px (short titles)
- 31+ chars โ 64px (longer multi-line titles)
- 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:
-
Exam series video: Use
render_exam_batch.py: -
Non-exam video: Edit
render_batch.pyBATCH array: -
Upload single thumbnail:
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')notmine: true - MCP server authenticates to Main channel only. For Bites API calls, use the YouTube Data API directly
- Playwright
page.set_content()blocksfile://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-Processpaths โ 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¶
- Download from Microsoft Learn:
https://learn.microsoft.com/en-us/media/learn/certification/badges/microsoft-certified-{level}-badge.svg - Copy to
badges/{exam-code}-badge.svg - Available levels:
fundamentals,associate,expert,specialty
Adding New Product Logos¶
- Check
badges/folder for existing logos - For missing logos, create clean SVGs (see
copilot-logo.svgandwindows-365-logo.svgfor examples) - 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:
- Pick a Tailwind colour (e.g., cyan-700 for accent, cyan-900 for bg_base)
- Set orb opacities at 0.50-0.60 for strong visibility
- 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¶
- Pastel โ distinct. Light/pastel backgrounds all look the same at YouTube thumbnail size. Dark saturated backgrounds create instant visual distinction between series.
- Edge-to-edge glass hides colour. The frosted glass panel should cover ~60% of the thumbnail, leaving 40% exposed colour for differentiation.
- Product logos > emojis for recognition. Students instantly recognise the Azure "A" or Teams icon. Emoji works for context but not as the primary visual.
- Exam code is the hero. Students search "AZ-900" not "Azure Fundamentals". Make the code massive (120px).
- 60-second upload delay is the sweet spot. Faster triggers YouTube's burst rate limit. Slower wastes time. 60s gives reliable ~200/day throughput.
- YouTube API quota is 10,000 units/day. Plan for ~200 thumbnail uploads per day maximum. Multi-day uploads are normal for large channels.
- Series-level, not category-level colours. AZ-900 and MS-900 are both "certification" but need completely different palettes for visual distinction.
- 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.
- Circular AI logos looked bad. Hand-drawn SVGs of ChatGPT/Gemini/Claude don't look authentic. Keep it simple โ use a brain emoji instead.
page.set_content()doesn't work for local assets. Always use temp file +page.goto()for Playwright rendering with local fonts/images.- ๐ด 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. - ๐ด 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.