mattintosh note

どこかのエンジニアモドキの備忘録

ffmpeg concatでファイルリストを作成せずに動画を結合する

ffmpeg によるビデオファイルの結合方法は公式に載っているのだけどたまにしかやらなくて忘れるのでメモっておく。

ffmpeg
ffmpeg

この方法で検索するとだいたいファイルリスト作って読み込ませる方法を使っているようなのだけどファイルリストを作るのが面倒なのでできればパイプで済ませたいところ。ポイントは下記の通り。

  • ファイルリスト内のファイルはクォートされてなくてもよい
  • ファイルリスト内のファイルパスがフルパスまたは相対パスでない場合は -i FILE で指定したファイルのプレフィックスが使用される
  • ファイルリストの標準入力に - は使用できない

ファイルリスト内のファイルはクォートされてなくてもよいがファイル名による

ファイル名にスペースが入っている場合はシングルクォートで括る必要がある。ファイル名自体にシングルクォートを使われているとパースがおかしくなり、この場合はダブルクォオートで括ったとしても入力が -i /dev/stdin の場合は変な結合をされてしまう。

# OK
file  /path/to/file.mp4
file '/path/to/file.mp4'
file '/path/to/file name.mp4'

# NG: ファイル名にスペースが入っていてシングルクォートで括られていない
file /path/to/file name.mp4

# NG: ダブルクォートが使われている
file "/path/to/file.mp4"

# NG: ファイル名にシングルクォートが使われている
file /path/to/file's.mp4

# NG: ファイル名にシングルクォートが使われている 
file "/path/to/file's.mp4"

ファイルリスト内のファイルパスがフルパスまたは相対パスでない場合やダブルクォートが使われている場合は -i /path/to/file で指定したファイルのディレクトリ名が使用される

Terminal

ffmpeg -f concat -safe 0 -i /dev/stdin -c copy out.mp4 <<\!
file file.mp4
!

Result

Impossible to open '/dev/file.mp4'
/dev/stdin: No such file or directory

ファイルパスをダブルクォートで括った場合はそのままプレフィックスに結合される。

Terminal

ffmpeg -f concat -safe 0 -i /dev/stdin -c copy out.mp4 <<\!
file "/path/to/file.mp4"
!

Result

Impossible to open '/dev/"/path/to/file.mp4"'
/dev/stdin: No such file or directory

ファイルリストの標準入力に - は使用できない

Terminal

ffmpeg -f concat -safe 0 -i - -c copy out.mp4 <<\!
file /path/to/file.mp4
!

これは -i でファイルリストではなく動画ファイルを開こうとしてしまうため NG。

Result

Protocol 'pipe' not on whitelist 'crypto,data'!
Impossible to open 'pipe:0001.mp4'


公式ドキュメントのプロセス置換を使った方法のうち、find を使用したものは macOS では BSD find に -printf オプションが無いため使用できない。プロセス置換が使えないシェルに関しても同様にこれらの方法は使えない。

Terminal

ffmpeg -f concat -safe 0 -i <(for f in ./*.wav; do echo "file '$PWD/$f'"; done) -c copy output.wav
ffmpeg -f concat -safe 0 -i <(printf "file '$PWD/%s'\n" ./*.wav) -c copy output.wav
ffmpeg -f concat -safe 0 -i <(find . -name '*.wav' -printf "file '$PWD/%p'\n") -c copy output.wav


プロセス置換も使わない場合の単純な方法は下記の方法になる。ただしこれでもファイル名にシングルクォートが使われている場合は正しくファイルパスが結合されないのでファイル名を変えるなどした方がいいかもしれない。

Terminal

\ls $PWD/*.mp4 \
| sed 's/\(.*\)/"\1"/;s/^/file /' \
| ffmpeg -f concat -safe 0 -i /dev/stdin -c copy out.mp4


Homebrew 版の ffmpeg を使用している場合に Automator のクイックアクションに雑に登録するならこんな感じ。Finder のファイル選択はフルパスで飛んでくるので難しく考える必要は無いが、注意する点として Automator の Finder 入力では「ファイルのみ」という指定ができないためディレクトリが飛んできた場合は除外しなければならない。入力に「stdin」を選択して行頭に file を追加しただけではディレクトリが紛れ込む可能性があるので「引数として」にして 1 ファイルごとに判別した方が良い。

Automator: シェルスクリプトを実行

cd "$(dirname "${1}")"
for f
do
    test -f "${f}" || continue
    echo "file '${f}'"
done \
| /usr/local/bin/ffmpeg -f concat -safe 0 -i /dev/stdin -c copy -y $(date +%Y%m%d%H%M%S).mp4

Automator
Automator