turtlechanのブログ

無知の私がLinuxをいじりながら書いていくブログ

【Python】ラジコを再生する

CUI環境でRadikoを聴きたいなと思って作ったPythonスクリプト。主にラズパイで定時に再生するため。自分用

はじめに

seleniumでPhantomJSを動かしてjavascriptを解釈してもらえばいいんだろうけど、重いのも嫌なので'requests'ライブラリを使ってやることにした。それにラズパイのPhantomJSって私の環境だと何故かデーモン化する。

再生するためにやること

  1. https://radiko.jp/v2/api/auth1 からトークンを取得
  2. https://radiko.jp/v2/api/auth2トークンを認証
  3. ffplayに認証したトークンを渡して処理を丸投げ

認証については下記の参考にしたサイトで詳しく書かれている。ありがとうございます。

今回作ったやつ

必要なもの

インストールする必要があるものを書いておく。

  • ライブラリ
  • ソフトウェア(どちらか一つでいい)
    • ffplay: 再生の処理をするため(ffmpegに同封されている)
    • mplayer: 再生の処理をするため

Pythonスクリプト

pyradiko.py
#! /usr/bin/env python
# coding: utf-8
import argparse # 引数の取扱のため import time # サーバーへの負担考慮のため import base64 # partialkey生成のため import subprocess # 外部ソフトウェアの起動のため import requests # インターネット接続のため class RadikoAPI(object): ''' RadikoAPIクラス ''' # クラス変数 authkey = 'bcd151073c03b352e1ef2fd66c32209da9ca0afa' # 現時点では固定(playerCommon.jsに記載) authtoken = None def __new__(cls): ''' 初期化メソッド ''' print('RadikoAPI __new__') return super(RadikoAPI, cls).__new__(cls) def __init__(self): ''' 初期化メソッド ''' print('RadikoAPI __init__') @classmethod def tuning(cls): ''' 認証1,2の処理 ''' # #Auth1 適当なヘッダーを用意してauth1に投げる処理 headers = { 'User-Agent': 'curl/7.52.1', 'Accept': '*/*', 'x-radiko-user': 'dummy_user', 'x-radiko-app': 'pc_html5', 'x-radiko-app-version': '0.0.1', 'x-radiko-device': 'pc' } response = requests.get('https://radiko.jp/v2/api/auth1', headers=headers) time.sleep(1) # auth1から返ってきたヘッダーの代入 length = int(response.headers['X-Radiko-KeyLength']) offset = int(response.headers['X-Radiko-KeyOffset']) cls.authtoken = response.headers['X-Radiko-AUTHTOKEN'] # PartialKey生成 partialkey = base64.b64encode(cls.authkey[offset: offset + length]) # #Auth2 auth1から得たauthtokenと生成したpartialkeyをauth2に投げる。 headers = { 'User-Agent': 'curl/7.52.1', 'Accept': '*/*', 'x-radiko-user': 'dummy_user', 'X-RADIKO-AUTHTOKEN': cls.authtoken, 'x-radiko-partialkey': partialkey, 'x-radiko-device': 'pc' } response = requests.get('https://radiko.jp/v2/api/auth2', headers=headers) time.sleep(1) @classmethod def live_m3u8(cls, station): # 認証の処理 cls.tuning() # .m3u8ファイルのURL取得 headers = { 'X-RADIKO-AUTHTOKEN': cls.authtoken } response = requests.get('http://c-radiko.smartstream.ne.jp/{0}/_definst_/simul-stream.stream/playlist.m3u8'.format(station), headers=headers) return response.content.splitlines()[-1] @classmethod def timefree_m3u8(cls, station, ft, to): # 認証の処理 cls.tuning() # .m3u8ファイルのURL取得 headers = { 'X-RADIKO-AUTHTOKEN': cls.authtoken } response = requests.get('https://radiko.jp/v2/api/ts/playlist.m3u8?station_id={0}&l=15&ft={1}&to={2}'.format(station, ft, to), headers=headers) return response.content.splitlines()[-1] class RadikoPlayer(RadikoAPI): ''' RadikoPlayerクラス Radikoの再生のプレイヤー操作関係 ''' # クラス変数 _instance = None # インスタンスが存在するか確認のため _process = None # プレイヤーのプロセスを格納するため def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super(RadikoPlayer, cls).__new__(cls) return cls._instance def __init__(self, player='mplayer', cache=2048): ''' 初期化メソッド ''' print('RadikoPlayer __init__') self.player = player self.cache = cache @staticmethod def kill(player='mplayer'): ''' プレイヤーを強制終了する。 このスクリプトでプレイヤーを見失ってしまった場合のため。 ''' subprocess.call(('pkill', player)) @classmethod def quit(cls): ''' 起動したプレイヤーを閉じる。 ''' cls._process.terminate() cls._process = None def play(self, url): ''' 再生メソッド ''' # プロセスがすでに動いてたら閉じる。 if self.__class__._process: self.quit() # コマンドの生成 if self.player == 'mplayer': cmd = 'mplayer -novideo -really-quiet -cache {0} -http-header-fields "X-RADIKO-AUTHTOKEN:{1}" {2}'.format(self.cache, self.__class__.authtoken, url) elif self.player == 'ffplay': cmd = 'ffplay -nodisp -loglevel quiet -headers "X-RADIKO-AUTHTOKEN:{0}" -i {1}'.format(self.__class__.authtoken, url) # プレイヤーの起動(再生) self.__class__._process = subprocess.Popen(cmd.split(' '), bufsize=0, stdout=subprocess.PIPE) class Live(object): ''' ライブ視聴用のクラス ''' def __init__(self, player=None, cache=None): ''' 初期化メソッド ''' if player is not None and cache is not None: self.radikoPlayer = RadikoPlayer(player, cache) else: self.radikoPlayer = RadikoPlayer() def play(self, station): m3u8_url = self.radikoPlayer.live_m3u8(station) self.radikoPlayer.play(m3u8_url) def stop(self): self.radikoPlayer.quit() class Timefree(Live): ''' タイムフリー視聴用のクラス ''' def __init__(self, player=None, cache=None): ''' 初期化メソッド ''' super(Timefree, self).__init__(player, cache) def play(self, station, ft, to): m3u8_url = self.radikoPlayer.timefree_m3u8(station, ft, to) self.radikoPlayer.play(m3u8_url) def main(): ''' メイン関数 ''' if args.kill: print('{0}をpkillします。'.format(args.kill)) subprocess.call(('pkill', args.kill)) if args.list: print('*--------------------------*') print('| ステーション一覧 |') print('*==========================*') print('| TBSラジオ: TBS |') print('| 文化放送: QRR |') print('| ニッポン放送: LFR |') print('| ラジオNIKKEI第一: RN1 |') print('| ラジオNIKKEI第二: RN2 |') print('| InterFM897: INT |') print('| TOKYO FM: FMT |') print('| J-WAVE: FMJ |') print('| ラジオ日本: JORF |') print('| bayfm78: BAYFM78 |') print('| NACK5: NACK5 |') print('| FMヨコハマ: YFM |') print('| 放送大学: HOUSOU-DAIGAKU |') print('| NHKラジオ第1: JOAK |') print('| NHKラジオ第2: JOAB |') print('| NHK-FM: JPAK-FM |') print('*--------------------------*') if args.station == '': quit() elif args.ft and args.to: radiko = Timefree(args.player, args.cache) radiko.play(args.station, args.ft, args.to) else: radiko = Live(args.player, args.cache) radiko.play(args.station) time.sleep(args.duration) radiko.stop() if __name__ == '__main__': ''' このスクリプトが直接呼び出された際 ここから処理が始まる ''' # # argparseインスタンス生成 parser = argparse.ArgumentParser( prog='pyradiko.py', usage='pyradiko.py station [option] ...', description='ラジコをCUI環境で再生するために作ったスクリプト。', epilog='end', add_help=True ) # 引数(オプション)の設定 parser.add_argument('station', help='再生したい放送局(string)', type=str, nargs='?', default='') parser.add_argument('-f', '--ft', help='タイムフリー開始日時', type=str) parser.add_argument('-t', '--to', help='タイムフリー終了日時', type=str) parser.add_argument('-d', '--duration', help='再生時間(秒)', type=int, nargs='?', const=1800, default=60) parser.add_argument('-p', '--player', help='再生するプレイヤー', type=str, nargs='?', default='mplayer') parser.add_argument('-c', '--cache', help='キャッシュ(mplayerのみ)', type=int, nargs='?', default=1024) parser.add_argument('-k', '--kill', help='指定したプロセスを終了', type=str, nargs='?', const='mplayer') parser.add_argument('-l', '--list', help='ステーション一覧', action='store_true') # 生成 args = parser.parse_args() # # メイン関数の呼び出し main()

使い方

文化放送のライブを1800秒間ffplayで再生する場合は以下。

~$ python pyradiko.py QRR -p 'ffplay' -d 1800

おわりに

ラズパイのcronに設定して朝めざまし代わりに使ってる。今の所満足。

 

超A&Gの再生も書いたので良かったら見てね。

turtlechan.hatenablog.com

参考にしたサイト