radikoでラジオ番組をタイムフリー録音してGoogleドライブにアップロードする
概要
radikoの指定した番組の放送終了後にVPS上でタイムフリー録音をダウンロードし、Google Driveにアップロードするようにしました。(自分のみの私的利用を目的としています)
わたしはラジオが好きでいつも作業したりコードを書いたりしながら聞いています。radikoのタイムフリー録音を使うと、1週間以内であれば放送終了後の番組を聞くことができるのですが、1週間以上経っても後から聞き返したくなることがあります。
番組をダウンロードするフリーソフトとしては、らじれこという優れたものがあります。これを活用して、1週間に1回、その週の番組をまとめてダウンロードしていました。しかし、聞く番組が増えてくると手でダウンロードボタンを押すのが面倒になってきますし、ダウンロードをし忘れることもあります。
技術屋としては技術で解決したいところです。色々調べてみると、タイムフリー録音するスクリプトを先人が作ってくれていましたので、それを活用して自動で録音する仕組みを作りました。
仕組みとしては以下のようになっています。
- タイムフリー録音をダウンロードするスクリプトをVPSにおいてcronで実行
- uru2/rec_radiko_ts: Radiko timefree program recorderをベースに、引数の与え方を少し変えたかったのでラッパーのPythonスクリプトを作成
- 放送終了の5分後に、その番組を保存するようなcronを番組の数だけ書く
- 録音したファイルをGoogleドライブにアップロードし、アップロード完了後にVPS上からファイルを削除するPythonスクリプトをVPS上でcronで実行
- GoogleドライブにネットワークドライブのようにエクスプローラからアクセスできるGoogle公式のツールをWindows PCにインストール
- ローカルに保存しているのと同じような感覚で快適にアクセスできる
- 参考
1個目と2個目については、エラーが発生した場合はtry~exceptでつかまえてSlackにwebhookで通知しています。
技術選定の理由はこんな感じです。
- VPS
- radikoへアクセスするアウトバウンドの通信がそれなりにあるので、通信量に課金がされるGCPなどのクラウドではなく、元々借りていた通信量に課金がされないVPSを利用。
- とりあえずcronで実行できるので楽ですね。cronが取っ散らかっていく問題はありますが…
- VPSへのコードデプロイはGitHubのmainブランチにpushしたらVPS上でコードをpullするGitHub Actionsで行っています。
- ちなみにConoHa(東京リージョン)ではradikoのフリープランでも東京の番組が聞けます。XServer VPSではプレミアムプランでなければ聞くことができませんでした。(サーバがあるリージョンの問題っぽい)
- radikoへアクセスするアウトバウンドの通信がそれなりにあるので、通信量に課金がされるGCPなどのクラウドではなく、元々借りていた通信量に課金がされないVPSを利用。
- Googleドライブ
- Googleドライブからローカルへのダウンロードに通信量がかからず、選択した容量で月額決まった料金となる上に、先述の通り、GoogleドライブはWindowsからネットワークドライブのように扱えて便利なため。スマートフォンからもアプリでアクセス可能。
- 最初は何も考えずGCPのCloud Storageを使おうとしたのですが、このメリットを思い出して変更しました。技術選定大事。
- 保存容量が15GBまでなら無料、200GBでも月380円で済みます。ちなみに1時間番組1本で20MB程度です。
- Googleドライブからローカルへのダウンロードに通信量がかからず、選択した容量で月額決まった料金となる上に、先述の通り、GoogleドライブはWindowsからネットワークドライブのように扱えて便利なため。スマートフォンからもアプリでアクセス可能。
Tips
以下、実装の過程で出会った技術的なTipsを書き留めます。
Pythonでのsubprocess.run()
のエラーハンドリング
Pythonからコマンドを実行するときに使うsubprocess.run()
ですが、正常に実行されたときとエラーが起きた時で処理を分けて、エラーの場合はエラーメッセージを取得したいというケースがあります。
解決策としては、引数にcapture_output=True
とtext=True
を指定します。前者により出力を受け取り、後者により出力をbyte型ではなく文字列で受け取ります。
リターンコード、標準出力、標準エラー出力はreturncode, stdout, stderrで受け取ることができます。
# 任意のコマンド
cmd = "bash hoge.sh"
res = subprocess.run(cmd, shell=True, text=True, capture_output=True)
if res.returncode == 0:
logger.info(f"success | {res.stdout}")
else:
logger.error(f"error | {res.stderr}")
引数にcheck=True
を指定すると、returncodeが0ではないときにsubprocess.CalledProcessError
の例外を起こすことができます。
# 上の例と同じことができる
cmd = "bash hoge.sh"
try:
res = subprocess.run(cmd, shell=True, text=True, capture_output=True, check=True)
logger.info(res.stdout)
except subprocess.CalledProcessError as e:
logger.error(e)
logger.error(res.stderr)
Pythonスクリプト中での相対パスを固定する
このようなディレクトリ構造において、以下のスクリプトをmain.pyで保存します。
hoge
|-- fuga
|-- main.py
import os
print(os.getcwd())
このスクリプトは、カレントディレクトリがhogeかfugaかで返ってくる値が異なります。
hoge $ python ./fuga/main.py
hoge
hoge/fuga $ python main.py
hoge/fuga
これでは、コード中で相対パスでファイルを読み込んでいるとき(プロジェクトディレクトリであるfugaを起点にするようなパターン)、cronなどでシェルから実行する場合、カレントディレクトリによって挙動が変わり不便です。
Python>=3.9では、以下のようにos.chdir(os.path.dirname(__file__))
を足してあげることで、コードが存在するディレクトリを起点にそれより下のコードが実行されて便利です。
import os
os.chdir(os.path.dirname(__file__))
print(os.getcwd())