ThumbAPI logoThumbAPI
Tutorial

Batch Thumbnail Generation for 100 Videos with Python

Aldin Kozica
Aldin Kozica
4 min read
Batch Thumbnail Generation for 100 Videos with Python — ThumbAPI Blog

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:

  1. Reads a list of video titles from a CSV file
  2. Calls the ThumbAPI generate endpoint for each title
  3. Saves each thumbnail to disk with a clean filename
  4. Handles rate limits and errors without crashing
  5. 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 requests library (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

VideosManual design timeBatch 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.

Aldin Kozica

Written by

Aldin Kozica

Full-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.

Generate Thumbnails With an API

Try ThumbAPI free. 5 thumbnail generations per month, no credit card required. One API call, production-ready output.