# Video Frame Extractor: Thumbnails and Frames from Video URLs (`rowcraft/video-frame-extractor`) Actor

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.

- **URL**: https://apify.com/rowcraft/video-frame-extractor.md
- **Developed by:** [Rowcraft](https://apify.com/rowcraft) (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.20 / 1,000 video 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: Thumbnails and Frames from Video URLs

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](https://api.apify.com/v2/key-value-stores/7hbQFQdGq61v2OZ2h/records/video-frame-extractor-input.png)

### 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

| Field | What it does |
|---|---|
| `videoUrls` | Direct links to video files, one per line. Up to 10,000 per run. Pages of video platforms and streaming playlists are not read. |
| `mode` | `count` (default): N frames evenly spaced. `interval`: one frame every N seconds. `timestamps`: the times you list. `scenes`: one frame where the picture changes sharply. |
| `framesPerVideo` | For `count` and `scenes`: how many frames per video, 1 to 300 (default 10). |
| `intervalSeconds` | For `interval`: seconds between two frames, from 0.5 (default 10). The first frame is at 0:00. |
| `timestamps` | For `timestamps`: seconds (`83`, `83.5`) or clock times (`1:23`, `01:02:03.5`), one per line. |
| `sceneThreshold` | For `scenes`: how sharp a change must be, 0.05 to 0.95 (default 0.4). Lower gives more frames. |
| `format` | `jpg` (default), `png` or `webp`. |
| `quality` | 1 to 100 for JPG and WebP (default 85). Ignored for PNG. |
| `maxWidth` | Frames wider than this are scaled down, keeping the shape; smaller ones are never enlarged. Default 1280; 0 = the original size. |
| `maxVideoMegabytes` | The most data read from one video (default 500). |
| `maxDurationMinutes` | Videos longer than this are skipped with a note (default 120). |
| `maxItems` | Stop after this many frames in total. 0 = no limit. |
| `onlyNew`, `stateName` | Skip videos delivered in an earlier run with the same state name. |
| `notifyUrl`, `notifyWhenEmpty` | A webhook that receives one message when the run ends; see **Automation** below. |
| `requestIntervalMs` | Pause between requests to the same host, default 1000 ms, minimum 250 ms. |

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

```json
{
  "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.

```json
{
  "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.

```json
{
  "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.

```json
{
  "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.

| Field | Type | Example |
|---|---|---|
| `videoUrl` | string | `https://upload.wikimedia.org/wikipedia/commons/5/5e/Jupiter-HubbleS…` |
| `status` | string | saved |
| `frameIndex` | integer | 1 |
| `timestampSeconds` | number | 0.302 |
| `timestamp` | string | 00:00:00.302 |
| `imageUrl` | string | `https://api.apify.com/v2/key-value-stores/e3d7995b-a12b-4825-9fa1-d…` |
| `key` | string | Jupiter-HubbleSpaceTelescope-Animation-2-3b303de2-0001-t302.jpg |
| `format` | string | jpg |
| `width` | integer | 1280 |
| `height` | integer | 720 |
| `bytes` | integer | 18353 |
| `sha1` | string | 94450afe76d6fa99344a61b94bc0bff1e56f2eff |
| `videoDurationSeconds` | number | 15.077 |
| `videoWidth` | integer | 1920 |
| `videoHeight` | integer | 1080 |
| `videoCodec` | string | vp8 |
| `fps` | number | 29.97 |
| `mode` | string | count |
| `note` | string | A remark on this frame, for example that the last frame before the end of the video was used |
| `error` | string | Why a video gave no frame, in one sentence; null on a delivered frame |
| `extractedAt` | string | 2026-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):

```json
[
  {
    "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](https://api.apify.com/v2/key-value-stores/7hbQFQdGq61v2OZ2h/records/video-frame-extractor-results.png)

### 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 plan | Price per 1,000 rows | Price 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):

- [Ten evenly spaced thumbnails from two videos](https://apify.com/rowcraft/video-frame-extractor/examples/ten-thumbnails-from-two-public-domain-videos)
- [A frame at every scene change of a silent cartoon](https://apify.com/rowcraft/video-frame-extractor/examples/scene-change-frames-of-a-silent-cartoon)

### 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:

- [Ashby Jobs Scraper – Job Postings with Salary Ranges](https://apify.com/rowcraft/ashby-jobs-scraper) — Job postings from Ashby job boards (jobs.ashbyhq.com) by company: title, department, team, locations with country, employment and workplace type, published salary range, full text.
- [Broken Link Checker – Find 404s and Dead Links API](https://apify.com/rowcraft/broken-link-checker) — Pay per crawled page, not per link: crawl a site within robots.txt and check every link on each page for 404s, 5xx errors, DNS and TLS failures and redirects.
- [Bulk Image Downloader API: Save Page Images with Sizes](https://apify.com/rowcraft/bulk-image-downloader) — Download the images of any web pages, or direct image links, into a key-value store with width, height, format, size and a download link per file.
- [Career Site Jobs Scraper: Greenhouse, Lever, Ashby, Workday](https://apify.com/rowcraft/career-site-jobs-scraper) — Job listings from the career pages of the companies you choose on Greenhouse, Lever, Ashby, Workday, Workable, Personio, Recruitee, Teamtailor and Gem, in one run and one format: title, locations, department, salary when published, full text.
- [Chrome Web Store Scraper: Users, Ratings & Versions](https://apify.com/rowcraft/chrome-web-store-scraper) — Reads public Chrome Web Store listing pages with no login, no proxy and no personal data: users, rating, category, version, update date, permissions and company developers.
- [Domain Email and DNS Checker API: MX, SPF, DMARC, Providers](https://apify.com/rowcraft/domain-email-dns-checker) — Find which email provider, sending services and DNS host each domain uses, from public DNS records only.
- [SIRENE France Company Scraper: SIREN, NAF, Revenue, Status](https://apify.com/rowcraft/france-company-register) — French companies from the official Recherche d'entreprises API (INSEE SIRENE + INPI): SIREN and SIRET, legal form, NAF activity, head-office address, employee band, revenue and net income, labels, creation date and VAT number.
- [France Fuel Prices Scraper – Official Open Data by Station](https://apify.com/rowcraft/france-fuel-prices-scraper) — Current fuel prices at every French station, straight from the official open-data feed (Licence Ouverte 2.0): gazole, SP95, SP98, E10, E85 and GPLc, each with its update time.
- [Gem Jobs Scraper – Job Board Listings from jobs.gem.com](https://apify.com/rowcraft/gem-jobs-scraper) — Job listings from Gem job boards (jobs.gem.com) by company: title, department, offices with country, employment type, remote, hybrid or on-site, requisition ID, publication date and full ad text, from the public Gem Job Board API.
- [Google Play Apps Scraper: App Details, Installs, Ratings](https://apify.com/rowcraft/google-play-apps-scraper) — Public Google Play app details as flat rows: installs, rating, number of ratings, price, version, last update, minimum Android, ads and in-app purchases.
- [Greenhouse Jobs Scraper – Job Listings by Company Board](https://apify.com/rowcraft/greenhouse-jobs-scraper) — Job listings from Greenhouse job boards by company: title, departments, offices, publication date, full ad text, published pay ranges.
- [Hacker News Scraper API: Top, New, Best, Ask, Show Stories](https://apify.com/rowcraft/hacker-news-scraper) — Read Hacker News stories from the official Firebase API: top, new, best, Ask HN, Show HN and job lists with score, comments, domain and age.
- [Himalayas Jobs Scraper: Remote Jobs API with Salaries](https://apify.com/rowcraft/himalayas-jobs-scraper) — Remote jobs from Himalayas through its public jobs API: title, company, salary range, seniority, categories, the countries and time zones open to candidates, full description and job link.
- [Image to Text OCR API: Extract Text from Images in Bulk](https://apify.com/rowcraft/image-ocr-text-extractor) — Read the text in images given by link or found on web pages, with a local OCR engine and no third-party API.
- [KRS Poland Companies Scraper: Register Data, PKD, Status](https://apify.com/rowcraft/krs-poland-companies) — Polish companies from the official KRS register API of the Ministry of Justice: name, KRS, NIP and REGON numbers, legal form, status, address, PKD activity codes, share capital, branches and registration dates.
- [LEI Records Scraper: Legal Entity Identifier Lookup API](https://apify.com/rowcraft/lei-records-scraper) — Legal Entity Identifier (LEI) records from the Global LEI Index, read through the public GLEIF API: legal name, legal form, register number, addresses, status, renewal date and parent companies.
- [Lever Jobs Scraper – Job Postings from jobs.lever.co](https://apify.com/rowcraft/lever-jobs-scraper) — Job postings from Lever career pages (jobs.lever.co) by company: title, team, department, location, commitment, workplace type, salary range when published, description, requirements and benefits.
- [Lighthouse Audit API: Core Web Vitals and Performance Scores](https://apify.com/rowcraft/lighthouse-performance-audit) — Run Lighthouse on any public page and get Performance, Accessibility, Best Practices and SEO scores, lab Core Web Vitals and the biggest savings.
- [Norway Company Register Scraper: Brønnøysund Business Data](https://apify.com/rowcraft/norway-company-register-scraper) — Norwegian companies from the Central Coordinating Register for Legal Entities (Brønnøysund, NLOD open data): organisation number, legal form, industry, employees, share capital, addresses, website, VAT and bankruptcy status.
- [PDF Text Extractor: Text, Pages and Metadata from PDF Links](https://apify.com/rowcraft/pdf-text-extractor) — Get the text of every PDF you link, page by page, with page count, language and word count.
- [Personio Jobs Scraper: Job Listings, Salaries, Departments](https://apify.com/rowcraft/personio-jobs-scraper) — Job ads from any company's Personio careers feed: title, department, office, employment type, schedule, seniority, salary when published, keywords and the full description in the language you choose.
- [Platsbanken Scraper: Sweden Jobs, Employers, Org Numbers](https://apify.com/rowcraft/platsbanken-jobs-scraper) — Every job ad on Platsbanken (Arbetsförmedlingen) from the official JobTech API: headline, employer and organisation number, occupation codes, municipality, coordinates, salary type, deadline and full text.
- [Recruitee Jobs Scraper – Job Listings by Company Career Site](https://apify.com/rowcraft/recruitee-jobs-scraper) — Job listings from Recruitee career sites by company: title, department, locations with country, employment type, remote flag, published salary and full text, one request per company.
- [Remote Jobs Aggregator API: RemoteOK, WeWorkRemotely, Jobicy](https://apify.com/rowcraft/remote-jobs-aggregator) — Remote jobs from RemoteOK, We Work Remotely and Jobicy in one clean list, read from their official feeds and APIs, merged without duplicates.
- [Robots.txt and AI Crawler Access Checker API](https://apify.com/rowcraft/robots-ai-crawler-checker) — Check which AI and search crawlers a website lets in: GPTBot, ClaudeBot, PerplexityBot, Google-Extended and more, read from robots.txt by the RFC 9309 rules.
- [RSS Feed Finder and Reader – Blog and News Article API](https://apify.com/rowcraft/rss-feed-finder-reader) — Find the RSS, Atom and JSON feeds of any website and read their newest articles, from a domain name alone.
- [SEO Audit Tool: Titles, Meta Tags, Headings and Links API](https://apify.com/rowcraft/seo-audit-tool) — Audit the on-page SEO of any list of pages or websites: titles, meta descriptions, headings, canonicals, indexing, links, images and structured data, with a score and the issues found per page.
- [Shopify Products Scraper: Store Catalog Export to JSON, CSV](https://apify.com/rowcraft/shopify-products-scraper) — Products from any Shopify store's public catalog: title, vendor, type, tags, description, variants with price, compare-at price, SKU and stock, images, options, store currency.
- [Sitemap URL Extractor: Every Page with Last-Modified Dates](https://apify.com/rowcraft/sitemap-url-extractor) — Get every page a site lists in its sitemaps, with last-modified dates, from robots.txt through sitemap indexes and .gz files.
- [SSL Certificate and Security Headers Checker API](https://apify.com/rowcraft/ssl-security-headers-checker) — Check the TLS certificate and the HTTP security headers of every domain in one run, with an A+ to F grade and the exact issues to fix.
- [Store Products Scraper – Shopify, WooCommerce, VTEX](https://apify.com/rowcraft/store-products-scraper) — Export the product catalogue of any Shopify, WooCommerce or VTEX store from its public catalogue endpoint: title, brand, prices, discounts, stock, categories, images and links, one row per product, the same columns on every platform.
- [Structured Data Extractor: JSON-LD, Schema.org, Open Graph](https://apify.com/rowcraft/structured-data-extractor) — Pull the schema.org JSON-LD, Microdata, RDFa and Open Graph of any page list into flat rows: types, product price, availability, ratings, organization, breadcrumbs, with validation issues.
- [Teamtailor Jobs Scraper – Career Site Jobs by Company](https://apify.com/rowcraft/teamtailor-jobs-scraper) — Job listings from Teamtailor career sites by company: title, department, role, locations with city and country, remote or hybrid status, publication date and full ad text, from the official job feed.
- [TED Tenders Scraper: EU Public Procurement Notices API](https://apify.com/rowcraft/ted-tenders-scraper) — EU public procurement notices from TED (Tenders Electronic Daily), the official EU tenders portal, through its public Search API: title, buyer, country, CPV codes, values, deadlines, lots and document links.
- [UK Companies House Scraper: Company Search & Profiles API](https://apify.com/rowcraft/uk-companies-house-scraper) — UK companies from the official Companies House API with your free key: search by name, status, type, SIC code, date or location, or look up company numbers.
- [UK Tenders Scraper: Find a Tender + Contracts Finder API](https://apify.com/rowcraft/uk-tenders-scraper) — UK public procurement notices from Find a Tender and Contracts Finder, the official UK government portals, through their open OCDS APIs: title, buyer, region, CPV codes, values, deadlines, notice type and links.
- [Bulk URL Status and Redirect Chain Checker API](https://apify.com/rowcraft/url-status-redirect-checker) — Check the status code, redirect chain, canonical and indexing signals of every URL in a list, with the final destination and a loop flag.
- [VTEX Products Scraper – Public Catalog Export](https://apify.com/rowcraft/vtex-products-scraper) — Export the catalogue of any VTEX store (Auchan, Chedraui and thousands of retailers in Europe and Latin America) from its public search endpoint: title, brand, EAN, prices, list prices, stock, categories, specifications and images, one row per product.
- [Website Intelligence API: Tech, Social, SSL, DNS Profile](https://apify.com/rowcraft/website-intelligence-profiler) — One row per domain with the whole website profile: technologies, social profile links, logo and brand assets, SSL expiry, DNS and mail providers, sitemap size, robots.txt and llms.txt.
- [Website to Markdown Extractor: Clean Text for LLMs and RAG](https://apify.com/rowcraft/website-markdown-extractor) — Turn a list of web page addresses into clean Markdown and plain text for LLMs and RAG: the main content only, with headings, links, images and tables.
- [Website Screenshot API: Full-Page PNG, JPEG and PDF Captures](https://apify.com/rowcraft/website-screenshot-api) — Capture full-page or viewport screenshots of any public web page as PNG, JPEG or WebP, with an optional A4 PDF.
- [Website Social Links Finder: Company Profiles API](https://apify.com/rowcraft/website-social-links-finder) — Get the official social media profiles of any company website: LinkedIn page, Facebook, Instagram, X, YouTube, TikTok and more, plus logo, favicon and share image.
- [Website Tech Stack Detector – Technology Lookup API](https://apify.com/rowcraft/website-tech-stack-detector) — Find the technologies behind a list of websites: CMS, e-commerce platform, analytics, tag managers, ad pixels, consent tools, chat, CDN and payments, from one polite home-page request per domain.
- [WooCommerce Products Scraper – Store API Export](https://apify.com/rowcraft/woocommerce-products-scraper) — Export every product of a WooCommerce store through its public Store API: title, SKU, prices and sale prices, stock, categories, tags, attributes, ratings, images and links, one row per product.
- [Workable Jobs Scraper: Job Listings from Career Pages](https://apify.com/rowcraft/workable-jobs-scraper) — Job listings from any company's Workable career page: title, department, location, remote or on-site, employment type, description, requirements, benefits and salary when published.
- [Workday Jobs Scraper – Jobs from myworkdayjobs.com Sites](https://apify.com/rowcraft/workday-jobs-scraper) — Job listings from Workday career sites (myworkdayjobs.com) by company: title, locations, country, posting date, time type, requisition ID and the full description.

### 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.

# Changelog

This Actor's version history is a separate document: https://apify.com/rowcraft/video-frame-extractor/changelog.md

# Actor input Schema

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

Direct links to video files you have the right to use, one per line. Pages of YouTube, TikTok, Vimeo, Instagram or Facebook and streaming playlists (.m3u8, .mpd) are not read and not charged.

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

Evenly spaced: the number of frames you set below, spread over the video without its first and last 2 percent. Every N seconds: one frame at each interval, starting at 0. Exact times: the time stamps you list. Scene changes: a frame where the picture changes sharply.

## `framesPerVideo` (type: `integer`):

For the modes Evenly spaced and Scene changes: how many frames to take from each video. 1 to 300.

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

For the mode Every N seconds: the gap between two frames, in seconds, starting at 0:00. At most 2,000 frames come from one video in one run.

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

For the mode Exact times: seconds (83, 83.5) or clock times (1:23, 01:02:03.5), one per line. Times after the end of a video are skipped with a note.

## `sceneThreshold` (type: `number`):

For the mode Scene changes: how sharp a change must be, from 0.05 (almost every cut) to 0.95 (only complete changes of picture). 0.4 suits most videos; lower it when too few frames come back.

## `format` (type: `string`):

JPG is the smallest for photos, PNG keeps every pixel exactly, WebP is small with good quality.

## `quality` (type: `integer`):

1 to 100. 85 is a good balance of size and detail. Ignored for PNG.

## `maxWidth` (type: `integer`):

Frames wider than this are scaled down, keeping the shape of the picture; smaller ones are never enlarged. 0 = the original size of the video.

## `maxVideoMegabytes` (type: `number`):

The most data read from one video. A server that does not allow reading in parts is downloaded whole, so a larger file is skipped; for the others the run stops reading at this number.

## `maxDurationMinutes` (type: `number`):

Videos longer than this are skipped with a note and not charged.

## `maxItems` (type: `integer`):

Stop after this many frames are delivered. 0 = no limit.

## `onlyNew` (type: `boolean`):

Remember the rows already delivered to you (in a key-value store in your Apify account) and deliver only rows that appeared since the last run with the same state key. The first run delivers everything that matches; later runs cost only the new rows. Ideal for schedules.

## `stateName` (type: `string`):

Name of the memory used by "Only new rows". Use a different name for each schedule or search (for example `stockholm-python`) so they do not share one memory.

## `notifyUrl` (type: `string`):

A webhook address that receives one JSON POST with the number of new rows and links to the dataset: a Slack or Discord incoming webhook, a Make, n8n or Zapier catch hook, or your own endpoint. The message is also in the `text` (Slack) and `content` (Discord) fields.

## `notifyWhenEmpty` (type: `boolean`):

By default the notification is sent only when at least one row was delivered.

## `requestIntervalMs` (type: `integer`):

Default 1000 ms (one request per second to the same host). A large video is read in parts of 512 KB, each part is one request, so a lower value makes runs faster on hosts that allow it. Minimum 250.

## Actor input object example

```json
{
  "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,
  "intervalSeconds": 10,
  "sceneThreshold": 0.4,
  "format": "jpg",
  "quality": 85,
  "maxWidth": 1280,
  "maxVideoMegabytes": 500,
  "maxDurationMinutes": 120,
  "maxItems": 0,
  "onlyNew": false,
  "stateName": "default",
  "notifyWhenEmpty": false,
  "requestIntervalMs": 1000
}
```

# Actor output Schema

## `results` (type: `string`):

All delivered rows in the default dataset (JSON, CSV, Excel and other formats via the dataset API).

## `runSummary` (type: `string`):

Counts, warnings and why the run stopped (RUN_SUMMARY record in the default 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://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"
    ]
};

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

# Run the Actor and wait for it to finish
run = client.actor("rowcraft/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://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"
  ]
}' |
apify call rowcraft/video-frame-extractor --silent --output-dataset

```

## MCP server setup

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