撮影した動画をFFmpegとWatsonを使って字幕とロゴの挿入を自動化した

bussorenre
286
この記事は RECRUIT MARKETING PARTNERS Advent Calendar 2017 の投稿記事です。

初めまして。ひっそりとRMPでエンジニア業をさせていただいております。 @bussorenre と申します。

Recruit Marketing Partners アドベントカレンダー14日目 では、あまり弊社ブログでは取り上げる頻度の少ないミドルウェアの話ができればなと思います。

さて、年の瀬も押し迫り、師匠もおかんも走る時期ですが1)悪寒が走ったら休んでください。my mother is not runnning.、忘年会や新年会の幹事に任命される事が往々にしてよくあります。そしたら「その年の振り返り動画を用意しよう」とか「全国の営業部長たちから新年の意気込みを集めてきて動画にまとめよう」みたいな業務が発生し、▂▅▇█▓▒░(’ω’)░▒▓█▇▅▂うわあああああああなんて事は、どの業界でもよくあると思います。

動画編集は相当手間と時間のかかる作業です。エンジニアとしては、やはりここのプロセスをなんとか自動化し時短したいですよね。ということで、今回はFFmpeg を用いて字幕やロゴの挿入といった動画の編集を全部自動でやるスクリプトを用意します

完成した動画はこちら。

FFmpeg とは

A complete, cross-platform solution to record, convert and stream audio and video.

FFmpegは、動画・音声処理の記録、変換、再生に関するオープンソースソフトウェア群の総称で、多くのweb企業に活用されています。一般的にFFmpeg と指す場合、変換ツールであるFFmpeg の事を指しますが、動画再生ソフトウェアのFFplayや、ストリーミングサーバーとなる FFserver等も兄弟分として存在します。

これらは共通のライブラリである libav* を用いて作成されており、C言語を用いてこれらのAPI を独自で呼び出すことができます。

FFmpeg は単なるエンコーダーだと思われがちですが、後述するようなフィルターを用いて入出力を操作する事ができ、フィルターを組み合わせることで多彩な表現を実現できます。本記事では、このフィルターを用いて簡単な動画編集を行います。

FFmpeg の処理の流れについて

さて、実際にフィルターを用いるサンプルを見せる前に、FFmpeg がどのように動画ファイルを読み取り最終出力までたどり着くのか構造を理解しておくとより高度なフィルターがかけれるでしょう。

FFmpeg の処理の流れは、以下の図のようになります。

ffmpeg architecture

FFmpeg は複数の入力ソースを受け取ることができ、受け取ったソースはFFmpeg が扱いやすいようにデコードされます。デコードされたメディアはフィルターグラフ(Filter Graph)という複雑なパイプラインを通って、OutputStream として出力されます。最終的にエンコードされ、メディアファイルとして出力されます。

また、上の図の下部にもあるように、一旦フィルターを通して出力した OutputStream をInputStream として新たにフィルターに通すといった再帰的な処理も可能です。

デコーダー、エンコーダーが具体的にどのような処理を行っているかは、Libavcodec が担っており、動作が詳しく知りたい人は、ここで中身を知ることが出来ます。

フィルターを使う

では早速フィルターを指定してみましょう。(フィルターに関してはLibavfilter が担っており、ここ で中身を知ることが出来ます。

例. 色調変換

例1 では、1つの動画ファイルを入力とし、セピア色に変換して出力するという簡単な例を紹介します。構造も最もシンプルです。以下のような処理の流れになります。

シンプルなフィルター

ffmpeg -i input.mp4 -vf "colorbalance=rm=0.3:bm=-0.4:gm=0.1" -pix_fmt yuv420p output.mp4

フィルターを指定する場合は、 Video Filter (-vf) Audio Filter(-af) オプションを用いて、オプション引数にフィルター文字列を指定する必要があります。

フィルター文字列の指定の仕方はだいたい以下の書式の通りになります。

-vf "
  filter1 = 
    setting1 = value1 : 
    setting2 = value2 ,
  filter2 = 
    setting1 = value1 :
    setting2 = value2
  "

この例では、colorbalanceを用いて映像をセピア色に変換しています。

複雑なフィルターを使う

例1  複数の入力を、連続した動画にする

ffmpeg -i input0.mp4 -i input1.mp4 -filter_complex "[0:0][0:1][1:0][1:1]concat=n=2:v=1:a=1" output.mp4

concatフィルターを用いて、2つの動画入力を1つの動画に連結して出力します。複雑なフィルターを使う時は、Filter complex(-filter_complex) オプションを用いてフィルターグラフを指定することが出来ます。

Filter Complex は 以下のような書式で指定することができます。最後までパスの通っていないストリームがあると、FFmpeg はエラーを返します。

[input0][input1][...]filter1[output0][output1][.....]; 
[input0][input1][...]filter2[output0][output1][.....]; 
[input0][input1][...]filter3[output0][output1][.....];

例2 単一の動画を4画面に分割し、それぞれガンマカラーを変えて出力

例2 結果イメージ

1つの入力ソースから画面を4分割し、それぞれ、元動画、赤、緑、青といったふうに色調を変更し1つの動画に出力します。メディアの拡大・縮小は Scaleを利用します。また、1つのストリームを複数に分割して扱いたい場合には splitを利用します。また、複数の映像を重ね合わせる時は Overlayを用います。

作戦としては、1つのストリームを4つに分割し、それぞれに色を適用。グリッド状になるようにoverlay を使い、最終的な結果を出力します。

フィルターグラフを図にすると、以下のような構造になります。

コマンドは以下の通りです。

ffmpeg \
 -i input0.mp4 \
 -i input1.mp4 \
 -filter_complex "\
 [0]scale=iw/2:-1,split=4[red][green][blue][origin]; \
 [red]eq=gamma_r=10.0[red]; \
 [green]eq=gamma_g=10.0[green]; \
 [blue]eq=gamma_b=10.0[blue]; \
 [0][origin]overlay=0[x]; \
 [x][red]overlay=w[y]; \
 [y][green]overlay=0:h[z]; \
 [z][blue]overlay=w:h[out]" \
 -map "[out]" output.mp4 \

フィルターは無数に用意されており、これらを全て紹介するには本3冊くらい必要なのでこのくらいでご勘弁を。以下のリンクが非常に参考になります。

動画編集を自動化する

さて、自動で動画編集をするスクリプトを実際に作ってみましょう。
今回自動で生成したい映像は、以下の図ような内容の映像です。

年初にあたり、各地の営業部長たちから「気合を入れるためのありがたいお言葉」を納めた映像ファイルが送られてくると仮定します。

ありがたいお言葉は、自動でしっかり字幕に変換されます。今回は字幕変換にIBM Watson Speech to Text を用います2)Google Cloud Platfotm Speech API でも問題ないと思います。お好みでどうぞ。予め音声ストリームのみを抽出しておき、watson に 文字起こししてもらいます。返ってくるテキストファイルはそのままの形式では利用できないため、srt ファイルとして出力するプログラムを書きます。

これらをffmpeg で読み込み、 元映像、字幕、ロゴの3メディアを1つの動画にします。最後に出来上がったありがたい動画群をffmpeg のconcat 機能で1つの動画に連結し、納品完了となります。

IBM Watson speech to Text を利用する

Watson Speech to Text は、curl, node, Java のライブラリが公式で用意されています。詳しくは  API リファレンス を参照すると良いでしょう。今回のサンプルではnode を選択します。

node でWatson に音声ファイルを送信し、レスポンス(json)を解析します。最終的な出力になるsrt ファイルはDVD 等に格納されている字幕ファイルで、再生開始時間、再生終了時間、文字列 といった単純な構成になっています。

1
00:02:17,440 --> 00:02:20,375
こんにちは。エンジニアのぶっそれんれです。

2
00:02:20,476 --> 00:02:22,501
このようなファイルを、node から出力します。

mp3 を受け取りsrt を吐き出す簡易スクリプトは、以下のような構成になります3)watson-developer-cloud v2.42.0 を使用しています

const SpeechToTextV1 = require('watson-developer-cloud/speech-to-text/v1');
const fs = require('fs');

const speech_to_text = new SpeechToTextV1 ({
  username: "{username}",
  password: "{password}"
});

var params = {
  model: 'ja-JP_BroadbandModel',
  content_type: 'audio/mp3',
  interim_results: false,
  max_alternatives: 1,
  word_confidene: false,
  timestamps: true,
  speaker_labels: false,
  realtime: true
};

for( var i = 2; i < process.argv.length; i++) { 
  audio2srt(process.argv&#91;i&#93;);
};

// float -> srt time の変換
Number.prototype.toTimeline = function(){
  var format = 'hh:mm:ss,ms';
  var hh = Math.floor(parseInt(this) / 3600);
  var mm = Math.floor((parseInt(this) / 60) % 60);
  var ss = Math.floor(parseInt(this) % 60);
  var ms = String(this).split(".")[1];

  format = format.replace(/hh/g, ('0' + hh).slice(-2));
  format = format.replace(/mm/g, ('0' + mm).slice(-2));
  format = format.replace(/ss/g, ('0' + ss).slice(-2));
  format = format.replace(/ms/g, ms);

  return format;
}

function audio2srt(input) {

  // 入出力ストリームの準備
  var output = fs.createWriteStream(input+'.srt');
  params.audio = fs.createReadStream(input);

  // 音声認識の開始
  speech_to_text.recognize(params, function(error, data) {
    if (error)
      console.log('Error:', error);
    else {
      var i = 1;
      for ( var result of data.results ) {
        var timestamps = result.alternatives[0].timestamps;
        var transcript = result.alternatives[0].transcript;
        output.write(i+ "\n");
        output.write(timestamps[0][1].toTimeline() + " --> " + timestamps[timestamps.length-1][2].toTimeline() + "\n");
        output.write(transcript.replace(/\s+/g, "") + "\n");
        output.write("\n");
        i = i + 1;
      }
    }
  });
}

FFmpeg で映像、字幕、ロゴを合成する。

素材が揃いましたので、FFmpeg でこれら3つを合成するフィルターグラフを作成します。やることは至ってシンプルで、以下の二点です。

  • 映像とロゴをoverlay フィルターで合成する
  • 字幕をsubtitles フィルターで映像に焼きつける4)DVD プレイヤーなど適切な再生環境がある場合は、OutputStream[0][2]にsrtファイルをマッピングすると、いい感じに字幕のオンオフができるようになります。

実行のためのコマンドは以下のようになります。

ffmpeg \
  -i input1.mov \
  -i logo.png \
  -filter_complex "[0:0][1:0]overlay=x=W-w-20:y=20,subtitles=input1.mp3.srt" \
  -map 0:1 \
  -video_track_timescale 29971 \
  -ac 1 \
  -s 1920x1080 \
output1.mp4

concat 機能で映像をつなぎ合わせる。

最後に、複数の映像を連結させます。先述の concat フィルターを用いても良いのですが、それとは別にconcatenate機能が用意されています。

以下のようなテキストファイルを用意し、FFmpeg に入力ソースとして渡すだけで自動で動画を接続してくれます。

# this is a comment
file '/path/to/file1'
file '/path/to/file2'
file '/path/to/file3'

コマンド例は以下のようになります。

ffmpeg -f concat -safe 0 -i mylist.txt output.mp4

利点としては圧倒的に操作量が少なくて楽な事ですが、トランジションフィルター等をかけることは出来ませんし、入力ファイルのエンコード形式や映像サイズが異なっているとエラーになります。

今回は予めFFmpeg でサイズ・エンコードを指定した中間生成ファイルを結合するだけなので、この方式で楽に動画を結合することが出来ます。

出来上がった物

ということで、こんな感じの物が自動で生成されました。∩( ・ω・)∩バンジャーイ5)この動画に出てくる人は、別に営業部長でもマネージャーでも何でも無いです6)無表情だなこいつって自分で撮影してて思いました。Youtuber の被写体力すごい

※ シークバーに字幕が隠れてしまうので、マウスカーソル等をオーバーしないで御覧ください7)字幕が少し見切れてますね。この辺はWatson に喋り方を合わせるか、適当な文字数で改行を挟むかで対処できそうです

今回は、時期が年末年始だったので忘年会・新年会をテーマにしましたが、誕生日や結婚式などのお祝いメッセージなどにもサクッと使えると思います。ぜひぜひ。

まとめ

今回紹介したとおり、FFmpeg を用いれば、所謂動画編集ソフトで人が作業を行わなくても、動画の編集をある程度自動化することが可能です。

また、兄弟分であるFFserver  を用いればリアルタイム配信も可能になりますし、内部ライブラリであるlibav* はC言語で操作することが可能です。最近流行りのディープラーニング等を組み合わせて、フィルターのかけ合わせ方を学習されればより高度な編集もしてくれるようになるかもしれません。新サービス・新機能の可能性が無限に広がりますね。ワクワクします。

さて、webと動画は切っても切れない関係になりました。弊社のように動画コンテンツを売り物として提供しているスタディサプリのようなサービスだけでなく、動画共有サービスやSNS での拡散、広告バナー等、動画を用意する場面は年々確実に増えています。

しかし、実際に事業者としてコンテンツを用意する側になると、大きく3つのボトルネックが存在します。

  1. 素材を用意する
  2. 用意した素材を適切に加工・編集する
  3. コンテンツをユーザーに届ける

そして、これら全ての工程で、人、金、時間、といったリソースを大量に消費してしまいます。技術者間の会話では「いかに高画質で安くて早い、安定したストリーミング配信を実現するか?」というポイントが議論に上がりやすいですが(というか、毎日のようにしていますが)、今回は「用意した素材を適切に加工・編集する」というフェーズに着目。何とか自動化・工数削減の糸口を見出すことが出来ないか?という観点で、ffmpeg による動画編集を紹介しました。

今回は編集がメインなので触れませんでしたが、新コーデック規格のAV1の話や、VR動画編集の話等もいずれさせて頂ければと思います。

それでは(  ̄ー ̄)ノ

脚注   [ + ]

1. 悪寒が走ったら休んでください。my mother is not runnning.
2. Google Cloud Platfotm Speech API でも問題ないと思います。お好みでどうぞ
3. watson-developer-cloud v2.42.0 を使用しています
4. DVD プレイヤーなど適切な再生環境がある場合は、OutputStream[0][2]にsrtファイルをマッピングすると、いい感じに字幕のオンオフができるようになります。
5. この動画に出てくる人は、別に営業部長でもマネージャーでも何でも無いです
6. 無表情だなこいつって自分で撮影してて思いました。Youtuber の被写体力すごい
7. 字幕が少し見切れてますね。この辺はWatson に喋り方を合わせるか、適当な文字数で改行を挟むかで対処できそうです