Automation and Development

How to Convert Images in Bulk with FFmpeg: a Practical Guide

5 min read

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

bash
# macOS
brew install ffmpeg

# Ubuntu / Debian
sudo apt install ffmpeg

# Windows (via Chocolatey)
choco install ffmpeg

Verify it's working:

bash
ffmpeg -version

The 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.

bash
# 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

bash
# Windows Command Prompt
mkdir output
for %f in (*.png) do ffmpeg -i "%f" -q:v 2 "output/%~nf.jpg"

bash
# 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 valueQualityTypical use
1–2Near-losslessPrint, archiving
3–5High qualityWeb photography
6–10Medium qualityThumbnails, previews
11+Low qualityAvoid 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.

bash
# 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

bash
# 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

bash
# 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.

bash
# 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

bash
# Linux / macOS — convert all WebP to PNG
mkdir -p output
for f in *.webp; do
  ffmpeg -i "$f" "output/${f%.webp}.png"
done

No 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:

bash
# 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

bash
# 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.

bash
# 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

bash
# 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

bash
# 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"
done

Bulk Convert with Subdirectory Support

The basic loop only processes files in the current directory. To recurse into subdirectories while preserving the folder structure:

bash
# 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

bash
# 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:

bash
# 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"' _ {}

bash
# Using GNU parallel (more control over job count)
mkdir -p output
parallel ffmpeg -i {} -q:v 3 output/{.}.jpg ::: *.png

The 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:

bash
# 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:

bash
# 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
done

Common Conversion Reference

ConversionCommand (Linux/macOS)Notes
All PNG → JPGfor f in *.pngdo ffmpeg -i "$f" -q:v 3 output/${f%.png}.jpg
All JPG → WebPfor f in *.jpgdo ffmpeg -i "$f" -quality 85 output/${f%.jpg}.webp
All JPG → PNGfor f in *.jpgdo ffmpeg -i "$f" output/${f%.jpg}.png
All → WebP (lossless)for f in *.pngdo ffmpeg -i "$f" -lossless 1 output/${f%.png}.webp
Resize + convertfor f in *.jpgdo 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

By default, FFmpeg does not copy EXIF metadata (camera settings, GPS, date) when converting images. To preserve metadata, add -map_metadata 0 to your command. For example: ffmpeg -i input.jpg -map_metadata 0 -q:v 3 output.jpg. Note that some metadata fields may still be lost depending on the output format.

This happens when the source JPG is already heavily compressed. Converting a low-quality JPG to WebP can produce a larger file because WebP is re-encoding already-degraded data. Always convert from the highest-quality source available. If you're converting from JPG, use -quality 80-85 and compare file sizes before running the full batch.

Use PNG as your output format (lossless), or use -lossless 1 for WebP. For JPG, -q:v 1 or -q:v 2 gets close to lossless but still involves some compression. True lossless JPG isn't possible — if you need lossless, use PNG or WebP lossless.

It depends on your FFmpeg build. On macOS with Homebrew's FFmpeg, HEIC conversion usually works. On Linux, you may need to build FFmpeg with libheif support. Test with a single file before running a batch: ffmpeg -i test.heic test.jpg

Add -y to automatically overwrite, or -n to skip existing files. Example: ffmpeg -y -i input.png output.jpg (always overwrites) or ffmpeg -n -i input.png output.jpg (skips if output exists).

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.

Share