Batch Thumbnail Generation for 100 Videos with Python
One thumbnail in Photoshop takes me maybe three minutes if I already have a layered template open. A hundred thumbnails in one sitting is the kind of task I'll abandon halfway through. This guide is the Python script I actually use when a client drops a spreadsheet of titles on me and wants the artwork by morning. CSV of titles in, folder of thumbnails out, via ThumbAPI.
By the end you'll have something you can drop straight into a content pipeline without touching it again.
What We're Building
A Python script that:
- Reads a list of video titles from a CSV file
- Calls the ThumbAPI generate endpoint for each title
- Saves each thumbnail to disk with a clean filename
- Handles rate limits and errors without crashing
- Logs results so you know what succeeded and what failed
It's the same shape of script I've shipped to two different agencies. Nothing fancy. The boring parts (retries, idempotent re-runs, slugged filenames) are what actually make it survive contact with a real content calendar.
Prerequisites
- Python 3.8 or higher
- A ThumbAPI API key (get one free here)
- The
requestslibrary (pip install requests)
Step 1 — Prepare Your Input CSV
Create a file called videos.csv with your video titles:
title,format,style
"How the Roman Empire Actually Collapsed",youtube,faceless
"The Deepest Cave Ever Explored",youtube,faceless
"Why the Stock Market Crashes Every Decade",youtube,faceless
"The Psychology of Persuasion Explained",youtube,faceless
"10 Ancient Civilizations Nobody Talks About",youtube,faceless
Adding format and style columns gives you flexibility to mix YouTube, blog, and Instagram thumbnails in a single batch run.
Step 2 — The Batch Generation Script
import requests
import base64
import csv
import time
import logging
from pathlib import Path
# --- Config ---
API_KEY = "your_api_key_here"
API_URL = "https://api.thumbapi.dev/v1/generate"
INPUT_CSV = "videos.csv"
OUTPUT_DIR = Path("thumbnails")
DELAY_BETWEEN_REQUESTS = 2 # seconds
CUSTOM_ASSETS_ID = None # set your dataset ID here if using brand styles
# --- Setup ---
OUTPUT_DIR.mkdir(exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s — %(levelname)s — %(message)s",
handlers=[
logging.FileHandler("batch_run.log"),
logging.StreamHandler()
]
)
def generate_thumbnail(title, format="youtube", style="faceless"):
payload = {
"title": title,
"format": format,
"imageStyle": style,
"outputFormat": "webp"
}
if CUSTOM_ASSETS_ID:
payload["customAssetsId"] = CUSTOM_ASSETS_ID
try:
response = requests.post(
API_URL,
headers={
"x-api-key": API_KEY,
"Content-Type": "application/json"
},
json=payload,
timeout=60
)
response.raise_for_status()
data = response.json()
image_b64 = data["image"].split(",")[1]
return base64.b64decode(image_b64)
except requests.exceptions.HTTPError as e:
logging.error(f"HTTP error for '{title}': {e.response.status_code}")
return None
except Exception as e:
logging.error(f"Unexpected error for '{title}': {e}")
return None
def slugify(text):
return text.lower().replace(" ", "-").replace("'", "").replace(",", "")[:60]
def run_batch():
with open(INPUT_CSV, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
rows = list(reader)
total = len(rows)
success = 0
failed = 0
logging.info(f"Starting batch — {total} thumbnails to generate")
for i, row in enumerate(rows, 1):
title = row["title"].strip()
fmt = row.get("format", "youtube").strip()
style = row.get("style", "faceless").strip()
filename = f"{i:03d}-{slugify(title)}.webp"
output_path = OUTPUT_DIR / filename
# Skip if already generated (safe to re-run)
if output_path.exists():
logging.info(f"[{i}/{total}] Skipping (exists): {filename}")
success += 1
continue
logging.info(f"[{i}/{total}] Generating: {title}")
image_bytes = generate_thumbnail(title, fmt, style)
if image_bytes:
output_path.write_bytes(image_bytes)
logging.info(f"[{i}/{total}] Saved: {filename}")
success += 1
else:
logging.warning(f"[{i}/{total}] Failed: {title}")
failed += 1
if i < total:
time.sleep(DELAY_BETWEEN_REQUESTS)
logging.info(f"Batch complete — {success} succeeded, {failed} failed")
if __name__ == "__main__":
run_batch()
Step 3 — Run It
python batch_thumbnails.py
Your thumbnails/ folder now contains production-ready WebP files at 1280x720.
Handling Large Batches
For 100 videos with a 2-second delay between requests, the full batch takes roughly 45–50 minutes. Two options:
Option A — Run overnight
Schedule the script via cron to run while you sleep:
# Run every night at 2am (cron)
0 2 * * * /usr/bin/python3 /path/to/batch_thumbnails.py
Option B — Resume interrupted runs
The script already handles this. The if output_path.exists(): continue check means you can stop and restart at any point without re-generating thumbnails you already have.
Using a Brand Style Dataset
If your channel has a consistent visual identity, upload reference images to ThumbAPI once and use the returned asset ID in every batch run:
CUSTOM_ASSETS_ID = "m6XhjtZNdF0N2AFXUOiq" # your dataset ID
Every thumbnail in the batch will use your channel's color palette, typography style, and visual tone automatically.
Mixed Format Batches
Your CSV can mix formats for different platforms in a single run:
title,format,style
"How the Roman Empire Collapsed",youtube,faceless
"The Roman Empire — A Deep Dive",blogpost,faceless
"5 Facts About Rome You Didn't Know",instagram,faceless
The script handles each row independently, so you can generate YouTube thumbnails, blog covers, and Instagram images in one pass.
Time Saved at Scale
| Videos | Manual design time | Batch script time |
|---|---|---|
| 10 | ~8 hours | ~4 minutes |
| 50 | ~40 hours | ~20 minutes |
| 100 | ~80 hours | ~45 minutes |
Run it once at the end of the day, walk away, and you wake up to a folder of 1280x720 WebPs named after their titles. That's the whole pitch.

Written by
Aldin KozicaFull-stack developer from Bosnia and Herzegovina. I built ThumbAPI because I kept watching content teams waste hours on thumbnail design when the patterns are predictable enough to automate. The API is the tool I wished existed when building content pipelines for my own projects.
Continue Reading
Upload reference images once, then every thumbnail the API generates matches your channel's visual identity. Step-by-step guide with cURL, Python, and JavaScript examples.
Generate YouTube Thumbnails with JavaScript and Node.jsComplete guide to integrating ThumbAPI into JavaScript and Node.js projects. Covers single requests, batch generation, TypeScript types, and integration patterns.
How to Automate YouTube Thumbnails for Faceless Channels with One API CallStep-by-step guide to automating faceless YouTube thumbnail generation with a single ThumbAPI POST request. Includes cURL, Python, and Node.js examples.
How to Auto-Generate YouTube Thumbnails with n8n (Step-by-Step)Build an n8n workflow that detects new YouTube uploads, generates a thumbnail with ThumbAPI, and saves it to Drive — fully automated, with batch and multi-platform patterns.
Generate Thumbnails With an API
Try ThumbAPI free. 5 thumbnail generations per month, no credit card required. One API call, production-ready output.