FFmpegを使った動画ファイルの編集法

# はじめに
この記事は備忘録であり、FFmpegで簡単な動画ファイルの処理を実行するための手順書として書いています。

あくまで自分用ですが、下記の操作を実行したい人は参考にしてみてください。

なお、Windows11およびPowerShellのユーザーを想定しています。

## FFmpegとは
FFmpegとは、コマンドライン上で実行できる音声・動画処理ツールです。

インストール方法についてはいろいろな方が記事を上げていますが、私は次の記事を参考にさせていただきました。

https://qiita.com/Tadataka_Takahashi/items/9dcb0cf308db6f5dc31b

# 説明する動作

ここでは、以下のような操作について紹介します。

- 拡張子を変換する
- 解像度を引き下げる
- フレームを間引く
- 動画を特定の秒数で分割
- 複数の動画を結合

なお、どちらもGUIがついていませんので特定のシーンの切り抜きなどはほかの適切なアプリを使用してください。
そのような場合には私はVideoproc Vlogger(無料)を使用しています。

下記の手順書は動画ファイルそのものが破損している場合様々なエラーを引き起こします。破損ファイルの修復方法に関してはこの記事の主眼ではありません(私自身よく知りません)ので、割愛します。
# 拡張子を変換する
コマンドラインのカレントディレクトリに変換したいファイル(名前I.拡張子I)を置き、このファイルを拡張子Oに変換したいとします。
```PowerShell:
ffmpeg -i 名前I.拡張子I 名前O.拡張子O
```
とすると、カレントディレクトリに変換後のファイル(名前O.拡張子O)ができます。

複数ファイルを使いたい場合にはPowerShellのforeach構文を使うことができます。
```PowerShell:
foreach ($f in Get-ChildItem input/*.*) {ffmpeg -i $f.FullName output/"$($f.BaseName)_out.mp4"}
```
カレントディレクトリのサブディレクトリにinputフォルダを生成し、input内を変換したい動画ファイルだけにしてこのコマンドを実行すれば、outputディレクトリ内に変換後のファイルが生成されます。

# 解像度を引き下げる

ffmpegでは変換前のファイルと変換後のファイルの間にオプションを書くことができます。解像度を変更するオプションは、-vf scale=縦ピクセル:横ピクセルです。
ここで、縦横の比が元の動画と違うと引き延ばされたりつぶれたりした映像ができます。そのため、片方のサイズを指定してもう片方のサイズを比率を維持するように決めるには、もう片方のサイズ部分に数値の代わりに-1を指定します。たとえば、

```PowerShell:
ffmpeg -i 名前I.拡張子I -vf scale=-1:240 名前O.拡張子O
```

とすると、縦横を維持したまま横240ピクセルに変化します。複数ファイルの場合は上記と同様に、

```PowerShell:
foreach ($f in Get-ChildItem input/*.*) {ffmpeg -i $f.FullName scale=-1:240 output/"$($f.BaseName)_240.mp4"}
```

解像度を無理に大きくしても画質は変わりませんが、画質が悪いのに解像度だけ大きい画像を適切なサイズまで小さくすると処理がしやすくなるのでそういった場合に使います。なお、FFmpegには画質を引き上げるオプションもありますが、それはここでは紹介しません。

# フレームを間引く

FFmpegにはフレームレートのオプションが2か所存在します。

```PowerShell:
ffmpeg -r フレームレート1 -i 名前I.拡張子I -r フレームレート2 名前O.拡張子O
```

このうちフレームレート1は入力ファイルをどれくらいのフレームレートで読み込むかを指定するものです。
指定しなければ動画ファイル通りのレートで読み込みます。ここでは間引きはなく、レートが高いと動画は短く、早くなります。

レート2は出力ファイルのレートを指定します。これは動画の再生時間を維持したままレートを変更し、ここで間引きが行われます。なお、レート2がレート1より高い場合は無視されます。

なので、再生時間を維持したまま30fpsの動画を15fpsにしたい場合、
```PowerShell:
ffmpeg -i 名前I.拡張子I -r 15 名前O.拡張子O
```
とします。複数ファイルの場合は、次のようにすればよいでしょう。
```PowerShell:
foreach ($f in Get-ChildItem input/*.*) {ffmpeg -i $f.FullName -r 15 output/"$($f.BaseName)_15fps.mp4"}
```

# 動画を特定の秒数で分割
ffmpegでは出力形式をsegmentにすると特定の秒数ごとに分割することができます。

```PowerShell:
ffmpeg -i 名前I.拡張子I -f segment -flags +global_header -reset_timestamps 1  -segment_time 10 -c 名前O%02d.拡張子O
```
このコードは入力した動画を10秒ごとに分割するコードで、-reset_timestamps 1がないと再生時間がおかしくなったりします。また、%02dは2桁の連番という意味で、名前O00.拡張子O,名前O01.拡張子O,といったファイルが順番に生成されます。これも複数まとめて実行でき、

```PowerShell:
foreach ($f in Get-ChildItem input/*.*) {ffmpeg -i $f.FullName -f segment -flags +global_header -reset_timestamps 1 -segment_time 10 output/"$($f.BaseName)_10_%02d.mp4"}
```

となります。ただし、このままだと複数の動画の分割ファイルが混ざってしまいますので、フォルダ分けしておくと便利だと思います。その場合、

```PowerShell:
foreach ($f in Get-ChildItem input/*.*) {
mkdir output/"$($f.BaseName)"
ffmpeg -i $f.FullName -f segment -flags +global_header -reset_timestamps 1 -segment_time 10 output/"$($f.BaseName)"/"$($f.BaseName)_10_%02d.mp4"}
```
とすれば解決します。
# 複数の動画を結合

FFmpegにはファイルを結合するオプションもあります。
```PowerShell:
ffmpeg -f concat -i 結合したいテキストのリスト.txt -c copy 名前O.拡張子O
```
ここでは入力ファイルをテキストにします。入力ファイルとテキストは同じフォルダに入れ、テキストには

```
file '名前I1.拡張子I'
file '名前I2.拡張子I'
file '名前I3.拡張子I'
(略)
```

といった形式で結合したいファイル名を上から順番に並べます。
このテキストファイル自体もコマンドラインで作成することができます。
```PowerShell:
Get-ChildItem -Path input -Filter *.* -FIle | ForEach-Object { "file 'input/$($_.Name)'"}> input_list.txt
```
とすると、input内のすべてのファイルを読み込んでffmpegが読み込める形式に変換し、カレントフォルダにinput_list.txtとして出力されます。

順番を間違えるとめちゃくちゃになりますが、上の分割コマンドで出力したファイルは少なくとも順番通りにつながってくれます。

あとは、
```PowerShell:
ffmpeg -f concat -i input_list.txt -c copy output/名前O.拡張子O
```
とすればoutputフォルダに結合された動画ファイルが生成されます。

※なお、このとき'input/$($_.Name)'を'/input/$($_.Name)'とするとUNIX式の絶対パスと認識されうまくいかなくなります。

# 連番画像の生成

ffmpegでは連番の画像を生成することもできます。拡張子をpngにするだけでよく、細かいオプションの設定もいりません。
```PowerShell:
ffmpeg -i input/名前I.拡張子I output/名前O_%04d.png
```
複数の動画の場合も同様に、
```PowerShell:
foreach ($f in Get-ChildItem input/*.*) {mkdir output/"$($f.BaseName)"
ffmpeg -i $f.FullName output/"$($f.BaseName)"/"$($f.BaseName)_%04d.png"}
```

とすれば連番画像をフォルダ別に生成できます。

# 連番画像から動画を生成

次は、連番画像から動画を生成する方法です。

```PowerShell:
ffmpeg -r フレームレート -f image2 -i 名前I-%04d.拡張子I 名前O.拡張子O
```

image2はffmpegが画像を読み込むときの形式です。元の動画が存在する場合は、フレームレートをそろえましょう。なお、一括の場合は以下のように書きます。うまくコマンドラインがパスを認識してくれないので、Join-Pathを使って書くことにしました。

```PowerShell:
 foreach ($f in Get-ChildItem -Directory input/*) {
    $inputPattern = Join-Path $f.FullName "$($f.BaseName)_%04d.png"
    $outputFile = Join-Path "output" "$($f.BaseName).mp4"
    ffmpeg -r 24 -i $inputPattern $outputFile
}
```

# 動画から音声を抽出
次は、動画から音声を抽出する方法について説明します。オプション-vn(映像なし)と-c:a copy(オーディオをコピー)を指定して、

```PowerShell:
ffmpeg -i 名前I.拡張子I -vn -c:a 名前O.拡張子O
```

とするだけで抽出できます。このとき拡張子Oは音声ファイルの拡張子であり、aacが標準的です。
一括で実行するには、

```PowerShell:
foreach ($f in Get-ChildItem input/*.*) {ffmpeg -i $f.FullName output/"$($f.BaseName)_audio.aac"}
```

とすれば実行できます。


なお、この方法では形式の変換により音声の劣化が生じます。それを避けるためには、

```PowerShell:
ffmpeg -i 名前I.拡張子I -vn -c:a copy 名前O.拡張子O
```

とします。ただし、このとき拡張子Oは入力ファイルの音声の拡張子と一致していなくてはいけません。拡張子を参照するには、

```PowerShell:
ffmpeg -i 名前I.拡張子I
```
として出てきた情報のStream #0:1 Audio: などといった部分に書かれています。

# 動画と音声を合成
動画と音声を合成する方法について紹介します。動画にすでに音声があった場合勝手に置き換えます。

```PowerShell:
ffmpeg -i 名前I.拡張子I -i 名前A.拡張子A -c:v copy -c:a copy -shortest 名前O.拡張子O
```

-shortestは動画と音声の尺が違う場合に尺が短いほうに時間をそろえるという意味のオプションです。一括の場合も次のように動作します。

```PowerShell:
foreach ($f in Get-ChildItem input/*.*) {ffmpeg -i $f.FullName -i audio/"$($f.BaseName)_audio.aac" -c:v copy -c:a copy output/"$($f.BaseName).mp4"}
```

ただし、inputディレクトリに名前I.拡張子Iがあるとき、audioディレクトリに名前I_audio.拡張子Aがある、ということを前提にしています。