FFMPEGのfilter_complexの記述方法に関するメモ

# はじめに
ffmpegのfilter_complexの記述方法に関するメモを備忘録として記す。
以前からffmpegを動画ファイルの取得・変換等に利用してきたが、手の込んだ加工をしようとするとfilter_complexオプションに色々書く必要がある。これまでは深く考えずにネット情報を探してはコピペでしのいできたが、やりたいことが全てネット上にある訳もなく、独自に細かい調整をしたい場合には流石に限界が見えてきた。そこで真面目におまじない(記述内容)の内容についてある程度理解することにした。例えばこんなコマンドをサンプルに取り上げる。
```
ffmpeg -i movie.mp4 -filter_complex "[0]crop=w=iw/2:h=ih/2:x=iw/4:y=ih/4[v]" -map "[v]" -vcodec libx264 -b:v 2000k -y crop_movie.mp4
```
(movie.mp4の画角の中央を横と縦を元の半分の長さで切り出してcrop_movie.mp4に出力する)
ここでの話題は、-filter_complexに続く""で括られた中の部分(おまじない？)の記述方法に関することになる。


# テスト環境
* macOS Ventura 13.4.1 (Apple M1)
* 使ったffmpegのバージョン(homebrewでインストールしたもの)
```
ffmpeg version 6.0 Copyright (c) 2000-2023 the FFmpeg developers
built with Apple clang version 14.0.3 (clang-1403.0.22.14.1)
configuration: --prefix=/opt/homebrew/Cellar/ffmpeg/6.0 --enable-shared --enable-pthreads --enable-version3 --cc=clang --host-cflags= --host-ldflags= --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libaribb24 --enable-libbluray --enable-libdav1d --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librist --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libsvtav1 --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox --enable-neon
```
* サンプル動画
今回は[こちら](https://www.home-movie.biz/free_movie.html "HYBRID CREATIVE MOVIE サクラ")のフリー動画を利用させて頂いた

# filter_complexの記述手順について
## 入力・処理・出力の流れを記述する
上に示したコマンドサンプルを例におまじないの意味を書いてみる。
```
ffmpeg -i movie.mp4 -filter_complex "[0]crop=w=iw/2:h=ih/2:x=iw/4:y=ih/4[v]" -map "[v]" -vcodec libx264 -b:v 2000k -y crop_movie.mp4
```
* [0]が処理入力のラベル、crop=w=iw/2:h=ih/2:x=iw/4:y=ih/4が処理(fliter)内容、[v]が処理結果(出力)を意味している。
* ffmpegのルールで、[0]は最初の入力ファイルを意味している。入力ファイルを複数与えると、先頭から順番に[0],[1],[2],..と指定することができる。
* 次のcropは画角の切り出し処理のことで、=に続けて処理パラメータを:で区切って記述する。
* w=切り出し幅(pixel数)、h=切り出し高さ(pixel数)、x=切り出し水平位置(pixel)、y=切り出し垂直位置(pixel)を其々意味している。各数値には数式を与えることも可能である(w=1920/2+480のような形式)。
* 切り出し位置の座標の原点(0,0)は画面の左上角である。なお、ihは入力画像の横幅、iwは入力画像の高さを意味している予約語である。
* 最後の[v]は処理結果のラベルであり、任意の英数字を使用できる。文字数も1文字を超えても構わない(上限は確認していない)。filter_complexの記述は""で括る必要がある。
* 基本的には、-filter_complex "(入力ラベル)(処理名＋パラメータ)(出力ラベル)"の形式で記述する
* -mapオプションで"[v]"を指定することで、処理結果を外部に出力できる。この例では、上限2Mbpsのh264形式でcrop_movie.mp4としてファイルに出力している。また、-vcodecでlibx264(ソフトエンコーダー)を指定している

## 前の処理の出力を次の処理につなぐ
上のコマンドを元にして次の処理を追加した例を示す。1行が長いので、改行を入れてバックスラッシュでつなぐ。
```
ffmpeg -i movie.mp4 -filter_complex "\
[0]crop=w=iw/2:h=ih/2:x=iw/4:y=ih/4[v];\
[v]scale=iw*2:ih*2[v2]" \
-map "[v2]" -vcodec libx264 -b:v 2000k -y crop_movie.mp4
```
* 最初の処理:cropで縦横半分の画角を切り出す([v]を処理結果(出力)とする)
* 次の処理:scaleで縦横の長さを其々2倍に拡大する([v]を入力、[v2]を処理結果とする)
⇢結果として、入力ファイル動画の中央部分を拡大した映像が得られることになる。
* scaleは入力画角を拡大・縮小する処理であり、単純に変換後の画素数を与えている。iw*2で入力画像の水平画素数が2倍に、ih*2で入力画像の垂直画素数が2倍になる。
* scaleの処理結果に[v2]というラベルを貼り、次のオブション(-map "[v2]")にて、処理結果をファイル出力するようにしている。
* 処理を連ねる場合、処理と処理の区切りは;を使う(-filter_complex "(入力ラベル)(処理名＋パラメータ)(出力ラベル1);(出力ラベル1)(処理名＋パラメータ)(出力ラベル2)"の形式)
* ここでの処理結果(1920x1080):
![スクリーンショット 2023-06-26 0.54.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/918174/9c4d7273-13c4-7905-2b7c-228134b76db8.png)

## 複数の処理結果を同時に出力する
上のコマンドを元にして更に複数の処理を追加した例を示す。
```
ffmpeg -i movie.mp4 -filter_complex "\
[0]split=2[00][01];\
[00]crop=w=iw/2:h=ih/2:x=iw/4:y=ih/4[v1];\
[01]scale=iw/2:ih/2[v2]" \
-map "[v1]" -vcodec libx264 -b:v 2000k -y crop_movie.mp4 \
-map "[v2]" -vcodec libx264 -b:v 2000k -y small_movie.mp4
```
* 最初の処理:splitで入力[0]を[00]と[01]の2つに分岐する。1つのラベルは1度しか参照できないため、split処理で分岐して複数処理で参照できるようにする
* 2分岐の場合、[0]split[00][01]と書くことが標準らしい。4分岐の場合、[0]split=4[00][01][02][03]のように分岐数を明記して書く
* 2番目の処理:先のcropと同じ処理だが、入力信号は分岐処理で作ったラベル[00]([0]と同じもの)、出力ラベルは[v1]としている
* 3番目の処理:scaleで入力を縦横半分に縮小する処理を行う。入力信号は分岐処理で作ったラベル[01]([0]と同じもの)、出力ラベルは[v2]としている
* 出力処理1:-map "[v1]"で指定した処理結果をcrop_movie.mp4でファイル出力する
* 出力処理2:-map "[v2]"で指定した処理結果をsmall_movie.mp4でファイル出力する
* 各-mapオプションごとに出力ファイルの設定(-vencoder,-b:v等)を与えること
* 今回の例をハードウェアエンコーダ(h264_nvenc,h264_videotoolbox等)を使って出力ファイルを生成する場合には、ハードウェアの制限によって同時出力の個数に制限が出る場合があるため、注意・確認が必要。

## 更に余白を追加してフルHDサイズに変更し、更にキャプションも追加
上のコマンドを元にして更に処理を追加した例を示す。説明は上記との差分の所だけ記す。
```
ffmpeg -i movie.mp4 -filter_complex "\
[0]split=2[00][01];\
[00]crop=w=iw/2:h=ih/2:x=iw/4:y=ih/4[v1];\
[v1]pad=w=1920:h=1080:x=480:y=270:color=darkgray[v11];\
[v11]drawtext=fontsize=40:fontfile=VL-PGothic-Regular.ttf:fontcolor=blue:\
text='サンプル10':x=0:y=0,
drawtext=fontsize=50:fontfile=VL-PGothic-Regular.ttf:fontcolor=green:\
text='サンプル11':x=w-text_w:y=h-text_h[v11c];\
[01]scale=iw/2:ih/2[v2];\
[v2]pad=w=1920:h=1080:x=480:y=270:color=black[v22];\
[v22]drawtext=fontsize=60:fontfile=VL-PGothic-Regular.ttf:fontcolor=red:\
text='サンプル20':x=0:y=0,\
drawtext=fontsize=70:fontfile=VL-PGothic-Regular.ttf:fontcolor=cyan:\
text='サンプル21':x=w-text_w:y=h-text_h[v22c]" \
-map "[v11c]" -vcodec libx264 -b:v 2000k -y crop_movie.mp4 \
-map "[v22c]" -vcodec libx264 -b:v 2000k -y small_movie.mp4
```
* crop処理の出力[v1]に余白追加処理(pad)を施す
* padのwには余白追加後の幅、hには余白追加後の高さ、xには入力画像の左角の水平位置、yには入力画像の左角の垂直位置、colorには余白の色、を其々設定。余白追加後の画面サイズと余白内のどこに画像を配置するかの指定になる。
* padの出力ラベルを[v11]と設定
* [v11]に対してテキスト重畳(drawtext)処理を行う。fontsizeには文字の大きさ、fontcolorには文字の色を設定
* fontfileは作業フォルダにVL-PGothic-Regular.ttfがあることを想定。フォントが対応していれば日本語も表示可能
* textには表示したい文字列を''で括って記述し、xにはフォントの水平位置、yにはフォントの垂直位置を設定。フォントの左上角が基準位置
* darwtextでの予約語：w:入力画像の横幅、h:入力画像の高さ、text_w:表示文字列の幅、text_h:表示文字列の高さ
* 複数の文字列を配置したい場合は、,で区切ってdrawtextの一連の記述を列挙する。この例では2箇所に文字列を配置している
* drawtext処理結果のラベルは[v11c]と設定
* scale処理の出力[v2]に余白追加処理(pad)を施す
* padの出力ラベルを[v22]と設定し、[v22]に対してdrawtext処理を施し、処理結果のラベルは[v22c]と設定
* [v11c]と[v22c]を其々-mapで指定し、其々に対してファイル出力設定を行い、出力ファイルを生成する

# 最終的に出来上がった2つの動画ファイルのスクリーンショット
## 入力画像
movie.mp4(1920x1080):
![スクリーンショット 2023-06-26 0.43.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/918174/90e1d16b-127e-c691-d9c1-d0458e7fbaf3.png)
## 処理画像1
入力画像の中央部を切り抜いて余白を付けたもの crop_movie.mp4(1920x1080):
![スクリーンショット 2023-06-26 0.21.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/918174/fc5657ec-f823-f38c-c155-9e30061ebd27.png)
## 処理画像2
入力画像をそのまま縦横1/2に縮小して余白を付けたもの small_movie.mp4(1920x1080):
![スクリーンショット 2023-06-26 0.21.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/918174/6d8a070e-e237-6173-e4ea-3600deefc2b9.png)

# 最後に
ffmpegの-filter_complexの記述について、基本的な部分はコピペに頼らずに記述できるようになったと感じている。個人的な感想として、

* 処理結果に[]でラベルを定義して次の処理に渡すことで連続的に処理を重ねていくことができること
* ラベルの参照は1度しかできないので、同じデータを複数の処理で使う場合は、splitで必要数を分岐すること

等が記述の際のポイントと考えている。但し、記述内容にコメントを入れることもできないため、記述内容が複雑になってくるとエラー時のデバッグに非常に難儀する。エラーメッセージも原因の特定がやりやすいものとは言えないところがある。場合によっては中間ファイルを生成しつつffmpegの処理を重ねることも検討した方がよいかも知れないが、その際は符号化・復号化が繰り返されることによる画質劣化に注意が必要になると思う。
なお、-filter_complexで使える処理(filter)は非常に沢山あるため、ffmpegのドキュメントで適宜確認し、希望の処理ができるかどうか調べるとよいと思う。

# 特に参考にさせて頂いたサイト
[ニコラボ](https://nico-lab.net "ニコラボ")