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"}'

関連記事