破損した動画ファイルを自動で高速に検出する

ネットで探してみると破損した動画ファイルを修正するフリーソフトの話はやたら出てきます。大体の場合は 動画ファイルの破綻検出 は手動と言うか、動画ファイルが上手く再生できないのを目視で確認して、それからソフトを使用して修正する例が殆どです。でも、動画ファイルの破損検出は最初から最後まで再生しなければならず、時間も掛かり、結構めんどいのです。実運用ではフォーマット上、破綻している所はないかをその場で調べたい場合があります。ダウンロードファイルが正しいかどうかすぐ調べたいとか。 一度に多量に動画ファイルの破綻検出をしたい場合とか。そこで、ffmpeg を使って自動で動画の破損を検出するスクリプト、なんちゃって動画エラー検出 を組んでみました。まあ、あくまで自分専用で、動画再生前に参考に一回使うだけですので、使い勝手は期待してはいけません。なんちゃって仕様です。最初は 検出精度は置いといて、動画ファイルの破綻検出を自動でする事 が目標です。私はこのシステムを debian base の RAID システム = Openmediavault6 (以降 OMV6) で主に使いますので、OMV6 の中に組み込みましたが、Linux系 OS ならば普通に使えると思います。

ffmpeg インストール & 簡易実験

まず ffmpeg をOMV6にインストールします。SSH 端末を開いて

# apt-get install ffmpeg

インストールに結構時間がかかります。最初に簡単な実験をしてみます。まず、ちゃんとエラーなく再生のできる 30秒の動画ファイル1 (=IPX-1.mkv) と そのファイルにバイナリエディタで x00 x00 2バイト を それぞれ、2か所に書き込んで “きず”を付けた動画ファイル1r (=IPX-1r.mkv) を用意しました。適当なディレクトリに配置して

# ffmpeg -v error -i IPX-1.mkv -f null - < /dev/null

パラメーターの意味は -v (=詳細を出力する → error の詳細を出力)  -i (=入力ファイル)  -f (=強制フォーマットで出力する → null なので出力しない)  – (= 出力ファイル名 → – なのでない) になります。あと、ffmpeg は あくまで、標準入力 標準出力の フィルタソフトなので、標準入力に < /dev/null を忘れずに入れて動作を安定させます。これがないと変な挙動をします。結果を以下に示します。

“きず”をつけたファイルは2行、警告を出力していますが、その内 1 行には error の文字を含んでいます。実際には壊れているファイルは多量にこの error を吐きます。まずは、なんちゃって動画エラー検出 として  error 行を数えて動画エラー検出とする事にしました。エラー検出の方法は色々考えられますので、色々な(壊れている)ファイルを ffmpeg で検出してみて、どんなエラー出力が出るか(又は何も出ないか) を見て判定アルゴリズムを変えてみると良いと思います。

なんちゃって動画エラー検出スクリプトの構成

動画エラー検出スクリプトは 2つのスクリプトから構成されます。1つ目は VideoSort.sh 2つ目は VideoCheck.sh 両スクリプト共  $PATH の通っている /sbin/ の下に置きます。

VideoSort.sh は 処理するべき動画ファイルリスト を /var/log/installer/Video_lst の下にフルパスで作成します。次に VideoCheck.sh はこのリストに従って 動画ファイルのエラーを検出して行きます。結果は処理リストと同様のファイルリストを緑色、黄色、赤色で作成します。色分けされているのでぱっと見で結果が解ります。試作では error 行 0 で 緑色 error 行 1-9 で 黄色 error 行 10以上で 赤色としています。この辺りの判定基準は後で色々検討の余地ありです。

VideoSort.sh      . . .

以下に VideoSort.sh のソースリストを示します。

#!/bin/bash
# VideoSort.sh script set

DIR_BASE=/var/log/installer/Video_dir
LOG_SAVE=/var/log/installer/Video_log
LST_SAVE=/var/log/installer/Video_lst
TMP_SAVE=/var/log/installer/Video_tmp

echo "/zfs/RAIDZ2-1/00_00_00 NAS300 (倉庫)" > $DIR_BASE      # 個々にカスタマイズする事

if [ $# = 0 ]; then
    echo ""
    echo " VideoSort.sh \$1 \$2 ... → (例) VideoSort.sh '動画フォルダ名 (ok)' mp4 ..."
    echo " LOG_SAVE → /var/log/installer/Video_log (=Exec Command)"
    echo " LST_SAVE → /var/log/installer/Video_lst (=Sort List)"
    echo " TMP_SAVE → /var/log/installer/Video_tmp (=Temp Data)"
    echo ""
fi

if [ $# != 0 ]; then
    echo -n "" > $TMP_SAVE
    date > $LOG_SAVE
    P_NOW="1"
    _IFS=$IFS; IFS=$'\n'
    for S in $@; do
        if [ $P_NOW -ge 2 ]; then
            read _DIR < $DIR_BASE
            echo "# find \"$_DIR\"/\"$1\" -name \*.$S >> $TMP_SAVE" >> $LOG_SAVE
            find "$_DIR"/"$1" -name \*.$S >> $TMP_SAVE
        fi
        ((P_NOW++))
    done
    IFS=$_IFS
    echo "# sort -u < $TMP_SAVE > $LST_SAVE" >> $LOG_SAVE
    sort -u < $TMP_SAVE > $LST_SAVE
fi

最初に 動画ファイルのディレクトリ構成を説明します。動画ファイルは 例では

/zfs/RAIDZ2-1/00_00_00 NAS300 (倉庫)/恋心は玉の如き <全45話>/*.ts または /temp/*.mkv

の構造になっています。①個々に異なるディレクト構造  ➁ディレクトリ又はファイルに漢字 やスペースを含む構造  でも問題なく処理できるスクリプトを作成してあります。

VideoSort.sh は 内部に

DIR_BASE=/var/log/installer/Video_dir  # base のディレクトリ 例では /zfs/RAIDZ2-1/00_00_00 NAS300(倉庫) 
LOG_SAVE=/var/log/installer/Video_log   # log として実際に実行した通りにコマンドが記録される。
LST_SAVE=/var/log/installer/Video_lst  # 結果 フルパスでリストが記録される
TMP_SAVE=/var/log/installer/Video_tmp   # 一時使用領域

の 4 つの変数を含んでおり、 まず、Video_dir にベースとなるディレクトリ 例では

 echo "/zfs/RAIDZ2-1/00_00_00 NAS300 (倉庫)" > $DIR_BASE      # 個々にカスタマイズする事

で書き込む事により設定できます。 個々にカスタマイズして下さい。スクリプトは

# VideoSort.sh $1 $2 $3 ...

と実行します。ここで、$1 は処理するファイルが格納されているフォルダ名となり、このフォルダー名 以下のサブフォルダーも含んだ .$2 .$3 …. (= 例えば .mkv .ts .avi など) の動画ファイルがリスト化されます。また、$1以下のパラメータを記述しない場合は 備忘録的にコマンド例を含むメモ書きが表示されます。実際の実行例を 各変数等も表示して以下に示します。

VideoCheck.sh execute

以下に VedeoCheck.sh のソースリストを示します。

#!/bin/bash
# VideoCheck.sh script set

LOG_SAVE=/var/log/installer/Video_log
LST_SAVE=/var/log/installer/Video_lst
TMP_SAVE=/var/log/installer/Video_tmp
RSU_SAVE=/var/log/installer/Video_rsu

if [ "execute" != "$1" ]; then
    echo ""
    echo " VideoCheck.sh execute"
    echo " LOG_SAVE → /var/log/installer/Video_log (=Exec Command)"
    echo " LST_SAVE ← /var/log/installer/Video_lst (=Sort List)"
    echo " TMP_SAVE → /var/log/installer/Video_tmp (=Temp Data)"
    echo " RSU_SAVE → /var/log/installer/Video_rsu (=Result Data)"
    echo ""
    exit
fi

LSUM=0; LNUM=0; LN_G=0; LN_Y=0; LN_R=0
while read LST; do
    ((LSUM++))
done < $LST_SAVE

if [ $((LSUM)) -eq 0 ]; then
    echo "No Input Source"
    exit
fi

echo -n `date`; echo " $LNUM (処理済) / $LSUM (合計) Green = $LN_G Yellow = $LN_Y Red = $LN_R"

echo -n `date` > $LOG_SAVE
echo " $LNUM (処理済) / $LSUM (合計) Green = $LN_G Yellow = $LN_Y Red = $LN_R" >> $LOG_SAVE
echo "" >> $LOG_SAVE

echo -n `date` > $RSU_SAVE
echo " $LNUM (処理済) / $LSUM (合計) Green = $LN_G Yellow = $LN_Y Red = $LN_R" >> $RSU_SAVE

while read LST; do
    ffmpeg -v error -i "$LST" -f null - < /dev/null 2>&1 | grep -i error | wc -l > $TMP_SAVE
    read RLT1 < $TMP_SAVE; ((LNUM++))

    echo "# ffmpeg -v error -i $LST -f null - < /dev/null 2>&1 | grep -i error | wc -l # Ans $RLT1" >> $LOG_SAVE

    if [ $((RLT1)) -eq 0 ]; then
        echo -e "\e[32m" >> $RSU_SAVE; echo -n "$LST" >> $RSU_SAVE; echo -e "\e[m" >> $RSU_SAVE
        echo -e "\e[32m" ; echo -n "$LST" ; echo -e "\e[m"; RLT1=0; ((LN_G++))    #Green
    fi
    if [ $((RLT1)) -ge 10 ]; then
        echo -e "\e[31m" >> $RSU_SAVE; echo -n "$LST" >> $RSU_SAVE; echo -e "\e[m" >> $RSU_SAVE
        echo -e "\e[31m" ; echo -n "$LST" ; echo -e "\e[m"; RLT1=0; ((LN_R++))    #Red
    fi
    if [ $((RLT1)) -ne 0 ]; then
        echo -e "\e[33m" >> $RSU_SAVE; echo -n "$LST" >> $RSU_SAVE; echo -e "\e[m" >> $RSU_SAVE
        echo -e "\e[33m" ; echo -n "$LST" ; echo -e "\e[m"; RLT1=0; ((LN_Y++))    #Yellow
    fi

    echo -n `date`; echo " $LNUM (処理済) / $LSUM (合計) Green = $LN_G Yellow = $LN_Y Red = $LN_R"
    echo -n `date` >> $LOG_SAVE
    echo " $LNUM (処理済) / $LSUM (合計) Green = $LN_G Yellow = $LN_Y Red = $LN_R" >> $LOG_SAVE
    echo "" >> $LOG_SAVE

    echo -n `date` >> $RSU_SAVE
    echo " $LNUM (処理済) / $LSUM (合計) Green = $LN_G Yellow = $LN_Y Red = $LN_R" >> $RSU_SAVE
done < $LST_SAVE

VideoSort.sh を実行した後に VideoCheck.sh execute を実行します。

VideoCheck.sh は 内部に

LOG_SAVE=/var/log/installer/Video_log   # log として実際に実行した通りにコマンドが記録される。
LST_SAVE=/var/log/installer/Video_lst  # 処理ファイル フルパスで処理リストが記録されている
TMP_SAVE=/var/log/installer/Video_tmp   # 一時使用領域
RSU_SAVE=/var/log/installer/Video_rsu   # 結果 フルパスリストに色付けして記録される。

の 4 つの変数を含んでおり、 結果は画面に表示されると同時に Video_rsu に格納されます。スクリプトは

# VideoCheck.sh execute

と実行します。ここで、$1 のexecute を書かないと、備忘録的にメモが表示されます。実際の実行例を以下に示します。VideoCheck.sh execute は大抵の場合、時間が掛かる事が多いです。 (= 動画ファイルTotal の 1/10強掛かる) 。

実行例では、Video_log を表示していないが長くなるので、省略しました。Video_logの最後にはコメントの形 # Ans xxで計算結果を追加してあります。試作プログラムでは 0 = 完全なファイル 1-9 = 注意ファイル  >=10 = 壊れている と判断する事にしました。例では赤がありませんが、偶々です。調べたファイルには error 10個以上のものがありませんでした。この辺りの閾値は適当ですので、なんちゃって動画エラー検出 の由来となります(^o^)。 結果は画面に表示すると同時に、検査ファイル名自体に色を付けて RSU_SAVE = /var/log/installer/Video_rsu に格納 していますので、ぱっと見でエラーのあるファイルを確認する事ができます。また、ファイルの検査か終わった時間を記録していますので、前のファイル検査の時間との差分でファイルの検査にかかった時間がわかります。私のシステムでは 実動作時間の 10倍弱の速さで検査できるみたいです。 (1時間の動画ファイルならば 6-7分)。まだまだ、なんちゃって動画エラー検出で時間もかかります。この検出部分の判定アルゴリズムは改良が必要だと思いますが、とりあえず 動画ファイルの破綻検出を自動でする事 はできました。

タイトルとURLをコピーしました