Video Frame Extractor: Thumbnails and Frames from Video URLs avatar

Video Frame Extractor: Thumbnails and Frames from Video URLs

Pricing

from $1.20 / 1,000 video frames

Go to Apify Store
Video Frame Extractor: Thumbnails and Frames from Video URLs

Video Frame Extractor: Thumbnails and Frames from Video URLs

Extract JPG, PNG or WebP frames from direct video links (MP4, WebM, MOV, MKV, OGV): evenly spaced, every N seconds, at exact times or at scene changes. A public link per frame, ffmpeg built in, and you pay only for each frame saved.

Pricing

from $1.20 / 1,000 video frames

Rating

0.0

(0)

Developer

Rowcraft

Rowcraft

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

an hour ago

Last modified

Share

Cut still pictures out of a video by giving the actor direct links to video files (MP4, WebM, MOV, M4V, MKV or OGV). You choose how the frames are picked: a fixed number spread evenly over the video, one frame every N seconds, the exact times you list, or a frame at every scene change. Each frame is saved as a JPG, PNG or WebP image in the key-value store of the run, and you get one dataset row per frame with a public link to the image, its size, its position in the video and the length, size and codec of the video, as JSON, CSV or Excel.

It is made for thumbnails and previews for a video catalogue, pictures of slides and key moments for a report, frames for your own image-recognition or quality-check pipeline, and contact sheets of what a clip contains. The actor reads large files in parts, so it does not download a whole 2 GB file to take ten pictures when the server allows partial reading. It obeys robots.txt, does not touch video platforms, and charges only for frames that were actually saved.

Input form of Video Frame Extractor: Thumbnails and Frames from Video URLs

What you get

One row per saved frame:

  • Where the frame comes from: the address of the video you gave, the frame number within that video, its position in seconds (timestampSeconds) and as hh:mm:ss.mmm (timestamp)
  • The picture: imageUrl, a public link to the image in the key-value store of the run, plus key, format, width, height, bytes and a sha1 fingerprint of the file
  • The video: length in seconds, width, height, codec and frames per second, read from the file itself
  • How it was picked: the mode you chose, and a note when something about that frame needs saying

A video that gave no frame gets one row without an image that says why in status and error; those rows are free. The run also writes a COVERAGE record with one line per video (status, length, frames saved, how much data was read) and a RUN_SUMMARY with the counts, the data read and any warnings.

Why this actor

  • Four ways to pick frames. Evenly spaced for thumbnails, every N seconds for a timeline, exact times for a known moment, and scene changes for slides, cuts and key moments.
  • Pay for pictures only. A video that cannot be read, a platform page, a robots.txt refusal, a video above your length or size limit and a picture identical to one already saved from the same video cost nothing.
  • No full download when it is not needed. A large file is read in parts of 512 KB through a small local reader, so only the parts needed for the frames are fetched. The run summary tells you how many bytes were read.
  • Real frame accuracy. Each frame is cut by seeking to its time and decoding up to it, so the picture is the one at that time, not the nearest keyframe.
  • Polite and safe by design. robots.txt is read on every host, redirects are followed and checked, private network addresses are refused, and requests to the same host are spaced out. The user agent names Rowcraft.
  • Built for schedules. Turn on Only new rows and a scheduled run cuts frames only from videos it has not delivered before; a webhook tells you when it finishes.

How to use it

  1. Open the Actor and fill in Video URLs (the other fields are optional filters).
  2. Set Maximum video frames to a small number for a first try (the default is a safe start).
  3. Click Start. The status line shows progress; the run stops cleanly at your limit or at the charge limit you set in the run options.
  4. Open the Dataset tab and download JSON, CSV, Excel, XML or HTML, or send the rows to Google Sheets, Slack, Make, n8n or Zapier with the Integrations tab.
  5. To run it on a schedule, use Schedules in Apify Console and turn on Only new rows since the last run below: every run then costs only the new rows, and a webhook URL tells you when something new came in.

For a first try keep the list short and the number of frames small. The image of each frame is also in the key-value store of the run, under a key such as Jupiter-HubbleSpaceTelescope-Animation-2-3b303de2-0001-t302.jpg: the start of the file name of the video, eight characters that identify the video address, the frame number and the position in milliseconds. The same key is in the key column and the full link in imageUrl.

Input

FieldWhat it does
videoUrlsDirect links to video files, one per line. Up to 10,000 per run. Pages of video platforms and streaming playlists are not read.
modecount (default): N frames evenly spaced. interval: one frame every N seconds. timestamps: the times you list. scenes: one frame where the picture changes sharply.
framesPerVideoFor count and scenes: how many frames per video, 1 to 300 (default 10).
intervalSecondsFor interval: seconds between two frames, from 0.5 (default 10). The first frame is at 0:00.
timestampsFor timestamps: seconds (83, 83.5) or clock times (1:23, 01:02:03.5), one per line.
sceneThresholdFor scenes: how sharp a change must be, 0.05 to 0.95 (default 0.4). Lower gives more frames.
formatjpg (default), png or webp.
quality1 to 100 for JPG and WebP (default 85). Ignored for PNG.
maxWidthFrames wider than this are scaled down, keeping the shape; smaller ones are never enlarged. Default 1280; 0 = the original size.
maxVideoMegabytesThe most data read from one video (default 500).
maxDurationMinutesVideos longer than this are skipped with a note (default 120).
maxItemsStop after this many frames in total. 0 = no limit.
onlyNew, stateNameSkip videos delivered in an earlier run with the same state name.
notifyUrl, notifyWhenEmptyA webhook that receives one message when the run ends; see Automation below.
requestIntervalMsPause between requests to the same host, default 1000 ms, minimum 250 ms.

Example: ten evenly spaced thumbnails from two public-domain videos.

{
"videoUrls": [
"https://upload.wikimedia.org/wikipedia/commons/5/5e/Jupiter-HubbleSpaceTelescope-Animation-20190823.webm",
"https://upload.wikimedia.org/wikipedia/commons/2/2d/NASA%27s_NICER_Tracks_a_Magnetar%27s_Hot_Spots_%28SVS14115_-_02_MAGNETAR_Wide_view_BlipOnly_web%29.webm"
],
"mode": "count",
"framesPerVideo": 10
}

Example: one frame every 30 seconds as a small WebP file, for a timeline.

{
"videoUrls": ["https://upload.wikimedia.org/wikipedia/commons/5/5a/Esplanade_des_Invalides_%281900%29.webm"],
"mode": "interval",
"intervalSeconds": 30,
"format": "webp",
"maxWidth": 640
}

Example: the frames at three exact times, at the original size and without loss.

{
"videoUrls": ["https://example.com/media/webinar.mp4"],
"mode": "timestamps",
"timestamps": ["1:23", "12:05", "00:45:10.5"],
"format": "png",
"maxWidth": 0
}

Example: a frame at every cut of a silent cartoon, with a low threshold because its changes are soft.

{
"videoUrls": ["https://upload.wikimedia.org/wikipedia/commons/2/23/Felix_The_Cat_1922_The_Stone_Age.webm"],
"mode": "scenes",
"framesPerVideo": 30,
"sceneThreshold": 0.15
}

How the four modes pick frames

  • Evenly spaced (count). The first frame is at 2 percent of the video length and the last at 98 percent, with the others spread evenly between them, which makes a black first frame or the end credits less likely. With framesPerVideo set to 1 you get the frame in the middle.
  • Every N seconds (interval). A frame at 0:00, then one every intervalSeconds while the time is inside the video. One video gives at most 2,000 frames in a run; the note in the coverage says so when the list is cut.
  • Exact times (timestamps). One frame per time you list. A time after the end of a video is skipped with a note, an entry that is not a time is skipped and counted in the summary, and a time at the very last moment of a video returns the last frame before it, with a note on the row.
  • Scene changes (scenes). The whole video is read once and a frame is saved at each place where the picture changes by more than the threshold, up to framesPerVideo frames. The first frame of a video is not a scene change, so it is not included. Because every part of the file is read, this mode reads more data than the others; a large file that fits maxVideoMegabytes is downloaded in one request instead of many, and the limits in maxVideoMegabytes and maxDurationMinutes apply to this mode too.

Output

21 fields per row. Empty values are null, never missing, so CSV and Excel columns are stable.

FieldTypeExample
videoUrlstringhttps://upload.wikimedia.org/wikipedia/commons/5/5e/Jupiter-HubbleS…
statusstringsaved
frameIndexinteger1
timestampSecondsnumber0.302
timestampstring00:00:00.302
imageUrlstringhttps://api.apify.com/v2/key-value-stores/e3d7995b-a12b-4825-9fa1-d…
keystringJupiter-HubbleSpaceTelescope-Animation-2-3b303de2-0001-t302.jpg
formatstringjpg
widthinteger1280
heightinteger720
bytesinteger18353
sha1string94450afe76d6fa99344a61b94bc0bff1e56f2eff
videoDurationSecondsnumber15.077
videoWidthinteger1920
videoHeightinteger1080
videoCodecstringvp8
fpsnumber29.97
modestringcount
notestringA remark on this frame, for example that the last frame before the end of the video was used
errorstringWhy a video gave no frame, in one sentence; null on a delivered frame
extractedAtstring2026-10-18T17:27:23.644Z

status is saved on every frame row. On a row without an image it says why the video gave nothing: invalid (not a web address), unsupported_platform (a video platform page), unsupported_streaming (an HLS or DASH playlist), not_video (the address is a web page, a text file or a file that is not an MP4, WebM or Ogg container), blocked (an anti-bot page or HTTP 401, 403 or 429), robots_disallowed, not_found, too_large, too_long, unreadable (the file is damaged, has no video picture, or does not state its length when the mode needs it), no_frames (nothing to cut, for example no scene change above the threshold) and error.

Example rows

Two real rows from a run (long texts shortened here, complete in the dataset):

[
{
"videoUrl": "https://upload.wikimedia.org/wikipedia/commons/5/5e/Jupiter-HubbleSpaceTelescope-Animation-20190823.webm",
"status": "saved",
"frameIndex": 1,
"timestampSeconds": 0.302,
"timestamp": "00:00:00.302",
"imageUrl": "https://api.apify.com/v2/key-value-stores/gQfHTdxyhgZNPFaeb/records/Jupiter-HubbleSpaceTelescope-Animation-2-3b303de2-0001-t302.jpg?signa…",
"key": "Jupiter-HubbleSpaceTelescope-Animation-2-3b303de2-0001-t302.jpg",
"format": "jpg",
"width": 1280,
"height": 720,
"bytes": 18352,
"sha1": "ad20b2301bec7b117e30588e70a1be48f1145aa0",
"videoDurationSeconds": 15.077,
"videoWidth": 1920,
"videoHeight": 1080,
"videoCodec": "vp8",
"fps": 29.97,
"mode": "count",
"note": null,
"error": null,
"extractedAt": "2026-10-18T18:04:47.980Z"
},
{
"videoUrl": "https://upload.wikimedia.org/wikipedia/commons/2/2d/NASA%27s_NICER_Tracks_a_Magnetar%27s_Hot_Spots_%28SVS14115_-_02_MAGNETAR_Wide_view_Bl…",
"status": "saved",
"frameIndex": 1,
"timestampSeconds": 0.6,
"timestamp": "00:00:00.600",
"imageUrl": "https://api.apify.com/v2/key-value-stores/gQfHTdxyhgZNPFaeb/records/NASA-s-NICER-Tracks-a-Magnetar-s-Hot-Spo-cae4038c-0001-t600.jpg?signa…",
"key": "NASA-s-NICER-Tracks-a-Magnetar-s-Hot-Spo-cae4038c-0001-t600.jpg",
"format": "jpg",
"width": 1280,
"height": 720,
"bytes": 15374,
"sha1": "2cb481dd45c7a81afc3b636736b415cf4496049b",
"videoDurationSeconds": 30.024,
"videoWidth": 1920,
"videoHeight": 1080,
"videoCodec": "vp8",
"fps": 30,
"mode": "count",
"note": null,
"error": null,
"extractedAt": "2026-10-18T18:04:46.891Z"
}
]

Results can be downloaded as JSON, CSV, Excel, XML or HTML from the Dataset tab, or read through the Apify API.

Results of Video Frame Extractor: Thumbnails and Frames from Video URLs in the Apify dataset view

Pricing

Pay per result. You pay for each video frame delivered to your dataset, nothing else: no subscription, no minimum, no charge for rows your filters drop or for the requests behind them.

Your Apify planPrice per 1,000 rowsPrice per row
Free plan$1.80$0.0018
Starter plan$1.60$0.0016
Scale plan$1.40$0.0014
Business plan and above$1.20$0.0012

Example: 1,000 video frames cost $1.80 on the Free plan and $1.20 on the Business plan. Platform usage of the run (compute, storage) is included in that price and is not billed to you separately (pay per event: the actor covers it). A start event of $0.00005 is charged once per run (Apify default). Cap your spend: set Maximum total charge in the run options. The actor stops cleanly when the cap is reached and you keep everything delivered until then. The run summary (RUN_SUMMARY in the key-value store) says why a run stopped.

What is charged and what is not: a frame is charged when it is saved and delivered to your dataset. Nothing is charged for a video that could not be read, for a link to a platform page or a playlist, for a video that robots.txt does not allow, for a video above your length or size limit, for an anti-bot page, for a picture that is identical to one already saved from the same video, or for a video skipped by Only new rows. The free rows in your dataset say what happened to each of them.

Time on the platform depends on the mode and on the delay between requests. In a local test of this actor on a PC, a 2-minute, 720p video of 43 MB on a fast local server gave 10 frames in 10 seconds with the delay set to 250 ms and in 34 seconds with the default delay of 1000 ms, because the large file is read in 32 parts of 512 KB, one request per delay, and the actor read 16.8 MB of the 43 MB. The peak memory of that run was about 290 MB, well inside the 1,024 MB the actor starts with. Lower the delay for hosts that allow it, for example your own storage, to shorten runs.

Automation

Only new rows since the last run. Turn on onlyNew and the actor remembers every video frame it delivered to you (in a key-value store named rowcraft-video-frame-extractor-state in your Apify account). The next run with the same stateName skips what you already have and you pay only for new rows. The first run delivers everything that matches. Use a different stateName for each schedule or search so they keep separate memories.

Notification when the run ends. Put a webhook address in notifyUrl and the actor sends one JSON POST with the number of new rows, links to the dataset (JSON and CSV) and the run. It works out of the box with Slack and Discord incoming webhooks (the message is in text and content), with Make, n8n and Zapier catch hooks, and with your own endpoint. By default it is sent only when there is at least one new row; notifyWhenEmpty sends it every time.

Schedules. Create a schedule in Apify Console (hourly, daily, weekly or cron) with onlyNew on. A daily schedule gives you a feed of new video frames for the price of the new rows only.

Integrations. Send the results to Make, n8n, Zapier, Google Sheets, Slack, Google Drive, Airtable or any webhooks from the Integrations tab of the Actor, with no code. Apify also offers native actions for Make, n8n and Zapier.

API, SDKs and AI agents. Every run is available through the Apify API and the official JavaScript and Python clients; the Actor can also be called by AI agents through the Apify MCP server. The example input in the API tab is a working call.

Only new rows here means only new videos. The actor remembers the address of each video it has read to the end. A video that was cut short by a limit (the charge limit, maxItems, the data limit or the time limit) is not remembered and is read again next time, so a list of videos that grows over time costs only the new ones. It does not compare the content of a video: if you change the mode or the number of frames and want new pictures of the same videos, use a different stateName.

Use cases

  • Thumbnails for a video library or a listing: ten evenly spaced frames per video give a preview strip and a cover picture to choose from.
  • Slides and key moments of a recorded talk: scenes with a lower threshold gives a picture whenever the slide changes.
  • Timelines and contact sheets: one frame every 10 or 30 seconds shows what happens in a long clip without watching it.
  • Frames for your own image pipeline: feed the imageUrl links to a classifier, an OCR service, a moderation model or a captioning model you run elsewhere.
  • Quality checks of delivered files: check that a rendered video starts with the right picture and keeps its resolution, using a frame at 0:00 (exact times) and the width, height and codec in each row.
  • Cover pictures for a media archive: the frame at a known moment, for example 5 seconds in, from every file in a list.
  • Evidence of what a clip shows at a given time: the exact time, the picture and the file's length in one row.

Ready-made examples, each with the input filled in (open one and press Start, or save it as your own task):

What this actor does not do

  • Download videos from YouTube, TikTok, Vimeo, Instagram, Facebook or other video platforms or social networks. Their terms do not allow it, so links to those pages are not read and not charged. Give a direct link to a video file you have the right to use.
  • Read streaming playlists (HLS .m3u8 or DASH .mpd). Version 1 reads video files only.
  • Cut video clips, convert videos or extract the sound. The result is still pictures.
  • Read the text in the pictures. For that, send the imageUrl links to the Image to Text OCR API actor of Rowcraft.
  • Recognise or identify people, faces or objects. It saves the frames; what you do with them is up to you.
  • Log in, use cookies or follow links on a page: it reads the file at the address you give, and a link that leads to a web page is reported as not_video.
  • Solve CAPTCHAs, get around anti-bot protection or use proxies. A host that answers with an anti-bot page, a refusal or a rate limit is reported as blocked and not charged.
  • Open private or local network addresses: addresses that resolve to localhost or a private range are refused on the platform.

Limits and fair use

  • Pace. Requests to the same host are spaced at least 1,000 ms apart by default (minimum 250 ms). Different hosts are not delayed. Two videos are read at the same time.
  • How a file is read. The first request is a partial one, for the first 512 KB, after robots.txt. A file of up to 16 MB is then fetched whole, one more request, and read from disk. A larger file is read in parts of 512 KB, only those that are needed. A server that ignores partial requests is downloaded whole, up to maxVideoMegabytes; a larger file is skipped as too_large and not charged. In the partial mode the reading also stops at that limit, and the frames cut before it are delivered with a note in the coverage.
  • Hosts that limit the data rate. Some hosts answer HTTP 429 when a client reads many megabytes in a few seconds. The actor does not retry in a loop and does not try to get around it: the frames cut before the refusal are delivered, the rest of that video is reported, and a larger requestIntervalMs or fewer frames per video helps.
  • Length. Videos longer than maxDurationMinutes (default 120) are skipped as too_long before any frame is cut. A file that does not state its length cannot be used with count or interval, but works with timestamps and scenes.
  • Number of frames. Up to 300 per video for count and scenes, up to 2,000 per video for interval and timestamps, and as many in total as maxItems allows.
  • Memory. The run starts with 1,024 MB. Very large pictures (for example 8K) decoded two at a time can need more: raise the memory in the run options.
  • Scene changes read the whole video. A file that fits maxVideoMegabytes is downloaded in one request and kept on the disk of the run until it is cut; a larger one is read in parts until the data limit, and the scenes found by then are delivered. The scene pass is stopped after 10 minutes of work. For a long video, set a data limit that fits the file, or use count or interval.
  • Frames are saved as they decode. HDR videos are not tone-mapped, so their colours can look flat, and a video with a rotation flag is turned upright. The picture is the video picture only; sound and subtitles are not used.
  • Time limit. The run stops 90 seconds before its timeout, with the summary and the Only new rows memory saved.

Data source and licence

There is no third-party data source: the actor reads the video files at the addresses you give, after checking robots.txt on the host, and cuts the frames with ffmpeg, an open-source program (licensed under the LGPL or GPL, depending on how it is built) run inside the actor. The videos and the frames cut from them belong to whoever made the videos. The actor gives you a picture of what the file shows at a given moment; whether you may keep, publish or reuse it depends on the licence of the video and on the law that applies to you. The public examples of this actor use videos that Wikimedia Commons lists as public domain.

Rowcraft is independent and not affiliated with or endorsed by the FFmpeg project (see ffmpeg.org) or by the hosts of the videos you read. The actor identifies itself as Rowcraft Video Frame Extractor (+https://apify.com/rowcraft) and reads robots.txt under the product token rowcraft.

Privacy: no personal data

Rowcraft actors are built not to collect personal data:

  • The rows hold no names, e-mail addresses, phone numbers or other fields about people. Links that start with mailto:, tel: or sms: in your list are skipped and not reported, and an e-mail address inside a video address is removed from the row.
  • The tags of the video file (title, author, location, comments) are never read into the rows; only the technical facts, such as length, size and codec.
  • A frame shows whatever the video shows, which can include people. You choose the videos, so you are responsible for what you cut and keep, and for having the right to do so.
  • The address you give appears in each row as you gave it, with any signature or token in it, so share datasets of runs on signed links with care.
  • Nothing is stored about your videos outside your own run and your own Only new rows memory.

Other Rowcraft actors

All Rowcraft actors read official or public sources, deliver flat rows without personal data and support only-new runs and notifications:

FAQ

Which links does it accept? Direct links to video files over http or https: the address must lead to an MP4, M4V, MOV, WebM, MKV or OGV file, and the server must answer with a video content type or a file name that ends in one of those. Temporary signed links from your own cloud storage work as long as they are valid during the run.

Can I use a YouTube, TikTok or Vimeo link? No. Those pages are not read, because the terms of video platforms do not allow downloading their videos. The row says unsupported_platform and nothing is charged. Use a direct link to a file you may use, such as one in your own storage.

Does it download the whole video? Not when it can avoid it. After robots.txt, one partial request looks at the first 512 KB. A file of up to 16 MB is then fetched whole, a larger one is read in parts and only the needed parts are fetched. A server that does not allow partial reading is downloaded whole, up to your size limit. The run summary shows the data read per run, and the coverage per video.

How accurate is the position of a frame? The actor seeks to the time and decodes up to it, so the frame is the one at that time to within one frame. The row shows the position it used in timestampSeconds. The first and last moments of a video are the exception: a frame requested at the very end is taken from just before it.

How do I get a frame at an exact time? Use mode set to timestamps and list the times, as seconds (83.5) or as clock times (1:23, 01:02:03.5). A time after the end of the video is skipped with a note, and the summary counts times that are after the end and entries that are not times.

Which scene threshold should I use? The default 0.4 finds hard cuts, as in trailers, news and edited films. Silent films, cartoons, slide shows and screen recordings change more softly, so try 0.15 to 0.3. Raise the value to 0.6 or more to keep only complete changes of picture. If no change is found, the video gets a no_frames row with a hint and costs nothing.

Why did I get fewer frames than I asked for? There are four usual reasons: identical pictures from one video are saved once (a still image or a long black screen gives one frame), a requested time is after the end of the video, a scene mode found fewer changes than the number you allowed, or a limit stopped the run (maxItems, the charge limit, the data limit or the time limit). The coverage record of the run names the reason for each video.

Are identical frames charged? No. When two frames of the same video have the same file content, only the first is saved and charged. The run summary counts the skipped ones in duplicateFramesSkipped.

What does a video that is too long or too large cost? Nothing. A video longer than maxDurationMinutes is skipped as too_long before any frame is cut. A file that cannot be read within maxVideoMegabytes is too_large. In both cases you get one free row that says so; raise the limit if you want those videos read.

Which image format should I choose? JPG is the smallest for photographs and video frames, and the default. PNG keeps every pixel exactly, which suits screen recordings and slides with fine text, at several times the size. WebP is small, with quality close to JPG. Use maxWidth to cap the size, and quality for JPG and WebP.

How long are the image files kept, and who can open the links? The images are in the default key-value store of the run, which Apify keeps for the retention period of unnamed storages (7 days at the time of writing; the Apify documentation has the current figure). Download what you need before then, or copy it to your own storage. On the platform the links in imageUrl carry a signature, so they open without logging in for as long as the files exist.

Does it work with HLS or DASH streams? No, not in this version. A link to an .m3u8 or .mpd playlist gets a free row with the status unsupported_streaming. Use a link to a video file.

Can it read a video behind a login or one that needs cookies? No. The actor sends no cookies and no login, and does not use proxies. A link that answers with a login page is reported as not_video, and one that answers with a refusal or an anti-bot page is reported as blocked, and neither is charged.

How fast is it? For a large file the number of requests decides: each 512 KB part is one request, and requests to the same host are spaced 1,000 ms apart by default. In our local test a 2-minute video took 10 to 34 seconds for 10 frames, depending on that delay. Small files are fetched in two requests and cut quickly. Scene mode reads the whole file once, so it takes longer on long videos: in our local test, a 43 MB video was read and checked for scenes in about 8 seconds.

Does it extract the sound or subtitles? No. It only uses the video picture.

Support and reviews

Problems, missing fields, questions: open an issue on the Issues tab of this Actor. We answer within one working day and fix confirmed bugs in the next release; the changelog lists every change.

If this Actor saved you time, a short review on its page helps other users find it and helps us keep improving it.