Why this article exists
Python developers who need video processing almost always find ffmpeg-python first. It is the most popular Python FFmpeg wrapper with 11,000 GitHub stars and over 83,000 dependent repositories. The core pattern -- ffmpeg.input(), .output(), .run() -- is the first thing every user learns.
But ffmpeg-python has a dirty secret. The last release was v0.2.0 in July 2019. That is seven years without an update. There are 479 open issues, 46 open pull requests, and zero maintainer responses. A question titled "Is this package being maintained?" (Issue #807) has sat unanswered since November 2023.
This article covers three things: how ffmpeg-python input, output, and run actually work, the hidden costs and pitfalls of using an unmaintained library, and when a hosted FFmpeg API like Very Good FFmpeg is the smarter choice for production.
TL;DR
- ffmpeg-python is a pure-Python wrapper over the FFmpeg CLI. It builds command-line arguments and runs them via subprocess.
- The FFmpeg binary must be installed separately. This is the number one source of errors.
- The library is unmaintained since July 2019. Issue #807 asking about maintenance has no response. Issue #43 (progress tracking) has been open since 2017. Issue #200 (async support) has been open since 2019.
- For local scripts and learning, ffmpeg-python is fine. For production, a hosted FFmpeg API removes the binary dependency and scales without infrastructure management.
- Very Good FFmpeg offers a Python SDK (
pip install very-good-ffmpeg) that replaces ffmpeg-python's.run()with an async API call to dedicated 16-core / 32 GB instances. No local FFmpeg needed. - Self-hosting FFmpeg on a comparable 16-core VM costs $336/month minimum on DigitalOcean (even when idle), while Very Good FFmpeg charges $0.50/GB with zero idle cost.
What is ffmpeg-python and how does it work?
ffmpeg-python is a thin Python wrapper around the FFmpeg command-line tool. It does not reimplement FFmpeg in Python. It builds an FFmpeg argument list and passes it to subprocess.Popen.
The global video streaming market was valued at $811.37 billion in 2025 and is projected to reach $3,394.56 billion by 2034 at a 17.00% CAGR (Fortune Business Insights). Streaming now commands 36% of all TV usage, surpassing cable at 27.9% and broadcast at 24.2% (Nielsen). There are approximately 1.8 billion video streaming subscriptions worldwide, and 83% of US households have at least one subscription, up from 52% in 2015. Americans spend 3 hours 6 minutes per day streaming video. With video processing demand exploding, the tools developers choose matter more than ever.
ffmpeg-python sits alongside other Python video libraries but occupies a specific niche:
| Library | Approach | Best For |
|---|---|---|
| ffmpeg-python | Thin CLI wrapper | Translating FFmpeg commands to Python |
| subprocess + raw FFmpeg | Direct CLI calls | Developers who know FFmpeg flags |
| moviepy | High-level API | Simple edits, beginners |
| opencv | Frame-level processing | Computer vision, real-time |
| PyAV | C library bindings | Performance-critical, low-level |
| Very Good FFmpeg SDK | Hosted API client | Production, serverless, scale |
The fundamental trade-off: ffmpeg-python is the most Pythonic way to work with FFmpeg commands, but it depends on a local FFmpeg binary and has not been maintained since 2019.
How do I use ffmpeg-python input, output, and run?
The basic pattern has three steps: create an input stream, chain output options, and execute.
import ffmpeg
stream = ffmpeg.input('input.mp4')
stream = ffmpeg.output(stream, 'output.mp4')
ffmpeg.run(stream)The fluent chaining style is more common in real code:
import ffmpeg
ffmpeg.input('input.mp4').output('output.mp4').run()You can pass encoding options as keyword arguments:
ffmpeg.input('input.mp4').output(
'output.mp4',
vcodec='libx264',
acodec='aac',
video_bitrate='2M',
audio_bitrate='192k'
).run()What does ffmpeg.input() accept?
ffmpeg.input() takes a filename string as its first argument. It also accepts pipe: for stdin input, plus keyword arguments that map to FFmpeg input options:
# Read raw video from stdin
ffmpeg.input('pipe:', format='rawvideo', pix_fmt='rgb24', s='640x480')
# Seek before reading
ffmpeg.input('input.mp4', ss=10, t=5)Common input options you can pass as kwargs: ss (seek position), t (duration), f or format (input format), framerate, hwaccel (hardware acceleration).
What does ffmpeg.output() support?
.output() takes one or more stream specs and a filename. Keyword arguments map to FFmpeg output options:
ffmpeg.input('input.mp4').output(
'output.mp4',
vcodec='libx265',
preset='fast',
crf=28,
acodec='copy'
)Some FFmpeg flags do not translate to clean Python keywords. Use the dict-unpacking hack for these:
ffmpeg.input('input.mp4').output(
'output.mp4',
**{'c:v': 'libx264', 'qscale:v': 3, 'tag:v': 'hvc1'}
)How does ffmpeg.run() execute the command?
.run() calls subprocess.Popen with the compiled FFmpeg arguments. It accepts several parameters:
out, err = ffmpeg.input('input.mp4').output('output.mp4').run(
capture_stdout=True,
capture_stderr=True,
quiet=True,
overwrite_output=True
)For non-blocking execution, use run_async():
process = ffmpeg.input('input.mp4').output('output.mp4').run_async()
process.wait()run_async() returns a subprocess.Popen object. This is useful for long-running encodes or when you pipe data through stdin/stdout.
How can I inspect the generated command?
Use compile() or get_args() to see the exact FFmpeg command:
args = ffmpeg.input('input.mp4').output('output.mp4').get_args()
print(args)
# ['-i', 'input.mp4', 'output.mp4']
args = ffmpeg.input('input.mp4').output(
'output.mp4', vcodec='libx264'
).compile()
print(args)
# ['ffmpeg', '-i', 'input.mp4', '-vcodec', 'libx264', 'output.mp4']This is essential for debugging when the generated command does not behave as expected. compile() includes the ffmpeg executable name, while get_args() returns only the arguments.
How do I handle complex FFmpeg filter graphs in ffmpeg-python?
ffmpeg-python supports complex filter graphs through method chaining and dedicated filter methods:
input1 = ffmpeg.input('video.mp4')
input2 = ffmpeg.input('overlay.png')
overlay = ffmpeg.overlay(input1, input2, x=10, y=10)
output = ffmpeg.output(overlay, 'output.mp4')
ffmpeg.run(output)For concatenation:
input1 = ffmpeg.input('part1.mp4')
input2 = ffmpeg.input('part2.mp4')
joined = ffmpeg.concat(input1, input2, v=1, a=1)
output = ffmpeg.output(joined, 'merged.mp4')
ffmpeg.run(output)Use .filter() for any FFmpeg filter that lacks a dedicated method:
stream = ffmpeg.input('input.mp4')
stream = ffmpeg.filter(stream, 'fps', fps=30)
stream = ffmpeg.filter(stream, 'scale', w=1280, h=720)
output = ffmpeg.output(stream, 'output.mp4')
ffmpeg.run(output)How do I pass global FFmpeg flags?
Use .global_args() for flags that apply to FFmpeg globally rather than to a specific input or output:
ffmpeg.input('input.mp4').output('output.mp4').global_args(
'-report', '-loglevel', 'debug'
).run()The -report flag generates a detailed log file, which is helpful for debugging.
What are the most common ffmpeg-python pitfalls?
ffmpeg-python wraps FFmpeg but does not abstract away its complexity. Several issues trip up users.
The FFmpeg binary must be installed separately
This is the number one source of errors. The README explicitly states: "ffmpeg-python makes no attempt to download/install FFmpeg -- it is merely a pure-Python wrapper."
# FileNotFoundError if ffmpeg is not in PATH
import ffmpeg
ffmpeg.input('input.mp4').output('output.mp4').run()On Windows, the common error is FileNotFoundError: [WinError 2] The system cannot find the file specified. Install FFmpeg via conda, choco, or download from ffmpeg.org and add it to PATH. The ffmpeg-downloader pip package and static-ffmpeg are portable alternatives.
Filter operations silently drop audio
Filter methods like .hflip(), .overlay(), and .drawbox() produce output with no audio. FFmpeg internally uses -map and drops unmentioned streams. Pass the audio stream explicitly:
input_video = ffmpeg.input('input.mp4')
video = input_video.video.hflip()
output = ffmpeg.output(video, input_video.audio, 'output.mp4')
ffmpeg.run(output)This is a recurring theme on Stack Overflow. Users regularly lose audio during overlay or filter operations and must manually re-merge it.
The library is unmaintained
ffmpeg-python has not been updated since July 2019. The PyPI page lists only Python 3.3 through 3.6 classifiers. Three GitHub issues illustrate the problem:
- Issue #807 (Nov 2023): "Is this package being maintained by anyone? It's a useful package and it would be a shame for it to die." No maintainer response.
- Issue #43 (Dec 2017): Request for progress tracking during ffmpeg execution. Users want to see frame-by-frame progress during encodes. Labeled "enhancement" but never addressed. Open for over 8 years.
- Issue #200 (May 2019): Request for
run_asyncio()returning a coroutine for async/await syntax. Modern Python supports async natively. Also never addressed.
With 83,532 dependent repositories and 479 open issues, this gap between adoption and maintenance is a serious dependency risk.
Windows console window flashing
On Windows, ffmpeg-python calls subprocess.Popen without creationflags=subprocess.CREATE_NO_WINDOW. A console window flashes briefly on each .run() call. The workaround is monkey-patching ffmpeg-python's internal run function or replacing it with a direct subprocess call.
FFmpeg-to-Python flag mapping is inconsistent
Not all FFmpeg CLI flags have clean keyword aliases. Flags like -c:v, -tag:v, and -bitexact must use dict syntax. This means you constantly switch between keyword args and dict unpacking, reducing readability.
What is the real cost of self-hosting FFmpeg vs a hosted API?
When you outgrow local ffmpeg-python scripts and need to run video processing in production, you face a choice: provision your own FFmpeg server or use a hosted API. Here is the real cost breakdown.
Self-hosted VM options
| Provider | Instance | Specs | Hourly Cost | Monthly Cost (always-on) |
|---|---|---|---|---|
| AWS EC2 | c7g.large | 2 vCPU, 4 GB | $0.0725 on-demand, $0.038 spot | $52.20 on-demand |
| AWS EC2 | c6i.2xlarge | 8 vCPU, 16 GB | $0.34 on-demand, $0.18 spot | $244.80 on-demand |
| AWS EC2 | c6i.4xlarge | 16 vCPU, 32 GB | $0.68 on-demand | $489.60 on-demand |
| DigitalOcean | c-8 | 8 vCPU, 16 GB, NVMe | $0.25/hr ($168/mo cap) | $168.00 |
| DigitalOcean | c-16 | 16 vCPU, 32 GB, NVMe | $0.50/hr ($336/mo cap) | $336.00 |
Hosted API pricing
| Provider | Model | Starting Rate | Volume Discount | Idle Cost |
|---|---|---|---|---|
| Very Good FFmpeg | Per GB processed | $0.50/GB | $0.08/GB at 100GB+ | $0 |
| AWS Elemental MediaConvert | Per minute + encoding | $0.01-$0.30/min | Reserved pricing | $0 |
| Bitmovin | Per encoding minute | Varies by codec | Enterprise tiers | $0 |
Real-world example: processing 100 GB of video per month
Self-hosted (DigitalOcean c-8):
- VM: $168/month (fixed, even if idle)
- Storage: ~$10/month (100 GB block storage)
- Bandwidth: ~$5-15/month depending on transfer
- Engineering time to maintain FFmpeg, handle upgrades, monitor usage
- Total: $183+/month regardless of actual processing volume
Very Good FFmpeg:
- First 100 GB at $0.50/GB = $50
- Or with volume tier: 100 GB at $0.08/GB = $8
- No idle cost. No infrastructure management.
- Total: $8-50/month, scaled to actual usage
For a team processing 100 GB of video monthly, hosted API saves 60-95% while eliminating infrastructure overhead.
How do I migrate from ffmpeg-python to a hosted FFmpeg API?
If you have existing ffmpeg-python code, the migration is straightforward. Here is the step-by-step process.
Step 1: Identify the FFmpeg command your script runs
Use ffmpeg-python's built-in debugging tools to see the exact command:
# Before migration: inspect the command
args = ffmpeg.input('input.mp4').output(
'output.mp4', vcodec='libx264', preset='fast', crf=23
).compile()
print(' '.join(args))
# ffmpeg -i input.mp4 -vcodec libx264 -preset fast -crf 23 output.mp4Copy the FFmpeg argument list. This is the command you will send to the hosted API.
Step 2: Install the Very Good FFmpeg Python SDK
pip install very-good-ffmpegStep 3: Get your API key and upload input files
Sign up at verygoodffmpeg.com and create an API key. Upload your input files to cloud storage (S3, GCS, or R2). The hosted API fetches inputs from URLs.
Step 4: Replace ffmpeg-python .run() with VGF SDK call
Before (ffmpeg-python):
import ffmpeg
ffmpeg.input('input.mp4').output(
'output.mp4',
vcodec='libx264',
preset='fast',
crf=23
).run(overwrite_output=True)After (Very Good FFmpeg SDK):
from very_good_ffmpeg import VGF
vgf = VGF("your-api-key")
job = vgf.run(
input_files={
"input.mp4": "https://your-bucket.s3.amazonaws.com/input.mp4"
},
output_files=["output.mp4"],
ffmpeg_commands=[
"-i {{input.mp4}} -c:v libx264 -preset fast -crf 23 -c:a aac {{output.mp4}}"
],
wait=True,
)
print(f"Job status: {job.status}")
print(f"Output URL: {job.output['output.mp4']}")Step 5: Handle the results
The SDK returns presigned URLs for output files. Download them directly:
import requests
output_url = job.output['output.mp4']
response = requests.get(output_url)
with open('output.mp4', 'wb') as f:
f.write(response.content)Migration checklist
| ffmpeg-python feature | Equivalent in VGF SDK |
|---|---|
.input(filename) | input_files dict with URLs |
.output(filename, **kwargs) | ffmpeg_commands string |
.run() | vgf.run(wait=True) |
.run_async() | vgf.run(wait=False) returns job ID |
capture_stdout / capture_stderr | Real-time job logs in dashboard |
overwrite_output=True | SDK generates unique output paths |
| Filter graphs | Express as full FFmpeg command string |
The key differences: the hosted SDK is async by default, requires no local binary, and processes files on dedicated infrastructure with up to 6-hour runtime and optional GPU acceleration.
When should I use ffmpeg-python vs subprocess vs a hosted API?
The choice depends on your deployment environment, scale, and how much infrastructure you want to manage.
| Factor | ffmpeg-python | subprocess.run() | Very Good FFmpeg SDK |
|---|---|---|---|
| FFmpeg binary needed | Yes, in PATH | Yes, in PATH | No |
| Setup time | pip + binary install | No pip dep, binary needed | pip + API key |
| Maintenance | Unmaintained since 2019 | Your own code | Vendor handles infra |
| Async support | No (Issue #200, open since 2019) | Manual | Native async |
| Progress tracking | No (Issue #43, open since 2017) | Manual | Real-time logs |
| Control | Full FFmpeg flags via wrapper | Full FFmpeg flags | Full FFmpeg flags |
| Scalability | Local machine only | Local machine only | 16-core / 32 GB per job |
| Serverless | Requires bundled binary | Requires bundled binary | Works out of the box |
| GPU encoding | Requires local GPU + drivers | Requires local GPU + drivers | Nvidia GPU support |
| Idle cost | $0 | $0 | $0 |
| Production cost | Free (software) | Free (software) | $0.50/GB (drops to $0.08/GB) |
Stick with ffmpeg-python when
You are running local development, learning FFmpeg concepts, or writing one-off scripts on your own machine. The Pythonic API is more readable than shell strings for basic operations like format conversion and thumbnail extraction.
Use raw subprocess when
You already know FFmpeg CLI flags well and want zero abstraction overhead. subprocess.run(['ffmpeg', '-i', 'in.mp4', 'out.mp4']) is the simplest possible path. The downside is more boilerplate and the same local binary requirement.
Switch to a hosted API when
Any of these are true:
- You deploy to serverless platforms (AWS Lambda, Vercel, Cloudflare Workers) where bundling a 30-50 MB FFmpeg binary is cumbersome
- You need to process videos longer than serverless function timeout limits (Lambda caps at 15 minutes)
- You manage multiple machines and do not want to install and version FFmpeg on each one
- You want to offload compute-heavy encoding so your application server stays responsive under load
- You need GPU-accelerated encoding without managing Nvidia drivers and CUDA versions
- You want to avoid the dependency risk of an unmaintained library with 479 open issues and zero maintainer response
- You process variable workloads and do not want to pay for an always-on VM that sits idle most of the time
How does ffmpeg-python compare to other Python video libraries?
ffmpeg-python is not the only option for video processing in Python. Here is how it stacks up:
- moviepy: Higher-level library built on top of FFmpeg. Easier API for simple edits but less control. Heavier dependency footprint and also wraps FFmpeg binary.
- opencv: Excellent for frame-level processing and computer vision. Does not handle encoding pipelines or container format conversion natively.
- PyAV: Direct Python bindings for FFmpeg's C libraries. More performant but much steeper learning curve. Requires understanding FFmpeg internals.
- ffmpeg-python: Best balance of simplicity and FFmpeg control for command-line-style workflows, but unmaintained.
- Very Good FFmpeg SDK: Best for production at scale. Same FFmpeg commands but no binary dependency. Async by default. Handles infrastructure.
For most encoding and transcoding tasks, ffmpeg-python provides the right level of abstraction for local work. When you need reliability, scale, or serverless deployment, a hosted API that accepts the same FFmpeg commands is the natural evolution.
FAQ
Does ffmpeg-python install FFmpeg for me?
No. ffmpeg-python is a pure Python wrapper. You must install the FFmpeg binary separately. The README explicitly states this: "ffmpeg-python makes no attempt to download/install FFmpeg." This is the most common source of FileNotFoundError.
How do I check which FFmpeg binary ffmpeg-python is using?
ffmpeg-python uses whichever ffmpeg binary is in your system PATH. Check with subprocess.run(['which', 'ffmpeg']) on Linux/macOS or subprocess.run(['where', 'ffmpeg']) on Windows.
Can I use ffmpeg-python in AWS Lambda or other serverless platforms?
Only if you bundle the FFmpeg static binary with your deployment package. The Lambda layer size limit (250 MB unzipped) makes this feasible but adds maintenance overhead. FFmpeg static binaries are around 30-50 MB. A hosted FFmpeg API avoids this entirely and is not subject to Lambda's 15-minute execution timeout.
What is the difference between run() and run_async()?
run() blocks until the FFmpeg process finishes and returns stdout/stderr bytes. run_async() returns a subprocess.Popen object immediately, allowing you to do other work while FFmpeg runs. Call .wait() on the Popen object to block until completion. Neither supports Python async/await natively (see Issue #200).
How do I pass complex FFmpeg flags to ffmpeg-python?
Use Python dict unpacking for flags without clean keyword aliases: **{'c:v': 'h264_nvenc', 'pix_fmt': 'yuv420p'}. Use .global_args() for flags that must appear before the first input. Use .filter() for filter parameters.
What happens when ffmpeg-python breaks in production?
Since the library is unmaintained, there is no vendor to fix bugs, patch security issues, or add support for newer FFmpeg versions. If a future OS update or FFmpeg release breaks compatibility, your pipeline breaks with no fix available. This is the primary risk of depending on ffmpeg-python in production.
How does Very Good FFmpeg pricing compare to self-hosting?
For light workloads (20 GB/month), Very Good FFmpeg costs about $10 compared to $168/month minimum for a DigitalOcean droplet. For heavy workloads (500 GB/month), Very Good FFmpeg costs $40-250 depending on volume tier, compared to $336+/month for dedicated infrastructure. There is no idle cost with the hosted model. You pay only for what you process.
References
- ffmpeg-python GitHub Repository
- ffmpeg-python on PyPI (v0.2.0, July 2019)
- ffmpeg-python API Reference
- GitHub Issue #807: Is ffmpeg-python being maintained?
- GitHub Issue #43: Progress tracking support
- GitHub Issue #200: Async/await support
- ffmpeg-python GitHub dependents (83,000+ repos)
- Very Good FFmpeg Python SDK on PyPI
- Very Good FFmpeg Hosted API
- AWS EC2 Compute-Optimized Instance Pricing
- DigitalOcean CPU-Optimized Droplet Pricing
- Video Streaming Market Statistics 2025-2034
- Stack Overflow: Combining video and audio in ffmpeg-python
- Stack Overflow: Hiding console output in ffmpeg-python
- Stack Overflow: Converting FFmpeg CLI to ffmpeg-python code
- Stack Overflow: ffmpeg-python overlay dropping audio
- Stack Overflow: ffmpeg binary not found on Windows