Hit here at 60 degree angle

USBのテレビチューナーを付けているのでXubuntu上で地上波テレビを見られるわけなのだが、困ったことに割と不安定なのである。

VLCでストリームを開くと、ノイズまみれになったり、何も映らなかったり、Xごと固まったりする、ときがある。再起動直後はそうなる可能性が高く、数日起動しっぱなしの後はうまく映る可能性の方が高くなるというのも妙にアナログ的で何が悪いのか目星を付けにくい。

特にXごと固まると復旧するのも大変だ。なにしろほぼどんな入力も受け付けなくなる。こうなった際はCtrl+Alt+F2で別のコンソールに切り替え(このキーストロークだけは生きている)、

sudo systemctl restart lightdm

とかするととりあえず入力を受け付ける所までは戻る。ただしこれでおかしなテレビが直ったりするわけではない。

さてワンセグを見る分には特に問題はなさそうなので、つまるところB-CASカードからのデータ読み込みと復号を担当するlibarib25.soに問題があるのかもしれない。そうすると、これを解決する手段としてはアレしかないわけで、あちこちからソースをかき集め、アレをビルドしてアレと差し替えたところ、安定するようになった。いやー…すごいなアレは。

Pushing latest episodes #3

次にアプリケーションサーバとなるここに関して。

アプリケーションサーバ側でもFirebaseが提供する、Firebase Admin SDKと呼ばれるライブラリを利用できる。ただしサポートされるのはNodeJavaPythonC#Goであり、phpはない。そんなわけで非公式のphp向けライブラリがあるのでそれを使う。どうしてphp版を公式に出さないんですか…どうして…。それはそれとして、php向けライブラリはドキュメントも充実しているのでありがたい。

アプリケーションサーバでやることは、クライアントから渡されたトークン、番組のシリーズIDをデータベースに保存し、番組表の取り込み時に該当するシリーズの最新話を見つけたらPush通知を行う…といった感じである。やること自体は割と簡単。

このシリーズIDとは何かというと。まず番組表は番組枠が列挙してある。番組枠とは時間で区切られた区間のことだ。この枠に番組が収まっている。この仕様から見れば、番組枠の中に複数の番組を入れることも不可能ではないので、データ上も番組枠(slot)にぶら下がっている番組(programs)は配列になっている。

シリーズIDはこのprogramsの各要素に生えているseriesオブジェクトのidプロパティであり、定義がないので断言はできないが、これが配信される作品全体に対して振られたユニークなIDのようである。また同様にprogramsの各要素に生えているepisodeオブジェクトのsequenceプロパティが該当シリーズにおける放送順を示している、ようである。

したがって、データベースにシリーズIDとその最新シーケンスナンバーのセットを保持しておき、番組表を取り込んだ際に照合して追跡中のシリーズでありかつ新しいシーケンスナンバーを持っている番組枠があれば、それが最新話ということになる(実際には、さらにmarkフラグなども見る)。

ところがここでひとつ問題がある。上記のシリーズIDの正規形は

\d+-\d+
らしいのだが、ときおりこのフォーマットから外れたものがある。例えばシリーズIDが
175-1rthzhecdme
といったものになったり、シーケンスナンバーが20とか80とか突拍子もないものになったりする。これはどうも「仮のシリーズID」的なもので、未来の番組表に初めて現れた最新話とかがこの状態になったりするようだ。そして、実際の配信日になると本来のシリーズIDが割り振られる。

この仕様がどういう意味を持っているのか分からないが、とにかく不完全なシリーズIDは最新話を検知するための足掛かりにならない。そこで、シリーズIDを元にした検知と平行して、タイトルによるヒューリスティックな検知も行うことにする。これは、例えば検知を開始した番組のタイトルを覚えておいて、それと似た感じなら同一シリーズと見なすものだ。似た感じというのはリーベンシュタイン距離とかそんな感じのアレである。

ところでここで使用する番組のタイトルというのは、正確には番組枠のタイトルである。これは配信される作品のタイトルとは独立しているため、物によって【WEB最速・単独最速】だとか【地上波先行・先行配信】だとか、様々な枕詞が付いたりする。これはヒューリスティックな検知にはノイズなので困る。seriesオブジェクトに作品自体のタイトルを示すプロパティが格納されていると嬉しいのだけど。

というわけでAbema.tvの番組表データはいろいろと不思議な点が多い。

Pushing latest episodes #2

ということで、ブラウザ側としては拡張機能内でFirebase SDKを使用する構成にする。この組み合わせは、一応Firebaseのドキュメントでも公式に動作が保証されている。ただし、どういうわけか知らないが拡張機能内でFirebaseを使用する際のサンプルやガイダンスはドキュメントに一切ない。そのため備忘録として気付いたことをメモしておく。

ちなみに、上記の保証というのはSDKがchrome-extension:スキームを認識するということである。これは今のところSDKはFirefox上のWebExtensionsでは動作しないことを意味する。したがって、あべ☆アニの最新話の追跡機能もまた、Firefoxでは動作しない。

  • 拡張にSDKを組み込む。これは具体的には
    <script src="https://www.gstatic.com/firebasejs/8.2.1/firebase-app.js"></script>
    てな感じの要素をバックグラウンドページに仕込んでおくことである。この場合、外部サイトのファイルを直接指定しているので、manifest.jsに
    "content_security_policy": "script-src 'self' https://www.gstatic.com; object-src 'self'"
    といったCSPの記述も必要。また、この拡張をストアにアップする際にリモートコードを使用している旨の告知とその理由が必要になる。
  • Push通知を受けるためにどこかでサービスワーカーを登録しなければならないのだが、Firebase SDKを使う分には明示的にそれをする必要はない。firebase-messaging-sw.jsという名前のファイルをサイトのルート(つまり拡張のアーカイブのルート)に置き、必要な処理をそこに書いとけばいい。ワーカーの登録はFirebaseが勝手にやる。登録済みのregistrationを元にFirebaseアプリケーションオブジェクトを構築することもできるが、Firebase側でよしなにしてくれることをわざわざやることもないだろう。なお登録は不要だがサービスワーカーからのメッセージはリスンする必要があるので、
    navigator.serviceWorker.addEventListener('message', aFunctionDefinedSomewhereElse);
    とかはしておく。
  • Push通知を受けるためのトークンを
    firebase.messaging().getToken()
    によって取得することができる。これによって得られる文字列は永続的なものであり、一度取得したら永続的なストレージに入れて使い回していい。この手のトークンにはたいていrefreshイベントをリスンして期限切れに備えましょう的なノウハウがあったりするが、必要ない。onTokenRefreshイベントは用意されているがすでにdeprecateである。
  • このトークンは通知の宛先となるものなので、通知を出す側であるアプリケーションサーバへ送り付ける。さてこれが漏れると勝手に通知を出されるので厳重に暗号化した上で適切に送信しなければならない…はずなのだが、ドキュメントでは単に「トークンを取得したら、それをアプリサーバーに送信して、適切な方法で保管します。」としか記述されていない。送信のためのベストプラクティスとかはないのである。単にpostすればいいらしい。本当? とりあえずあべ☆アニextensionではblowfishで暗号化した上でここのサーバに送信している。まあ拡張のソース見ればすぐバレちゃうけど。
  • Push通知を受けるには、サービスワーカーにおいてもFirebase SDKのコードをimportScript()して、必要な情報を元にFirebaseアプリケーションオブジェクトを生成し、onBackgroundMessageイベントをリスンする…とドキュメントには書いてあり、それが正道なんだろうが、実は単に通知を受けるだけならワーカー自身のpushイベントをリスンするだけで事足りる。FirebaseはPush通知以外にも様々な機能が統合されているので、通知を受けるとともにそれらの機能を利用する場合はドキュメント通りのやりかたが正しいのだろう。あべ☆アニextensionでは通知を単に受けるだけで良いので、直接pushイベントを見ている。importScript()云々もしていない。
  • サービスワーカーで通知を受けたらデータペイロードを拡張のバックグラウンドページへ送信する。ここで困るのはバックグラウンドページがイベントページ、つまり用が済んだら勝手にアンロードされるモードの場合だ。この場合、サービスワーカーのClients#matchAll()ではバックグラウンドページを取得できないし、起こすこともできない。どうするか。実はサービスワーカーのグローバルオブジェクトにchrome.runtime.getBackgroundClient()というものが生えており、それを呼ぶと必要ならバックグラウンドページを起こした上でそれに対応するClientオブジェクトで解決されるPromiseを返すようになっている。このメソッドはChromeの拡張APIドキュメントのどこにも載ってない、この記事だけのかなりのオトク情報である。まあいずれ載ると思うけど。

こんなもんかな。

Pushing latest episodes

あべ☆アニに、任意の番組の最新話を自動的に通知予約する機能を追加している。

そのためにはまず最新話を検知する処理をどこに置くかということを考えないといけない。番組の通知予約機能自体に立ち返ってみると登場人物が3者いる: ここのサーバ、ブラウザの拡張機能(のバックグラウンドページ)、あべ☆アニのページである。

番組の開始時刻になったことを検知し、タブを開く処理を置くのにどこが相応しいかを考えると、あべ☆アニのページはそれを開いている時しか機能しない。サーバ側でやってPush通知をするとなると通知が番組開始1分前に確実に届くかは保証できないし、通知を受けるサービスワーカーから行えるタブ操作が微妙に痒いところに手が届かない。と言うわけで、ブラウザの拡張機能にアラームをしかける実装になっている。

では、最新話の検知機能はどうか。それを行うには番組表の全ての枠を走査しなければならないが、まずあべ☆アニのページにしても拡張機能にしても個々のクライアントがやるべき処理ではない。一方で元々ここのサーバが番組表をキャッシュするために定期的に番組表を取り込んでいるのだから、その一環として最新話かどうかを判断する処理を入れるのが最も収まりがいいだろう。

そんなわけで検知機能はサーバ側に置き、見つけたらPush通知により個々のクライアントに送りつける構成にしよう。Push通知に関してはWeb PushのAPIをそのまま使うと妙にやることが多くてめんどくさいので、Firebase SDKを使うことにする。

ということでだいたいできて今dog-foodingしているところである。今月中くらいにはリリースしたい。

chrome.input.ime

ブラウザの中にはたくさんの文字入力可能なオブジェクトがありさまざまな種別の文字が入力される。一方で IME には入力モードというものがある。そのため時々入力したいものと入力されるもののミスマッチが起こる。

そんなわけで、その辺をブラウザと IME が密接にやりとりして調整してくれると嬉しい。しかし実際には、ブラウザから IME を制御することはほとんどできないし、HTML の規格を見るにむしろできないようにしている感すらある。

ところで、タイトルのような chrome extension の API がある。これは本来は ChromeOS のためのもので、IME の機能のうちプリエディットや変換候補リストといった UI 部分を担当する。つまり変換エンジン自体は自分で書かないといけないのだが、これを活用すればブラウザから制御できる IME ができるかもしれない。

で、すでにこの API を用いて作成された SKK というものがあって:
http://blog.jmuk.org/2012/07/chromeosskk.html
インストールしてみたものの、ChromeOS には存在するのであろう入力メソッド選択インターフェースが ChromeOS 以外の OS で動くブラウザにはないので、つまりこの SKK をアクティブにする手段がない。おのれ…おのれ…!

finding time shifted programs

abema.tvでへやキャン△をやっているのだけど、配信が木曜の3:55〜という謎の時間帯で困る。

なので1週間有効の見逃し配信で見るわけなのだが、見逃し配信が有効な過去の番組枠をあべ☆アニから検索するのがめんどくさい。

ということで検索時にそういうものも対象とするようにした。

tab emphasis

アクティブでないタブがトラッカーによって更新された場合、文書のタイトルを点滅させるようにした。しかし単に点滅だと意外に目立たない。タブの背景色をいじれるといいんだけどな。あるいは音を出せばいいのかな?

数年前の赤福プラスは (Presto)Opera、Chrome、Firefox(Add-on SDK) で動いていた。これらのブラウザがサポートするエクステンションの API は微妙に、あるいはかなり異なっていたので、それらの差異を吸収する層が必要で、赤福プラスにおいてはそれは Kosian というライブラリだった。

さてそこから時が過ぎてブラウザのエクステンションは Chromium のそれで統一されてしまう勢いになったので、Kosian の存在意義が危うくなってしまった。なのでラッパの層を段階的に剥がしていきたい。同時に、個々のモジュールは ECMAScript Module で組み込むようにしたい。

ということで、そうした。文章で書くと簡潔だけどこれはなかなか大変でした。

skk is … #3

・カタカナを入力する簡便なショートカットとして、見出し語を入力して Q を押すとカナに変換されて確定する仕様がある。これを「ゔぁいおりん」という入力について適用すると「ゔァイオリン」になってしまう。

これは元をたどればもともと skk が euc-jp エンコーディングで動いていたためで、Unicode にしかない濁点つきの「ゔ」を知らないからなのだろう。しかし今動いてる skk の本体は vala で書かれた libskk であり、基本となるエンコーディングもたぶん utf-8 だろうからそういうしがらみは実はないはずだ。

ということで追ってみたが https://github.com/ueno/libskk/blob/master/libskk/util.vala#L43 以下に定義されている変換テーブルに「ゔ」のエントリがないからだと思う。「う゛」はある。このテーブルもキーマッピングやローマ字同様に json ファイルから読み込むようにしてほしい…。

・libskk 内のいじった設定を反映させるために fcitx を再起動するのだが、その直後に高い確率で thunar がクラッシュする。ふふ、お前マジぶっころがすぞ。

・fcitx-skk の設定で初期入力モードを「直接入力」にしても反映されない。常にひらがな入力で開始する。

・現在の入力モードを通知領域に表示してもらえるととっても嬉しいのだけど、そうなってくれない。そのかわりに状態パネルなるものを常に表示するようにすると、そこに現れる。まあ…それでもいいんだけど…。いや良くない。邪魔だ。

・xkb で右 alt を F20 に交換している。そのキーにひらがな入力に入るコマンドを割り当てたいが、なぜか動作しない。どういうキーを受けてどういうマッピングをしたのかをレポートするログ機能が libskk にほしい。

skk is … #2

絵文字の変換を行うために
https://github.com/uasi/skk-emoji-jisyo
の SKK 辞書を使いたい。ダウンロードして、dictionary_list に追記する。

file=$FCITX_CONFIG_DIR/SKK-JISYO.emoji.utf8,mode=readonly,encoding=UTF-8,type=file

が、認識してくれない。なんで…? と思ってソースを見てみたら
https://gitlab.com/fcitx/fcitx-skk/blob/master/src/skk.c#L196
なるほど readonly の場合は $FCITX_CONFIG_DIR の展開をしていない。というか、この関数自体の文字列処理とメモリ管理の書き方がなんとなく危うい感じがする…。

そういうわけで仕方ないのでベタに書いて認識。

file=/home/akahuku/devel/ref/skk/skk-emoji-jisyo/SKK-JISYO.emoji.utf8,mode=readonly,encoding=UTF-8,type=file

その他の環境変数や “~” はどうなんだろうか。このパス名は libskk を経由して GLib の File_for_path() という立派な関数に渡されるのでそこでなんかうまいことしてくれるのかもしれない。試してない。

しかし認識したのはよいものの、入力中に “/” で入る見出し語をアルファベットで編集するモードでこの辞書内の見出しを補完できない。

うーむ。まあこの補完って先頭一致なのでできたとしてもあんまり嬉しくはないのだけど。例えば heart だけでも

black_heart
blue_heart
broken_heart
couple_with_heart
couple_with_heart_man_man
couple_with_heart_woman_man
couple_with_heart_woman_woman
gift_heart
green_heart
heart
heart_decoration
heart_eyes
heart_eyes_cat
heartbeat
heartpulse
hearts
heavy_heart_exclamation
kissing_heart
purple_heart
revolving_hearts
sparkling_heart
two_hearts
yellow_heart

こんな感じで先頭だったり真ん中だったり末尾だったり単語中にあったりごちゃごちゃなので。