You have 500 PNG screenshots that need to be WebP for your website. Or a folder of TIFF exports from Lightroom that need to become JPG. Or hundreds of product photos shot in RAW that your CMS won't accept. Doing these one at a time isn't an option.
FFmpeg is best known as a video tool, but it handles image conversion just as well — and because it runs from the command line, it scales to any number of files with a single command. This guide covers the exact commands you need for every common bulk image conversion scenario, on all three operating systems.
Why FFmpeg for Images?
Most people reach for ImageMagick for bulk image work, and that's a perfectly valid choice. FFmpeg has some specific advantages though:
It's likely already installed if you do any video work. It handles a very wide range of formats natively. It integrates naturally into shell scripts and CI pipelines that already use FFmpeg for other tasks. And for converting between common web formats — JPG, PNG, WebP, AVIF — it produces excellent results with simple commands.
For RAW camera formats (CR2, NEF, ARW), use darktable or RawTherapee instead. FFmpeg's RAW support is limited. For everything else, it's one of the fastest options available.
Installing FFmpeg
# macOS
brew install ffmpeg
# Ubuntu / Debian
sudo apt install ffmpeg
# Windows (via Chocolatey)
choco install ffmpegVerify it's working:
ffmpeg -versionThe Core Pattern
Every bulk conversion command follows the same structure. On Linux and macOS you use a shell loop; on Windows you use a for loop in Command Prompt or a foreach loop in PowerShell.
The key principle: never overwrite your source files. Always write output to a separate directory. The examples below all follow this convention.
Bulk Convert PNG to JPG
The most common conversion. PNG to JPG reduces file size significantly for photographs, at the cost of lossless quality.
# Linux / macOS — convert all PNG in current directory
mkdir -p output
for f in *.png; do
ffmpeg -i "$f" -q:v 2 "output/${f%.png}.jpg"
done# Windows Command Prompt
mkdir output
for %f in (*.png) do ffmpeg -i "%f" -q:v 2 "output/%~nf.jpg"# Windows PowerShell
New-Item -ItemType Directory -Force output
Get-ChildItem *.png | ForEach-Object {
ffmpeg -i $_.FullName -q:v 2 "output/$($_.BaseName).jpg"
}The -q:v 2 flag controls quality. The scale runs from 1 (best, largest file) to 31 (worst, smallest file). Values 2–5 are the practical range for web use:
| -q:v value | Quality | Typical use |
|---|---|---|
| 1–2 | Near-lossless | Print, archiving |
| 3–5 | High quality | Web photography |
| 6–10 | Medium quality | Thumbnails, previews |
| 11+ | Low quality | Avoid for most uses |
Bulk Convert JPG to WebP
WebP typically produces files 25–35% smaller than equivalent JPG at the same perceived quality. Converting your existing JPG library to WebP is one of the highest-impact optimisations for web performance.
# Linux / macOS — lossy WebP at quality 85
mkdir -p output
for f in *.jpg *.jpeg; do
[ -f "$f" ] || continue
ffmpeg -i "$f" -quality 85 "output/${f%.*}.webp"
done# Linux / macOS — lossless WebP (for graphics, screenshots, logos)
mkdir -p output
for f in *.png; do
ffmpeg -i "$f" -lossless 1 "output/${f%.png}.webp"
done# Windows Command Prompt
mkdir output
for %f in (*.jpg) do ffmpeg -i "%f" -quality 85 "output/%~nf.webp"Quality guideline: 80–85 is the sweet spot for photographs. Values above 90 produce files nearly as large as JPG with minimal visual benefit. For logos and graphics with text, use lossless WebP instead.
Bulk Convert Any Format to PNG
Converting to PNG preserves full quality — useful when you need a lossless intermediate format or when preparing images for further editing.
# Linux / macOS — convert all JPG to PNG
mkdir -p output
for f in *.jpg *.jpeg; do
[ -f "$f" ] || continue
ffmpeg -i "$f" "output/${f%.*}.png"
done# Linux / macOS — convert all WebP to PNG
mkdir -p output
for f in *.webp; do
ffmpeg -i "$f" "output/${f%.webp}.png"
doneNo quality flag is needed for PNG — it's lossless. FFmpeg will encode at maximum PNG quality by default.
Bulk Convert Mixed Formats
When your source folder contains a mix of JPG, PNG, and WebP files and you want everything as a single output format:
# Linux / macOS — convert everything to JPG
mkdir -p output
for f in *.jpg *.jpeg *.png *.webp *.tiff *.bmp; do
[ -f "$f" ] || continue
ffmpeg -i "$f" -q:v 3 "output/${f%.*}.jpg"
done# Windows Command Prompt — convert JPG, PNG, and WebP to JPG
mkdir output
for %f in (*.jpg *.png *.webp) do ffmpeg -i "%f" -q:v 3 "output/%~nf.jpg"Bulk Resize While Converting
FFmpeg can resize and convert in a single pass using the -vf scale filter. This is significantly faster than converting and then resizing separately.
# Resize all PNG to 1080px wide, convert to JPG
# Height scales proportionally (the -1 preserves aspect ratio)
mkdir -p output
for f in *.png; do
ffmpeg -i "$f" -vf scale=1080:-1 -q:v 3 "output/${f%.png}.jpg"
done# Resize to max 800px on longest side (scale down only, never up)
mkdir -p output
for f in *.jpg; do
ffmpeg -i "$f" -vf "scale='min(800,iw)':'min(800,ih)':force_original_aspect_ratio=decrease" \
-q:v 3 "output/${f%.jpg}.jpg"
done# Create square thumbnails (crop to centre, then scale)
mkdir -p output
for f in *.jpg; do
ffmpeg -i "$f" -vf "crop='min(iw,ih)':'min(iw,ih)',scale=200:200" \
"output/${f%.jpg}_thumb.jpg"
doneBulk Convert with Subdirectory Support
The basic loop only processes files in the current directory. To recurse into subdirectories while preserving the folder structure:
# Linux / macOS — convert all PNG recursively, preserve directory structure
find . -name "*.png" -not -path "./output*" | while read f; do
output="output/${f#./}"
mkdir -p "$(dirname "$output")"
ffmpeg -i "$f" -q:v 3 "${output%.png}.jpg"
done# Linux / macOS — flat output (all files in one output directory)
mkdir -p output
find . -name "*.png" -exec sh -c \
'ffmpeg -i "$1" -q:v 3 "output/$(basename "${1%.png}").jpg"' _ {} \;Parallel Processing for Speed
By default, the loop processes one file at a time. On a machine with multiple CPU cores, you can run conversions in parallel using xargs or GNU parallel:
# Linux / macOS — parallel conversion using xargs (4 simultaneous jobs)
mkdir -p output
ls *.png | xargs -P 4 -I {} sh -c 'ffmpeg -i "$1" -q:v 3 "output/${1%.png}.jpg"' _ {}# Using GNU parallel (more control over job count)
mkdir -p output
parallel ffmpeg -i {} -q:v 3 output/{.}.jpg ::: *.pngThe practical speedup depends on your CPU and storage. On an 8-core machine with SSD storage, parallel processing typically reduces conversion time by 3–5x. On spinning disks, the disk I/O becomes the bottleneck and parallelism helps less.
Adding a Progress Indicator
For large batches, it's useful to know how far along the conversion is:
# Linux / macOS — show progress with count
total=$(ls *.png | wc -l)
count=0
mkdir -p output
for f in *.png; do
count=$((count + 1))
echo "Converting $count/$total: $f"
ffmpeg -loglevel quiet -i "$f" -q:v 3 "output/${f%.png}.jpg"
done
echo "Done. Converted $count files."The -loglevel quiet flag suppresses FFmpeg's own output so only your progress messages appear.
Handling Errors Gracefully
When processing hundreds of files, some may be corrupted or in an unexpected format. A robust script logs failures without stopping the batch:
# Linux / macOS — skip failed files, log errors
mkdir -p output
error_log="conversion_errors.txt"
> "$error_log"
for f in *.jpg *.jpeg *.png *.webp; do
[ -f "$f" ] || continue
if ffmpeg -loglevel error -i "$f" -quality 85 "output/${f%.*}.webp" 2>>"$error_log"; then
echo "✓ $f"
else
echo "✗ $f (see $error_log)"
fi
doneCommon Conversion Reference
| Conversion | Command (Linux/macOS) | Notes |
|---|---|---|
| All PNG → JPG | for f in *.png | do ffmpeg -i "$f" -q:v 3 output/${f%.png}.jpg |
| All JPG → WebP | for f in *.jpg | do ffmpeg -i "$f" -quality 85 output/${f%.jpg}.webp |
| All JPG → PNG | for f in *.jpg | do ffmpeg -i "$f" output/${f%.jpg}.png |
| All → WebP (lossless) | for f in *.png | do ffmpeg -i "$f" -lossless 1 output/${f%.png}.webp |
| Resize + convert | for f in *.jpg | do ffmpeg -i "$f" -vf scale=1080:-1 -q:v 3 output/${f%.jpg}.jpg |
When FFmpeg Is Not the Right Tool
FFmpeg is excellent for batch conversion between common formats, but it has gaps worth knowing:
RAW camera formats (CR2, NEF, ARW, DNG) — FFmpeg has limited support. Use darktable, RawTherapee, or LibRaw for RAW processing.
AVIF encoding — FFmpeg can produce AVIF via -c:v libaom-av1, but it's extremely slow without hardware acceleration. For bulk AVIF conversion, libavif or Squoosh CLI is faster.
HEIC/HEIF — Support depends on how FFmpeg was compiled. On macOS with Homebrew, it typically works. On Linux it often requires additional libraries.
ICC colour profile preservation — FFmpeg doesn't always preserve embedded colour profiles. For colour-critical work, ImageMagick or ExifTool handles this more reliably.
Just need to convert a few images quickly? Command-line tools require setup time. For occasional one-off conversions, an online tool like FastConvert is faster — no installation, no script, just upload and download.
Frequently Asked Questions
Summary
FFmpeg's bulk image conversion comes down to a simple shell loop: iterate over your source files, run the conversion command on each, write output to a separate directory. The commands in this guide cover the most common scenarios — PNG to JPG, JPG to WebP, mixed formats, resize with conversion, and recursive directory processing — on all three operating systems.
The two things most guides miss: always test on a small batch before running against your full library, and use -loglevel quiet combined with an error log so you know which files failed without wading through FFmpeg's verbose output.
Need to convert images without the command line? Try FastConvert image converter — free, no signup required.