Need to convert 100 MP4s to MP3, compress an entire folder to 720p, or process videos overnight while you sleep? FFmpeg batch conversion takes just a Bash for loop. Add skip logic, error handling, and parallel processing and you can process thousands of files safely and reliably. Time to complete: 10 minutes.
Tested with: FFmpeg 6.1 (ubuntu-latest / GitHub Actions CI-validated)
What You Will Learn
- Basic
forloop pattern for bulk conversion - Automatic output filename generation (extension replacement)
- Skip-if-exists logic for resumable jobs
- Saving output to a separate directory
- GNU parallel and xargs for multi-core parallel processing
- Error handling and log recording
- Windows batch file (.bat) syntax
- Five common errors and fixes
- Five frequently asked questions
Command Examples
1. Basic: Convert All MP4s to MP3
# Convert every *.mp4 in the current directory to *.mp3
for f in *.mp4; do
ffmpeg -nostdin -i "$f" -vn -c:a libmp3lame -q:a 2 "${f%.mp4}.mp3" -y
done
"$f"— double-quote the variable to handle filenames with spaces${f%.mp4}— Bash parameter expansion that strips the.mp4extension-nostdin— prevents FFmpeg from waiting on stdin (required for loops)-y— overwrite output without asking
2. Bulk Compress to 720p H.264
for f in *.mp4; do
ffmpeg -nostdin -i "$f" \
-vf scale=1280:-2 -c:v libx264 -crf 23 -c:a aac \
"${f%.mp4}_720p.mp4" -y
done
3. Skip Already-Converted Files (Resumable)
for f in *.mp4; do
out="${f%.mp4}_720p.mp4"
if [ -f "$out" ]; then
echo "Skipping: $out already exists"
continue
fi
ffmpeg -nostdin -i "$f" -vf scale=1280:-2 -c:v libx264 -crf 23 -c:a aac "$out"
done
4. Save Output to a Separate Directory
mkdir -p output
for f in input/*.mp4; do
base=$(basename "$f" .mp4)
ffmpeg -nostdin -i "$f" -c:v libx264 -crf 23 -c:a aac "output/${base}.mp4" -y
done
5. Recursively Process Subdirectories
find . -name "*.mp4" -type f | while IFS= read -r f; do
out="${f%.mp4}_converted.mp4"
ffmpeg -nostdin -i "$f" -c:v libx264 -crf 23 -c:a aac "$out" -y
done
6. GNU parallel — Multi-Core Parallel Processing
# Use all CPU cores
ls *.mp4 | parallel ffmpeg -nostdin -i {} -c:v libx264 -crf 23 -c:a aac {.}_out.mp4
# Limit to 4 parallel jobs (controlled CPU usage)
ls *.mp4 | parallel -j 4 ffmpeg -nostdin -i {} -c:v libx264 -crf 23 {.}_out.mp4
7. xargs — Parallel Processing Without GNU parallel
find . -name "*.mp4" | xargs -P 4 -I{} bash -c \
'ffmpeg -nostdin -i "$1" -c:v libx264 -crf 23 "${1%.mp4}_out.mp4" -y' _ {}
Full Logging Batch Script
#!/bin/bash
INPUT_DIR="./input"
OUTPUT_DIR="./output"
LOG_FILE="./batch_convert.log"
mkdir -p "$OUTPUT_DIR"
for f in "$INPUT_DIR"/*.mp4; do
[ -f "$f" ] || continue
base=$(basename "$f" .mp4)
out="$OUTPUT_DIR/${base}_720p.mp4"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Processing: $f" | tee -a "$LOG_FILE"
if ffmpeg -nostdin -i "$f" -vf scale=1280:-2 -c:v libx264 -crf 23 -c:a aac "$out" \
-loglevel error 2>>"$LOG_FILE"; then
echo "[OK] $out" | tee -a "$LOG_FILE"
else
echo "[ERROR] Failed: $f" | tee -a "$LOG_FILE"
fi
done
echo "Done. Log: $LOG_FILE"
Windows Batch File (.bat)
@echo off
rem Convert *.mp4 to *.mp3 on Windows
for %%f in (*.mp4) do (
ffmpeg -nostdin -i "%%f" -vn -c:a libmp3lame -q:a 2 "%%~nf.mp3" -y
)
echo Done
pause
Sequential vs Parallel Comparison
| Method | Tool | Speed | CPU Cores Used | Best For |
|---|---|---|---|---|
for loop | bash built-in | Sequential | 1 | Few files, debugging |
xargs -P N | coreutils | N parallel | N | Unix/Linux without extras |
GNU parallel | install required | N parallel, flexible | N | Large batches, complex jobs |
Parallel processing note: Running more jobs than your physical core count doesn’t help — it can cause thermal throttling and slow things down. Aim for 50–75% of your physical core count.
Essential Flags for Batch Processing
| Flag | Meaning | Necessity |
|---|---|---|
-nostdin | Disable stdin waiting | Required — prevents loop from freezing |
-y | Overwrite output without asking | Needed for re-runs |
-loglevel error | Only log errors | Keeps log files clean |
-n | Skip if output already exists | Opposite of -y — protects existing files |
Troubleshooting
Problem 1: FFmpeg Hangs Mid-Batch
Cause: FFmpeg is waiting for stdin input
Fix: Always add -nostdin:
for f in *.mp4; do
ffmpeg -nostdin -i "$f" -c:v libx264 -crf 23 "${f%.mp4}_out.mp4" -y
done
Problem 2: Filenames With Spaces Break the Loop
Cause: Variable $f not quoted with double quotes
Fix:
# Wrong (splits on spaces)
for f in *.mp4; do ffmpeg -i $f ...; done
# Correct (always double-quote)
for f in *.mp4; do ffmpeg -nostdin -i "$f" ...; done
Problem 3: *.mp4 Is Passed Literally Instead of Expanded
Cause: No .mp4 files exist in the current directory, or the shell cannot expand the glob
Fix: Verify files exist first:
ls *.mp4 | head -5
# or
find . -name "*.mp4" | head -5
Problem 4: Disk Full Error Mid-Conversion
Cause: Large batch fills the destination disk
Fix: Check free space before starting and output to a different drive:
df -h .
mkdir -p /mnt/external/output
for f in *.mp4; do
ffmpeg -nostdin -i "$f" -c:v libx264 -crf 23 "/mnt/external/output/${f%.mp4}.mp4" -y
done
Problem 5: parallel Command Not Found
Cause: GNU parallel is not installed
Fix:
sudo apt install parallel # Ubuntu/Debian
brew install parallel # macOS
If you can’t install it, use xargs -P 4 as a drop-in replacement.
FAQ
Q1. Can I resume a batch job that was interrupted partway through?
A. Yes — use -n to skip files whose output already exists:
for f in *.mp4; do
ffmpeg -nostdin -n -i "$f" -c:v libx264 -crf 23 "${f%.mp4}_out.mp4"
done
Q2. Does running 100 parallel jobs make it 100× faster?
A. No. Beyond your physical core count, more parallelism causes contention and thermal throttling. Set -j to 50–75% of your core count for best throughput.
Q3. How do I delete the source files after successful conversion?
A. Only delete on successful exit (exit code 0):
for f in *.mp4; do
out="${f%.mp4}_out.mp4"
if ffmpeg -nostdin -i "$f" -c:v libx264 -crf 23 "$out" -y; then
rm "$f"
fi
done
Q4. Can I do this in Windows PowerShell?
A. Yes — use a foreach loop:
foreach ($f in Get-ChildItem *.mp4) {
ffmpeg -nostdin -i $f.FullName -c:v libx264 -crf 23 "$($f.BaseName)_out.mp4" -y
}
Q5. How do I estimate how long the batch will take?
A. Convert one file and time it, then multiply by the total count. Or get the total duration of all files with ffprobe:
find . -name "*.mp4" -exec ffprobe -v error \
-show_entries format=duration -of default=noprint_wrappers=1:nokey=1 {} \; \
| awk '{sum+=$1} END {print sum/60 " minutes total"}'
Related Articles
- FFmpeg via Pipe (stdin/stdout)
- Stream Mapping with -map
- Video Format Conversion — Transcoding to MP4
- Video Compression Complete Guide — CRF, Bitrate, and Presets
Tested with: ffmpeg 6.1.1 / Ubuntu 24.04 (GitHub Actions runner)
Primary sources: ffmpeg.org/ffmpeg.html / gnu.org/software/bash/manual/