25. Vue.jsを使用して音楽プレーヤー

Vue.jsと云うjavascriptのフレームワークの存在を知り、どんなものかとお試しで音楽プレーヤーを作ってみた。どうにかイメージ通りに作成出来たが、もう少し勉強が必要かな。
また、作成中に音楽再生プログラムmpvをリモート制御する方法を見つけたので導入してみた(参考サイトの"webui.lua"を利用する)。
更に、フランスのネットラジオ局FIPで放送中の曲のジャケット情報を入手する方法が分かったので取り入れてみた。

機能概要:

  1. トップ画面は、事前登録したローカルとNASの音楽フォルダと'youtube'と'radio'の項目を表示
  2. 音楽フォルダをクリックすると順次フォルダを開いていく
  3. フォルダ内音楽ファイル(mp3/m4a/flac)をクリックするとその曲を再生し、曲情報を表示する
  4. 音楽ファイルが有るフォルダを開いている時に、'PLAY'ボタンをクリックするとフォルダ内全曲順次再生
  5. フォルダ内全曲再生中に'FSKIP'や'BSKIP'ボタンを押すと次曲または前曲へ移動
  6. 途中で再生を止める場合は'STOP'ボタンを押す
  7. トップ画面で'youtube'を選ぶと事前登録したYouTubeソース一覧が表示され、どれかクリックすると再生
  8. トップ画面で'radio'を選ぶと事前登録したradio局一覧が表示され、どれかクリックすると再生
  9. 'youtube'と'radio'では'STOP'ボタンのみ有効('PLAY','FSKIP','BSKIP'は無効)
  10. 曲再生中は、一覧内項目のクリックは不可で、'STOP'してからクリックする


●MPVプレーヤーの再生中曲情報を得る
参考サイトから入手したスクリプト"webui.lua"を利用してMPVプレーヤーが再生している曲情報を得る。(音楽ファイルとFIP以外のネットラジオ)

"webui.lua"を使用するにあたり、raspiで実行するには下記インストールが必要だった lua-basexx-0.3-2 baseXX encoding/decoding library for Lua lua-socket-3.0~rc1+git+ac3201d-4 TCP/UDP socket library for the Lua language " ~/.config/mpv/scripts"フォルダに"webui.lua"を入れるとmpv実行時に自動でscriptが起動する * MPVから再生中の曲情報を得る handleStatusResponse: function(self, json) { self.position = this.format_time(json['position']); self.remaintime = this.format_time(json['remaining']); let metas = []; let metadata = json['metadata']; let tracklist = json['track-list']; let playlist = json['playlist']; if(metadata['track']) metas.push('Track -------- ' + metadata['track']); if(metadata['title']) metas.push('Title -------- ' + metadata['title']); else if(metadata['TITLE']) metas.push('Title -------- ' + metadata['TITLE']); else if(metadata['icy-title']) metas.push('Title -------- ' + metadata['icy-title']); // radio else metas.push('Title -------- ' + self.plist[0].item); metas.push('Duration ----- ' + this.format_time(json['duration'])); if(metadata['album']) metas.push('Album -------- ' + metadata['album']); else if(metadata['ALBUM']) metas.push('Album -------- ' + metadata['ALBUM']); else if(metadata['icy-name']) metas.push('Radio -------- ' + metadata['icy-name']); // radio if(metadata['artist']) metas.push('Artist ------- ' + metadata['artist']); else if(metadata['ARTIST']) metas.push('Artist ------- ' + metadata['ARTIST']); else if(metadata['icy-br']) metas.push('BitRate ------ ' + metadata['icy-br']); // radio if(metadata['genre']) metas.push('Genre -------- ' + metadata['genre']); else if(metadata['GENRE']) metas.push('Genre -------- ' + metadata['GENRE']); else if(metadata['icy-genre']) metas.push('Genre -------- ' + metadata['icy-genre']); // radio metas.push('Codec -------- ' + tracklist[0]['codec']); metas.push('SampleRate --- ' + Math.round(tracklist[0]['demux-samplerate']*100/1000)/100 + 'kHz'); self.tags = metas for (let i = 0; i < self.plist.length; i++){ // リスト内再生中の曲をセレクトにする self.plist[i].sel = playlist[i].hasOwnProperty('playing') ? true : false } }, getStatus: function(self){ var http = new XMLHttpRequest() if(self.radioFip) http.open('get', self.fipUrl + '?fip='+self.plist[0].item) else http.open('get', self.mpvUrl + 'status') http.onreadystatechange = function() { if (http.readyState === 4 && http.status === 200) { var json = JSON.parse(http.responseText); if ( self.radioFip ) self.handleStatusFip(self, json); else self.handleStatusResponse(self, json); }else if (http.status === 0) { // clear play related flags if (self.remaintime == "00:00:01"){ self.remaintime = "00:00:00" }; }; http.onerror = function() { console.log('onerror' + http.status + ' -- ' + http.statusText) }; http.send(null); },

●音楽ファイルからアルバム画像を得る
pythonのモジュール"mutagen"と"PIL"をインストールして作成。
音楽ファイルに画像データが含まれていない場合は、フォルダ内に有る画像ファイル(jpg/png)を使う。

#!/usr/bin/python3 # -*- coding: utf-8 -*- import os import sys import mutagen import glob import re from PIL import Image def getAlbumArt(song, imgfile): tags = mutagen.File(song) if 'APIC:' in tags: # MP3 artwork = tags.get("APIC:").data elif 'covr' in tags: # MP4 artwork = tags.get("covr")[0] elif tags.pictures: # FLAC print(tags.pictures[0].type) artwork = tags.pictures[0].data else: pathname = os.path.dirname(song) coverlist = [p for p in glob.glob(pathname+"/*") if re.search('/*\.(jpg|png)', str(p))] # 画像ファイル抽出 jpglist = [jpg for jpg in coverlist if '.jpg' in jpg] pnglist = [png for png in coverlist if '.png' in png] if len(jpglist) > 1: img = Image.open(jpglist[0]) else: img = Image.open(pnglist[0]) img = img.convert('RGB') # png → jpg変換 if img.size[0] > 300 or img.size[1] > 300: img = img.resize((300, int(300*img.size[1]/img.size[0]))) img.save(imgfile, quality=30) return 0 with open(imgfile,"wb") as img: img.write(artwork) return 0 if __name__ == '__main__': req = "all" # request function all:tags&image(default), tags:tags, img:image imgfile = "./image.jpg" # default image store file path song = "" # song file path for parm in sys.argv[1:]: if re.search('/*\.(jpg|png)', parm): imgfile = parm elif re.search('/*\.(mp3|m4a|flac|wav)', parm): song = parm if re.search('(all|tags|img)', parm): req = parm st = 0 if re.search('(all|img)', req): st = getAlbumArt(song, imgfile) sys.exit(st)

●YouTubeの再生情報を得る

YouTubeから再生情報を得るには"youtube-dl"を使用 youtube-dl --ignore-config --get-title --get-duration --get-description --get-thumbnail '.$code.' 2>/dev/null --get-duration : 再生時間 --get-description : ソース情報(登録者により内容はバラバラ) --get-thumbnail : 静止画像ファイルのアドレス

●radio FIPの再生情報
フランスのネットラジオ局 FIP では、その他のラジオ局の様にMPVで再生中曲情報を得られない。
ググっていたら特定のアドレスから情報が得られることが分かり利用した。しかし、再生音楽と得られる情報にタイムラグが有り、ディレイを置いて調整した。
また、当初、情報を得るために"file_get_contents()"を使用していたが、chromeブラウザではエラーになり(firefoxはOK)取得出来なかったので、サイト情報により"cURL"に変更したらエラーにならず取得出来た。

下記をPHPで作成 $fips = array( array('name'=>"FIP", 'url'=>"https://www.fip.fr/latest/api/graphql?operationName=Now&variables=%7B%22bannerPreset%22%3A%22600x600-noTransform%22%2C%22stationId%22%3A7%2C%22previousTrackLimit%22%3A3%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%228a931c7d177ff69709a79f4c213bd2403f0c11836c560bc22da55628d8100df8%22%7D%7D", 'delay' => 12),  :  中略  : ); $req = str_replace('radio ','',$_GET['fip']); $matchFip = array_search($req, array_column($fips, 'name')); $ch = curl_init(); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_URL, $fips[$matchFip]['url']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $json = curl_exec($ch); curl_close($ch); $json = (array)json_decode($json, true); $json[] = ['delay' => $fips[$matchFip]['delay']];

【参考】

  1. Vue.js とは?
  2. vue.jsを使ってaxiosを学ぶ
  3. ..is a web based user interface with controls for the mpv mediaplayer.
  4. webradio-metadata
  5. 容量の大きいpngファイルを、jpgファイルに変換する
  6. SSH(https)にfile_get_contents()が使えないときはcURLだよね
このページは"Vue.js"を利用してみました。
2020/01/18