# Video Frame Extractor: Scenes, Keyframes & On-Screen Text (`yodeling_november/video-frame-extractor`) Actor

🎞️ Turn any video file into still frames: one per scene (shots and slides, with fades, blank frames and repeats skipped), every N seconds, N evenly spaced or at exact times. Images ready for AI vision, a contact sheet, and optional OCR of on-screen text. Pay per frame.

- **URL**: https://apify.com/yodeling_november/video-frame-extractor.md
- **Developed by:** [João Fernandes](https://apify.com/yodeling_november) (community)
- **Categories:** Videos, AI, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 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: Scenes, Keyframes & On-Screen Text

**Turn any video file into the still frames that matter, ready for AI vision, thumbnails and search.**

Give this Actor links to video files, or upload them, and get back images: **one frame per scene** (every shot, every slide, every new view), a frame **every N seconds**, **N frames evenly spaced**, or frames **at the exact times** you list. Fades, blank frames and repeated pictures are skipped, and you are not charged for them. Each video also gets a **contact sheet**, and you can have the **text on screen read with OCR** in 27 languages.

### ✨ Why use it

- 🎬 **One frame per scene, not one per second:** cuts, fades through black, dissolves and crossfades are recognised, so you get each shot or slide once, taken where the picture is still, not mid-motion and not half-blended.
- 🧹 **No duplicates, no black frames:** near-identical frames (a static shot, a slide shown twice, a shot that comes back) and blank frames are left out. **Skipped frames are free.**
- 🖼️ **Images ready for AI vision:** JPEG, WebP or PNG, scaled to 1,280 px by default (the size vision models work best with), with a link to each image and its time code.
- 🔤 **On-screen text (OCR):** slides, titles, captions, labels and credits read into a `text` field, in 27 languages. Only words the OCR is confident about are kept.
- 🗂️ **Contact sheet:** every frame of a video in one image, with time codes. Free.
- 📱 **Any video file:** MP4, MOV, WebM, MKV, AVI, M4V, MPEG-TS and more, H.264, HEVC, VP9, AV1. Phone videos are turned upright.
- 🛡️ **Robust:** a broken file, a web page instead of a video or a dead link becomes a failed row with a clear message, is not charged, and does not stop the run.
- 💸 **Pay per frame:** $2 per 1,000 frames, no subscription.

### 🎞️ Which frames

| Mode | What you get | Good for |
|---|---|---|
| **One per scene** (default) | The stillest frame of every shot, slide or new view. *Sensitivity* low / medium / high; frame from the *start*, *middle* or *end* of each scene. | AI video analysis, storyboards, slide decks from recordings, ad creatives |
| **Every N seconds** | A frame every N seconds (0.5 is two a second), duplicates skipped. | Timelines, monitoring, sampling for datasets |
| **N frames evenly spaced** | N frames spread across the video. | Thumbnails, previews, quick summaries |
| **At exact times** | Frames at the times you list (`90`, `1:30`, `1:02:03.5`). | Known moments, chapters, QA |

A *Start time* and *End time* limit any mode to part of the video.

### 💡 Use cases

- **AI vision and video understanding:** send a few representative frames to a vision model instead of the whole video: cheaper, faster, and nothing important left out.
- **Lectures, webinars and screen recordings:** get each slide once (use *Frame from each scene: End* for slides that build up), with its text, to make notes, search or a PDF.
- **Ads and social videos:** break creatives into their shots for review, competitor research or tagging.
- **Thumbnails and previews:** pick the best stills for a video page or a catalogue.
- **Content review and moderation:** check what a video shows without watching it.
- **Datasets:** sample frames for labelling or training, without near-duplicates.

### 🚀 How to use it

1. Click **Try for free**.
2. Paste links to your video files in *Video URLs*, or upload files.
3. Pick *Which frames*. For slides or text on screen, turn on *Read on-screen text (OCR)*.
4. Run it. Each frame is a row with its image link, time and (with OCR) text. Download as **JSON, CSV or Excel**, open the images from the **Storage** tab, or read everything through the **Apify API**, Make, Zapier or n8n.

### 📥 Input examples

One frame per scene (the default):

```json
{ "videoUrls": ["https://images-assets.nasa.gov/video/A1Launch/A1Launch~large.mp4"] }
```

A recorded presentation: one frame per slide, the finished slide, with its text:

```json
{ "videoUrls": ["https://example.com/webinar.mp4"], "framePosition": "end", "ocr": true, "ocrLanguages": ["eng", "deu"] }
```

A frame every 5 seconds of the first two minutes, as WebP:

```json
{ "videoUrls": ["https://example.com/clip.mp4"], "mode": "interval", "intervalSeconds": 5, "endTime": "2:00", "imageFormat": "webp" }
```

Frames at exact times:

```json
{ "videoUrls": ["https://example.com/match.mp4"], "mode": "timestamps", "timestamps": ["0:45", "12:03", "1:02:30.5"] }
```

### 📤 Output

One row per frame:

```json
{
  "videoUrl": "https://images-assets.nasa.gov/video/A1Launch/A1Launch~large.mp4",
  "fileName": "A1Launch~large.mp4",
  "status": "ok",
  "mode": "scenes",
  "frameIndex": 3,
  "framesInVideo": 14,
  "scenesFound": 14,
  "timestamp": 6.6,
  "timecode": "00:00:06.600",
  "sceneStart": 5.4,
  "sceneEnd": 8.0,
  "imageUrl": "https://api.apify.com/v2/key-value-stores/.../records/video-001-frame-0003.jpg",
  "imageFormat": "jpg",
  "width": 1280,
  "height": 720,
  "fileSizeBytes": 118234,
  "contactSheetUrl": "https://api.apify.com/v2/key-value-stores/.../records/video-001-contact-sheet.jpg",
  "videoDuration": 59.6,
  "videoWidth": 1920,
  "videoHeight": 1080,
  "videoFps": 30,
  "videoCodec": "h264",
  "extractedAt": "2026-10-21T21:48:56Z"
}
```

With OCR on, rows also have `text` (lines and paragraphs as on screen) and `textConfidence` (0-100). A video that could not be read gives one row with `status: "failed"` and the reason in `message`. The dataset has three views: **Frames** (with previews), **On-screen text** and **Videos** (one contact sheet per video).

### 💸 Pricing

Pay per event, no subscription:

| Event | Price | Charged when |
|---|---|---|
| **Frame** | $0.002 ($2 per 1,000) | A frame is returned. |
| **OCR frame** | $0.002 | A frame is read with OCR (only with OCR on). |
| **Scene detection minute** | $0.004 | Each started minute of video analysed in *One per scene* mode, counted at Full HD (a 4K minute counts as 4). |

Free: skipped duplicates and blank frames, failed videos, and the contact sheet. Higher Apify plans get 10-20% off.

**Example:** the one-minute NASA launch video above gives 14 frames in *One per scene* mode: 14 × $0.002 + 1 × $0.004 = **$0.032**. A 30-minute webinar with 40 slides, with OCR: 40 × ($0.002 + $0.002) + 30 × $0.004 = **$0.28**.

Set a *maximum cost per run* and the Actor stops before it: it never starts a video it cannot pay at least one frame for.

### ❓ FAQ

**Can I use YouTube, TikTok, Instagram or Vimeo links?**
No. Those are web pages that play a video, not video files, and their terms do not allow downloading. Use a direct link to the file (a cloud storage link, a CDN link, your own server) or upload the file.

**How big can a video be?**
Up to 1 GB by default; raise *Max video size* up to 8 GB (for files of several GB, give the run more memory). Long videos work too: for videos of an hour or more, raise *Timeout per video* and the run's own timeout. Live streams and streaming playlists (HLS, DASH) are not supported.

**How are scenes detected?**
The video is sampled five times a second and each sample is compared with the one before and the one a second earlier, block by block. Sharp changes are cuts; settled-before, settled-after changes are fades and dissolves; black or single-colour stretches separate scenes; a slow change of exposure is ignored. From each scene, the stillest and clearest frame is returned.

**How good is the OCR?**
It reads printed text well: slides, titles, captions, signs, credits, screen recordings. Handwriting, stylised logos, tiny text and text over busy pictures may be missed or partly read. Words with low confidence are dropped rather than guessed.

**Where are the images stored, and for how long?**
In the run's key-value store, with a link in each row. They are kept for as long as your Apify plan keeps run storage; download them or copy them elsewhere if you need them longer.

**Is my video kept?**
No. Videos are downloaded into the run's temporary disk, processed and deleted when the run ends. Only the frames and the dataset you asked for are saved, in your own Apify storage.

### 🧩 Notes

- The input example video is "Artemis I Launch Collage" by NASA (public domain).
- Frame extraction uses ffmpeg; OCR uses Tesseract.
- Questions or ideas? Open an issue on the Actor's **Issues** tab.

# Actor input Schema

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

Direct links to video files: MP4, MOV, WebM, MKV, AVI, M4V, MPEG-TS and more (anything ffmpeg reads). Links to video pages (YouTube, TikTok, Instagram, Vimeo...) are not video files and are not supported.

## `files` (type: `array`):

Or upload video files from your computer. They are stored in one of your key-value stores and read from there.

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

One per scene: one frame for every shot, slide or new view, with fades, blank frames and repeats skipped. Every N seconds, N frames evenly spaced, or at the exact times you list.

## `sceneSensitivity` (type: `string`):

With 'One per scene'. Medium finds every cut and every new slide. Low keeps only clear changes of picture. High also catches small changes, such as one more bullet on a slide.

## `framePosition` (type: `string`):

With 'One per scene': the stillest frame near the start, the middle or the end of each scene. For slides that build up bullet by bullet, 'End' shows the finished slide.

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

With 'Every N seconds': time between frames. Decimals work (0.5 is two frames a second).

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

With 'N frames evenly spaced': how many frames across the video (or across the time range below).

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

With 'At exact times': times in seconds (90 or 90.5) or as mm:ss / hh:mm:ss (1:30, 1:02:03.5).

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

Only use the video from this time (seconds or mm:ss). Empty: from the beginning.

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

Only use the video up to this time (seconds or mm:ss). Empty: to the end.

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

Upper limit of frames returned for one video. With 'One per scene', the longest scenes are kept.

## `skipDuplicates` (type: `boolean`):

Leave out a frame when it looks the same as one already returned (a static shot, a slide shown twice, a shot that comes back). You are not charged for skipped frames. Not used with 'At exact times'.

## `skipBlankFrames` (type: `boolean`):

Leave out black, white and single-colour frames (fades, leaders). 'One per scene' always skips them; 'At exact times' never does.

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

JPEG is small and works everywhere, WebP is smaller still, PNG is lossless (and large).

## `maxImageSize` (type: `integer`):

Longest side of the saved images; the aspect ratio is kept. 1280 suits AI vision models. 0 keeps the video's own size.

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

JPEG and WebP quality, from 1 to 100.

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

Also save one image with all the frames of each video in a grid, with their time codes (up to 100 frames). Free.

## `ocr` (type: `boolean`):

Read the text in each frame (slides, titles, captions, labels) into the 'text' field. Billed per frame as 'OCR frame'.

## `ocrLanguages` (type: `array`):

Languages of the text on screen. Pick only the ones you need: each extra language makes OCR slower.

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

Larger videos are skipped and reported as failed rows (not charged).

## `timeoutPerVideoSecs` (type: `integer`):

A video that takes longer returns the frames found so far, marked 'partial'.

## Actor input object example

```json
{
  "videoUrls": [
    "https://images-assets.nasa.gov/video/A1Launch/A1Launch~large.mp4"
  ],
  "mode": "scenes",
  "sceneSensitivity": "medium",
  "framePosition": "middle",
  "intervalSeconds": 10,
  "frameCount": 10,
  "maxFramesPerVideo": 100,
  "skipDuplicates": true,
  "skipBlankFrames": true,
  "imageFormat": "jpg",
  "maxImageSize": 1280,
  "imageQuality": 85,
  "contactSheet": true,
  "ocr": false,
  "ocrLanguages": [
    "eng"
  ],
  "maxVideoSizeMb": 1024,
  "timeoutPerVideoSecs": 1800
}
```

# Actor output Schema

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

One row per frame: image link, time, scene and on-screen text.

## `images` (type: `string`):

The frame images and contact sheets, in the run's key-value store.

# 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://images-assets.nasa.gov/video/A1Launch/A1Launch~large.mp4"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("yodeling_november/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://images-assets.nasa.gov/video/A1Launch/A1Launch~large.mp4"] }

# Run the Actor and wait for it to finish
run = client.actor("yodeling_november/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://images-assets.nasa.gov/video/A1Launch/A1Launch~large.mp4"
  ]
}' |
apify call yodeling_november/video-frame-extractor --silent --output-dataset

```

## MCP server setup

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