radiko.jp のタイムフリーの録音(保存、ダウンロード)

radiko.jp のタイムフリーの録音(保存、ダウンロード)ができないか調べてみました。

環境

  • macOS Sierra バージョン10.12.4(16E195)
  • swfextract - part of swftools 0.9.2
  • ffmpeg version 3.3

Homebrew をインストールして、swftools と ffmpeg をインストールしておきました。

$ brew install swftools
$ brew install ffmpeg

radiko.jp の通信の内容

最初に radiko.jp の通信の内容を調べてみました。

大きな流れは認証してからタイムフリー聴取をするようです。

1. 認証

認証の流れは次の通りのようです。

  1. swf ファイルをダウンロードする
  2. ダウンロードした swf ファイルの中から画像ファイルを抽出する
  3. 認証 1(auth1_fms)にアクセスして、X-Radiko-Authtoken{1}, X-Radiko-KeyLength{2}, X-Radiko-KeyOffset{3} を取得する
  4. X-Radiko-KeyLength{2}, X-Radiko-KeyOffset{3} をもとに画像ファイルから X-Radiko-Partialkey{4} を抽出する
  5. 認証 2(auth2_fms)に X-Radiko-Authtoken{1}, X-Radiko-Partialkey{4} を送信する

1.1. swf ファイルのダウンロード

curl 'http://radiko.jp/apps/js/flash/myplayer-release.swf' \
-XGET \
-H 'Referer: http://radiko.jp/' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30'

1.2. 認証 1

curl 'https://radiko.jp/v2/api/auth1_fms' \
-XPOST \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Referer: http://radiko.jp/' \
-H 'Pragma: no-cache' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30' \
-H 'X-Radiko-Device: pc' \
-H 'X-Radiko-App-Version: 4.0.0' \
-H 'X-Radiko-User: test-stream' \
-H 'X-Radiko-App: pc_ts' \
--data $'\r\n'

1.3. 認証 2

curl 'https://radiko.jp/v2/api/auth2_fms' \
-XPOST \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Referer: http://radiko.jp/' \
-H 'Pragma: no-cache' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30' \
-H 'X-Radiko-Device: pc' \
-H 'X-Radiko-Authtoken: mNzMUHWKtgzPYNPa0HiYyg' \
-H 'X-Radiko-App-Version: 4.0.0' \
-H 'X-Radiko-User: test-stream' \
-H 'X-Radiko-Partialkey: /wDrfge1MkkLHBUZ25GeAA==' \
-H 'X-Radiko-App: pc_ts' \
--data $'\r\n'

2. タイムフリー聴取

2.1. プレイリスト 1

curl 'https://radiko.jp/v2/api/ts/playlist.m3u8?station_id=LFR&l=15&ft=20170423010000&to=20170423030000' \
-XPOST \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Referer: http://radiko.jp/' \
-H 'Pragma: no-cache' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30' \
-H 'X-Radiko-Authtoken: mNzMUHWKtgzPYNPa0HiYyg' \
--data 'flash=1'

2.2. プレイリスト 2

curl 'https://radiko.jp/v2/api/ts/chunklist/TcAwn1TY.m3u8' \
-XGET \
-H 'Referer: http://radiko.jp/' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30'

実行

1. 認証

1.1. swf ファイルのダウンロード

-O のオプションをつけて myplayer-release.swf ファイルを保存しました。

$ curl 'http://radiko.jp/apps/js/flash/myplayer-release.swf' \
> -XGET \
> -H 'Referer: http://radiko.jp/' \
> -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30' \
> -O
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  669k  100  669k    0     0   243k      0  0:00:02  0:00:02 --:--:--  243k
1.1.1. 画像ファイルを抽出する

swf ファイルの内容を確認します。

$ swfextract myplayer-release.swf
Objects in file myplayer-release.swf:
 [-i] 1 Shape: ID(s) 1
 [-i] 1 MovieClip: ID(s) 2
 [-b] 10 Binarys: ID(s) 3-12
 [-f] 1 Frame: ID(s) 0

Binarys の最後の 12 の番号がついたファイルが “それ” らしいので、オプションに -b 12 をつけて実行してみます。

$ swfextract myplayer-release.swf -b 12 -o authkey.png

authkey.png の名前で画像ファイルが抽出できたようです。

$ ls authkey.png
authkey.png

1.2. 認証 1

認証 1(auth1_fms)にアクセスして、X-Radiko-AuthToken{1}, X-Radiko-KeyLength{2}, X-Radiko-KeyOffset{3} を取得します。

$ curl 'https://radiko.jp/v2/api/auth1_fms' \
> -XPOST \
> -H 'Content-Type: application/x-www-form-urlencoded' \
> -H 'Referer: http://radiko.jp/' \
> -H 'Pragma: no-cache' \
> -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30' \
> -H 'X-Radiko-Device: pc' \
> -H 'X-Radiko-App-Version: 4.0.0' \
> -H 'X-Radiko-User: test-stream' \
> -H 'X-Radiko-App: pc_ts' \
> --data $'\r\n'
X-Radiko-AppType=pc
X-Radiko-AppType2=pc
X-RADIKO-AUTHTOKEN=wpU-C0IvwTPf_magS8T7gA
X-Radiko-AuthWait=0
X-Radiko-Delay=15
X-Radiko-KeyLength=16
X-Radiko-KeyOffset=180274

please send a part of key

please send a part of key と表示されていますので、先に抽出した画像ファイル(画像ファイルがキーとなっているらしい)から X-Radiko-KeyOffset{3} の次の 1 バイトを開始位置として、X-Radiko-KeyLength{2} の長さの分だけを抽出して、送信するように、とのことのようです。

1.2.1. Partialkey を抽出する

X-Radiko-KeyLength{2}, X-Radiko-KeyOffset{3} をもとに画像ファイルから X-Radiko-Partialkey{4} を抽出します。 オプションは次の通りです。

  • if: 先に抽出した画像ファイル
  • ibs: 一度に指定したバイトのブロックを読み出す
  • skip: 認証 1 で取得した X-Radiko-KeyOffset{3} の値
  • count: 認証 1 で取得した X-Radiko-KeyLength{2} の値

dd コマンドで抽出できるのはバイナリーなので、base64 コマンドにパイプで渡して文字列にしました。

$ dd if=authkey.png ibs=1 skip=180274 count=16 | base64
16+0 records in
0+1 records out
16 bytes transferred in 0.000333 secs (48038 bytes/sec)
LfX/AD/OhWw+4qRkjqOp7w==

最後の行の LfX/AD/OhWw+4qRkjqOp7w==X-Radiko-Partialkey{4} です。

1.3. 認証 2

認証 2(auth2_fms)に X-Radiko-Authtoken{1}, X-Radiko-Partialkey{4} を送信します。

送信すると言っても POST のデータ部分は改行だけのようで、次のヘッダーに指定するようです。

  • X-Radiko-Authtoken: X-Radiko-Authtoken{1}
  • X-Radiko-Partialkey: X-Radiko-Partialkey{4}
$ curl 'https://radiko.jp/v2/api/auth2_fms' \
> -XPOST \
> -H 'Content-Type: application/x-www-form-urlencoded' \
> -H 'Referer: http://radiko.jp/' \
> -H 'Pragma: no-cache' \
> -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30' \
> -H 'X-Radiko-Device: pc' \
> -H 'X-Radiko-Authtoken: wpU-C0IvwTPf_magS8T7gA' \
> -H 'X-Radiko-App-Version: 4.0.0' \
> -H 'X-Radiko-User: test-stream' \
> -H 'X-Radiko-Partialkey: LfX/AD/OhWw+4qRkjqOp7w==' \
> -H 'X-Radiko-App: pc_ts' \
> --data $'\r\n'


JP13,東京都,tokyo Japan

認証に成功すると、地域の情報が表示されるようです。

認証した時の X-Radiko-Authtoken{1} がタイムフリー聴取する時に必要になるようです。

2. タイムフリー聴取

2.1. プレイリスト 1

ヘッダーに X-Radiko-Authtoken{1} を指定します。 POST しているデータは flash=1 となっているようです。

クエリー文字列は次の通りです。

$ curl 'https://radiko.jp/v2/api/ts/playlist.m3u8?station_id=LFR&l=15&ft=20170423223000&to=20170423230000' \
> -XPOST \
> -H 'Content-Type: application/x-www-form-urlencoded' \
> -H 'Referer: http://radiko.jp/' \
> -H 'Pragma: no-cache' \
> -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30' \
> -H 'X-Radiko-Authtoken: wpU-C0IvwTPf_magS8T7gA' \
> --data 'flash=1'
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=52973,CODECS="mp4a.40.5"
https://radiko.jp/v2/api/ts/chunklist/JZTdYq-2.m3u8

プレイリスト 1 にアクセスしてみると、さらにプレイリストの URL が記述されているようでした。

2.2. プレイリスト 2

プレイリスト 2 にアクセスしてみると、aac ファイルの URL が記述されているようでした。

プレイリスト 2 の URL に chunklist とあるように、5 秒ごとのファイルに分割されたものの集合になっているようです。

$ curl 'https://radiko.jp/v2/api/ts/chunklist/JZTdYq-2.m3u8' \
> -XGET \
> -H 'Referer: http://radiko.jp/' \
> -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30'
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:NO
#EXT-X-TARGETDURATION:5
#EXT-X-MEDIA-SEQUENCE:1
#EXT-X-PROGRAM-DATE-TIME:2017-04-23T22:30:00+09:00
#EXTINF:5,
http://media.radiko.jp/sound/b/LFR/20170423/20170423_223000_AxevQ.aac
#EXT-X-PROGRAM-DATE-TIME:2017-04-23T22:30:05+09:00
#EXTINF:5,
http://media.radiko.jp/sound/b/LFR/20170423/20170423_223005_Lggb3.aac

# ・・・略・・・

#EXT-X-PROGRAM-DATE-TIME:2017-04-23T22:59:50+09:00
#EXTINF:5,
http://media.radiko.jp/sound/b/LFR/20170423/20170423_225950_mcvZi.aac
#EXT-X-PROGRAM-DATE-TIME:2017-04-23T22:59:55+09:00
#EXTINF:5,
http://media.radiko.jp/sound/b/LFR/20170423/20170423_225955_Rt10X.aac

#EXT-X-ENDLIST

2.3. 録音(保存、ダウンロード)

ffmpeg コマンドを使うと、プレイリスト 1 を入力に指定することで、録音(保存、ダウンロード)ができるようです。

ヘッダーに X-Radiko-AuthToken{1} をつける必要があるようです。

$ ffmpeg \
> -content_type 'application/x-www-form-urlencoded' \
> -headers 'Referer: http://radiko.jp/' \
> -headers 'Pragma: no-cache' \
> -headers 'X-Radiko-AuthToken: wpU-C0IvwTPf_magS8T7gA' \
> -user_agent 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30' \
> -method 'POST' \
> -i 'https://radiko.jp/v2/api/ts/playlist.m3u8?station_id=LFR&l=15&ft=20170423223000&to=20170423230000' \
> -c copy \
> out.aac
ffmpeg version 3.3 Copyright (c) 2000-2017 the FFmpeg developers
  built with Apple LLVM version 8.1.0 (clang-802.0.41)
  configuration: --prefix=/usr/local/Cellar/ffmpeg/3.3 --enable-shared --enable-pthreads --enable-gpl --enable-version3 --enable-hardcoded-tables --enable-avresample --cc=clang --host-cflags= --host-ldflags= --enable-libmp3lame --enable-libx264 --enable-libxvid --enable-opencl --disable-lzma --enable-vda
  libavutil      55. 58.100 / 55. 58.100
  libavcodec     57. 89.100 / 57. 89.100
  libavformat    57. 71.100 / 57. 71.100
  libavdevice    57.  6.100 / 57.  6.100
  libavfilter     6. 82.100 /  6. 82.100
  libavresample   3.  5.  0 /  3.  5.  0
  libswscale      4.  6.100 /  4.  6.100
  libswresample   2.  7.100 /  2.  7.100
  libpostproc    54.  5.100 / 54.  5.100
[https @ 0x7fa3f4504080] No trailing CRLF found in HTTP header.
Input #0, hls,applehttp, from 'https://radiko.jp/v2/api/ts/playlist.m3u8?station_id=LFR&l=15&ft=20170423223000&to=20170423230000':
  Duration: 00:30:00.00, start: 23567.910400, bitrate: N/A
  Program 0
    Metadata:
      variant_bitrate : 52973
    Stream #0:0: Audio: aac (HE-AAC), 48000 Hz, stereo, fltp, 46 kb/s
    Metadata:
      variant_bitrate : 52973
Output #0, adts, to 'out.aac':
  Metadata:
    encoder         : Lavf57.71.100
    Stream #0:0: Audio: aac (HE-AAC), 48000 Hz, stereo, fltp, 46 kb/s
    Metadata:
      variant_bitrate : 52973
Stream mapping:
  Stream #0:0 -> #0:0 (copy)
Press [q] to stop, [?] for help
size=   10546kB time=00:29:59.86 bitrate=  48.0kbits/s speed=  17x    
video:0kB audio:10546kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.000000%

ヘッダーの最後の改行が見つからない警告が出まして、

[https @ 0x7fa3f4504080] No trailing CRLF found in HTTP header.

#3268 (CRLF problem with custom headers when playing hls streams on windows) – FFmpeg と同じことかと思いまして、明示的に改行をつけてみたのですが、エラーになってしまうので放置しました。

それから、flash=1 のデータを送信したかったのですが、post_data の使い方がわからなくて、Error setting option post_data to value flash=1. のエラーになってしまうので、送信しないことにしました。 FFmpeg のドキュメントにコマンドのサンプルがなかったので、わかりませんでした。

3.11 http

post_data
Set custom HTTP post data.

FFmpeg Protocols Documentation

それでも録音(保存、ダウンロード)したファイルには影響はなく、大丈夫だと思うのですけど。

終わり

認証を済ませておけば ffmpeg コマンド 1 つで録音(保存、ダウンロード)できるようでした。 ただ、ある程度時間が経過すると認証の期限的なものが切れるようですので、改めて認証しなければいけなそうでした。

参考