語学学習のためにNHKラジオを聴くようになった。 テキストを物理本で購入していて、あわせて見るようにしている。 3ヶ月くらい続いている。
特に「エンジョイ・シンプル・イングリッシュ」と「ラジオ英会話」を聴いている。このあたりが、おれのいまのレベル感に合っていて、抵抗なく聞けるラインだ。
4月は節目になっていて、いろいろな番組がリセットされる。エンジョイ・シンプル・イングリッシュは、構成が大きく変わるし、ラジオ英会話は、またレベルがリセットされて、エントリー向けの内容なるし、メンバーも変わる。
何度も聞いてみたいと思ったので、これを機に、録音しておくことにした。前期までの放送は、もう公開が終わってしまうので、まとめてCDを買うことにする。
どうやって録音するのか。軽く探してみたが、気に入るソフトが無かった。そこで、NHKラジオを保存するPCアプリを自分で作ってみることにした。
Electron について
さて、おれは最近仕事でElectronを使う機会があった。Electronの以下のような点が気にいっている。
- 当然だが、Web技術でフロントエンドを書くことができる。
- Node.js のコードを、ポータブルに、クロスプラットフォームで持ち運ぶことができる
- TypeScript で開発できる
Electronの練習も兼ねて番組をダウンロードするアプリケーションを作ることにした。
Electron は、かなり枯れているし、古い情報も多い。最近では、似ているけど、Rustをバックエンドにした Tauri というフレームも登場している。こちらは、かなりバンドルサイズが小さくて良い。 しかし、今回は、Node.js にフォーカスしたかったのでElectronを選んだ。
完成したアプリはこちら:
Electron アプリを作る
今回の出発点は、公式サイトの聞き逃し配信のWebサイトを解析することからだ。 まず、playwright でサイトをスクレイピングしようかと思ったのだが、JSON API を見つけたので、それを呼び出すことにした。ここから番組とエピソード取得する。 エピソードはWebRTCで配信される。 次に取り組んだのはWebRTCのフォーマットを解析することだ。 ストリーミング配信では音声ファイルは小さなチャンク単位に区切られて配信される。M3U8ファイルを解析して、 すべてのチャンクのURLを調べてダウンロードする。 今回の場合はAESでファイルが暗号化されているので、これも解除する。有料コンテンツをこのような方法でデクリプトするのは何かと問題があるが、今回の場合は、無料コンテンツなので大丈夫だと考えているが、どうだろう。 最後に、細切れになった音声ファイルを1つに結合する。MPEGのような音声ファイルは、基本的には、小さなフレームの集合体になっている。フレームは、フレームヘッダーとサイズが頭にあるので、音声フレームだけを抜き出していけば、余計なメタデータを回避できる。 こうして、まずは、GUI無しの単体のライブラリとして、やりたいことを一通り全部実装した。
あとは、GUIを作っていく。
UI のモデルを考える。まず、番組とエピソードはある。それから、ダウンロードマネージャー(ブラウザなどについている)のような形式を考えた。もしやろうと思えば、パラレルに、高速ダウンロードできるように作っていくこともできたが、非公式なアプリという性質上、逆に、時間をかけて1つずつダウンロードキューを消化していくような設計にしたいと考えた。
初期のモデル設計はこのようになった。
結局、エピソードの詳細をていねいに見せることは本アプリの関心事では無いと考えるようになったので、「番組」と「キュー」の2ペイン構成にすることにした。
本アプリでは、負荷をかけないための待ち行列機構が重要になる。通常のアプリの感覚では、ボタンが押されたら即アクションを起こす。しかし今回は、キューを経由した非同期な処理となる。更新チェックについても同じで、ボタンを押すと即チェックではなく、「更新チェックのためのキュー」にエンキューする。ダウンロードも、「ダウンロードのためのキュー」にエンキューしていって、1つずつ順番にダウンロードする。また、ダウンロードキューは永続化もする。
あとは、GUI を作っていく。Electronの IPC の仕組みを使って、バックエンドのキューシステムとイベント送受信を作り込んでいく。
完成。
WebRTCについては、実は ffmpeg を使えば、すんなり保存からエンコードまで可能なのだが、依存を減らしたいし、自分でできることだとも思ったので、自分でやることにした。実のところ、Electron に ffmpeg がバンドルされるので(Chromiumが使うからだ)、それをうまく使う道もあったのかもしれない。
今回は Electron Forge というフレームワークを使ってやってみた。マイナートラブルが多くて苦労した。これか electron-builder かの2択だろうか。 また、今回は、ほとんど TypeScript だけの小さな boilerplate から開発をはじめた。React をはじめ、必要なものを自分で持ってこないといけないので、大変だったが、勉強になった。もっと出来上がっている boilerplate はたくさんあるのだが、自分で作るとなると、ビルドをちゃんと通すだけでも一苦労だった。
実際に運用する
我が家には、常時起動させている Windowsマシンがあるので、そこで動かすことにした。最初からこの Windows で実行するために作っていたのだ。この Windowsマシンは、Jellyfin (詳しくは以前の日記)サーバにもなっている。
n-radio-downloader
は自動的に新着エピソードをチェックしてくれるので、放っておけば、録音がたまっていく。そして、たまった録音は、 Jellyfin が自動的に認識してくれるってわけ。 Jellyfin を使えば、数話連続で聞くこともできるし、自分でプレイリストを組むこともできる。iPhoneでもPCでもだいたい自由にアクセスできる。
というわけで、本家で視聴するよりも良い体験を組むことができた。
それでは。