radikoを録音するpythonスクリプトを先人たちのコードを読みながら作ってわかったこと

## サマリ

* Radikoの録音をしたくて調べたら色んな人がコードを書いていました。
* 単にgit cloneして動かしてもよかったのですが、どんな処理をしているのか気になり、先人たちのコードを読みながら自分でもコードを書いてみました。
* やってみると学べたことがわりとあったのでまとめてみました。
  * Radikoで使われているhls(HTTP Live Streaming)というプロトコル
  * Radikoで番組を聞くときに行われている処理
  * 仕様が公開されていない処理をどうやって調べるのか
  * どう調べたかまとめて得たこと


## 試した環境

* OS: Mac OS 10.14.6
* docker desktop community: 2.1.0.5
* docker image: [python:3.7.5-buster](https://hub.docker.com/_/python/)
* Google Chrome: バージョン: 78.0.3904.108 Official Build 64ビット

## つくったもの

https://github.com/1021ky/radiko_recorder

## わかったこと

### Radikoで使われているhlsというプロトコル

* Radikoではhls（HTTP Live Streamingの略）という動画や音声といったメディアファイル配信用のプロトコルを使っている。
  * 数年前はflashを使っていたらしいが、[flashが2020年に終了](https://japan.zdnet.com/article/35104776/)する影響かhlsを使うようになった。
* hlsは[RFC8216](https://tools.ietf.org/html/rfc8216)で仕様が規定されている。
  * サーバーは分割したメディアファイルと、分割されたファイルをどう再生すればよいか記述した2種類のPlaylistを取得できるようにしている。
    * 配信内容全体に関することが記述されたMaster Playlist
    * 分割された各ファイルに関することが記述されたMedia Playlist
    * PlaylistはUTF-8のテキストファイルで.m3u8か.m3uという拡張子であり、URIで参照できる。
  * クライアントはPlaylistを参照して、そこからメディアファイルのURIやメディアファイルの再生に必要な情報を取得して、再生をする。

### Radikoで番組を聞くときに行われている処理概要

下記図のような感じにHTTPでクライアントとサーバーでやりとりが行われていることがわかりました。

![処理概要.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18485/e1af5a41-499d-8c7c-c3f0-172096e2116c.png)


## どうやって調べたのか

ざっとはこんな感じです。すんなりとこの順番でいけたわけではなく、いったりきたりを何回かしました。

1. 他の人が書いたコードを読む→認可処理と音声ファイル取得処理が必要だとわかる
2. コードを書いてみる→RadikoのAPIをコールしてHTTPステータスコード40Xが返ってくるので、何か足りていないとわかる
3. Google Chromeのディベロッパーツールでどんな通信が行われているか調べる→具体的な通信内容を見ることで不足していたことを理解
4. コードを書いたあとに今回やったことをまとめてみる→うまく説明できない単語があった→hlsやm3u8はRadikoの独自仕様ではなくRFCで仕様が規定されているとわかる

コードは以下の記事のものを読ませていただきました。

1. [簡易Radiko録音スクリプト](https://gist.github.com/matchy2/3956266)
2. [【Python】ラジコを再生する](https://turtlechan.hatenablog.com/entry/2019/06/24/172207)
3. [Python3でradikoと超A&Gと音泉と響を自動検索・録音するソフトを作った](https://qiita.com/taittide/items/7219cc9ff6788423ab50#fn2)
4. [radiko録音(ダウンロード)への道 その1](https://blog.bluedeer.net/archives/216)

1はシェルスクリプトで書かれていたのでわからないコマンドはググることで、どんなパッケージが必要かすぐわかりました。

* flashで再生される音声ファイル取得のためのrtmpdump
* 取得したファイルをMP3にするためのffmpeg

ただ、その前処理はなぜやっているのか、何をやっているのかよくわかりませんでした。（シェル力が足りない。。）
2,3は普段使っているPythonで書こうとしていたためわかりやすく、ここでさきほどのシェルスクリプトでよくわからなかった処理は認可処理だとわかりました。
4でRadiko独自の仕様のパーシャルキーというのをどういうロジックで生成しているのか調べ方がわかりました。

必要なパッケージとライブラリはこれで決まったので、コードを書きました。
で、上述のとおりRadikoのAPIをコールしてHTTPステータスコードが40X系のものが返ってきたので、Chromeのディベロッパーツールで自分のコードでは失敗する処理をブラウザ上ではどうしているのか調べました。
以下は認可処理について調べたときです。
![devtool_auth.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/18485/832972b7-0ed9-8ed8-c127-5b6e6657f20a.png)

リクエスト内容を見てリクエストヘッダーにX-Radiko-***とはじまるものがいつどこで必要なのかわかりました。コードをざっと見たときは見落としていたところでした。で、ようやく動くようになりました。

## どう調べたか記事にまとめて得たこと

まとめるときにhlsってなんだ？m3u8ってなんだ？と改めて疑問に思い、調べるとRFCで決まっていることがわかりました。RFCで決まっているのならば、それらを扱いやすいようにしているライブラリがあるのではないかと調べるとやはりありました。

* [m3u8](https://pypi.org/project/m3u8/): Python m3u8 parser
* [hls関連のライブラリと思われるもの](https://pypi.org/search/?q=hls)

m3u8のライブラリは使いやすく、煩雑な私のコードをきれいにしてくれました。
また、RFCを読んで用語がわかるとメソッドや変数の名前を決めやすくなりました。たとえば、Master PlaylistとMedia playlistにあたる変数はRFCを読む前はとても適当で読み直すとよくわからない名前になっていました。

最近読んだ本、上田勲. プリンシプル オブ プログラミング 3年目までに身につけたい 一生役立つ101の原理原則.に、
>「良いプログラマは良いコードを書く、偉大なプログラマはよいコードを借りてくる」という格言もあります。

とありました。読んだときはなんとなくわかるぐらいの理解でしたが、今回、何が標準になっているか知っているのかはレベルアップに繋がることなのかなと理解が深まった気がします。

## まとめ

* Radikoの録音スクリプト作成を通じてhlsとその実装について知ることができた
* 何が標準となっているか知っているかわかっているとメリットが大きいことを体感できた
  * ライブラリが見つけやすい（余計なものを作らなくて済む）
  * 実装がやりやすくなる
* 同じ機能が実装されたコードを読み比べると読みやすいコードを考えるいい機会になる