2020.07.01

Webブラウザで簡単な動画を生成する方法

Pocket

こんにちは。F.S.です。
先日Webサービスの調査の一環で、ブラウザで簡単な動画を作る方法を調べてみたので紹介します。
簡易なプロモーション動画広告の作成に使われるようなやつです。
MS Power Pointみたいに画像や動画、テキスト、図形を素材にしたスライドショーのようなものをイメージしています。

今回は、ブラウザ上のアニメーションを動画に出力するところまでやってみます。

こちらが作成した動画のサンプルです。 ※音が出ます



解像度は640×360です。本当は1280×720でやりたかったのですが、処理落ちが目について断念しました・・

やり方


大まかな流れとしては下記の通りですが、具体的なやり方は様々あるかと思います。

  1. 画像やテキスト、サウンドを元にしたアニメーションスクリプト(シナリオ)を作成
  2. スクリプト(シナリオ)に従って、JSでアニメーションを再生
  3. アニメーションをキャプチャして動画に出力(やり方によっては別途サウンドを合成)

動画にする方法


まずブラウザの画面をキャプチャして動画にする方法から調べていきます。

なお、動画ファイルはバックエンドで非同期生成することを想定していて、ヘッドレスChrome(Chromium)のNode.js APIであるPuppeteerを使って操作することを前提としています。

さて、動画の元になる連続したキャプチャ画像を得るには下記の方法があります。

  1. ブラウザでスクリーンショットを撮る(Puppeeterのscreenshotを使用)
  2. CanvasのdataURLから逐次画像データを取得
  3. CanvasのストリームからMediaRecorderで直接動画として記録(執筆時点でSafari非対応)

3. はWeb Audioのコンテキストからオーディオストリームともつなぐことができて最も手軽です。Safari非対応のAPIを使いますが、Puppeteer利用前提なので問題はありません。
フォーマットはwebmになりますが、想像通りffmpegでmp4に変換することができます。

こちらはMediaRecorderのコード例です。
// Videoストリーム
// layer.getCanvas()._canvas は konva.js のcanvas参照
const canvasStream = layer.getCanvas()._canvas.captureStream(FPS);

// Audioストリーム
// オーディオライブラリにはhowler.jsを使用
const audioContext = Howler.ctx;
const audioNode = Howler.masterGain;
const destination = audioContext.createMediaStreamDestination();
audioNode.connect(destination);
const audioStream = destination.stream;

// それぞれのストリームを結合したMediaStreamを作成
const mediaStream = new MediaStream();
[canvasStream, audioStream].forEach((stream) => {
  stream.getTracks().forEach((track) => mediaStream.addTrack(track));
});

// webmのフォーマットでMediaRecorderを作成
const recorder = new MediaRecorder(mediaStream, {mimeType:'video/webm;codecs=vp9'});

// データが利用可能になった際に動画ファイルのダウンロードリンクを表示
recorder.ondataavailable = function(e) {
  const videoBlob = new Blob([e.data], { type: e.data.type });
  const dataUrl = window.URL.createObjectURL(videoBlob);
  window.webmData = dataUrl;
  const anchor = document.querySelector('#downloadlink');
  anchor.download = `movie${Date.now()}.webm`;
  anchor.href = dataUrl;
  anchor.style.display = 'block';
}

ちなみに、CanvasではなくてDOMを記録したい場合は 1. の方法を使うか、html2canvasを利用して一旦Canvasに書き出してから 2. の方法で画像データを取得します。
ただしscreenshotは要素指定でも比較的重く、解像度640×360のちょっとしたアニメーションでも手元のMacbook Airでは10fpsも出ませんでした。html2canvasでCanvasを使った場合は同条件で20fpsは出てました。

余談ですが、アニメーションライブラリではスロー再生できるものが多く、例えば 1/2倍速で再生して15fpsでキャプチャし、それを30fpsとして動画生成するのが映像としては一番キレイに思えました。

アニメーション再生


次にブラウザでアニメーションを実現するにあたり、レンダリングおよびアニメーションのライブラリを選定します。
ブラウザアニメーションは大きく分けて下記のものがあります。
  • DOMを操作するもの(CSSアニメーションなど)
  • Canvasに描画するもの(WebGL含む)
  • SVG

それぞれ有用なアニメーションライブラリがあるようです。
サウンドの扱いを含むものもありますが、大抵のものは描画が中心です。

DOMやSVGのサンプルでは滑らかで凝ったアニメーションが目立って目移りしたのですが・・・
前述の通りCanvasを利用することにしたため、Canvasに描画するライブラリを選定します。

2D描画の主流はPixiJSのようで、ブラウザゲームで使われることが多いようですね。
FLASHコンテンツの移行ではCreateJSが有効だったようですが、WebGLへの対応が限定的だとか。
(CreateJSはversion 2.0のリリースに向けて頑張っているようです)

そして今回はそのどちらでもなく、Konva.jsを導入してみました。
なぜかというと、レンダリングやアニメーションだけでなくエディタにも同じライブラリを使いたく、Konvaは導入事例でエディタが多く挙げられていたためです。シンプルで扱いやすいと感じたのも理由です。
アニメーション機能(Tween)はKonva自体にもありますが、業界標準(?)の多機能ライブラリGreenSockがKonva Pluginとして使用できるので、それを使います。
なお、オーディオライブラリにはGoogleやDisneyなど様々なサイトで利用されているhowler.jsを使うことにしました。

雰囲気の参考までに、アニメーションはこのように記述されます。
// テキストのノードを作成
const textNode = new Konva.Text({
  text: 'それは長い道のり',
  fontSize: 24,
  fontFamily: 'sans-serif',
  fill: 'white',
  shadowColor: 'black',
  shadowBulr: 10,
  shadowOpacity: 0.5,
  opacity: 0.0,
});
// ノードをレイヤーに設置
layer.add(textNode);

// GreenSockのTimeline機能でアニメーションを作成
const tl = new TimelineLite({
  onStart:()=> {console.log('start timeline’)}, 
  onComplete:()=>{console.log(‘finish timeline’)}
});
// 初期位置を設定
tl.set(textNode, {konva: {y:stage.height() - textNode.height()}});
// 1.0秒後に1.0秒かけて不透明度を100%にする
tl.to(textNode, 1.0, {konva: {opacity:1.0}}, 1.0);
// 3.0秒後に0.5秒かけて不透明度を0%にする
tl.to(textNode, 0.5, {konva: {opacity:0.0}}, 3.0);

なお、サンプル動画冒頭の文字列のアニメーションはDOMでよくあるスプリットテキストアニメーションを真似てみたのですが、上記コード例のような単純なテキストノードでは実現できません。
複雑になるので詳細は割愛しますが、テキストを1文字ずつ分割して文字幅から横位置を計算して配置し、TimelineのstaggerToでわずかに時間をずらしながら同じアニメーションを適用しています。
回転の中心座標が文字の左上ではなく文字の中心になるようにoffsetをあらかじめ調整しておくのもポイントです。

Puppeteerでの操作


Puppeteerでのブラウザ操作はネット上で参考になる例がありますので、詳細は割愛します。

当社グループのエンジニアがPuppeeterを紹介しているブログがあったので参考までにリンクを張っておきます。

Webブラウザで動画を生成する動作イメージを紹介するため、Puppeteerを使った自動操作ではありますが、ヘッドレスモードではなくChromiumを起動させて実行した動画を載せます。

動画の解説です。
  • 前半はPlayアンカーのクリックイベントでCanvasにアニメーションを再生し、裏側でVideo/Audioストリームをキャプチャしてます
  • 後半はwebmをダウンロードしてブラウザで開いて再生している様子です
    (見せるためだけにwebmを開いてます。本来はmp4に変換して終わりです)



最後に


Flash Playerが2020年末にサポート終了になりますね。Flash全盛の当時はFlashさえあればなんでもできると言われていたのが懐かしいです。
筆者はフロントエンドがメインではなく、今回のようなライブラリやAPIを触ることは稀なのですが、実際にやってみると面白いですね。
また、ビジュアル系は変に凝ったり色々とハマって無駄に時間を過ごしてしまいがちです。


それではまた。


次世代システム研究室では、アプリケーション開発や設計を行うアーキテクトを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。