# AI Image Caption & Alt Text Generator (Images + Video) (`kantolabs/image-caption-generator`) Actor

Generate image captions, detailed descriptions, accessibility/SEO alt text and keyword tags for images and video frames with an open-source AI model (Florence-2) - no API key. Video files also get a timed scene list and a summary. Chains with Video Frame Extractor. Failed images are free.

- **URL**: https://apify.com/kantolabs/image-caption-generator.md
- **Developed by:** [Kanto Labs](https://apify.com/kantolabs) (community)
- **Categories:** AI, SEO tools, Videos
- **Security:** Limited permissions
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.60 / 1,000 images

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

### What does AI Image Caption & Alt Text Generator do?

**AI Image Caption & Alt Text Generator** writes an **image caption**, a **detailed image description** and ready-to-use **alt text** for every image you give it, plus **keyword tags** and, if you want, the **text written in the image (OCR)**. It also works on **video files**: frames are sampled, repeated shots are skipped for free, and you get a caption per scene, a timed scene list and a one-paragraph **video summary**. It runs an open-source vision-language model (Microsoft **Florence-2**, MIT licence) inside the Actor, with **no API key** and no images sent to a third-party AI service.

Give it image links (your shop, CMS, S3, a CDN, Dropbox, Google Drive) or the dataset of a [Video Frame Extractor](https://apify.com/kantolabs/video-frame-extractor) run, and get clean JSON back: one row per image.

### Why use this image captioning API?

- **Three texts per image, for one price**: a short `caption`, a `detailedCaption` (up to 3 sentences: subject, clothing, colours, setting) and `altText` that fits the 125-character screen-reader guideline, written without "image of" filler.
- **Reads screenshots, slides and charts**, not only photos: "A screenshot of a code editor window in Visual Studio Code", "A graphic representation of the Wikimania Nairobi Onboarding Issue".
- **Video too**: a caption per scene with its time, a `timeline` and a `summary` paragraph - frames that look like the previous one are skipped and not charged.
- **You only pay for captioned images.** Broken links, web pages, unsupported files, blank images and skipped video frames are free, each with a plain-language reason.
- **Private and predictable**: the model runs inside the Actor; no OpenAI/Google key, no per-token bills, the same image gives the same text every time.
- **Built for automation**: n8n, Make, Zapier, the Apify API, schedules and webhooks. Chains with Video Frame Extractor.

### Use cases

- **Alt text for SEO and accessibility (WCAG)**: fill missing `alt` attributes on a website, blog or shop catalogue in bulk.
- **E-commerce product images**: first-draft product image descriptions and tags for listings and on-site search.
- **Digital asset management**: searchable captions and keywords for photo libraries, CMS media and stock folders.
- **Video indexing**: what is on screen in each scene of a video, with timestamps, for search, chapters or an LLM.
- **Dataset labelling**: quick captions for image datasets before human review.
- **Content moderation pre-screening**: a text description of each image to route or filter.

### How to generate captions and alt text for images

1. Paste **direct image links** into **Image URLs** (JPG, PNG, WEBP, GIF, BMP, TIFF), one per line. Remove the two sample photos.
2. Optional: add **Video URLs**, or pick a **Video Frame Extractor dataset**.
3. Choose the **detailed caption length** (`detailed` = up to 3 sentences, `paragraph` = up to 5) and the **alt text max length**.
4. Click **Start**. Results appear in the **Captions & alt text** view; videos also get a row in the **Videos** view.

Pages of social and video platforms (Instagram, Pinterest, X, Facebook, YouTube, TikTok, Vimeo...) are refused politely, free of charge: their terms forbid downloading. Use files you are allowed to process.

### Input example

```json
{
  "imageUrls": [
    "https://example.com/shop/products/red-sneaker.jpg",
    "https://example.com/blog/team-offsite.png"
  ],
  "detailLevel": "detailed",
  "altTextMaxLength": 125,
  "includeTags": true,
  "readImageText": false
}
```

Video: put file links into **Video URLs** (MP4, MOV, WEBM, MKV...). Frames are looked at every 2 seconds by default; only frames that look different from the last captioned one are captioned.

### Output example

One row per image (real output for the built-in sample photo):

```json
{
  "type": "image",
  "source": "image",
  "imageUrl": "https://api.apify.com/v2/key-value-stores/.../records/sample-caption-football.jpg",
  "fileName": "sample-caption-football.jpg",
  "width": 1024,
  "height": 683,
  "status": "ok",
  "caption": "A young boy playing soccer on a dirt field.",
  "detailedCaption": "A young boy playing soccer on a dirt field. He is wearing a red t-shirt, black shorts, and black sneakers. The boy is in mid-air, kicking a white soccer ball with his right foot.",
  "altText": "A young boy playing soccer on a dirt field.",
  "altTextLength": 43,
  "tags": ["boy", "soccer", "black", "dirt", "field", "red", "t-shirt", "shorts", "sneakers", "mid-air", "white", "ball"],
  "imageText": null,
  "processingTimeMs": 2148
}
```

- `caption`: a short headline (at most ~70 characters, cut at a natural break).
- `altText`: the first sentence of the description without "image/photo of", shortened to **Alt text max length**. Often the same as `caption`; it differs when the first sentence is long.
- `detailedCaption`: the full description. `tags`: keywords from it. `imageText`: lines of text read in the image (only with **Also read text in the image**).

Video frames have `source: "video-frame"` plus `videoUrl`, `timestampSeconds` and `timecode`, and `imageUrl` points to the stored frame. Each video also gets one row with `type: "video"`:

```json
{
  "type": "video",
  "fileName": "product-demo.mp4",
  "status": "ok",
  "durationSeconds": 30.03,
  "framesRead": 16,
  "framesCaptioned": 7,
  "framesSkippedSimilar": 9,
  "summary": "A 30-second video. It opens with ... Then ... It ends with ...",
  "timeline": [
    { "startSeconds": 0, "endSeconds": 4, "timecode": "00:00:00.000", "caption": "...", "framesCaptioned": 1, "frameUrl": "https://..." }
  ],
  "tags": ["..."]
}
```

### How much does it cost to caption images?

Pay per event: **$0.004 per captioned image** ($4 per 1,000 images), plus Apify's tiny actor-start fee. Store discounts: Silver and Gold $0.0036 per image. The same price covers the paragraph length and the OCR option.

**Free:** links that fail (404, private, timeout), web pages and other non-image files, unsupported formats (SVG, HEIC), blank single-colour images, refused platform links, video frames skipped as similar, and anything over your **Max cost per run**.

Worked examples:

| Job | Images charged | Cost |
|---|---|---|
| 1,000 product photos | 1,000 | $4.00 |
| 20 mixed images (the quality test below) | 20 | $0.08 |
| 30-second explainer video, a frame every 2 s (16 looked at, 9 similar skipped) | 7 | $0.028 |
| Video Frame Extractor run of 2 videos (24 frames) | 24 | $0.096 |

**Max cost per run** is respected exactly: captioning stops when the budget is reached and the remaining images get a free `skipped` row. **Max captioned frames per video** (default 60) caps the cost of one video.

### Accuracy - what to expect

Measured on 20 varied real images (people, products, food, screenshots, slides, a chart, landscapes, night and low-light scenes, video frames), every output checked by eye against the image:

- **9 of 20 fully correct, 10 partly wrong, 1 wrong.** The caption / alt text named the main subject on every image; on 4 it also stated a wrong detail (a head count, a website's name, the city of a night street, what a chart shows).
- The **detailed caption** gets the scene right, but its 2nd-3rd sentences contained a wrong detail on about half of the images (a count, "creamy" for a tomato sauce, a streetcar called a double-decker bus). Review descriptions before publishing them where exact facts matter.
- **Charts and infographics** are recognised as such, but what the chart shows can be misread (our 1 "wrong"). Turn on **Also read text in the image** to get the real labels and numbers.

The `paragraph` option writes more, and its later sentences are more often guesses.

### Tips

- **Alt text for a website**: keep **Alt text max length** at 125 and use `altText` as is; use `detailedCaption` for a longer description (`aria-describedby`, product copy drafts).
- **Screenshots, slides, posters, packaging**: turn on **Also read text in the image** to get the words exactly as written in `imageText`.
- **Long videos**: raise **Video: look at a frame every** to 5-10 s, or lower **Video: max captioned frames per video**.
- **Video Frame Extractor first** if you want exact scene-cut frames or thumbnails: pick its dataset here and its frames are captioned with their timestamps.

### Limits

- English output only.
- The model does not identify people (it describes them) and does not know brand names unless the brand is written on the product.
- Images up to 40 MB / 200 megapixels; SVG and HEIC are not supported (convert to JPG/PNG/WEBP).
- Videos up to 120 minutes / 2 GB by default (raise up to 600 minutes / 4 GB).
- Speed: about 4-5 seconds per image on the default 4 GB memory.

### Related Kanto Labs actors

- [Video Frame Extractor](https://apify.com/kantolabs/video-frame-extractor) - keyframes, scenes, thumbnails and evenly spaced frames from any video file. Caption its frames here.
- [Video OCR](https://apify.com/kantolabs/video-ocr) - every piece of on-screen text in a video (subtitles, captions, titles) with timestamps and SRT.
- [Video to LLM Context](https://apify.com/kantolabs/video-to-llm-context) - keyframes per scene + transcript, packaged for an LLM.
- [AI Image Upscaler 4x](https://apify.com/kantolabs/ai-image-upscaler) - sharpen and enlarge small images (Real-ESRGAN) before captioning.

### FAQ

#### Is this free image captioning?

The model is free and open source; the Actor charges $0.004 per captioned image to cover the cloud computer that runs it. Failures are free.

#### Can I use it for WCAG / accessibility alt text?

Yes - `altText` is written for screen readers (no "image of", at most 125 characters by default). Like any AI caption, review alt text for images where the exact meaning matters (charts, legal or medical images).

#### Does it send my images to OpenAI or Google?

No. The Florence-2 model runs inside the Actor on Apify's servers. Your images are only downloaded from the links you give.

#### Which languages?

Captions are written in English. Text in the image (OCR) is read best for English and other Latin-script text.

#### Can I caption Instagram or YouTube content?

No. Those platforms forbid downloading in their terms. Use your own files or files you are allowed to process.

#### Does it work from n8n, Make or Zapier?

Yes. Run the Actor (or a saved task) through the Apify integration and read the dataset; each row has `caption`, `altText`, `detailedCaption` and `tags`.

#### Something went wrong?

Every problem (404, a web page instead of an image, too large, unsupported format) gets a clear `error` in its row and is free. If a caption is clearly wrong, open an Issue with the image link and we will look at it.

# Actor input Schema

## `imageUrls` (type: `array`):

Direct links to images (JPG, PNG, WEBP, GIF, BMP, TIFF), one per line: your shop or CMS, S3/R2/GCS, a CDN, an Apify key-value store, Dropbox or Google Drive share links (anyone with the link). Pages of social platforms (Instagram, Pinterest, X, Facebook...) are refused. Remove the two sample photos to caption only your own.

## `videoUrls` (type: `array`):

Direct links to video files (MP4, MOV, WEBM, MKV...). Frames are looked at every few seconds, frames that look like the last captioned one are skipped for free, and the rest are captioned. You also get a timed scene list and a one-paragraph video summary. YouTube, TikTok, Instagram, Vimeo... links are refused.

## `datasetId` (type: `string`):

Chain from Kanto Labs Video Frame Extractor (its frames are captioned with their timestamps, plus a summary per video) or pick any dataset whose rows have an image link in `imageUrl` or `url`.

## `detailLevel` (type: `string`):

Length of `detailedCaption`. `detailed` = up to 3 sentences (most reliable). `paragraph` = up to 5 sentences with more about clothing, colours and background; later sentences are more often guesses. Same price.

## `altTextMaxLength` (type: `integer`):

Alt text is shortened to this many characters at a natural break. 125 is the common accessibility guideline for screen readers.

## `includeTags` (type: `boolean`):

Add `tags`: keywords taken from the description (objects, people, places, colours) for search, filters and SEO.

## `readImageText` (type: `boolean`):

Add `imageText` with the text written in the image (slides, screenshots, posters, packaging). Same price, slower.

## `frameIntervalSeconds` (type: `number`):

How often a video frame is looked at. Only frames that look different from the last captioned frame are captioned (and charged), so 1-2 s catches every scene without paying for repeats.

## `skipSimilarFrames` (type: `boolean`):

Skip (free) frames that look like the last captioned frame. Turn off to caption every frame at the chosen interval.

## `maxSecondsBetweenCaptions` (type: `integer`):

Even when the picture does not change, caption a frame at least this often, so long static shots still appear in the timeline.

## `maxFramesPerVideo` (type: `integer`):

Upper limit of captioned (charged) frames per video, also for Video Frame Extractor datasets. Caps the cost of one video at this many images.

## `maxVideoMinutes` (type: `integer`):

Longer videos are refused (free).

## `maxVideoSizeMb` (type: `integer`):

Larger files are refused (free).

## `downloadTimeoutSecs` (type: `integer`):

Give up on a video that does not download within this time (free).

## Actor input object example

```json
{
  "imageUrls": [
    "https://api.apify.com/v2/key-value-stores/aXcXwfChlioPWAUJv/records/sample-caption-football.jpg?signature=1JgTIIzZ1d7YgA9U3N97u",
    "https://api.apify.com/v2/key-value-stores/aXcXwfChlioPWAUJv/records/sample-caption-desk.jpg?signature=1IcPS7TS1OJG5tN2OfzIh"
  ],
  "detailLevel": "detailed",
  "altTextMaxLength": 125,
  "includeTags": true,
  "readImageText": false,
  "frameIntervalSeconds": 2,
  "skipSimilarFrames": true,
  "maxSecondsBetweenCaptions": 30,
  "maxFramesPerVideo": 60,
  "maxVideoMinutes": 120,
  "maxVideoSizeMb": 2000,
  "downloadTimeoutSecs": 600
}
```

# Actor output Schema

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

No description

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

No description

# 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 = {
    "imageUrls": [
        "https://api.apify.com/v2/key-value-stores/aXcXwfChlioPWAUJv/records/sample-caption-football.jpg?signature=1JgTIIzZ1d7YgA9U3N97u",
        "https://api.apify.com/v2/key-value-stores/aXcXwfChlioPWAUJv/records/sample-caption-desk.jpg?signature=1IcPS7TS1OJG5tN2OfzIh"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kantolabs/image-caption-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 = { "imageUrls": [
        "https://api.apify.com/v2/key-value-stores/aXcXwfChlioPWAUJv/records/sample-caption-football.jpg?signature=1JgTIIzZ1d7YgA9U3N97u",
        "https://api.apify.com/v2/key-value-stores/aXcXwfChlioPWAUJv/records/sample-caption-desk.jpg?signature=1IcPS7TS1OJG5tN2OfzIh",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("kantolabs/image-caption-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 '{
  "imageUrls": [
    "https://api.apify.com/v2/key-value-stores/aXcXwfChlioPWAUJv/records/sample-caption-football.jpg?signature=1JgTIIzZ1d7YgA9U3N97u",
    "https://api.apify.com/v2/key-value-stores/aXcXwfChlioPWAUJv/records/sample-caption-desk.jpg?signature=1IcPS7TS1OJG5tN2OfzIh"
  ]
}' |
apify call kantolabs/image-caption-generator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kantolabs/image-caption-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/6SF0aeOGyqh2zWANA/builds/lZtoLbu7daQkeofkD/openapi.json
