SimulRadio(サイマルラジオ) - MPD(music player daemon)

前回までに作成したSimulRadioは少々UIがダサいことを除けば問題なく稼働しているのですが唯一の欠点は接続、再生開始に約10秒前後を要することです。

なんとかこの待ち時間を短縮すべく再生プレーヤーにMPD(music player daemon)を導入してみました。

MPDはpc audio,raspberry pi(moode audio)などで音質、安定性とも定評のあるmusic daemonですね。
当サイトの環境では音声はpulse audioで全てmoode audioに送っているのでMPDはユーザーで起動実行しています。(systemdのmpd(pulse audio)は接続できない)
RaspberryPi3,OrangePi3上のubuntuコンテナで確認

MPDのインストール

# apt install mpd mpc ncmpcpp

インストールしたMPDはsystemdから自動起動しないようdisableしておきます。

# systemctl stop mpd
# systemctl disable mpd

mpd.conf

/etc/mpd.confから抜粋 alsaを使う場合は有効に!
raspberry piのalsaでも確認していますがmpvなどのようにalsa-pulseaudioの自動切り替えはしないのでOS(pulseaudio)の設定次第になります。
~/.config/mpd/mpd.conf

music_directory     "~/radio/music"
playlist_directory  "~/radio/playlist"
db_file             "~/.config/mpd/database"
log_file            "~/.config/mpd/mpd.log"
pid_file            "~/.config/mpd/pid"
state_file          "~/.config/mpd/state"
sticker_file        "~/.config/mpd/sticker.sql"
bind_to_address "localhost"
port            "6600"
input {
    plugin   "curl"
}
input {
    enabled  "no"
    plugin   "tidal"
}
input {
    enabled  "no"
    plugin   "qobuz"
}
decoder {
    enabled  "no"
    plugin   "hybrid_dsd"
}
audio_output {
    type     "pulse"
    name     "pulse audio"
}
#audio_output {
#    type    "alsa"
#    name    "My Sound Card"
#}

ディレクトリを作成

$ mkdir ~/radio/music && mkdir ~/radio/playlist

mpdの起動、確認

$ /usr/bin/mpd &

適当な音楽ファイルを~/radio/musicに放り込んでインストールしたncmpcppからmpdの確認をします。

$ ncmpcpp


ncmpcというクライアントソフトもありますがraspberry piではうまく接続できませんでした。
browse画面で選択するとplaylistに登録追加されます。
デフォルトではyoutubeから直接再生はできないようです。
ダウンロードしたmp4ファイルは音声のみ再生可
設定に間違いが無ければalsaまたはpulse audioから再生します。

radioの再生

$ mpc help | less


mpdクライアントのmpcにはいろいろなコマンドがあります。
この中から適当なコマンドを使ってプログラム内で実行しています。


mpdの基本はplaylistを作成してmpcから実行指示します。
mpc add urlでplaylistに登録
mpc play 再生開始
mpc stop 停止
mpc clear playlistをクリア
pythonプログラムではplaylistを書き換えて実行しています。

killall.sh

mpc stopとmpc clearを追加

#!/bin/sh
mpc stop
mpc clear
killall -q mpv
killall -q mplayer
killall -q ffplay

simul.py

SimulRadioの動作はmpvプレーヤー時と同じようにしたいと思います。
参考 simul.py

mpdの起動
simul.py起動時プログラムの最初の方でmpdが起動していなかったらmpdを起動します。

# mpd
command = ("ps ax | grep mpd | grep -v grep")
proc = (subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0]).decode('utf-8')
if not (proc):
    subprocess.run("/usr/bin/mpd &", shell=True)

play
前回は余計なことをしてyoutube urlも実行できると書いてしまったのでyoutube再生はmpvで実行するようにしました。選択したurlをmpc addしてwww.youtube.comの文字列があったらmpvで実行します。

# selection
def play_selection():
    for i in lb.curselection():
        a = (lb.get(i))
        subprocess.run("mpc add " + (a), shell=True)
        command = ("mpc playlist | grep -oP 'www.youtube.{0,4}' | grep -v {")
        proc = (subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0]).decode('utf-8')
        proc = proc.strip()
        if proc == 'www.youtube.com':
            command = ((("mpv ") + (a)) + (" &"))
        else:
            command = ("mpc play")
        subprocess.run(command, shell=True)

ON AIR
ON AIR情報はmpc playlistから取得しています。停止時はkillall.shでmpc clearしてplaylistから削除

# ON AIR
def button7_click():
    command = ("mpc playlist | grep -oP 'JCB.{0,3}' | grep -v {")
    proc = (subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0]).decode('utf-8')
    if not (proc):
        command = ("mpc playlist | grep -oP 'jp/3.{0,4}' | grep -v {")
        proc = (subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0]).decode('utf-8')
        # CSRA NHK
        if not (proc):
            command = ("mpc playlist")
            proc = (subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0]).decode('utf-8')
            proc = proc.strip()
            if proc == 'https://nhkradiohkfm-i.akamaihd.net/hls/live/512076/1-fm/1-fm-01.m3u8':
                proc = 'NHK FM 仙台'
            if proc == 'https://nhkradiohkr1-i.akamaihd.net/hls/live/512075/1-r1/1-r1-01.m3u8':
                proc = 'NHK R1 仙台'
            if not (proc): 
                command = ("ps ax | grep mpv | grep -v grep | awk '{print $NF}'")
                proc = (subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).communicate()[0]).decode('utf-8')
                proc = proc.strip()
                if proc == '-':
                    proc = 'Radiko playing'
                else:
                    proc = 'No Station'
            messagebox.showinfo('ON AIR', proc)
        # ListenRadio
        else:
            proc = proc.strip()
            proc = proc[3:]
            with open("/home/pi/radio/CSRA/ListenRadio", 'r') as f:
                for id in f:
                    if re.search((proc), id):
                        messagebox.showinfo('ON AIR', id)
    # JCB
    else:
        proc = proc.strip()
        with open("/home/pi/radio/JCBA/JCB", 'r') as f:
            for id in f:
                if re.search((proc), id):
                    messagebox.showinfo('ON AIR', id)

simul.py実行

$ ~/bin/simul.py &

さて肝心のmpdの成果ですがかなりの改善を得ることができました。
特にListenRadioではplayとほぼ同時に再生を開始します。

ubuntuコンテナからも同様で
JCBA 3〜4秒
mms: 2〜3秒
接続再生開始します。
http(https)はmpdが圧倒的に早そうです。
これでストレス無く聴取できそうです。あとはラジオ局間の音量のばらつきが少なければいいのですが…
UI、操作類は一切変わっていないはずです。

Raspberry pi SimulRadio(サイマルラジオ)

前回までサイマルラジオCSRA(ListenRadio)とJCBAの動作を確認したので一つのプログラムに纏めてみたいと思います。

基本は同じプログラムなのでボタンの数は増えますがON AIRの部分を少し変更することでいけると思います。

ON AIR(再生中の放送局表示)はエリアを切り替えること、局数も多いことから必須と考えています。

SimulRadio

ON AIR
前回messageboxには正常に表示するのですがmessageboxから抜ける際Typeerrorが出ていました。どうやらmessagebox.askyesnoが関係しているようです。(loopしている?)
messagebox.showinfoで表示することでエラーを回避することができました。

mpvはurlを実行しています。
JCBAはJCB+3文字をCSRA(ListenRadio)はjp/3+4文字を取り出しています。
改行コード、CSRA(ListenRadio)は3文字(jp/)を削除してそれぞれIDを抽出します。
その値をre.searchでJCBAはファイル(JCB),CSRAはファイル(ListenRadio)を読んでmessageboxに出力しています。

$ ps ax | grep mpv
 1639 ?  Sl  0:01 mpv http://musicbird-hls.leanstream.co/musicbird/JCB001.stream/playlist.m3u8?args=web_03
$ ps ax | grep -oP 'JCB.{0,3}' | grep -v {
JCB001

$ ps ax | grep mpv
 1706 ?  Sl  0:01 mpv http://mtist.as.smartstream.ne.jp/30031/livestream/playlist.m3u8
$ ps ax | grep -oP 'jp/3.{0,4}' | grep -v {
jp/30031


検索は最初にJCBAを実行しています。結果が空のときはCSRA(ListenRadio)で検索し直します。
それも空のとき(CSRA(直)やNHKなど)はurlを取得します。


再生開始はCSRA(直)mms://が早いですね。
http(https)はubuntuコンテナで10秒前後かかっています。
RaspberryPi3はもう少し早い
現在のウィンドウは10局表示しています。20局位までにしておくと選局しやすいかな


Area Editボタンは表示中のエリアファイルを編集します。
ID EditボタンはIDファイルJCB(JCBA)ファイル、ListenRadio(CSRA)ファイルを編集します。
選択はmessagebox.askyesnoで簡易に済ませています。
エディタは使いやすいものに変更するといいでしょう。


radiko再生中
killall.shは共通で使っているのでSimul RadioからRadikoまた逆もOKです。
stopボタンはradikoも停止します。


favorite(お気に入り)ボタンは自由に使えます。
エリアリストの中から気に入った局をコピーして再登録します。
mpvは動画を再生できるのでyoutubeのurlをラジオリスト同様に登録しておけば気に入ってる動画を再生することもできます。依存で入ったyoutube-dlは削除して最新のyoutube-dlをインストールする必要があります。

see https://rg3.github.io/youtube-dl/download.html

$ sudo apt remove youtube-dl
$ sudo curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl
$ sudo chmod a+rx /usr/local/bin/youtube-dl
youtube-dlは定期的にアップデート
$ sudo youtube-dl -U


再生開始はキャッシュを取得するため多少時間がかかることがあります。

simul.py Station List

内容は無保証です。間違いなどがあれば勝手にバンバンしてください。
リストは220局くらいあると思います。
参考 simul.py

Station List
CSRA
JCBA
NHK

ID List
ListenRadio
JCB


ディレクトリ、ファイルの構成はこんな感じ
pythonプログラムなどは~/bin


ubuntuコンテナで作成したpythonファイル、リストをRaspberriPi3にコピー
音量、音質はラジオ局によって違います。
音量はかなり低めの局もあれば高すぎる局もあります。Suzuka Voice FMはバリバリレッドゾーンに入っていますね。当サイトの環境では歪んでいます。(場所柄難聴ラジオかな?)
時間外などはmusicbirdやj-wave配信の共通番組を流しているところも多いようです。

Python3 サイマルラジオ CSRA JCBA

Wikipediaより

SimulRadio(サイマルラジオ)は、コミュニティFMの自主制作番組をネット配信するサイト、およびそのためのプロジェクトである。

コミュニティ放送の役割の一つとして災害時の情報を提供することが挙げられます。
災害の現地に住んでいなくてもより詳しい情報を知り得たいという場面があるかもしれません。普段からもう少しサイマルラジオを聞くようにしたいと思います。
本当は各地のトーク番組などを聞きたいだけなのですが…ぼそ

聴取方法はPcのwebブラウザからやスマホのアプリケーションなどいろいろありますができるだけ良好な音でしかも軽快に利用したいためRaspberry PiなどのSBCから実行できるものとします。
CSRA、JCBAともurlがわかれば直接再生が可能です。

$ mpv "mms://hdv3.nkansai.tv/chofu"

目標は100局以上のラジオ局をエリア別に簡易なリストを作成、簡単なpythonプログラムで選局、再生するものとします。

ラジオ局リスト

自由に利用できるSimulRadioのプロジェクトにはCSRAとJCBAがあります。
現在JCBAはmusicbirdが配信しているようです。
CSRAは独自で配信している部分もありますがリスラジ(ListenRadio)に誘導されることもあります。実質配信に関してはリスラジが主体という印象があります。
どちらもまとまった形でurlリストを入手することは困難のようですが以下のサイトが参考になりました。
http://fsyublog.blog13.fc2.com/blog-entry-1214.html
JCBAインターネットサイマルラジオの仕様が変更された
CSRAは上記の記事を参考にリスラジ主体のリストを作成、JCBAはブラウザからurlを取得、取り敢えずテストに必要な分だけ揃えました。

Pi3のchromiumから取得
再生を開始
右クリック–検証–Networkタブ–Filter Boxに”m3u8″を打ち込む
chunklist_wxxx 右クリック–Copy–Copy link address

目的のurlはJCBxxxとm3u8が含まれているものです。
必要なアドレスはweb_03まで JCBxxxが固有のIDですね

https://chi-edge2-wow-relay1.leanstream.co/musicbird/_definst_/JCB019.stream/chunklist_w199142625.m3u8?args=web_03

# 追記
Filter Boxに打ち込む文字列はplaylist.m3u8ですね。基本のurlが取得できます。
JCBxxx=ラジオ局を紐付けできます。

https://musicbird-hls.leanstream.co/musicbird/JCB019.stream/playlist.m3u8?args=web_03


取得したurlをエリア別に局名とurlを1行形式で記述
エリアファイルは~/rajio/JCBA/以下に置いています。


ファイル JCB
IDと局名を記載 こちらは再生中の表示(ON AIR)に使います。

Python3 SimulRadio

pythonプログラムは前回作成したdocker container Listboxをベースに作成しました。

Listboxはエリアファイルを読み込むと局名とurlの2行で表示します。再生はurlを選択してPLAY
各エリアは瞬時に切り替えが可能です。
現在の不具合は起動時はスクロールバーが動作するのですがエリアを切り替えると動作しなくなります。但しマウスでのスクロール、及びキーボードでの操作は可能なので1エリア20〜30位の局数であれば問題ないと思います。


EDITボタンはエディタが起動して現在表示しているエリアの編集、新規追加が可能。
ON AIRボタンは再生中の局を表示、Yesでダイアログ終了、Noでファイル(JCB)の編集になります。ID以外はちょっとしたメモの追加など自由に編集できます。

python ~/bin/jcba.py

参考 jcba.py
~/bin/killall.shは前回radikoのものを使います。
Listbox
最初の方に起動時のデフォルトエリアを設定しています。

# default area
t = "/home/pi/radio/CSRA/関東"

mainでそのファイルを読み出しています。
エリアを切り替えるためにdef area_selection():で再度読み直しています。この時スクロールバー設定を引き継げないようです。

    # Listbox
    with open(t, 'r') as f:
        proc = f.read()
        currencies = (proc)
        v1 = StringVar(value=currencies)
        lb = Listbox(frame1, listvariable=v1,height=20,width=60)
        lb.grid(row=0, column=0)

# selection
def area_selection():
    global v1,lb
    with open(t, 'r') as f:
        proc = f.read()
        currencies = (proc)
        v1 = StringVar(value=currencies)
        lb = Listbox(frame1, listvariable=v1,height=20,width=60)
        lb.grid(row=0, column=0)

play
一旦killall.shを実行、Listboxで選択したurlを変数”a”にしてプレーヤー(mpv)で実行

# play
def button1_click():
    subprocess.run("~/bin/killall.sh", shell=True)
    play_selection()

# selection
def play_selection():
    for i in lb.curselection():
        a = (lb.get(i))
        command = ((("mpv ") + (a)) + (" &"))
        subprocess.run(command, shell=True)

ON AIR
ON AIRボタン(再生中の表示)はps axからurlの中のJCB+3文字を拾い出して、改行コードを削除、その値をreモジュールからファイル(JCB)を検索、読み出しています。

import re


コマンドラインから実行確認するとTypeErrorが出てしまいますが結果は正常に検索できているので取り敢えず様子を見ることにします(要調査)
TypeError: first argument must be string or compiled pattern

CSRA FM

CSRAで作成してみました。

単に再生することあればエリアリストをJCBAと共通にできるのですがプロジェクト別に管理、違いを確認したいこともあって別にしています。

まだまだエリアリストはできていないのですがCSRAで100局くらい、JCBAも同じくらいあるものと思います。
エリアリストを別にしたままCSRA,JCBA共通プログラムにしてみようと思います。

Python3-Tkinter radiko player

pythonボタンでradiko playerを作成してみました。

pythonボタンメニューのプログラムは基本共通なのでラジコスクリプト(関連アプリケーション)とアイコン画像を用意するだけで簡単に作成することができます。

確認はRaspberryPi3とOrangePi3のubuntuコンテナにインストール、確認しています。メインはubuntu-X(vnc)で使用する予定です。

インストール

python環境(python3.5以降, python3-tkなど)はあるものとします。
ubuntuコンテナからはpulseaudioをインストールして再生します。
再生プレーヤーは mpv mplayer ffplay を試してみます。

# apt install pulseaudio
# apt install mpv mplayer swftools libxml2-utils rtmpdump ffmpeg

ラジコスクリプト

当サイトではほとんど再生オンリーなので以前から使用しているスクリプトをそのまま使っています。
radiko.sh (chmod 755)

ラジコスクリプトの再生プレーヤーは168行目前後のコメントを付け替えて切り替えます。
ファイルは~/binに置いています。
各スクリプトは実行権限を付けておきます。(chmod 755)


インストールなど準備が済んだらコマンドラインから実行できることを確認します。
スクリプトメニューは関東圏を記載していますが地域に応じた放送局IDを指定します。
最軽量のプレーヤーはffplayですが安定性、音質などから最良と思われるmpvにしました。
キャッシュ設定にもよりますが以前はmplayerのほうが再生開始は早かったのですが現在はmpvが早くなっています。大体2秒位で開始します。

pythonラジコ

radiko.py (chmod 755)
2重起動を抑止及び停止。使用する可能性のあるプレーヤーを記載
~/bin/killall.sh (chmod 755)

#!/bin/sh
killall -q mpv
killall -q mplayer
killall -q ffplay


アイコンは~/.icons/radioに置いています。


ボタンカラーは# Button colorの以下2行のコメントを外すことで変更可


再生中の放送局はON AIRボタンで確認
Exitボタンはメニューを終了
subprocess.run(“killall.sh”)のコメントを外せば再生を停止して終了
Pi1B,Zeroはpythonの起動、radiko.shに時間がかかると思います。
Pi2以降であればほぼ問題ないでしょう。

次回はサイマルラジオプレーヤーを作成してみたいと思います。

Orange Pi 3 docker tool menu

当サイトではdockerを少しでも使いやすくするためメニューを作成していますが今回鯨を少しだけ増やしてみました。pythonの勉強です。

青鯨、黒鯨は今までのとおりですが新たに赤鯨と緑鯨を追加してみました。
dockerの状態やコントロールするためにpythonのListboxを作成してみたいと思います。

参考

雛形はボタンでもお世話になっているこちらのサイトのものを参考にしています。
https://python.keicode.com/advanced/tkinter-widget-scrollbar.php

docker container ls

docker container ls(docker ps)は基本ですね。
以下のコマンドを実行するとNAMESの項だけを取り出すことができます。

$ docker container ls | grep -v NAMES | grep .$ | awk '{print $NF}'
$ docker container ls -a | grep -v NAMES | grep .$ | awk '{print $NF}'


1行目を除外して文字列を検索、awkで最後の文字列を取得します。
この値を変数に読み込ませてリストに出力、表示します。


赤鯨は現在実行中のコンテナを表示します。
緑鯨は作成済みの全コンテナを取得します。

黒鯨は全コンテナを停止してしまいますが赤鯨はリストから選択して特定のコンテナを停止、または再スタートすることができます。
緑鯨はリストに載っているコンテナを選択してスタートorストップします。緑鯨からのスタート及びストップは実行後緑鯨は終了します。赤鯨が起動していれば再起動してリストに反映します。
また不要なコンテナを選択して削除します。
赤鯨を起動後コマンドラインからスタートしたコンテナはRELOADすることでリストに反映されますがリストの内容だけを書き換えることは難しかったので赤鯨を再起動することでリストに反映しています。(緑鯨も同様)

alpineでssh接続の様子
今までのボタン操作で起動しています。
赤鯨が起動しているとアプリケーションの再起動によってdwmのウインドウ位置が入れ替わります。赤鯨は確認、及び必要があるときに起動するとdwm本来の動作になります。
ウインドウ位置が変わるだけなので使いやすい方でいいのですが…


dockerメニューのコンテナボタンをクリックするとコンテナがスタートします。
コンテナがスタートするとメッセージボックスが出現します。コンテナによって違いますが数秒もしくはもう少しかかる場合もあります。赤鯨を起動していればメッセージボックスとともに赤鯨が再起動してリストに反映されます。Yesで接続(sleep 1)にいきますが即接続できるのはalpinだけですね。X(vnc)コンテナは20〜30秒待ったほうが確実と思います。

Noではコンテナ起動のままダイアログを閉じます。(接続は再度ボタンをクリック)
ssh or vnc接続のタイミングはヒューマン感覚に任せることにします。

緑鯨からは不要なコンテナを削除できます。
buildで失敗したときなどは外人さんふうのコンテナが作成されているときがあります。
そんなコンテナを削除するには便利と思います。(コンテナはSTOP)


当サイトではできるだけ新コマンド形式を使うようにしています。旧コマンドに比べると長ったらしくなるのですがエイリアスにするとイメージしやすくなります。
それでもやっぱり忘れるのでテキストボタンからすぐ呼び出せるようにしています。
画像では.cshrcですが.bashrcも記述方法が少し違うだけで同じように利用できます。

リストからnameをコピーできるのでペーストして簡単な編集確認はすぐできます。
(注)Osのコマンドとバッティングしないようにします。dc(計算機)がある場合が多いと思います。

ボタンカラー

ボタンや文字の色についてはなかなか的確な情報を見つけられなかったのですがこのページがヒットしました。
https://www.geeksforgeeks.org/python-add-style-to-tkinter-button/
最初NameErrorが出てうまくいかなかったのですがよく見るとimportする専用モジュールがあるんですね。似たような名称なので見逃していました。

Traceback (most recent call last):
  File "docker_run.py.txt", line 85, in 
    style = Style()
NameError: name 'Style' is not defined

緑鯨を例にすると追加した項目は以下のようになりました。
‘TButton’がデフォルトで摘要されます。
‘W.TButton’で個別に設定します。

from tkinter.ttk import *

    # Butoon color
    style = Style()
    style.configure('TButton', font=('calibri', 9), background='#A4DCBF')
    style.configure('W.TButton', font=('calibri', 9), foreground='#FF0089')

    button4 = ttk.Button(frame2, text='REMOVE', style='W.TButton', command=button4_click)

参考 赤鯨 緑鯨 dockerメニュー

赤鯨、緑鯨はコマンドラインの補助ツールです。
当サイトの環境で動作確認していますが自己責任で!
dockerメニューは一部コンテナボタン割愛しています。
ファイルの保存場所は~/binにしています。パスを通すか明示的に~/bin/xxx.pyのように変更する必要があると思います。
赤鯨 docker_run.py
緑鯨 docker_container.py
メニュー docker.py


管理ツール portainer
比べるまでもなく作成したpython toolよりははるかに高機能ですがportainer自体がdockerコンテナであること、webブラウザ経由であることを考慮するとpython tool(赤鯨、緑鯨)もコマンドラインのサブツールとして意外と便利かもしれません。

Top