FFmpeg is the de facto open-source toolkit for converting, transcoding, and processing audio and video. It is free, scriptable, and runs everywhere - your laptop, a Linux server, a Docker container, or a serverless function. This guide covers the FFmpeg command-line essentials and then shows how to drive it from Python 3 for automated, repeatable media pipelines.
Two ideas make everything else click:
- Container vs codec. A container (
.mp4,.mov,.webm,.mkv) is the wrapper file; the codec (H.264, H.265/HEVC, VP9, AAC, Opus, MP3) is how the audio/video inside is actually encoded. Changing the file extension does not change the codec - FFmpeg does. - Stream copy vs re-encode.
-c copyremuxes streams into a new container without decoding them: near-instant and lossless, but only works when the existing codecs are already what you want. Anything that changes resolution, codec, bitrate, or needs a frame-accurate cut requires a re-encode.
Installing FFmpeg
FFmpeg is a standalone binary - install it once on the machine or image that runs your code.
# Ubuntu / Debian
sudo apt update && sudo apt install -y ffmpeg
# macOS (Homebrew)
brew install ffmpeg
# Windows (winget) or: choco install ffmpeg
winget install Gyan.FFmpeg
# Verify it is on PATH and see which encoders are built in
ffmpeg -versionThe FFmpeg command model
Almost every command follows the same shape: global options, one or more inputs (-i), per-stream/output options, then the output file. FFmpeg infers the output format from the extension.
ffmpeg [global options] -i input.ext [output options] output.extUseful flags you will reuse constantly:
-c:v/-c:a- choose the video / audio codec (e.g.libx264,libx265,libvpx-vp9,aac,libopus).-c copy- copy all streams (no re-encode).-crf N- Constant Rate Factor quality for x264/x265 (lower = better quality + bigger file; 18 β visually lossless, 23 default, 28 small).-preset- encoder speed/efficiency trade-off (ultrafastβ¦slow); slower preset = smaller file at the same CRF.-b:v/-b:a- target a fixed video / audio bitrate instead of CRF.-vn/-an- drop video / drop audio.-y- overwrite the output without prompting (handy in scripts).
Common FFmpeg recipes (command line)
Change container or codec
# Remux MOV to MP4 with no re-encode (fast + lossless) when codecs are already compatible
ffmpeg -i input.mov -c copy output.mp4
# Re-encode to H.264 video + AAC audio - the most widely compatible MP4
ffmpeg -i input.mov -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k output.mp4
# Convert to WebM (VP9 video + Opus audio) for the web
ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 32 -b:v 0 -c:a libopus output.webmExtract or convert audio
# Pull the audio track out unchanged (no re-encode) into an .m4a/AAC container
ffmpeg -i input.mp4 -vn -c:a copy output.m4a
# Rip to MP3 (-vn drops video, -q:a 2 is ~190 kbps VBR)
ffmpeg -i input.mp4 -vn -c:a libmp3lame -q:a 2 output.mp3
# WAV to Opus - excellent quality at a low bitrate
ffmpeg -i input.wav -c:a libopus -b:a 96k output.opusResize, scale, and reframe
# Scale to 720p height, keep aspect ratio (-2 rounds width to the nearest even number)
ffmpeg -i input.mp4 -vf "scale=-2:720" -c:a copy output_720p.mp4
# Change the frame rate to 30 fps
ffmpeg -i input.mp4 -r 30 output_30fps.mp4Trim and cut clips
# Fast cut, no re-encode: seek with -ss before -i (keyframe-aligned, may be slightly off)
ffmpeg -ss 00:00:10 -to 00:00:25 -i input.mp4 -c copy clip.mp4
# Frame-accurate cut: put -ss/-to after -i and re-encode
ffmpeg -i input.mp4 -ss 00:00:10 -to 00:00:25 -c:v libx264 -crf 20 -c:a aac clip_exact.mp4Stream-copy cuts are instant but can only start/end on keyframes; re-encoding gives you exact boundaries at the cost of CPU time.
Compress and control quality with CRF
# Smaller H.264 file - raise CRF for less quality and a smaller size
ffmpeg -i input.mp4 -c:v libx264 -crf 28 -preset slow -c:a aac -b:a 96k smaller.mp4
# H.265 / HEVC: ~40-50% smaller than H.264 at similar quality (slower, less universal)
ffmpeg -i input.mp4 -c:v libx265 -crf 28 -preset medium -c:a aac compressed_hevc.mp4
# Target a specific bitrate instead of CRF (e.g. for streaming limits)
ffmpeg -i input.mp4 -c:v libx264 -b:v 1M -maxrate 1M -bufsize 2M output.mp4Thumbnails, frames, and GIFs
# Single thumbnail captured at 5 seconds
ffmpeg -ss 00:00:05 -i input.mp4 -frames:v 1 thumb.jpg
# Export one frame per second as numbered PNGs
ffmpeg -i input.mp4 -vf fps=1 frame_%04d.png
# High-quality GIF using a generated palette (two passes)
ffmpeg -i input.mp4 -vf "fps=12,scale=480:-1:flags=lanczos,palettegen" palette.png
ffmpeg -i input.mp4 -i palette.png -lavfi "fps=12,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse" output.gifConcatenate (join) files
# Same codecs: list inputs in files.txt and stream-copy (concat demuxer)
# files.txt -> lines like: file 'part1.mp4'
ffmpeg -f concat -safe 0 -i files.txt -c copy joined.mp4
# Different codecs/resolutions: re-encode with the concat filter
ffmpeg -i a.mp4 -i b.mp4 \
-filter_complex "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]" \
-map "[v]" -map "[a]" joined.mp4Hardware-accelerated encoding
# NVIDIA NVENC (GPU H.264) - far faster than CPU x264 for batch jobs
ffmpeg -i input.mp4 -c:v h264_nvenc -preset p5 -cq 23 -c:a copy output.mp4
# Linux VAAPI (Intel / AMD GPUs)
ffmpeg -hwaccel vaapi -vaapi_device /dev/dri/renderD128 -i input.mp4 \
-vf 'format=nv12,hwupload' -c:v h264_vaapi output.mp4Hardware encoders trade a little quality (or size at the same bitrate) for a big speed-up - ideal for high-volume transcoding, less so when you need the smallest possible file.
Driving FFmpeg from Python
In production these commands rarely run by hand - they run inside a Celery task, an API endpoint, or a serverless function. There are three common ways to invoke FFmpeg from Python. All of them require the ffmpeg binary to be installed on the system, container image, or Lambda layer (or bundled via imageio-ffmpeg); the libraries are wrappers, not the codecs themselves.
Option 1: subprocess (full control)
Calling FFmpeg directly with subprocess.run and a list of arguments is the most predictable approach. Pass args as a list (never shell=True with untrusted input) so filenames with spaces or special characters cannot be misinterpreted, and check the return code.
import subprocess
def convert(src, dst):
"""Re-encode src to an H.264/AAC MP4. Returns True on success."""
result = subprocess.run(
[
"ffmpeg", "-y", # -y overwrites the output if present
"-i", src,
"-c:v", "libx264", "-crf", "23", "-preset", "medium",
"-c:a", "aac", "-b:a", "128k",
dst,
],
capture_output=True,
text=True,
)
if result.returncode != 0:
print(result.stderr) # ffmpeg logs progress + errors to stderr
return False
return True
convert("input.mov", "output.mp4")Option 2: ffmpeg-python (filter graphs in Python)
The ffmpeg-python library lets you build the command - including filter graphs - as Python objects. It assembles the argument list and still calls the same ffmpeg binary under the hood.
# pip install ffmpeg-python
import ffmpeg
# Simple re-encode
(
ffmpeg
.input("input.mov")
.output("output.mp4", vcodec="libx264", crf=23, acodec="aac", audio_bitrate="128k")
.run(overwrite_output=True)
)
# Filter graph: scale to 720p, then write a VP9 WebM
(
ffmpeg
.input("input.mp4")
.filter("scale", -2, 720)
.output("output_720p.webm", vcodec="libvpx-vp9", crf=32)
.run(overwrite_output=True)
)Option 3: higher-level libraries
When you want convenience over fine-grained control:
- moviepy - clip editing, concatenation, text/overlays, and compositing in pure Python.
- pydub - dead-simple audio conversion and slicing (calls ffmpeg under the hood).
- imageio-ffmpeg - bundles a prebuilt ffmpeg binary, so you do not have to install one separately - handy for locked-down or serverless environments.
# pip install pydub (still needs an ffmpeg binary on PATH)
from pydub import AudioSegment
sound = AudioSegment.from_file("input.wav", format="wav")
sound.export("output.mp3", format="mp3", bitrate="192k")Which approach should I use?
| Approach | Control | Ease | Best for |
|---|---|---|---|
subprocess + ffmpeg |
Full - every flag | Verbose | Production batch jobs, exact CLI parity |
ffmpeg-python |
High - filter graphs | Medium | Programmatic pipelines, dynamic commands |
moviepy / pydub |
Limited - high level | Easy | Quick edits, audio slicing, prototypes |
A good default: reach for subprocess for predictable server-side transcoding, ffmpeg-python when you are composing complex filters in code, and moviepy/pydub for quick one-off edits.
Where these pipelines fit
Media conversion almost never lives alone - it sits inside a larger Python workflow that ingests an upload, transcodes it, and ships the result. A few related building blocks:
- Generate QR codes in Python - share links to the converted media.
- Send media via Twilio (SMS/MMS) - deliver a clip or audio file once it is ready.
- Web scraping with BeautifulSoup - source files or metadata to process.
- Create Excel reports with XlsxWriter - log the results of a batch conversion.
We build media and document pipelines like these as part of our Python development services. Across 12+ years and 50+ delivered projects, FFmpeg-backed transcoding shows up in everything from user-generated-content platforms to internal tooling.
Frequently Asked Questions
How do I extract just the audio from a video?
Use -vn to drop the video stream. To keep the original audio untouched, stream-copy it: ffmpeg -i input.mp4 -vn -c:a copy output.m4a. To convert to MP3 instead, ffmpeg -i input.mp4 -vn -c:a libmp3lame -q:a 2 output.mp3.
What is CRF and how do I control quality?
CRF (Constant Rate Factor) sets a target quality for x264/x265 encoders. Lower numbers mean better quality and larger files. For libx264, about 18 is near visually lossless, 23 is the default, and 28 is noticeably smaller. Pair CRF with -preset - a slower preset squeezes out a smaller file at the same CRF.
When can I use -c copy?
Use stream copy when you are only changing the container or trimming on keyframes, and the existing codecs are already what you want. It remuxes without re-encoding, so it is near-instant and lossless. You must re-encode whenever you change the codec, resolution, or bitrate, or need a frame-accurate cut.
Should I call FFmpeg with subprocess or ffmpeg-python?
Use subprocess when you want full, explicit control and exact parity with the command line - it is the most predictable for production batch jobs. Use ffmpeg-python when you prefer building filter graphs in Python or generating commands dynamically. Both ultimately run the same ffmpeg binary.
How do I convert an MP4 to WebM?
Re-encode to VP9 video and Opus audio: ffmpeg -i input.mp4 -c:v libvpx-vp9 -crf 32 -b:v 0 -c:a libopus output.webm. The -b:v 0 flag puts VP9 in constant-quality mode; raise the CRF for smaller files. Expect WebM encoding to be slower than H.264.
Do I need to install FFmpeg separately when using Python?
Yes. Libraries such as ffmpeg-python, pydub, and moviepy are wrappers that call an ffmpeg binary - they do not include the codecs themselves. Install ffmpeg on the machine or container (or add a Lambda layer), or use imageio-ffmpeg, which ships a prebuilt binary the others can point to.
Why is my converted video so large or low quality?
Quality and size are governed by your codec and rate-control settings, not the file extension. For x264/x265, lower the CRF for higher quality (bigger file) or raise it to shrink the file; switch to H.265/VP9 for better compression; or set an explicit bitrate with -b:v when you must hit a size or streaming target.