上図の矢印部分に放送中のアーチスト名と曲名を表示する。
●PHP方式
参考サイトのサンプルのままでは、ネットラジオ局URLにport指定が無いとエラーになるので、無い場合はport:80を付加するようにした。
<?php
define('CRLF', "\r\n");
class streaminfo{
public $valid = false;
public $useragent = 'Winamp 2.81';
protected $headers = array();
protected $metadata = array();
public function __construct($location){
$errno = $errstr = '';
$t = parse_url($location);
if(isset($t['port']))
$sock = fsockopen($t['host'], $t['port'], $errno, $errstr, 5);
else
$sock = fsockopen($t['host'], 80, $errno, $errstr, 5);
$path = isset($t['path'])?$t['path']:'/';
if ($sock){
$request = 'GET '.$path.' HTTP/1.0' . CRLF .
'Host: ' . $t['host'] . CRLF .
'Connection: Close' . CRLF .
'User-Agent: ' . $this->useragent . CRLF .
'Accept: */*' . CRLF .
'icy-metadata: 1'.CRLF.
'icy-prebuffer: 65536'.CRLF.
(isset($t['user'])?'Authorization: Basic '.base64_encode($t['user'].':'.$t['pass']).CRLF:'').
'X-TipOfTheDay: Winamp "Classic" rulez all of them.' . CRLF . CRLF;
if (fwrite($sock, $request)){
$theaders = $line = '';
while (!feof($sock)){
$line = fgets($sock, 4096);
if('' == trim($line)){
break;
}
$theaders .= $line;
}
$theaders = explode(CRLF, $theaders);
foreach ($theaders as $header){
$t = explode(':', $header);
if (isset($t[0]) && trim($t[0]) != ''){
$name = preg_replace('/[^a-z][^a-z0-9]*/i','', strtolower(trim($t[0])));
array_shift($t);
$value = trim(implode(':', $t));
if ($value != ''){
if (is_numeric($value)){
$this->headers[$name] = (int)$value;
}else{
$this->headers[$name] = $value;
}
}
}
}
if (!isset($this->headers['icymetaint'])){
$data = ''; $metainterval = 512;
while(!feof($sock)){
$data .= fgetc($sock);
if (strlen($data) >= $metainterval) break;
}
$matches = array();
preg_match_all('/([\x00-\xff]{2})\x0\x0([a-z]+)=/i', $data, $matches, PREG_OFFSET_CAPTURE);
preg_match_all('/([a-z]+)=([a-z0-9\(\)\[\]., ]+)/i', $data, $matches, PREG_SPLIT_NO_EMPTY);
$title = $artist = '';
foreach ($matches[0] as $nr => $values){
$offset = $values[1];
$length = ord($values[0]{0}) +
(ord($values[0]{1}) * 256)+
(ord($values[0]{2}) * 256*256)+
(ord($values[0]{3}) * 256*256*256);
$info = substr($data, $offset + 4, $length);
$seperator = strpos($info, '=');
$this->metadata[substr($info, 0, $seperator)] = substr($info, $seperator + 1);
if (substr($info, 0, $seperator) == 'title') $title = substr($info, $seperator + 1);
if (substr($info, 0, $seperator) == 'artist') $artist = substr($info, $seperator + 1);
}
$this->metadata['streamtitle'] = $artist . ' - ' . $title;
}else{
$metainterval = $this->headers['icymetaint'];
$intervals = 0;
$metadata = '';
while(1){
$data = '';
while(!feof($sock)){
$data .= fgetc($sock);
if (strlen($data) >= $metainterval) break;
}
$len = join(unpack('c', fgetc($sock))) * 16;
if ($len > 0){
$metadata = str_replace("\0", '', fread($sock, $len));
break;
}else{
$intervals++;
if ($intervals > 100) break;
}
}
$metarr = explode(';', $metadata);
foreach ($metarr as $meta){
$t = explode('=', $meta);
if (isset($t[0]) && trim($t[0]) != ''){
$name = preg_replace('/[^a-z][^a-z0-9]*/i','', strtolower(trim($t[0])));
array_shift($t);
$value = trim(implode('=', $t));
if (substr($value, 0, 1) == '"' || substr($value, 0, 1) == "'"){
$value = substr($value, 1);
}
if (substr($value, -1) == '"' || substr($value, -1) == "'"){
$value = substr($value, 0, -1);
}
if ($value != ''){
$this->metadata[$name] = $value;
}
}
}
}
fclose($sock);
$this->valid = true;
}else echo 'unable to write.';
}else echo 'no socket '.$errno.' - '.$errstr.'.';
}
public function __get($name){
if (isset($this->metadata[$name])){
return $this->metadata[$name];
}
if (isset($this->headers[$name])){
return $this->headers[$name];
}
return null;
}
}
?>
●python方式
python方式も参考サイトのサンプルのままではステータスに"ICY 200 OK"を返すラジオ局はエラーになってしまうので、別のネット情報に有った"ICY 200 OK"を"HTTP/1.0 200 OK"に替える仕組みを追加した。
#!/usr/bin/env python3
from __future__ import print_function
import re
import struct
import sys
from pprint import pprint
try:
import urllib2
except ImportError: # Python 3
import urllib.request as urllib2
# 'ICY 200 OK' status対応('HTTP/1.0 200 OK'に変換)
def NiceToICY(self):
class InterceptedHTTPResponse():
pass
import io
line = self.fp.readline().replace(b"ICY 200 OK\r\n", b"HTTP/1.0 200 OK\r\n")
InterceptedSelf = InterceptedHTTPResponse()
InterceptedSelf.fp = io.BufferedReader(io.BytesIO(line))
InterceptedSelf.debuglevel = self.debuglevel
InterceptedSelf._close_conn = self._close_conn
return ORIGINAL_HTTP_CLIENT_READ_STATUS(InterceptedSelf)
# 'ICY 200 OK' status対応
ORIGINAL_HTTP_CLIENT_READ_STATUS = urllib2.http.client.HTTPResponse._read_status
urllib2.http.client.HTTPResponse._read_status = NiceToICY
if len(sys.argv) == 2:
url = sys.argv[1]
else:
url = 'http://94.23.201.38:8020/stream' # radio stream for test
#encoding = 'latin1' # default: iso-8859-1 for mp3 and utf-8 for ogg streams
encoding = 'utf8' # default: iso-8859-1 for mp3 and utf-8 for ogg streams
try:
request = urllib2.Request(url, headers={'Icy-MetaData': 1}) # request metadata
response = urllib2.urlopen(request)
metaint = int(response.headers['icy-metaint'])
except Exception as e:
sys.exit('no icy-metaint found')
for _ in range(5): # # title may be empty initially, try several times
response.read(metaint) # skip to metadata
metadata_length = struct.unpack('B', response.read(1))[0] * 16 # length byte
metadata = response.read(metadata_length).rstrip(b'\0\n')
# extract title from the metadata
if metadata.find(b"StreamTitle=") != -1:
title = metadata[metadata.find(b"='")+2:metadata.find(b"';")]
if title != b"": # available title
break
else:
sys.exit('no title found')
print(title.decode(encoding, errors='replace'))
sys.exit(0)
●raspi側再生プレーヤー修正
ラジオの放送中曲名が分かるようになったのでプレーヤーの機能追加修正を行った。また、ブラウザからPulseAudioで出力先をDAC等に切り替えても音声出力されなかったので、直にmpvコマンド実行からソケット通信で実行させる方法に変更した。
<?php
require "radio_meta.php";
function execPlay($song){
require_once('./shelper1.php');
$sockethelper = new sockethelper('localhost',5557);
$sockethelper->send_data("m$song");
$spkrs = $sockethelper->read_data() . PHP_EOL;
$sockethelper->close_socket();
}
function playReq($req_id)
{
if(is_numeric($req_id)){ // idが数値なら該当曲再生
$fullpath = getMusicPath($req_id);
# play中のものは中止して新たに再生
if(!empty(exec('pgrep mpv'))){exec('sudo pkill -15 mpv');}
exec('sudo -u pi rm ./tmp/album.jpg');
if(strpos($fullpath,'.flac') !== false){
$cmd = 'sudo -u pi metaflac "'.$fullpath.'" --export-picture-to=./tmp/album.jpg';
}else{
$cmd = 'sudo -u pi /usr/bin/ffmpeg -i "'.$fullpath.'" -an -s 150x150 -scodec copy ./tmp/album.jpg';
}
exec($cmd . ' 2>&1', $array, $st);
if($st==1){
$st_e = strrpos($fullpath, '/');
$st_path = substr($fullpath, 0, $st_e);
$arr = glob($st_path.'/*.jpg');
exec('sudo -u pi cp "'.$arr[0].'" ./tmp/album.jpg');
}
execPlay($fullpath);
$msg = '再生開始';
}elseif(strpos($req_id,'http') == 0){ // radio play
if(!empty(exec('pgrep mpv'))){exec('sudo pkill -15 mpv');} // mpvが起動していたらkill
execPlay($req_id);
$msg = 'RADIO開始';
}elseif(strpos($req_id,'rhttp') == 0){ // radio meta info
if(!empty(exec('pgrep mpv'))){ // mpvが起動していたらkill
$url = substr($req_id, 1);
$t = new streaminfo($url); // get metadata
if($t->valid){
if($t->streamtitle == ' - ') $msg = 'no title found';
else $msg = $t->streamtitle;
}
else $msg = 'invalid address';
}else{
$msg = 'no run mpv';
}
}elseif(!empty(exec('pgrep mpv'))){ // mpvが起動している
:
:中略
:
}else{
return 0;
}
return $msg;
}
function getMusicPath($id)
{
$attr = json_decode(file_get_contents('./tmp/sql-music.json'),true);
$playlist = $attr["playlist"];
try{
# sqlite3 DBアクセス
$db = new PDO('sqlite:./db/music.sqlite3');
# fetchモード設定
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_COLUMN);
$sql = "SELECT fpath FROM ".$playlist." WHERE id IN (0,".$id.");";
$mpath = $db->query($sql)->fetchAll();
$fullpath = $mpath[0].'/'.$mpath[1]; # base-path + music-path
} catch (Exception $e) {
echo $e->getMessage() . PHP_EOL;
}
$db = null;
return $fullpath;
}
$response = array();
if($_SERVER['REQUEST_METHOD'] == 'POST') {
// always return true if you save the contact data ok or false if it fails
$tmp = playReq($_POST['id']);
$response['status'] = $tmp!==0 ? 'success' : 'AP error';
$response['message'] = $response['status'] == 'success'
? $tmp
: 'Your Requested ID:'.$_POST['id'].' is incorrect.';
header('Content-type: application/json');
echo json_encode($response);
exit;
}
?>