# Video Thumbnail & Frame Extractor (`automation-lab/video-thumbnail-frame-extractor`) Actor

Extract timestamped thumbnails and still frames from uploaded or direct video files, with image files and source metadata ready for automation.

- **URL**: https://apify.com/automation-lab/video-thumbnail-frame-extractor.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Videos, Developer tools, Automation
- **Stats:** 3 total users, 3 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.12 / 1,000 frame extracteds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Video Thumbnail & Frame Extractor

Generate a **video thumbnail** or a timestamped sequence of still frames from video files. Supply anonymously reachable direct video URLs, upload one file, or read private files from an Apify key-value store. The Actor stores ready-to-download JPEG, PNG, or WebP images and returns the timestamp, dimensions, format, file size, codec, and source metadata for every successful frame.

This is a file-processing Actor. It does not scrape a webpage or resolve a YouTube, Vimeo, TikTok, or other platform page into a video URL.

### What can Video Thumbnail & Frame Extractor do?

- Create one representative thumbnail near the beginning of each video.
- Extract frames at exact timestamps such as 5, 15, and 30 seconds.
- Sample an entire video at a fixed interval for moderation or visual analysis.
- Resize images into a maximum bounding box while preserving aspect ratio.
- Produce JPEG, PNG, or WebP files with structured dataset metadata.
- Process public direct files, one uploaded file, or private KVS records.
- Continue past a bad source while returning an explicit failure record.

### Who is this video frame extractor for?

**Catalog teams** can create consistent preview images for product, property, course, or media catalogs. **Publishers** can generate several candidate cover frames for an editor. **Trust and safety teams** can sample videos before sending images to a vision model or human queue. **Developers and automation teams** can turn new videos into predictable image assets in scheduled Apify Tasks, Make, Zapier, n8n, or custom pipelines.

### Why use this Actor?

The output is more than a screenshot. Each charged result is a successfully extracted image stored in the run's key-value store, paired with typed metadata in the default dataset. Exact timestamps and interval sampling use the same output contract, which makes downstream processing simple. Downloads are bounded by redirects, timeouts, file size, and duration. Private and local-network URLs are rejected to reduce server-side request forgery risk.

### Quick start

1. Open the Actor input.
2. Add a direct video file URL under **Direct video URLs**, or upload one video.
3. Leave both timestamp fields empty to create one representative thumbnail.
4. Alternatively, add **Exact timestamps** or set a **Sampling interval**.
5. Choose JPEG, PNG, or WebP and set maximum dimensions.
6. Run the Actor.
7. Open **Frame metadata** for records and **Frame image files** to download images.

A working starter input is:

```json
{
  "videoUrls": [
    { "url": "https://media.w3.org/2010/05/sintel/trailer.mp4" }
  ],
  "timestampSeconds": [5, 15, 30],
  "outputFormat": "webp",
  "quality": 80,
  "maxWidth": 960,
  "maxHeight": 540,
  "maxFrames": 3,
  "maxFileSizeMb": 50,
  "maxDurationMinutes": 5
}
```

### Input parameters

| Field | Type | Default | Purpose |
| --- | --- | ---: | --- |
| `videoUrls` | array | `[]` | Direct, anonymously reachable HTTP(S) video files. |
| `uploadedVideo` | string | — | One file selected with Apify's upload editor. |
| `sourceKeyValueStoreId` | string | default store | Store containing private source videos. |
| `sourceKeys` | string\[] | `[]` | Binary video record keys in that store. |
| `timestampSeconds` | number\[] | `[]` | Exact non-negative timestamps for every video. |
| `intervalSeconds` | number | — | Sampling interval starting at 0 seconds. |
| `maxFrames` | integer | `20` | Per-video cap, from 1 to 100. |
| `outputFormat` | enum | `jpeg` | `jpeg`, `png`, or `webp`. |
| `quality` | integer | `85` | JPEG/WebP encoder quality from 1 to 100. |
| `maxWidth` | integer | `1280` | Maximum image width. |
| `maxHeight` | integer | `720` | Maximum image height. |
| `maxFileSizeMb` | integer | `250` | Per-video download/storage limit, up to 500 MB. |
| `maxDurationMinutes` | integer | `60` | Per-video duration limit, up to 180 minutes. |

Provide at least one source. You may combine URL, upload, and KVS sources in one run, up to 20 videos. Use either `timestampSeconds` or `intervalSeconds`, not both. Duplicate exact timestamps are removed. Every requested timestamp must be earlier than the video duration.

### Extracted frame data

| Field group | Fields |
| --- | --- |
| Source | `sourceType`, `source`, `sourceKey` |
| Status | `status`, `error`, `processedAt` |
| Frame identity | `frameNumber`, `timestampSeconds`, `timestamp` |
| Source media | `durationSeconds`, `sourceWidth`, `sourceHeight`, `sourceVideoCodec` |
| Generated image | `outputFileName`, `outputFormat`, `outputMimeType`, `outputWidth`, `outputHeight`, `outputBytes` |
| Storage | `storedFileKey`, `storedFileUrl` |

A failed source produces one uncharged dataset record with `status: "failed"`, a concise `error`, and null frame/file fields.

Successful frames have `status: "succeeded"` and trigger the configured `frame` event.

### Output example

```json
{
  "sourceType": "url",
  "source": "https://media.w3.org/2010/05/sintel/trailer.mp4",
  "sourceKey": null,
  "status": "succeeded",
  "error": null,
  "frameNumber": 1,
  "timestampSeconds": 5,
  "timestamp": "00:00:05.000",
  "durationSeconds": 52.208333,
  "sourceWidth": 854,
  "sourceHeight": 480,
  "sourceVideoCodec": "h264",
  "outputFileName": "video-001-frame-001-5.000s.webp",
  "outputFormat": "webp",
  "outputMimeType": "image/webp",
  "outputWidth": 854,
  "outputHeight": 480,
  "outputBytes": 4162,
  "storedFileKey": "frame-81f4a3719a-video-001-frame-001-5.000s.webp",
  "storedFileUrl": "https://api.apify.com/v2/key-value-stores/STORE_ID/records/frame-81f4a3719a-video-001-frame-001-5.000s.webp",
  "processedAt": "2026-01-15T12:00:00.000Z"
}
```

The local runtime may report `storedFileUrl` as null when no cloud store ID exists. Cloud runs return the API record URL.

### How much does it cost to extract video frames?

Pay-per-event pricing has a **$0.005 start fee** plus one `frame` event for each successfully extracted and stored image. Failed source records are not charged as frames.

| Plan | Price per frame |
| --- | ---: |
| Free | $0.0059846 |
| Bronze | $0.005204 |
| Silver | $0.0040591 |
| Gold / Platinum / Diamond | $0.0031224 |

On Bronze, one generated frame costs about **$0.010204 including the start fee**, 10 frames cost about **$0.05704**, and 100 frames cost about **$0.5254**. Apify compute and platform pricing are represented through the Actor's active pay-per-event configuration; check the live pricing panel for the plan applied to your account.

### Thumbnail selection tips

For a simple catalog thumbnail, leave `timestampSeconds` and `intervalSeconds` empty. The Actor chooses a frame near the beginning while avoiding the exact first instant where a video may be black. For editorial options, request a small list of meaningful timestamps. For moderation or machine vision, use an interval and a realistic `maxFrames` cap.

JPEG is usually smallest for photographic scenes. WebP can reduce transfer size while preserving quality. PNG is lossless but often much larger. Smaller maximum dimensions reduce processing time, storage, and downstream vision-model cost.

### Batch and private-file workflows

For private batches, put binary video records in an Apify key-value store and pass its ID in `sourceKeyValueStoreId` with record names in `sourceKeys`. Leave the ID empty to read from the run's default store. Do not place secret-bearing signed URLs in public task examples, source control, or logs.

Each output image is stored under a stable `frame-` key containing a digest of the source reference, source position, frame position, and timestamp. Use `storedFileKey` when another Actor works in the same store, or `storedFileUrl` to download the image over the API.

### API: cURL

Start a run and wait for its dataset items:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~video-thumbnail-frame-extractor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "videoUrls": [{"url":"https://media.w3.org/2010/05/sintel/trailer.mp4"}],
    "timestampSeconds": [5, 15],
    "outputFormat": "jpeg",
    "maxFrames": 2
  }'
```

For larger videos or many frames, use the asynchronous `/runs` endpoint, poll the run, then read the dataset and key-value store.

### API: JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/video-thumbnail-frame-extractor').call({
    videoUrls: [{ url: 'https://media.w3.org/2010/05/sintel/trailer.mp4' }],
    intervalSeconds: 10,
    maxFrames: 6,
    outputFormat: 'webp',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map(({ timestampSeconds, storedFileUrl }) => ({ timestampSeconds, storedFileUrl })));
```

### API: Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("automation-lab/video-thumbnail-frame-extractor").call(run_input={
    "videoUrls": [{"url": "https://media.w3.org/2010/05/sintel/trailer.mp4"}],
    "timestampSeconds": [5, 15, 30],
    "outputFormat": "png",
    "maxFrames": 3,
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
print([(item["timestampSeconds"], item["storedFileKey"]) for item in items])
```

### Use with Apify MCP

Add this Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/video-thumbnail-frame-extractor"
```

**Claude Desktop**, **Cursor**, and **VS Code** can use the same HTTP MCP server configuration in their MCP settings:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/video-thumbnail-frame-extractor"
    }
  }
}
```

Example prompts:

- "Extract JPEG frames at 5, 15, and 30 seconds from this direct video file and return the image URLs."
- "Sample this uploaded video every 20 seconds, capped at 12 WebP frames."
- "Create one 1280×720-bounded catalog thumbnail for each supplied direct MP4 URL."

### Automation patterns

- **Catalog ingestion:** trigger the Actor after a new media file lands, then write `storedFileUrl` to the catalog record.
- **Moderation:** sample frames at an interval, send only successful image URLs to a vision classifier, and retain timestamps for review.
- **Publishing:** request several exact moments, let an editor choose a cover image, then copy the chosen KVS record to permanent storage.
- **Quality control:** compare returned dimensions and byte sizes with publishing requirements before accepting an asset.

### Limits and failure behavior

The Actor accepts direct files, not ordinary video webpages. URLs must be anonymously reachable and must not require cookies, DRM, login, or custom request headers. Private, loopback, link-local, and local-network targets are rejected. Redirects are limited to five. Network errors, HTTP 429, and temporary server errors receive bounded retries; stable client errors do not.

A source may fail because the URL is a webpage, the response exceeds `maxFileSizeMb`, FFprobe finds no video stream, duration exceeds the configured limit, or a timestamp lies outside the video. Other valid sources continue. If every source fails, the run ends with a non-zero status after writing diagnostic records.

### Legality and responsible use

Process videos you own or are authorized to use. A publicly reachable URL does not automatically grant copyright or redistribution rights. Follow the source host's terms, access controls, privacy requirements, and applicable law. Do not use the Actor to bypass DRM, authentication, technical restrictions, or private networks. Review retention and access settings for generated frames that may contain people or sensitive material.

### FAQ

#### Can it extract a thumbnail from a YouTube page URL?

No. Supply an actual direct video file URL or use a separate authorized downloader/resolver first. This Actor intentionally does not claim platform-page resolution.

#### Why did my run say the source has no video stream?

The URL likely returned HTML, an access-denied response, audio, or another non-video object. Open the URL without login and confirm it downloads the video file itself.

#### Why is an exact timestamp rejected?

Every timestamp must be non-negative and strictly less than the probed duration. Reduce the timestamp or remove it and use default thumbnail selection.

#### Can I create a contact sheet?

The Actor returns separate image files and metadata. Use interval sampling, then combine selected output files in your downstream image workflow.

#### Are failed videos charged per frame?

No. The one-time start event still applies, but a `frame` event is emitted only after an image is extracted, stored, and prepared as a successful dataset item.

### Related automation-lab Actors

- [Video to Text Transcriber](https://apify.com/automation-lab/video-to-text-transcriber) converts direct video files into timestamped transcripts.
- [Bulk Image Format Converter](https://apify.com/automation-lab/bulk-image-format-converter) converts generated or source images among JPEG, PNG, WebP, and AVIF.
- [Bulk Image Compressor](https://apify.com/automation-lab/bulk-image-compressor) reduces image file size for publishing pipelines.

### Support checklist

When reporting a problem, include the run URL, whether the source was a direct URL/upload/KVS key, the configured timestamp or interval, and the failure record's `error`. Do not include private signed URLs or credentials in public messages.

# Actor input Schema

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

Anonymously reachable direct HTTP(S) video file URLs. Web pages and login-protected media are not supported.

## `uploadedVideo` (type: `string`):

Upload one video with Apify's file picker. For private batches, use sourceKeyValueStoreId and sourceKeys.

## `sourceKeyValueStoreId` (type: `string`):

Optional key-value store ID or name containing private input videos. Leave empty to read sourceKeys from the run's default store.

## `sourceKeys` (type: `array`):

Binary video record keys in the selected key-value store.

## `timestampSeconds` (type: `array`):

Exact non-negative timestamps to extract from every video. Leave empty for one representative thumbnail near the start. Cannot be combined with intervalSeconds.

## `intervalSeconds` (type: `number`):

Extract frames at 0, interval, 2 × interval, and so on, capped by maxFrames. Cannot be combined with timestampSeconds.

## `maxFrames` (type: `integer`):

Safety cap for exact timestamps or interval sampling.

## `outputFormat` (type: `string`):

Image format used for every extracted frame.

## `quality` (type: `integer`):

Quality from 1 to 100 for JPEG and WebP. PNG uses lossless compression.

## `maxWidth` (type: `integer`):

Maximum frame width in pixels. Frames preserve aspect ratio and are never enlarged beyond this box.

## `maxHeight` (type: `integer`):

Maximum frame height in pixels. Frames preserve aspect ratio and are never enlarged beyond this box.

## `maxFileSizeMb` (type: `integer`):

Reject each video when its downloaded or stored size exceeds this limit.

## `maxDurationMinutes` (type: `integer`):

Reject each video when its probed duration exceeds this limit.

## Actor input object example

```json
{
  "videoUrls": [
    {
      "url": "https://media.w3.org/2010/05/sintel/trailer.mp4"
    }
  ],
  "sourceKeys": [],
  "timestampSeconds": [],
  "maxFrames": 20,
  "outputFormat": "jpeg",
  "quality": 85,
  "maxWidth": 1280,
  "maxHeight": 720,
  "maxFileSizeMb": 250,
  "maxDurationMinutes": 60
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing one record per extracted frame plus per-source failure records.

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

Key-value store containing generated JPEG, PNG, or WebP frame images.

# 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 = {
    "videoUrls": [
        {
            "url": "https://media.w3.org/2010/05/sintel/trailer.mp4"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/video-thumbnail-frame-extractor").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 = { "videoUrls": [{ "url": "https://media.w3.org/2010/05/sintel/trailer.mp4" }] }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/video-thumbnail-frame-extractor").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 '{
  "videoUrls": [
    {
      "url": "https://media.w3.org/2010/05/sintel/trailer.mp4"
    }
  ]
}' |
apify call automation-lab/video-thumbnail-frame-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/video-thumbnail-frame-extractor"
        }
    }
}

```

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/aiRRZDn8uV5FmDJEd/builds/jxaPuyF9tZrB4B4wG/openapi.json
