100本の動画をまとめて MP3 にする、フォルダ内の全ファイルを 720p に縮める、といった一括変換は、Bash の for ループ 1 つで書けます。
⚠️ 実行前に確認してください(元ファイルを上書きするおそれ)
- 入力と同じ拡張子で出力するときは、ファイル名にサフィックス(末尾に足す
_720pなどの文字)を付けてください。video.mp4に対して${f%.mp4}.mp4は同じパスになり、FFmpeg は変換せずに止まります。./video.mp4とvideo.mp4のように書き方が違うと同じファイルだと気づかず、-y付きなら元ファイルを上書きして壊します -yは確認なしで上書きするので、パスを確かめるまでは付けないでください- 初めて実行するときは、
-n(出力ファイルがあればスキップ)を付けると安全です - 大事なファイルは、先にバックアップを取ってください
コマンド例
1. 基本:全 MP4 を MP3 に変換(forループ)
# カレントディレクトリの *.mp4 を全て *.mp3 に変換
for f in *.mp4; do
ffmpeg -nostdin -i "$f" -vn -c:a libmp3lame -q:a 2 "${f%.mp4}.mp3" -n
done
"$f":ファイル名にスペースがあっても正しく渡せるよう、ダブルクォートで囲みます。${f%.mp4}:Bash のパラメータ展開で、末尾の.mp4を取り除きます。-nostdin:処理の途中で FFmpeg が標準入力(キーボードなどからの入力)を待って止まるのを防ぎます(推奨)。-n:出力ファイルがすでにあれば、上書きせずにスキップします。初めて実行するときは-yより安全です。- MP4→MP3 は拡張子が変わるので、元ファイルを上書きすることはありません。それでも
-yはふだんから付けないほうが安全です。
2. 720p H.264 に一括圧縮
# 出力ファイル名に "_720p" サフィックスを付けること(必須)
# "${f%.mp4}.mp4" のようにサフィックスなしにすると、出力が入力と同じファイルになります
for f in *.mp4; do
ffmpeg -nostdin -i "$f" \
-vf scale=-2:720 -c:v libx264 -crf 23 -c:a aac \
"${f%.mp4}_720p.mp4" -n
done
サフィックスが必要な理由:
"${f%.mp4}.mp4"は入力のvideo.mp4と同じパスです。まったく同じ書き方なら、FFmpeg は変換しません。-nを付けているとFile 'video.mp4' already exists. Exiting.、-yを付けているとFFmpeg cannot edit existing files in-place.と出ます。ところが./video.mp4とvideo.mp4のように書き方が違うと同じファイルだと判定できず、-yを付けていると元ファイルが警告なしに上書きされて壊れます。
3. 変換済みファイルをスキップして再実行に対応
for f in *.mp4; do
case "$f" in *_720p.mp4) continue ;; esac # 前回の出力ファイルは飛ばす
out="${f%.mp4}_720p.mp4"
if [ -f "$out" ]; then
echo "スキップ: $out は既に存在します"
continue
fi
ffmpeg -nostdin -i "$f" -vf scale=-2:720 -c:v libx264 -crf 23 -c:a aac "$out"
done
途中で止めたときに書きかけだった出力ファイルも「既に存在する」として飛ばされるので、再実行の前に消してください。
4. 出力を別ディレクトリに保存
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" -n
done
5. サブディレクトリを再帰的に処理
find . -name "*.mp4" ! -name "*_converted.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" -n
done
6. GNU parallel で並列バッチ変換(マルチコア活用)
# 全CPUコアで並列変換(find で前回の出力 *_out.mp4 を除く)
find . -maxdepth 1 -name "*.mp4" ! -name "*_out.mp4" | parallel ffmpeg -nostdin -i {} -c:v libx264 -crf 23 -c:a aac {.}_out.mp4 -n
# 4並列に制限(CPU使用率を抑える)
find . -maxdepth 1 -name "*.mp4" ! -name "*_out.mp4" | parallel -j 4 ffmpeg -nostdin -i {} -c:v libx264 -crf 23 {.}_out.mp4 -n
7. xargs で並列処理(GNU parallel 不要)
find . -name "*.mp4" ! -name "*_out.mp4" | xargs -P 4 -I{} bash -c \
'ffmpeg -nostdin -i "$1" -c:v libx264 -crf 23 "${1%.mp4}_out.mp4" -n' _ {}
ログ記録付きバッチスクリプト(安全版)
#!/bin/bash
set -euo pipefail # エラー即終了・未定義変数検出・パイプエラー検出
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"
# 入力と出力のパス衝突チェック
if [ "$(realpath "$f")" = "$(realpath "$out" 2>/dev/null || echo "$out")" ]; then
echo "[SKIP] 入力と出力が同一パス: $f" | tee -a "$LOG_FILE"
continue
fi
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 処理中: $f" | tee -a "$LOG_FILE"
if ffmpeg -nostdin -i "$f" -vf scale=-2:720 -c:v libx264 -crf 23 -c:a aac "$out" \
-n -loglevel error 2>>"$LOG_FILE"; then
# 出力ファイルのサイズを確認(0バイトなら異常)
out_size=$(stat -c%s "$out" 2>/dev/null || echo 0)
if [ "$out_size" -lt 1024 ]; then
echo "[WARN] 出力サイズが異常に小さい: $out ($out_size bytes)" | tee -a "$LOG_FILE"
else
echo "[OK] $out (${out_size} bytes)" | tee -a "$LOG_FILE"
fi
else
echo "[ERROR] $f の変換に失敗" | tee -a "$LOG_FILE"
fi
done
echo "完了。ログ: $LOG_FILE"
このスクリプトの安全策:
set -euo pipefail:未定義の変数を使ったときや、ifの外のコマンドが失敗したときに、スクリプトを止めます。ifの条件として実行している FFmpeg は失敗しても止まらず、[ERROR]をログに書いて次のファイルに進みます。- パスの衝突チェック:入力と出力が同じパスなら、そのファイルはスキップします。
-n:出力ファイルがすでにあれば、上書きせずにスキップします(このときも FFmpeg は正常に終わるので、ログには[OK]と出ます)。- 出力サイズの確認:変換後のファイルがきちんとできているかを確かめます。
Windows バッチファイル(.bat)
@echo off
rem Windows バッチファイル: *.mp4 を *.mp3 に変換
for %%f in (*.mp4) do (
ffmpeg -nostdin -i "%%f" -vn -c:a libmp3lame -q:a 2 "%%~nf.mp3" -n
)
echo 変換完了
pause
逐次処理 vs 並列処理 比較表
| 方法 | ツール | 速度 | 同時に動く FFmpeg | 適した用途 |
|---|---|---|---|---|
for ループ |
bash 標準 | 順番に処理 | 1 本 | 少ないファイル・動作確認 |
xargs -P N |
GNU findutils | N並列 | N 本 | Unix/Linux の標準環境 |
GNU parallel |
要インストール | N並列・柔軟 | N 本 | 大量のファイル・複雑な処理 |
CPU のコア数より多く並列にしても速くなりません。増えた分は、同じコアやメモリ、ディスクが空くのを待つだけです。
並列にしてどれだけ速くなるかは、エンコーダが内部で使うスレッド数(libx264 は 1 本でも複数のコアを使い、libmp3lame は 1 コアだけです)や、メモリ、ストレージの速さでも変わります。まず数本のファイルで順番に処理した場合と比べ、かかった時間と出力が正常かを確かめてから並列数を増やしてください。
よく使うフラグ
| フラグ | 意味 | バッチ処理での必要性 |
|---|---|---|
-nostdin |
標準入力を待たない | 必須(ループが止まるのを防ぐ) |
-n |
出力ファイルがあればスキップ | 推奨(既存ファイルを守る) |
-y |
出力ファイルを確認なしで上書き | パスを確かめた再実行のときだけ使う |
-loglevel error |
エラーだけを出力 | ログが読みやすくなる |
トラブルシューティング
エラー1: バッチ処理の途中で FFmpeg が止まる
原因: FFmpeg が標準入力からの入力を待っています。
解決策: -nostdin を必ず付けます。
for f in *.mp4; do
ffmpeg -nostdin -i "$f" -c:v libx264 -crf 23 "${f%.mp4}_out.mp4" -n
done
エラー2: スペースを含むファイル名が正しく処理されない
原因: 変数を "$f" のようにダブルクォートで囲んでいません。
解決策: 変数をダブルクォートで囲みます。
# 間違い(スペースで分割される)
for f in *.mp4; do ffmpeg -i $f ...; done
# 正しい(ダブルクォートで囲む)
for f in *.mp4; do ffmpeg -nostdin -i "$f" ...; done
エラー3: *.mp4 が展開されず *.mp4 リテラルが渡される
原因: カレントディレクトリに .mp4 ファイルがないか、シェルが glob(* などのワイルドカード)を展開できていません。
解決策: まず ls *.mp4 で、ファイルがあるかを確かめます。
ls *.mp4 | head -5
# もしくは
find . -name "*.mp4" | head -5
エラー4: 変換完了後にストレージ不足でエラー
原因: 大量のファイルを変換して、ディスクがいっぱいになっています。
解決策: 変換前に空き容量を確かめ、出力先を別のドライブにします。
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" -n
done
エラー5: GNU parallel がインストールされていない
原因: parallel コマンドが見つかりません。
解決策: 次のコマンドでインストールします。
sudo apt install parallel # Ubuntu/Debian
brew install parallel # macOS
インストールできないときは、xargs -P 4 で代わりに並列処理できます。
FAQ
Q1. バッチ処理中に途中でエラーが出た場合、続きから再開できますか?
A. できます。-n(出力ファイルがあればスキップ)を付けて同じループをもう一度実行すると、変換済みのファイルを飛ばして続きから処理します。ただし、中断したときに書きかけだった出力ファイルも変換済みとして飛ばされるので、先に削除してから実行してください。
for f in *.mp4; do
case "$f" in *_out.mp4) continue ;; esac # 前回の出力ファイルは飛ばす
ffmpeg -nostdin -n -i "$f" -c:v libx264 -crf 23 "${f%.mp4}_out.mp4"
done
Q2. 100 本同時に変換すると速くなりますか?
A. CPU のコア数より多く並列にしても速くなりません。同じ CPU やメモリ、ディスクを取り合うだけです。libx264 は 1 本の変換でも複数のコアを使うので、並列にしても本数ぶん速くなるとは限りません。少ない並列数から試して、かかった時間を比べてください。
Q3. 変換後に元ファイルを自動削除したいのですが?
A. 削除は取り消せないので、必ず先にバックアップを取ってください。 次の例は、出力をいったん仮の名前で書き出し、FFmpeg がエラーなく終わったときだけ正式な名前に変えてから、元ファイルを削除します。出力ファイルがすでにあるときは、最後まで書けたファイルか分からないので、元ファイルを残します(-n を付けた FFmpeg は、既存の出力を飛ばしても正常終了を返します)。
for f in *.mp4; do
case "$f" in *_out.mp4) continue ;; esac # 前回の出力ファイルは飛ばす
out="${f%.mp4}_out.mp4"
if [ -e "$out" ]; then
echo "$out が既にあるので残しました: $f"
continue
fi
# -y が上書きするのは仮のファイル(中断で残ったものなど)だけ
if ffmpeg -nostdin -y -i "$f" -c:v libx264 -crf 23 -f mp4 "$out.part"; then
mv "$out.part" "$out" && rm "$f"
else
rm -f "$out.part"
echo "失敗したので残しました: $f"
fi
done
Q4. Windows PowerShell でも同じように書けますか?
A. 書けます。PowerShell では foreach ループを使います。
foreach ($f in Get-ChildItem *.mp4) {
ffmpeg -nostdin -i $f.FullName -c:v libx264 -crf 23 "$($f.BaseName)_out.mp4" -n
}
Q5. 変換にかかる時間を事前に見積もるには?
A. まず 1 ファイルを変換して時間を測り、ファイル数を掛けます。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"}'