GIFアニメーションを再生し終えてから次のGIFを表示するElectronアプリを作っていました

作っていました。

[khsk/electron-gif-loop: Gifyからgifを取得し、最低1ループはアニメを見せるgifビューワー]

備忘録。


# 特徴

最低1ループはアニメーションを見せる、スライドショー的gifビューワーです。

* [GIPHY] からgifを取得しつづけます。
* 任意のタグを複数登録することで、好きなジャンルのgifを表示できます。(精度は[GIPHY]APIに依存します)
* 可能な限り、ループの終了直後に次の画像を表示します。
* 画面のリサイズと移動が可能です。
* 常に前面表示の切り替えが可能です。

# デモ

![demo](https://github.com/khsk/electron-gif-loop/raw/master/readme/demo.gif "demo")


# 動機

昔からgifアニメの扱いに難儀していた。
静止画ほど気楽に流し見できないし、ループしてから次の画像へ移るのも最初の部分を2回見ることになってもやもやする。
スライドショーにまじると途中までしかアニメがみれないor短いアニメが繰り返しすぎてもやもやする。

最低1回はアニメーションを完走させ、切りが良いところで次のgifが見たい。

# gif解析ライブラリ編

## 手をつけるまで長かった

アニメーションの総時間は(当然)gif内の各画像の表示時間の合計であることはわかっていました。
[GIFフォーマットの詳細]
が、これを手動で解析する気には到底なれなかった。

サーバーサイドで一旦保存して何かに変換、というのも処理が大変だしアプリ形式でないし、.Net系も疎いのでどう実装するか悩んでいました。

その後、Electronを知り、JavaScriptでなんでもでき、gifはHTMLでよく表示するしなんとかできるだろうと奮起。

**しかし、「gif アニメーション」という検索キーワードが一般的すぎて、Googleでは欲しい情報やライブラリを手に入れることが長い間できなかった。**

JavaScriptキーワードを足しても、普通のホームページ的な表示・再生解説が多く、良いワードが思いつかなかった。

**欲しいものは、「gifアニメーションの終了検知」だったが、これは結局手に入りませんでした。**

## 最初の光明

そんななか見つけたものが
[shachaf/jsgif: JavaScript GIF parser and player]
で、gifをフレーム単位で解析してくれそうだったので、それを上手くすれば再生時間をつかめるんじゃないかと思いました。

だけど、コードは難しいし、どちらかと言えばブックマークレットというエンドユーザー向けの匂いもするしで挫折しました。

## 次のライブラリ

次に見つけたものが、
[jDataView/jDataView: DataView. Extended. Anywhere.]
で、JavaScriptでバイナリ解析するようなライブラリでした。
もうバイナリから各フレームの時間を取得しようと意気込んでいましたが、
やはりコードが読めず、特に利用段階での`ArrayBuffer`というデータタイプがまったく理解できず、同じく挫折。

## 結局、デキる人がデキるものを作ってくださっている

ただ、**jDataView**で望むことができる可能性は高く感じていたので、gifと合わせて検索しました。
すると、
[rfrench/gify: JavaScript API for decoding/parsing information from animated GIFs using ArrayBuffers.]
というまさしく、durationが一発で取れるライブラリが見つかった！
望むものそのものすぎて小躍りした記憶がありますが、やはり検索方法が難しく、時間がたった今では**jDataView**も**gify**もどうやって見つけ出したかが定かではないです…。

余談ですが、この名前のせいでgiphyをgifyと打ち間違え続けています。

# API編

表示する画像は[GIPHY]から取得することにしました。
[Giphy/GiphyAPI: Public facing API docs, notes and more]
が公開されているし、簡単です。
当然ながら、海外色が強いのが難点か。

## なぜ外部APIにしたのか

ローカルのフォルダ内の画像の方が好きに扱えるではないか。

建前
出先など異なるPCでもgifを楽しみたかったから。

本音
自分が、画像を保存して見返さないどころか画像を保存すらしないぐらいに枯れ果てていたから。
(ローカルファイル操作よりAPIの方が簡単そうだったから)

gifを集めてるわけではないのですが、昔からのgif(ビューワー)に対する不満だけは募っていた感じです。
ただ、シネマグラフはいろいろたれ流し続けて見たいなーという欲求はありました。

## 【対抗馬】国内発のgifアニメサイト

Giphyの他に候補として
[GIFアニメ好きの画像検索&作成コミュニティ - GIFMAGAZINE]

[秋葉原VSニューヨーク。GIFアニメ戦争勃発?! “GIF”をテーマに戦うスタートアップ2社。【GIFMAGAZINE & GIPHY】特集インタビュー | GIF89a|最新自主制作アニメ,映画,映像制作を楽しむ動画メディア]
も見つけたのですが、APIがけっこう厳しくて

[GIFMAGAZINE GIF画像検索API]

>APIの呼び出しはモバイルアプリからのみ許容し、それ以外のアクセスと判断された場合はAPIへのアクセスを制限いたします。

モバイルアプリ限定ということで利用できませんでした。

# 実装

ほぼ1画面ですし、HTMLは`<img>`タグを表示するのみ。
JavaScriptはかなりひどいです。

作ってる途中にはいくつか記事も投稿してきました。

[Electron終了時にCacheを消す - Qiita]

[Electronのレンダラーでうっかりメイン側node.jsのrequestを使っちゃったら - Qiita]
これは誤っているというか、体感エラーは減ったんですが、まだ出る時もあって究明中です。

[フォーカス時かつトレイクリックができない - Qiita]

[if ( Electron && Windows && フレームレス && 全面ドラッグ移動可 && リサイズ可能) { 設定して欲しいCSS } - Qiita]

[ElectronからCSSのurl()を動的に使う場合の注意点 - Qiita]

[Electronが作成・保存するlocalStorageなどのキャッシュディレクトリ - Qiita]


## 基本の流れ

1. GIPHY APIからランダムなgifURLを取得する。
2. URLからgifのバイナリデータを取得して、アニメーション時間(duration)を取得する
3. HTMLの`<img>`の`src`属性を書き換える
4. アニメーション時間が経過したら、再度1から実行し、新しいgifを表示する

という流れです。

## 空転時間の問題

最初は、基本の流れ1~3を一つの処理にして、4をdurationの`setTimeout`にして再帰することで実現していました。

しかし、
アニメーション終了→URL取得→gif解析
には時間がかかるため、2ループ目の中途半端な位置で新しいgifに変わることが多発しました。

そこで、現在のgif表示中に次のgifを1枚先読みできるように、

1と2と34を別々の処理にして、12は`setInterval`、34は変わらずdurationの`setTimeout`に分けてみました。
先読みをグローバル変数にまかせて、
ループを`setTimeout`で行い、
変数の中身を確認したり空にしたりで同期を実現するというのは、
とてもきれいではないのですが、何も考えずに作れるということだけがメリットですか。代案が浮かびませんでした。

副作用として、gifの変わり目がだいたいループ終了後になったのは怪我の功名。

## キャッシュを諦めた話

このアプリでは同じgifを取得するときがあって、そこをキャッシュで効率化できないかと考えましたが、あんまりうまく行かなかったお話。

### 解析とHTML表示で2回通信する

ランダムなgifURLは、`XMLHttpRequest`によるバイナリ取得と、`<img src>`で、2回参照され、通信します。
これは無駄だと思ったので、一度目のバイナリ取得時に、gifを`base64`形式に変換して、`src=URL`の代わりに利用できないかと思いました。

が、変換に時間がかかるわ、変換後のデータが大きすぎてメモリをすごく消費するわ[^1]、あげく、Chromeが通信をキャッシュしているらしく、`<img src>`の通信はキャッシュで問題無さそうに見えることが判明して取りやめとなりました。

### durationのキャッシュ

ランダムURLが過去と同じURLになった場合、durationは変わらないので、不要なリクエストと計算をやめようと思いました。
そこで`sessionStorage`にURLをキーにdurationを記録して、バイナリ取得前にチェックを入れてみました。
動作はしているのですが、体感の変化はほぼ無いので、あまり有効なアプローチではないのかと思いました。現代のCPU性能が優秀ということなのか。

## バリデーションエラー表示を諦めた話

HTMLのvalidatitonは当然使えるのですが、普通のChromeのようなリッチなエラー表示が、デフォルトではされません。表示メッセージは取得できるし、カスタマイズ方法もあるのですが、ブラウザのデフォルトがどうなっているのかがわかりませんでした。
手抜きをしたかったので、簡単なAlert表示にとどまりました。
あのデフォルトバリデーション表示を使いたい…

## 設定は`localStorage`

`electron-config`や`electron-json-storage`などがあるそうですが、まあ簡単に使える`localStorage`でウィンドウサイズなどを保存してみました。
最初に参考した記事でそうしていたのと、レンダラープロセスだけで困らなかったのが理由、でしょうか。


## やる気の問題-実装編

[gify]を見つけた段階で5割、gifが終了直後に切り替わったことを確認した段階で9割ほど達成感を得るというか、技術的に美味しそうな所は頂いてしまい、充足してしまいました。
そこからリサイズや設定画面などをやっつけで追加したのですが、アプリを完成させるということが難しいことを実感しました。
かっこいいアプリにしたいとは思っても、それはプログラミングとしてあまり楽しい作業ではないなあ、と。
あとアイコンとかデザイン面ががが。
せめてQiitaの話のタネになる程度には形にしてひとまず挙げることを目標にしました。

# やる気の問題-Github編

実装が済んだのが確か5月ぐらい。
そこから手が動かず、7月ごろにようやくgitを設定してコミット。
どちらも普段使わないので苦戦するのですが、Githubはリモートリポジトリということで、ローカルのgitでただadd-comitするより1ランクハードルがあがります…

## やる気の問題-Readme編

更に2ヶ月放置して、やっとReadmeを書き出します。
他のしっかりとしたReadmeを普段見ているせいで、こちらもハードルがあがり中々手を出せませんでした。
頑張って英語で書くのか？とか、Contributeの方法は自分でも操作方法がよくわかってないのに載せるのか？とか、頭だけで考える日々が過ぎます。
「とりあえずリリース」すら高望みしている状態です。

# やる気の問題-Qiita編

これです。Readme書いてから更に2ヶ月放置です。
「今までQiitaに書いてきたプログラムより少しは苦労してるからちゃんとした記事にしなければなあ」
と考えて2ヶ月です。
書き始めたら書き始めたで、良くも悪くも一日で書き上げるのですが…


# 展望

`<canvas>`タグは色々できそうなので、もっとよりよくイメージファイルを扱ってみたい。

# 何か作ろう、公表しようと思っている人がいたら

自分が言えたことではないですが、まず手を動かしましょう。
良いことはないよということを半年の放置期間の経験からお伝えしました。

[「手を動かさない人」へのアドバイスは、とても難しい。 | Books&Apps]

[「手が動かせない人」への処方箋 | Books&Apps]


# Electronは楽しいよ

さんざ言われているので特に言いたいことはないですが、サーバーの建て方わからない人や、無料で作りたい人や、古いIE対応に追われている方にはオススメです。簡単！楽しい！新しい！
メインプロセス触らなくても十分何かはできる。


# 参考記事

* [Electronでデスクトップウィジェットを作るまで - Qiita]

[GIPHY]の著作権は…googleと同じ？その利用者は…？

* [TwitterのGIFアニメ添付機能が「著作権侵害」に？　米国では「フェアユース」 (1/2) - ITmedia ニュース]
* [Twitterに追加されたGIF機能と著作権について - Togetterまとめ]

[GIPHY]:http://giphy.com/

[khsk/electron-gif-loop: Gifyからgifを取得し、最低1ループはアニメを見せるgifビューワー]:https://github.com/khsk/electron-gif-loop

[GIFフォーマットの詳細]:http://www.tohoho-web.com/wwwgif.htm
[shachaf/jsgif: JavaScript GIF parser and player]:http://slbkbs.org/jsgif/

[jDataView/jDataView: DataView. Extended. Anywhere.]:https://github.com/jDataView/jDataView

[rfrench/gify: JavaScript API for decoding/parsing information from animated GIFs using ArrayBuffers.]:https://github.com/rfrench/gify
[gify]:https://github.com/rfrench/gify



[Giphy/GiphyAPI: Public facing API docs, notes and more]:https://github.com/Giphy/GiphyAPI



[GIFアニメ好きの画像検索&作成コミュニティ - GIFMAGAZINE]:http://gifmagazine.net/


[秋葉原VSニューヨーク。GIFアニメ戦争勃発?! “GIF”をテーマに戦うスタートアップ2社。【GIFMAGAZINE & GIPHY】特集インタビュー | GIF89a|最新自主制作アニメ,映画,映像制作を楽しむ動画メディア]:http://gif89a.net/gifmagazine-giphy/

[GIFMAGAZINE GIF画像検索API]:http://api.gifmagazine.net/


[Electron終了時にCacheを消す - Qiita]:http://qiita.com/khsk/items/9d6a089a4a799e207e2a

[Electronのレンダラーでうっかりメイン側node.jsのrequestを使っちゃったら - Qiita]:http://qiita.com/khsk/items/ffb4f82e4455f1799258


[フォーカス時かつトレイクリックができない - Qiita]:http://qiita.com/khsk/items/1033a70cc1ccba8c6bfc


[if ( Electron && Windows && フレームレス && 全面ドラッグ移動可 && リサイズ可能) { 設定して欲しいCSS } - Qiita]:http://qiita.com/khsk/items/81982d89d92f88f28e41


[ElectronからCSSのurl()を動的に使う場合の注意点 - Qiita]:http://qiita.com/khsk/items/5c9b7419835bd9b7955e

[Electronが作成・保存するlocalStorageなどのキャッシュディレクトリ - Qiita]:http://qiita.com/khsk/items/90486b2359aa6f9d4b94

[^1]: 先読みで結果2個分保持するタイミングもあって…

[「手を動かさない人」へのアドバイスは、とても難しい。 | Books&Apps]:http://blog.tinect.jp/?p=32311

[「手が動かせない人」への処方箋 | Books&Apps]:http://blog.tinect.jp/?p=34070

[Electronでデスクトップウィジェットを作るまで - Qiita]:http://qiita.com/SallyAcolyte/items/94ed26ab62b8b32b1b2c

[TwitterのGIFアニメ添付機能が「著作権侵害」に？　米国では「フェアユース」 (1/2) - ITmedia ニュース]:http://www.itmedia.co.jp/news/articles/1602/25/news085.html

[Twitterに追加されたGIF機能と著作権について - Togetterまとめ]:http://togetter.com/li/942287