Video Frame Extractor - Screenshots, Scenes, Contact Sheet avatar

Video Frame Extractor - Screenshots, Scenes, Contact Sheet

Pricing

from $4.00 / 1,000 video processeds

Go to Apify Store
Video Frame Extractor - Screenshots, Scenes, Contact Sheet

Video Frame Extractor - Screenshots, Scenes, Contact Sheet

Extract frames from video URLs (mp4, webm, mov, mkv, m3u8) with FFmpeg: every N seconds, N evenly spaced, scene changes or exact timestamps. JPG/PNG/WebP, public links, optional contact sheet. Pay per video and frame.

Pricing

from $4.00 / 1,000 video processeds

Rating

0.0

(0)

Developer

Andrew Boiko

Andrew Boiko

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

28 minutes ago

Last modified

Categories

Share

Video Frame Extractor - Screenshots, Scene Changes and Contact Sheets (FFmpeg)

Turn video links into image files. Give the Actor direct video URLs (MP4, WebM, MOV, MKV or an HLS .m3u8 playlist) and it extracts frames with FFmpeg: every N seconds, N evenly spaced frames, frames at detected scene changes, or frames at exact timestamps. Every frame is stored as JPG, PNG or WebP with a public link, and you can get one contact sheet per video.

Pay only for videos that were processed and frames that were stored. Unreadable videos, HTTP errors and anything over your limits are never charged. New Apify accounts can try it with the free plan credit.

Who is it for

  • Teams that need thumbnails or preview stills for a video library.
  • Content, SEO and social teams creating cover images, storyboards or slide-style summaries.
  • Developers feeding frames into OCR, image classification or vision-model pipelines (the dataset gives you stable image URLs and timestamps).
  • QA and compliance teams that need periodic snapshots of a video, or the frame at a known time code.

What it does and does not do

  • Reads the video over HTTP with range requests: for interval, count and timestamp modes FFmpeg seeks to each position and only reads the part of the file it needs, it does not download the whole video. scenes mode must decode the whole video once.
  • Needs a direct video file or HLS playlist URL. Pages such as YouTube, Vimeo, TikTok or Instagram are not video files and will fail with a clear error. DRM-protected or encrypted streams are not supported. Live streams without a known duration only work in timestamps mode.
  • Frames are exact to the video's frame grid at the requested time. Scene detection runs at 5 frames per second on a 320 px wide copy, so scene times are accurate to about 0.2 s.
  • No audio extraction, no video clips, no OCR and no face detection.

Input

FieldTypeDefaultDescription
videoUrlsarrayrequired (or videoFiles)Direct video links, one per row.
videoFilesarray[]Videos stored in an Apify key-value store: storeId/recordKey, read with the run's token.
modeinterval, count, scenes, timestampscountHow frame positions are chosen.
intervalSecondsinteger10Mode interval: seconds between frames, starting at 0.
frameCountinteger 1-3008Mode count: evenly spaced frames, centered in equal slices.
sceneThresholdstring "0.1".."0.9""0.3"Mode scenes: lower finds more (soft) cuts, higher only hard cuts.
timestampsarray[]Mode timestamps: 83.5, 01:23, 00:01:23.500, 90s.
maxFramesPerVideointeger 1-30020Hard cap per video (protects your budget).
formatjpg, png, webpjpgImage format.
maxWidthinteger 0-76801280Scale down wider frames (never up). 0 keeps the original size.
qualityinteger 1-10085JPG/WebP quality.
makeContactSheetbooleanfalseOne tiled JPG per video. Included in the price.
contactSheetColumnsinteger 1-125Grid columns of the contact sheet (320 px tiles).
maxDurationMinutesinteger120Longer videos are rejected without charge.
maxVideoSizeMbinteger2048Files larger than this (as reported by the server) are rejected without charge.
includeFailedbooleanfalseAdd free rows with status: "failed" for videos that failed.

Output

The dataset has one row per frame (type: "frame") and one summary row per video (type: "video"). Frame images are in the default key-value store; frameUrl is the public link.

Frame row:

{
"type": "frame",
"id": "41be81f899:2",
"videoId": "41be81f899",
"videoUrl": "https://example.com/video.mp4",
"frameIndex": 2,
"timestampSec": 4.5,
"timestampLabel": "00:00:04.500",
"frameUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/frames-41be81f8-0002.jpg",
"recordKey": "frames-41be81f8-0002.jpg",
"format": "jpg",
"width": 1280,
"height": 720,
"bytes": 84231,
"sceneScore": null,
"extractedAt": "2026-10-14T10:57:10.203Z",
"source": "ffmpeg"
}

Video row:

{
"type": "video",
"id": "41be81f899",
"videoUrl": "https://example.com/video.mp4",
"status": "ok",
"mode": "count",
"durationSec": 12,
"resolution": "1920x1080",
"width": 1920,
"height": 1080,
"fps": 29.97,
"codec": "h264",
"container": "mov",
"bitrate": 3439000,
"hasAudio": true,
"frames": 4,
"contactSheetUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/contact-sheet-41be81f8.jpg",
"warnings": [],
"processedAt": "2026-10-14T10:57:10.221Z",
"source": "ffmpeg"
}

frameIndex starts at 1. In scenes mode sceneScore holds FFmpeg's scene change score (0-1) for that frame. Record keys use frames-<videoHash>-<n>.<ext> (key-value store keys cannot contain slashes).

The run also writes OUTPUT (counts, charged events, failed videos with reasons, warnings, stop reason) and a short RESULTS_MD to the key-value store.

Pricing

Pay-per-event, pay only for videos processed and frames stored:

EventPriceWhen
video$0.005One video successfully opened, probed and processed with at least one frame stored.
frame$0.001One frame image extracted and stored.

Example: 3 frames from one video cost $0.005 + 3 x $0.001 = $0.008. 100 frames from 5 videos cost $0.025 + $0.10 = $0.125.

Not charged: videos that cannot be opened, are not videos, are too long or too large, frames that FFmpeg could not produce, free includeFailed rows, and anything beyond your maximum cost per run (the run then stops cleanly and OUTPUT.stoppedReason is budget). The contact sheet is free. Apify may apply Bronze/Silver/Gold plan discounts.

How it works

  1. Each URL is checked (public address only, redirects followed with the same check) and its size is read with a HEAD request.
  2. ffprobe reads duration, resolution, frame rate and codec.
  3. Frame positions are planned for the chosen mode. scenes mode first runs one analysis pass with FFmpeg's scene filter.
  4. FFmpeg extracts each frame with a fast input seek (-ss before -i), a few at a time per host.
  5. Frames are saved to the key-value store, then charged and added to the dataset one by one.
  6. Progress is saved after every frame. After a migration or restart the Actor continues and does not charge a video or frame twice.

Limits and honest caveats

  • The video host must answer from a normal HTTP(S) server. Signed URLs that expire, hosts that block data-center IPs and pages that need cookies or logins cannot be read.
  • Seeking is fast for MP4 with the index (moov) at the start and for HLS. MP4 files with the index at the end, or very sparse keyframes, are slower.
  • scenes mode reads and decodes the entire video; a 2-hour 1080p video can take many minutes. Use maxDurationMinutes to bound it.
  • If no scene change reaches the threshold, one frame from the middle of the video is returned with a warning (lower sceneThreshold for more).
  • Frames at the very end of a video are clamped slightly before the last frame.
  • Frames are not de-duplicated: if you ask for 10 frames of a still image you get 10 near-identical images.
  • You are responsible for having the right to process the videos you submit. The Actor does not bypass access controls or DRM.
  • Frames of people are personal data under GDPR and similar laws; make sure you have a lawful basis.
  • Extracted frames are stored in your own Apify key-value store under its retention rules. Video URLs are only contacted to read the video.
  • FFmpeg is used as a separate program (ffmpeg-static and ffprobe-static binaries). FFmpeg is licensed under the GPL/LGPL; see the FFmpeg project for details.

FAQ

Can I use YouTube links? No. YouTube pages are not direct video files, and downloading them may violate YouTube's terms. Use a direct file or playlist URL you are allowed to access.

Does it download the whole video? Not in interval, count and timestamps modes: it seeks with range requests. scenes mode reads the whole file.

What does a frame cost? $0.001 per frame plus $0.005 per video.

How many frames can I get from one video? Up to 300 per video (maxFramesPerVideo), set by you.

Can I get the frame at an exact time? Yes, use mode timestamps with values like 00:01:23.500.

What happens if my budget runs out mid-run? The Actor stops cleanly, keeps the frames already delivered and lists the rest in OUTPUT. Uncharged frames are deleted from the store.

Is the order of dataset rows guaranteed? Frame rows of a video come first in time order, then the video summary row. With several videos the rows of different videos can interleave.

Which formats are supported? Anything FFmpeg can decode over HTTP: H.264, HEVC, VP8/VP9, AV1 and more, in MP4, MOV, WebM, MKV, TS or HLS.

Use via API

Replace username~video-frame-extractor with this Actor's ID and YOUR_TOKEN with your Apify API token.

curl -X POST "https://api.apify.com/v2/acts/username~video-frame-extractor/run-sync-get-dataset-items?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"videoUrls":["https://example.com/video.mp4"],"mode":"count","frameCount":6,"format":"jpg"}'
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('username~video-frame-extractor').call({
videoUrls: ['https://example.com/video.mp4'],
mode: 'interval',
intervalSeconds: 10,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const frames = items.filter((x) => x.type === 'frame');
console.log(frames.map((f) => f.frameUrl));
from apify_client import ApifyClient
client = ApifyClient("YOUR_TOKEN")
run = client.actor("username~video-frame-extractor").call(run_input={
"videoUrls": ["https://example.com/video.mp4"],
"mode": "scenes",
"sceneThreshold": "0.3",
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if item["type"] == "frame":
print(item["timestampLabel"], item["frameUrl"])

Integrations

Connect the Actor to Make, n8n, Zapier or Google Sheets with the Apify integrations: start a run with a list of video URLs, then use the frameUrl column to save images to Drive, a CMS or a vision-model step. Webhooks tell your system when a run finishes.

Support

Open an issue from the Actor page with the run link and the video URL (if it is public). Please do not send videos you do not have rights to.

More tools by the same author