# AI Thumbnail Generator (`dami_studio/viral-thumbnail-generator`) Actor

AI thumbnail generator: make a video thumbnail from your title or story, an AI close-up face showing the story's emotion, with a short all-caps hook headline burned on. PNG in 9:16, 16:9 or 1:1. Runs on your own OpenAI key. $150.00 per 1,000 thumbnails ($0.15 each).

- **URL**: https://apify.com/dami_studio/viral-thumbnail-generator.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** For creators, Marketing, AI
- **Features:** Limited permissions
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $144.00 / 1,000 thumbnails

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

**AI thumbnail generator for your video title, with a close-up face and a hook headline**: give it
a video title, or the whole story. It works out the emotion, writes a short all-caps hook, draws a
close-up face with that expression, then burns the headline on with a real font so the words stay
sharp. You get a PNG in 9:16, 16:9 or 1:1.

The image is drawn on **your own OpenAI key**, which you paste into the input. The $0.15 below is
what this actor charges per thumbnail; the image generation itself is billed to you by OpenAI. Leave
the key out and the run writes a free sample record showing the exact output shape, with no image
and no thumbnail charged.

| | |
|---|---|
| **Input** | A title, or a title plus the story text |
| **Output** | One thumbnail PNG per run, and a row with its link, the emotion drawn and the hook burned on |
| **Ceiling** | One thumbnail per run |
| **Account needed** | Your own OpenAI key |
| **Price** | $150.00 per 1,000 thumbnails, so $0.15 a thumbnail on the Free plan, a little less on paid plans. The free plan's $5 a month covers about 33. OpenAI bills your key separately |

### 🔍 What AI Thumbnail Generator does

It reads your title or story and picks an emotion: betrayal, rage, shock, disgust, heartbreak, fear,
guilt or defiance. That emotion becomes an explicit facial description in the image prompt, which is
why the face reads at phone size instead of looking mildly concerned. The pick comes from English
keyword rules, so you can override it.

It then pulls a short hook out of your text, in all caps, and keeps the bottom quarter of the image
deliberately plain so the words have somewhere to sit.

Finally the headline is drawn on afterwards with Montserrat ExtraBold, not asked for inside the
image prompt. Image models are bad at letters. Burning the text on with a real font is the
difference between a headline and a smear of glyph-shaped noise.

Sizes follow the ratio: `9:16` gives 1024x1536, `16:9` gives 1536x1024, `1:1` gives 1024x1024.

### 📋 What data you get from each thumbnail

| What you get | Field |
|---|---|
| The finished PNG in the run's key-value store, and a link to it | `output.imageKey`, `output.imageUrl` |
| The emotion it drew, read from your text or forced by you | `emotion` |
| The headline as burned on, uppercased | `hookText` |
| The shape and pixel size | `aspectRatio`, `size` |
| Your title and subreddit, echoed back | `title`, `subreddit` |
| How long the run took | `processingSeconds` |

### ▶️ How to make a thumbnail from a video title

1. Open [AI Thumbnail Generator](https://apify.com/dami_studio/viral-thumbnail-generator) and click
   **Try for free**.
2. Put your video title into **Title / headline**. Paste the story into **Story / script** if you
   have one.
3. Pick an **Aspect ratio**.
4. Paste your key into **OpenAI API key (BYO)**. It is stored as a secret.
5. Click **Start**, then open the **Storage** tab and download `thumbnail-*.png`.

Start it with no key and you get the free sample record, with the emotion, hook and prompt it would
have used.

### 💰 How much does it cost to make a thumbnail?

**$0.15 per thumbnail** on the Free plan. Paid plans pay a little less: $0.148 on Starter, $0.146 on Scale and $0.144 on Business. One run makes one thumbnail, so
a run is one charge, and on the free plan the $5 Apify gives you each month covers about 33.

A keyless run is not charged for a thumbnail, because no thumbnail is made and nothing is written to
the dataset. Image generation is billed to you by OpenAI on your own key and does not appear here.
`imageQuality` changes that side of the bill, not this one.

### 📥 What you give it

```json
{
  "title": "AITA for refusing to pay for my sister's wedding?",
  "aspectRatio": "9:16",
  "openaiApiKey": "sk-...",
  "burnText": true
}
```

| Field | Default | What it is |
|---|---|---|
| `title` | none | The video title or story headline. This drives both the emotion and the hook. The box starts with an example, which is a prefill. |
| `story` | none | The full story or script. Longer text gives the emotion detection more to work with. |
| `subreddit` | none | Informs the hook style. `amitheasshole` fixes the hook to `AM I THE A-HOLE ?`. |
| `aspectRatio` | `9:16` | `9:16` for Shorts, Reels and TikTok, `16:9` for YouTube, `1:1` for square. |
| `emotion` | none, detected | Force one of the eight instead of letting it read the text. |
| `hookText` | none, derived | Force the burned-in headline word for word. |
| `gender` | none | Bias the face, for example `feminine` or `masculine`. |
| `burnText` | `true` | Turn it off to get the raw face with no headline on it. |
| `openaiApiKey` | none | Your OpenAI key, stored as a secret. Without it you get the sample record. |
| `imageModel` | `gpt-image-2.5-flare` | The image model name. |
| `imageQuality` | `high` | `low`, `medium` or `high`. This changes what OpenAI bills you, not what this charges. |
| `imageBaseUrl` | none | An OpenAI-compatible images endpoint of your own, if you use one. Empty means OpenAI's own. |

Leave both **Title / headline** and **Story / script** empty and the run stops straight away, key
or no key, because there is nothing to read.

### 📤 What you get back

One dataset row per thumbnail, with the PNG itself in the run's key-value store under
`thumbnail-<timestamp>.png`.

Below is the real record a keyless run wrote, cut short where the note and the prompt run long. A
real run returns the same fields with `output.imageKey` and `output.imageUrl` filled in, and no
`_sample`, `sampleData` or `note`:

```json
{
  "ok": true,
  "_sample": true,
  "sampleData": true,
  "note": "SAMPLE OUTPUT ...",
  "title": "AITA for refusing to pay for my sister's wedding?",
  "subreddit": null,
  "emotion": "shock",
  "hookText": "REFUSING TO PAY",
  "aspectRatio": "9:16",
  "size": "1024x1536",
  "imagePrompt": "Generate one ultra-high-quality viral YouTube thumbnail image. VERTICAL 9:16 portrait (1024x1536). ...",
  "output": { "imageKey": null, "imageUrl": null },
  "processingSeconds": 0
}
```

| Field | How to read it |
|---|---|
| `imagePrompt` | The whole prompt that was sent, on the sample record. Worth reading once: it is how you learn what to change. |
| `hookText` | The headline as it was burned on, already uppercased. |
| `emotion` | Which of the eight it read out of your text, or the one you forced. |
| `subreddit` | Echoed back as `null` when you left it empty. |

### 🧾 Reading the output

Only a delivered thumbnail is written to the dataset. Everything else goes somewhere it cannot bill.

| Row | Where it lands | Charged |
|---|---|---|
| A thumbnail | the dataset, one row | yes |
| The sample record | the key-value store, under `SAMPLE_AND_NOTICES` | no |
| A `TIME_LIMIT` record, when the run's time limit came before the image was made | the key-value store, under `SAMPLE_AND_NOTICES` | no |
| A `BAD_INPUT` record, when there was no headline or story, or when your key was refused (it names `openaiApiKey`) | the key-value store, under `SAMPLE_AND_NOTICES` | no |

That split is deliberate: this actor is billed per dataset row, so a sample is never written there.
A keyless run leaves your dataset empty and the sample in `SAMPLE_AND_NOTICES`.

If the headline overlay fails for any reason, the run still hands you the image without the text
rather than failing, and says so in the log. That image is charged like any other.

A refused key ends the run with the free `BAD_INPUT` note above. Any other failure, a rate limit
for example, fails the run with the reason in the log. Neither is charged.

### 💡 What people use it for

- Covers for Reddit story channels, where the hook and the face are the whole click.
- Testing three emotions on the same title to see which one gets the click, by forcing `emotion`.
- Making the 16:9 and the 9:16 version of one cover so a long video and its short match.
- Getting the raw face with `burnText` off, then setting your own type in an editor.

From a story to a cover, in three runs:

1. Rewrite the story with
   [Story to Script Rewriter](https://apify.com/dami_studio/story-to-script-rewriter). Each row has a
   `title`, a `hook` and two `altHooks`.
2. Paste those lines into **Texts / variants** on
   [Hook & Virality Scorer](https://apify.com/dami_studio/hook-virality-scorer) and take the
   top-ranked one.
3. Put that line into **Title / headline** here, with the story in **Story / script**.

### 🚧 What it does not do

- **One thumbnail per run.** There is no batch mode.
- **It draws a face, not your face.** No photo upload, no likeness of a real person, no face swap.
- **No logos, no brand assets, no product shots.** The prompt asks for a clean image with no text,
  no watermark and no UI.
- **The model still decides what it draws.** Two runs on the same title give you two different
  faces, which is useful for testing and annoying if you wanted the same one twice.
- **No editing after the fact.** To change the headline, set `hookText` and run it again.
- **The headline is one line in one typeface**, Montserrat ExtraBold, lower third, uppercase, with
  no wrapping. A derived hook stays short, but a long `hookText` of your own runs off the edges.
- **Emotion detection is English word lists.** On other languages, or when it guesses wrong, set
  `emotion` yourself.
- It is not an official tool of any platform or company named on this page. Dami's Studio is independent and is not affiliated with or endorsed by any of them.

### 🧭 Which creator tool do you need?

| If you want | Use |
|---|---|
| A thumbnail with a face and a hook | This one |
| Titles, descriptions and hashtags for the post | [Social Metadata Generator](https://apify.com/dami_studio/social-metadata-generator) |
| A hook scored before you shoot it | [Hook & Virality Scorer](https://apify.com/dami_studio/hook-virality-scorer) |
| Captions burned into the video | [Auto Caption Burner](https://apify.com/dami_studio/auto-caption-burner) |
| A whole faceless short built from a script | [AI Faceless Video Generator](https://apify.com/dami_studio/ai-faceless-short-generator) |
| A long video cut into short clips | [AI Viral Clip Cutter](https://apify.com/dami_studio/ai-viral-clip-cutter) |

### ❓ Questions people ask

#### Why do I need my own OpenAI key for thumbnails?

The image is generated on your key, so the usage and the cost of generation stay on your account.

#### Can I use my own face on the thumbnail?

No. It generates a person, it does not edit a photo you upload.

#### Can I force the exact headline?

Yes, put it in `hookText` and it is used word for word, uppercased. Keep it short, since there is
no wrapping.

#### What does the emotion actually change?

It swaps in a specific facial description, like a dropped jaw and raised eyebrows for `shock`. That
is the bit that makes a thumbnail readable at phone size.

#### Can I call it from code or connect it to an AI assistant?

The [API tab](https://apify.com/dami_studio/viral-thumbnail-generator/api/python) has code for Python, JavaScript and
the CLI. MCP clients like Claude and ChatGPT use
`https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/viral-thumbnail-generator`; coding assistants can load
[our skill file](https://api.apify.com/v2/key-value-stores/BTBHU3DdiuoF4JOsK/records/apify-ai-video-content-SKILL.md).
All three bill your Apify account at the same price, and OpenAI bills your key as usual.

### 🆘 If something breaks

Open the **Issues** tab on the actor page. Send the run ID and the title you used. Read
`imagePrompt` on the sample record first: most surprises are the prompt doing exactly what the
title asked for.

# Actor input Schema

## `title` (type: `string`):

The video title or story headline (e.g. an AITA/TIFU title). Drives emotion + hook text.

## `story` (type: `string`):

Full story or script text for richer emotion detection and hook extraction.

## `subreddit` (type: `string`):

If 'amitheasshole', the hook is fixed to 'AM I THE A-HOLE ?'. Otherwise informs hook style.

## `aspectRatio` (type: `string`):

9:16 for Shorts/Reels/TikTok, 16:9 for YouTube long-form, 1:1 for square.

## `emotion` (type: `string`):

Force a facial emotion instead of auto-detecting from the text.

## `hookText` (type: `string`):

Force the burned-in headline text. Leave empty to auto-derive from the title/story.

## `gender` (type: `string`):

Bias the generated face, e.g. 'feminine' or 'masculine'. Leave empty for model default.

## `burnText` (type: `boolean`):

Overlay the bold hook headline on the lower third. Turn off to get the raw face image only.

## `openaiApiKey` (type: `string`):

Your OpenAI key for image generation (gpt-image-2.5-flare). Kept private. Leave empty to get a labeled SAMPLE thumbnail-metadata record (no image is rendered and you are not charged), add your key to render a real thumbnail PNG.

## `imageModel` (type: `string`):

Image model name. Default gpt-image-2.5-flare.

## `imageQuality` (type: `string`):

gpt-image-2.5-flare quality: low / medium / high.

## `imageBaseUrl` (type: `string`):

OpenAI-compatible images base URL. Default https://api.openai.com/v1.

## Actor input object example

```json
{
  "title": "AITA for refusing to pay for my sister's wedding?",
  "aspectRatio": "9:16",
  "burnText": true,
  "imageModel": "gpt-image-2.5-flare",
  "imageQuality": "high"
}
```

# Actor output Schema

## `results` (type: `string`):

Result rows / metadata are stored in the default dataset (one row per item).

## `files` (type: `string`):

Generated media/files (video, audio, images, captions) are stored in the default key-value store.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "title": "AITA for refusing to pay for my sister's wedding?"
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/viral-thumbnail-generator").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "title": "AITA for refusing to pay for my sister's wedding?" }

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/viral-thumbnail-generator").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "title": "AITA for refusing to pay for my sister'\''s wedding?"
}' |
apify call dami_studio/viral-thumbnail-generator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/viral-thumbnail-generator"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/gGbmaM9JuaJflZ47M/builds/BFo2GHG5iycdSLE6K/openapi.json
