Originally published at ffmpeg-micro.com
You need video transcoding in your app. Maybe users upload MP4s that need converting to WebM. Maybe you are building a course platform and need consistent 720p output. Maybe you just got handed a "video processing" ticket and realized FFmpeg has 400 command-line flags.
The typical path: spin up a server, install FFmpeg, write a job queue, handle failures, scale when traffic spikes. That is weeks of work before you process a single video.
There is a faster way. An API call.
The self-hosted FFmpeg trap
Most developers start by SSH-ing into a VPS and running apt install ffmpeg. Simple enough. Then reality hits.
Your server needs enough RAM to hold the input video in memory during processing. A single 4K video transcode can eat 8GB. Two concurrent users and your $20/month server is swapping to disk.
So you add a job queue. Now you need Redis or RabbitMQ. You need worker processes. You need retry logic for when FFmpeg segfaults on a corrupt input file (and it will). You need monitoring to know when the queue backs up.
Then there is the dependency problem. FFmpeg versions matter. The codec support compiled into your binary matters. That one transcode that works on your Mac but fails in the Docker container because the production image was built without libvpx? You will spend a full day debugging it.
Three weeks in, you have built a video processing platform instead of the product you actually wanted to build.
Upload, transcode, download
FFmpeg Micro wraps all of that infrastructure into five API calls. Get a presigned upload URL, upload your file, confirm the upload, kick off the transcode, and download the result.
In Python, the core workflow looks like this:
import requests
API_KEY = "your-api-key"
BASE = "https://api.ffmpeg-micro.com/v1"
headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
# Step 1: Get a presigned upload URL
resp = requests.post(f"{BASE}/upload/presigned-url", headers=headers, json={
"filename": "input.mp4",
"contentType": "video/mp4",
"fileSize": 15728640 # size in bytes
})
upload_url = resp.json()["result"]["uploadUrl"]
server_filename = resp.json()["result"]["filename"]
# Step 2: Upload directly to cloud storage (no API key needed)
with open("input.mp4", "rb") as f:
requests.put(upload_url, headers={"Content-Type": "video/mp4"}, data=f)
# Step 3: Confirm the upload
requests.post(f"{BASE}/upload/confirm", headers=headers, json={
"filename": server_filename,
"fileSize": 15728640
})
# Step 4: Start the transcode
resp = requests.post(f"{BASE}/transcodes", headers=headers, json={
"inputs": [{"url": f"gs://your-bucket/{server_filename}"}],
"outputFormat": "mp4",
"preset": {"quality": "medium", "resolution": "1080p"}
})
job_id = resp.json()["id"]
# Step 5: Poll until done, then download
import time
while True:
status = requests.get(f"{BASE}/transcodes/{job_id}", headers=headers).json()
if status["status"] == "completed":
break
time.sleep(5)
download = requests.get(f"{BASE}/transcodes/{job_id}/download", headers=headers).json()
print(f"Download your video: {download['url']}")
That is the entire workflow. No server provisioning. No FFmpeg installation. No job queue. The infrastructure scales automatically whether you are processing one video or a thousand.
cURL version for quick testing
If you want to test before writing any application code, the same flow works with cURL:
# Get upload URL
curl -X POST https://api.ffmpeg-micro.com/v1/upload/presigned-url \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename": "input.mp4", "contentType": "video/mp4", "fileSize": 15728640}'
# Upload your file to the presigned URL
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: video/mp4" \
--data-binary @input.mp4
# Confirm, then transcode
curl -X POST https://api.ffmpeg-micro.com/v1/transcodes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"inputs": [{"url": "gs://your-bucket/input.mp4"}], "outputFormat": "mp4", "preset": {"quality": "medium", "resolution": "1080p"}}'
You can go from zero to a transcoded video in under five minutes.
When you need more control
The preset mode handles most use cases. But when you need specific FFmpeg flags, the API accepts them directly:
{
"inputs": [{"url": "gs://your-bucket/input.mp4"}],
"outputFormat": "webm",
"options": [
{"option": "-c:v", "argument": "libvpx-vp9"},
{"option": "-crf", "argument": "30"},
{"option": "-b:v", "argument": "0"}
]
}
Same FFmpeg power. You just don't manage the machine running it.
This matters for complex operations like text overlays. The API supports virtual options that abstract away FFmpeg filter graph syntax:
{
"options": [
{
"option": "@text-overlay",
"argument": {
"text": "Draft - Do Not Distribute",
"style": {"fontSize": 48, "x": "0.05*w", "y": "0.05*h"}
}
}
]
}
Compare that to the raw FFmpeg equivalent: -vf "drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf:text='Draft':fontsize=48:x=50:y=50". One is JSON you can generate programmatically. The other is a string that breaks if you look at it wrong.
What about cost?
Self-hosting sounds cheaper until you factor in the hidden costs. A dedicated transcoding server runs $50-200/month whether you process 10 videos or 10,000. You pay for idle capacity, and you pay again in engineering time when you need to scale up.
With a usage-based API, you pay per minute of video processed. Light usage costs a few dollars a month. Hit a viral spike and the infrastructure scales without you touching anything. No 3am pages about disk space or OOM kills.
When to self-host vs use an API
Self-hosting makes sense if you process millions of minutes monthly and have a dedicated infrastructure team to maintain it. For everyone else, an API gets you to production faster and costs less until you hit serious scale.
If you are building a SaaS product, a content platform, or any app where video processing is a feature but not the core product, spending weeks on FFmpeg infrastructure is time stolen from the thing that actually makes money.
Get a free API key and start transcoding in minutes.
Top comments (0)