Radikoのタイムフリーで番組名を指定して録音してみる

番組名を指定して実行するだけで、前日から7日前までの番組表を取得して見つかったものを録音するバッチファイル radiko_keyword.zip を作ってみた。Windowsのタスク スケジューラで1週間ごとに実行すれば、指定した番組を毎週録音できると思う。バグがあったので修正(2016/12/17追記)。バグを修正しきれてなかったので再度修正(2017/01/01追記)。別のバグを見つけたので修正(2017/01/07追記)。
必要なファイルはwget.exeswfextract.exedd.exesed.exenkf.exeffmpeg.exeだけで、rtmpdump.exeは使わない。

使い方

radiko_download 番組名」で前日までの1週間の番組表に指定した番組名を含む番組があればすべて録音される。バッチファイルと同名のiniファイル(例:radiko_download.ini)がある場合は、番組名を1行ごとに複数指定することが出来る。
録音ファイル名は、放送開始日時-放送終了日時-放送局ID-番組名.m4a となっており、同名のファイルがある場合は上書きされる。放送局IDはRadiko側で定められているもので、例えばラジオ関西ならCRK文化放送ならQRR等になる。
尚、録音中には以下のようなメッセージが繰り返し表示されるが、特に問題では無い模様。

[http @ 05680260] Invalid cookie found, no value, path or domain specified
    Last message repeated 10 times

処理内容

  1. プレイヤーのswfファイルを http://radiko.jp/apps/js/flash/myplayer-release.swf からダウンロードする。
  2. プレイヤーのswfファイルの12番目のバイナリデータを認証キーを含むファイルとして抽出する。
  3. 認証トークンを含むファイルを https://radiko.jp/v2/api/auth1_fms からダウンロードする。この時、引数として「\r\n」を、ヘッダとして「pragma: no-cache」、「X-Radiko-App: pc_ts」、「X-Radiko-App-Version: 4.0.0」、「X-Radiko-User: test-stream」、「X-Radiko-Device: pc」を指定する。
  4. 認証トークンを含むファイルの「X-Radiko-AuthToken」から認証トークン、「X-Radiko-KeyLength」からキーの長さ、「X-Radiko-KeyOffset」からキーの位置を取得する。
  5. 認証キーを含むファイルのキーの位置からキーの長さ分を認証キーのファイルとして抽出する。
  6. 認証キーのファイルをBase64方式でエンコードして認証キーを取得する。
  7. エリアIDを含むファイルを https://radiko.jp/v2/api/auth2_fms からダウンロードして認証を完了させる。この時、引数として「\r\n」を、ヘッダとして「pragma: no-cache」、「X-Radiko-App: pc_ts」、「X-Radiko-App-Version: 4.0.0」、「X-Radiko-User: test-stream」、「X-Radiko-Device: pc」、「X-Radiko-Authtoken: 認証トークン」、「X-Radiko-Partialkey: 認証キー」を指定する。
  8. エリアIDを含むファイルの「,」までをエリアIDとして取得する。
  9. 現在日付が格納されている環境変数 DATEから年、月、日を取得する。月や日が1桁の場合は先頭に0が付く為、頭に1を加えたものから100を引くことで月や日の値を取得する。
  10. 何日前の計算を容易にするために、年、月、日を西暦1年1月1日を1日目とする日数に変換する。1月と2月を1年前の13月、14月と考えると、3月から5ヶ月ごとに31日、30日、31日、30日、31日となる153日間が、その中は2ヶ月ごとに31日、30日となる61日間が、その中は1ヶ月ごとに31日となる31日間が含まれており、うるう年は4年に1回だが、100年ごとにうるう年ではなく、400年ごとにやはりうるう年となるので、これを考慮して換算した後、1月始まりとなるように1年を引いて1月と2月の日数を加える。
  11. 現在の日数の7日前を、番組表を取得する日数として求める。
  12. 番組表を取得する日数を年、月、日に変換する。先ほどと同様に考慮すべきな400年ごとの日数の146097日、100年ごとの日数の36524日、4年ごとの日数の1461日で割るために日数は0から始まるように1を引いておき、最終的に求めた日に1を加えて調整する。
  13. 番組表のXMLファイルを http://radiko.jp/v3/program/date/年月日/エリアID.xml からダウンロードする。年月日は年2桁、月2桁、日2桁をくっつけたものを指定する。
  14. 番組表のXMLファイルの改行コードをCR、LFに、文字コードシフトJISに変換する。
  15. 続けて、「<station 」を含む行と、「<prog 」を含む行とその次の行(「<title>番組名</title>」を含む行を想定)を1つの行にまとめる。
  16. 続けて、行の半角カンマ「,」を全角カンマ「,」に置換、「<prog」から「 ft=」まで含めて行頭から削除、「 to=」を「,」に置換、「 ftl=」から「<title>」を「,」に置換、「</title>」から「<station id=」を「,」に置換、「>」から行末までを削除することで、CSV形式に変換する。
  17. 続けて、「&」を「&」のようにXMLファイル内で使われる特殊文字を変換する。
  18. 続けて、「*」を「*」のようにファイル名に使えない文字を全角に変換したものを、番組表のCSVファイルとして保存する。
  19. 番組表のCSVファイルから、引数で指定したキーワード(番組名の一部など)を検索して、キーワードを含む行を録画リストとして保存する。
  20. 同様にバッチファイルと同名のiniファイルの各行で指定したキーワードを検索して、キーワードを含む行を録画リストに追加する。
  21. 録画リストを1行ずつ読み込んで、放送開始日時、放送終了日時、番組名、放送局IDを取得する。
  22. プレイリストのm3u8ファイルを https://radiko.jp/v2/api/ts/playlist.m3u8?l=15&station_id=放送局ID&ft=放送開始日時&to=放送終了日時 からダウンロードする。この時、引数として「flash=1」、ヘッダとして「pragma: no-cache」、「Content-Type: application/x-www-form-urlencoded」、「X-Radiko-Authtoken: 認証トークン」、「Referer: http://radiko.jp/apps/js/flash/myplayer-release.swf」を指定する。
  23. プレイリストのm3u8ファイルの「http:」もしくは「https:」で始まるストリームURLを取得する。
  24. 番組のAACデータをストリームURLからダウンロードする。この時、ヘッダとして「X-Radiko-Authtoken: 認証トークン」を指定する。AACデータはAACコンテナやTSコンテナには格納できるが、MP4コンテナ(m4aファイル等)にはそのまま格納できないので、ストリーミングURLからのダウンロードにffmpegを使う場合は、無劣化で変換するために「-bsf aac_adtstoasc」オプションを指定している。
  25. 番組表を取得する日数を1日進めて、現在の日数でなければ、番組表を取得する日数を年、月、日に変換する処理に戻る。

尚、タイムフリーではなくリアルタイムに録音する場合は、認証が完了した後に、ストリームURLを含むファイルを http://radiko.jp/v2/station/stream/放送局ID.xml からダウンロードして「<item>」と「</item>」で囲まれたストリームURLを取得したら、rtmpdump.exeを使って、--live(もしくは-v)でライブストリームであることを指定、--rtmp(-r)でストリームURLを指定、--swfVfy(-W)でプレイヤーのswfファイルのURLを指定、--conn(-C)でAMFデータとして文字列を「S:文字列」として、""、""、""、認証トークンを指定、--stop(-B)で録音時間の秒数を指定、--flv(-o)で出力ファイル名を指定する。(例:「rtmpdump -v -r ストリームURL -W http://radiko.jp/apps/js/flash/myplayer-release.swf -C S:"" -C S:"" -C S:"" -C S:認証トークン --stop 録音秒数 --flv ファイル名.flv」)

バッチファイル内容

@ECHO OFF
ECHO ●認証キー取得
wget -O myplayer-release.swf http://radiko.jp/apps/js/flash/myplayer-release.swf
swfextract -b 12 myplayer-release.swf -o authkey.jpg
ECHO ●認証トークン取得
wget -O auth1_fms ^
	--header="pragma: no-cache" ^
	--header="X-Radiko-App: pc_ts" ^
	--header="X-Radiko-App-Version: 4.0.0" ^
	--header="X-Radiko-User: test-stream" ^
	--header="X-Radiko-Device: pc" ^
	--post-data='\r\n' ^
	--no-check-certificate ^
	--save-headers ^
	https://radiko.jp/v2/api/auth1_fms
FOR /F "usebackq tokens=1,2 delims=: " %%I IN (auth1_fms) DO (
	IF "%%~I"=="X-Radiko-AuthToken" SET AUTHTOKEN=%%~J
	IF "%%~I"=="X-Radiko-KeyLength" SET KEYLENGTH=%%~J
	IF "%%~I"=="X-Radiko-KeyOffset" SET KEYOFFSET=%%~J
)
ECHO ●認証キー抽出
dd if=authkey.jpg bs=1 skip=%KEYOFFSET% count=%KEYLENGTH% of=partialkey.bin
certutil -f -encode partialkey.bin partialkey.txt
FOR /F "usebackq eol=- delims=" %%I IN (partialkey.txt) DO SET PARTIALKEY=%%~I
ECHO ●認証処理
wget -O auth2_fms ^
	--header="pragma: no-cache" ^
	--header="X-Radiko-App: pc_ts" ^
	--header="X-Radiko-App-Version: 4.0.0" ^
	--header="X-Radiko-User: test-stream" ^
	--header="X-Radiko-Device: pc" ^
	--header="X-Radiko-Authtoken: %AUTHTOKEN%" ^
	--header="X-Radiko-Partialkey: %PARTIALKEY%" ^
	--post-data='\r\n' ^
	--no-check-certificate ^
	https://radiko.jp/v2/api/auth2_fms
ECHO ●エリアID取得
FOR /F "usebackq tokens=1 delims=," %%I IN (auth2_fms) DO SET AREA_ID=%%~I
ECHO ●現在日付の日数を取得
SET NOWDATE="%DATE%"
SET /A YY=1%NOWDATE:~1,4%-10000
SET /A MM=1%NOWDATE:~6,2%-100
SET /A DD=1%NOWDATE:~9,2%-100
IF %MM% LSS 3 (
	SET /A YY=%YY%-1
	SET /A MM=%MM%+12
)
SET /A NOWDAYS=%YY%*365+%YY%/4-%YY%/100+%YY%/400+(%MM%-3)/5*153+(%MM%-3)%%5/2*61+(%MM%-3)%%5%%2*31+%DD%-306
ECHO ●1週間前の日数を取得
SET /A EPGDAYS=%NOWDAYS%-7
:LOOP
ECHO ●日数を年、月、日に変換
SET /A Y400Q=(%EPGDAYS%+305)/146097
SET /A Y400R=(%EPGDAYS%+305)%%146097
SET /A Y100Q=%Y400R%/36524
SET /A Y100R=%Y400R%%%36524
SET /A Y4Q=%Y100R%/1461
SET /A Y4R=%Y100R%%%1461
SET /A Y1Q=%Y4R%/365
SET /A Y1R=%Y4R%%%365
SET /A D5R=(%Y1R%+%Y100Q%/4*365+%Y1Q%/4*365)/153
SET /A D5Q=(%Y1R%+%Y100Q%/4*365+%Y1Q%/4*365)%%153
SET /A D2R=%D5Q%/61
SET /A D2Q=%D5Q%%%61
SET /A D1R=%D2Q%/31
SET /A D1Q=%D2Q%%%31
SET /A YY=%Y400Q%*400+%Y100Q%*100-%Y100Q%/4+%Y4Q%*4+%Y1Q%-%Y1Q%/4
SET /A MM=%D5R%*5+%D2R%*2+%D1R%+3
SET /A DD=%D1Q%+1
IF %MM% GTR 12 (
	SET /A YY=%YY%+1
	SET /A MM=%MM%-12
)
SET YY=0000%YY%
SET MM=00%MM%
SET DD=00%DD%
ECHO ●番組表取得
wget -O program.xml http://radiko.jp/v3/program/date/%YY:~-4%%MM:~-2%%DD:~-2%/%AREA_ID%.xml
ECHO ●CSV変換
nkf -Lw program.xml ^
	| sed -n -e "/<station /h;/<prog /{N;G;s/\n//gp}" ^
	| sed -e "s/,/,/g;s/[^<]*<prog[^>]* ft=//;s/ to=/,/;s/ ftl=[^>]*>[^<]*<title>/,/;s/<\/title>[^<]*<station id=/,/;s/>.*//" ^
	| sed -e "s/&amp;/&/g;s/&quot;/”/g;s/&lt/</g;s/&gt/>/g;s/&#39;/'/g" ^
	| sed -e "y/\\\/:*?<>|/¥/:*?<>|/" ^
	> program.csv
ECHO ●キーワード検索
FIND "%~1" < program.csv > program.txt
IF EXIST "%~dpn0.ini" (
	FOR /F "usebackq delims=" %%I IN ("%~dpn0.ini") DO (
		FIND "%%~I" < program.csv >> program.txt
	)
)
ECHO ●録音
FOR /F "usebackq tokens=1-4 delims=," %%I IN (program.txt) DO (
	wget -O playlist.m3u8 ^
		--header="pragma: no-cache" ^
		--header="Content-Type: application/x-www-form-urlencoded" ^
		--header="X-Radiko-Authtoken: %AUTHTOKEN%" ^
		--header="Referer: http://radiko.jp/apps/js/flash/myplayer-release.swf" ^
		--post-data='flash=1' ^
		--no-check-certificate ^
		"https://radiko.jp/v2/api/ts/playlist.m3u8?l=15&station_id=%%~L&ft=%%~I&to=%%~J"
	FOR /F "usebackq tokens=1,2 delims=:" %%M IN (playlist.m3u8) DO (
		IF "%%~M"=="https" (
			ffmpeg -headers "X-Radiko-Authtoken: %AUTHTOKEN%" -i %%~M:%%~N -vn -acodec copy -bsf aac_adtstoasc "%%~I-%%~J-%%~L-%%~K.m4a"
		)
		IF "%%~M"=="http" (
			ffmpeg -headers "X-Radiko-Authtoken: %AUTHTOKEN%" -i %%~M:%%~N -vn -acodec copy -bsf aac_adtstoasc "%%~I-%%~J-%%~L-%%~K.m4a"
		)
	)
)
ECHO ●日数を進めて現在日数で無ければ繰り返す
SET /A EPGDAYS=%EPGDAYS%+1
IF %EPGDAYS% NEQ %NOWDAYS% GOTO LOOP
EXIT /B