If you've ever tried to batch process videos at scale, you know the drill. You write a quick script on your laptop, feed it a folder of 200 clips, and it works beautifully—until you try to do it in production. Suddenly you're dealing with runaway CPU usage, crashed workers, jobs that hang forever, and a hosting bill that makes you question your career choices.
The promise of a serverless FFmpeg API is simple: take the tool you already know and love, and let someone else handle the messy parts of running it at scale. No more provisioning GPU instances at 2 a.m. No more babysitting containers. No more worrying that a single malformed input file will take down your entire encoding pipeline.
In this post, we'll look at what batch video processing actually involves, why the traditional approach breaks down as you scale, and how a serverless FFmpeg API like FFmpeGo changes the math. We'll walk through real code, cover the pricing quirks you should care about, and dig into the edge cases nobody warns you about.
Why Batch Video Processing Is Harder Than It Looks
Let's start with what "batch processing" really means in a media context.
You might be a SaaS founder who needs to generate thumbnail variants for every user upload. You might be an automation engineer compressing years of archived footage to save on storage. You might be building an app that takes user videos and re-encodes them into multiple resolutions for adaptive streaming. Or maybe you just have 4,000 family videos from an old hard drive that you want converted to H.265.
All of these look similar on the surface: a list of inputs, a set of FFmpeg commands, and a need for outputs. But the moment you move past a few dozen files, three things start to happen at once.
First, CPU contention. Video encoding is famously CPU-hungry. Running one FFmpeg job on your laptop is fine. Running forty of them simultaneously on the same machine means everything slows to a crawl, jobs start timing out, and your fans sound like a jet engine taking off.
Second, queue management. You need something to hold the jobs, dispatch them, retry the failures, and report back. If you've ever built a queue on top of a database table with a status column, you know how quickly that gets ugly—race conditions, stuck locks, partial outputs.
Third, error handling. FFmpeg is a remarkably robust tool, but it also fails in creative ways. Corrupted streams, unsupported codecs, mismatched timestamps, weird container quirks. When a job does fail, you want to know why, and you don't want to pay for it.
The traditional solution to all three problems is to build a fleet of encoding servers, put a proper queue in front of them, and manage the whole thing yourself. That works—lots of big media companies do it—but it's a lot of undifferentiated heavy lifting for what is ultimately a commodity operation.
The Real Cost of Rolling Your Own Encoding Farm
Here's a rough breakdown of what "rolling your own" actually involves if you want to batch process videos at scale without a serverless FFmpeg API:
- Compute: you need instances with enough CPU to encode at reasonable speed. For H.264 1080p, a modern 4-core instance can usually manage real-time or slightly better. For 4K or H.265, you'll want more.
- Orchestration: a queue (SQS, RabbitMQ, Redis), worker processes, and something to monitor the whole thing.
- Storage: inputs and outputs both need somewhere to live, and if you're processing terabytes, that's a real bill.
- Auto-scaling: you don't want to pay for 20 idle instances when nothing is in the queue, so you build autoscaling logic—which is where most homegrown pipelines quietly become unmaintainable.
- Retries and idempotency: failed jobs need to be retried, but you also need to make sure a partially-completed job doesn't get processed twice.
- Monitoring: Prometheus, Grafana, alerting. Encoding pipelines fail silently more often than you'd expect.
Add all of that up and you're easily spending a few engineer-weeks building it, plus ongoing maintenance every time a codec library updates or a cloud provider changes its instance types.
What a Serverless FFmpeg API Actually Is
A serverless FFmpeg API takes the FFmpeg binary, wraps it in a managed execution environment, and exposes it as an HTTP endpoint. You send a JSON payload with your inputs and the exact FFmpeg arguments you want to run. The service spins up the work, runs the command, and returns the output (or a URL to it).
The key word is arbitrary. Some video APIs only offer preset operations: "convert to mp4," "extract audio," "add watermark." That's fine if your needs fit inside their presets. But the moment you want a custom filter_complex graph combining three inputs with a scaled overlay and a fade-in, those APIs are useless.
A true serverless FFmpeg API gives you the whole command-line tool. If you can type it in a terminal, you can send it to the API. That's a big deal because it means you're not learning some proprietary DSL—you're using the same FFmpeg you already know.
FFmpeGo is one example of this pattern. It exposes a single endpoint (/v2/run) that accepts a JSON body with input URLs and FFmpeg arguments, runs the job in a managed environment, and returns a result. Billing is based on compute seconds—the actual wall-clock time your job spends executing—and jobs that fail with a non-2xx response aren't billed.
The Anatomy of a Batch Job: From Input to Output
Before we get into code, let's talk about what a batch job actually consists of. Whether you're using a serverless FFmpeg API or your own cluster, the structure is roughly the same.
Inputs
Each job has one or more inputs. Usually these are URLs pointing to video or audio files. With a serverless API, you typically pass HTTPS URLs that the service can fetch. In some setups, you can also send base64 blobs, though that's rarely a good idea for anything over a few megabytes.
The hidden complexity with inputs is that they're not always well-behaved. A file URL that returns 200 might still have a broken stream inside. Audio and video tracks might have different durations. The container might claim it's MP4 but actually contain something else. A robust batch pipeline assumes inputs are messy.
The Command
The second part is the FFmpeg command itself. In a serverless API, you typically pass the arguments as an array of strings, without the leading ffmpeg (since that's implied).
For example, a simple conversion looks like this:
["-i", "https://example.com/input.mov", "-c:v", "libx264", "-crf", "23", "output.mp4"]
A more complex one, using filter_complex and multiple inputs, might look like this:
[
"-i", "input1.mp4",
"-i", "input2.mp4",
"-filter_complex", "[0:v][1:v]hstack=inputs=2[v]",
"-map", "[v]",
"-c:v", "libx264",
"-crf", "20",
"side_by_side.mp4"
]
That's the whole point. You're not limited to prefab operations—you have the entire FFmpeg surface area.
The Output
Output location varies. With some APIs, the service uploads the result to your own S3 bucket. With others, it returns a signed URL where the file lives temporarily. FFmpeGo's model is closer to the latter: you get back a URL, and you fetch the result.
Either model works, but the key thing is that your batch pipeline needs to handle output retrieval reliably. If you're processing 5,000 files, you don't want to discover that half the outputs have expired by the time your download step runs.
Writing Your First Batch Script
Let's get concrete. Here's a minimal Python script that submits a batch of videos to a serverless FFmpeg API. I'll write it in a way that could adapt to any provider, but the shape of the request matches FFmpeGo's API.
import requests
import concurrent.futures
import time
API_URL = "https://api.ffmpego.com/v2/run"
API_KEY = "your-api-key"
def process_video(input_url, output_name):
payload = {
"args": [
"-i", input_url,
"-c:v", "libx264",
"-preset", "medium",
"-crf", "23",
"-c:a", "aac",
"-b:a", "128k",
output_name
]
}
headers = {"Authorization": f"Bearer {API_KEY}"}
r = requests.post(API_URL, json=payload, headers=headers, timeout=600)
r.raise_for_status()
return r.json()["output_url"]
inputs = [
"https://example.com/clip1.mp4",
"https://example.com/clip2.mp4",
# ... hundreds more
]
with concurrent.futures.ThreadPoolExecutor(max_workers=20) as pool:
futures = {pool.submit(process_video, u, f"out_{i}.mp4"): u for i, u in enumerate(inputs)}
for fut in concurrent.futures.as_completed(futures):
try:
print(f"Done: {fut.result()}")
except Exception as e:
print(f"Failed: {e}")
That's the skeleton. From here you'd want to add retries, logging, and a proper backoff. But notice what's not in that script: no instance provisioning, no queue, no worker pool to manage, no autoscaling logic. The API is your queue.
Handling Concurrency Without Getting Throttled
The max_workers=20 in that example is doing a lot of work. It controls how many requests you're firing at the API concurrently. Set it too high and you'll get rate-limited. Set it too low and your batch takes forever.
A good starting point is somewhere between 10 and 30 concurrent requests, depending on your API plan. From there, watch for 429 responses and dial back. If you're building something more serious, implement exponential backoff with jitter—the classic pattern:
def retry_with_backoff(fn, max_retries=5):
for attempt in range(max_retries):
try:
return fn()
except requests.HTTPError as e:
if e.response.status_code == 429:
wait = (2 ** attempt) + random.random()
time.sleep(wait)
else:
raise
raise RuntimeError("Max retries exceeded")
Since the serverless API only bills successful jobs, retries cost you nothing extra (other than the time). That's one of those small advantages that adds up when you're running thousands of jobs.
Cost Math: Compute Seconds vs. Hourly Instances
Here's the thing that usually surprises people when they switch from managing their own encoding servers to a serverless FFmpeg API: the pricing model is fundamentally different, and it changes what you should optimize for.
When you rent an EC2 instance by the hour, your goal is to keep it busy. Idle time is wasted money. So you batch your jobs, you queue everything up, and you try to always have work ready. But that means you're paying for the instance even when there's a lull.
With compute-seconds pricing, you only pay for what you actually run. A 30-second encode costs the same whether it's the only job of the day or one of ten thousand. That's freeing in a way that's hard to appreciate until you've spent a month paying for idle capacity.
Let's do a rough comparison. Say you need to process 1,000 eight-minute videos, each requiring about 4 minutes of encode time.
Self-managed approach:
- 8-core instance: ~$0.34/hour (roughly, spot pricing gets cheaper)
- Say the instance can run 2 videos in parallel, so 4 minutes per pair
- 1,000 videos ÷ 2 = 500 pairs × 4 minutes = 2,000 minutes = ~33 hours
- Cost: ~$11 in compute, but you also need storage, and if you scale to run this faster you rent more instances
Serverless FFmpeg API:
- 1,000 jobs × 240 compute seconds = 240,000 compute seconds
- If your provider charges, say, $0.00004 per compute second, that's $9.60
- No idle time, no storage management, no orchestration code
The numbers are in the same ballpark for this scale. The gap widens as you go bigger—because at larger scale, you're saving real engineering hours, and the elasticity means you can finish the batch in a fraction of the time without overprovisioning.
The other cost factor is failed jobs. On a self-managed setup, a failed job still consumed CPU you paid for. With FFmpeGo's model, only 2xx responses get billed. If 5% of your inputs are corrupted, you save that 5% either way—but on the API it's automatic.
Designing a Robust Batch Pipeline
A batch of 20 videos is a script. A batch of 20,000 is a data pipeline. The difference is in the details.
Idempotency
Every job should have a stable identifier so that if you retry it, you don't accidentally create two outputs. Use the input URL hash plus a version tag as the job key. Store job state in a database or a durable log.
Checkpointing
Don't keep the list of pending jobs only in memory. Write progress to disk or to a durable store. If your batch script crashes at job 4,000 of 5,000, you want to resume from 4,001, not restart.
Output Storage
Decide early where outputs land. If the API returns a temporary URL, you need to fetch and persist it promptly. If it uploads to your bucket, even better—just make sure the paths are consistent and easy to enumerate later.
Logging
Log the full response for every job. Compute time, status code, output URL, any error message. When something goes wrong at scale, these logs are your only guide.
Timeouts
Set a client-side timeout that's longer than you expect the longest job to take, but not so long that a hung request blocks your worker indefinitely. Something like 30 minutes is usually safe for video jobs.
Common Mistakes When Batching With FFmpeg
Let's talk about the mistakes I've seen (and made). These are the ones that quietly waste hours.
Mistake 1: Not Specifying the Output Filename
FFmpeg needs a final argument for the output. If you're passing arguments as an array, that last element is your output path. Forgetting it produces an error that looks like it's about codecs but is really just "you didn't tell me where to write."
Mistake 2: Overwriting <output_name> per Job
If your batch submits 100 jobs all writing to output.mp4, and the API runs them concurrently, you'll get one file with the last writer's content. Always include a unique identifier in the output filename.
Mistake 3: Ignoring -y and -n
FFmpeg will prompt to overwrite existing files by default. In a serverless setting that prompt usually just causes the job to hang until it times out. Add -y when you want to overwrite, or -n if you want it to fail fast.
Mistake 4: Using Presets That Don't Match the Hardware
-preset slow on a serverless API might still be fast because the underlying hardware is beefy. But -preset placebo is a waste of compute seconds no matter where you run it. Use medium as your default and only reach for slower presets when the quality gain is worth the cost.
Mistake 5: Forgetting the -i for the Input
It sounds trivial, but when you're building arguments programmatically, it's easy to slip. Always validate your command line locally with ffmpeg -i input.mp4 ... before shipping it to a batch pipeline.
Mistake 6: Not Handling Non-2xx Responses
Some APIs return 200 with an error message in the body. Others return 500 for user errors. Trust the status code—if FFmpeGo only bills for 2xx, then your job didn't succeed if it's not 2xx.
When to Use a Serverless FFmpeg API (and When Not To)
A serverless FFmpeg API isn't the right tool for every situation. Here's a quick guide.
Use It When:
- Your workload is spiky or unpredictable.
- You want to avoid managing encoding infrastructure.
- You need arbitrary FFmpeg commands, not just presets.
- Your batch jobs are moderate in size (hundreds to tens of thousands).
- You're a small team without a dedicated media infrastructure engineer.
Don't Use It When:
- You have a stable, always-saturated workload where reserved instances are cheaper long-term.
- You need on-prem processing for compliance or latency reasons.
- You're processing enormous files (multi-hour 4K streams) where the transfer in and out dominates cost.
- You need sub-100ms latency responses (this is not what a batch API is for).
For most indie developers, small SaaS teams, and automation engineers, the first set of scenarios describes their reality almost perfectly. The second set describes a company that has already decided to invest in a media platform team.
Using filter_complex for Real Batch Work
One of the biggest differentiators of a proper serverless FFmpeg API is that it supports filter_complex. This is where preset-based APIs fall apart and FFmpeg itself shines.
Suppose you want to create a "highlight" video by concatenating segments from multiple inputs. You'd do something like:
[
"-i", "part1.mp4",
"-i", "part2.mp4",
"-i", "part3.mp4",
"-filter_complex", "[0:v][0:a][1:v][1:a][2:v][2:a]concat=n=3:v=1:a=1[v][a]",
"-map", "[v]",
"-map", "[a]",
"-c:v", "libx264",
"-crf", "21",
"-preset", "medium",
"highlight.mp4"
]
Or suppose you want to overlay a logo with a specific opacity and position:
[
"-i", "base.mp4",
"-i", "logo.png",
"-filter_complex", "[1:v]format=rgba,colorchannelmixer=aa=0.7[logo];[0:v][logo]overlay=W-w-20:20",
"-c:v", "libx264",
"-crf", "20",
"branded.mp4"
]
These are things that preset APIs just can't do. In a batch context, they're the difference between a pipeline that produces what you actually need and one that produces something close enough you'll fix it later.
Multi-Input Jobs and the Handling of Different Durations
Multi-input jobs are where FFmpeg gets interesting—and where batch pipelines get complicated.
Consider a job that overlays a short watermark video onto a longer main video. What happens to the watermark after it ends? By default, FFmpeg will loop the last frame. If you don't want that, you need to set -stream_loop 0 and accept a frozen frame, or you need to encode carefully.
Similarly, if you're concatenating clips with mismatched resolutions or frame rates, you need to normalize them first. The scale and fps filters handle this, but only if you ask.
A common pattern for batch normalization is:
[
"-i", input_url,
"-vf", "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,fps=30",
"-c:v", "libx264",
"-crf", "23",
"-preset", "medium",
"-c:a", "aac",
"-b:a", "128k",
output_name
]
That pipeline handles mismatched resolutions, aspect ratios, and frame rates in one shot. It's the sort of thing you'd want to apply across thousands of user uploads before feeding them into a downstream system.
Batch Audio Processing with the Same API
The same serverless FFmpeg API you use for video handles audio too. Since FFmpeg speaks both languages, you don't need a separate pipeline or a separate vendor.
Common batch audio jobs:
- Normalize loudness with the
loudnormfilter (useful for podcast workflows). - Convert to opus for web streaming (
-c:a libopus -b:a 96k). - Extract audio from video for transcription pipelines (
-vn -c:a aac). - Generate spectrograms via
showspectrumpic. - Split audio into segments using the
segmentmuxer.
For example, normalizing a batch of podcast episodes:
[
"-i", input_url,
"-af", "loudnorm=I=-16:TP=-1.5:LRA=11",
"-c:a", "libopus",
"-b:a", "96k",
output_name
]
That's a one-liner locally and a one-liner through the API. The difference is that instead of running it one at a time, you fire off a thousand of them and come back in twenty minutes.
Triggering Batches From Real Workflows
Batch processing rarely lives in isolation. It's usually one step in a larger pipeline. Here are some trigger patterns that come up a lot:
- New uploads: user uploads to S3, a Lambda function fires, which calls the FFmpeg API, stores the output, and updates the database.
- Nightly jobs: a cron job or scheduled task enumerates unprocessed files and submits them.
- On-demand reports: a user clicks "generate highlight reel" and the backend fans out a batch.
- Internal tools: an operations team uploads a CSV of URLs and a batch runs.
All of these look the same from the API's perspective: a list of inputs and a set of arguments. The integration effort is mostly in the surrounding plumbing.
If you're using a service like FFmpeGo, the fact that there's a single endpoint means your integration code is small—a thin client that builds args, POSTs them, and handles responses. That makes it easy to drop into any of those trigger patterns without vendor-specific SDKs.
Monitoring and Observability for Large Batches
When you're running a batch of 50,000 jobs, "it's working" isn't enough. You want to know:
- How many jobs are pending, running, succeeded, and failed?
- What's the average compute time per job?
- What's the failure rate, and what error messages dominate?
- Are there inputs that consistently fail (corrupted files, missing URLs)?
- What's the total cost so far?
The simplest way to track this is a small database table with columns like job_id, input_url, status, compute_seconds, output_url, error_message, started_at, finished_at. It doesn't need to be fancy.
If you're processing more than a few thousand a day, add a dashboard. Grafana, Metabase, whatever you like. The key metric is failure rate—if it climbs, you want to know before someone complains.
A Worked Example: Compressing an Archive of 5,000 Videos
Let's walk through a realistic scenario from end to end.
The setup: You have 5,000 legacy videos stored in an S3 bucket. They're a mix of formats (MOV, AVI, WMV, older MP4s) at various resolutions and bitrates. Total size is about 2 TB. You want to convert them all to H.265 MP4 at 1080p, with AAC audio, and store the outputs in a different bucket. Your goal is to cut storage costs and make the archive web-friendly.
Step 1: Inventory and Classify
Before submitting anything, run a probe pass. You could use ffprobe locally, or if you want it all done remotely, submit a batch of jobs with ffmpeg -i input -f null - to get metadata in stderr.
Build a CSV with: input_url, duration, resolution, codec, size.
Step 2: Define the Target Command
For your use case:
[
"-i", input_url,
"-vf", "scale='min(1920,iw)':-2,fps=30",
"-c:v", "libx265",
"-crf", "26",
"-preset", "medium",
"-c:a", "aac",
"-b:a", "128k",
"-movflags", "+faststart",
output_name
]
Note the min(1920,iw) — this avoids upscaling smaller videos, which would waste both time and quality.
Step 3: Submit in Batches
Don't dump all 5,000 jobs at once. Submit them in waves of 100–200, tracking progress. This gives you a chance to spot problems early and adjust.
Step 4: Handle Failures
Expect roughly 1–5% failure on messy archives. Log the failures and inspect them. Common causes: truncated files, unusual codecs, missing audio tracks that break -c:a (though usually FFmpeg just skips them).
Step 5: Verify Outputs
For a random sample, download the output and play it. Check that duration matches, that the resolution scaled correctly, and that audio is present. Automated checks (ffprobe on each output) catch most issues cheaply.
Step 6: Reconcile Costs
Pull the compute seconds for each job and sum them. Compare against your original estimate. If the actual cost is much higher than expected, there's probably a specific set of inputs dragging the average up—maybe a few outliers with unusually high bitrates or long durations.
That whole pipeline, from inventory to reconciliation, is maybe 300 lines of Python. The bulk of the work is in classification and verification, not in the encoding itself—which is exactly what you'd hope for.
Pricing and Budgeting in a Batch Context
The transparent billing model of compute seconds is easiest to appreciate when you're looking at a big batch. Let's break it down.
If your average job takes 90 seconds of compute and you have 5,000 jobs, that's 450,000 compute seconds. Multiply by the rate and you have your number. No surprises.
The tricky part is estimating compute seconds before you run the batch. A few heuristics:
- SD to H.264: often faster than real-time on modern hardware—a 5-minute clip might take 60–90 seconds.
- 1080p to H.264: roughly real-time or a bit slower—a 5-minute clip might take 3–6 minutes.
- 1080p to H.265: typically 2–4x slower than H.264—a 5-minute clip might take 10–20 minutes.
- Filter-heavy jobs:
filter_complexwith multiple inputs can be significantly slower; budget extra.
Run a sample of 20 jobs first, measure actual compute seconds, and extrapolate. That's far more reliable than guessing.
Free Tiers and Caps
Most serverless FFmpeg APIs offer a free tier, which is genuinely useful for development—you can build and test your entire pipeline without cooking your credit card. FFmpeGo goes further with hard monthly caps, which means you can set a maximum spend and the API will refuse new jobs once you hit it. For teams without dedicated finance oversight, that's a nice safety net.
If you're running an internal tool where one user could accidentally trigger a massive batch, a hard cap is the difference between a bad afternoon and a horrifying invoice.
Security and Data Handling Considerations
When you send your media files to a third-party API, you're trusting the provider with your content. A few things to think about:
- Are inputs fetched over HTTPS? They should be. Signed URLs from S3 or similar are the standard way to give the service access without making files publicly available.
- Where is the processing happening? Data residency matters for some industries. Check the provider's region.
- How long are outputs retained? If they're temporary URLs, fetch them promptly.
- What's the authentication model? An API key with scoped permissions is better than a shared secret.
For most indie developers and small SaaS teams, this is not a blocker—they're already using third-party storage, CDNs, and monitoring. But if you're in healthcare, finance, or government, it's a real consideration.
A practical compromise many teams use: keep public content in the third-party pipeline, and route sensitive content through a self-hosted FFmpeg setup with tighter controls.
Comparing Serverless FFmpeg APIs to Alternatives
There are a few routes people take when they outgrow local FFmpeg.
| Approach | Pros | Cons |
|---|---|---|
| Local FFmpeg | Free, full control, no latency | Doesn't scale, ties up your machine |
| Self-managed servers | Predictable cost at steady state | High setup and maintenance cost |
| Managed video platforms (Mux, Cloudflare Stream, etc.) | Great for playback, easy setup | Limited to their presets and workflows |
| Serverless FFmpeg API | Arbitrary commands, elastic, no infra | Per-second costs at very large scale |
| Hybrid | Best of both | More complexity to manage |
For most developers, the serverless FFmpeg API hits a sweet spot: enough flexibility to do anything, enough abstraction to skip the infra headache. If your needs are narrow (say, just HLS packaging), a managed platform might be simpler. If your needs are quirky or custom, a serverless FFmpeg API is likely the better fit.
FAQ: Serverless FFmpeg API for Batch Processing
Q: Can I really run arbitrary FFmpeg commands, or is there a subset? With FFmpeGo, yes—arbitrary commands. You send the arguments as an array, and the service runs the FFmpeg binary with those arguments. If your command works locally, it should work through the API (assuming the inputs are reachable and the output format is supported by the FFmpeg build).
Q: How do I pass multiple inputs?
Just include multiple -i flags in your args array. For example, ["-i", "a.mp4", "-i", "b.mp4", "-filter_complex", "...", "output.mp4"]. The API handles input fetching for each.
Q: What happens if a job fails halfway? Failed jobs return a non-2xx status. You're not billed for them. You can retry with the same arguments, or inspect the error and adjust.
Q: How long can a single job run? That depends on the provider. Most serverless FFmpeg APIs support jobs up to 30 minutes or an hour. For longer encodes, you'd split the input or use a different approach.
Q: Can I use my own storage bucket? Many serverless FFmpeg APIs support outputting directly to your bucket. If not, you'll get a temporary URL you can fetch and store yourself.
Q: Is the pricing really only on successful jobs? With FFmpeGo, yes. The billing is based on compute seconds, and only 2xx responses are billed. That's genuinely unusual in the infrastructure world and makes it cheap to test and iterate.
Q: How do I handle very large batches? Submit in waves, track progress in a durable store, and implement retry logic with exponential backoff. The API itself doesn't need to know you have 50,000 jobs queued—it just needs each request to be valid.
Q: What about live streaming or real-time transcoding? A batch API is not designed for real-time. If you need sub-second latency for live streams, look at dedicated real-time transcoding services instead.
Practical Tips for Smooth Batch Runs
A short checklist that's saved me more than once:
- Probe first. Know your inputs before you encode.
- Test with 10–20 jobs before launching a large batch.
- Set a client timeout longer than your slowest expected job.
- Log everything. Full args, full response, timestamps.
- Use
-yor-nto avoid prompts. - Add
-movflags +faststartfor MP4s destined for the web. - Normalize resolution and frame rate early in the pipeline.
- Store job state durably. In-memory progress dies with the process.
- Watch your failure rate. A spike means something systemic.
- Set a hard spend cap if your provider supports it.
Wrapping Up: The Batch Pipeline You'd Actually Want to Build
The best batch video pipeline is the one you build in an afternoon and never think about again. That's the real pitch of a serverless FFmpeg API—not just cost savings or scalability, but the fact that encoding stops being a project and starts being a function call.
You still get FFmpeg. All the codecs, all the filters, all the weird command-line quirks you've learned to love. You just don't have to run the fleet. For indie developers shipping features, for automation engineers gluing systems together, and for small SaaS teams where "hire a media infrastructure engineer" isn't in the budget, that trade is usually a good one.
If you've been putting off that large-scale conversion project—the one with thousands of files sitting on a drive somewhere—this is a good moment to revisit it. Build a small test batch, measure the compute seconds, and see what the math looks like for your actual workload. You might find that the thing you've been avoiding is now a straightforward weekend job.
FFmpeGo's free tier is enough to run that test batch at no cost, and its compute-seconds pricing means the only thing you're betting on is a small trial run. Give it a shot—the worst case is you learn what your pipeline costs before committing to anything bigger.