Electronでメディアファイル用のファイルブラウザ「BlackAlbum」を作った

Electronで動作する動画ファイル及びJPG in Zip向けのファイルブラウザを作ってみました。
構成としてはElectron+React+Reduxで、gulpfile以外はbabelを使って書いてます。
そこそこ今風な感じを目指して、一部flowtypeとかも取り入れてますが、割と適当な感じで使ってます。
実は以前Node.jsで同じもの作ってたんだけど、せっかくちゃんとデスクトップアプリとして作れるようになったしReactにも慣れたのでElectronと今の技術で作り直してみたのがこれです。名前も同じだったりする。


https://github.com/joker1007/blackalbum
https://github.com/joker1007/blackalbum/releases/download/v0.2.0/BlackAlbum-darwin-x64-0.2.0.zip
https://github.com/joker1007/blackalbum/releases/download/v0.2.0/BlackAlbum-linux-x64-0.2.0.zip

Screenshot


仕様

動作にはffmpegthumbnailerとffprobe(ffmpeg付属)という二つのコマンドが必要です。
Macならhomebrewで両方入ります。
その他は、PureJSのライブラリで動作しているのでLinuxでもコマンドがあれば動くと思います。(バイナリは作ったけどロクにテストしていない)


特定のディレクトリのファイルを攫って、メタデータとファイルパスをIndexedDBに登録しつつElectronアプリ用のuserDataパスにサムネイルを生成します。
ファイルは完全にフラットに並び、無限スクロールで全てのファイルを参照できます。
後は、設定で拡張子毎に再生用のアプリケーションの実行コマンドを書いておけば実行できます。
再生用アプリケーションは複数登録可能で、右クリックで再生プレイヤーを選択して起動できます。
雑な検索フォーム、ソート順変更、お気に入り登録と、検索クエリのプリセット保存機能なんかがあります。
しかし、設定画面作るのが面倒だったので、設定はuserDataパスにYAMLで直で書くスタイルです。
設定ファイルはこんな感じで、Macだと~/Library/Application Support/blackalbum/config.ymlにLinuxだと~/.blackalbum/config.ymlに置きます。

directories:
  - "/path/to/target/directory"
filterWords:
  - NGWORD
thumbnail:
  concurrency: 3
  size: 200
players:
  mplayer: open -a "/Applications/MPlayer OSX Extended.app"
  vlc: open -a "/Applications/VLC.app"
  cooViewer: open -a "/Applications/cooViewer.app"
extensions:
  avi:
    - mplayer
    - vlc
  mkv:
    - mplayer
    - vlc
  mp4:
    - mplayer
    - vlc
  m4v:
    - mplayer
    - vlc
  mpg:
    - mplayer
    - vlc
  wmv:
    - mplayer
    - vlc
  zip:
    - cooViewer


自分のために作ったものなので、色々不親切ですが自分としてはそれなりに便利に使えてます。
その内、もうちょっと親切なインターフェースを作るかもしれませんが、あんまりモチベーションは無いですw

使った主要なライブラリ

dexie.js

IndexedDBを操作するために使ってます。大体何でもPromiseで帰ってくるのでasync/awaitが使えると綺麗に書き易い。

immutable.js

不変データ構造定番のやつ。

material-ui

Material DesignなReact Componentを簡単に使えるライブラリ。見た目がそれっぽくなるw

react-infinite

React用の無限スクロールライブラリ。リングバッファっぽくなっていて、何件表示しようが一定範囲しかDOMを描画しないので項目が大量になっても動作が軽い。
同種のライブラリが他にもいくつかあった。
ファイル数が数万の単位になると、この手のライブラリが無いと重過ぎてまともに描画できなくなる。

mousetrap

キーボード操作によるホットキーの定義に利用。

redux-thunk/redux-promise

Reduxで非同期処理のアクションを扱うためのミドルウェア

sweetalert

Material Designっぽいアラートダイアログ。

jszip

Pure JSなzipの圧縮・解凍用ライブラリ。adm-zipという同種のライブラリがあるが、こっちの方が大分早い。

pica

Pure JSな画像リサイズライブラリ。lanczosでリサイズできて、触ってみた中ではPureJSでは結構早い方だと思う。

humanize

ファイルサイズとか日付を読み易く整形する。

ビルド環境

gulpとbrowserifyでビルドし、パッケージ化にelectron-packagerを使っています。
一部、browserifyをバイパスしなければならないライブラリがいくつかあるので、その用途で使うものだけどdependenciesに追加し、後は全てdevDependenciesにしてます。
最終的にパッケージ化する時にelectron-packagerのignore設定でdevDependenciesのライブラリを全てignoreすることで、無駄なパッケージでファイルサイズが肥大化しないようにしています。
参考にしたのは以下の記事です。

また、実験的にReactのホットリロードにlivereactloadを使っていますが、SourceMapがおかしくなったりしてちょっと微妙感があります。後、バージョン上げたらビルド壊れたりするし……。

苦労した点

別に大したことはしてないのですが、いくつかハマりました。


まず、無限スクロールはreact-infiniteと同種のライブラリを見つけるまでは、初期描画のパフォーマンスに大きな問題があった。
後、リストする対象が多いのでshouldComponentUpdateをちゃんとやらないとクソ重くなりました。


zip展開と画像縮小のパフォーマンス問題の解決に少し手間取りました。
最初に選択したライブラリが割と遅くて、動作はするけど使い物にならなかった。
picaは高速だったけどbitmap化された画像しか扱えなかったので、BufferをBlobに変換してからCanvasに取り込んでpicaで縮小してからCanvasをElectronのnative-imageモジュールで画像に戻す、というやり方でそこそこ実用的なスピードでzip展開とサムネイル作成ができました。


picaの画像縮小はWebWorkerを使って並列で動作できるようになっているが、そのために利用しているwebworkifyというnpmパッケージが作成したobjectURLをrevokeせずに放置するため、いくつも処理するとobjectURLが溜まり続ける問題があった。そのせいか300件程処理した時点でWebWorkerにアクセスできなくなる謎のエラーが発生。適切にrevokeするようにライブラリをforkして修正して解決しました。


async/awaitのエラー処理をサボると、何の情報も無いままいきなり処理が止まるのでデバッグに苦労した。当たり前ですが、失敗しそうな処理はちゃんとtry/catch書くようにした方が良さそうです。


ElectronはNode.jsのネイティブモジュールが呼べるので、一部のモジュールがbrowserifyと衝突します。何が大丈夫で何が引っかかるのか手探りだったので、動いたり動かなかったり調べてglobal.requireしていくのが面倒だった。
でもbrowserify使わないとnode_modulesでファイルサイズが無駄に膨れ上がるし、それはそれで困る。


後、これ→→ Electronを使ってMac向けのアプリを開発する時のファイル名の扱いについて (所謂UTF-8-MAC問題) - Qiita


と、こんな感じで1ヶ月弱ぐらいElectronとReduxを触ってみたけど、大体使えるようになった気がする。
まあソースコードは割と雑な感じになってるしテストも無いんだけど……。