Video Frame Extractor (Apify Actor)

Extract frames from video and make video thumbnails. Give a direct MP4 URL, get a JPG/PNG screenshot every N seconds, N evenly spaced frames, or at scene changes. Optional contact sheet, one dataset row per frame. $0.0008 per frame. Runs as an Apify Actor, callable from MCP clients via the Apify MCP server (https://mcp.apify.com/?actors=pennydata/video-frame-extractor).

Hosted MCP Server

npx add-mcp 'https://mcp.apify.com/?actors=pennydata/video-frame-extractor'

Installs into Claude Code, Codex, Cursor and more

Documentation

Video Frame Extractor: Extract Frames from Video URL to JPG (pennydata/video-frame-extractor) Actor

Extract frames from a video URL: give a direct MP4 link, get a JPG/PNG thumbnail every N seconds, N evenly spaced frames, or at scene changes. One dataset row per frame, optional contact sheet. $0.0008 per frame.

Pricing

from $0.56 / 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?

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 section below.

Each client library has reference documentation the quickstart does not restate: JavaScript/TypeScript (npm install apify-client) and Python (pip install apify-client).

README

Extract Frames from a Video URL: MP4 to JPG Every N Seconds

Paste a direct video link, get JPG/PNG thumbnails back, one dataset row per frame. Sample output for a 5-second MP4 with intervalSeconds: 2:

timestampSecframeUrlframeWidthframeHeight
0.../records/v1-t0.jpg960540
2.../records/v1-t2000.jpg960540
4.../records/v1-t4000.jpg960540

Turn video file URLs into still images. Pick a mode: a frame at N-second intervals, N evenly spaced frames, or a frame at each scene change. Each frame is saved as JPG or PNG with a public URL, and you get one dataset row per frame (video URL, timestamp, frame URL, width, height). Optionally get a contact sheet (one grid image per video).

$0.0008 per frame saved ($0.80 per 1,000), failed videos and the contact sheet are free; input is a direct video file URL, no API key or proxy setup needed. Click Start: the prefilled input (a public sample MP4, 3 evenly spaced JPG frames) runs as is and costs at most $0.0024. New Apify accounts include free monthly credit.

You are charged only for frames that were extracted and saved. Failed downloads, undecodable files and videos with no frames found are free, and so is the contact sheet.

Not supported: YouTube, Vimeo, TikTok or other page links. Such a URL returns one free row {status: "not_a_video_file", hint: "pass a direct .mp4/.webm link"} and is never charged. Link the video file itself (mp4, webm, mov, mkv ...). Only use videos you have the right to process.

Input example

{
  "videoUrls": ["https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"],
  "mode": "interval",
  "intervalSeconds": 2,
  "format": "jpg",
  "contactSheet": true,
  "maxCostUsd": 0.5
}
FieldMeaning
videoUrlsDirect http(s) links to video files (required).
modeinterval (default), count, scene or timestamps.
timestampsExact times to grab: seconds (1, 3.5) or mm:ss / hh:mm:ss. Filling it switches to exact-timestamp mode.
startTime / endTimeWindow for interval and count modes (seconds or hh:mm:ss).
intervalSecondsMode interval: seconds between frames (default 10).
frameCountMode count: evenly spaced frames per video (default 8).
sceneThresholdMode scene: 0.3 default. Lower finds more cuts.
formatjpg (default) or png.
maxWidthScale down frames wider than this (default 1280). Never scaled up.
maxFramesPerVideoUpper limit in any mode (default 100).
contactSheetAlso save a grid image per video (free).
downloadZip / maxZipMbAlso save all frames of the run as one frames.zip (free); URL in zipUrl on every frame row and in the run output. Frames beyond maxZipMb (default 100) are left out of the ZIP only.
maxCostUsdOptional spending cap: the run stops once that many frames are extracted.

Output

One dataset row per frame:

{
  "videoUrl": "https://example.com/clip.mp4",
  "status": "ok",
  "type": "frame",
  "timestampSec": 2,
  "frameUrl": "https://api.apify.com/v2/key-value-stores/<id>/records/v1-t2000.jpg",
  "width": 960,
  "height": 540,
  "durationSec": 5.06
}

The contact sheet is a row with type: "contact-sheet". Failed or skipped videos get a row with status: "error" or "skipped" and the reason; they are never charged. Every row has a status field (ok, error, skipped) and failed rows carry the error message. At the end of each run an OUTPUT record (also saved as SUMMARY) in the key-value store lists videos succeeded/failed, rows by status, frames charged, billed events and cost, so you never have to count rows to see what happened.

Pricing (pay per event)

EventPrice
frame-extracted: one frame saved with a public URL$0.0008

100 frames cost $0.08. Apify's standard platform usage rules apply on top of the event price as shown in the Console. A frame is charged only after it is saved.

Notes

  • Interval and count modes seek to each timestamp, so frames are exact to the nearest decodable frame. Scene mode uses ffmpeg's scene score (select=gt(scene,X)); tune sceneThreshold for your footage. If none is found the video is reported as failed and not charged.
  • Private network addresses are blocked. Files up to 1.5 GB. Frame URLs live in the run's key-value store and follow your Apify storage retention.
  • Built with ffmpeg. This Actor was written with the help of an AI (Claude, by Anthropic). It is not affiliated with any video host.

Switching from another video frame extractor

Drop-in compatible with seemuapps/video-frame-extractor input. Paste the input you already use and change only the actor ID. The video link is accepted under any of these keys, as a list, a single string, or {url} objects:

Other actor's inputUsed here as
videoUrl, url (single link)videoUrls
videoUrls, urls, startUrls (list)videoUrls
everySeconds (tidydata)intervalSeconds
outputFormatformat
contactSheetColumns, proxyMode, maxDownloadMbPerVideoaccepted, ignored
frameCount, intervalSeconds, startTime, endTime, timestamps, maxWidth, sceneThreshold, contactSheetsame names here

tidydata/video-frame-extractor (checked 2026-10-22). Its public input (videoUrls, mode interval/count, everySeconds, frameCount, maxFramesPerVideo, format, maxWidth) runs here unchanged. Differences: the default interval here is 10 s (theirs 2 s), so set everySeconds if you relied on their default, and the default cap is 100 frames per video (theirs 200). Price: this actor charges $0.0008 per saved frame; tidydata's listed price is $0.001 per frame, and Apify shows it rising to $0.002 on 2026-11-05. Check both Store pages for current prices.

Input is compatible; output rows use this actor's own field names (videoUrl, frameUrl, timestampSec), so check your downstream mapping once. Direct video file links only.

Related actors

Every frame row also carries the video's durationSeconds, width, height, fps and videoCodec (frame pixel size is frameWidth/frameHeight); the SUMMARY record lists them per video under videoMeta.


Other languages

Español: Extraer fotogramas de un vídeo (MP4 a JPG)

Extrae imágenes de un vídeo a partir de su URL: un fotograma cada N segundos, un número fijo de fotogramas o cambios de escena, en JPG o PNG con URL pública. Sin instalar ffmpeg. Precio: 0,0008 USD por fotograma extraído; los vídeos que fallan no se cobran y la hoja de contactos es gratis.

Ejemplo de entrada:

{"videoUrls":["https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"],"mode":"interval","intervalSeconds":10}

Português: Extrair quadros de um vídeo (MP4 para JPG)

Extraia imagens de um vídeo a partir da URL: um quadro a cada N segundos, um número fixo de quadros ou mudanças de cena, em JPG ou PNG com URL pública. Sem instalar o ffmpeg. Preço: US$ 0,0008 por quadro extraído; vídeos que falham não são cobrados e a folha de contatos é grátis.

Exemplo de entrada:

{"videoUrls":["https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"],"mode":"interval","intervalSeconds":10}

Deutsch: Frames aus einem Video extrahieren (MP4 zu JPG)

Extrahiert Bilder aus einem Video per URL: ein Frame alle N Sekunden, eine feste Anzahl von Frames oder bei Szenenwechseln, als JPG oder PNG mit öffentlicher URL. ffmpeg muss nicht installiert werden. Preis: 0,0008 USD pro extrahiertem Frame; fehlgeschlagene Videos kosten nichts, das Kontaktblatt ist kostenlos.

Beispieleingabe:

{"videoUrls":["https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"],"mode":"interval","intervalSeconds":10}

I built this and earn from its runs. Written with AI assistance (Claude).

Actor input Schema

videoUrls (type: array):

Direct http(s) links to video files (mp4, webm, mov, mkv ...). Pages such as YouTube or Vimeo are not supported; link the file itself.

mode (type: string):

interval: one frame every N seconds. count: N evenly spaced frames. scene: a frame at each scene change. timestamps: only the times in Timestamps (also used automatically when Timestamps is filled).

intervalSeconds (type: number):

Mode interval: seconds between frames.

everySeconds (type: number):

Alias of Interval (seconds), for inputs written for tidydata/video-frame-extractor. Interval wins if both are set.

frameCount (type: integer):

Mode count: number of evenly spaced frames per video.

sceneThreshold (type: number):

Mode scene: 0.3 is a good start. Lower finds more cuts, higher finds only hard cuts.

timestamps (type: array):

Exact times to grab, in seconds (1, 3.5) or mm:ss / hh:mm:ss. One frame per timestamp, per video. Times past the end of a video are skipped.

startTime (type: string):

Interval and count modes: only use the part of the video after this time (seconds or hh:mm:ss). Default: from the start.

endTime (type: string):

Interval and count modes: only use the part of the video before this time (seconds or hh:mm:ss). Default: to the end.

format (type: string):

jpg is smaller; png is lossless.

maxWidth (type: integer):

Frames wider than this are scaled down, keeping the aspect ratio. Never scaled up.

maxFramesPerVideo (type: integer):

Upper limit for any mode. Only extracted frames are charged.

contactSheet (type: boolean):

Also save one grid image per video with all its frames (free). Its URL is in the dataset row with type contact-sheet.

downloadZip (type: boolean):

Also save all extracted frames of the run as one frames.zip in the key-value store (free). Its URL is in the zipUrl field of every frame row and in the run output.

maxZipMb (type: integer):

Only with Bundle frames as ZIP: frames beyond this total size are left out of the ZIP (they stay in the dataset and store, and are still charged).

maxCostUsd (type: number):

Spending guard: the run stops once this many frames worth of cost are extracted. Leave empty for no cap.

videoUrl (type: string):

Same as Video file URLs, for one link; accepted so inputs saved for other video frame extractors run unchanged.

url (type: string):

Same as Video file URLs, for one link (alias).

urls (type: array):

Same as Video file URLs (alias).

startUrls (type: array):

Same as Video file URLs (alias). Strings or {url} objects.

outputFormat (type: string):

Compatibility with seemuapps/video-frame-extractor input; same as format. If set, it overrides format.

contactSheetColumns (type: integer):

Accepted so rival input runs unchanged; the contact sheet layout is fixed here.

proxyMode (type: string):

Accepted so rival input runs unchanged; no proxy is used.

maxDownloadMbPerVideo (type: integer):

Accepted so rival input runs unchanged; the file size cap is fixed here.

Actor input object example

{
  "videoUrls": [
    "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
  ],
  "mode": "count",
  "intervalSeconds": 10,
  "frameCount": 3,
  "sceneThreshold": 0.3,
  "timestamps": [],
  "format": "jpg",
  "maxWidth": 1280,
  "maxFramesPerVideo": 100,
  "contactSheet": false,
  "downloadZip": false,
  "maxZipMb": 100
}

Actor output Schema

frames (type: string):

No description

summary (type: string):

No description

output (type: string):

No description

framesZip (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

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": "count",
    "frameCount": 3,
    "format": "jpg"
};

// Run the Actor and wait for it to finish
const run = await client.actor("pennydata/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

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": "count",
    "frameCount": 3,
    "format": "jpg",
}

# Run the Actor and wait for it to finish
run = client.actor("pennydata/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

echo '{
  "videoUrls": [
    "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
  ],
  "mode": "count",
  "frameCount": 3,
  "format": "jpg"
}' |
apify call pennydata/video-frame-extractor --silent --output-dataset

MCP server setup

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