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.
- URL: https://apify.com/pennydata/video-frame-extractor.md
- Developed by: Penny Data (community)
- Categories: Videos, Automation, AI
- Stats: 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- User rating: No ratings yet
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:
| timestampSec | frameUrl | frameWidth | frameHeight |
|---|---|---|---|
| 0 | .../records/v1-t0.jpg | 960 | 540 |
| 2 | .../records/v1-t2000.jpg | 960 | 540 |
| 4 | .../records/v1-t4000.jpg | 960 | 540 |
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
}
| Field | Meaning |
|---|---|
videoUrls | Direct http(s) links to video files (required). |
mode | interval (default), count, scene or timestamps. |
timestamps | Exact times to grab: seconds (1, 3.5) or mm:ss / hh:mm:ss. Filling it switches to exact-timestamp mode. |
startTime / endTime | Window for interval and count modes (seconds or hh:mm:ss). |
intervalSeconds | Mode interval: seconds between frames (default 10). |
frameCount | Mode count: evenly spaced frames per video (default 8). |
sceneThreshold | Mode scene: 0.3 default. Lower finds more cuts. |
format | jpg (default) or png. |
maxWidth | Scale down frames wider than this (default 1280). Never scaled up. |
maxFramesPerVideo | Upper limit in any mode (default 100). |
contactSheet | Also save a grid image per video (free). |
downloadZip / maxZipMb | Also 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. |
maxCostUsd | Optional 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)
| Event | Price |
|---|---|
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)); tunesceneThresholdfor 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 input | Used here as |
|---|---|
videoUrl, url (single link) | videoUrls |
videoUrls, urls, startUrls (list) | videoUrls |
everySeconds (tidydata) | intervalSeconds |
outputFormat | format |
contactSheetColumns, proxyMode, maxDownloadMbPerVideo | accepted, ignored |
frameCount, intervalSeconds, startTime, endTime, timestamps, maxWidth, sceneThreshold, contactSheet | same 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
- Audio Transcriber: transcribes the audio track of the same video.
- Shot PDF: captures web pages rather than video files.
- Website Image Extractor: collects the images on a page, for when the source is a site rather than a video.
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