気の向くままに辿るIT/ICT/IoT
IoT・電子工作

Raspberry Pi 3 Model B+自作スマートスピーカーにラジオを追加

ホーム前へ次へ
Raspberry Piって?

Raspberry Pi 3 Model B+自作スマートスピーカーにラジオを追加

Raspberry Pi 3 Model B+自作スマートスピーカーにラジオを追加

2018/12/20

 以前、作って運用しつつもブラッシュアップ中のRaspberry Pi 3 Model B+とJuliusOpen JTalkベースの自作スマートスピーカーがあります。

 主な機能は、

 尚、ラズパイ用ACアダプタを挿したスイッチ付きコンセントでのON/OFFとは別にラズパイ用boot/reboot/shutdown物理ボタン付き。

 音声認識にJuliusを使った自作スマートスピーカーに伝言とメモの機能を実装するにあたり、マイクとスピーカーを専有してしまうOSSやALSAからPulseAudioに移行しました。

 ちなみに便利なのでラズパイだけでなく、PC/Debianにも自作スマートスピーカー機能を搭載しています。

 自ずとモニタ付きとなるPC版スマートスピーカーには、PC及びラズパイ双方のスマートスピーカー機能のデスクトップアプリとしてPyQt5/Qt Designerによる操作パネルも作成しました。

radiko/らじるらじる/サイマルラジオ/ICECAST等ラジオ機能の追加

 今回は、更にradiko、らじるらじる、サイマルラジオ、ICECAST、オーストラリア放送ABC Newsと英国放送BBC World News等の無料ラジオ局を声で再生、停止させる機能を加えることにしました。

 BGMとしてなど音楽をかけ流ししたい場合にもラジコでラジオNIKKEI第2(RN2)を選局すれば、平日8時〜21時30分の間、ほぼトークなしの邦楽・洋楽を聴けますし、ICECASTでジャズやクラシック、ロック等々任意の放送局を複数選んでおけば、基本、365日終日、いつでも音楽を聴けるようになるでしょう。

 後述の通り、他にも事前にいくつかやることはあるにせよ、メインの作業となる部分は、こんな感じであり、先人のおかげもあって、それほど難解というわけではありません。

追加インストール

$ sudo apt install mplayer rtmpdump swftools libxml2-utils
$

 今回の実装に伴い、Debianを使っている自身の環境では、mplayer、rtmpdump、swfextractを得るべく、swftools、xmllintを得るべく、libxml2-utilsパッケージを追加インストールしておく必要がありました。

事前準備

$ curl -O http://radiko.jp/apps/js/flash/myplayer-release.swf
$ swfextract myplayer-release.swf -b 12 -o authkey.png
$ vi sound/radio/radiko.sh
...
#wkdir='/var/tmp'
wkdir="$HOME/var/tmp"
...
$ mkdir -p ~/var/tmp/
$ cp authkey.png ~/var/tmp/
$

 また、radikoタイムフリー保存方法にあるようにradiko.sh用に、予め、myplayer-release.swfをダウンロード、swfextractコマンドからauthkey.pngと命名した認証ファイルを抽出、radiko.shwkdirに置いておく必要がありました。

 このwkdir、デフォルトの/var/tmpにするならchownなりしておく必要があるでしょうが、/home/xxx/以下にしておく方が、無難と判断、radiko.shを編集し、今回はそうしました。

 尚、radiko.sh再生時のオプション[-p]の引数となる値は、radiko番組表xml取得APIにあるように全放送局一覧から得られる内の[id]タグの値。

Radiko/ICECAST/らじるらじる

 radikoについては、Raspberry Piインターネットラジオの通り、radiko.shパッチを当てます。

 ICECASTについては、そのリンク先にあるmplayer -playlistを使う方法でサイマルラジオの.asxファイル、ICECASTの.m3uファイルを指定します。

 らじるらじるについては、「らじる☆らじる」をHLS経由ででプロトコルの変遷を眺めつつ、らじるらじる配信の最新プロトコルであるHLS対応のURIのあるconfig_web.xmlで必要なm3u8(M3U UTF-8)のURLを指定します。

 それぞれ、後述のようにJulius辞書の編集と前回作った自作スマートスピーカ用のスクリプトvoicerecive.plへの追記。

Juliusオリジナル辞書の編集

$ awk '{print $1}' mysmartspeaker.list > dummy.list
$ vi dummy.list
TBSラジオ    てぃーびーえすらじお
ニッポン放送        にっぽんほうそう
ラジオ日本    らじおにっぽん
ラジオNIKKEI第1    にっけいだいいち
ラジオNIKKEI第2      にっけいだいに
NACK5        なっくふぁいぶ
$ iconv -f utf8 -t eucjp dummy.list | ../../../julius/julius-kits/grammar-kit/bin/linux/yomi2voca.pl > iconv -f eucjp -t utf8 dummy.list.out.utf8
$ cat dummy.list.out.utf8 >> mysmartspeaker.list
$ vi mysmartspeaker.list
$ ...
$ iconv -f utf8 -t eucjp mysmartspeaker.list > mysmartspeaker.eucjp
$

 独自辞書に追記した際、[TBS]ラジオ、[ニッポン]放送、ラジオ[日本]、[日経]、[NACK]5のローマ字表記が思い当たらず、適当に書いたら、やはり、エラーになりました。

 そこで、これらのみ抜き出してタブ区切りで平仮名をあてたファイルを作り、実行権限を与えた(sudo chmod u+xした)yomi2voca.plで変換、出力された結果を反映させたところ、うまく機能しました。(そのためのスクリプトなので当然ですが。)

$ cat config_file
-w mysmartspeaker.eucjp
-v model/lang_m/bccwj.60k.htkdic
-h model/phone_m/jnas-tri-3k16-gid.binhmm
-hlist model/phone_m/logicalTri
-n 5
-output 1
-input mic
-input alsa
-rejectshort 600
-charconv euc-jp utf8
-lv 1500
$

 後にjuliusをモジュールモード(-module)で起動する際に-Cオプションで指定するdictation kit(ver 4.4)の構成ファイルconfig_fileにおいて独自に作った辞書は、-wオプション付きで指定できます。

 この中でmysmartspeaker.eucjp以外の言語モデルや音響モデルは、Julius標準のものを使っているだけ。

 尚、input値については、ラズパイで使えるよう前回追記の通り、OSSではなく、ALSAを使うべく、一連の作業を行ないつつ、明示的に./configure --with-mictype=alsaしたので-input alsaとしてあります。

スマートスピーカー応答スクリプトの編集

$ pwd
/home/xxx/sound/
$ vi ./voicerecieve.pl
#!/usr/bin/env perl
...
      # 【radiko】
      when("ラジオ日本"){
       system("/home/xxx/sound/radio/radiko.sh -p JORF &");
      }
      when("JWAVE"){
       system("/home/xxx/sound/radio/radiko.sh -p FMJ &");
      }
      when("放送大学"){
       system("/home/xxx/sound/radio/radiko.sh -p HOUSOU-DAIGAKU &");
      }
 ...
      # 【らじるらじる by radiko】
      when("東京NHK第1"){
      # system("mplayer -playlist https://nhkradioakr1-i.akamaihd.net/hls/live/511633/1-r1/1-r1-01.m3u8 &");
       system("/home/xxx/sound/radio/radiko.sh -p JOAK &");
      }
      when("東京NHK第2"){
      # system("mplayer -playlist https://nhkradioakr1-i.akamaihd.net/hls/live/511633/1-r1/1-r1-01.m3u8 &");
       system("/home/xxx/sound/radio/radiko.sh -p JOAB &");
      }
      when("東京NHK第 FM"){
      # system("mplayer -playlist ://nhkradioakfm-i.akamaihd.net/hls/live/512290/1-fm/1-fm-01.m3u8 &");
       system("/home/xxx/sound/radio/radiko.sh -p JOAK-FM &");
      }
 ...
      # 【ICECAST】
      when("JAZZ"){
       system("mplayer -playlist http://dir.xiph.org/listen/212037/listen.m3u &");
      }
 ...
      # 【ラジオ停止】
      when("ラジオ停止"){
       system("/home/xxx/sound/jsay ラジオを停止します");
       system("pkill -f rtmpdump");
       system("pkill -f mplayer");
      }
 ...
 

 これらを踏まえ、自作スマートスピーカー用スクリプトvoicerecieve.plに適宜追記。

 ラジオを止める場合、rtmpdumpしているradiko.sh、mplayer、何れで再生していようがいまいが、両方とも強制的にpkillすることにしました。

 コマンドラインから実行した限りにおいては、rtmpdumpを止めても即終了せず、タイムラグがありましたが、スマートスピーカー実装後は、なぜか、このタイムラグがなくなりました。

 尚、らじる★らじるは、mplayerで再生すると10秒以内程度の周期で更新がかかり、その都度、途切れるが、幸い、試験的で年度をまたぐと若干聴取できない期間が生じがちな模様もradiko対応している為、これを利用するとよいでしょう。

 なぜか全国版にはNHK第2がなかったので先のリンクから、地域別、例えば東京ならJP13.xmlにある通り、NHK第1なら[JOAK]、NHK第2なら[JOAB]、NHK FMなら[JOAK-FM]というidで先の例のように[radiko.sh -p]の引数としてこれらidを渡してradikoで再生すれば、途切れることなく、良好に聴取できます。

$ pwd
/home/xxx/julius/julius-kits/dictation-kit-v4.4
$ julius -C config_file -C am-gmm.jconf -module
...

 あとは、検証中なら、例えば、端末からこのようにJuliusをモジュールモードで実行しておき...

$ pwd
/home/xxx/sound/
$ ./voicerecieve.pl
...

 他の端末でスマートスピーカ用スクリプトを実行...

 スクリプトの手順に沿って独自辞書に登録したワードを発すれば、今回新たに追加したラジオを再生できるはず。

変更の反映

 辞書に影響がある(辞書を編集した)場合は、少なくともJulius 4.4においては、iconvでeucjpに変換が必要です。

 構成ファイル変更の反映については、systemd/systemctlならsystemd自動起動設定参照。

注意

 自動起動する際などroot起動の可能性を考えると$HOMEなどをも使わず、[/home/xxx/sound/jsay]などは、省略せずに[/home/xxx/sound/jsay]などとした方が賢明かと。

備考

 とても良好に機能しています。

 ただ、課題がなくもない。

 1つは、ラジオ局を複数再生できてしまい、その場合、混線状態となること。

 ただ、日付時刻や天気などラジオ局再生中でも確認できるのは、特に音楽鑑賞中は、意外と重宝する為、ラジオ局再生が複数重ならないように何か工夫が必要かとも思いますが、停止をかければ全て止まるし、誤動作でない限りは、そもそも再生中に重複指示しなければよく、運用でなんとかなるとも言える。

 これは課題というのか、現状、スピーカースクリプトにラジオの音量調整機能はなく、必要ならスピーカーZ120のボリューム調整つまみでなら調整できる状態ですが、ソフト的な音量調整を実装するか、スピーカーのつまみでの運用を前提にするか...。

 WiFi経由で赤外線リモコン操作するテレビなどについては、音量調整も搭載しますが、スマートスピーカ自体の音量も声で操作できた方がいいのかな...。

追記

ABCニュース/BBCニュース
$ pwd
/home/xxx/sound/
$ vi ./voicerecieve.pl
#!/usr/bin/env perl
...
      when("ABCAustraliaNews"){
       system("/home/xxx/sound/jsay ABCニュース");
       system("mplayer -playlist http://abc.net.au/res/streaming/audio/aac/news_radio.pls &");
      }
      when("BBCNews"){
       system("/home/xxx/sound/jsay BBCワールドニュース");
       system("mplayer http://bbcwssc.ic.llnwd.net/stream/bbcwssc_mp1_ws-eieuk &");
      }
 ...
 
2019/01/15

 Mplayer, Radio Station, TUNE-IN Plugin available?を参考に英語系ニュースラジオ・ライブとしてオーストラリア放送ABC Newsと英国放送BBC World Newsを追加。

 何れもmplayerでいけますが、BBCの方は、-playlistオプションは不要な点に注意。

2019/01/24

 UPnP/DLNAメディア再生機能を追加した際、ラジオや音楽の再生が複数被らないようラジオ停止操作をstop_radio.shにまとめました。

 他のラジオでは、このスクリプトをバックグラウンド起動させてから再生スクリプトを実行しても何ら問題ありませんが、BBCニュース、ABCニュース再生前に置く時は、なぜか、フォアグラウンド起動しないと[can not connect socket]、LIRCがどうのといった予期せぬエラーになるので注意。

 stop_radio.shを実行しない手もあるにはありますが、重複再生を回避できない為、本末転倒。

 ちなみにLIRCに関するエラー自体は、mplayer.conf内で設定したり、-nolircオプション付きでmplayerを実行すれば回避できるのですが、それだけだと肝心のニュースが再生されない。

$ cd path/to/dictation-kit-v4.4
$ vi mysmartspeaker.list
...
ABCニュース    [ABCAustraliaNews]    e: b i: sh i: ny u: s u
BBCニュース    [BBCWorldNews]    b i: b i: sh i: ny u: s u
...
$ iconv -f utf8 -t eucjp mysmartspeaker.list > mysmartspeaker.eucjp
$

 Juliusのdictation kit ver 4.4の独自辞書に追記、ファイルエンコーディングをUTF-8からEUC-JPに変換。

ICECAST
2019/01/30

 ICECAST STREAMからJAZZ、CLASSICに加え、BLUESを追加。

2019/07/18

 ICECAST STREAMからURLを直接指定していたJAZZ、CLASSIC、BLUESですが、スクリプトを作成、実行する方式にすることにしました。

 というのも当初から、URL指定だとURLが度々変更になるようで再生できなくなり、その度にスクリプト内のURLを変更するというアナログな方法をとる必要がありながら、対策を講じることなく放置、しまいには使わなくなっていたのですが、ちょっと時間ができたため、考えてみることに。

 何らかの方法でURLを特定できるのであれば、HTMLやXMLを解析して持ってくればよいんだよね?と思ったら、巷では、そういうのをWebスクレイピングと呼ぶらしいことを知りました。

 どれかと言われればPerlが慣れているのですが、今やスクリプトと言えば、RubyかPythonらしい、Pythonのほうが馴染みやすかったためPythonで作ってみることにしました。

$ pip3 install beautifulsoup4
$ pip3 install lxml
$

 PythonでWebスクレイピングと言えば、BeautifulSoup一択の雰囲気。

 そこでpython3でwebスクレイピング(Beautiful Soup)をとっかかりにBeautiful Soup 4.4.0 documentation(Beautiful Soup 4.2.0 Doc. 日本語訳 (2013-11-19最終更新)もあったらしい)を見ながら書いてみました。

$ cd path/to/smartspeaker/script
$ cat GetIcecastJazz.py
#!/usr/bin/env python3
#coding:UTF-8
import requests
from bs4 import BeautifulSoup
 
host = "http://dir.xiph.org"
url = "http://dir.xiph.org/search?search=jazz"
 
html = requests.get(url)
 
# lxmlの方が高速らしい
#soup = BeautifulSoup(html.text, "html.parser")
soup = BeautifulSoup(html.text, 'lxml')
 
for musicline in soup.find_all(title="Listen to 'jazzheart'"):
  print(host + musicline.get('href'))
  break
 
$

 aタグのtitleで*.m3uと*.xspfの2つに、breakで抜けることで強引に一意に絞れそうだったため、そうすることにしました。

 import sysしてsys.argvを使ってスクリプト1つで済まそうと思ったら、うまくいかなかったので引数を取る方法は早々に諦め、ジャンル(ラジオ局)ごとに1つスクリプトを作ることにしました。

 また、常用しているmplayerをpythonから直接起動する方法としてimport subprocessしてsubprocess.call()しようと思ったら、値を直接渡せばいけるものの、変数だとダメ...、これまたあっさり手を引き、URLを吐くだけにしてshellスクリプトからpythonスクリプトを呼ぶ恰好にしました。

$ cd path/to/smartspeaker/script
$ cat GetIcecastJazz.py
#!/usr/bin/env python3
#coding:UTF-8
import requests
from bs4 import BeautifulSoup
import re
 
url = "http://dir.xiph.org/search?q=jazz"
 
html = requests.get(url)
soup = BeautifulSoup(html.text, 'lxml')
 
for musicline in soup.find_all(href=re.compile("jazz.w3.at")):
  print(musicline.get('href'))
  break
 
$
2020/05/23

 ある日、Jazzを再生しても音が出ない、別の日、クラシックもブルースも音が出ない...音がでなーい、どーしよっ、どーしよっ、パオッって感じですが、ストリームがなくなったのかなと思って、しばらく放置していました。

 が、今日、確認してみたところ、ICECASTのSTREAMページがリニューアルしており、HTMLも内容が変わっていることに気づきました。

 そう、これに伴い、マッチングも無効ですが、それ以前にCGI引数の渡し方が微妙に変更されている時点で再生されなかったのでした。

 というわけで書き直したのがこれです。

 正規表現ライブラリreをインポートしてURLをマッチさせる方法で特定、複数ある場合に備え、相変わらず、breakで抜けて取得は1件だけということで。

 ただ、以前のように全結果にアクセスできておらず、1ページめだけっぽい気がしなくもない感じ...そうだとすると2ページめ含む以降に存在しても再生されないですが、とりあえず、いっか。

$ pwd
path/to/smartspeaker/script
$ cat IcecastJazz.sh
#!/bin/sh
 
mplayer -playlist `/home/xxx/path/to/smartspeaker/script/GetIcecastJazz.py`
 
$

 そのshellスクリプトは、mplayerの引数にURLを返すpythonスクリプトを指定するだけ。

 結果、スマートスピーカー用の応答スクリプトvoicerecieve.plからは、このshellスクリプトを呼ぶことにしました。

 こんな感じでClassicalとBlues、今回新たにPOPS(洋楽)も加え、それぞれスクリプトを作りました。

 当初、soup.select('a[href^="/listen/"]')して最初の局を再生しようと思ったのですが、仮にブラウザ上で聴取できたとしてもCLIで実行するとなぜか[This station is not available in your country.]と言われてしまうことがあった為、聴取できることを確認できた特定の局を再生することにしました。

2020/01/09

 ラズパイ版スマートスピーカーについては、mplayerのバグっぽく、mplayerからmpvに変更することになりました。

2020/05/23

 PC版スマートスピーカー機能においても、mpvに変更することになりました。

 が、PCの方のmplayerは、ラズパイでの変更時に確認したバージョンから変わっていないのでmplayerのバグというわけではなさそうです。

 これに伴い、ラジオ停止操作スクリプトstop_radio.shにpkill -f mpvを追記。

 尚、スクリプト修正後、systemctl restart ...が必要です。

Flashを使わないRadiko再生スクリプト
2021/03/15

 昨夜、自作スマートスピーカーでふとRadikoを再生しようと思ったら、なぜか再生できないことに気づきました。

 調べてみると、どうも2020年のAdobe Flashのサポート終了に伴い、昨年11月でRadikoがFlash対応を終了したことによる模様。

 4ヶ月も5ヶ月もラジコ再生してなかったってことですよね!?まぁ、既存スクリプトradiko.shは、確かFlash依存なはず、ということで今日になってFlashレスで再生できるスクリプトを検索した結果、【Python】ラジコを再生するを発見しました。

 タイムスタンプは[2019-06-24]となっており、そもそもFlash終了以前からFlashに依存しないスクリプトを作っていらっしゃいました。

 そこにあるとおり、スクリプトのあるパス上で./pyradiko.py QRR -p 'ffplay'(または、./pyradiko.py -p 'ffplay' QRR)のようにすれば、再生時間を指定せずともライブ配信されました、が、そのままでは、すぐ停止してしまうので後述の通り、スクリプト内の時間設定を修正しました。

 自身はライブ配信視聴しかしませんが、このスクリプト多機能でいろいろできる模様、プレイヤーもffmpegパッケージに同梱されるらしきffplayの他、mplayerも指定できるようですが、詳しく見ていないものの、mplayerだとエラー([do_connect: could not connect to socket]/[connect: No such file or directory])になりました。

 が、python2対応のようでpython3だとbase64部分でエラーが、そこをクリアしてもreturn response.content.splitlines()[-1]のエラーが。

 そこでめぼしいところにprint文を入れてみたところ、ffplayで再生するにあたり、なぜか、URL全体が、b''でくくられ、byte扱いになっていることが判明、これをdecodeしてstr型にすることでpython3でもいけました。

anyuser@raspberrypi~$ cat /path/to/pyradiko.py
#! /usr/bin/env python3
# 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])
#partialkey = base64.b64encode(b'cls.authkey[offset: offset + length]')
partialkey = base64.b64encode(cls.authkey[offset: offset + length].encode("utf-8"))
#partialkey = base64.urlsafe_b64encode(cls.authkey[offset: offset + length].encode("utf-8"))
print(partialkey)
# #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)
response = requests.get('http://f-radiko.smartstream.ne.jp/{0}/_definst_/simul-stream.stream/playlist.m3u8'.format(station), headers=headers)
print(response)
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):
print("RadikoPlayer start")
'''
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):
print("RadikoPlayer play")
'''
再生メソッド
'''
# プロセスがすでに動いてたら閉じる。
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':
print("ffplay select")
#cmd = 'ffplay -nodisp -loglevel quiet -headers "X-RADIKO-AUTHTOKEN:{0}" -i {1}'.format(self.__class__.authtoken, url)
cmd = 'ffplay -nodisp -loglevel quiet -headers "X-RADIKO-AUTHTOKEN:{0}" -i {1}'.format(self.__class__.authtoken, url.decode('utf-8'))
#cmd = 'ffplay -nodisp -loglevel quiet -headers "X-RADIKO-AUTHTOKEN:{0}" -i {1}'.format(self.__class__.authtoken, urlencoded)
print(cmd)
# プレイヤーの起動(再生)
print("ffplay start")
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):
print(station)
m3u8_url = self.radikoPlayer.live_m3u8(station)
print(m3u8_url)
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('-d', '--duration', help='再生時間(秒)', type=int, nargs='?', const=36000, default=36000)
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()
anyuser@raspberrypi~$

 たぶん、時間指定しなければ、再生継続なはずと信じつつ、と思ったら甘かったので[parser.add_argument('-d',..., const=1800, default=60]の[const=1800]が後述のように上限っぽいのででないと思い込んで[const=1800, default=60]のdefaultをとりあえず、[const=1800, default=36000]に...

 あ、1800秒って、このスクリプトでも再生できるっぽいタイムフリー機能の制限の3時間か?と思ったら、1800秒って30分か...。

 ということは、const=1800ってなんだろ...、それにしても、やはり、この上限かな...。

 何れにせよ、ライブ視聴には上限ってないですよね?radiko.shでも30分や3時間どころかもっとかけ流していたこともあったような...、どっかいじらなくちゃダメか...const値を変えれば良いだけかな...?そんなにラジオ聴くことはないけど、かけ流しにはしたいので変えておこう...。

 ん!?ラジオNIKKEI第1、ラジオNIKKEI第2、NHK第一、NHK FM、放送大学については、リターンコード404エラーで視聴できない...。

 404はサーバにアクセスはできてるけど要求されたもの返せないよエラーだよね?ID変わったのかと思いつつ、http://radiko.jp/v3/station/region/full.xmlを見る限り、ブラウザからRadiko視聴してみても、変わってなさ気...Python2の元のスクリプトも試してみても同様、なぜ...。

 わかった!いろいろ検索しているとストリーミングサーバのサブドメイン(≒サブディレクトリ)がc-radikoとf-radikoの2種類あるなと思っていたのですが、[c-radiko.smartstream.ne.jp]を[f-radiko.smartstream.ne.jp]にしてみたところ、全局、再生できるようになりました。

 これは、ブラウザ(Firefox)のヨコ三本線の[設定]、[ウェブ開発]、[ネットワーク]から[メディア]に絞って[ドメイン]で、また、行をマウスオーバーして選んで右クリック、[コピー]、[URLをコピー]した中身を眺めるとわかります。

 今日確認した限りでは、[c-radiko]は見たような見てないような、[f-radiko]はあっても稀、たいていは、[rpaa]というサブドメインで、このスクリプト上で、単にこれにするとエラーになる、コピーして確認するとパラメータも違うっぽいものの、もしや、以後、このサブドメインに統一していく過渡期なのか...、とは言え、とりあえず今は、[f-radiko]でいけたということで。

 結果、太字・フォント大きめにした

でpython3対応としての3箇所含め、実用上は5箇所の修正だけで済み、あと実用上、問題ないものの、

しておきました。

 あ、[parser.add_argument('-p', '--player', help='再生するプレイヤー', type=str, nargs='?', default='mplayer')]をdefault='ffplay'にすれば、プレイヤーを指定することなく、./radiko.py TBSなどとしても再生できますね(mplayerだとダメですが)。

 というわけでサイトへのコメントは割愛させて頂きますが、作者Turtlechan氏に感謝して有り難く使わせて頂きます。

 尚、[NHKラジオ第2: JOAB]はRadiko経由については既にサービスが終了しています(らじる☆らじるでは、視聴可)。

 ちなみにtakuya / play-radikoでは、そのままで全局、見事に再生できたのですが、なぜか、再生されるまでにちょっとしたタイムラグがあった、端末で実行、終了(Ctrl+C)するとフリーズして端末を閉じるしかなくなったりしたので先出のものを選ばせて頂きました。

 というわけで自身の場合、自作のラズパイスマートスピーカー、Linuxパソコン用の自作スマートスピーカー機能に反映させるべく、スマートスピーカー応答スクリプトとPCから操作できるスマートスピーカー操作パネル用のスクリプトをradiko.shからradiko.py(radiko.sh -p 放送局IDからradiko.py -p ffplay 放送局ID)に変更、音楽・ラジオ等停止スクリプトにpkill -f ffplayを追記、応答ファイルの反映は、systemctl stop サービス/systemctl daemon-reload/systemctl start サービス、操作パネルは、編集後、パネルを開くかパネルを再起動しておきました。

2021/03/18

 加えて、音声操作応答ファイルにおいて、音楽やラジオ再生中に他の再生を指示した際、2重3重に音声が被らないよう停止スクリプトで止めてから再生するようにしているのですが、停止スクリプトと各種再生スクリプトやチャンネルとの間に(バックグラウンドじゃなくてフォアグラウンドで)スリープ[system("sleep 1");]を入れる必要がありました。

 radiko.shの際には不要だったのですが、radiko.pyと停止スクリプトの間には、タイムラグがあるようで、そもそも停止スクリプトを入れなければ再生されるものの、停止スクリプトを介してスリープさせないと再生されなかったので。

 もう少し長い方が良いのかと最初は10秒で試しましたが、1秒でOKでした。

 操作パネルからでもRadikoの放送局の再生を停止するまでに微妙なタイムラグがあったことから、すぐに目星はつき、当初、停止スクリプトのpkill -f ffplay分だけ停止に時間がかかるのか繊細なものだなとも思いましたが、停止スクリプトを共有していてmplayerで再生させているICECASTやYouTube、ABCラジオやBBCラジオでは影響がないので、もしかすると、それにプラスしてか、ffplayでの再生開始が速い可能性はあるのかも。

2021/07/17

 ICECASTにおいてJazzやBluesは安定しているものの、Classicalは、局に関わらず、なぜか再生できなくなることが多く、放置していました。

 が、ICECAST以外の選択肢をと思い、クラシックについては、internet-radio.comからチョイスさせて頂くことにしました。

2021/09/01

 と思ったら、ICECASTのジャズやブルースも時折、局が見当たらなくなるのでスクレイピングはやめて同じくinternet-radio.comから探した特定の局を指定、再生することにしました。

ホーム前へ次へ