rtmpdumpでradikoにアクセスする手順

rec_radiko.shをさらに自分好みに修正したい。そのためにはスクリプトの中で何をしているのか?ちゃんと調べておく必要がある。すでに以下のページで詳しい解説が行われているが、それを正確に理解するために自分自身でスクリプトの手順を1つずつ実行して体感してみるのだ。

(1)事前準備

  • まずは一連の作業のため~/Downloads/radiko_testフォルダを作って、そこで作業することにした。
$ mkdir ~/Downloads/radiko_test
$ cd ~/Downloads/radiko_test

(2)player.swfをダウンロード

$ /usr/local/bin/wget -q -O ./player.swf http://radiko.jp/player/swf/player_3.0.0.01.swf
$ ls
player.swf

(3)authkey.jpgを取り出す

  • (2)でダウンロードしたplayer.swfの中から、authkey.jpgを取り出す。
$ /usr/local/bin/swfextract -b 14 player.swf -o authkey.jpg
$ ls
authkey.jpg  player.swf

(4)auth1_fmsをダウンロード

  • auth1_fmsをダウンロードする
$ /usr/local/bin/wget -q \
       --header="pragma: no-cache" \
       --header="X-Radiko-App: pc_1" \
       --header="X-Radiko-App-Version: 2.0.1" \
       --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
$ ls
auth1_fms    authkey.jpg  player.swf
  • ダウンロードしたauth1_fmsの内容はこんな感じ。
$ cat auth1_fms
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 24 Jan 2013 01:32:37 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive
X-Radiko-AppType: pc
X-RADIKO-AUTHTOKEN: 2IjGnHwXqhjyXXM9OJK4Cg
X-Radiko-AuthWait: 0
X-Radiko-Delay: 0
X-Radiko-KeyLength: 16
X-Radiko-KeyOffset: 114930

X-Radiko-AppType=pc
X-RADIKO-AUTHTOKEN=2IjGnHwXqhjyXXM9OJK4Cg
X-Radiko-AuthWait=0
X-Radiko-Delay=0
X-Radiko-KeyLength=16
X-Radiko-KeyOffset=114930

(5)authtoken, offset, lengthを取り出す

  • (4)でダウンロードしたauth1_fmsから、authtoken, offset, lengthを取り出す。
$ authtoken=`perl -ne 'print $1 if(/x-radiko-authtoken: ([\w-]+)/i)' auth1_fms`
$ offset=`perl -ne 'print $1 if(/x-radiko-keyoffset: (\d+)/i)' auth1_fms`
$ length=`perl -ne 'print $1 if(/x-radiko-keylength: (\d+)/i)' auth1_fms`

$ echo $authtoken $offset $length
2IjGnHwCqhjyRRM9OJK4Cg 114930 16

(6)base64エンコードしたpartialkeyを取得

  • authkey.jpgの先頭から114930バイトをスキップして、16バイトを取り出し、そのデータをbase64エンコードする。
$ partialkey=`dd if=authkey.jpg bs=1 skip=${offset} count=${length} 2> /dev/null | base64`
$ echo $partialkey
gyXn94HLdhxyXe2T1p3lxA==

(7)auth2_fmsをダウンロード

  • (5)のauthtoken、(6)のpartialkeyをヘッダーに添付して、auth2_fmsをダウンロードする。
$ /usr/local/bin/wget -q \
        --header="pragma: no-cache" \
        --header="X-Radiko-App: pc_1" \
        --header="X-Radiko-App-Version: 2.0.1" \
        --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
$ ls
auth1_fms    auth2_fms    authkey.jpg  player.swf
  • ダウンロードしたauth2_fmsの内容はこんな感じ。
$ cat auth2_fms


JP13,東京都,tokyo Japan

(8)stream_urlを取得

$ channel_xml=`/usr/local/bin/wget -q "http://radiko.jp/v2/station/stream/FMJ.xml" -O -`
$ /usr/local/bin/wget -q "http://radiko.jp/v2/station/stream/FMJ.xml" -O -
<?xml version="1.0" encoding="utf8" ?>

  rtmpe://w-radiko.smartstream.ne.jp/FMJ/_definst_/simul-stream.stream
  rtmpe://w-radiko.smartstream.ne.jp/FMJ/_definst_/simul-stream.stream

  • xmlの中からurlを取得する。
$ stream_url=`echo $channel_xml | sed 's/^\(\)/\1UTF-8\2/' | xpath "//url/item[1]/text()" 2>/dev/null`
$ echo $stream_url
rtmpe://w-radiko.smartstream.ne.jp/FMJ/_definst_/simul-stream.stream
  • urlを3つのパーツに分割する。($stream_url_partsは配列になっている)
$ stream_url_parts=(`echo ${stream_url} | perl -pe 's!^(.*)://(.*?)/(.*)/(.*?)$/!$1://$2 $3 $4!'`)
$ echo ${stream_url_parts[@]}
rtmpe://w-radiko.smartstream.ne.jp FMJ/_definst_ simul-stream.stream

(9)rtmpdumpでダウンロード

  • (5)のauthtoken、(8)のstream_urlを利用して、rtmpdumpでradikoにアクセスすることで、ようやく録音できた!
$ /usr/local/bin/rtmpdump -v \
            -r ${stream_url_parts[0]} \
            --app ${stream_url_parts[1]} \
            --playpath ${stream_url_parts[2]} \
            -W http://radiko.jp/player/swf/player_3.0.0.01.swf\
            -C S:"" -C S:"" -C S:"" -C S:$authtoken \
            --live \
            --stop 30 \
            --flv "J-WAVE.flv"
RTMPDump 2.4
(c) 2010 Andrej Stepanchuk, Howard Chu, The Flvstreamer Team; license: GPL
WARNING: No application or playpath in URL!
Connecting ...
WARNING: Trying different position for server digest!
INFO: Connected...
Starting Live Stream
For duration: 30.000 sec
INFO: Metadata:
INFO:   StreamTitle           
182.783 kB / 30.04 sec
Download complete

$ ls
J-WAVE.flv   auth1_fms    auth2_fms    authkey.jpg  player.swf


長い道のりであった...。これでやっとJ-WAVE.flvというファイルになって、30秒間の録音ができた!

rec_radiko.shの改良

  • 以上、rec_radiko.shを体感してみて、その処理は認証と録音の2つに大別できると感じた。
  • そして一度認証されれば認証状態はしばらく継続するようだ。
  • そこで、rtmpdumpでアクセスしてエラーが発生した時だけ認証する方式に変更してみた。
    • 週数回の予約録音目的では、ほぼ毎回アクセスエラーが発生して認証が必要になるのでやめた。
  • -aオプションを追加して、エリア情報を取得できるようにしてみた。
  • -oオプションを拡張して、ディレクトリ名とファイル名を変更する自由度を高めてみた。
  • flv → m4a変換(無劣化)も追加
#!/bin/sh
# Original1: https://gist.github.com/875864 saiten / rec_radiko.sh
# Original2: http://backslash.ddo.jp/wordpress/archives/1020 http://backslash.ddo.jp/tools/rec_radiko2.txt 

# Install: wget swftools rtmpdump ffmpeg http://d.hatena.ne.jp/zariganitosh/20130120/radiko_recoding_again

# 使い方
show_usage() {
  echo "Usage: $COMMAND [-a] [-o output_path] [-t recording_seconds] station_ID" 1>&2
  echo '       -a  Output area info(ex. 'JP13,東京都,tokyo Japan'). No recording.' 1>&2
  echo '       -o  Default output_path = $HOME/Downloads/${station_name}_`date +%Y%m%d-%H%M`.flv' 1>&2
  echo '             a/b/c/ = $HOME/Downloads/a/b/c/J-WAVE_20130123-1700.flv' 1>&2
  echo '             a/b/c  = $HOME/Downloads/a/b/c.flv' 1>&2
  echo '            /a/b/c/ = /a/b/c/J-WAVE_20130123-1700.flv' 1>&2
  echo '            /a/b/c  = /a/b/c.flv' 1>&2
  echo '           ./a/b/c/ = ./a/b/c/J-WAVE_20130123-1700.flv' 1>&2
  echo '           ./a/b/c  = ./a/b/c.flv' 1>&2
  echo '       -t  Default recording_seconds = 30' 1>&2
  echo '           60 = 1 minute, 3600 = 1 hour, 0 = go on recording until stopped(control-C)' 1>&2
}

# 認証
radiko_authorize() {
  echo "==== authorize ===="
  #
  # get player
  #
  if [ ! -f $playerfile ]; then
    echo $playerfile downloading...
    /usr/local/bin/wget -q -O $playerfile $playerurl

    if [ $? -ne 0 ]; then
      echo "failed get player"
      exit 1
    fi
  fi

  #
  # get keydata (need swftool)
  #
  if [ ! -f $keyfile ]; then
    echo $keyfile extracting...
    # /usr/local/bin/swfextract -b 5 $playerfile -o $keyfile <---radiko仕様変更点
    /usr/local/bin/swfextract -b 14 $playerfile -o $keyfile

    if [ ! -f $keyfile ]; then
      echo "failed get keydata"
      exit 1
    fi
  fi

  #
  # access auth1_fms
  #
  rm -f auth1_fms
  
  /usr/local/bin/wget -q \
       --header="pragma: no-cache" \
       --header="X-Radiko-App: pc_1" \
       --header="X-Radiko-App-Version: 2.0.1" \
       --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

  if [ $? -ne 0 ]; then
    echo "failed auth1 process"
    exit 1
  fi

  #
  # get partial key
  #
  authtoken=`perl -ne 'print $1 if(/x-radiko-authtoken: ([\w-]+)/i)' auth1_fms`
  offset=`perl -ne 'print $1 if(/x-radiko-keyoffset: (\d+)/i)' auth1_fms`
  length=`perl -ne 'print $1 if(/x-radiko-keylength: (\d+)/i)' auth1_fms`
  partialkey=`dd if=$keyfile bs=1 skip=${offset} count=${length} 2> /dev/null | base64`

  echo "authtoken: ${authtoken}\n offset: ${offset}\n length: ${length}\n partialkey: $partialkey"

  #
  # access auth2_fms
  #
  rm -f auth2_fms
  
  /usr/local/bin/wget -q \
       --header="pragma: no-cache" \
       --header="X-Radiko-App: pc_1" \
       --header="X-Radiko-App-Version: 2.0.1" \
       --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

  if [ $? -ne 0 -o ! -f auth2_fms ]; then
    echo "failed auth2 process"
    exit 1
  fi

  echo "authentication success"

  areaid=`perl -ne 'print $1 if(/^([^,]+),/i)' auth2_fms`
  echo "areaid: $areaid"
}

# 録音
radiko_record() {
  echo "==== recording ===="
  #
  # get stream-url
  #
  channel_xml=`/usr/local/bin/wget -q "http://radiko.jp/v2/station/stream/${channel}.xml" -O -`
  stream_url=`echo $channel_xml | sed 's/^\(<?xml .*\)[Uu][Tt][Ff]8\(.* ?>\)/\1UTF-8\2/' | xpath "//url/item[1]/text()" 2>/dev/null`
  stream_url_parts=(`echo ${stream_url} | perl -pe 's!^(.*)://(.*?)/(.*)/(.*?)$/!$1://$2 $3 $4!'`)

  #
  # get authtoken
  #
  authtoken=`perl -ne 'print $1 if(/x-radiko-authtoken: ([\w-]+)/i)' auth1_fms`

  #
  # /usr/local/bin/rtmpdump
  #
  echo "save as '$output'"
  /usr/local/bin/rtmpdump -v \
           -r ${stream_url_parts[0]} \
           --app ${stream_url_parts[1]} \
           --playpath ${stream_url_parts[2]} \
           -W $playerurl \
           -C S:"" -C S:"" -C S:"" -C S:$authtoken \
           --live \
           --stop "${rectime:=30}" \
           --flv "${output}"
}

# 引数解析
COMMAND=`basename $0`
while getopts aho:t: OPTION
do
  case $OPTION in
    a ) OPTION_a="TRUE" ;;
    o ) OPTION_o="TRUE" ; VALUE_o="$OPTARG" ;;
    t ) OPTION_t="TRUE" ; VALUE_t="$OPTARG" ;;
    * ) show_usage ; exit 1 ;;
  esac
done

shift $(($OPTIND - 1)) #残りの非オプションな引数のみが、$@に設定される

if [ $# = 0 -a "$OPTION_a" != "TRUE" ]; then
  show_usage ; exit 1
fi

# オプション処理
channel=$1

if [ "$OPTION_o" = "TRUE" ]; then
  if echo $VALUE_o|grep -q -v -e '^./\|^/'; then
    mkdir -p $HOME/Downloads ; cd $HOME/Downloads
  fi
  fname_ext="${VALUE_o##*/}"
  fname="${fname_ext%.*}"
  fext="${fname_ext#$fname}"
  wdir="${VALUE_o%/*}"; [ "$fname_ext" = "$wdir" ] && wdir=""
fi

if [ "$OPTION_t" = "TRUE" ]; then
  rectime=$VALUE_t
fi

mkdir -p ${wdir:=$HOME/Downloads} ; cd $wdir ; wdir=`pwd`

station_name=`curl -s http://radiko.jp/v2/api/program/station/today?station_id=$channel|xpath "//station/name/text()" 2>/dev/null`
output="${wdir}/${fname:=${station_name}_`date +%Y%m%d-%H%M`}${fext:=.flv}"

# playerurl=http://radiko.jp/player/swf/player_2.0.1.00.swf <---radiko仕様変更点
playerurl=http://radiko.jp/player/swf/player_3.0.0.01.swf
playerfile=./player.swf
keyfile=./authkey.jpg

if [ "$OPTION_a" = "TRUE" ]; then
  radiko_authorize && cat auth2_fms|grep -e '^\w\+'
else
  radiko_authorize && radiko_record
fi

ffmpeg -v quiet -y -i "${output}" -acodec copy "${output%.*}.m4a"
rm -f "${output}"