動画ファイルからパラパラ漫画を作ってjavascriptで再生する

はじめに

今回やろうとしたことは動画ファイルから画像のキャプチャを作って、その画像を順番にcanvasに描画し続けるということ。
手順としては

> キャプチャ画像の生成(ffmpeg使用)
・動画ファイルの情報を取得(fps、再生時間、画像サイズ)
・音声ファイルを抽出
・動画のキャプチャ画像生成

> パラパラ漫画生成
javascriptcanvasに順番に描画

キャプチャ画像の生成(ffmpeg使用)

動画ファイル情報を取得(fps、再生時間)
> ffmpeg -i sample.mp4

・・・

  Duration: 00:05:06.53, start: 0.046440, bitrate: 248 kb/s
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 284x160 [SAR 1:1 DAR 71:40], 117 kb/s, 15 fps, 15 tbr, 15360 tbn, 30 tbc (default)
    Metadata:
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default)
    Metadata:
      handler_name    : SoundHandler

Durationから再生時間が「00:05:06.53」とわかる
StreamがVideoが動画ファイルの情報でfpsが15、画像サイズが284x160とわかる
StreamがAudioの行が音声ファイルの情報

音声ファイルを抽出
ffmpeg -i sample.mp4 -acodec copy -map 0:1 sample.m4a
動画のキャプチャ画像生成
ffmpeg -i sample.mp4 -ss 0 -r 15 -q 1 /work_dir/frame%d.jpg

上記のコマンドでframe1.jpg、frame2.jpg、frame3.jpg・・・の形で連番で生成してくれる
オプション-rは先に取得したfps

数フレームを1ファイルにまとめる

15fps(1秒間に15フレーム表示)の場合、15秒間の動画だと225枚(15fps x 15秒)の画像が生成される。
何フレームかを1ファイルにまとめてファイル数を減らす
ここでは1ファイル36フレームにして7ファイルになるようにする

imagemagicを使えば画像を水平・垂直方向へ連結できるので今回はこれを使う

frame1.jpg〜frame6.jpgを水平方向に連結してhorizon_frame1-6.jpgを生成

convert +append frame1.jpg frame2.jpg frame3.jpg frame4.jpg frame5.jpg frame6.jpg horizon_frame1-6.jpg

全てのフレームを同じように処理してhorizon_frame1-6.jpg、horizon_frame7-12.jpg・・・みたいになる

frame1-6.jpg〜frame31-36.jpgを垂直報告に連結してvertical_frame1.jpgを生成

convert -append horizon_frame1-6.jpg horizon_frame7-12.jpg ・・・ horizon_frame31-36.jpg vertical_frame1.jpg

全てのフレームを同じように処理してvertical_frame1.jpg〜vertical_frame7.jpgを生成

パラパラ漫画再生

html

<canvas id="canvas"></canvas><br/>
<button id="btn-audio-play">音あり</button>
<button id="btn-audio-pause">音声なし</button>

javascript

<script>
var width = 284, //画像サイズ
      height = 160,
      frameCount = 225, // フレーム数
      imageCount = 7, // 画像数
      movieTime = 15, // 動画時間
      frameNo = 0;

var canvas = document.getElementById("canvas"),
      ctx = canvas.getContext("2d"),
      canvas.style.width = width + "px",
      canvas.style.height = height + "px",

var imgList = [],
      timer = null;

// 画像7枚読込
for (var i = 1; i < imageCount; i++) {
    var img = new Image();
    img.src = "/vertical_frame" + i + ".jpg";
    imgList.push(img);
}

// 1枚目の画像の読込完了したら再生開始
imgList[0].onload = function() {
    startVideo();
}

// 動画再生開始
function startVideo() {
    timer = setInterval(draw, ((movieTime/frameCount)*1000))/1.05);
}

// 音声読み込み
var audio = new Audio();
audio.src = baseUrl + "/sample.m4u";

// 音声再生開始
function startAudio() {
  var nowTime = new Date();
  var diffTime = (nowTime - startTime) / 1000;
  audio.currentTime = diffTime;
  audio.play();
}

// 音声再生開始
document.getElementById("btn-audio-play").addEventListener("click", function() {
  startAudio();
});

// 音声再生停止
document.getElementById("btn-audio-pause").addEventListener("click", function() {
  audio.pause();
});

// 描画処理
function draw() {
      var prevFrameNo = frameNo;

      if (audio.paused) {
        // 経過時間から何フレーム目を表示するか求める
        var nowTime = new Date();
        var diffTime = (nowTime - startTime) / 1000;
        frameNo = Math.floor(diffTime / (movieTime/frameCount));
      } else {
        // 音声の位置から何フレーム目を表示するか求める
        var nowTime = new Date();
        var diffTime = (nowTime - startTime) / 1000;
        frameNo = Math.floor((audio.currentTime) / (movieTime/frameCount));
      }

      if (prevFrameNo != frameNo) {
        // 表示する画像を求める(何枚目のどの画像か)
        var n = Math.floor(frameNo / 36);
        var m = (frameNo % 36);
        var x = (m % 6) * width;
        var y = Math.floor((m / 6)) * height;
        var img = imgList[n];

        ctx.clearRect(0, 0, width, height);
        ctx.drawImage(img, x, y, width, height, 0, 0, width, height);
      }

      // 最後のフレームを表示したら終了
      if (frameCount <= frameNo) {
        clearInterval(timer);
      }
}
</script>

ズラズラ書いたけどこの処理は何に使えるのだろう・・・
以上です