¥0から使える、本人認証サービス Value-Auth

radikoの再生や録音をツールで自動化

Last modified: 2020-12-11

radikoで聴けるラジオ局の番組の再生や録音をWebブラウザを用いずにツールで行う。Webブラウザでサイトを訪問して聴くこともできるが、(らじる★らじると同様)CPUに負荷がかかる上に自動化に向かないため、別の方法を探す。

現在の放送の再生に使えるツール (改造で録音も可)

https://gist.github.com/ji6czd/f86440200ba286f1f7af2e103dd430ff

にあるPythonスクリプトを保存して実行属性を付けて引数にラジオ局のID1を指定して実行すると、ffplayでその局のラジオを再生する。指定した時間経過後に自動的に終了する機能はないので、端末ではCtrl-cを押して止める必要があり、ある程度の時間再生した後で自動で終了させたい場合はkillallなどのプロセス制御コマンドを用いる。

58行目で文法エラーが出て動かない場合、57行目の波括弧がコメントアウトされているのが原因なので次の行に}を付ける。

Pythonのバージョン3.6以上の機能に依存している関係で、Raspberry Pi OS (当時は “Raspbian”) Stretch(“2019-04-09” が最終バージョン)で最初からインストールされているPythonを使用している場合は

url = f'http(中略).m3u8'

の行でエラーが出る。これは古い書き方にすることで動くようにできる。

url = 'ht(中略).jp/{}/(中略).m3u8'.format(argv[1])

Raspberry Pi OSがBuster(“2019-06-24” 以降)であればPythonが新しいため、そのままで動作する。

再生する時間の長さを指定できるようにする

ffplayの代わりに(個人的に使いやすい)mpvを使用し、かつ2番目の引数を再生時間(例:1:35:0で1時間35分の指定)として解釈するようにしてみる。

末尾付近でffplayを呼んでいる行の部分を

# Stretchではformat()を用いる
os.system( "mpv --really-quiet --no-cache --http-header-fields='X-Radiko-Authtoken: {}' --end='{}' '{}'".format(token, argv[2], m3u8) )

# Buster以降の場合ではこう書ける
os.system( f"mpv --really-quiet --no-cache --http-header-fields='X-Radiko-Authtoken: {token}' --end='{argv[2]}' '{m3u8}'" )

のどちらかにすると、指定された時間再生された後に自動的に終了するようになる(より自動化向きになる)。

録音ツールに改造する

ffmpegを録音に使用し

  • 2番目の引数に録音時間
  • 3番目の引数に保存先ディレクトリ(省略時は現在の作業ディレクトリ)

を指定する形としてみる。保存されるファイル名は “[録音開始日時]-[ラジオ局ID].aac” 形式で自動生成されるようにした(後述のツールのファイル名生成ルールに合わせている)。

import time
t = time.localtime()
filename = '{}{:02}{:02}{:02}{:02}{:02}-{}.aac'.format(
  t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, argv[1])
outdir = argv[3] if len(argv) == 4 else os.getcwd()
os.system( "ffmpeg -loglevel quiet -headers 'X-Radiko-Authtoken:{}' -i '{}' -acodec copy -t '{}' -y '{}'".format(token, m3u8, argv[2], os.path.join(outdir, filename)))

Raspberry Pi OS Buster以降では最後の行が下のように書ける。

# Buster以降の場合の最後の行
os.system( f"ffmpeg -loglevel quiet -headers 'X-Radiko-Authtoken:{token}' -i '{m3u8}' -acodec copy -t '{argv[2]}' -y '{os.path.join(outdir, filename)}'")

改造版radiko.pyを用いたcrontabの設定例

ここでは前述の2つの改造が適用されたスクリプトを用意して

  • 再生時間指定を追加した再生用スクリプト:play-radiko.py
  • ffmpegを用いた録音用スクリプト:rec-radiko.py

という形でファイル名を付けたものとする。

放送局は地域によって異なるため、ここでは全国放送となっている局を用いている。

# 月曜から金曜の6:20からラジオNIKKEI第1を1時間20分再生する
20 6 * * mon,tue,wed,thu,fri /path/to/play-radiko.py RN1 "1:20:0"

# 8月10-17日の6:00から放送大学を1時間35分録音して /dev/shm 内に保存
0 6 10-17 8 * /path/to/rec-radiko.py HOUSOU-DAIGAKU "1:35:0" /dev/shm

過去の放送(タイムフリー)の保存に使える録音ツール

radigoと呼ばれるコマンドラインツールが開発・公開されている。ライセンスはGPL-3。

このツールはradikoの現在または聴取可能期間内の過去の放送(タイムフリー)を保存することができる。再生に直接使うことはできない。

2019年夏時点では、GNU/Linuxでは現在の放送を10分以上保存しようとすると外部ツールが止まってしまい録音も停止してしまう。rtmpdumpからffmpegにパイプでデータを渡さないでflv形式のまま保存するようにいじると10分を超えた録音自体はできるが、一時ファイルを保存する必要が出てしまう。また、rtmpdumpを用いない再生ツール(改造で録音も可)は前述したものがあるため、このツールは過去の放送用に使うのがよい。

2020年11月末でサーバ側の変更により過去の放送の取得処理が動作しなくなったが、このソフトウェアが使用している外部プロジェクトgo-radikoのソースがこの変更に対応しており、そちらのソースを最新に更新してradigoを再ビルドすることで正しく動作する。なお、ffmpegのバージョンが古いと別の変更の影響で取得処理に失敗するようになっており、その場合はradigo側のソースは2019年時点のものを使用してgo-radikoのみ更新する。

使い方としては、オプションとして

  • ラジオ局のID(-id=[ID文字列]形式で、radigo areaを実行することで一覧が取得可能)
  • 現在の放送を録音する場合の録音時間(-t=[秒数])または過去の放送の開始日時(-s=[YYYYMMDDhhmmss]2)

の2つを指定して実行することで、指定したラジオ局の放送に対して

  • 現在の放送を録音する場合は録音を開始したタイミングから指定した秒数後まで
  • 過去の放送の場合は指定日時に開始した番組の “枠” の終了時刻まで3

が自動的に保存される。既定の保存形式はAACだが、MP3へのエンコードもできる。

作者によると、このツールは個人での(私的な)聴取目的以外には使用できない。

日付が変わった後の時刻指定は番組表(24-29時)とは異なり、翌日午前の実際の日付と時刻を指定する必要があるため、その時間帯の過去の放送を指定する場合は注意が必要。

同じ番組であっても “枠” が細かく分かれている場合があり、過去の放送の開始日時として最初の枠の開始時刻を指定しても、2つ目以降の枠は保存されない。これはradikoのサイト(放送局のサイトではない)で番組表を見て枠がどのように分かれているかを確認するしかない。

環境変数RADIGO_HOMEで保存先ディレクトリが実行時に指定できる。常に同じ保存先を指定するのであれば/etc/environmentRADIGO_HOME=/dev/shmのような形で記述すると再起動後に反映される。

ビルド時に必要なパッケージ

Go言語で書かれているため、Goコンパイラが必要で、Raspberry Pi OSではgolangパッケージが必要。

PC上のRaspberry Pi OS環境をQEMUで動かすと、このソフトウェアのビルドが正常に行えないことがある。その場合、実機でビルドする必要がある。幸いビルド時間は短めで、夏場でもSoC温度が大きく上昇する心配はない。

ビルド例

プロジェクトのページの説明だけを見てビルドを行おうとしたらGOPATHPATHといった環境変数で問題が起きたので、下のようにした。

[~]$ mkdir -p /dev/shm/gopath/bin
[~]$ mkdir /dev/shm/gopath/src
[~]$ cd /dev/shm/gopath/src
[/dev/shm/gopath/src]$ git clone https://github.com/yyoshiki41/radigo.git
[/dev/shm/gopath/src]$ export GOPATH=/dev/shm/gopath
[/dev/shm/gopath/src]$ export PATH=$PATH:$GOPATH/bin
[/dev/shm/gopath/src]$ cd radigo

以下はホームディレクトリの下にbinというディレクトリを作成してその中にインストールする場合の例。

[/dev/shm/gopath/src/radigo]$ make installdeps
[/dev/shm/gopath/src/radigo]$ make build
[/dev/shm/gopath/src/radigo]$ mkdir ~/bin
[/dev/shm/gopath/src/radigo]$ install -s radigo ~/bin
[/dev/shm/gopath/src/radigo]$ cd
[~]$ rm /dev/shm/gopath -fr

radigo本体はバージョン1.7以上でビルド可能とされているが、依存する別リポジトリのパッケージがGo 1.8から追加されたos.Executable()を使用している関係でRaspberry Pi OS (当時は “Raspbian”) StretchのGo 1.7ではビルドに失敗する。

vendor/github.com/posener/complete/cmd/install/install.go:102: undefined: os.Executable

この関数はビルドしてできた実行ファイルの絶対パスを得るためのものなので、例えば/home/pi/bin/に置いて実行するのであれば/dev/shm/gopath/src/radigo/vendor/github.com/posener/complete/cmd/install/install.gogetBinaryPath()

func getBinaryPath() (string, error) {
	return filepath.Abs("/home/pi/bin/radigo")
}

のようにいじる方法もある。ただ、実際にこの関数が呼ばれる部分がradigo本体から使われているかは怪しい。

実行時に必要なパッケージ

いずれもRaspberry Pi OSでは下のコマンド名と同名のパッケージとしてインストールできる。

  • ffmpeg
  • rtmpdump (現在の放送を録音する場合にのみ使われるが、古い方法なので廃止される可能性がある)

らじる★らじるの場合もそうだが、ダウンロードしたAACの音声ファイルをそのままエンコードせずに保存する場合はCPUの負荷が非常に低い。

処理が時々失敗する不具合とその対処

残念ながら、サーバとのやりとりの中でサーバ側の一時的な問題によってエラーが発生して保存に失敗することが稀にある。

下は過去の放送を保存しようとした際のエラーメッセージの例だが、いずれも再試行したところ正常に処理が完了している。

  • ERROR: Failed to get playlist.m3u8: expected element type <radiko> but have <html>
  • ERROR: Failed to get auth_token: Post https://radiko.jp/v2/api/auth1_fms: net/http: TLS handshake timeout

録音を確実に行うために、失敗した場合に少し待機した後で数回程度を上限として再試行するようなスクリプトを別途書いて実行することにした。

下のスクリプトを通して実行すると、エラーが出て失敗した場合にコード中のretry_countの回数まで再試行し、再試行の度にretry_waitの秒数待機する。このコードはLuaJITを用いているが、標準のLuaでも動作する。

ファイル名:radigo-wrapper.lua ライセンス:CC0
#! /usr/bin/luajit

-- CC0

do
    local retry_count = 5
    local retry_wait = 10

    local cmdline = ('"%s"'):format(arg[1])
    for i = 2, #arg do
        cmdline = cmdline .. ' ' .. arg[i]
    end

    local success = false
    local sleep = ('sleep %d'):format(retry_wait)
    for i = 1, retry_count do
        status = os.execute(cmdline)
        if status == 0 or status == true then  -- 0:Lua 5.1 true:Lua 5.2+
            success = true
            break
        end
        os.execute(sleep)
    end
    if not success then
        os.exit(false)
    end
end

radigoを用いたcrontabの設定例

radigoはラジオの再生には使用できず、現在の放送の録音にも前述した問題があるため、過去の放送の録音についてのみ設定例を下に示す。

# 日曜17:30からのラジオNIKKEI第1の放送を同日の23:45から保存
45 23 * * sun /path/to/radigo-wrapper.lua /path/to/radigo rec -id=RN1 -s=$(date "+\%Y\%m\%d")173000

# 月曜12:00からのラジオNIKKEI第2の放送を翌日である火曜の4:56から保存
56 4 * * tue /path/to/radigo-wrapper.lua /path/to/radigo rec -id=RN2 -s=$(date --date "1 day ago" "+\%Y\%m\%d")120000

このツールはリアルタイムに録音するよりも短い時間でデータをダウンロードできるが、これはWebブラウザでアクセスして聴くのと比べて大きなネットワーク負荷をradiko側に対してかけてしまうことにもなる。そのため、短時間に番組を大量にダウンロードするのは控えたほうがよい(対策される可能性もある)。月曜2-5時のような放送休止時間帯の局の多い時間帯(メンテナンスが入ることもあるので注意)またはその前後にしたり、自主的に帯域幅を絞ったりして配慮するのが望ましい。

dateコマンドの引数のパーセント記号にバックスラッシュを付けているが、これはcronの設定ファイルの中のパーセント記号が特殊な意味を持っているために必要なもの。

当日放送分でない番組の開始日時の日付部分を得る(日付の引き算をする)には、dateコマンドで--date "2 days ago"などのような形でずらす日数の指定を行うとよい。


  1. 後述のツールで調べられる他、Wikipediaのラジオ局一覧内の放送局記号でも確認可 ↩︎

  2. 4桁の西暦年の後ろに月, 日, 時, 分, 秒を2桁ずつ並べたもので、10未満の値は10の位を0にする ↩︎

  3. 指定した日時に開始した番組が存在しない場合はエラーとなる ↩︎