VapourSynth で MP4 エンコード

はじめに
ここでは、VapourSynth などを使って、MP4 (H.264 + AAC) の動画を作成する手順を説明していきます。

VapourSynth でソースから動画を読み込み、シーンカットとリサイズを行って H.264 で映像をエンコードした後、音声ファイル単独で映像の位置に合わせて範囲をカットし、AAC でエンコードします。
その後、映像と音声を結合して、MP4 ファイルを作成します。
映像エンコード用のスクリプトを用意
まず、映像エンコード用のスクリプトを用意します。
以下のスクリプトを参考にして、作成してください。

<enc.vpy>
#!/usr/bin/python3

import vapoursynth as vs

core = vs.core

# プラグイン読み込み
#core.std.LoadPlugin('/usr/local/lib/vapoursynth/libsavfsource.so')

# ffms2 でソースを読み込み
clip = core.ffms2.Source(source='src.mp4')
# 編集用にフォーマットを YUV444 に変換したい場合
#clip = core.ffms2.Source(source='src.mp4',format=vs.YUV444P8)

# savfsource でソースを読み込み (WebM:VP9 の場合など)
#clip = core.savf.Source('src.webm')
# フォーマットを YUV444 に変換したい場合 (savf.Source でフォーマット変換はできない)
#clip = core.resize.Point(clip,format=vs.YUV444P8)

# フレームのカット
clip = clip[0:100] + clip[200:300] + clip[500:]

# 画像の左右8pxを切り取り
# [!] YUV420 の状態だと、奇数位置からの切り取りや、奇数サイズでの切り取りはできない
#clip = core.std.Crop(clip,left=8,right=8)

# 画像のリサイズ
clip = core.resize.Spline36(clip,width=1280,height=720,format=vs.YUV420P8)

# 映像を出力
clip.set_output()
シーンカット
映像のシーンカットを行いたい場合は、まず、VapourSynth ビューアなどを使って、カットするフレームの位置を確認します。
プレビュー用のスクリプトを用意
まずは、映像を読み込むだけの、プレビュー用スクリプトファイルを用意します。

<prev.vpy>
#!/usr/bin/python3

import vapoursynth as vs
core = vs.core
c = core.ffms2.Source(source='src.mp4')
c.set_output()
フレーム位置を確認
ビューア上でスクリプトを読み込み、表示される画像を確認しながら、カットするフレーム位置を見つけてください。
映像のフレーム位置は、0が先頭になります。

スクリプトに書く際には、カットする部分の範囲ではなく、映像として残す部分の範囲を指定するため、「先頭のフレーム位置」と「終端のフレーム位置 + 1」の数値を確認します。

位置が確認できたら、エンコード用のスクリプトファイルに記述していきます。

# 100〜199 + 300〜500
clip = clip[100:200] + clip[300:501]

なお、一度エンコードした後に、映像もしくは音声を再エンコードすることになった場合、フレーム位置をまた一から確認するのは面倒なので、カット部分のコードを別のテキストに保存しておくと、再エンコードする時に楽になります。
映像のエンコード
vspipe でエンコードスクリプトを読み込んで、標準出力に y4m 形式 (無圧縮) で出力し、パイプで繋げた x264 コマンドを使って、H.264 でエンコードします。

Arch Linux/Ubuntu のパッケージ名は、x264 です。

$ vspipe -c y4m enc.vpy - | x264 --demuxer y4m --sar 1:1 --crf 23 - -o out.264
x264 オプション (一部抜粋)
--demuxer y4m入力のフォーマットを y4m に指定
--profile STRH.264 プロファイル。
再生機器によっては対応プロファイルが制限されているため、PC 以外で再生したいや、8bit 以上のビット数にしたい場合は適切なものを選んでください。

main : 携帯機器など用
high : PC 用
high10 : 8〜10bit をサポート
high422 : high10 + YUV422 サポート
high444 : high10 + YUV444 サポート
--fps N映像のフレームレートを指定します。
整数または分数表記で指定できます。
入力の y4m にフレームレートの情報があるので、指定しなくても構いません。
-I Nキーフレーム間隔の最大サイズ。
fps * 10 の値が妥当。
--sar N:N再生時のピクセルのアスペクト比。
通常は 1:1 です。
--crf FLOAT品質ベースの VBR。
0.0〜51.0 の数値で品質を指定する。値が小さいほど高画質になる。

20 あたりで、ほぼ劣化は気にならない。
23 (デフォルト) で、画質とサイズのバランスが良い。動きのある部分は少し劣化が気になる。
--bframes NBフレーム (差分) の最大連続数。
値を大きくすると容量を下げることができるが、再生時とエンコード時の負荷が増える。
デフォルトの3にしておいた方が、多くの機器で再生できるかも。
--ref N参照するフレームの距離の最大数。
値を大きくすると圧縮率と画質が上がるが、再生とエンコードの負荷が増える。
--me STR動き探索モード。
hex でデフォルト。
umh はそれより一段階負荷が重く、高品質。
音声を WAV に変換
音声は、映像とは別にエンコードしていきます。

ソースの音声が AAC などでエンコードされている場合は、まず動画から音声を抽出した後、無圧縮の WAV に変換します。

※動画から直接音声を WAV 変換することも出来ますが、動画に設定されている音声の遅延分がスキップされてデコードされてしまうので、エンコーダディレイは手動で除去します。
※動画内に音声が複数格納されている場合、動画の情報を確認した上で、必要なトラックだけを抽出してください。
※ソースが 5.1ch などの場合、必要であれば 2ch に変換してください。

詳しくは、音声のエンコーダディレイ - デコード を参考にしてください。
ffmpeg を使って、音声を抽出
## 最初の音声を抽出 (MP4->AAC)
$ ffmpeg -i src.mp4 -c:a copy asrc.aac

## 2番目の音声を抽出
$ ffmpeg -i src.mp4 -map 0:a:1 -c:a copy asrc.aac

音声が複数あって、2番目以降の音声を指定したい場合は、
"-map [入力ファイルの番号(通常 0)]:a:[何番目の音声か(0〜)]" で指定します。
ffmpeg を使って、抽出した音声を WAV 変換
## AAC->WAV
$ ffmpeg -i asrc.aac src.wav

## 5.1chなど -> 2ch
$ ffmpeg -i asrc.aac -ac 2 src.wav

この状態では、WAV にエンコーダディレイの無音部分が含まれています。
音声の編集を行う
音量の変更などを行いたい場合は、出力した WAV ファイルを編集してください。
音声を AAC でエンコード
WAV ファイルを元に、音声を AAC でエンコードします。

ここでは、fdkaac コマンドを使います。
Arch Linux/Ubuntu でのパッケージ名は、fdkacc です。
vasynccut
https://gitlab.com/azelpg/vasynccut

音声ファイル単独で、映像のフレーム位置に合わせてカットを行うソフトを自作しました。
これを使って音声をカットし、出力した WAV をエンコードします。

オーディオのサンプル位置は、「映像のフレーム位置 * 音声のサンプルレート(Hz) / fps」で計算できるので、VapourSynth のスクリプトで音声を処理しなくても、音声ファイルだけで、映像に合わせたカットが行えます。

映像は映像、音声は音声で別々に処理した方が効率が良いので、ここでは vasynccut を使います。
カットとエンコード
以下のコマンドで、WAV の音声を映像のフレーム単位でカットし、出力した WAV を fdkaac でエンコードします。

$ vasynccut -i src.wav -f 24000/1001 0:100 200:300 | fdkaac -b 128 -o out.m4a -

"0:100 200:300" は、フレーム位置の指定です。
映像エンコード用のスクリプトで記述した、シーンカットの clip[A:B] の [] 内の文字列をそのまま指定します。
※終端位置は省略できません。

フレーム位置を複数指定した場合は、先頭から順番に結合されます。

clip = clip[0:100] + clip[200:300] => "0:100 200:300" で指定

vasynccut オプション
-f <N or NUM/DEN>映像のフレームレートの指定。
23.976 fps なら、24000/1001。
29.97 fps なら、30000/1001。
-s <INT>先頭の指定サンプル数を除外する。
入力の音声にエンコーダディレイがある場合に使います。
-v <FLOAT>音量を変更する場合、デシベル単位で指定。

エンコーダディレイ
ソースの音声が AAC などの場合、WAV に変換した時に、先頭にエンコーダディレイの余分な無音部分が含まれています。
※Opus の場合は、デコード時に余分な部分は自動で除外されるため、問題ありません。

エンコーダディレイについての詳細は、音声のエンコーダディレイ - 基本 をご覧ください。

例えば、fdkaac でエンコードされた LC-AAC をデコードした場合、デコード後の音声データの先頭に 2048 サンプル分の余分な無音部分があるので、それをスキップする必要があります。

vasynccut では、エンコーダディレイを除外するために、開始位置のサンプル数を指定できるので、ソースの音声が fdkaac でエンコードされた LC-AAC であれば、"-s 2048" で、先頭の 2048 サンプルをスキップします。

$ vasynccut -i src.wav -f 24000/1001 -s 2048 0:100 | fdkaac ...
fdkaac コマンド
-o <FILE>出力ファイル名
-p <N>プロファイル。
2 : LC-AAC
5 : HE-AAC
29 : HE-AAC v2
-b <N>CBR ビットレート (kbps)

音質重視なら、LC-AAC : 128 kbps〜。
圧縮重視なら、HE-AAC : 48〜80 kbps。
超低ビットレートなら、HE-AAC v2 : 32 kbps 以下。
映像と音声の結合
エンコードした映像と音声を、最終的に MP4 に結合するために、MP4Box コマンドを使います。

Arch Linux の場合、MP4Box は gpac パッケージ内にあります。
結合
x264 でエンコードした映像は out.264、AAC でエンコードした音声は out.m4a なので、この2つを MP4 コンテナに格納します。

$ MP4Box -add out.264 -add out.m4a:delay=-2048/44100 -new res.mp4

-add で、コンテナに追加するトラックのファイルを指定します。
ファイル名の後に ":" を付けると、そのトラックにオプションを指定することができます。

-new は、常に出力ファイルを新規作成の状態にします。既存のファイルがある場合、このオプションがないと、トラックが追加されます。

AAC にはエンコーダディレイがあるので、先頭の余分な無音部分の遅延情報を delay オプションで指定します。
これを設定しないと、エンコーダディレイのサンプル数分、音声が少し遅れて再生されます。
(LC-AAC 44100 Hz であれば、0.046 秒ほどなので、視聴する分にはそこまで差は感じませんが)
delay
詳しくは、音声のエンコーダディレイ - 遅延のセット をご覧ください。

「delay=-<エンコーダディレイのサンプル数>/<サンプルレート>」で、分数表記による秒単位で指定します。

音声の開始位置を早めたいので、ここでは負の値にします。
整数で指定した場合は、ミリセカンド単位になります。

fdkaac でエンコードした AAC の場合、エンコーダディレイのサンプル数は、LC-AAC なら 2048、HE-AAC なら 5058、HE-AAC v2 なら 7106 になります。

Opus の場合は自動で設定されるので、指定する必要はありません。
音声を再エンコードする
音声だけ再エンコードしたい場合は、vasynccut などを使って音声を再エンコードした後、MP4Box で結合する時に、作成済みの mp4 ファイルから映像を読み込むことができます。

$ MP4Box -add res.mp4#video -add out.m4a:delay=-2048/44100 -new res2.mp4

res.mp4 からエンコード済みの映像を取得したい場合、"-add res.mp4#video" として、ファイル名の後に #video を付けると、res.mp4 の最初の映像トラックを入力に指定できます。

トラックIDで指定したい場合は、"-add res.mp4#1" というように、番号の数字 (1〜) を指定します。
遅延の再設定
これは、音声の遅延を再設定したい場合などでも使えます。

$ MP4Box -add res.mp4#video -add res.mp4#audio:delay=-2048/44100 -new res2.mp4

res.mp4 の最初の映像と音声を読み込んで、新しい動画を作成することができます。

なお、delay を指定しなかった場合、元の mp4 ファイルの遅延が維持されます。