読者です 読者をやめる 読者になる 読者になる

Gifアニメを作るChrome拡張Animated Gif Captureを公開しました

f:id:y_d:20140511200605p:plain

「Animated Gif Capture」というアニメーションGifを作るChrome拡張を公開しました。
Chromeに表示中のWebページをアニメーションGifに変換します。また、オプションページから設定を変更すると、デスクトップやChrome以外のウィンドウもアニメーションGifにすることが出来ます。

Chrome Web Storeに登録してあるので、ここからインストールできます。
Chrome Web Store - Animated Gif Capture - アニメーションGifキャプチャ

キャプチャ例

Flashもキャプチャできます。
http://demouth.net/sketch/016/ 以前私がAS3で作ったやつ
f:id:y_d:20140511224420g:plain


FlashのキャプチャできるのでVimeoでもYouTubeもいけます。
フレームレートを高めると滑らかなアニメーションGifになります。
※これは私のVimeo動画です。著作権違反にはお気をつけください
f:id:y_d:20140511225623g:plain


Chrome拡張ではありますが、Chrome以外のウィンドウや、デスクトップ全体のキャプチャも可能です。
これはフレームレートを落とし、撮影時間を長くして、Illustratorをキャプチャしています。
f:id:y_d:20140511225746g:plain

使い方

詳しい使い方はインストール後のオプションページに書いてありますので、インストール後に拡張アイコンを右クリックしてオプションページを表示してください。

あとYouTubeに動画をアップしてあるので、こっちを見ていただくとだいたいわかると思います。
個人的にChrome拡張を使う時一番心配なのは勝手に個人情報にアクセスしてWebにアップしたりしないかという事なのですが、この拡張はWebアクセスはしませんし余計な事は何もしません(当たり前だけど一応)。


How to use Animated Gif Capture - YouTube



以上がアプリの紹介になります。
以降、作った時の技術的な感想をだらだらと書きます。

技術的なこと

Chrome ExtensionはJavaScriptなので、インストールすればソース見られますが、一応Githubにもソースを公開してます。リファクタリングの時間が取れずにソース汚くなっていますが、ソースを公開することで誰かのお役に立てれば幸いです。
demouth/AnimatedGifCapture

基本的に Chrome Extension APIs にすべて必要な情報はあるので、こちらを見ます。

この拡張はキャプチャする方法を3種類の中から選んで設定出来ます。
今回使ったキャプチャを出来るAPIは下記です(あまり確認してないけど、キャプチャ出来るAPIは多分これ以外には無いと思う)。

  • chrome.tabs.captureVisibleTab(integer windowId, types.ImageDetails options, function callback)
  • chrome.tabCapture.capture(object options, function callback)
  • chrome.desktopCapture.chooseDesktopMedia(array of DesktopCaptureSourceType sources, tabs.Tab targetTab, function callback)

tabCaptureはChromeのバージョン31、desktopCaptureは34から使えるようになったようなのでなかなかタイムリーでした。

chrome.tabs.captureVisibleTab()

chrome.tabs.captureVisibleTab()を使うと、コールバックで画像のdataURLが返ってきます。
このAPIは一瞬ではありますがブラウザが固まるのが難点です。アニメーションGifを作るために何度もキャプチャするとChromeのレスポンスが悪くなります。
あと、このAPIで返ってくる画像には、マウスカーソルやセレクトボックスの中身などは映りこみません。

chrome.tabCapture.capture()

chrome.tabCapture.capture()を使うとコールバックにタブの内容を動画と音声で返すLocalMediaStreamオブジェクトを返します。これを使ってタブの中身のキャプチャを撮ります。これをURL.createObjectURL();でvideo要素のsrcに入れると動画を再生できます。あとはこれをcanvas要素を使ってcontext2dにdrawImage()すれば画像に変換出来ます。

var video = document.createElement('video');
video.src = window.URL.createObjectURL(stream);
video.addEventListener('canplay', this.handler);
video.play();

video.srcにstreamを入れると動画の再生が始まるのかと最初思っていましたが、play()しないとvideoに表示される内容は更新されませんでした。後から考えると当たり前かと思いましたが。
あと、タブの動画の再生が始まるまで多少タイムラグがあるようで、canplayイベントの後でないと真っ暗な画像になってしまいました。canplayイベントでなくてもいいかも知れません。

キャプチャする動画のサイズをchrome.tabCapture.capture()の引数に渡すと、その通りの画像サイズでキャプチャしてくれます。タブの中身をキャプチャしたいのでタブのサイズを取得してから渡します。タブのサイズよりも大きなサイズを指定するとタブよりも大きい部分は黒く塗りつぶされた動画が返ってきます。
なお、このAPIはタブのサイズが奇数になると、偶数に丸められ、同時に動画の品質が落ちるようです。この問題をどうにかしようと頑張りましたがどうにもなりませんでした。何か情報をお持ちの方は教えて頂ければと思います。

ちなみにcanvas要素のtoDataURL()メソッドですが、これはtoDataURL('image/webp')のように画像フォーマットを指定できますが、jpegが一番早かったです。
計測結果メモっておけばよかったのですが、残していないので、参考までにざっくりとした感覚値を残しておきます。おそらく画像の内容によっても変換時間は変わると思うのであくまで参考ということで。

  • toDataURL('image/webp') -> ダントツで遅い
  • toDataURL('image/png') -> 速い gifと変わらない
  • toDataURL('image/gif') -> 速い pngと変わらない
  • toDataURL('image/jpeg',1) -> 1番速いけど、png,gifとそんなに変わらない

あと、このAPIで返ってくる動画には、マウスカーソルやセレクトボックスの中身などは映りこみません。

chrome.desktopCapture.chooseDesktopMedia()

desktopCapture APIchrome.tabCapture.capture()と似ていて、動画でキャプチャするAPIです。
tabCaptureと違うのは、chooseDesktopMedia()を呼ぶとウィンドウやデスクトップを選択する為の下記のウィンドウを表示します。
f:id:y_d:20140511212849p:plain
これを使ってウィンドウやデスクトップをユーザーが選択すると、コールバックにstreamIdが返ってきます。
このstreamIdを使ってnavigator.webkitGetUserMedia()に渡すと、tabCaptureと同じようにstreamを取得できます(もしかしてwebkitのプリフィックスいらないかも)。あとはtabCaptureと同じ要領です。下記のような感じ。

    chrome.desktopCapture.chooseDesktopMedia(
      ["screen", "window"] , //ウィンドウとデスクトップどちらも
      function(streamId) {
        if (!streamId) { /* error */ }
        
        navigator.webkitGetUserMedia(
          {
            audio: false,
            video: {
              mandatory: {
                chromeMediaSource: 'desktop',
                chromeMediaSourceId: streamId,
                minWidth: 10,
                maxWidth: 1920,
                minHeight: 10,
                maxHeight: 1080
              }
            }
          },
          function(stream){
            var video = document.createElement('video');
            video.src = window.URL.createObjectURL(stream);
            video.play();
          }
        );
      }
    );

あと、このAPIで返ってくる動画にはマウスカーソルやセレクトボックスの中身は映りこみます。

アニメーションGIFの変換ライブラリ

antimatter15/jsgif を使いました。
※bytearray.orgのAS3の移植!

さすがにGIF画像を作るのは処理時間がかかります。このライブラリの中でWebWorkerを使っているので対象早くなっているかもしれませんが、さすがに画像サイズやアニメーション枚数が多いと数十秒間処理時間がかかります。WebWorkerについてあまり調べていませんが、ライブラリの中を少し覗いてみたところ1スレッドにGIF生成の処理を流し込んでいるようなのでこの辺を書き換えればマルチコアのCPUの時に多少早くなるのかも知れません(たぶん)。

jsgifを使うとgifのdataURLを取得できます。
しかしdataURLは大変重く、ファイルサイズが大きくなった場合ブラウザが死んでしまうこともあるので、バイナリに変換してダウンロードさせています。

http://blog.agektmr.com/2013/09/canvas-png-blob.html
こちらを参考に下記のようなコードでBlobに変換し、ブラウザにダウンロードさせています。

// 空の Uint8Array ビューを作る
var buffer = new Uint8Array(rawData.length);
// Uint8Array ビューに 1 バイトずつ値を埋める
for (var i = 0; i < rawData.length; i++) {
  buffer[i] = rawData.charCodeAt(i);
}
// Uint8Array ビューのバッファーを抜き出し、それを元に Blob を作る
var blob = new Blob([buffer.buffer], {type: 'image/gif'});

var a = document.createElement('A');
a.href = window.URL.createObjectURL(blob);
a.setAttribute('download', filename);
a.click();

Chrome拡張の公開にあたって

f:id:y_d:20140511214654p:plain

Chrome拡張の公開

Chrome拡張をChromeウェブストアに公開する手順は下記を参考にしました。
アイコンの作り方などが紹介されていました(あまり読んでいない)。
https://developer.chrome.com/webstore/publish

デベロッパー ダッシュボードでは言語毎に説明文や画像を設定したり出来ます。

画像について

アプリの画像は下記があると良いです。
試していませんが、おそらく最低限アイコン用の128x128の画像と、ストア用のスクリーンショットが1枚あればいいと思います。

  • アプリ内のアイコン画像 16,19,32,48,128の正方形。manifest.jsonで設定する。
  • ストア用のアイコン画像 128x128である必要がありますが、96x96のアイコンを作って、上下左右に16pxの透明な領域をつける必要があるそうです。
  • ストア用のスクリーンショット1280x800 または 640x400を最大5枚、もしくはYouTube動画。
  • ストア用のプロモーションタイル画像 440x280,920x680,1400x560を1枚ずつ。



以上です。