TOKYO IDOL FESTIVAL のタイムテーブル画像化ツール 2019

3年前に作り始めたのがきっかけで、今年もTIFのタイムテーブルが公開されたので作ってみた。

memo.sugyan.com

Backend

昨年までのものをほぼ使い回しで出来るかな、と思っていたのだけど、今年は動かすプラットフォームを変えてみることにした。

今までは HerokuでRails appとして動かしていたのだけど、いつも月末になるとfree dynoを使い切ってしまい課金しないと見られなくなってしまっていた。 開催直前で使えなくなってしまうのはちょっと致命的だし困る… し、そもそも別にHerokuでRailsでないと動かないわけでもないので別のところに移しても問題ないな、と。

最近はBackendはGoで書きたい気分だったし、Google App Engine の Go1.12 Standard Environment で。

cloud.google.com

何故今までHeroku+Railsを使っていたかというと 画像生成の部分で rmagick を使っていたから、というのが大きい。 最近のGo 1.11/1.12 Standard Environmentは もうGAEのAPIに縛られた特殊なアプリ(ていうと言い方アレだけど)ではなく もはや普通のWebアプリとして作ってそのまま動かせる感じになっている。 imagemagick も普通に入っているので使える。のでGAEを使えない理由は無かった。

また、作成したタイムテーブルをリンク共有する機能でDBを使っていたけど、これはURLに使うユニークなIDと選択したステージのIDリストをひもづけるだけのものなので Cloud Datastore がむしろ用途として合っている。

ImageMagickでの画像生成

以前は rmagick に頼ったコードをRailsで書いていたけど、機能自体はImageMagickCLIでも実現できるもののはずなので、Go版ではライブラリなどを使わず convert コマンドのみで画像生成を実装した。

タイムテーブル画像は同じサイズの細長い画像を縦に連結する形で作られている。合成するアイテム数が分かっていればそのサイズのcanvasを生成してしまって描画する位置を調整していけば良いのだけど、日付の列は -gravity Center で中央寄せで annotate したものを作りたい、などの要求があったのでやはり横長を複数連結する方式にした。

convert -size 100x30 xc:'#303030' -fill white -gravity Center -annotate +0+0 Hello out.png

といったコマンドで f:id:sugyan:20190722224424p:plain のような画像を作れる。 こういったものを複数作って、最後に -append してやれば良い。 …のだけど、いちいちtempfileに出力してファイル名を管理して…というのはやりたくない。 調べてみるとMIFFというformatを使ってstreamingに処理することが出来るらしい。 それぞれの出力をstdoutに繋げて出力して、pipeでまとめて受け取って使える。

File Handling -- IM v6 Examples

(
    convert -size 100x30 xc:'#303030' -fill white -gravity Center -annotate +0+0 Hello miff:-;
    convert -size 100x30 xc:'#505050' -fill white -gravity Center -annotate +0+0 World miff:-;
    convert -size 100x30 xc:'#707070' -fill white -gravity Center -annotate +0+0 '!!!' miff:-;
) | convert - -append out.png

と、こういう形で f:id:sugyan:20190722225309p:plain のような画像を出力することが出来る。 最後のところを png:- とすればPNGのバイナリデータも受け取れるので一切中間ファイルに吐き出す必要がなくなる。知らなかった〜〜

結局サーバ側の実装ではpipeを使わず bufferに貯め込んで読み取る という方式にしたけど、ともかく convert コマンドだけで冒頭のような画像を生成することが出来た。

Frontend

Frontendは元々 React + ReactRouter でSPAにしていたものを昨年TypeScript化していて、ほぼ変える必要なかった。

memo.sugyan.com

Updateしたといえば tslint を使うのやめて @typescript-eslint に変えた、というのが大きいか。出来るだけ recommended な設定を使って 1件もwarning出ないよう書ける限り正確に型を書いて使うように心掛けて結局ほぼ全部イチから書き直した。

出演者名で絞り込みするフォームで 入力文字列を使って RegExp を組み立ててmatchさせてフィルタリングする、という処理をしていたのだけど ?* を入れるとぶっ壊れるバグがあることに今年になって気付いた。ひどいバグをずっと埋めていたんだ。。。気付かせてくれた 「転校少女*」さんに感謝。 しかしJavaScriptRegExpのescapeしてくれる関数みたいのって無いものなのか。

あとは時刻系ライブラリ。去年までは Moment.js を使っていたのだけど、そんなデカい処理は必要なくて Backendから取得できるJSONの時刻文字列をparseして 「8/2(金) 09:30」のようにformat出来れば良いだけ。なので とても軽いらしいという Day.js を最初つかってみたのだけど、任意のtimezoneに固定した出力が出来ない、ということに気付いた。

こんなの日本国内の人間しか使わんやろ、と油断していたら昨年 フィンランドのヲタクのヒトから「お前んとこでは動くかもしれんけど こっちのTZだと時刻ズレるんやで」とプルリクを貰ったのだった。

Set time zone for formatting times by hannesj · Pull Request #1 · sugyan/tif2018-mytt · GitHub

なので Asia/Tokyo で固定して出力できる必要はある。適当に調べていたら spacetime というライブラリがあったので 今回はこれを使うことにした。

昨年のJSは 545KB だったのに対し 今年は 230KB と半分以下のサイズになったので効果あったと言えそう

Others

その他にも今回は PWA化とかも入れようかな? と思っていたけど、それほど享受できるメリットも無さそうだし微妙かな、と思って見送った。

生成した画像も CloudStorage に入れるように、とかすれば良いのかもしれないけど そこまで参照されるものでもなさそうだし別にいいかな、と。

要するに、自分が行くわけでもないフェスのためにそこまで頑張るモチベが湧かなかった、、、

Repository