来月開催のTOKYO IDOL FESTIVAL (#TIF2019)、自分の行きたいところだけ選んで組んだタイムテーブルをこんな感じに1枚絵の画像で生成できるWebアプリを今年も作りました。ご自由にお使いください https://t.co/OvzLbmHXGS #TIF2019_MyTT pic.twitter.com/gxO85LovHu
— すぎゃーん💯 (@sugyan) July 22, 2019
3年前に作り始めたのがきっかけで、今年もTIFのタイムテーブルが公開されたので作ってみた。
Backend
昨年までのものをほぼ使い回しで出来るかな、と思っていたのだけど、今年は動かすプラットフォームを変えてみることにした。
今までは HerokuでRails appとして動かしていたのだけど、いつも月末になるとfree dynoを使い切ってしまい課金しないと見られなくなってしまっていた。 開催直前で使えなくなってしまうのはちょっと致命的だし困る… し、そもそも別にHerokuでRailsでないと動かないわけでもないので別のところに移しても問題ないな、と。
最近はBackendはGoで書きたい気分だったし、Google App Engine の Go1.12 Standard Environment で。
何故今までHeroku+Railsを使っていたかというと 画像生成の部分で rmagick を使っていたから、というのが大きい。
最近のGo 1.11/1.12 Standard Environmentは もうGAEのAPIに縛られた特殊なアプリ(ていうと言い方アレだけど)ではなく もはや普通のWebアプリとして作ってそのまま動かせる感じになっている。 imagemagick
も普通に入っているので使える。のでGAEを使えない理由は無かった。
また、作成したタイムテーブルをリンク共有する機能でDBを使っていたけど、これはURLに使うユニークなIDと選択したステージのIDリストをひもづけるだけのものなので Cloud Datastore がむしろ用途として合っている。
ImageMagickでの画像生成
以前は rmagick
に頼ったコードをRailsで書いていたけど、機能自体はImageMagickのCLIでも実現できるもののはずなので、Go版ではライブラリなどを使わず convert
コマンドのみで画像生成を実装した。
タイムテーブル画像は同じサイズの細長い画像を縦に連結する形で作られている。合成するアイテム数が分かっていればそのサイズのcanvasを生成してしまって描画する位置を調整していけば良いのだけど、日付の列は -gravity Center
で中央寄せで annotate したものを作りたい、などの要求があったのでやはり横長を複数連結する方式にした。
convert -size 100x30 xc:'#303030' -fill white -gravity Center -annotate +0+0 Hello out.png
といったコマンドで のような画像を作れる。
こういったものを複数作って、最後に
-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
と、こういう形で のような画像を出力することが出来る。
最後のところを
png:-
とすればPNGのバイナリデータも受け取れるので一切中間ファイルに吐き出す必要がなくなる。知らなかった〜〜
結局サーバ側の実装ではpipeを使わず bufferに貯め込んで読み取る という方式にしたけど、ともかく convert
コマンドだけで冒頭のような画像を生成することが出来た。
Frontend
Frontendは元々 React + ReactRouter でSPAにしていたものを昨年TypeScript化していて、ほぼ変える必要なかった。
Updateしたといえば tslint
を使うのやめて @typescript-eslint
に変えた、というのが大きいか。出来るだけ recommended な設定を使って 1件もwarning出ないよう書ける限り正確に型を書いて使うように心掛けて結局ほぼ全部イチから書き直した。
出演者名で絞り込みするフォームで 入力文字列を使って RegExp
を組み立ててmatchさせてフィルタリングする、という処理をしていたのだけど ?
や *
を入れるとぶっ壊れるバグがあることに今年になって気付いた。ひどいバグをずっと埋めていたんだ。。。気付かせてくれた 「転校少女*」さんに感謝。
しかしJavaScriptでRegExpの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 に入れるように、とかすれば良いのかもしれないけど そこまで参照されるものでもなさそうだし別にいいかな、と。
要するに、自分が行くわけでもないフェスのためにそこまで頑張るモチベが湧かなかった、、、