# Video Frame Extractor: Keyframes, Scenes & Thumbnails for AI (`data-done-right/video-frame-extractor`) Actor

Video frame extractor API: extract frames from video URLs as JPG, PNG or WebP. Get keyframes, scene detection cuts, evenly spaced frames, every-N-seconds frames or smart thumbnails, plus contact sheets and base64 for AI vision pipelines.

- **URL**: https://apify.com/data-done-right/video-frame-extractor.md
- **Developed by:** [Data Done Right](https://apify.com/data-done-right) (community)
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.44 / 1,000 frames

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

## Video Frame Extractor: Keyframes, Scenes & Thumbnails for AI

This **video frame extractor** turns a video file URL into a set of image files: **keyframes, scene changes, evenly spaced frames, a frame every N seconds, or one thumbnail**. Give it direct video links, get a JPG, PNG or WebP image for each frame, with its timestamp, ready for AI vision models, video search, dataset building and thumbnail generation. It runs ffmpeg, so there is no browser or API key to set up.

> **Rights and sources.** This Actor reads **direct video files** (mp4, webm, mov, mkv...) from a URL or from your own key-value store. It **does not download from YouTube, Vimeo, TikTok, Instagram or other streaming sites**, and it rejects those page links without charge. Use it only on videos you own or have the right to process.

### What can you extract from a video

- **Every N seconds:** one frame at 0 s, 2 s, 4 s... for a quick storyboard or sampling for AI.
- **N evenly spaced frames:** 10 frames spread across the whole video, whatever its length.
- **Keyframes (I-frames):** the frames the encoder stores in full. Fast, and a natural way to sample a video.
- **Scene detection:** a frame at every cut or scene change, using ffmpeg's scene score with a threshold you control.
- **Single thumbnail:** the frame at a time you choose, or the most representative frame of the first seconds.
- **Output images:** JPG, PNG or WebP, optional resize (width and/or height, aspect ratio kept, never upscaled), JPG/WebP quality.
- **Contact sheet:** one grid image per video with a timecode on every tile. Not charged.
- **Base64 for AI pipelines:** optional `imageBase64` in each row (off by default, size-capped).

### How to extract frames from a video

1. Paste one or more **direct video URLs** (or the keys of video files in a key-value store).
2. Pick a **mode** and its setting (interval, number of frames, scene threshold or thumbnail time) and a **max frames per video**.
3. Run it. Each frame is saved to the key-value store and listed in the dataset with its link and timestamp. The run summary (duration, resolution, fps, codec, frames returned per video) is in the `SUMMARY` record.

### Input example

```json
{
  "videoUrls": ["https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"],
  "mode": "everyNSeconds",
  "intervalSeconds": 2,
  "maxFrames": 5,
  "imageFormat": "jpg",
  "width": 1280,
  "contactSheet": true
}
```

Other modes: `"mode": "sceneChanges", "sceneThreshold": 0.3`, `"mode": "keyframes"`, `"mode": "evenlySpaced", "frameCount": 12`, `"mode": "thumbnail", "thumbnailTimestamp": 12.5`.

### Output

One dataset row per frame:

```json
{
  "videoUrl": "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4",
  "videoIndex": 1, "frameIndex": 2, "timestampSec": 2.0, "timecode": "00:00:02.000",
  "frameUrl": "https://api.apify.com/v2/key-value-stores/.../records/v01-frame-0002.jpg",
  "width": 960, "height": 540, "sizeBytes": 26328, "mode": "everyNSeconds",
  "imageFormat": "jpg", "key": "v01-frame-0002.jpg"
}
```

With a contact sheet the rows also carry `contactSheetUrl`; with `includeBase64` they carry `imageBase64`. The `SUMMARY` record in the key-value store lists, for each video, its duration, resolution, fps, codec, container, whether it has audio, how many frames were returned, and any error or note (for example "maxFrames reached", or why a video was rejected).

### Scene detection, keyframes and thumbnails: which mode to use

- **Scene changes** find the moments that look different from the frame before. Lower the threshold (0.1 to 0.2) for soft transitions, raise it (0.4 and up) to catch only hard cuts. If nothing changes, you still get the first frame and a note.
- **Keyframes** are placed by the encoder (often every 1 to 10 seconds), not by the content. They are fast to find and good for even coverage.
- **Every N seconds** and **evenly spaced** give predictable timestamps. `timestampSec` is the requested time; the image is the frame at that time.
- **Thumbnail** picks the frame at `thumbnailTimestamp`, or the most representative frame of the first seconds (`thumbnailWindowSeconds`, default 10) when you leave the time empty.

### Video frame extractor API

Call this Actor from the Apify API, Python, JavaScript, Make, Zapier or n8n and get frame image URLs as JSON. Example with the Apify Python client:

```python
from apify_client import ApifyClient

run = ApifyClient("<APIFY_TOKEN>").actor("data-done-right/video-frame-extractor").call(run_input={
    "videoUrls": ["https://example.com/clip.mp4"], "mode": "sceneChanges", "maxFrames": 20, "includeBase64": True})
```

Then read the frames from the run's default dataset. Set `includeBase64` to send the images straight to a vision model, or pass `frameUrl` if your model fetches images by URL.

### Use it in Claude, ChatGPT and Cursor

Connect Apify's MCP server (mcp.apify.com) and your AI assistant can look inside a video file itself. For example: "extract the scene changes from this mp4 and describe each scene". The only required input is a video URL (`videoUrls`).

### Use cases

- **AI and vision pipelines:** sample frames for GPT, Claude or Gemini vision, captioning, OCR, moderation or embeddings.
- **Thumbnails and previews:** poster images, storyboards and contact sheets for a video library or CMS.
- **Video search and datasets:** build image datasets from your own footage, with a timestamp for every frame.
- **Review and QA:** check a long recording quickly with one frame per scene or per minute.

### How much does it cost

Pay per event: **$0.0018 per frame** (`frame`), charged once for each frame image delivered. Example: 100 frames cost about $0.18. Contact sheets, the `SUMMARY` record, rejected links and failed videos are free. Apify Silver plans get 10% off, Gold and higher 20%. `maxFrames` caps what one video can cost, and if you set a maximum spend per run the Actor stops cleanly when it is reached and keeps the frames already delivered.

### FAQ

#### How do I extract frames from a video?

Paste a direct video URL, choose a mode (every N seconds, keyframes, scene changes, evenly spaced or thumbnail) and run. You get one image link per frame, with its timestamp, in the dataset.

#### Can it extract frames from a YouTube video?

No. It does not download from YouTube, Vimeo, TikTok, Instagram or other streaming sites, and those links are rejected with a clear message and no charge. Use a direct link to a video file you have the rights to, or put the file in a key-value store and use `videoKeys`.

#### What is the difference between keyframes and scene changes?

Keyframes are chosen by the video encoder at fixed intervals, so they give even coverage. Scene changes are chosen by comparing how different each frame looks from the one before, so they follow the content. Use scene detection when you want one image per shot.

#### Which video formats work?

MP4, MOV, WebM, MKV, AVI, FLV, MPEG, MPEG-TS and other common files, with any codec ffmpeg can decode. Streaming playlists (HLS .m3u8, DASH .mpd) are not supported.

#### Are there size limits?

By default a video can be up to 500 MB (up to 2 GB with `maxVideoSizeMb`) and 120 minutes (`maxDurationMinutes`). Files are streamed to disk, and each video has a timeout (`videoTimeoutSecs`). Links to private or internal network addresses are blocked.

#### How do I send the frames to an AI vision model?

Turn on `includeBase64` to get the image bytes in each row (frames above `base64MaxKb` are skipped, so lower the size with `width` or the quality), or use `frameUrl` for models that take image URLs. For public links use a named store (`outputStoreName`) so the frames stay available after the run.

#### How long are the frame links kept?

Frames are saved in the run's default key-value store, which expires after your plan's data retention period like other unnamed storages. Set `outputStoreName` to save them in a named store, which stays until you delete it.

#### Do I need an API key or login?

No.

### Related tools

- **All-in-One File Converter: PDF, Office, Images, Audio & Video** converts and compresses the videos before you extract frames.
- **Transcribe Audio to Text: Files, Podcasts & Video URLs** turns the audio of the same video into a transcript.
- **PDF to Text & Markdown Converter with Automatic OCR** reads text from the frames and documents you extract.

# Actor input Schema

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

Direct links to video files (mp4, webm, mov, mkv, avi...). Example: https://example.com/clip.mp4. Page URLs of YouTube, Vimeo, TikTok, Instagram and other streaming sites are rejected: use files you have the rights to.

## `videoKeys` (type: `array`):

Keys of video files already stored in a key-value store (this run's default store, or the one named below). Example: my-clip.mp4.

## `videoStore` (type: `string`):

Name or ID of the key-value store that holds the keys above. Leave empty for this run's default store.

## `mode` (type: `string`):

How frames are chosen: every N seconds, N evenly spaced frames, keyframes (I-frames), scene changes, or one thumbnail.

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

Mode 'Every N seconds': one frame every N seconds, starting at 0. Example: 2.

## `frameCount` (type: `integer`):

Mode 'N evenly spaced frames': how many frames to take, spread across the video. Limited by Max frames per video.

## `sceneThreshold` (type: `number`):

Mode 'Scene changes': 0.1 is very sensitive, 0.3 is a good default for cuts, 0.5 or more finds only hard cuts.

## `includeFirstFrame` (type: `boolean`):

Mode 'Scene changes': also return the opening frame so every scene, including the first, has an image.

## `thumbnailTimestamp` (type: `number`):

Mode 'Single thumbnail': the time of the frame, for example 12.5. Leave empty to let ffmpeg pick the most representative frame of the first seconds.

## `thumbnailWindowSeconds` (type: `number`):

Mode 'Single thumbnail' without a time: the best frame is chosen from the first N seconds.

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

Hard cap on frames returned for each video (and on what you are charged for it).

## `imageFormat` (type: `string`):

File format of the saved frames: JPG is smallest, PNG is lossless, WebP is a good middle ground.

## `imageQuality` (type: `integer`):

1 to 100. 85 is a good balance; ignored for PNG.

## `width` (type: `integer`):

Shrink frames to fit this width, keeping the aspect ratio. 0 = original size. Never upscales. Example: 1280.

## `height` (type: `integer`):

Shrink frames to fit this height, keeping the aspect ratio. 0 = original size. Use together with width to fit a box.

## `contactSheet` (type: `boolean`):

Also save one grid image per video with a timecode on every tile (up to 48 frames). Not charged.

## `contactSheetColumns` (type: `integer`):

Tiles per row in the contact sheet.

## `includeBase64` (type: `boolean`):

Adds an imageBase64 field with the frame bytes, ready for vision-model APIs. Off by default because it makes rows large.

## `base64MaxKb` (type: `integer`):

Frames larger than this are not inlined (imageBase64 stays empty). Lower the size or quality to fit.

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

Downloads above this size are rejected before they finish.

## `maxDurationMinutes` (type: `number`):

Longer videos are rejected without charge.

## `videoTimeoutSecs` (type: `integer`):

Applies to the download and, separately, to the frame extraction of each video.

## `outputStoreName` (type: `string`):

Optional key-value store name. Named stores are kept until you delete them, while a run's own store expires after your plan's retention period.

## Actor input object example

```json
{
  "videoUrls": [
    "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
  ],
  "mode": "everyNSeconds",
  "intervalSeconds": 2,
  "frameCount": 10,
  "sceneThreshold": 0.3,
  "includeFirstFrame": true,
  "thumbnailWindowSeconds": 10,
  "maxFrames": 5,
  "imageFormat": "jpg",
  "imageQuality": 85,
  "width": 0,
  "height": 0,
  "contactSheet": false,
  "contactSheetColumns": 4,
  "includeBase64": false,
  "base64MaxKb": 512,
  "maxVideoSizeMb": 500,
  "maxDurationMinutes": 120,
  "videoTimeoutSecs": 600
}
```

# Actor output Schema

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

All result items of this run (the default dataset), as JSON, CSV, Excel and more.

## `records` (type: `string`):

Key-value store records: the run summary, notes on any problems, and any files.

# 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": [
        "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
    ],
    "mode": "everyNSeconds",
    "intervalSeconds": 2,
    "maxFrames": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("data-done-right/video-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": ["https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"],
    "mode": "everyNSeconds",
    "intervalSeconds": 2,
    "maxFrames": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("data-done-right/video-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": [
    "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
  ],
  "mode": "everyNSeconds",
  "intervalSeconds": 2,
  "maxFrames": 5
}' |
apify call data-done-right/video-frame-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,data-done-right/video-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/3TJXJ4cy8kwarhZ3n/builds/V34EyjkM1fzfg9fhD/openapi.json
