radikoの録音・再生(ArchLinux)

Arch Linux にラジコの録音・再生環境を構築する。

準備

必要なパッケージ(rtmpdump、ffmpeg、mplayer)をインストールする。

sudo pacman -S rtmpdump ffmpeg mplayer

mplayer の設定

デフォルトでUSBスピーカーから音を出す

sudo vi /etc/mplayer/mplayer.conf
ao=alsa:device=hw=1.0

SWFTOOLS のソース取得・コンパイル

あと SWFTOOLS に含まれる swfextract も必要なんだけど、ODROID-U2 や Raspberry Pi などの ARM CPU 向けにはパッケージが公開されていないので、自前でコンパイルする。SWFTOOLS のコンパイルに必要なパッケージ(pdflib、zip、fftw、zziplib、ruby)をインストールする。コンパイルするには開発環境も必要なので、インストールされていなければbase-develもインストールする。

sudo pacman -S pdflib zip fftw zziplib ruby
sudo pacman -S --needed base-devel

SWFTOOLS をダウンロードして解凍する。

wget http://www.swftools.org/swftools-2013-04-09-1007.tar.gz
tar xvf swftools-2013-04-09-1007.tar.gz

そのままmakeするとgif2swfというモジュールでエラー中断してしまう。。。でもradiko再生に必要なswfextractの作成は出来ているので「気にしない/気にならない/gif2swfが必要ない」なら、そのまま続けてmake installしてもOK。

「エラー中断が嫌/gif2swfが必要」なら中断されない程度にソースをいじる。(エラー処理用のロジックなので特に深く考えずに手を入れる)

vi swftools-2013-04-09-1007/src/gif2swf.c
修正1
【修正前】
233      if ((gft = DGifOpenFileName(sname)) == NULL) {
234          fprintf(stderr, "%s is not a GIF file!\n", sname);
235          return t;
236      }
【修正後】
add      int *err_cd; //行追加
233      if ((gft = DGifOpenFileName(sname, err_cd)) == NULL) { //err_cdを追加
234          fprintf(stderr, "%s is not a GIF file!\n", sname);
235          return t;
236      }

修正2
【修正前】
492      if ((gft = DGifOpenFileName(s)) == NULL) {
493          fprintf(stderr, "%s is not a GIF file!\n", fname);
494          return -1;
495      }
【修正後】
add      int *err_cd; //行追加
492      if ((gft = DGifOpenFileName(s, err_cd)) == NULL) { //err_cdを追加
493          fprintf(stderr, "%s is not a GIF file!\n", fname);
494          return -1;
495      }

修正3
関数の追加(ファイルの一番下に追記)
706  void PrintGifError(void)
707  {
708      int err_cd;
709      char *Err = GifErrorString(err_cd);
710      fprintf(stderr, "\nGIF-LIB error: %s.\n", Err);
711  }

SWFTOOLS の make、install

cd swftools-*
sudo ./configure
sudo make
sudo make install

radiko_rec.sh を元に radiko.sh を作成

ラジコ録音用のシェルスクリプト(radiko_rec.sh)がいくつか公開されているので、自分用にカスタマイズ。

とりあえず使い方。(シェルスクリプトは下の方に記載してます)

録音したいとき(AACで出力(拡張子.m4a)で、録音中は音が出ない)

radiko.sh -d 出力先 -f ファイル名 -t 録音時間 -s 00:01:00 CH

-d:出力先のパス。省略可。デフォルトは実行ユーザのホームディレクトリ。
-f:ファイル名(日本語名もOK)。省略可。デフォルトは「チャンネル名_年月日_時分_プロセスID」。
-t:録音時間(分で指定)。省略可。デフォルトは1分。
   0を指定すると無制限。Ctrl+C等で中断できる。
-s:ffmpegでデコードする際の開始位置を指定。省略可。デフォルトは開始位置調整なし。
   指定方法は「00:00:00」(時:分:秒)
   録音開始時間に余裕を持たせて起動した際に、その余裕分をカットして保存したい場合に指定する
   例えば放送開始1分前に起動させて -s 00:01:00 と指定すると、最初の1分間をカットして保存する
CH:チャンネル名。必須。ラジコのチャンネル名。

例)radiko.sh -d music -f 大竹まことのゴールデンラジオ -t 151 -s 00:01:00 QRR
   出力先=~/music
   ファイル名=大竹まことのゴールデンラジオ
   録音時間=151分
   最初の1分間は保存しない
   チャンネル=文化放送

再生したいとき(録音されない)

radiko.sh -p -t 再生時間 CH

-p:録音でなく、再生する場合に指定する。値は無し。
-t:再生時間(分で指定)。省略可。デフォルトは0(無制限)。
  再生を止めるには再生時間満了まで待つか、Ctrl+Cで中断。
CH:チャンネル名。必須。ラジコのチャンネル名。

例)radiko.sh -p -t 120 TBS
  再生時間=120分
  チャンネル=TBS

例)radiko.sh -p YFM
  チャンネル=FMヨコハマ
  Ctrl+C等で中断するまで再生

radiko.sh の作成
【2017/02/08 追記】以下radiko.shの内容は古いので、こちらのページを参考にしてください

vi radiko.sh
#!/bin/bash

pid=$$
wkdir='/var/tmp'
playerurl=http://radiko.jp/player/swf/player_3.0.0.01.swf
playerfile="${wkdir}/player.swf"
keyfile="${wkdir}/authkey.png"
auth1_fms="${wkdir}/auth1_fms_${pid}"
auth2_fms="${wkdir}/auth2_fms_${pid}"
date=`date +%Y%m%d_%H%M`
stream_url=""
url_parts=""

# Usage
show_usage() {
	echo 'Usage:'
	echo ' RECORD MODE' 1>&2
	echo "   `basename $0` [-d out_dir] [-f file_name]" 1>&2
	echo '          [-t rec_minute] [-s Starting_position] channel' 1>&2
	echo '           -d  Default out_dir = $HOME' 1>&2
	echo '                  a/b/c = $HOME/a/b/c' 1>&2
	echo '                 /a/b/c = /a/b/c' 1>&2
	echo '                ./a/b/c = $PWD/a/b/c' 1>&2
	echo '           -f  Default file_name = channel_YYYYMMDD_HHMM_PID' 1>&2
	echo '           -t  Default rec_minute = 1' 1>&2
	echo '               60 = 1 hour, 0 = go on recording until stopped(control-C)' 1>&2
	echo '           -s  Default starting_position = 00:00:00' 1>&2
	echo ' PLAY MODE' 1>&2
	echo "   `basename $0` -p [-t play_minute] channel" 1>&2
	echo '           -p  Plya mode. No recording.' 1>&2
	echo '           -t  Default play_minute = 0' 1>&2
	echo '               60 = 1 hour, 0 = go on recording until stopped(control-C)' 1>&2
}

# authorize
authorize() {
	#
	# get player
	#
	if [ ! -f ${playerfile} ]; then
		wget -q -O ${playerfile} ${playerurl}

		if [ $? -ne 0 ]; then
			echo "[stop] failed get player (${playerfile})" 1>&2 ; exit 1
		fi
	fi

	#
	# get keydata (need swftool)
	#
	if [ ! -f ${keyfile} ]; then
		swfextract -b 14 ${playerfile} -o ${keyfile}

		if [ ! -f ${keyfile} ]; then
			echo "[stop] failed get keydata (${keyfile})" 1>&2 ; exit 1
		fi
	fi

	#
	# access auth1_fms
	#
	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 \
		-O ${auth1_fms} https://radiko.jp/v2/api/auth1_fms

	if [ $? -ne 0 ]; then
		echo "[stop] failed auth1 process (${auth1_fms})" 1>&2 ; 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} 1>&2
	#echo "offset: ${offset} 1>&2
	#echo "length: ${length} 1>&2
	#echo "partialkey: ${partialkey}" 1>&2

	rm -f ${auth1_fms}

	#
	# access auth2_fms
	#
	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 \
		-O ${auth2_fms} \
		https://radiko.jp/v2/api/auth2_fms

	if [ $? -ne 0 -o ! -f ${auth2_fms} ]; then
		echo "[stop] failed auth2 process (${auth2_fms})" 1>&2 ; exit 1
	fi

	#echo "authentication success" 1>&2

	areaid=`perl -ne 'print $1 if(/^([^,]+),/i)' ${auth2_fms}`
	#echo "areaid: ${areaid}" 1>&2
	rm -f ${auth2_fms}

	#
	# get stream-url
	#
	wget -q -O ${ch_xml} \
		"http://radiko.jp/v2/station/stream/${channel}.xml"

	if [ $? -ne 0 -o ! -f ${ch_xml} ]; then
        echo "[stop] failed stream-url process (channel=${channel})"
		rm -f ${ch_xml} ; show_usage ; exit 1
    fi

	stream_url=`echo "cat /url/item[1]/text()" | \
			xmllint --shell ${ch_xml} | tail -2 | head -1`
	url_parts=(`echo ${stream_url} | \
			perl -pe 's!^(.*)://(.*?)/(.*)/(.*?)$/!$1://$2 $3 $4!'`)
	rm -f ${ch_xml}

}

# Record
record() {
	# rtmpdump
	rtmpdump -r ${url_parts[0]} \
		--app ${url_parts[1]} \
		--playpath ${url_parts[2]} \
		-W ${playerurl} \
		-C S:"" -C S:"" -C S:"" -C S:${authtoken} \
		--live \
		--stop ${duration} \
		--flv "${wkdir}/${tempname}.flv"

	ffmpeg -ss ${starting} -i "${wkdir}/${tempname}.flv" \
		-acodec copy "${wkdir}/${tempname}.m4a" && \
		rm -f "${wkdir}/${tempname}.flv"

	mv -b "${wkdir}/${tempname}.m4a" "${outdir}/${filename}.m4a"

	if [ $? -ne 0 ]; then
		echo "[stop] failed move file (${wkdir}/${tempname}.m4a to \
			${outdir}/${filename}.m4a)" 1>&2 ; exit 1
	fi
}

# Play
play() {
	# rtmpdump
	rtmpdump -r ${url_parts[0]} \
		--app ${url_parts[1]} \
		--playpath ${url_parts[2]} \
		-W $playerurl \
		-C S:"" -C S:"" -C S:"" -C S:$authtoken \
		--live \
		--stop ${duration} | \
		mplayer -
}

# debug
debug() {
	echo "-p : ${OPTION_p}"
	echo "-d : ${OPTION_d}	value: \"${VALUE_d}\""
	echo "-f : ${OPTION_f}	value: \"${VALUE_f}\""
	echo "-t : ${OPTION_t}	value: \"${VALUE_t}\""
	echo "-s : ${OPTION_s}	value: \"${VALUE_s}\""
	echo ''
	echo "channel : \"${channel}\""
	echo "outdir  : \"${outdir}\""
	echo "filename: \"${filename}\""
	echo "duration: \"${duration}\""
	echo "starting: \"${starting}\""
	echo ''
}

# Get Option
while getopts pd:f:t:s: OPTION
do
	case $OPTION in
		p ) OPTION_p=true
			;;
		d ) OPTION_d=true
			VALUE_d="$OPTARG"
			;;
		f ) OPTION_f=ture
			VALUE_f="$OPTARG"
			;;
		t ) OPTION_t=true
			VALUE_t="$OPTARG"
			if ! expr "${VALUE_t}" : '[0-9]*' > /dev/null ; then
				show_usage ; exit 1
			fi
			;;
		s ) OPTION_s=ture
			VALUE_s="$OPTARG"
			;;
		* ) show_usage ; exit 1 ;;
	esac
done

# Get Channel
shift $(($OPTIND - 1))
if [ $# -ne 1 ]; then
	show_usage ; exit 1
fi
channel=$1

ch_xml="${wkdir}/${channel}${pid}.xml"

#
# RECORD Mode
#
if [ ! "${OPTION_p}" ]; then
	# Get Directory
	if [ ! "$OPTION_d" ]; then
		cd ${HOME}
	else
		if echo ${VALUE_d}|grep -q -v -e '^./\|^/'; then
			mkdir -p "${HOME}/${VALUE_d}"
			if [ $? -ne 0 ]; then
				echo "[stop] failed make directory (${HOME}/${VALUE_d})" 1>&2 ; exit 1
			fi
			cd "${HOME}/${VALUE_d}"
		else
			mkdir -p ${VALUE_d}
			if [ $? -ne 0 ]; then
				echo "[stop] failed make directory (${VALUE_d})" 1>&2 ; exit 1
			fi
			cd ${VALUE_d}
		fi
	fi
	outdir=${PWD}

	# Get File Name
	filename=${VALUE_f:=${channel}_${date}_${pid}}
	tempname=${channel}_${pid}

	# Get Minute
	min=${VALUE_t:=1}
	duration=`expr ${min} \* 60`

	# Get Starting Position
	starting=${VALUE_s:='00:00:00'}

#	debug
	authorize && record

#
# PLAY Mode
#
else
	# Get Minute
	duration=`expr ${VALUE_t:=0} \* 60`

#	debug
	authorize && play

fi

実行権限を付ける

chmod +x radiko.sh

あとは上の方で書いた様に実行すればOK。

多重起動(同じ番組を録音モードと再生モードで2プロセス起動させるとか)も問題ないと思います。

「目覚ましラジコ」とか「深夜放送の録音」とか、指定時刻に自動実行したい場合はcronに登録。
※最近Archにはcronが入ってないのでsystemd/Timerでスケジューリングするか、cronを新規インストール

crontab -e

設定例

59 6 * * 1-5 amixer sset PCM 20% > /dev/null 2>&1
0 7 * * 1-5 ~/radiko.sh -p QRR > /dev/null 2>&1
28 6 * * 1-5 ~/radiko.sh -d music -f スタンバイ -t 122 -s 00:02:00 TBS > /dev/null 2>&1

書式は「分 時 日 月 曜日 コマンド」
1行目:radiko再生前に音量の設定(どのくらいの音量(%)で再生したいか事前にチェックしておく)
2行目:月曜〜金曜の7:00から文化放送を無期限で再生(目覚ましラジコ)
3行目:月曜〜金曜の6:28からTBSを122分間録音。ただし最初の2分を捨てて~/music/スタンバイ.m4aとして保存

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。