技法集と豆知識

本稿では FFmpeg を利用した手筋を集める。各トピックを論理的な塊に集約するほどの理解がないので、実現の簡単なものから難しいものに並べる。なお、このページに目を通す前に、参考文献 にある記事集を当たること。技術も記述も正確性において高い。

解法がフィルター単品の適用による手筋については、公式文書の当該フィルターの記載をも併せて確認すること: FFmpeg Filters Documentation

逆再生

映像と音声を同時に逆転させることも、一方だけを逆転させることも可能だ。

bash$ ffmpeg -i input.mp4 -vf reverse output.mp4
bash$ ffmpeg -i input.mp4 -vf reverse -af areverse output.mp4

この処理は入力全体をメモリーに格納することに注意を要する。巨大なビデオに対しては、何分割かしてからそれぞれを個別に逆転させて結合する(上述)ことを検討する。

回転および反転

携帯電話で撮影した映像を扱うのであれば、ちょうど 90 度だけ回転させたり反転させたりする手筋を覚えておくと何かのときに助かる。

反転

映像フィルター hfilter, vfilter を用いる。水平軸または垂直軸に関する反転を実現する。オプションがないので紛れがない。

bash$ ffmpeg -i input.avi -vf "hflip" -c:a copy output.avi
bash$ ffmpeg -i input.avi -vf "vflip" -c:a copy output.avi

回転

映像フィルター transpose を用いる。コマンドの基本形は次のとおり:

bash$ ffmpeg -i input.mp4 -vf "transpose=dir=1" -c:a copy output.mp4

引数 dir の値は数字かキーワードで指定できる。都合の良いほうを使っていい:

番号

名前

変換内容

0

cclock_flip

+90 度回転してミラー

1

clock

-90 度回転

2

clock

+90 度回転

3

clock_flip

-90 度回転してミラー

  • 有効な値には 4 .. 7 もあるが、これは非推奨だ。代わりに後述の引数を指示する。

  • 180 度回転は transpose を合成すれば実現できる。

縦長・横長を ffmpeg 判定させて必要な場合に限り回転させるというコマンドもあり得る。引数 passthrough=landscape 等を指定する。「横長ならば横長のままとする」の指示を意味する:

bash$ ffmpeg -i input.mp4 -vf "transpose=dir=2:passthrough=landscape" -c:a copy output.mp4

ビデオを Twitter に投稿可能な状態にエンコードし直す

携帯電話で撮影した MP4 ファイルに対してならば、上記リンク先スレッドの foone commented on May 18, 2018 コメントのコマンドを加工して実行するといい。状況に応じてオプションを加えたり除いたりすることだ。

静止画像抽出

画像ファイル群からスライドショウを作成する

紙幅がないのでコツを箇条書きにして済ませる:

  • 単純な成果で良ければフィルターを用いることはなく実現できる。入力オプション -framerate DURATION くらいしか本質的には与えない。

  • ページごとに表示時間を変えたいなどの場合には、後述するビデオ結合の手法を選ぶ。

    • file 行の次に duration 行を明記する。

    • 末端付近で file エントリーを重複させるのがコツとなる。二度目では duration を指定しない。

    • 出力オプション -vsync vfr を指定することがある。これは、同じタイムスタンプを持つフレームが二つと存在しないように、タイムスタンプのまま通過させるか、一つを除いて捨てる。

一枚絵のビデオを作成する

画像ファイル input.jpg10 秒間表示するだけのビデオを作成したいとする。それには次のようなコマンドを実行する:

bash$ ffmpeg -loop 1 -i input.jpg -c:v libx264 -t 10 output.mp4

次のコマンドは再生時間を音楽に合わせて input.mp3 を BGM とする MP4 ビデオを出力する:

bash$ ffmpeg -loop 1 -i input.jpg -i input.mp3 -c:v libx264 -c:a copy -shortest output.mp4

ビデオを結合する

同一形式のものを結合する

いちばん単純な場合は画面寸法、ピクセルフォーマット、codec などが同じである MP4 ファイル二つを連結するものだ。携帯電話で撮影して保存した MP4 ファイルに対して適用可能。結合処理は二段階からなる:

  1. 連結したいファイルの名前とパスが記載されたテキストファイルを用意する

  2. このテキストファイルを ffmpeg コマンドに与える

テキストファイルの内容は次のようなものだ:

# fileList.txt
file '/path/to/input0.mp4'
file '/path/to/input1.mp4'

コマンドラインはこうなる:

bash$ ffmpeg -f concat -safe 0 -i fileList.txt -c copy output.mp4
  • -f concat: demuxer を concat とする。

  • -safe 0: ファイルパスに対するチェックを大甘にする。

一般の条件で結合する

結合コマンドを実行する前に、対象ビデオファイル群を同一形式に再エンコードする必要がある。

  • 結合前のファイルに対して再エンコードする。結合する前に品質を正確に制御できる。

  • 映像フィルターのほうの concat を用いる。

後者の例は次のようなものだ。ここでは与えないが、出力オプションで encoder を指定する余地がある:

bash$ ffmpeg -i input0.mp4 -i input1.mp4 -i input2.mp4 \
  -filter_complex "[0:v][0:a][1:v][1:a][2:v][2:a]
    concat=n=3:v=1:a=1[vv][aa]" \
  -map "[vv]" -map "[aa]" output.mp4

また、紙幅の都合上ここには記さぬが、ビデオファイルを TS フォーマットに変換すると UNIX/Linux コマンドの cat で直接連結が可能になる。詳しくは上述の文献を参照。

再生区間を抽出する

時間帯を指定して元ビデオから再生時間がより短いビデオを得たい。

時間指定用オプションを以下にまとめる。まずは一部を捨てるコマンドから:

指定方式

コマンド

外で計算

開始から指定時間だけ捨てる

-ss DURATION -i INPUT ... OUTPUT

NO

開始から指定時刻まで捨てる

-ss POSITION -i INPUT ... OUTPUT

NO

終了までの指定時間だけ捨てる

-i INPUT -t DURATION ... OUTPUT

YES

指定時刻から終了まで捨てる

-i INPUT -to POSITION ... OUTPUT

YES

終了時間付近のカットは時刻なり時間なりをあらかじめ計算しておかねばならない。

一部を残すコマンドについて述べる。 -sseof POSITION -i INPUT を用いると、時刻を終端基準とし、かつ時間軸が逆向きになる。したがって引数は負の数を指定する必要がある。

指定方式

コマンド

外で計算

開始から指定時間だけ残す

-i INPUT -t DURATION ... OUTPUT

NO

開始から指定時刻まで残す

-i INPUT -to POSITION ... OUTPUT

NO

終了までの指定時間だけ残す

-sseof -DURATION -i INPUT ... OUTPUT

NO

指定時刻から終了まで残す

-ss POSITION -i INPUT ... OUTPUT

NO

内側を残す方法は上記をどうにか組み合わせる。

  • -t DURATION (input/output)

    • (input) 入力ファイルから読み込まれるデータの継続時間

    • (output) 出力が DURATION に達した後、書き込みを停止する。

  • -to POSITION (input/output)

    • 出力の書き込みまたは入力の読み取りを POSITION で停止する。

オプション -to-t は同時に機能しない。両方指定すると -t が優先される。

  • -ss position (input/output)

    • (input) この入力ファイルの位置まで seek する。厳密には POSITION にセットされないことが普通だ。

    • (output) タイムスタンプが POSITION に達するまで、入力を復号しつつも捨てる。

  • -sseof position (input)

    • -ss の EOF 基準バージョン。0 は EOF を指し、負の値はより BOF に近づく。

最後に、再エンコードをするかしないかで結合処理の性質が異なることを考慮することだ。再エンコードせずに済むならば、変質せずに高速に処理される。

ビデオの画面を伸縮する

伸縮操作の基本は映像フィルター scale を用いるものだ。次のコマンド呼び出しは省略部分が同一ならばすべてが同値だ:

bash$ ffmpeg -i input.mp4 -vf scale=w=${width}:h=${height} ... output.mp4
bash$ ffmpeg -i input.mp4 -vf scale=${width}:${height} ... output.mp4
bash$ ffmpeg -i input.mp4 -vf scale=${width}x${height} ... output.mp4

品質が劣化するのが気になる場合は出力オプション部に encoding 指定をする。例えば libx264 の低速プリセットで crf=18 を使用するなど:

bash$ ffmpeg -i input.mp4 -vf scale=${width}:${height} -preset slow -crf 18 output.mp4

入力画面の幅と高さをそれぞれ iwih で参照できる。

  • 例:画面幅を二倍に拡大する scale=iw*2:ih. この * はシェルに展開されない。

  • 例:寸法を半分にする scale=iw/2:ih/2. こちらは引用符で囲むこと。

アスペクト比を維持して伸縮する

アスペクト比を保ったまま動画を拡大縮小したい場合、 height か``width`` のどちらかの引数を値で設定し、もう一方の引数の値を負の値に設定するといい。

映像形式によっては画面寸法が偶数であることを要求する。そのときは -1 の代わりに -2 を指定する:

bash$ ffmpeg -i input.mp4 -vf scale=320:-2 output.mp4

関数 min()iw, ih を組み合わせれば最小の幅と高さを決められる。単純な方法で質の悪い伸縮を防げる手筋だ:

bash$ ffmpeg -i input.mp4 -vf "scale='min(320,iw)':'min(240,ih)'" output.mp4

テキスト

字幕という手もあるが、ここではフィルター drawtext を用いる方法を述べる。

文字を打ち込む作業は何度も何度も画面を見直すから ffplay で確認するといい:

bash$ ffplay -vf "drawtext=text='なんらかのテキスト':
    fontfile=/path/to/fontfile:
    box=0:boxcolor=white@0.5:
    x=20:y=20:
    shadowx=1:shadowy=1:shadowcolor=deeppink@0.9:
    fontcolor=deeppink:fontsize=48:line_spacing=8" -autoexit -y 480 input.mp4

利用者ノート

問題はオプション fontfile の指定だ。これは Windows のフォントを指定しても OK ではある。しかし、できれば WSL 側で適宜設定して単純なパスで指定するか、あるいは代わりにオプション font でフォント名だけを指定すれば十分であるように持っていきたい。

あと、TrueType フォントを指定すると描画が乱れる現象が起こっている。現状、拡張子 .ttc のものしか描けない。

複数配置

フィルター hstack, vstack, xstack がその目的にはふさわしい。

フィルター hstack, vstack を組み合わることで 2x2 レイアウトを実現することもできるが、効率がより良いフィルター xstack があるのでそれを利用したい。例を示す。簡単のために、入力映像の画面寸法はすべて同じであると仮定する:

bash$ ffmpeg \
    -i input0.mp4 -i input1.mp4 \
    -i input2.mp4 -i input3.mp4 \
    -filter_complex "xstack=inputs=4:layout=0_0|0_h0|w0_0|w0_h0:shortest=1"
    output.mp4

これは次のようなレイアウトになる:

input0 input2
input1 input3

引数 layout の値は縦棒区切りの謎の記号だが、これで出力における各映像入力の位置を指示する。POSX_POSY のような形式で座標を指定している。数字は序数で w0h0 はそれぞれ入力映像 0 の幅と高さを表す。

同系統のフィルターと同様に、入力映像すべてが同一のピクセルフォーマットでなければならない。

グリッドの個数は 2 以上でも可能だし、極端に言えばグリッド状でなくてもいい。同一の映像入力を用いてもよい。演習として、ビートマニアの V のクリップのようなものを構成してみるといい。

ぼかし

映像の空間的に、または時間的に一部をぼかす方法を記す。

考え方を述べる。オリジナルの映像の一部を矩形に「クリップボード」にコピーし、ぼかしフィルターで加工する。加工した映像矩形を元映像の同じ位置に「貼り付ける」というのが基本的な考え方だ。コマンドラインも比較的単純な構造になる。オプション -filter_complex の引数だけを抜粋したものを示す:

bash$ ffmpeg -i input.mp4 \
  -filter_complex "
    [0:v]crop=400:400:300:350,boxblur=10[fg];
    [0:v][fg]overlay=300:350[v]" \
  -map "[v]" output.mp4

模式化しておく:

flowchart TB input --> 0(0:v) 0 --> crop\n400:400:300:350 --> boxblur\n10 --> fg(fg); 0 --> overlay[overlay\n300:350] fg --> overlay overlay --> output
  • crop=400:400:300:350: 座標 (300, 350) を原点とする矩形 400x400 を crop するの意。

  • overlay=300:350: オーバーレイ座標。

  • boxblur=10: ぼかしの強度。

ぼかしを矩形の周囲にしたい場合は全域を boxblur した絵にオリジナルの矩形を crop したものを overlay すればいい。また、フィルターには boxblur の他にも豊富にある。

映像遷移効果

Demuxer concat で物足りないときにはフィルター xfade を検討したい。これはある映像から別の映像へ切り替わるときに、スライドやワイプなどの視覚的効果を与えるものだ。

次のコマンドは input0.mp4 から input1.mp4 へクロスフェイドする映像を出力するはずだ:

bash$ ffmpeg \
    -i input0.mp4 \
    -i input1.mp4 \
    -filter_complex "xfade=transition=fade:
    duration=${TRANSITION_DURATION_IN_SECONDS}:
    offset=${OFFSET_RELATIVE_TO_FIRST_STREAM_IN_SECONDS}" \
    output.mp4

オプション transition に効果を指定する。xfade が対応する利用可能な遷移効果を指定する。選択肢がべらぼうに多いので本稿では割愛。

オプション duration には遷移効果時間を指定する。60 秒以下である必要がある。

オプション offset には遷移を開始する時刻を秒単位で指定する。時刻の基準は最初の映像開始時点とする。

最後に出力ファイルを指定する。望むなら encoding オプションを追加的に指定する。