Developer Tutorials – Jun 11, 2026 – 5 min read
FFmpeg API: Python, Node.js, Java, C# & PHP — REST Without a Server

FFmpeg via REST API in Python, Node.js, Java, C# & PHP — No Server Required
TL;DR - Run any FFmpeg command over HTTP — no binary to install, no server to configure, no OS-level dependency headaches. - A single
POSTrequest with your file and FFmpeg flags returns a download URL for the converted output. - Convertfleet's FFmpeg REST API accepts raw FFmpeg flag strings, so the full power of FFmpeg is accessible from every language. - Below are production-ready, copy-paste examples for Python, Node.js, Java, C#, and PHP — each handling upload, polling, and error recovery.
Most FFmpeg tutorials assume you have the binary installed. That assumption quietly breaks every Docker image you build on Alpine, every AWS Lambda function bumping against the 50 MB zipped package limit (250 MB unzipped, per AWS documentation, 2024), and every shared host where apt-get is off the table. This article takes a different angle: treat FFmpeg as a REST API. Send a file, declare your flags, get back a converted file. No installation, no PATH hacks, no libavcodec version mismatch at 2 a.m.
If you're building in Python, Node.js, Java, C#, or PHP, you'll find a complete, working code sample for your stack below — not a skeleton, a real polling loop with error handling you can drop into a project today.
What Is an FFmpeg REST API and Why Does It Exist?
An FFmpeg REST API is a hosted HTTP service that accepts a source file and FFmpeg flags, runs the encoding job on remote infrastructure, and returns the output via a download URL. You never install the binary. The API layer exists because FFmpeg has no native HTTP interface — it is a CLI and a set of C libraries that require a wrapper to become network-accessible.
FFmpeg itself ships as a command-line tool and four core C libraries: libavcodec (codec), libavformat (mux/demux), libavfilter (filter graphs), and libswscale (pixel-format conversion). The REST layer is built on top by services that run FFmpeg internally and expose the results over HTTPS — giving every language the same access that C developers have through the native API, with zero compilation overhead.
Why developers reach for the API instead of the library:
- No binary management. FFmpeg static builds for Linux/macOS/Windows run 40–100 MB depending on enabled codecs. Cloudflare Workers caps script bundles at 1 MB compressed (Cloudflare documentation, 2024). Lambda and most serverless platforms leave too little headroom once your actual application code is included.
- No compilation. Building FFmpeg with non-GPL codecs (AAC, H.264, VP9) from source takes 20–45 minutes and produces environment-specific binaries that break on OS upgrades. Every CI pipeline that compiles FFmpeg is a pipeline that occasionally hangs for an hour on a codec dependency.
- Language agnosticism. FFmpeg's native C API requires linking against half a dozen shared libraries. Every language in this article covers the same functionality with a three-line HTTP call, and the REST endpoint accepts the exact same flag syntax you already know from the CLI.
- Instant scaling. A single developer laptop cannot encode 50 concurrent 4K files. A REST API offloads that problem to purpose-built infrastructure with no changes to your application code.
According to the FFmpeg project, the tool powers video processing on over one billion mobile devices through embedded usage in Android and iOS — meaning it is absolutely the right engine for media work. The question is just where you run it.
How to Run FFmpeg Commands via REST API Without a Server
The fastest path from zero to a converted file takes five steps regardless of language. No server provisioning, no apt-get install ffmpeg, no Docker layer caching ffmpeg binaries into your images.
- Get an API key. Sign up at Convertfleet.com — the free tier requires no credit card and covers every example in this article.
- Upload your source file via a
multipart/form-dataPOST request. Alternatively, pass a public URL in theinput_urlfield if the file is already hosted — skips the upload step entirely for cloud-to-cloud workflows. - Specify your FFmpeg flags in the
flagsfield. Use the exact same syntax you would on the command line:-c:v libvpx-vp9 -b:v 1M -crf 33. No preset translation, no format abstraction — raw flags pass directly to the FFmpeg process. - Receive a job ID from the API response and poll the
/jobs/{job_id}endpoint untilstatusiscompletedorfailed. Files under ~10 MB typically resolve in under 3 seconds; large 4K encodes may take 30–90 seconds depending on the codec and filter complexity. - Download the output from
output_urlin the completed job response. URLs expire after one hour by default — download to your own storage immediately rather than storing the URL.
POST /v1/ffmpeg/convert
↓ returns job_id
GET /v1/jobs/{job_id} ← poll every 1 s, cap at 300 s
↓ status = "completed"
GET output_url ← download immediately; URL expires in 1 h
That is the complete mental model. The code below implements it in each language, including the error paths most tutorials skip.
FFmpeg Python API: Full Working Example
The Python FFmpeg REST API pattern uses requests for HTTP and standard library modules for everything else. This function handles upload, job submission, polling, and failure — production-ready with no additional dependencies beyond requests.
import os
import time
import requests
API_KEY = os.environ["CONVERTFLEET_KEY"] # never hardcode secrets
BASE = "https://api.convertfleet.com/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
POLL_INTERVAL = 1 # seconds between status checks
POLL_TIMEOUT = 300 # abort after 5 minutes
def ffmpeg_convert(input_path: str, output_format: str, flags: str = "") -> str:
"""
Upload a local file, run an FFmpeg job via REST, return the output URL.
Raises RuntimeError on FFmpeg failure or poll timeout.
Raises requests.HTTPError on HTTP errors (4xx/5xx).
"""
with open(input_path, "rb") as fh:
resp = requests.post(
f"{BASE}/ffmpeg/convert",
headers=HEADERS,
files={"input": fh},
data={"output_format": output_format, "flags": flags},
timeout=120, # generous for large files
)
resp.raise_for_status() # 422 = malformed flags — read the body
job = resp.json()
elapsed = 0
while job["status"] not in ("completed", "failed"):
if elapsed >= POLL_TIMEOUT:
raise RuntimeError(f"Job {job['job_id']} timed out after {POLL_TIMEOUT}s")
time.sleep(POLL_INTERVAL)
elapsed += POLL_INTERVAL
job = requests.get(
f"{BASE}/jobs/{job['job_id']}",
headers=HEADERS,
timeout=10,
).json()
if job["status"] == "failed":
raise RuntimeError(f"FFmpeg job failed: {job.get('error', 'unknown')}")
return job["output_url"]
# Example 1: MP4 → WebM with VP9 + Opus (modern web delivery)
url = ffmpeg_convert(
"promo.mp4", "webm",
"-c:v libvpx-vp9 -b:v 0 -crf 33 -row-mt 1 -c:a libopus -b:a 96k"
)
print(f"WebM: {url}")
# Example 2: Extract audio, normalise loudness to -14 LUFS (streaming standard)
audio_url = ffmpeg_convert(
"interview.mp4", "mp3",
"-vn -af loudnorm=I=-14:TP=-1:LRA=11 -b:a 192k"
)
print(f"Audio: {audio_url}")
# Example 3: Generate a contact-sheet thumbnail grid (4×4, one frame every 10 s)
thumb_url = ffmpeg_convert(
"documentary.mp4", "jpg",
"-vf 'fps=1/10,scale=320:-1,tile=4x4' -frames:v 1 -q:v 3"
)
print(f"Thumbnail grid: {thumb_url}")
Key design decisions in this code:
- timeout=120 on the upload handles large files (500 MB+) without a ReadTimeout.
- The POLL_TIMEOUT guard prevents an infinite loop when a job stalls on a corrupt source file.
- The -b:v 0 -crf 33 flag combination on VP9 uses constrained quality mode, producing better results than bitrate-capped encoding for web delivery.
For async contexts (FastAPI, Django Channels), swap requests for httpx and add async/await — httpx.AsyncClient is a drop-in replacement with an identical interface. Replace time.sleep(1) with await asyncio.sleep(1).
FFmpeg Node.js REST API: Full Working Example
Node 18+ ships with native fetch. The form-data package handles multipart uploads cleanly across all Node versions. The example below includes parallel batch processing — the pattern most teams need once they move past single-file demos.
// Node 18+: native fetch. npm install form-data (all versions)
const FormData = require("form-data");
const fs = require("fs");
const API_KEY = process.env.CONVERTFLEET_KEY;
const BASE = "https://api.convertfleet.com/v1";
const authHeader = { Authorization: `Bearer ${API_KEY}` };
async function ffmpegConvert(inputPath, outputFormat, flags = "") {
const form = new FormData();
form.append("input", fs.createReadStream(inputPath));
form.append("output_format", outputFormat);
form.append("flags", flags);
const res = await fetch(`${BASE}/ffmpeg/convert`, {
method: "POST",
headers: { ...authHeader, ...form.getHeaders() },
body: form,
});
if (!res.ok) throw new Error(`Upload failed: ${res.status} ${await res.text()}`);
let job = await res.json();
const deadline = Date.now() + 300_000; // 5-minute cap
while (!["completed", "failed"].includes(job.status)) {
if (Date.now() > deadline) throw new Error(`Job ${job.job_id} timed out`);
await new Promise((r) => setTimeout(r, 1000));
job = await fetch(`${BASE}/jobs/${job.job_id}`, { headers: authHeader })
.then((r) => r.json());
}
if (job.status === "failed") throw new Error(`FFmpeg error: ${job.error ?? "unknown"}`);
return job.output_url;
}
// Batch: convert a folder of MP4s to HLS in parallel
async function batchToHLS(filePaths) {
const results = await Promise.all(
filePaths.map((p) =>
ffmpegConvert(p, "m3u8", "-c:v copy -c:a copy -f hls -hls_time 6 -hls_playlist_type vod")
.then((url) => ({ file: p, url, ok: true }))
.catch((err) => ({ file: p, error: err.message, ok: false }))
)
);
return results;
}
batchToHLS(["ep1.mp4", "ep2.mp4", "ep3.mp4"])
.then(console.table)
.catch(console.error);
Do not await in a for loop when processing batches. Sequential processing turns a 20-file batch into a 5-minute wait; Promise.all runs all jobs concurrently. The .catch inside the map prevents one failed job from aborting the entire batch — each result carries its own ok flag. See our batch conversion guide for the rate-limit-aware queuing pattern when processing hundreds of files.
FFmpeg Java API: Full Working Example
OkHttp 4.x and Gson are the standard pairing for HTTP work in enterprise and Android Java projects. The code below compiles against Java 11+ and Android API 21+.
// Maven: com.squareup.okhttp3:okhttp:4.12.0 com.google.code.gson:gson:2.10.1
import com.google.gson.*;
import okhttp3.*;
import java.io.*;
import java.util.concurrent.TimeUnit;
public class ConvertfleetFFmpeg {
private static final String API_KEY = System.getenv("CONVERTFLEET_KEY");
private static final String BASE = "https://api.convertfleet.com/v1";
// Reuse one OkHttpClient across all calls — thread-safe, connection-pooled
private static final OkHttpClient CLIENT = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.build();
public static String convert(String inputPath, String outputFormat, String flags)
throws IOException, InterruptedException {
RequestBody body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("input", new File(inputPath).getName(),
RequestBody.create(new File(inputPath),
MediaType.parse("application/octet-stream")))
.addFormDataPart("output_format", outputFormat)
.addFormDataPart("flags", flags)
.build();
Request uploadReq = new Request.Builder()
.url(BASE + "/ffmpeg/convert")
.header("Authorization", "Bearer " + API_KEY)
.post(body)
.build();
JsonObject job;
try (Response r = CLIENT.newCall(uploadReq).execute()) {
if (!r.isSuccessful()) throw new IOException("Upload failed: " + r.code());
job = JsonParser.parseString(r.body().string()).getAsJsonObject();
}
long deadline = System.currentTimeMillis() + 300_000L;
String status = job.get("status").getAsString();
while (!status.equals("completed") && !status.equals("failed")) {
if (System.currentTimeMillis() > deadline)
throw new RuntimeException("Job timed out: " + job.get("job_id").getAsString());
Thread.sleep(1_000);
Request pollReq = new Request.Builder()
.url(BASE + "/jobs/" + job.get("job_id").getAsString())
.header("Authorization", "Bearer " + API_KEY)
.build();
try (Response r = CLIENT.newCall(pollReq).execute()) {
job = JsonParser.parseString(r.body().string()).getAsJsonObject();
}
status = job.get("status").getAsString();
}
if ("failed".equals(status))
throw new RuntimeException("FFmpeg error: " + job.get("error").getAsString());
return job.get("output_url").getAsString();
}
public static void main(String[] args) throws Exception {
// WAV → AAC for mobile delivery (44.1 kHz, 128 kbps)
String url = convert("recording.wav", "aac", "-b:a 128k -ar 44100 -movflags +faststart");
System.out.println("Output: " + url);
}
}
Android note: On Android API 21+, move the network call off the main thread using ExecutorService or Kotlin coroutines — OkHttp supports both. The d/platformdecodermodule va-api ffmpeg is disabled by platform message you may see in Logcat is a device hardware-decoder log, not a REST API error. See the dedicated FAQ entry below.
FFmpeg C# API: Full Working Example
The C# implementation uses HttpClient (built into .NET 6+) and System.Text.Json — no NuGet packages required. This pattern works in ASP.NET Core, .NET MAUI, console applications, and Azure Functions.
using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
public static class ConvertfleetClient
{
// Reuse a single HttpClient — avoids socket exhaustion under load
private static readonly HttpClient Http = new();
private const string Base = "https://api.convertfleet.com/v1";
public static async Task<string> ConvertAsync(
string inputPath,
string outputFormat,
string flags = "",
CancellationToken ct = default)
{
var apiKey = Environment.GetEnvironmentVariable("CONVERTFLEET_KEY")
?? throw new InvalidOperationException("CONVERTFLEET_KEY is not set.");
// Step 1 — multipart upload
await using var fileStream = File.OpenRead(inputPath);
using var form = new MultipartFormDataContent();
var fileContent = new StreamContent(fileStream);
fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
form.Add(fileContent, "input", Path.GetFileName(inputPath));
form.Add(new StringContent(outputFormat), "output_format");
form.Add(new StringContent(flags), "flags");
using var uploadReq = new HttpRequestMessage(HttpMethod.Post, $"{Base}/ffmpeg/convert");
uploadReq.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
uploadReq.Content = form;
var uploadRes = await Http.SendAsync(uploadReq, ct);
uploadRes.EnsureSuccessStatusCode();
var json = await uploadRes.Content.ReadAsStringAsync(ct);
using var doc = JsonDocument.Parse(json);
var job = doc.RootElement.Clone();
// Step 2 — poll with a 5-minute deadline
var deadline = DateTimeOffset.UtcNow.AddMinutes(5);
while (job.GetProperty("status").GetString() is not ("completed" or "failed"))
{
if (DateTimeOffset.UtcNow > deadline)
throw new TimeoutException($"Job timed out: {job.GetProperty("job_id")}");
await Task.Delay(1000, ct);
using var pollReq = new HttpRequestMessage(
HttpMethod.Get, $"{Base}/jobs/{job.GetProperty("job_id").GetString()}");
pollReq.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
var pollRes = await Http.SendAsync(pollReq, ct);
json = await pollRes.Content.ReadAsStringAsync(ct);
job = JsonDocument.Parse(json).RootElement.Clone();
}
if (job.GetProperty("status").GetString() == "failed")
throw new Exception($"FFmpeg error: {job.GetProperty("error").GetString()}");
return job.GetProperty("output_url").GetString()!;
}
}
// Usage — MKV to web-optimised MP4 (H.264, fast-start for streaming)
var url = await ConvertfleetClient.ConvertAsync(
"documentary.mkv", "mp4",
"-c:v libx264 -preset fast -crf 22 -movflags +faststart -c:a aac -b:a 128k");
Console.WriteLine($"Output: {url}");
ASP.NET Core / Azure Functions: Register HttpClient via IHttpClientFactory rather than the static instance above — this integrates with .NET's connection pool management and ILogger. The CancellationToken parameter threads through naturally from the request's HttpContext.RequestAborted, so the encoding job request cancels cleanly if the HTTP client disconnects.
-movflags +faststart is non-optional for web delivery. Without it, the MP4 moov atom sits at the end of the file, forcing the browser to download the entire file before playback can begin. With it, the atom moves to the front, enabling instant seek from a CDN.
FFmpeg PHP API: Full Working Example
PHP's Guzzle 7.x handles multipart uploads cleanly. The example uses PHP 8.x features (named arguments, JSON_THROW_ON_ERROR) and covers both CLI and web-context usage patterns.
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$apiKey = getenv('CONVERTFLEET_KEY') ?: throw new RuntimeException('CONVERTFLEET_KEY not set');
$base = 'https://api.convertfleet.com/v1';
$client = new Client(['timeout' => 120]);
function ffmpegConvert(string $inputPath, string $outputFormat, string $flags = ''): string {
global $client, $apiKey, $base;
try {
$response = $client->post("{$base}/ffmpeg/convert", [
'headers' => ['Authorization' => "Bearer {$apiKey}"],
'multipart' => [
['name' => 'input', 'contents' => fopen($inputPath, 'r'),
'filename' => basename($inputPath)],
['name' => 'output_format', 'contents' => $outputFormat],
['name' => 'flags', 'contents' => $flags],
],
]);
} catch (RequestException $e) {
$body = $e->hasResponse() ? (string)$e->getResponse()->getBody() : '';
throw new RuntimeException("Upload failed: {$e->getMessage()} — {$body}");
}
$job = json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR);
$deadline = time() + 300; // 5-minute cap
while (!in_array($job['status'], ['completed', 'failed'], true)) {
if (time() > $deadline)
throw new RuntimeException("Job {$job['job_id']} timed out after 300 s");
sleep(1);
$poll = $client->get("{$base}/jobs/{$job['job_id']}", [
'headers' => ['Authorization' => "Bearer {$apiKey}"],
]);
$job = json_decode($poll->getBody(), true, 512, JSON_THROW_ON_ERROR);
}
if ($job['status'] === 'failed')
throw new RuntimeException('FFmpeg error: ' . ($job['error'] ?? 'unknown'));
return $job['output_url'];
}
// Example 1: Thumbnail at 5-second mark, quality 2 (JPEG scale 1–31, lower = better)
$thumbUrl = ffmpegConvert('product-video.mp4', 'jpg', '-ss 00:00:05 -vframes 1 -q:v 2');
echo "Thumbnail: {$thumbUrl}\n";
// Example 2: Watermark an MP4 with an overlay image
$watermarked = ffmpegConvert(
'raw-footage.mp4', 'mp4',
"-i watermark.png -filter_complex 'overlay=W-w-10:H-h-10' -c:a copy"
);
echo "Watermarked: {$watermarked}\n";
Web context warning: sleep(1) blocks PHP-FPM for the full poll duration. In Laravel or Symfony, submit the job, persist the job_id to your database, and poll via a queue worker (php artisan queue:work). For webhook-driven patterns where the API calls your endpoint on completion, see the n8n FFmpeg workflow guide.
FFmpeg API Services Compared
Choosing an FFmpeg API service comes down to three factors: raw flag support (can you pass the exact FFmpeg command you want?), free-tier generosity, and automation platform integrations. Here is an honest comparison as of 2026 — verify current plan details directly on each provider's pricing page.
| Convertfleet | CloudConvert | Transloadit | Self-hosted on EC2 | |
|---|---|---|---|---|
| Free tier | Yes, no credit card | ~25 conversion-min/day | ~1 GB/month | N/A (compute cost) |
| Raw FFmpeg flags | Full CLI parity | Preset-based | Template-based | Full |
| n8n native node | Yes | Community | Community | Manual webhook |
| Avg. latency (< 10 MB file) | < 3 s | 3–8 s | 3–10 s | Hardware-dependent |
| Setup to first conversion | < 5 min | < 10 min | < 10 min | 30–120 min |
| Supported formats | 177+ | ~200 | ~100 | Codec-dependent |
| No-registration test | Yes | No | No | N/A |
| Serverless-friendly | Yes | Yes | Yes | Rarely |
| Webhook on completion | Yes | Yes | Yes | DIY |
C API vs. REST API — when each is right:
| Requirement | Use the C API (libavcodec) |
Use the REST API |
|---|---|---|
| Sub-frame decode control | ✓ | |
| Real-time streaming < 50 ms latency | ✓ | |
| Custom hardware decoder surface | ✓ | |
| Web app / serverless backend | ✓ | |
| No-code automation (n8n, Make) | ✓ | |
| Cross-language portability | ✓ | |
| Zero binary management | ✓ | |
| Batch processing with auto-scaling | ✓ |
The C API is the right choice for firmware, broadcast encoders, and real-time DSP pipelines. For everything else — web services, automation workflows, backend file conversion — the REST API eliminates an entire class of operational complexity.
What About FFmpeg's Native C, C++, and Android APIs?
The FFmpeg C API is the authoritative, lowest-level interface — everything the CLI does, it does by calling libavcodec, libavformat, libavfilter, and libswscale. Frame-level access means you can decode a packet, apply a custom filter graph in real time, re-encode, and mux — all in a single pipeline with microsecond control.
The documentation: The official FFmpeg API documentation (Doxygen-generated, updated with each release) is the authoritative reference. The most useful starting points are AVCodecContext for codec configuration, AVFilterGraph for filter chains, and the avformat_open_input / avformat_write_header pair for container I/O. The ffmpeg-users mailing list and the #ffmpeg IRC channel on Libera.Chat are where hard C API questions get answered.
The C++ API is not officially separate — libav* is a C API with C linkage. Community wrappers like avcpp provide RAII, C++ iterators over packet queues, and modern error handling via std::error_code. They are maintained by individual contributors, not the FFmpeg project — verify commit activity before adopting in production.
The Android FFmpeg note: The debug log d/platformdecodermodule va-api ffmpeg is disabled by platform appears in Android Logcat when an app (typically Chrome, Firefox, or a media-player app built on ExoPlayer) attempts hardware-accelerated VA-API surface rendering and the device ROM reports it as unavailable. The runtime falls back to software decoding automatically. No fix is required — and this message has no connection to REST API calls.
For the vast majority of application developers — backend services, automation pipelines, mobile apps that convert files — the REST API is the correct choice. Build against the C API when the CLI cannot express what you need, or when you require frame-level callbacks in a real-time system.
Figure: Request flow for the FFmpeg REST API — upload, process, poll, download.
Common Mistakes When Using an FFmpeg REST API
Teams adopting this pattern hit the same walls. Every mistake below has been observed in production integrations.
1. Codec–container mismatches.
-c:v libvpx-vp9 in an .mp4 container will fail — VP9 belongs in WebM. -c:v libx264 in a .webm container fails for the same reason. The API returns the exact FFmpeg stderr; read it. Invalid option almost always means a codec–container mismatch, not an API bug.
2. No poll timeout.
A job stuck at processing for 10+ minutes usually means the source file was corrupt, or the requested codec isn't available on the server. Without a timeout cap, your code hangs indefinitely. Cap the polling loop at 300 seconds and mark the job failed in your system.
3. Treating output_url as a permanent storage address.
Download URLs expire — typically after one hour. Download the output to your own storage (S3, GCS, Azure Blob) immediately after the job completes. Never store the URL in your database and expect to fetch it a day later.
4. Sending the file as base64 in a JSON body.
Base64 inflates payload size by 33% and breaks on files over a few MB due to request-body size limits on most HTTP servers. Always use multipart/form-data as every example above demonstrates.
5. Ignoring Retry-After on 429 responses.
A 429 Too Many Requests response includes a Retry-After header specifying the exact number of seconds to wait. Sleeping for an arbitrary 1 second instead — or hammering with immediate retries — will trigger a temporary key block. Parse the header value and wait that long.
6. Hardcoding the API key in source code.
Use environment variables: os.environ["CONVERTFLEET_KEY"] in Python, process.env.CONVERTFLEET_KEY in Node, System.getenv() in Java, Environment.GetEnvironmentVariable() in C#, getenv() in PHP. A key in a git commit is a compromised key.
7. Reusing a watermark -i path across concurrent jobs.
FFmpeg's filter complex for overlay requires the watermark file to be accessible to the server. Pass the watermark as a second upload field or a public URL in input_url_2 — don't reference a local path from your machine.
8. Assuming -preset always controls quality.
-preset controls the encoding speed/compression tradeoff for H.264 and H.265, not quality. Quality in H.264 is -crf (18–28 for distribution; 22–23 is a good default). Setting -preset ultrafast without a -crf often produces surprisingly large files at poor quality, because the bitrate budget is not constrained.
Frequently Asked Questions
How do I run FFmpeg commands through a REST API without a server?
Send a POST request to an FFmpeg REST API endpoint with your source file as a multipart/form-data upload and your FFmpeg flags as a string field. The service executes FFmpeg on its own infrastructure and returns a download URL. No server is required, no binary is installed — any language capable of an HTTP request can use the full FFmpeg feature set. Convertfleet's FFmpeg API accepts raw CLI flags for complete compatibility with existing FFmpeg knowledge.
What is the best FFmpeg API for developers building no-code automations?
For n8n, Make, and Zapier workflows, prioritise two features: a native integration node (not a generic HTTP request block) and raw FFmpeg flag passthrough. Convertfleet offers both. CloudConvert is the main alternative, but its preset model adds a translation layer between your intended command and the actual FFmpeg invocation — flags like -crf or -vf are not directly accessible, making workflow templates harder to reproduce across API versions.
Can I use the FFmpeg Python API with async frameworks like FastAPI?
Yes. Replace requests with httpx — an httpx.AsyncClient is a drop-in replacement. Change requests.post(...) to await client.post(...) and time.sleep(1) to await asyncio.sleep(1) inside the polling loop. Headers, multipart upload structure, and JSON parsing are identical. Use async with httpx.AsyncClient() as client: rather than a module-level client to participate correctly in FastAPI's async lifecycle.
Is there a C# FFmpeg API that works without installing FFmpeg?
Yes — use the REST API pattern shown above. The HttpClient implementation in the C# section requires no NuGet packages beyond the .NET 6+ standard library, works in Azure Functions and ASP.NET Core controllers, and gives you the full FFmpeg flag surface without any native binary. For projects that must run FFmpeg in-process on Windows, FFmpeg.AutoGen provides P/Invoke bindings against the native DLLs — but it requires the DLLs to be present on disk, which the REST API does not.
What does "d/platformdecodermodule va-api ffmpeg is disabled by platform" mean? This is an Android or Chromium debug log, not an FFmpeg API error. It means the hardware VA-API (Video Acceleration API) decoder is unavailable on the current device, ROM, or browser build, and the runtime has fallen back to software decoding. It does not affect REST API calls, has no user-visible impact on playback quality, and requires no fix.
Is there an FFmpeg API with a free tier for small projects? Yes. Convertfleet offers a free tier with no registration required, covering video transcoding, audio extraction, thumbnail generation, and format conversion at no cost. The free tier is sufficient for development and low-volume production use. Check the pricing page for current limits — free tier parameters are updated periodically as the service scales.
What FFmpeg API documentation exists beyond code examples? For the REST API, Convertfleet maintains an OpenAPI 3.1 specification covering all endpoints, request schemas, error codes, and flag validation rules — importable directly into Postman or Insomnia. For the native C API, the official Doxygen reference is canonical. The FFmpeg Wiki covers encoding guides, filter references, and common flag recipes.
Conclusion
Installing FFmpeg is a solved problem on a local machine. On every containerised service, serverless function, and shared host you deploy to, it is an ongoing maintenance cost: binary versioning, codec licensing, OS-specific compilation, and 40–100 MB added to every deployment package.
The REST API pattern eliminates that cost at the expense of one network round-trip per conversion — a tradeoff that is correct for the overwhelming majority of application use cases. The Python, Node.js, Java, C#, and PHP examples above are production-ready starting points with proper error handling, poll timeouts, and environment-variable key management.
Pick the implementation matching your stack, wire in your API key from an environment variable, and you have FFmpeg's complete flag surface — from codec selection to filter graphs to container mux options — without a single apt-get install.
If you're building automation workflows in n8n or processing files at scale, Convertfleet's FFmpeg API is free to start, handles 177+ formats, and requires no registration to test.
SEO / Publishing Metadata
- Suggested URL:
/blog/ffmpeg-api-python-nodejs-java-php - Internal links used:
[FFmpeg REST API](/docs/ffmpeg-api)— product docs pillar[batch conversion guide](/blog/batch-file-conversion-api)— cluster sibling[n8n FFmpeg workflow guide](/blog/ffmpeg-n8n-workflow)— cluster sibling[pricing page](/pricing)— commercial cluster node- External authority links:
https://ffmpeg.org— official FFmpeg project sitehttps://ffmpeg.org/doxygen/trunk/index.html— C API documentationhttps://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html— AWS Lambda package limitshttps://developers.cloudflare.com/workers/platform/limits/— Cloudflare Workers limitshttps://square.github.io/okhttp/— OkHttp library reference- Image alt texts:
1.
hero-ffmpeg-api-python-nodejs-java-php.png— "Developer workflow showing FFmpeg REST API used in Python, Node.js, Java, C#, and PHP without server installation" 2.ffmpeg-api-python-nodejs-java-php-flow.png— "Architecture diagram of FFmpeg REST API request flow: file upload, cloud processing, job polling, and output download" 3.ffmpeg-api-python-nodejs-java-php-comparison.png— "Comparison checklist of FFmpeg REST API vs self-hosted FFmpeg across setup time, cost, flag support, and scalability"
Image Prompts
1. Hero image (16:9)
- Filename: hero-ffmpeg-api-python-nodejs-java-php.png
- Alt: Developer workflow showing FFmpeg REST API used in Python, Node.js, Java, C#, and PHP without server installation
- Prompt: Clean modern flat vector illustration in a cool blue and slate palette with a single bright cyan accent. Five floating code-editor panels — each labeled with a language tag (small color-coded tabs: blue for Python, green for Node.js, orange for Java, purple for C#, red-violet for PHP) — arranged in a loose arc around a central glowing cloud-server icon. Each panel shows three stylized text lines representing a POST request snippet (no readable text). Thin rounded-corner arrows flow from each panel toward the cloud center; a single download arrow exits downward to a clean file icon. Soft gradient background from slate-900 to slate-700. Generous negative space. No text baked in. No real logos. Rounded corners on all UI elements. Professional SaaS-tech aesthetic.
2. Inline flow diagram (16:9)
- Filename: ffmpeg-api-python-nodejs-java-php-flow.png
- Alt: Architecture diagram of FFmpeg REST API request flow: file upload, cloud processing, job polling, and output download
- Prompt: Flat vector horizontal flow diagram on a deep slate background. Five stages connected by thick rounded cyan arrows, left to right. Stage 1: a document icon inside a rounded rectangle with an upload-arrow glyph. Stage 2: a POST badge styled as a pill shape. Stage 3: a cloud/server icon with three small rotating-gear symbols indicating processing. Stage 4: a circular arrow icon with a small clock glyph below (poll/check loop). Stage 5: a checkmark inside a green-tinted rounded rectangle with a download arrow. Each stage sits on a lighter slate card with a thin cyan border and soft drop shadow. Cool blue + slate palette, single bright cyan accent on the arrows. No readable text. No real logos. Generous padding between stages.
3. Inline comparison checklist (1:1)
- Filename: ffmpeg-api-python-nodejs-java-php-comparison.png
- Alt: Comparison checklist of FFmpeg REST API versus self-hosted FFmpeg across setup time, scalability, and developer effort
- Prompt: Clean flat vector two-column comparison card on an off-white background with a slate border. Left column header: muted red-pink with a server/wrench icon (self-hosted FFmpeg). Right column header: vivid cyan with a cloud/lightning icon (REST API). Five rows: setup time, package size, scalability, maintenance, multi-language support. Left column rows show an X icon in muted red. Right column rows show a checkmark icon in cyan/green. Row labels are small universally-recognised icons only (clock, package-box, graph, wrench, code-brackets) — no readable text. Rounded corners, soft shadows, generous padding. SaaS-tech aesthetic.
JSON-LD Schema
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "BlogPosting",
"@id": "https://convertfleet.com/blog/ffmpeg-api-python-nodejs-java-php",
"headline": "FFmpeg API: Python, Node.js, Java, C# & PHP — REST Without a Server",
"description": "Use FFmpeg via REST API in Python, Node.js, Java, C#, and PHP — no server install needed. Copy-paste examples for all five stacks with Convertfleet's FFmpeg API.",
"url": "https://convertfleet.com/blog/ffmpeg-api-python-nodejs-java-php",
"datePublished": "2026-06-06",
"dateModified": "2026-06-06",
"author": {
"@type": "Organization",
"name": "Convert Team",
"url": "https://convertfleet.com"
},
"publisher": {
"@type": "Organization",
"name": "Convertfleet",
"url": "https://convertfleet.com",
"logo": {
"@type": "ImageObject",
"url": "https://convertfleet.com/logo.png"
}
},
"image": {
"@id": "https://convertfleet.com/blog/images/hero-ffmpeg-api-python-nodejs-java-php.png"
},
"keywords": "ffmpeg python api, ffmpeg rest api, ffmpeg java api, ffmpeg php api, ffmpeg api c#, ffmpeg api tutorial, ffmpeg online api, ffmpeg c api, ffmpeg api documentation, android ffmpeg api, ffmpeg c++ api",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://convertfleet.com/blog/ffmpeg-api-python-nodejs-java-php"
}
},
{
"@type": "ImageObject",
"@id": "https://convertfleet.com/blog/images/hero-ffmpeg-api-python-nodejs-java-php.png",
"url": "https://convertfleet.com/blog/images/hero-ffmpeg-api-python-nodejs-java-php.png",
"contentUrl": "https://convertfleet.com/blog/images/hero-ffmpeg-api-python-nodejs-java-php.png",
"caption": "Developer workflow showing FFmpeg REST API used in Python, Node.js, Java, C#, and PHP without server installation",
"width": 1600,
"height": 900,
"representativeOfPage": true
},
{
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "How do I run FFmpeg commands through a REST API without a server?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Send a POST request to an FFmpeg REST API endpoint with your source file as a multipart/form-data upload and your FFmpeg flags as a string field. The service executes FFmpeg on its own infrastructure and returns a download URL for the converted output. No server is required, no binary is installed — any language capable of an HTTP request can access the full FFmpeg feature set."
}
},
{
"@type": "Question",
"name": "What is the best FFmpeg API for developers building no-code automations?",
"acceptedAnswer": {
"@type": "Answer",
"text": "For n8n, Make, and Zapier workflows, look for an API with a native integration node and raw FFmpeg flag passthrough. Convertfleet offers both: a purpose-built n8n node and a REST endpoint that accepts the same flag syntax as the FFmpeg CLI — so workflow templates transfer directly without format translation or preset abstraction."
}
},
{
"@type": "Question",
"name": "Is there a C# FFmpeg API that works without installing FFmpeg?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. Use the FFmpeg REST API pattern with .NET's built-in HttpClient — no NuGet packages required beyond the .NET 6+ standard library. This works in ASP.NET Core, Azure Functions, and console applications. The approach requires no native FFmpeg DLLs on the host machine and gives full access to FFmpeg's flag surface over HTTPS."
}
},
{
"@type": "Question",
"name": "Can I use the FFmpeg Python API with async frameworks like FastAPI?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. Replace the requests library with httpx, which provides a drop-in async client. Change requests.post() to await client.post() and time.sleep(1) to await asyncio.sleep(1) in the polling loop. All other code — headers, multipart upload structure, and JSON parsing — remains identical."
}
},
{
"@type": "Question",
"name": "What does 'd/platformdecodermodule va-api ffmpeg is disabled by platform' mean?",
"acceptedAnswer": {
"@type": "Answer",
"text": "This is an Android or Chromium debug log, not an FFmpeg REST API error. It means the hardware VA-API video decoder is unavailable on the current device or browser build, and the runtime has fallen back to software decoding automatically. It has no impact on REST API calls, no effect on playback quality, and requires no fix."
}
},
{
"@type": "Question",
"name": "Is there an FFmpeg API with a free tier for small projects?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. Convertfleet offers a free tier with no registration required, covering video transcoding, audio extraction, thumbnail generation, and format conversion. The free tier handles most development and low-volume production workloads at no cost. Check the Convertfleet pricing page for current limits, as free tier parameters are updated periodically."
}
},
{
"@type": "Question",
"name": "Where can I find FFmpeg API documentation?",
"acceptedAnswer": {
"@type": "Answer",
"text": "For Convertfleet's REST API, the OpenAPI 3.1 specification at convertfleet.com/docs/ffmpeg-api covers all endpoints, request schemas, error codes, and flag validation — importable into Postman or Insomnia. For FFmpeg's native C API, the official Doxygen reference at ffmpeg.org/doxygen/trunk/index.html is the authoritative source, updated with each FFmpeg release."
}
}
]
}
]
}
Read next

Workflow Automation · Jun 11, 2026
n8n vs Zapier for File Conversion: 2026 Guide
n8n vs Zapier vs Make.com stress-tested on file conversion: pricing, rate limits, FFmpeg support, and error recovery compared for 2026 automation buyers.

Developer Guides · Jun 11, 2026
File Conversion API Explained: What It Is & When to Use It
A file conversion API lets apps convert documents, images, and video via HTTP. Learn how it works, when to build vs. buy, and how to automate at scale.

Developer Guides · Jun 11, 2026
FFmpeg Tools Explained: CLI vs Cloud API
FFmpeg tools demystified: key commands, what they do, and why local FFmpeg breaks in n8n, Docker, or serverless — plus how a cloud API solves it.