【Volumio3】volumio_jpradioでRadikoを再生する環境構築

Volumio3

はじめまして、mOqOmです。
突然ですがVolumio3でRadikoを再生したいですよね!
有志の方が作成したvolumio_jpradioだと私の知識が足りなく起動ができず困っていたのでNode.jsに移植してみました。
2022/11/02時点でタイムフリーダウンロード機能を追加し、またサービス(デーモン)として自動起動設定を行うことも可能となっています。

このNode.jsはプラグイン化していますので、簡単にインストールしたい場合はβ版プラグインをお使いください。

では早速ですが、Node.jsに移植したvolumio_jpradioのインストール方法を記載したいと思います。

※あくまで私がやった方法ですので、環境によって手順が異なる場合があります。

まずインストールする環境は以下の通りとなっています。
※npmは後述の手順でアップデートを行い、その他のバージョンについてはVolumio3(Ver:3.378)をインストールした環境のままのバージョンです。
また以前のVolumioのバージョンではインストールされていなかったffmpegがデフォルトでインストールされているので手順を飛ばしています。

動作環境

  • Raspberry Pi 4 Model B(Raspberry Pi Zeroも同じ手順で可能ですが動作が重いので推奨していません。)
  • Volumio3(Ver:3.378)
  • Node.js(Ver:14.15.4)
  • npm(Ver:8.19.2)
  • Python3(Ver:3.7.3)
  • Python2(Ver:2.7.16)
  • ffmpeg(Ver:4.1.9-0+deb10u1)

インストール

以下の流れに沿ってインストールを進めていきます。

日本時間にするためTimezoneを変更

まずタイムゾーンをTokyoに変更します。

$ sudo dpkg-reconfigure tzdata
# Asia→Tokyoを選択

プロジェクトのクローン

※gitはデフォルトで入っていると思いますが入っていない方はインストールしてください。

$ git clone https://github.com/mOqOm/volumio_jpradio ./radio
$ cd radio

パッケージのインストール

npmのパッケージをインストールしていきます。

$ sudo apt-get update
$ sudo apt-get install -y build-essential
# npmのアップデート
$ sudo npm install -g npm
# パッケージのインストール
$ npm install express sqlite3 node-yaml-config date-utils xml2js log4js got capitalize m3u-file-parser icy-metadata cron tough-cookie

動作確認

volumio_jpradioが動作するか軌道の確認を行います。

$ node /home/volumio/radio/app.js
# Volumio3の「Playlist」>「Radiko」から選局

自動起動(サービス化)

起動確認までできれば残りは、デーモンに登録する手順のみです。
これをすると、Volumio3の起動時にvolumio_jpradioが自動で起動されます。

サービスの作成

まず、サービスが格納されるフォルダとファイルを作成します。

$ sudo mkdir /usr/lib/systemd/system
$ sudo nano /usr/lib/systemd/system/radio.service

radio.serviceの書込み内容

radio.serviceの書込み内容の説明は省略しますが以下のようになっています。

[Unit]
Description=radio
After=network.target

[Service]
ExecStart=/usr/bin/node /home/volumio/radio/app.js
Restart=always
User=volumio
Group=volumio
KillMode=process
WorkingDirectory=/home/volumio/radio

[Install]
WantedBy=multi-user.target

デーモンのリロードと自動起動設定

デーモンのリロードとRadoサービスの自動起動設定を以下のコマンドで実施します。

$ sudo systemctl daemon-reload
$ sudo systemctl start radio.service

$ sudo systemctl enable radio.service

Radikoのログインについて

最後にRadikoのプレミアム登録されている方向けの内容になります。
Radikoのアカウントでログインを行うには、プロジェクトをクローンした際にある「config.yaml」ファイルを変更する必要があります。
下記の変更前、変更後の差分のようにmailとpassの「#」を削除し、自身のアカウント情報を入力するとログインできます。

変更前

  account :
    #mail : xxx@yyy.zzz
    #pass : foobarhogehoge

変更後

  account :
    mail : xxx@yyy.zzz
    pass : foobarhogehoge

おわりに

この移植は完全ではないと思われるのでバグ等があった場合はできる限り修正を行いたいと思いますので、お力添えをお願いいたします。
それではよいVolumioライフを!

GithubのURL

参考サイト

コメント

  1. Trunkene より:

    どうも!有志の(笑)Trunkeneです。最近になって、こちらのブログに気が付きました。
    僕の作った拙いモジュールを使っていただきありがとうございますwww
    最近、備忘録的にブログも始めたので、このコメント欄で紹介させていただきます(^▽^)/

    • mOqOm mOqOm より:

      Trunkene様
      mOqOmです。
      コメントありがとうございます!
      まさかコメントしてくださるとは思いもしませんでした(笑
      > 最近、備忘録的にブログも始めたので、このコメント欄で紹介させていただきます(^▽^)/
      ぜひぜひ紹介していってください!
      私も記事を楽しみにまってます!

  2. Trunkene より:

    まぁ、こんな感じです。
    https://note.com/cute_zebra764/n/n8c40eb23b39d

    あと、最近までほったらかしにしていたので、全く気が付きませんでしたが
    radiko有料会員さん向けのlogin urlが変わってたみたいで
    下記の記事も目にしましたので、ご参考までに。
    https://onotori.com/linux/4247/

    • mOqOm mOqOm より:

      Trunkene様
      ありがとうございます!
      早速フォローさせていただきました!
      > radiko有料会員さん向けのlogin urlが変わってたみたいで
       下記の記事も目にしましたので、ご参考までに。
      こちらにつてもありがとうございます!

'}},vars:{discoveredBrushes:null,highlighters:{}},brushes:{},regexLib:{multiLineCComments:XRegExp("/\\*.*?\\*/","gs"),singleLineCComments:/\/\/.*$/gm,singleLinePerlComments:/#.*$/gm,doubleQuotedString:/"([^\\"\n]|\\.)*"/g,singleQuotedString:/'([^\\'\n]|\\.)*'/g,multiLineDoubleQuotedString:XRegExp('"([^\\\\"]|\\\\.)*"',"gs"),multiLineSingleQuotedString:XRegExp("'([^\\\\']|\\\\.)*'","gs"),xmlComments:XRegExp("(<|<)!--.*?--(>|>)","gs"),url:/https?:\/\/[\w-.\/?%&=:@;#]*/g,phpScriptTags:{left:/(<|<)\?(?:=|php)?/g,right:/\?(>|>)/g,eof:!0},aspScriptTags:{left:/(<|<)%=?/g,right:/%(>|>)/g},scriptScriptTags:{left:/(<|<)\s*script.*?(>|>)/gi,right:/(<|<)\/\s*script\s*(>|>)/gi}},toolbar:{getHtml:function(e){function t(e,t){return B.toolbar.getButtonHtml(e,t,B.config.strings[t])}for(var n='
',r=B.toolbar.items,i=r.list,a=0,l=i.length;l>a;a++)n+=(r[i[a]].getHtml||t)(e,i[a]);return n+=""},getButtonHtml:function(t,n,r){return n=e(n),''+e(r)+""},handler:function(e){function t(e){var t=RegExp(e+"_(\\w+)"),n=t.exec(r);return n?n[1]:null}var n=e.target,r=n.className||"",i=s(g(n,".syntaxhighlighter").id),a=t("command");i&&a&&B.toolbar.items[a].execute(i),e.preventDefault()},items:{list:["expandSource","help"],expandSource:{getHtml:function(e){if(1!=e.getParam("collapse"))return"";var t=e.getParam("title");return B.toolbar.getButtonHtml(e,"expandSource",t?t:B.config.strings.expandSource)},execute:function(e){var t=o(e.id);r(t,"collapsed")}},help:{execute:function(){var e=x("","_blank",500,250,"scrollbars=0"),t=e.document;t.write(B.config.strings.aboutDialog),t.close(),e.focus()}}}},findElements:function(e,t){var n=t?[t]:i(document.getElementsByTagName(B.config.tagName)),r=B.config,a=[];if(r.useScriptTags&&(n=n.concat(A())),0===n.length)return a;for(var l=0,s=n.length;s>l;l++){var o={target:n[l],params:p(e,E(n[l].className))};null!=o.params.brush&&a.push(o)}return a},highlight:function(e,t){var n=this.findElements(e,t),r="innerHTML",i=null,a=B.config;if(0!==n.length)for(var l=0,s=n.length;s>l;l++){var o,t=n[l],u=t.target,c=t.params,g=c.brush;if(null!=g){if("true"==c["html-script"]||1==B.defaults["html-script"])i=new B.HtmlScript(g),g="htmlscript";else{var h=b(g);if(!h)continue;i=new h}o=u[r],a.useScriptTags&&(o=M(o)),""!=(u.title||"")&&(c.title=u.title),c.brush=g,i.init(c),t=i.getDiv(o),""!=(u.id||"")&&(t.id=u.id),u.parentNode.replaceChild(t,u)}}},all:function(e){m(window,"load",function(){B.highlight(e)})}};return B.Match=function(e,t,n){this.value=e,this.index=t,this.length=e.length,this.css=n,this.brushName=null},B.Match.prototype.toString=function(){return this.value},B.HtmlScript=function(e){function t(e,t){for(var n=0,r=e.length;r>n;n++)e[n].index+=t}function n(e){for(var n,a=e.code,l=[],s=r.regexList,o=e.index+e.left.length,u=r.htmlScript,c=0,g=s.length;g>c;c++)n=L(a,s[c]),t(n,o),l=l.concat(n);null!=u.left&&null!=e.left&&(n=L(e.left,u.left),t(n,e.index),l=l.concat(n)),null!=u.right&&null!=e.right&&(n=L(e.right,u.right),t(n,e.index+e[0].lastIndexOf(e.right)),l=l.concat(n));for(var h=0,g=l.length;g>h;h++)l[h].brushName=i.brushName;return l}var r,i=b(e),a=new B.brushes.Xml,l=this,s="getDiv getHtml init".split(" ");if(null!=i){r=new i;for(var o=0,u=s.length;u>o;o++)(function(){var e=s[o];l[e]=function(){return a[e].apply(a,arguments)}})();return null==r.htmlScript?(v(B.config.strings.brushNotHtmlScript+e),void 0):(a.regexList.push({regex:r.htmlScript.code,func:n}),void 0)}},B.Highlighter=function(){},B.Highlighter.prototype={getParam:function(e,t){var n=this.params[e];return d(null==n?t:n)},create:function(e){return document.createElement(e)},findMatches:function(e,t){var n=[];if(null!=e)for(var r=0,i=e.length;i>r;r++)"object"==typeof e[r]&&(n=n.concat(L(t,e[r])));return this.removeNestedMatches(n.sort(k))},removeNestedMatches:function(e){for(var t=0,n=e.length;n>t;t++)if(null!==e[t])for(var r=e[t],i=r.index+r.length,a=t+1,n=e.length;n>a&&null!==e[t];a++){var l=e[a];if(null!==l){if(l.index>i)break;l.index==r.index&&l.length>r.length?e[t]=null:l.index>=r.index&&i>l.index&&(e[a]=null)}}return e},figureOutLineNumbers:function(e){var t=[],n=parseInt(this.getParam("first-line"));return y(e,function(e,r){t.push(r+n)}),t},isLineHighlighted:function(e){var t=this.getParam("highlight",[]);return"object"!=typeof t&&null==t.push&&(t=[t]),-1!=h(t,""+e)},getLineHtml:function(e,t,n){var r=["line","number"+t,"index"+e,"alt"+(""+(0==t%2?1:2))];return this.isLineHighlighted(t)&&r.push("highlighted"),0==t&&r.push("break"),'
'+n+""},getLineNumbersHtml:function(e,t){var n="",r=a(e).length,i=parseInt(this.getParam("first-line")),l=this.getParam("pad-line-numbers");1==l?l=(""+(i+r-1)).length:1==isNaN(l)&&(l=0);for(var s=0;r>s;s++){var o=t?t[s]:i+s,e=0==o?B.config.space:S(o,l);n+=this.getLineHtml(s,o,e)}return n},getCodeLinesHtml:function(e,t){e=C(e);for(var n=a(e),r=(this.getParam("pad-line-numbers"),parseInt(this.getParam("first-line"))),e="",i=this.getParam("brush"),l=0,s=n.length;s>l;l++){var o=n[l],u=/^( |\s)+/.exec(o),c=null,g=t?t[l]:r+l;null!=u&&(c=""+u[0],o=o.substr(c.length),c=c.replace(" ",B.config.space)),o=C(o),0==o.length&&(o=B.config.space),e+=this.getLineHtml(l,g,(null!=c?''+c+"":"")+o)}return e},getTitleHtml:function(t){return t?""+e(t)+"":""},getMatchesHtml:function(e,t){function n(e){var t=e?e.brushName||a:a;return t?t+" ":""}for(var r=0,i="",a=this.getParam("brush",""),l=0,s=t.length;s>l;l++){var o,u=t[l];null!==u&&0!==u.length&&(o=n(u),i+=N(e.substr(r,u.index-r),o+"plain")+N(u.value,o+u.css),r=u.index+u.length+(u.offset||0))}return i+=N(e.substr(r),n()+"plain")},getHtml:function(t){var n,r,i,a="",s=["syntaxhighlighter"];return 1==this.getParam("light")&&(this.params.toolbar=this.params.gutter=!1),className="syntaxhighlighter",1==this.getParam("collapse")&&s.push("collapsed"),0==(gutter=this.getParam("gutter"))&&s.push("nogutter"),s.push(this.getParam("class-name")),s.push(this.getParam("brush")),t=w(t).replace(/\r/g," "),n=this.getParam("tab-size"),t=1==this.getParam("smart-tabs")?R(t,n):H(t,n),this.getParam("unindent")&&(t=P(t)),gutter&&(i=this.figureOutLineNumbers(t)),r=this.findMatches(this.regexList,t),a=this.getMatchesHtml(t,r),a=this.getCodeLinesHtml(a,i),this.getParam("auto-links")&&(a=I(a)),"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.match(/MSIE/)&&s.push("ie"),a='
'+(this.getParam("toolbar")?B.toolbar.getHtml(this):"")+''+this.getTitleHtml(this.getParam("title"))+""+""+(gutter?'
'+this.getLineNumbersHtml(t)+"":"")+''+'
'+a+""+""+""+""+""+""},getDiv:function(e){null===e&&(e=""),this.code=e;var t=this.create("div");return t.innerHTML=this.getHtml(e),this.getParam("toolbar")&&m(c(t,".toolbar"),"click",B.toolbar.handler),this.getParam("quick-code")&&m(c(t,".code"),"dblclick",X),t},init:function(e){this.id=f(),u(this),this.params=p(B.defaults,e||{}),1==this.getParam("light")&&(this.params.toolbar=this.params.gutter=!1)},getKeywords:function(e){return e=e.replace(/^\s+|\s+$/g,"").replace(/\s+/g,"|"),"\\b(?:"+e+")\\b"},forHtmlScript:function(e){var t={end:e.right.source};e.eof&&(t.end="(?:(?:"+t.end+")|$)"),this.htmlScript={left:{regex:e.left,css:"script"},right:{regex:e.right,css:"script"},code:XRegExp("(?"+e.left.source+")"+"(?.*?)"+"(?"+t.end+")","sgi")}}},B}();"undefined"!=typeof exports?exports.SyntaxHighlighter=SyntaxHighlighter:null ");const o=ie?ie.createHTML(e):e;if(nt===tt)try{t=(new W).parseFromString(o,at)}catch(e){}if(!t||!t.documentElement){t=le.createDocument(nt,"template",null);try{t.documentElement.innerHTML=ot?ae:o}catch(e){}}const i=t.body||t.documentElement;return e&&n&&i.insertBefore(r.createTextNode(n),i.childNodes[0]||null),nt===tt?ue.call(t,Ie?"html":"body")[0]:Ie?t.documentElement:i},bt=function(e){return ce.call(e.ownerDocument||e,e,H.SHOW_ELEMENT|H.SHOW_COMMENT|H.SHOW_TEXT|H.SHOW_PROCESSING_INSTRUCTION|H.SHOW_CDATA_SECTION,null)},St=function(e){return e instanceof B&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof z)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},Rt=function(e){return"function"==typeof b&&e instanceof b},wt=function(e,t,n){pe[e]&&u(pe[e],(e=>{e.call(o,t,n,ut)}))},Ct=function(e){let t=null;if(wt("beforeSanitizeElements",e,null),St(e))return _t(e),!0;const n=st(e.nodeName);if(wt("uponSanitizeElement",e,{tagName:n,allowedTags:Ne}),e.hasChildNodes()&&!Rt(e.firstElementChild)&&_(/<[/\w]/g,e.innerHTML)&&_(/<[/\w]/g,e.textContent))return _t(e),!0;if(e.nodeType===J)return _t(e),!0;if(Me&&e.nodeType===Q&&_(/<[/\w]/g,e.data))return _t(e),!0;if(!Ne[n]||Ce[n]){if(!Ce[n]&&Dt(n)){if(we.tagNameCheck instanceof RegExp&&_(we.tagNameCheck,n))return!1;if(we.tagNameCheck instanceof Function&&we.tagNameCheck(n))return!1}if(Ye&&!qe[n]){const t=re(e)||e.parentNode,n=oe(e)||e.childNodes;if(n&&t){for(let o=n.length-1;o>=0;--o){const r=X(n[o],!0);r.__removalCount=(e.__removalCount||0)+1,t.insertBefore(r,$(e))}}}return _t(e),!0}return e instanceof R&&!Et(e)?(_t(e),!0):"noscript"!==n&&"noembed"!==n&&"noframes"!==n||!_(/<\/no(script|embed|frames)/i,e.innerHTML)?(ke&&e.nodeType===Z&&(t=e.textContent,u([fe,de,he],(e=>{t=g(t,e," ")})),e.textContent!==t&&(p(o.removed,{element:e.cloneNode()}),e.textContent=t)),wt("afterSanitizeElements",e,null),!1):(_t(e),!0)},Lt=function(e,t,n){if(Be&&("id"===t||"name"===t)&&(n in r||n in mt))return!1;if(ve&&!Le[t]&&_(ge,t));else if(De&&_(Te,t));else if(!Se[t]||Le[t]){if(!(Dt(e)&&(we.tagNameCheck instanceof RegExp&&_(we.tagNameCheck,e)||we.tagNameCheck instanceof Function&&we.tagNameCheck(e))&&(we.attributeNameCheck instanceof RegExp&&_(we.attributeNameCheck,t)||we.attributeNameCheck instanceof Function&&we.attributeNameCheck(t))||"is"===t&&we.allowCustomizedBuiltInElements&&(we.tagNameCheck instanceof RegExp&&_(we.tagNameCheck,n)||we.tagNameCheck instanceof Function&&we.tagNameCheck(n))))return!1}else if(Ze[t]);else if(_(Ae,g(n,Ee,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==T(n,"data:")||!Ke[e]){if(Oe&&!_(ye,g(n,Ee,"")));else if(n)return!1}return!0},Dt=function(e){return"annotation-xml"!==e&&h(e,_e)},vt=function(e){wt("beforeSanitizeAttributes",e,null);const{attributes:t}=e;if(!t)return;const n={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:Se};let r=t.length;for(;r--;){const i=t[r],{name:a,namespaceURI:l,value:c}=i,s=st(a);let p="value"===a?c:y(c);if(n.attrName=s,n.attrValue=p,n.keepAttr=!0,n.forceKeepAttr=void 0,wt("uponSanitizeAttribute",e,n),p=n.attrValue,n.forceKeepAttr)continue;if(At(a,e),!n.keepAttr)continue;if(!xe&&_(/\/>/i,p)){At(a,e);continue}if(Me&&_(/((--!?|])>)|<\/(style|title)/i,p)){At(a,e);continue}ke&&u([fe,de,he],(e=>{p=g(p,e," ")}));const f=st(e.nodeName);if(Lt(f,s,p)){if(!We||"id"!==s&&"name"!==s||(At(a,e),p=Ge+p),ie&&"object"==typeof G&&"function"==typeof G.getAttributeType)if(l);else switch(G.getAttributeType(f,s)){case"TrustedHTML":p=ie.createHTML(p);break;case"TrustedScriptURL":p=ie.createScriptURL(p)}try{l?e.setAttributeNS(l,a,p):e.setAttribute(a,p),St(e)?_t(e):m(o.removed)}catch(e){}}}wt("afterSanitizeAttributes",e,null)},Ot=function e(t){let n=null;const o=bt(t);for(wt("beforeSanitizeShadowDOM",t,null);n=o.nextNode();)wt("uponSanitizeShadowNode",n,null),Ct(n)||(n.content instanceof s&&e(n.content),vt(n));wt("afterSanitizeShadowDOM",t,null)};return o.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=null,r=null,i=null,l=null;if(ot=!e,ot&&(e="\x3c!--\x3e"),"string"!=typeof e&&!Rt(e)){if("function"!=typeof e.toString)throw A("toString is not a function");if("string"!=typeof(e=e.toString()))throw A("dirty is not a string, aborting")}if(!o.isSupported)return e;if(Ue||ft(t),o.removed=[],"string"==typeof e&&(je=!1),je){if(e.nodeName){const t=st(e.nodeName);if(!Ne[t]||Ce[t])throw A("root node is forbidden and cannot be sanitized in-place")}}else if(e instanceof b)n=Nt("\x3c!----\x3e"),r=n.ownerDocument.importNode(e,!0),r.nodeType===V&&"BODY"===r.nodeName||"HTML"===r.nodeName?n=r:n.appendChild(r);else{if(!Fe&&!ke&&!Ie&&-1===e.indexOf("<"))return ie&&ze?ie.createHTML(e):e;if(n=Nt(e),!n)return Fe?null:ze?ae:""}n&&Pe&&_t(n.firstChild);const c=bt(je?e:n);for(;i=c.nextNode();)Ct(i)||(i.content instanceof s&&Ot(i.content),vt(i));if(je)return e;if(Fe){if(He)for(l=se.call(n.ownerDocument);n.firstChild;)l.appendChild(n.firstChild);else l=n;return(Se.shadowroot||Se.shadowrootmode)&&(l=me.call(a,l,!0)),l}let m=Ie?n.outerHTML:n.innerHTML;return Ie&&Ne["!doctype"]&&n.ownerDocument&&n.ownerDocument.doctype&&n.ownerDocument.doctype.name&&_(q,n.ownerDocument.doctype.name)&&(m="\n"+m),ke&&u([fe,de,he],(e=>{m=g(m,e," ")})),ie&&ze?ie.createHTML(m):m},o.setConfig=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};ft(e),Ue=!0},o.clearConfig=function(){ut=null,Ue=!1},o.isValidAttribute=function(e,t,n){ut||ft({});const o=st(e),r=st(t);return Lt(o,r,n)},o.addHook=function(e,t){"function"==typeof t&&(pe[e]=pe[e]||[],p(pe[e],t))},o.removeHook=function(e){if(pe[e])return m(pe[e])},o.removeHooks=function(e){pe[e]&&(pe[e]=[])},o.removeAllHooks=function(){pe={}},o}();return oe}))
タイトルとURLをコピーしました