How to Use FFmpeg with PHP (No Installation Required)

You need to process video in your PHP application. Resize uploads, convert formats, extract thumbnails, or generate social clips from user content. You search "php ffmpeg" and find tutorials telling you to install FFmpeg on your server and call it with exec().
That works on your local machine. It falls apart the moment you deploy.
TL;DR: You can process video from PHP without installing FFmpeg. Send an HTTP request to a cloud API, get results back. Works anywhere PHP runs: shared hosting, Laravel Forge, serverless, Docker.
The exec() Approach (And Why It Breaks)
The standard PHP approach to FFmpeg looks like this:
$input = escapeshellarg("/path/to/input.mp4");
$output = escapeshellarg("/path/to/output.mp4");
exec("ffmpeg -i {$input} -c:v libx264 -crf 23 {$output} 2>&1", $out, $code);
if ($code !== 0) {
throw new RuntimeException("FFmpeg failed: " . implode("\n", $out));
}
On your dev machine, this is fine. In production:
- Shared hosting almost never has FFmpeg installed, and you can't add it
exec()is disabled on many hosts for security reasons- Laravel Forge and similar platforms require manual FFmpeg setup per server
- Serverless environments (Laravel Vapor, Vercel) have no persistent filesystem for FFmpeg binaries
- You're responsible for codec updates, security patches, and disk space for temp files
The php-ffmpeg Library
PHP-FFmpeg wraps the binary in an object-oriented API:
$ffmpeg = FFMpeg\FFMpeg::create();
$video = $ffmpeg->open("input.mp4");
$video->filters()->resize(new FFMpeg\Coordinate\Dimension(1280, 720));
$video->save(new FFMpeg\Format\Video\X264(), "output.mp4");
Cleaner syntax, but it still requires FFmpeg installed on the machine. It's a wrapper, not a replacement. The library's GitHub repo has hundreds of open issues and updates have slowed considerably.
Processing Video via API (No FFmpeg Needed)
FFmpeg Micro is a cloud API that runs FFmpeg for you. One HTTP request from PHP, and you can transcode, resize, compress, or watermark video. No binary, no server config.
With PHP's built-in cURL:
$apiKey = getenv("FFMPEG_MICRO_API_KEY");
$ch = curl_init("https://api.ffmpeg-micro.com/v1/transcodes");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . $apiKey,
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"inputs" => [["url" => "https://example.com/video.mp4"]],
"outputFormat" => "mp4",
"preset" => ["quality" => "high", "resolution" => "1080p"],
]),
CURLOPT_RETURNTRANSFER => true,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
echo "Job ID: " . $response["id"];
If you're using Laravel or any framework with Guzzle:
use GuzzleHttp\Client;
$client = new Client([
"base_uri" => "https://api.ffmpeg-micro.com",
"headers" => ["Authorization" => "Bearer " . $apiKey],
]);
$response = $client->post("/v1/transcodes", [
"json" => [
"inputs" => [["url" => "https://example.com/video.mp4"]],
"outputFormat" => "mp4",
"preset" => ["quality" => "high", "resolution" => "1080p"],
],
]);
$job = json_decode($response->getBody(), true);
echo "Job ID: " . $job["id"];
Both approaches do the same thing. Pick whichever matches your project.
Checking Job Status and Downloading Results
Transcoding takes seconds to minutes depending on file size. Poll for status:
$jobId = $response["id"];
do {
sleep(2);
$ch = curl_init("https://api.ffmpeg-micro.com/v1/transcodes/{$jobId}");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . $apiKey],
CURLOPT_RETURNTRANSFER => true,
]);
$status = json_decode(curl_exec($ch), true);
curl_close($ch);
} while ($status["status"] === "queued" || $status["status"] === "processing");
if ($status["status"] === "completed") {
$ch = curl_init("https://api.ffmpeg-micro.com/v1/transcodes/{$jobId}/download");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . $apiKey],
CURLOPT_RETURNTRANSFER => true,
]);
$download = json_decode(curl_exec($ch), true);
curl_close($ch);
echo "Download: " . $download["url"];
}
The download endpoint returns a signed URL valid for 10 minutes. Save the file locally or redirect your user to it.
Advanced: Custom FFmpeg Options
Presets cover most cases. For full control, pass raw FFmpeg options:
$response = $client->post("/v1/transcodes", [
"json" => [
"inputs" => [["url" => "https://example.com/video.mp4"]],
"outputFormat" => "webm",
"options" => [
["option" => "-c:v", "argument" => "libvpx-vp9"],
["option" => "-crf", "argument" => "30"],
["option" => "-b:v", "argument" => "0"],
["option" => "-c:a", "argument" => "libopus"],
],
],
]);
Same FFmpeg flags you'd use on the command line, passed as structured JSON. This converts MP4 to WebM using VP9 and Opus codecs.
Uploading Local Files
If your video isn't hosted at a public URL, upload it first. Three steps:
// Step 1: Get a presigned upload URL
$presign = $client->post("/v1/upload/presigned-url", [
"json" => [
"filename" => "my-video.mp4",
"contentType" => "video/mp4",
"fileSize" => filesize("/path/to/my-video.mp4"),
],
]);
$presignData = json_decode($presign->getBody(), true);
// Step 2: Upload directly to cloud storage
$uploadClient = new Client();
$uploadClient->put($presignData["result"]["uploadUrl"], [
"headers" => ["Content-Type" => "video/mp4"],
"body" => fopen("/path/to/my-video.mp4", "r"),
]);
// Step 3: Confirm the upload
$confirm = $client->post("/v1/upload/confirm", [
"json" => [
"filename" => $presignData["result"]["filename"],
"fileSize" => filesize("/path/to/my-video.mp4"),
],
]);
$confirmData = json_decode($confirm->getBody(), true);
// Use the GCS URL in your transcode request
$gcsUrl = $confirmData["result"]["gcsUrl"];
After confirmation, pass the returned GCS URL as the input for your transcode job.
Common Pitfalls
- **Using
file_get_contentsfor API calls.** It doesn't support custom headers cleanly. Use cURL or Guzzle. - **Forgetting
json_encodeon the request body.** The API expects JSON, not form-encoded data. Set the Content-Type header and encode your payload. - Polling too aggressively. A 2-second interval is plenty. Don't hit the status endpoint every 100ms.
- **Ignoring the
failedstatus.** Always check for failure in your polling loop. An infinite loop on a failed job wastes resources and never resolves. - Hardcoding API keys. Use environment variables. In Laravel:
config("services.ffmpeg_micro.key"). In vanilla PHP:getenv("FFMPEG_MICRO_API_KEY").
Frequently Asked Questions
Does this work on shared hosting?
Yes. The API approach only needs PHP's cURL extension, which virtually every host has enabled. No FFmpeg binary, no exec(), no special server permissions.
What PHP version do I need?
PHP 7.4 or later. The code examples use modern array syntax and type handling. PHP 8+ works fine and gives you named arguments for cleaner Guzzle calls.
How much does FFmpeg Micro cost?
FFmpeg Micro bills per minute of video processed. The free tier gives you enough minutes to build and test your integration. A 5-minute video costs a few cents to transcode. Check the pricing page for current rates.
Can I process video synchronously in a web request?
You can for short clips, but you shouldn't for anything substantial. Queue the transcode job, return a 202 to your user, and poll from a background worker. Laravel's job system or a simple cron works well for this.
What video formats are supported?
MP4, WebM, and MOV for video output. The API also supports image output formats (JPEG, PNG, WebP) for extracting thumbnails. See the thumbnail guide for details on frame extraction.
*Last verified: May 2026. All API examples tested against FFmpeg Micro v1.*
About Javid Jamae
Founder & CEO at FFmpeg Micro
Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.
You might also like

How to Use FFmpeg with Node.js (No Installation Required)
Learn 4 ways to use FFmpeg with Node.js: child_process, fluent-ffmpeg, ffmpeg.wasm, and a cloud API. Working code, common pitfalls, and when to use each.

How to Use FFmpeg in Python Without Installing It
Learn how to use FFmpeg in Python without installing the binary. Use a cloud API instead of subprocess for serverless-friendly video processing.

How to Process Video with a JSON API (FFmpeg Micro Request Examples)
Learn how to process video with a JSON API using FFmpeg Micro. Real request examples for compression, format conversion, text overlays, and custom FFmpeg options.
Ready to process videos at scale?
Start using FFmpeg Micro's simple API today. No infrastructure required.
Get Started Free