ffmpeg での動画結合に一時ファイル作るのが面倒なのでシェル関数にした話

Linux,Mac,プログラムbash,ffmpeg,シェルスクリプト,動画編集

ffmpeg を使ってコマンドラインで動画を結合したいときは割とあるのですが、そのためには

  • 結合するファイル一覧を書き込んだテキストファイルを作って
  • ffmpeg -f concat -i テキストファイル -c copy 出力ファイル コマンドを実行する

という手順を踏む必要があり、(テキストファイルを作るのが)めんどくさいです。この面倒くささをサボるためのシェル関数を作ったのでメモしておきます。

引数の動画を結合する ffmpeg-cat

.bashrc に以下のような関数を定義しておきました<ref>なお、私は zsh 使いなので書き込んだのは .zshrc です。</ref>。

function ffmpeg-cat() {
    tmpFile=$(mktemp)
    outFile=${@:$#:1}

    for files in ${@:1:$(($# - 1))}
    do
        echo 'file' $(realpath $files) >> $tmpFile
    done

    ffmpeg -f concat -safe 0 -i $tmpFile -c copy $outFile
    rm $tmpFile
}

これを使うことで結合したい動画のファイル名一覧と、出力ファイル名を引数に与えれば結合できます。

# 引数に結合したい複数のファイル, 一番最後に出力したいファイルを指定
ffmpeg-cat aa.mp4 bb.mp4 cc.mp4 output.mp4
# 以下のような使い方もできる. 便利
ffmpeg-cat *.mp4 output.mp4

関数の解説

ffmpeg で動画を結合するには、結合したい動画ファイルのパスを以下のような形で各行ごとに行頭に 'file’ をつけて書き込んだテキストファイルを用意する必要があります。

file /path/to/movie1.mp4
file /path/to/movie2.mp4
file /path/to/movie3.mp4

そのため、引数に与えたファイル名をこの形でテキストファイルに書き込む処理が以下の部分です。

tmpFile=$(mktemp)

for files in ${@:1:$(($# - 1))}
do
    echo 'file' $(realpath $files) >> $tmpFile
done

まず mktemp コマンドを使って一時ファイルを作ります。これは通常 /tmp ディレクトリにユニークな(重複しない)ファイルを作ります。また引数に与えた要素のうち、最後の要素は出力ファイル名なので、それ以外の要素に対してループを行います。最後の要素を除いた引数を ${@:1:$(($# – 1))} の形でループ変数に代入します。

bash 系統の場合、${配列:取り出す要素のオフセット指定:取り出す要素の個数} で任意の要素が取り出せます。以下のパラメータを指定しています。

配列@引数の配列
取り出す要素のオフセット指定1引数の 1 番目
取り出す要素の個数$(($# – 1))引数配列の要素数 $# から -1 した数

ループ内では echo コマンドを使って 'file’ を付して上で realpath コマンドを使ってファイルパスを絶対パスに変換して tmpFile に追記していきます。

ここで作成したテキストファイルは次のようにして ffmpeg コマンドに与えます。

outFile=${@:$#:1}

ffmpeg -f concat -safe 0 -i $tmpFile -c copy $outFile

ここで outFile として引数の最後の要素を代入しています。上記の方法とほとんど同じです。

配列@引数の配列
取り出す要素のオフセット指定$#引数配列の要素数
取り出す要素の個数1最後の要素から 1 つ目

ffmpeg のオプションとして -c copy を指定しています。これは元々の動画のフォーマットをそのまま使うということです。動画のフォーマットがバラバラだった場合はデフォルトのフォーマットが使われます。また -safe 0 オプションはファイルパスに日本語があったりしてエラーを吐いたりするのを防ぐために入れています。

最後に rm $tmpFile で一時ファイルを削除して終了します。

参考資料