# Video Frame Extractor (Direct URLs) (`myuon/video-frame-extractor`) Actor

Extract JPG or PNG frames from direct video file URLs, by interval, count or timestamp. No ffmpeg setup. Pay per frame.

- **URL**: https://apify.com/myuon/video-frame-extractor.md
- **Developed by:** [myuon](https://apify.com/myuon) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 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.

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 (direct video URLs)

Give it links to video files, get still images (JPG or PNG) back from a key-value store, with one dataset row per frame. It is meant for people who build automations in no-code tools (n8n, Make, Zapier-style flows) or AI agents and who need frames from a video but do not want to install or host ffmpeg themselves.

If you already run ffmpeg on your own machine or server, you do not need this Actor.

### What it does

- Downloads each video you list (direct `http`/`https` links to a video file only).
- Picks frames in one of three ways:
  - `interval`: one frame every N seconds (at 0, N, 2N, ...)
  - `count`: N evenly spaced frames (taken from the middle of N equal segments)
  - `timestamps`: the exact times in seconds you list (times past the end are skipped)
- Saves each frame as JPG or PNG, optionally scaled down to a maximum width.
- Writes one dataset item per frame: source URL, timestamp, public image URL, width, height, byte size.
- A video that fails produces one error item (with an error code and message) and the run continues with the next video. The run itself only fails on invalid input.

### What it does not do

- It does **not** accept YouTube, TikTok, Vimeo, Instagram, Facebook, X/Twitter, Twitch or similar page URLs. Those are rejected with a clear message. Only direct links to video files work (for example `https://example.com/media/clip.mp4`).
- It does not bypass logins, paywalls, DRM, geo-blocks or any other access control. If a URL needs credentials, it fails.
- It does not read HLS/DASH playlists, local files or other protocols.

### Your responsibility

You must have the right to download and process every video you submit, and to use the frames you get. The Actor does not check this for you.

### Input

| Field | Meaning | Default |
| --- | --- | --- |
| `videoUrls` | Direct video file URLs, up to 100 per run | required |
| `samplingMode` | `interval`, `count` or `timestamps` | `interval` |
| `intervalSeconds` | Seconds between frames (interval mode) | 10 |
| `frameCount` | Number of frames (count mode) | 5 |
| `timestamps` | List of seconds (timestamps mode) | `[]` |
| `outputFormat` | `jpg` or `png` | `jpg` |
| `maxWidth` | Scale down frames wider than this; 0 keeps the original | 0 |
| `maxFramesPerVideo` | Cap on frames per video (hard limit 200) | 30 |

Example:

```json
{
  "videoUrls": ["https://example.com/media/clip.mp4"],
  "samplingMode": "count",
  "frameCount": 8,
  "outputFormat": "jpg",
  "maxWidth": 1280
}
```

### Output

Frame item:

```json
{
  "type": "frame",
  "sourceUrl": "https://example.com/media/clip.mp4",
  "frameIndex": 0,
  "timestampSeconds": 1.25,
  "key": "video-001_t00001250ms.jpg",
  "publicUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/video-001_t00001250ms.jpg",
  "format": "jpg",
  "width": 1280,
  "height": 720,
  "byteSize": 48211,
  "videoDurationSeconds": 20
}
```

Error item:

```json
{ "type": "error", "sourceUrl": "https://example.com/missing.mp4", "errorCode": "DOWNLOAD_FAILED", "message": "The server responded with HTTP 404." }
```

Error codes include `PLATFORM_URL_NOT_SUPPORTED`, `INVALID_URL`, `UNSUPPORTED_PROTOCOL`, `BLOCKED_ADDRESS`, `TOO_MANY_REDIRECTS`, `DOWNLOAD_FAILED`, `DOWNLOAD_TIMEOUT`, `FILE_TOO_LARGE`, `NOT_A_VIDEO_FILE`, `FFMPEG_FAILED`, `FFMPEG_TIMEOUT`, `VIDEO_TOO_LONG`, `NO_FRAMES`, `SPEND_LIMIT_REACHED`.

Who can open the `publicUrl` depends on your Apify storage access settings; check them if you share the links outside your account.

### Pricing (pay per event)

Prices:

- `video-processed`: $0.005 per video that delivered at least one frame

- `frame-extracted`: $0.0015 per delivered frame

- `apify-actor-start`: a very small Apify start fee of $0.00005, charged for every run

Example: one video with 10 frames costs about $0.005 + 10 x $0.0015 = $0.02. Platform usage is included.

Apart from the start fee, you are charged only for results that were delivered. Failed videos, rejected URLs, and frames that could not be produced are free. If you set a maximum charge per run, the Actor stops cleanly when the next video would not fit: it delivers only the frames it can charge for and lists the remaining videos as `SPEND_LIMIT_REACHED` items without charging for them.

(The prices are configured in Apify Console, not in this repository; `.actor/pay_per_event.json` only records the suggestion.)

### Limits

| Limit | Value |
| --- | --- |
| Videos per run | 100 |
| Download size per video | 500 MB |
| Video duration | 2 hours |
| Frames per video | 30 by default, 200 maximum |
| Redirects followed | 5 |
| Download time per video | 5 minutes |
| ffmpeg time per operation | 60 seconds |

Requests to private, loopback, link-local and cloud-metadata addresses are blocked, including when a public hostname resolves to one and when a redirect points to one. Only `http` and `https` are allowed. ffmpeg is started without a shell and is restricted to local plain media container formats, so a crafted playlist cannot make it read local files or contact other hosts.

### FFmpeg licensing

This Actor runs FFmpeg as a separate program, installed from the Alpine package repository when the image is built. The build was checked on 2026-11-04 and is configured with `--enable-gpl --enable-version3`, so it is licensed under the GPL version 3 or later. See [FFmpeg's licensing page](https://ffmpeg.org/legal.html).

### Development

```bash
npm install
npm test        # needs ffmpeg/ffprobe; set FFMPEG_PATH / FFPROBE_PATH if they are not on PATH
npm run build
```

Tests generate their own video with the local ffmpeg and serve it from a local HTTP server. The test-only way to reach 127.0.0.1 is the `VFE_TEST_ALLOW_HOSTS` environment variable (exact `host:port` entries); it cannot be set through Actor input and is ignored on the Apify platform.

# Actor input Schema

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

Direct http/https links to video files (for example https://example.com/clip.mp4). Links to YouTube, TikTok, Vimeo, Instagram and similar pages are rejected. Up to 100 per run. You are responsible for having the right to process these videos.

## `samplingMode` (type: `string`):

interval: one frame every N seconds. count: N evenly spaced frames. timestamps: the exact times you list.

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

Used when sampling mode is 'interval'. A frame is taken at 0, N, 2N, ... seconds.

## `frameCount` (type: `integer`):

Used when sampling mode is 'count'. Frames are taken from the middle of N equal segments of the video.

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

Used when sampling mode is 'timestamps'. Times in seconds, for example 1.5 and 30. Times beyond the end of a video are skipped and not charged.

## `outputFormat` (type: `string`):

Image format for the extracted frames. JPG is smaller; PNG is lossless.

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

Frames wider than this are scaled down, keeping the aspect ratio. 0 keeps the original size.

## `maxFramesPerVideo` (type: `integer`):

Upper bound on frames from one video. Extra frames are dropped (and not charged). Hard limit: 200.

## Actor input object example

```json
{
  "videoUrls": [
    "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
  ],
  "samplingMode": "interval",
  "intervalSeconds": 10,
  "frameCount": 5,
  "timestamps": [],
  "outputFormat": "jpg",
  "maxWidth": 0,
  "maxFramesPerVideo": 30
}
```

# Actor output Schema

## `frames` (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

```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://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("myuon/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://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"] }

# Run the Actor and wait for it to finish
run = client.actor("myuon/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://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
  ]
}' |
apify call myuon/video-frame-extractor --silent --output-dataset

```

## MCP server setup

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