以前の記事で書いたように、自宅のサーバー上で録画環境を構築している。このサーバーでは、番組の録画が完了すると、録画した TS ファイルを h.264 の MP4 ファイルにエンコードして別のディレクトリに保存するようなスクリプトを動かしている。元の TS ファイルもそのまま保持していたのだが、最近ストレージ容量が厳しくなってきたので、エンコード済みの TS ファイルを削除しようと思い立った。

問題は、エンコードが正しく終了せず破損した MP4 ファイルがいくつか存在することだ。これらのファイルについては元の TS ファイルを残しておきたい。そこで、大量のファイルの中から破損したファイルを高速に検出する方法を調べた。

調べた結果、以下のようにするのが良さそうだ。(ffprobe コマンドは ffmpeg をインストールするとインストールされる)

$ ffprobe -v error -f lavfi movie="/path/to/file.mp4" 2>&1 | grep lavfi;

まずパイプ前段の ffprobe がエラーを検出し、検出したエラーを標準エラー出力に書き出してくれる。例えばエラーがあると(ffprove 自体の出力は)以下のようになる。

[mov,mp4,m4a,3gp,3g2,mj2 @ 0x5654a3bb3400] moov atom not found
[Parsed_movie_0 @ 0x5654a3bb2e80] Failed to avformat_open_input '/path/to/file.mp4'
[lavfi @ 0x5654a3bb1000] Error initializing filter 'movie' with args '/path/to/file.mp4'
movie=/path/to/file.mp4: Invalid data found when processing input

筆者が持っているファイル群では、正常にエンコードされたファイルでもなぜか音声にエラーが検出され

[aac @ 0x55a600a76380] Reserved bit set.
[aac @ 0x55a600a76380] Number of bands (4) exceeds limit (2).

のように表示された。このような出力は正常なものと見なしたかったため、標準出力にリダイレクトした上で lavfi からのエラーを grep で拾うようにしている。grep の出力や終了コードを確認することで、与えたファイルが壊れているかどうかを判断できる。

上記をもとに、大量のファイルを処理する例としては以下のようになる。

$ ls /path/to/movie_dir |\
  xargs -I@ bash -c '
    fn="@";
    if (ffprobe -v error -f lavfi movie="$fn" 2>&1 | grep lavfi) >/dev/null; then
      echo @ is corrupted;
    else
      echo @ is safe;
    fi
  '

上記のスクリプトは、指定されたディレクトリ内のファイルを全て調べ、壊れていた場合は

{{ filename }} is corrupt

壊れていない場合は

{{ filename }} is safe

と表示する。

上記の検査方法により、無事、正常にエンコードできたファイルの元 TS ファイルだけを削除することができた。これで壊れていると判断されたファイルは実際に VLC で再生できないことを確認できた。一方で正常とされたファイルについて(数が多いので)全てを確認できていないので、実は壊れているものが含まれている可能性もあるが、そこは...そんなことがなかったことを祈りたい...。

調べる方法としては、他にも ffmpeg を使って

$ ffmpeg -v error -i "/path/to/file.mp4" -f null -;

とする方法もあるようだが、こちらは全フレームを見て検査するようで結構時間がかかる。ただ、その分より正確に壊れたファイルを検出できそうだ。

参考にしたサイト

How can I tell if a video file is corrupted? FFmpeg? - Stack Overflow