# Video Frame Extractor – Thumbnails, Scenes & Keyframes (`gazidev/video-frame-extractor`) Actor

Extract still frames from any video file URL (MP4, MOV, WebM, MKV, HLS): one frame every N seconds, N thumbnails, exact timestamps, scene changes or keyframes. JPEG/PNG/WebP links, timecodes, contact sheet and video metadata. Ready for AI vision. $1 per 1,000 frames.

- **URL**: https://apify.com/gazidev/video-frame-extractor.md
- **Developed by:** [Cemal Atakli](https://apify.com/gazidev) (community)
- **Categories:** Videos, AI, Developer tools
- **Stats:** 12 total users, 8 monthly users, 96.4% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 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.

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 – Thumbnails, Scenes & Keyframes

Turn any **video file URL** into **still images**: one frame every N seconds, N evenly spaced **thumbnails**, frames at **exact timestamps**, **scene changes** (shot detection) or all **keyframes**. Every frame is saved as **JPEG, PNG or WebP** with a public link, its **timecode** and size, and every video gets a **contact sheet** and its **metadata** (duration, resolution, fps, codecs, bitrate). The frames are ready to send to GPT-4o, Claude or Gemini vision, to an OCR step, or to your CMS.

- **$1 per 1,000 frames** + $0.002 per video. Popular frame extractors on the Store charge $2–$6 per 1,000 frames. Black frames, failed videos and invalid links are free.
- **Fast on long videos.** Interval, thumbnail and timestamp modes seek straight to each position with HTTP range requests: a frame per minute from a 15-minute 1080p film took 30 seconds in our tests, without downloading the whole file.
- **Scene detection** finds shot changes for video summaries, AI captioning and highlight reels; billed per minute scanned ($0.002) with a cost cap.
- **Any format ffmpeg reads:** MP4, MOV, M4V, WebM, MKV, AVI, MPEG-TS and HLS `.m3u8` streams, rotation handled, frames downscaled to your max width.

### What can I use it for?

- **Video thumbnails and posters** for websites, LMS, e-commerce product videos and video CMSs.
- **AI video understanding:** feed one frame every few seconds (or one per scene) to a vision model to describe, tag or moderate a video.
- **Content moderation and compliance:** sample frames from user uploads for review.
- **Dataset creation:** frames for computer-vision training, at a fixed rate or per shot.
- **Storyboards and contact sheets** for editors, reviewers and clients.

Video-platform pages (YouTube, TikTok, Instagram, Vimeo…) are deliberately **not supported**: use direct links to files you own or are allowed to process (your CDN, S3 / R2 / GCS bucket, Dropbox or Drive direct links, stock footage).

### Input

| Field | Description |
|---|---|
| `videoUrls`, `bulkText`, `datasetId` | Direct video file links |
| `mode` | `interval`, `count`, `timestamps`, `scenes`, `keyframes` |
| `intervalSec` / `frameCount` / `timestamps` | Settings for each mode (`90`, `12.5`, `00:01:30`) |
| `sceneThreshold`, `accurateScenes` | Scene sensitivity (0.3 default); keyframe-only (fast) or frame-exact detection |
| `startTime`, `endTime` | Only part of the video |
| `maxFramesPerVideo`, `skipBlackFrames` | Cap per video (100); drop black frames (free) |
| `imageFormat`, `quality`, `maxWidth` | `jpg` / `png` / `webp`, quality 85, max width 1280 px |
| `contactSheet` | One overview image per video (free) |
| `maxScanMinutes`, `maxDurationMinutes`, `maxConcurrency` | Cost cap for scanning modes (60 min); safety limit per video (240 min, 0 = none); videos in parallel |

```json
{
  "videoUrls": ["https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4"],
  "mode": "interval",
  "intervalSec": 5,
  "imageFormat": "jpg",
  "maxWidth": 1280
}
```

### Progress & limits

- **Results stream in.** Every frame is saved and added to the dataset the moment it is extracted, so the Output tab fills while the run is going; you can stop a run at any time and keep (and pay only for) what is already there.
- **Live progress.** The run status shows `frame 12/40, ETA 20s` per video (or `scanned 55%` in scene modes); the log prints the video length and an estimated time before extraction starts, and a progress line every 15 s.
- **Safety limits.** `maxFramesPerVideo` (100), `maxScanMinutes` (60, scene/keyframe modes) and `maxDurationMinutes` (240: only the first 4 hours of a video are used; 0 = no limit). Hosts that keep failing are given up after a few retries with a free error row instead of hanging.
- **Never times out silently.** The Actor stops about 30 s before the run timeout, keeps every frame saved so far, marks the affected video rows `"partial": true, "stopReason": "TIME_LIMIT"` and writes a `SUMMARY`. The same happens when your maximum total charge is reached (`MAX_CHARGE_REACHED`).
- **Bad input never fails the run.** Invalid values fall back to their defaults with a warning; invalid or unreachable URLs get a free error row.

### Output

Two row types in one dataset. The Output tab shows **Frames** (with image previews) and **Videos & contact sheets**.

Frame row (from a cloud run):

```json
{
  "type": "frame",
  "videoIndex": 0,
  "videoUrl": "https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4",
  "frameIndex": 12,
  "timestampSec": 65,
  "timecode": "00:01:05.000",
  "imageUrl": "https://api.apify.com/v2/key-value-stores/Wls5gg0pkbv7kDZhC/records/v000-frame-0012.jpg?signature=…",
  "imageKey": "v000-frame-0012.jpg",
  "width": 640, "height": 360, "bytes": 27924, "format": "jpg"
}
```

Video row:

```json
{
  "type": "video",
  "videoUrl": "https://test-videos.co.uk/vids/jellyfish/mp4/h264/720/Jellyfish_720_10s_5MB.mp4",
  "status": "ok",
  "mode": "interval",
  "durationSec": 10.01,
  "container": "QuickTime / MOV",
  "fileSizeBytes": 5241877,
  "bitrateKbps": 4189,
  "videoCodec": "h264", "width": 1280, "height": 720, "fps": 29.97,
  "hasAudio": false,
  "framesExtracted": 3,
  "contactSheetUrl": "https://api.apify.com/v2/key-value-stores/Wls5gg0pkbv7kDZhC/records/v002-contact-sheet.jpg?signature=…"
}
```

Error codes (free): `INVALID_URL`, `UNSUPPORTED_PLATFORM`, `HTTP_403`, `HTTP_404`, `HTTP_429`, `NOT_A_VIDEO`, `NO_VIDEO_STREAM`, `UNKNOWN_DURATION`, `TIMEOUT`, `NETWORK_ERROR`, `INTERNAL_ERROR`. Rows of videos stopped early carry `partial: true` and `stopReason` (`TIME_LIMIT`, `MAX_CHARGE_REACHED`, `SCAN_TIMEOUT`); a video stopped before its first frame has `status: "skipped"`. Image files live in the run's key-value store (retained like any Apify storage).

### Pricing

| Event | Price | When |
|---|---|---|
| Frame | **$0.001** ($1 per 1,000) | Each frame saved |
| Video | $0.002 | Each video that delivers at least one frame |
| Scene-scan minute | $0.002 | Per started minute scanned in `scenes` / `keyframes` mode |
| Run start | $0.00005 | |

Examples: 10 thumbnails from 100 videos = $1.20. Shot detection on a 10-minute video with 40 scenes = $0.002 + $0.02 + $0.04 = $0.062.

| Actor | Monthly users | Price |
|---|---|---|
| **This Actor** | – | **$1 / 1,000 frames** + $0.002 / video |
| automation-lab/video-thumbnail-frame-extractor | 60 | ≈ $6 / 1,000 frames |
| kantolabs/video-frame-extractor | 42 | $2 / 1,000 frames |
| frameprobe/video-scene-splitter | 26 | $0.02 / video minute + $0.01 / run |

Competitor figures are from the Apify Store on 2026-10-06.

### FAQ

**Why does scene detection miss some cuts?** By default it compares keyframes only, which is fast and cheap but cannot see cuts between two keyframes. Turn on `accurateScenes` for frame-exact cuts (slower, same price), or lower `sceneThreshold`.

**Can I use YouTube or TikTok links?** No. Those platforms' terms forbid downloading their videos. Use a direct file URL you are allowed to process.

**How long are the images kept?** They are stored in the run's default key-value store, which follows your Apify storage retention. Download them or copy them to your own storage if you need them longer.

**What about very large files?** Seeking modes read only the parts they need. Scanning modes download the file first (up to 2 GB; bigger files are read over HTTP) and scan only the requested range; `maxScanMinutes` caps it.

**I get HTTP_403 for a link that opens in my browser.** Some hosts block cloud/datacenter IPs. Enable *Proxy configuration* (Apify Proxy) and run again.

**Is this legal?** The Actor only processes the video URLs you provide and sends an honest User-Agent. Make sure you have the right to process those videos. Questions: iletisim.ortalamabiri@gmail.com.

### Use with AI agents / Apify MCP

Ask *"take a frame every 10 seconds from this video and describe what happens"*. Through the [Apify MCP server](https://mcp.apify.com) (`https://mcp.apify.com?actors=gazidev/video-frame-extractor`), Claude, ChatGPT, Cursor and other MCP clients can call it directly. Over the API: `POST https://api.apify.com/v2/acts/gazidev~video-frame-extractor/run-sync-get-dataset-items`.

### Related Actors

[Media Transcriber (audio & video to text)](https://apify.com/gazidev/media-transcriber) · [Image OCR](https://apify.com/gazidev/image-ocr) · [Image Metadata & EXIF Extractor](https://apify.com/gazidev/image-metadata-extractor) · [Website Screenshot](https://apify.com/gazidev/website-screenshot)

# Actor input Schema

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

Direct links to video files: MP4, MOV, WebM, MKV, AVI, M4V, TS, or HLS `.m3u8` playlists (your CDN, S3/R2 bucket, Dropbox direct link…). Pages of YouTube, TikTok, Instagram, Vimeo etc. are not supported.

## `bulkText` (type: `string`):

One URL per line or a CSV with a `url` column.

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

Chain after another Actor.

## `datasetField` (type: `string`):

Field name in that dataset.

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

`interval` and `count` seek directly to each timestamp (fast even on hour-long videos). `scenes` and `keyframes` scan the video and are billed per minute scanned.

## `intervalSec` (type: `number`):

For `interval` mode, e.g. 1 for one frame per second.

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

For `count` mode: frames spread evenly over the video (or the start–end range).

## `timestamps` (type: `array`):

For `timestamps` mode: seconds (`90`, `12.5`) or `HH:MM:SS(.ms)` / `MM:SS`.

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

For `scenes` mode: lower = more frames (0.2 sensitive, 0.4 only hard cuts).

## `accurateScenes` (type: `boolean`):

Off = compare keyframes only (fast, may miss cuts between keyframes). On = frame-exact cuts, slower. Same price.

## `startTime` (type: `string`):

Optional, e.g. `00:01:00`.

## `endTime` (type: `string`):

Optional, e.g. `00:05:00`.

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

Hard cap per video.

## `skipBlackFrames` (type: `boolean`):

Fades and black screens are dropped and not charged.

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

Output format of the frame images.

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

Higher = sharper and bigger files.

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

Frames wider than this are downscaled (aspect ratio kept). Never upscaled.

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

One overview image with up to 48 frames and their timecodes. Free.

## `maxScanMinutes` (type: `integer`):

Cost cap for scanning modes: longer videos are scanned only up to this length.

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

Safety limit: only this many minutes of each video (from `startTime`) are processed, so a huge file or endless stream cannot run for hours. 0 = no limit. The run also stops ~30 s before its timeout and keeps everything saved so far.

## `maxConcurrency` (type: `integer`):

Each video also uses up to 4 parallel seeks.

## `proxyConfiguration` (type: `object`):

Only for hosts that block datacenter IPs (HTTP 403).

## Actor input object example

```json
{
  "videoUrls": [
    "https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4"
  ],
  "datasetField": "url",
  "mode": "interval",
  "intervalSec": 30,
  "frameCount": 10,
  "sceneThreshold": 0.3,
  "accurateScenes": false,
  "maxFramesPerVideo": 100,
  "skipBlackFrames": true,
  "imageFormat": "jpg",
  "quality": 85,
  "maxWidth": 1280,
  "contactSheet": true,
  "maxScanMinutes": 60,
  "maxDurationMinutes": 240,
  "maxConcurrency": 2,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `frames` (type: `string`):

No description

## `videos` (type: `string`):

No description

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

No description

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

No description

## `summary` (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 = {
    "videoUrls": [
        "https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4"
    ],
    "intervalSec": 30
};

// Run the Actor and wait for it to finish
const run = await client.actor("gazidev/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://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4"],
    "intervalSec": 30,
}

# Run the Actor and wait for it to finish
run = client.actor("gazidev/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://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4"
  ],
  "intervalSec": 30
}' |
apify call gazidev/video-frame-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gazidev/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/nWfJ7BChT6EyTTMfe/builds/fjb7BJSIMPsNs0xRU/openapi.json
