# Video Frame Extractor & Scene Detector for AI (`earlytern/video-frame-extractor`) Actor

Extract frames from direct video links by count, interval, exact timestamps or scene changes. Get image links, timecodes and a contact sheet, with near-duplicate frames removed and not charged. Runs ffmpeg for you.

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

## Pricing

from $1.20 / 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 & Scene Detector for AI

Extract frames from any **direct link to a video file**: a set number of frames, one every few seconds, exact
timestamps, or every scene change. You get a public link to each image with its timecode, plus one contact sheet per
video. Near-duplicate frames are removed and not charged, so an AI model or a person has fewer images to read. No
ffmpeg to install, no server to run.

### What does Video Frame Extractor do?

- Reads video files from direct links: MP4, WebM, MOV, MKV, AVI, MPEG, MPEG-TS, FLV, WMV and Ogg.
- **Four ways to pick frames**: evenly spread by count, a fixed interval, exact timestamps, or scene changes with a
  score for each cut.
- **Skips similar frames**: a frame that looks almost the same as the one before is left out and free.
- **Contact sheet**: one free image showing the frames in a grid, handy for a quick look or a single AI request.
- Returns video length, size and codec, with each frame's time, timecode, size and image link.
- Reads a large file piece by piece, jumping to the frames it needs, when the video server allows it. Ogg files and
  files from servers that cannot send part of a file are downloaded once instead, up to the run's memory size.
- Tells you why a link failed, in plain words. Failed videos cost nothing.

### Who uses it?

- **AI workflows on n8n, Make or Zapier**: turn a video into a few images a vision model can read.
- **AI agents running in the cloud**, where ffmpeg cannot be installed.
- **Ad and video analysts**: review creative shot by shot from a contact sheet.
- **Dataset builders**: sample images from your own video collections.

### How to use it

1. Click **Try for free**.
2. In **Video links**, add one direct link per line, up to 100. The link must open the file itself, for example
   `https://example.com/ad.mp4`.
3. Choose a **Frame selection** and set its field: **Number of frames**, **Seconds between frames**, **Exact times** or
   **Scene sensitivity**.
4. Click **Start**. A short video takes seconds. Download the results as JSON, CSV or Excel, or use the API.

YouTube, TikTok, Instagram, Facebook, X, Vimeo, Twitch and other video platforms are refused, because their terms do
not allow automated downloads. Streaming playlists (HLS, DASH) are refused too.

### Use with AI agents (MCP)

AI assistants and AI agents can call this Actor through Apify's MCP server at [mcp.apify.com](https://mcp.apify.com)
or through the Apify API. The image links open without signing in, so an agent can pass them straight to a vision
model. Questions it answers well:

- "What happens in this video?" Use `mode: "count"` with `frameCount: 10`, then read `contactSheetUrl`.
- "Where are the cuts in this ad?" Use `mode: "scenes"` and read each frame's `timeSeconds` and `sceneScore`.
- "What is on screen at 1:30?" Use `mode: "timestamps"` with `timestamps: [90]`.

Only `videoUrls` is required. Keep `frameCount` low and leave **Skip similar frames** on, so answers stay short and cheap.
When `status` is `failed`, `errorReason` says what to change.

### Input example

```json
{
  "videoUrls": ["https://upload.wikimedia.org/wikipedia/commons/f/f3/Chen-Gackstatter_surface.webm"],
  "mode": "count",
  "frameCount": 3,
  "skipSimilarFrames": true,
  "contactSheet": true,
  "imageFormat": "jpg",
  "maxWidth": 1280
}
```

Use **Start at** and **End at** to read only part of a video. **Maximum file size** (500 MB unless you change it) refuses
larger files free of charge.

The example video is "Chen-Gackstatter surface" by Rectas, from
[Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Chen-Gackstatter_surface.webm), released under
[CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/).

### Output example

```json
{
  "videoUrl": "https://upload.wikimedia.org/wikipedia/commons/f/f3/Chen-Gackstatter_surface.webm",
  "status": "ok",
  "durationSeconds": 15.042,
  "width": 1080,
  "height": 1080,
  "codec": "vp9",
  "framesKept": 3,
  "framesSkippedSimilar": 0,
  "contactSheetUrl": "https://api.apify.com/v2/key-value-stores/aBcDeFgHiJkLmNoPq/records/sheet-0.jpg",
  "frames": [
    {
      "timeSeconds": 2.507,
      "timecode": "00:00:02.507",
      "imageUrl": "https://api.apify.com/v2/key-value-stores/aBcDeFgHiJkLmNoPq/records/f-0-2507.jpg",
      "sceneScore": null,
      "bytes": 22192
    },
    {
      "timeSeconds": 7.521,
      "timecode": "00:00:07.521",
      "imageUrl": "https://api.apify.com/v2/key-value-stores/aBcDeFgHiJkLmNoPq/records/f-0-7521.jpg",
      "sceneScore": null,
      "bytes": 21661
    },
    {
      "timeSeconds": 12.535,
      "timecode": "00:00:12.535",
      "imageUrl": "https://api.apify.com/v2/key-value-stores/aBcDeFgHiJkLmNoPq/records/f-0-12535.jpg",
      "sceneScore": null,
      "bytes": 22181
    }
  ],
  "note": null,
  "errorReason": null
}
```

Each video gives one row, also when it fails: `status` is then `failed` and `errorReason` says why, or `skipped` when your
maximum cost per run was reached first. `note` says if anything was left out of a good result.

Images stay available for as long as Apify keeps the run's storage, which depends on your plan. Download the ones you
want to keep.

### How much does it cost?

You pay only for frames you get. No start fee and no fee per video. Prices per 1,000 events, by Apify plan:

| Event | Free | Bronze | Silver | Gold and up |
|---|---|---|---|---|
| Frame delivered | $1.50 | $1.50 | $1.30 | $1.20 |
| Minute of video scanned (Scene changes mode only) | $5.00 | $5.00 | $4.50 | $4.00 |

- **Free of charge**: contact sheets, frames skipped as similar, and videos that could not be read.
- Scene changes mode reads the whole video, so each started minute is charged, plus each frame delivered.
- Set a **maximum cost per run** and the Actor stops before going over it. Videos it did not reach are listed as
  `skipped`.

Example on the Free plan: 1,000 frames cost $1.50. Finding scenes in a 10-minute video with 20 scenes costs
10 × $0.005 + 20 × $0.0015 = $0.08.

### FAQ

**Why is my link refused?** The link must return the video file itself. A page with a player on it is not a video file:
open the link in a browser, and if you see a website rather than the bare video, find the direct link to the file.

**Why does a video server refuse the Actor?** Some servers block automated requests, or only allow links for a short
time. The Actor tries three times, then reports the answer it got, free of charge. Check that the link opens without
signing in.

**Why did a large video fail on my own server?** Files over 100 MB are read piece by piece, which needs a server that
can send part of a file (byte ranges); most storage services and web servers can. When yours cannot, the Actor downloads
the whole file first, which works for files up to the run's memory size: give the run more memory for larger files.

**Can I use signed links from my cloud storage?** Yes, as long as they are still valid during the run. They appear in
the results as you entered them.

**Which frames count as similar?** The Actor compares each frame with the last one it kept, by pattern and brightness.
Exact times mode keeps every frame you ask for.

**Does scene detection find every cut?** It finds hard cuts well. Lower **Scene sensitivity** to catch slow fades, and
raise it if you get too many frames. The first frame of the part you read is always included.

**Does it identify people?** No. It takes images from your video and does not recognize faces or add names.

### Limits

- Up to 100 videos per run, processed one after another, and 500 frames per video.
- Files up to 2,000 MB, or 4,000 MB in Scene changes mode.
- Scene changes mode stops reading a single video after 30 minutes. Give the run more memory for long or high-resolution
  videos, since that also gives it more processor power.
- Links to private or internal addresses are refused.

### Is it legal?

The Actor processes the video files you link to and collects nothing else. You are responsible for having the right to
process those videos, and for how you use the images, including any people shown in them. Video platforms are refused
because their terms do not allow automated downloads.

### Questions and feedback

Open an issue on the **Issues** tab.

# Actor input Schema

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

Direct links to video files, one per line (MP4, WebM, MOV, MKV, AVI, MPEG, FLV, WMV or Ogg), up to 100. The link must open the file itself. YouTube, TikTok, Instagram and other video platforms are not supported. Only use videos you have the right to process.

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

Number of frames: evenly spread over the video. Every few seconds: one frame per interval. Exact times: the moments you list. Scene changes: the first frame and each cut, which reads the whole video and adds a fee per minute.

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

Frames per video in the Number of frames mode.

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

Used by the Every few seconds mode. A video gives at most 500 frames.

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

Seconds into the video, for example \[5, 12.5, 60]. Used by the Exact times mode, which keeps every frame you ask for.

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

Used by the Scene changes mode: how much the picture must change, from 0.01 to 1. Lower finds more scenes.

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

Leave out frames that look almost the same as the frame before. Skipped frames are free.

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

Add one free image showing the frames of the video in a grid (up to 100 of them).

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

JPG files are smaller; PNG files keep every detail.

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

Larger frames are scaled down to this width in pixels. Smaller frames keep their size.

## `startSeconds` (type: `number`):

Leave out everything before this second of the video.

## `endSeconds` (type: `number`):

Leave out everything after this second of the video.

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

Larger files are refused and not charged. Up to 2,000 MB, or 4,000 MB in the Scene changes mode.

## Actor input object example

```json
{
  "videoUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/f/f3/Chen-Gackstatter_surface.webm"
  ],
  "mode": "count",
  "frameCount": 3,
  "intervalSeconds": 10,
  "timestamps": [],
  "sceneThreshold": 0.3,
  "skipSimilarFrames": true,
  "contactSheet": true,
  "imageFormat": "jpg",
  "maxWidth": 1280,
  "maxFileSizeMb": 500
}
```

# Actor output Schema

## `results` (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://upload.wikimedia.org/wikipedia/commons/f/f3/Chen-Gackstatter_surface.webm"
    ],
    "frameCount": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("earlytern/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://upload.wikimedia.org/wikipedia/commons/f/f3/Chen-Gackstatter_surface.webm"],
    "frameCount": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("earlytern/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://upload.wikimedia.org/wikipedia/commons/f/f3/Chen-Gackstatter_surface.webm"
  ],
  "frameCount": 3
}' |
apify call earlytern/video-frame-extractor --silent --output-dataset

```

## MCP server setup

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