FFprobe Video Metadata: Duration, Codec & Resolution (Bulk) avatar

FFprobe Video Metadata: Duration, Codec & Resolution (Bulk)

Pricing

from $3.20 / 1,000 video probeds

Go to Apify Store
FFprobe Video Metadata: Duration, Codec & Resolution (Bulk)

FFprobe Video Metadata: Duration, Codec & Resolution (Bulk)

Get video metadata for a list of video file URLs: duration, container, video and audio codec, resolution, frame rate, bitrate, rotation, audio channels and tags. Reads the header with HTTP range requests, no full download. Direct files only, no YouTube.

Pricing

from $3.20 / 1,000 video probeds

Rating

0.0

(0)

Developer

Data Done Right

Data Done Right

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Security

Limited permissions

Share

Get the video metadata of a whole list of video file URLs with ffprobe in the cloud: duration, container, video and audio codec, resolution, frame rate, bit rate, rotation, audio channels and the file tags, one row per URL. The Actor reads only the header of each file with HTTP range requests, so a multi-gigabyte video is probed in seconds instead of being downloaded.

What it does not do. It reads direct file URLs (mp4, mov, webm, mkv, avi, flv, ts, mp3 and other audio/video files). It does not read YouTube, Vimeo, TikTok, Instagram or other streaming-site pages and rejects them without charge. HLS/DASH playlists (.m3u8, .mpd) are not supported. It reads metadata only: it does not decode, convert, cut or analyse the picture. Files behind a login are not reachable. Use it only on files you have the right to access.

What you get

  • One dataset row per URL: duration, container, codecs, width and height, fps, bit rates, rotation, HDR flag, audio channels and sample rate, file size, tags, and a clear status or error.
  • Large files are probed from their first and last few MB (range requests); if the header is elsewhere, the Actor downloads the file once (up to a cap you set) and says so in the row.
  • Failed, rejected and non-video links never stop the run and are free.

How to get the duration and resolution of many videos from URLs

  1. Paste direct video file links in videoUrls, for example https://example.com/clip.mp4.
  2. Run it. Each URL becomes one row, in input order, with durationSeconds, durationTimecode, width, height, fps, videoCodec, audioCodec and more.
  3. Export the dataset as CSV, Excel or JSON, or read it through the API.

How to check a video file without downloading it

Leave fullDownloadFallback on and headTailMb at 4. The Actor asks the server for the first 4 MB and the last 4 MB (most mp4, mov, mkv, webm, avi, flv and ts files keep their header in one of the two) and runs ffprobe on them. The row's probeMethod shows what happened: range (head and tail read), range-full or full (the file was small enough to read completely), stream-head (the server ignores range requests, so only the start was read; duration may be missing and partialProbe is true).

Run your list and filter on status. error rows explain why: a web page instead of a file, HTTP 404, a streaming-site link, a private address, an unreadable or corrupt file. Only ok rows are charged.

How to read audio files too

Audio files (mp3, wav, flac, m4a, ogg) work the same way: hasVideo is false and the audio fields are filled.

Input

FieldDefaultWhat it does
videoUrlssample videoDirect file links, one result row each (required).
deduplicateonProbe each distinct URL once.
maxVideos1000Hard cap per run (max 10000).
includeStreamsoffAdd a streams list with every stream's details.
headTailMb4MB fetched from the start and from the end of each file.
fullDownloadFallbackonDownload the whole file if the header is not at the start or end.
maxDownloadMb200Largest file the fallback may download (max 2048).
videoTimeoutSecs90Time limit per video.
concurrency5Videos read in parallel (max 20).

Input example:

{
"videoUrls": [
"https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4",
"https://upload.wikimedia.org/wikipedia/commons/c/c0/Big_Buck_Bunny_4K.webm"
],
"includeStreams": false
}

Output

From a real local run on the first URL above (tags shortened):

{
"index": 1,
"sourceUrl": "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4",
"status": "ok", "error": null, "note": null,
"probeMethod": "range-full", "partialProbe": false, "contentType": "video/mp4", "fileSizeBytes": 1128375,
"container": "mov,mp4,m4a,3gp,3g2,mj2", "containerName": "QuickTime / MOV",
"durationSeconds": 5.055, "durationTimecode": "00:00:05.055", "bitRate": 1785756,
"hasVideo": true, "hasAudio": true, "videoStreams": 1, "audioStreams": 1, "subtitleStreams": 0,
"videoCodec": "h264", "videoProfile": "High", "width": 960, "height": 540, "rotation": 0,
"aspectRatio": "16:9", "fps": 29.97, "frameCount": 150, "pixelFormat": "yuv420p", "hdr": false, "videoBitRate": 1538137,
"audioCodec": "aac", "audioChannels": 2, "audioChannelLayout": "stereo", "audioSampleRate": 48000,
"audioBitRate": 256000, "audioLanguage": "und",
"title": null, "creationTime": "2018-03-07T15:21:21.000000Z", "encoder": null,
"tags": {"major_brand": "mp42", "creation_time": "2018-03-07T15:21:21.000000Z"},
"streams": null
}

The second URL, a 2.96 GB 4K WebM, was read with two range requests (probeMethod: "range") and reported 634.553 s, 4000x2250, VP8 video and 5.1 Vorbis audio; the whole local run took about five seconds. An error row looks like {"status": "error", "error": "www.youtube.com is a video site page, not a direct video file...", "videoCodec": null}.

Field notes: width and height are the coded size; if rotation is 90 or 270 a player shows them swapped. fps is the stream's average frame rate; absurd values stored in a file (above 1000) are dropped. bitRate is read from the file or, when missing, estimated from size and duration. The SUMMARY record in the key-value store counts ok, failed and skipped videos.

Use with the Apify API, MCP, n8n and Make

Python:

from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("data-done-right/ffprobe-video-metadata").call(run_input={
"videoUrls": ["https://example.com/a.mp4", "https://example.com/b.mov"]})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
print(row["sourceUrl"], row["durationSeconds"], row["width"], row["height"], row["error"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<APIFY_TOKEN>' });
const run = await client.actor('data-done-right/ffprobe-video-metadata').call({
videoUrls: ['https://example.com/a.mp4'], includeStreams: true });
const { items } = await client.dataset(run.defaultDatasetId).listItems();

In n8n or Make use the Apify node with the Actor name and the same JSON. With Apify's MCP server (mcp.apify.com) an AI assistant can call it: "what codec and resolution is this mp4?" Only a URL is needed.

Use cases

  • Audit a media library or CDN: find files over a resolution, duration or bit-rate limit.
  • Check uploads before they enter an encoding pipeline: readable, expected codec, has audio.
  • Sort a list of clips by duration or orientation (rotation) before editing.
  • Find videos without an audio track, or with more than one.
  • Build a catalogue of video files with size, duration and creation date.

How much does it cost

Pay per event: $0.004 per video probed (video-probed, $4 per 1,000). Failed and skipped links, rejected URLs and the SUMMARY record are free. Plan discounts apply (Silver about 10%, Gold and higher about 20%).

  • 1,000 videos: $4.
  • 10,000 videos: $40.
  • 100,000 videos: $400.

maxVideos caps a run, and if you set a maximum spend per run the Actor stops cleanly when it is reached and marks the remaining rows skipped. The price is per video whatever its size; Apify platform usage is included.

FAQ

Does it download the whole video?

Normally no: it fetches the first and last few MB with HTTP range requests. If the metadata sits somewhere else in the file (some mp4 files), it downloads the whole file once, up to maxDownloadMb (200 by default), and the row's probeMethod says full. Turn the fallback off with fullDownloadFallback: false if you never want that; such files then give an error row.

Which servers work?

Any public HTTP or HTTPS server. Servers that ignore range requests still work but only the start of the file is read, so formats that keep the duration at the end may show no duration (partialProbe: true). In local tests, mp4 (header at start or end), mov, mkv, webm, avi, flv, ts and mp3 files were read correctly from a range-capable test server; the large-file test was against Wikimedia. It has not been tested on Apify yet.

No. YouTube, Vimeo, TikTok, Instagram and similar page links are rejected with a message and no charge, as are m3u8/mpd playlists. Use a direct link to a media file.

Is the duration exact?

It is the duration stored in the file or computed by ffprobe from its header, the same value ffprobe -show_format prints. It is not measured by decoding the video. Broken or truncated files can report wrong values.

What about private or internal URLs?

Links to private, loopback and internal addresses are blocked, and every redirect hop is checked. ffprobe itself never opens a network connection; it only reads the local copy of the header bytes.

Which fields can be empty?

Everything that the file does not contain: fps and frameCount for audio files, audio fields for silent videos, creationTime and title when the tags are missing, frameCount for many mkv and ts files.

  • Video Frame Extractor: Keyframes, Scenes & Thumbnails for AI pulls still images from the same videos.
  • Cut Video: Video Trimmer, Clip Cutter & Video to GIF cuts clips out of the files you just checked.
  • Image Metadata & EXIF Extractor: GPS, Camera, Dates (Bulk) does the same metadata job for photos.