# Video Frame Extractor & Media Probe for AI Agents (`fluidfaith/media-probe`) Actor

Give AI agents eyes for video: send direct video or audio file links, get ffprobe metadata (duration, codecs, resolution, fps, rotation, 9:16 check), frames at set times, every N seconds or at scene cuts, a contact sheet for vision models, EBU R128 loudness and MP3 audio.

- **URL**: https://apify.com/fluidfaith/media-probe.md
- **Developed by:** [FFS Marketing](https://apify.com/fluidfaith) (community)
- **Categories:** AI, Videos, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 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

**Give your AI agent eyes and ears for video.** Send a direct link to a video or audio file; get back its metadata (duration, codecs, resolution, frame rate, rotation, aspect ratio), frames at the times you choose, every N seconds or at scene changes, one contact sheet image sized for vision models, EBU R128 loudness, and the audio as an MP3 link. One call, JSON out.

**Works as a tool for AI agents.** Add it to Claude, ChatGPT, Cursor or any MCP client through Apify's MCP server (`https://mcp.apify.com/?tools=fluidfaith/media-probe`), or call it with one API request. Agents cannot watch video and cloud agents have no ffmpeg: this Actor is that missing step.

**Costs $0.002 per file, $0.001 per frame**, $0.005 per contact sheet, $0.002 per loudness measurement and $0.003 per minute of MP3 audio. Failed links and errors are free, and your maximum charge per run is never exceeded.

### What an agent can do with it

- **See a clip:** ask for 6 to 12 evenly spaced frames plus a contact sheet, and pass the sheet to a vision model in one image.
- **Check a deliverable before posting:** is this reel 9:16, under 90 seconds, with audio, near -14 LUFS? `checks.isVertical916`, `durationSeconds`, `checks.hasAudio` and `loudness.integratedLufs` answer it.
- **Find the shots:** scene mode returns the time of every cut plus one frame per shot.
- **Transcribe:** `extractAudio` returns a small mono MP3 link ready for a speech-to-text model.

### Why use it

#### Probe, frames, contact sheet and loudness in one call

Most frame grabbers return frames only. This Actor also reads the full ffprobe metadata, runs simple delivery checks and measures loudness, so an agent gets everything it needs about a file in one row.

#### Long files are not decoded end to end

Frames are taken with a seek before decoding, so asking for 5 frames of a 2-hour file decodes only a few seconds around each time. (Scene detection has to look at the whole video, so it decodes it at 5 frames per second.)

#### Sized for vision models

Frames default to 768 px wide and are never upscaled. The contact sheet is a grid with time labels that is at most 1568 px on its long side, the size vision models read without shrinking it.

#### Direct file links only, on purpose

It reads files you can link to directly (your storage bucket, CDN, Apify key-value store, a podcast feed enclosure, a client upload). It does not download from YouTube, TikTok, Instagram, Facebook, X, Vimeo or other social and streaming sites; those links get a clear error and are not charged.

### Use it from an agent

#### Any MCP client

Add `https://mcp.apify.com/?tools=fluidfaith/media-probe` as a remote MCP server (OAuth or your Apify token). The agent sees one tool with the input fields below.

#### One API request

```bash
curl -X POST "https://api.apify.com/v2/acts/fluidfaith~media-probe/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.com/reel.mp4"], "framesMode": "count", "frameCount": 9, "contactSheet": true, "loudness": true}'
```

### Pricing

Pay per event. You only pay for what is returned.

| Event | Price | Charged when |
|---|---|---|
| File probed | $0.002 | the file's metadata is returned |
| Frame extracted | $0.001 | each frame image returned |
| Contact sheet | $0.005 | each contact sheet returned |
| Loudness measured | $0.002 | loudness numbers are returned |
| Audio minute extracted | $0.003 | each started minute of MP3 returned |

Examples:

| Job | Cost |
|---|---|
| Metadata only (`framesMode: "none"`) | $0.002 |
| Metadata + 6 frames (the default) | $0.008 |
| A reel check: metadata + 9 frames + contact sheet + loudness | $0.018 |
| A 12-minute podcast as MP3 for transcription + loudness | $0.040 |
| 20 files, metadata + 6 frames each | $0.16 |

Nothing is charged for blocked or social links, failed downloads, files that are not media, or files that pass a time limit. Set a maximum charge on the run and the Actor stops exactly there: what does not fit is skipped, listed in `warnings` and not charged, and `stoppedAtSpendingCap` is true in the run summary.

### Input

| Field | Default | What it does |
|---|---|---|
| `urls` | required unless `files` given | Direct http(s) links to video or audio files (up to 20). |
| `files` | `[]` | Up to 5 small files as `{ "name": "clip.mp4", "base64": "..." }`, 6 MB each. |
| `framesMode` | `count` | `none`, `count` (evenly spaced), `every` (every N seconds), `times` (your list), `scene` (at cuts). |
| `frameCount` | 6 | Frames for `count` mode (1 to 100). |
| `everyNSeconds` | 10 | Seconds between frames for `every` mode. |
| `frameTimes` | `[]` | Times for `times` mode: seconds (`12.5`) or `"mm:ss"` / `"hh:mm:ss"`. |
| `sceneThreshold` | 0.3 | Scene mode sensitivity, 0.05 (more cuts) to 0.95 (only hard cuts). |
| `maxFramesPerFile` | 100 | Hard limit on frames per file. |
| `frameWidth` | 768 | Frame width in px (64 to 3840), never upscaled. |
| `frameFormat` | `jpg` | `jpg`, `webp` or `png`. |
| `contactSheet` | `false` | Also return a grid image of the frames with time labels. |
| `loudness` | `false` | EBU R128 integrated loudness, loudness range and true peak. |
| `extractAudio` | `false` | Return the first audio track as MP3. |
| `audioQuality` | `speech` | `speech` (mono 64 kbps) or `music` (stereo 192 kbps). |
| `audioMaxMinutes` | 30 | Audio is cut after this many minutes (up to 180). |
| `maxFileSizeMb` | 500 | Larger files are refused before download (hard limit 2048). |
| `maxDurationSeconds` | 0 | Use only the first N seconds for frames, scenes, loudness and audio (0 = whole file). |
| `maxConcurrency` | 2 | Files at the same time (1 to 4). |

Example:

```json
{
  "urls": ["https://example.com/reel.mp4"],
  "framesMode": "scene",
  "contactSheet": true,
  "loudness": true
}
```

### Output

One dataset row per file:

```json
{
  "source": "https://example.com/reel.mp4",
  "fileName": "reel.mp4",
  "status": "OK",
  "error": null,
  "durationSeconds": 28.4,
  "container": "mp4",
  "containerLongName": "QuickTime / MOV",
  "sizeBytes": 9123456,
  "bitRate": 2570000,
  "creationTime": "2026-11-02T15:04:11.000000Z",
  "streams": { "video": 1, "audio": 1, "subtitle": 0, "coverArt": 0 },
  "video": {
    "codec": "h264", "profile": "High", "width": 1920, "height": 1080,
    "displayWidth": 1080, "displayHeight": 1920, "rotation": 90,
    "fps": 29.97, "aspectRatio": "9:16", "pixelFormat": "yuv420p", "bitRate": 2400000, "frameCount": 851
  },
  "audio": { "codec": "aac", "profile": "LC", "sampleRate": 48000, "channels": 2, "channelLayout": "stereo", "bitRate": 128000 },
  "checks": { "hasVideo": true, "hasAudio": true, "isVertical": true, "isVertical916": true, "isHorizontal169": false },
  "loudness": { "integratedLufs": -14.2, "loudnessRangeLu": 6.1, "truePeakDbtp": -1.3, "thresholdLufs": -24.3, "silent": false },
  "frames": [
    { "index": 1, "timeSeconds": 0.0, "url": "https://api.apify.com/v2/key-value-stores/<id>/records/01-reel-mp4-1a2b3c-frame-001.jpg?signature=<signature>", "key": "01-reel-mp4-1a2b3c-frame-001.jpg", "width": 768, "height": 1366 }
  ],
  "sceneChanges": [3.2, 7.8, 12.0, 19.4],
  "contactSheetUrl": "https://api.apify.com/v2/key-value-stores/<id>/records/01-reel-mp4-1a2b3c-contact-sheet.jpg?signature=<signature>",
  "contactSheet": { "columns": 3, "rows": 2, "tiles": 5, "width": 1192, "height": 1550, "timeLabels": true },
  "audioUrl": null,
  "audioDurationSeconds": null,
  "charged": { "probes": 1, "frames": 5, "contactSheets": 1, "loudness": 1, "audioMinutes": 0, "usd": 0.014 },
  "warnings": []
}
```

- `status`: `OK`, `PARTIAL` (metadata returned but something asked for was skipped: spending limit or a frame that could not be read; see `warnings`) or `ERROR` (nothing returned, nothing charged; see `error`).
- `rotation` is the clockwise rotation a player applies; `displayWidth` and `displayHeight` are what viewers see, and the checks use them.
- `aspectRatio` is the nearest common label within 2% (16:9, 9:16, 4:3, 1:1, 4:5, 21:9 and so on), else the exact ratio.
- Frame, contact sheet and MP3 links point to the run's key-value store and follow your Apify storage retention.

The run summary (key-value store record `OUTPUT`) holds counts, the total charged, `stoppedAtSpendingCap` and the status of every file.

### Limits

- Up to 20 links per run, 100 frames per file, 2048 MB per file (500 MB by default).
- Time limits: download 5 minutes, each ffmpeg step 2 minutes (scene detection, loudness and audio 7 minutes), each file 10 minutes. A file that passes a limit is an `ERROR` row and is not charged.
- Formats: whatever ffmpeg reads in mp4, mov, m4a, webm, mkv, avi, flv, ts, mpeg, wmv/asf, mxf, mp3, wav, ogg, opus, flac, aac, aiff and similar containers. Streaming playlists (HLS .m3u8, DASH) are refused: give a direct link to one file.

### Privacy and safety

- Files are downloaded into the run's temporary folder and deleted when the file is done. Only the frames, contact sheet and MP3 you asked for are stored, in the run's own storage.
- Only public http(s) links are fetched. Localhost, private networks, cloud metadata addresses and internal host names are refused, also when a redirect or a DNS answer points there.
- ffmpeg only ever reads the downloaded local file: it cannot open network addresses or other files on disk.
- Metadata tags that can hold personal data (GPS location, device owner) are not returned; only `creationTime` is.

### FAQ

#### Why no YouTube, TikTok or Instagram links?

Those sites' terms forbid downloading outside their apps, they change often, and Actors that depend on them fail a lot. This Actor is for files you have a direct link to and the right to process: your own uploads, client deliverables, your storage bucket or CDN. If you have the rights to a social video, download it with the platform's own tools and pass the file link.

#### My link is a Google Drive or Dropbox share link and it fails.

Share links open a web page. Use the direct download form (Dropbox: change `dl=0` to `dl=1`; Google Drive: a direct download link for public files) or put the file in any storage that gives a direct URL.

#### How do I check a reel before posting?

`{"urls": ["<file>"], "framesMode": "count", "frameCount": 9, "contactSheet": true, "loudness": true}`. Read `checks.isVertical916`, `durationSeconds`, `checks.hasAudio` and `loudness.integratedLufs` (most social platforms normalize near -14 LUFS), and look at the contact sheet.

#### Is a frame at an exact time exact?

Yes. The seek goes to the keyframe before your time and decodes forward to it, so the frame is the one at (or right after) the time you asked for.

#### What happens at my spending limit?

Metadata comes first, then frames, the contact sheet, loudness and audio, as far as your maximum charge allows. Anything skipped is listed in `warnings`, the row is `PARTIAL`, and nothing past your maximum is ever charged.

### Other Actors by Fluid Faith

- [Mobile Website Test on iPhone & Android (Mobile QA Gate)](https://apify.com/fluidfaith/mobile-qa-gate): PASS or FAIL per page on today's phones, with screenshots. $0.01 per page per phone.
- [Lighthouse & Accessibility Audit](https://apify.com/fluidfaith/lighthouse-gate): Lighthouse scores, Core Web Vitals and axe issues, median of 3 runs, PASS/FAIL and top fixes. $0.02 per URL per form factor.
- [PDF to Markdown with Tables](https://apify.com/fluidfaith/pdf-to-markdown-tables): PDF, Word, PowerPoint and Excel to Markdown with every table as JSON rows. $0.002 per page.

Load them together as MCP tools: `https://mcp.apify.com/?tools=fluidfaith/mobile-qa-gate,fluidfaith/lighthouse-gate,fluidfaith/pdf-to-markdown-tables,fluidfaith/media-probe`

### Credits

Built on [FFmpeg](https://ffmpeg.org) (ffprobe and ffmpeg, run as separate programs). Made by Fluid Faith Solutions.

# Actor input Schema

## `urls` (type: `array`):

Direct http(s) links to video or audio files, one per line (up to 20). Each link must return the file itself, not a web page. YouTube, TikTok, Instagram, Facebook, X, Vimeo and other page links are refused. Localhost, private networks and internal hosts are refused.

## `files` (type: `array`):

For agents that already hold a small file: a list of {"name": "clip.mp4", "base64": "..."} objects. Up to 5 files, 6 MB each after decoding (Apify limits the whole input to about 9 MB).

## `framesMode` (type: `string`):

How to pick frames. count: frameCount frames spread evenly over the file. every: one frame every everyNSeconds starting at 0. times: the times in frameTimes. scene: the first frame plus one frame at every cut (the video is decoded at 5 fps to find cuts). Frames are found by seeking, so long files are not decoded end to end (except in scene mode).

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

Number of evenly spaced frames, taken at the middle of equal slices of the file (so never a black first frame).

## `everyNSeconds` (type: `number`):

Seconds between frames, starting at 0.

## `frameTimes` (type: `array`):

Times in seconds (12.5) or as "mm:ss" / "hh:mm:ss". Times past the end of the file are skipped with a warning. Up to 100.

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

0.05 to 0.95. Lower finds more cuts (including soft ones); higher finds only hard cuts. 0.3 suits most edited video.

## `maxFramesPerFile` (type: `integer`):

Frames beyond this limit are not taken and not charged.

## `frameWidth` (type: `integer`):

Frames are scaled to this width, keeping the aspect ratio, and never upscaled. 768 is a good size for vision models.

## `frameFormat` (type: `string`):

jpg (small, default), webp (smaller) or png (lossless, larger).

## `contactSheet` (type: `boolean`):

Also return one grid image of the frames with time labels, at most 1568 px on its long side, so a vision model can see the whole clip in one image. With frames set to none, the sheet uses 9 evenly spaced frames that are not returned separately.

## `loudness` (type: `boolean`):

Measure integrated loudness (LUFS), loudness range (LU) and true peak (dBTP) of the first audio track.

## `extractAudio` (type: `boolean`):

Return the first audio track as an MP3 link, for transcription or review. Billed per started minute.

## `audioQuality` (type: `string`):

speech: mono 64 kbps 22 kHz (an hour is about 28 MB). music: stereo 192 kbps 44.1 kHz.

## `audioMaxMinutes` (type: `integer`):

Audio is cut after this many minutes (at most 180, or 60 with music quality).

## `maxFileSizeMb` (type: `integer`):

Larger files are not downloaded and not charged. Hard limit 2048 MB.

## `maxDurationSeconds` (type: `integer`):

0 means the whole file. Otherwise frames, scene detection, loudness and audio cover only the first N seconds (metadata always describes the whole file).

## `maxConcurrency` (type: `integer`):

1 to 4. Lowered automatically to fit the run memory (one file per GB) and disk.

## Actor input object example

```json
{
  "urls": [
    "https://api.apify.com/v2/key-value-stores/zfUhFlmnDjZd41UIE/records/sample-5s.mp4?signature=vMpp1ag9JwR8SkEgV5iE"
  ],
  "files": [],
  "framesMode": "count",
  "frameCount": 6,
  "everyNSeconds": 10,
  "frameTimes": [],
  "sceneThreshold": 0.3,
  "maxFramesPerFile": 100,
  "frameWidth": 768,
  "frameFormat": "jpg",
  "contactSheet": false,
  "loudness": false,
  "extractAudio": false,
  "audioQuality": "speech",
  "audioMaxMinutes": 30,
  "maxFileSizeMb": 500,
  "maxDurationSeconds": 0,
  "maxConcurrency": 2
}
```

# Actor output Schema

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

No description

## `summary` (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 = {
    "urls": [
        "https://api.apify.com/v2/key-value-stores/zfUhFlmnDjZd41UIE/records/sample-5s.mp4?signature=vMpp1ag9JwR8SkEgV5iE"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fluidfaith/media-probe").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 = { "urls": ["https://api.apify.com/v2/key-value-stores/zfUhFlmnDjZd41UIE/records/sample-5s.mp4?signature=vMpp1ag9JwR8SkEgV5iE"] }

# Run the Actor and wait for it to finish
run = client.actor("fluidfaith/media-probe").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 '{
  "urls": [
    "https://api.apify.com/v2/key-value-stores/zfUhFlmnDjZd41UIE/records/sample-5s.mp4?signature=vMpp1ag9JwR8SkEgV5iE"
  ]
}' |
apify call fluidfaith/media-probe --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fluidfaith/media-probe"
        }
    }
}
```

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/m3eO3sczWEfAzyY2c/builds/MCNA3am0K363QJ8L9/openapi.json
