diff --git a/etc/nginx/fastcgi_params b/etc/nginx/fastcgi_params index 7e997c3fb..6f286814d 100755 --- a/etc/nginx/fastcgi_params +++ b/etc/nginx/fastcgi_params @@ -1,5 +1,7 @@ # # 2017-MM-DD TC moOde 4.0 +# 2018-MM-DD TC moOde 4.3 +# - revised fastcgi_params # fastcgi_param QUERY_STRING $query_string; @@ -28,9 +30,21 @@ fastcgi_param SERVER_NAME $server_name; # PHP only, required if PHP was built with --enable-force-cgi-redirect fastcgi_param REDIRECT_STATUS 200; -# TC for performance bump +# TC moOde 4.3 testing +fastcgi_buffers 8 16k; +fastcgi_buffer_size 32k; fastcgi_read_timeout 600000; -fastcgi_buffer_size 4k; -fastcgi_buffers 4 32k; fastcgi_busy_buffers_size 96k; +# TC moOde 4.0 original +#fastcgi_buffers 4 32k; +#fastcgi_buffer_size 4k; +#fastcgi_read_timeout 600000; +#fastcgi_busy_buffers_size 96k; + +# TC article suggested +#fastcgi_buffers 8 16k; +#fastcgi_buffer_size 32k; +#fastcgi_connect_timeout 300; +#fastcgi_send_timeout 300; +#fastcgi_read_timeout 300; diff --git a/mpd/RADIO/Amys FM (320K).pls b/mpd/RADIO/Amys FM (320K).pls new file mode 100755 index 000000000..e620dc6f1 --- /dev/null +++ b/mpd/RADIO/Amys FM (320K).pls @@ -0,0 +1,6 @@ +[playlist] +numberofentries=1 +File1=http://94.23.222.12:8024/amysfm +Title1=Amys FM (320K) +Length1=-1 +Version=2 diff --git a/mpd/RADIO/Amys FM Spirit of Soul (320K).pls b/mpd/RADIO/Amys FM Spirit of Soul (320K).pls new file mode 100755 index 000000000..b5e554ff5 --- /dev/null +++ b/mpd/RADIO/Amys FM Spirit of Soul (320K).pls @@ -0,0 +1,6 @@ +[playlist] +numberofentries=1 +File1=http://91.121.59.45:10073/amysfmspiritofsoul +Title1=Amys FM Spirit of Soul (320K) +Length1=-1 +Version=2 diff --git a/mpd/mpd.conf.default b/mpd/mpd.conf.default index e38116850..b3145f45d 100755 --- a/mpd/mpd.conf.default +++ b/mpd/mpd.conf.default @@ -29,7 +29,7 @@ buffer_before_play "10%" max_output_buffer_size "81920" id3v1_encoding "UTF-8" filesystem_charset "UTF-8" -max_connections "20" +max_connections "128" decoder { plugin "ffmpeg" diff --git a/mpd/playlists/Favorites.m3u b/mpd/playlists/Favorites.m3u new file mode 100755 index 000000000..e69de29bb diff --git a/other/build/build_recipe_v2.5.txt b/other/build/build_recipe_v2.6.txt similarity index 94% rename from other/build/build_recipe_v2.5.txt rename to other/build/build_recipe_v2.6.txt index a13740c29..0eced6382 100644 --- a/other/build/build_recipe_v2.5.txt +++ b/other/build/build_recipe_v2.6.txt @@ -1,8 +1,8 @@ ################################################################ # -# Build Recipe v2.5, 2018-07-11 +# Build Recipe v2.6, 2018-09-27 # -# moOde 4.2 +# moOde 4.3 # # These instructions are written for Linux Enthusiasts # and System Integrators and provide a recipe for making @@ -15,6 +15,15 @@ # # Changes: # +# v2.6: Move timezone setting to after ssh login in STEP 2 +# Replace Browse tab w/Music tab in STEP 12 1b. +# Correct STEP 1 Option 2 to use 2018-06-27 Stretch Lite +# Update root symlink +# Bump to kernel 4.14.72 in STEP 11 +# Add libaudiofile for wav to mpd compile in STEP 6 +# Add php7.0-gd in STEP 3 +# Add COMPONENT 4B - Librespot +# Add 6. Patch for upmpdcli gmusic plugin to COMPONENT 6. # v2.5: Add -DGPIO cflag to new Squeezelite compile in COMPONENT 5. # Use BlueZ-master-4e926f8.zip in STEP 4 # Use bluez-alsa-master-88aefee.zip in STEP 4 @@ -151,8 +160,8 @@ sudo poweroff // OPTION 2: Using Windows or Mac computer //////////////////////////////////////////////////////////////// -1. Download Raspbian Stretch Lite 2017-11-29 -http://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2017-12-01/2017-11-29-raspbian-stretch-lite.zip +1. Download Raspbian Stretch Lite 2018-06-27 +http://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2018-06-29/2018-06-27-raspbian-stretch-lite.zip 2. Unzip and install the .img file to an SD Card https://www.raspberrypi.org/documentation/installation/installing-images/ @@ -194,9 +203,7 @@ net.ifnames=0 1. Insert the SD Card into a Raspberry Pi and POWER UP. -2. sudo timedatectl set-timezone "America/Detroit" - -3. Change the current password (raspberry) to moodeaudio and the host name to moode. +2. Change the current password (raspberry) to moodeaudio and the host name to moode. ssh pi@raspberrypi (pwd=raspberry) @@ -204,6 +211,10 @@ echo "pi:moodeaudio" | sudo chpasswd sudo sed -i "s/raspberrypi/moode/" /etc/hostname sudo sed -i "s/raspberrypi/moode/" /etc/hosts +3. Change timezone to local time zone + +sudo timedatectl set-timezone "America/Detroit" + 4. Download moOde application sources and configs. // @@ -213,8 +224,8 @@ sudo sed -i "s/raspberrypi/moode/" /etc/hosts // cd ~ -wget http://moodeaudio.org/downloads/prod/rel-stretch-r42.zip -sudo unzip ./rel-stretch-r42.zip +wget http://moodeaudio.org/downloads/prod/rel-stretch-r43.zip +sudo unzip ./rel-stretch-r43.zip 5. Expand the root partition to 3GB. @@ -257,7 +268,7 @@ sudo reboot sudo apt-get update -sudo apt-get -y install rpi-update php-fpm nginx sqlite3 php-sqlite3 memcached php-memcache mpc \ +sudo apt-get -y install rpi-update php-fpm nginx sqlite3 php-sqlite3 memcached php-memcache php7.0-gd mpc \ bs2b-ladspa libbs2b0 libasound2-plugin-equal telnet automake sysstat squashfs-tools tcpdump shellinabox \ samba smbclient udisks-glue ntfs-3g exfat-fuse git inotify-tools libav-tools avahi-utils @@ -383,7 +394,7 @@ sudo chmod 0666 /etc/mpd.conf 2. Install MPD dev libs. sudo apt-get -y install libmad0-dev libmpg123-dev libid3tag0-dev \ -libflac-dev libvorbis-dev libfaad-dev \ +libflac-dev libvorbis-dev libaudiofile-dev libfaad-dev \ libwavpack-dev \ libavcodec-dev libavformat-dev \ libmp3lame-dev \ @@ -423,7 +434,7 @@ sudo ./configure --enable-database --enable-libmpdclient --enable-alsa \ --disable-wildmidi --disable-sqlite --disable-jack --disable-ao --disable-oss \ --disable-ipv6 --disable-pulse --disable-nfs --disable-smbclient \ --disable-upnp --disable-expat --disable-lsr \ ---disable-sndfile --disable-audiofile --disable-sidplay +--disable-sndfile --disable-sidplay 5. Compile and install. @@ -472,7 +483,7 @@ sudo mkdir /mnt/SDCARD sudo ln -s /mnt/NAS /var/lib/mpd/music/NAS sudo ln -s /mnt/SDCARD /var/lib/mpd/music/SDCARD sudo ln -s /media /var/lib/mpd/music/USB -sudo ln -s /var/lib/mpd/music /var/www/vlmm03846271 +sudo ln -s /var/lib/mpd/music /var/www/95187460 # Logs sudo touch /var/log/moode.log sudo chmod 0666 /var/log/moode.log @@ -624,8 +635,8 @@ sudo reboot // STEP 11 - Optionally install updated Linux Kernel -# kernel ver 4.14.54 -echo "y" | sudo PRUNE_MODULES=1 rpi-update ec9d84e1d2ba701fd28897809269d8116b31dbf5 +# kernel ver 4.14.72 +echo "y" | sudo PRUNE_MODULES=1 rpi-update 0abe903f4a137e2738fe3be5f14a2a34afc9762b sudo rm -rf /lib/modules.bak sudo rm -rf /boot.bak @@ -643,7 +654,7 @@ sudo reboot 1. Initial configuration a. http://moode -b. Browse Tab, Default Playlist, Add +b. Music Tab, Browse button, Default Playlist, Add c. Menu, Configure, Sources, UPDATE mpd database d. Menu, Audio, Mpd options, EDIT SETTINGS, APPLY e. Menu, System, Set timezone @@ -746,14 +757,14 @@ sudo git clone https://github.com/hrkfdn/mpdas cd mpdas sudo make sudo cp ./mpdas /usr/local/bin -cd ~/ +cd ~ sudo rm -rf ./mpdas sudo cp ./rel-stretch/usr/local/etc/mpdasrc.default /usr/local/etc/mpdasrc sudo chmod 0755 /usr/local/etc/mpdasrc //////////////////////////////////////////////////////////////// // -// COMPONENT 4 - Shairport-sync +// COMPONENT 4A - Shairport-sync // //////////////////////////////////////////////////////////////// @@ -772,6 +783,28 @@ cd ~ sudo rm -rf ./shairport-sync sudo cp ./rel-stretch/usr/local/etc/shairport-sync.conf /usr/local/etc +//////////////////////////////////////////////////////////////// +// +// COMPONENT 4B - Librespot +// +//////////////////////////////////////////////////////////////// + +sudo apt-get -y install portaudio19-dev + +cd ~ +git clone https://github.com/librespot-org/librespot +curl https://sh.rustup.rs -sSf | sh +# choose 1 + +sudo reboot + +cd librespot/ +cargo build --release --features alsa-backend + +sudo cp target/release/librespot /usr/local/bin +cd ~ +sudo rm -rf librespot + //////////////////////////////////////////////////////////////// // // COMPONENT 5 - Squeezelite @@ -860,6 +893,9 @@ sudo make install cd ~ sudo rm -rf ./libupnppsamples-code +6. Patch for upmpdcli gmusic plugin +sudo cp ./rel-stretch/other/upmpdcli/session.py /usr/share/upmpdcli/cdplugins/gmusic + //////////////////////////////////////////////////////////////// // // COMPONENT 7 - Optionally install gmusicapi diff --git a/other/librespot/librespot-a4e0f58 b/other/librespot/librespot-a4e0f58 new file mode 100755 index 000000000..1e2e0eacb Binary files /dev/null and b/other/librespot/librespot-a4e0f58 differ diff --git a/other/librespot/librespot-master-a4e0f58.zip b/other/librespot/librespot-master-a4e0f58.zip new file mode 100644 index 000000000..584563be5 Binary files /dev/null and b/other/librespot/librespot-master-a4e0f58.zip differ diff --git a/other/mpd/mpd-0.20.20 b/other/mpd/mpd-0.20.20 index bf2c5bb56..9906ed96a 100755 Binary files a/other/mpd/mpd-0.20.20 and b/other/mpd/mpd-0.20.20 differ diff --git a/other/shairport-sync/shairport-sync-3.1.7 b/other/shairport-sync/shairport-sync-3.1.7 deleted file mode 100755 index cc7eb93bf..000000000 Binary files a/other/shairport-sync/shairport-sync-3.1.7 and /dev/null differ diff --git a/other/shairport-sync/shairport-sync-3.1.7.zip b/other/shairport-sync/shairport-sync-3.1.7.zip deleted file mode 100644 index 2d3136f65..000000000 Binary files a/other/shairport-sync/shairport-sync-3.1.7.zip and /dev/null differ diff --git a/other/shairport-sync/shairport-sync-3.2.1 b/other/shairport-sync/shairport-sync-3.2.1 new file mode 100755 index 000000000..ecbfc87cd Binary files /dev/null and b/other/shairport-sync/shairport-sync-3.2.1 differ diff --git a/other/shairport-sync/shairport-sync-3.2.1.zip b/other/shairport-sync/shairport-sync-3.2.1.zip new file mode 100644 index 000000000..279a746b2 Binary files /dev/null and b/other/shairport-sync/shairport-sync-3.2.1.zip differ diff --git a/other/upmpdcli/session.py b/other/upmpdcli/session.py new file mode 100644 index 000000000..874c2036b --- /dev/null +++ b/other/upmpdcli/session.py @@ -0,0 +1,396 @@ +# Copyright (C) 2016 J.F.Dockes +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +from __future__ import print_function + +import sys +import json +import datetime +import time +from upmplgmodels import Artist, Album, Track, Playlist, SearchResult, \ + Category, Genre +from gmusicapi import Mobileclient +from upmplgutils import uplog + +class Session(object): + def __init__(self): + self.api = None + self.user = None + self.lib_albums = {} + self.lib_artists = {} + self.lib_tracks = {} + self.lib_updatetime = 0 + self.sitdata = [] + self.sitbyid = {} + self.sitdataupdtime = 0 + + def dmpdata(self, who, data): + uplog("%s: %s" % (who, json.dumps(data, indent=4))) + + # Look for an Android device id in the registered devices. + def find_device_id(self, data): + for entry in data: + if "type" in entry and entry["type"] == u"ANDROID": + # Get rid of 0x + id = entry["id"][2:] + uplog("Using deviceid %s" % id) + return id + return None + + def login(self, username, password, deviceid=None): + self.api = Mobileclient(debug_logging=False) + + if deviceid is None: + logged_in = self.api.login(username, password, + Mobileclient.FROM_MAC_ADDRESS) + if logged_in: + # Try to re-login with a valid deviceid + data = self.api.get_registered_devices() + #self.dmpdata("registered devices", data) + deviceid = self.find_device_id(data) + if deviceid: + logged_in = self.login(username, password, deviceid) + else: + logged_in = self.api.login(username, password, deviceid) + + isauth = self.api.is_authenticated() + #uplog("login: Logged in: %s. Auth ok: %s" % (logged_in, isauth)) + return logged_in + + def _get_user_library(self): + now = time.time() + if now - self.lib_updatetime < 300: + return + data = self.api.get_all_songs() + #self.dmpdata("all_songs", data) + self.lib_updatetime = now + tracks = [_parse_track(t) for t in data] + self.lib_tracks = dict([(t.id, t) for t in tracks]) + for track in tracks: + # We would like to use the album id here, but gmusic + # associates the tracks with any compilations after + # uploading (does not use the metadata apparently), so + # that we can't (we would end up with multiple + # albums). OTOH, the album name is correct (so seems to + # come from the metadata). What we should do is test the + # album ids for one album with a matching title, but we're + # not sure to succeed. So at this point, the album id we + # end up storing could be for a different albums, and we + # should have a special library-local get_album_tracks + self.lib_albums[track.album.name] = track.album + self.lib_artists[track.artist.id] = track.artist + + def get_user_albums(self): + self._get_user_library() + return self.lib_albums.values() + + def get_user_artists(self): + self._get_user_library() + return self.lib_artists.values() + + def get_user_playlists(self): + pldata = self.api.get_all_playlists() + #self.dmpdata("playlists", pldata) + return [_parse_playlist(pl) for pl in pldata] + + def get_user_playlist_tracks(self, playlist_id): + self._get_user_library() + data = self.api.get_all_user_playlist_contents() + entries = [] + for item in data: + if item['id'] == playlist_id: + entries = item['tracks'] + break + if not entries: + return [] + #self.dmpdata("user_playlist_content", entries) + tracks = [] + for entry in entries: + if entry['deleted']: + continue + if entry['source'] == u'1': + tracks.append(self.lib_tracks[entry['trackId']]) + elif 'track' in entry: + tracks.append(_parse_track(entry['track']) ) + return tracks + + def create_station_for_genre(self, genre_id): + id = self.api.create_station("station"+genre_id, genre_id=genre_id) + return id + + def get_user_stations(self): + data = self.api.get_all_stations() + # parse_playlist works fine for stations + stations = [_parse_playlist(d) for d in data] + return stations + + def delete_user_station(self, id): + self.api.delete_stations(id) + + # not working right now + def listen_now(self): + print("api.get_listen_now_items()", file=sys.stderr) + ret = {'albums' : [], 'stations' : []} + try: + data = self.api.get_listen_now_items() + except Exception as err: + print("api.get_listen_now_items failed: %s" % err, file=sys.stderr) + data = None + + # listen_now entries are not like normal albums or stations, + # and need special parsing. I could not make obvious sense of + # the station-like listen_now entries, so left them aside for + # now. Maybe should use create_station on the artist id? + if data: + ret['albums'] = [_parse_ln_album(a['album']) \ + for a in data if 'album' in a] + #ret['stations'] = [_parse_ln_station(d['radio_station']) \ + # for d in data if 'radio_station' in d] + else: + print("listen_now: no items returned !", file=sys.stderr) + print("get_listen_now_items: returning %d albums and %d stations" %\ + (len(ret['albums']), len(ret['stations'])), file=sys.stderr) + return ret + + def get_situation_content(self, id = None): + ret = {'situations' : [], 'stations' : []} + now = time.time() + if id is None and now - self.sitdataupdtime > 300: + self.sitbyid = {} + self.sitdata = self.api.get_listen_now_situations() + self.sitdataupdtime = now + + # Root is special, it's a list of situations + if id is None: + ret['situations'] = [self._parse_situation(s) \ + for s in self.sitdata] + return ret + + # not root + if id not in self.sitbyid: + print("get_situation_content: %s unknown" % id, file=sys.stderr) + return ret + + situation = self.sitbyid[id] + #self.dmpdata("situation", situation) + if 'situations' in situation: + ret['situations'] = [self._parse_situation(s) \ + for s in situation['situations']] + if 'stations' in situation: + ret['stations'] = [_parse_situation_station(s) \ + for s in situation['stations']] + + return ret + + def _parse_situation(self, data): + self.sitbyid[data['id']] = data + return Playlist(id=data['id'], name=data['title']) + + def create_curated_and_get_tracks(self, id): + sid = self.api.create_station("station"+id, curated_station_id=id) + print("create_curated: sid %s"%sid, file=sys.stderr) + tracks = [_parse_track(t) for t in self.api.get_station_tracks(sid)] + #print("curated tracks: %s"%tracks, file=sys.stderr) + self.api.delete_stations(sid) + return tracks + + def get_station_tracks(self, id): + return [_parse_track(t) for t in self.api.get_station_tracks(id)] + + def get_media_url(self, song_id, quality=u'med'): + url = self.api.get_stream_url(song_id, quality=quality) + print("get_media_url got: %s" % url, file=sys.stderr) + return url + + def get_album_tracks(self, album_id): + data = self.api.get_album_info(album_id, include_tracks=True) + album = _parse_album(data) + return [_parse_track(t, album) for t in data['tracks']] + + def get_promoted_tracks(self): + data = self.api.get_promoted_songs() + #self.dmpdata("promoted_tracks", data) + return [_parse_track(t) for t in data] + + def get_genres(self, parent=None): + data = self.api.get_genres(parent_genre_id=parent) + return [_parse_genre(g) for g in data] + + def get_artist_info(self, artist_id, doRelated=False): + ret = {"albums" : [], "toptracks" : [], "related" : []} + # Happens,some library tracks have no artistId entry + if artist_id is None or artist_id == 'None': + uplog("get_artist_albums: artist_id is None") + return ret + else: + uplog("get_artist_albums: artist_id %s" % artist_id) + + maxrel = 20 if doRelated else 0 + maxtop = 0 if doRelated else 10 + incalbs = False if doRelated else True + data = self.api.get_artist_info(artist_id, include_albums=incalbs, + max_top_tracks=maxtop, + max_rel_artist=maxrel) + #self.dmpdata("artist_info", data) + if 'albums' in data: + ret["albums"] = [_parse_album(alb) for alb in data['albums']] + if 'topTracks' in data: + ret["toptracks"] = [_parse_track(t) for t in data['topTracks']] + if 'related_artists' in data: + ret["related"] = [_parse_artist(a) for a in data['related_artists']] + return ret + + def get_artist_related(self, artist_id): + data = self.get_artist_info(artist_id, doRelated=True) + return data["related"] + + def search(self, query): + data = self.api.search(query, max_results=50) + #self.dmpdata("Search", data) + + tr = [_parse_track(i['track']) for i in data['song_hits']] + ar = [_parse_artist(i['artist']) for i in data['artist_hits']] + al = [_parse_album(i['album']) for i in data['album_hits']] + #self.dmpdata("Search playlists", data['playlist_hits']) + try: + pl = [_parse_splaylist(i) for i in data['playlist_hits']] + except: + pl = [] + return SearchResult(artists=ar, albums=al, playlists=pl, tracks=tr) + + + +def entryOrUnknown(data, name, default="Unknown"): + return data[name] if name in data else default + + +def _parse_artist(data): + return Artist(id=data['artistId'], name=data['name']) + +def _parse_genre(data): + return Genre(id=data['id'], name=data['name']) + +def _parse_playlist(data): + return Playlist(id=data['id'], name=data['name']) + +def _parse_splaylist(data): + return Playlist(id=data['playlist']['shareToken'], + name=data['playlist']['name']) + +def _parse_situation_station(data): + return Playlist(id=data['seed']['curatedStationId'], name=data['name']) + + +# 'id' source when initiated from playlist data: +# +# The previous version used the 'id' entry from the track data if set, +# else 'nid'. This only worked for source=='1' entries, pointing to +# user library data. +# +# The initial version for parsing non-user-lib playlist entries (which +# have an embedded track record) used 'trackId' from the playlist +# wrapper if set, else 'storeId' from the embedded track entry: +# +# { +# 'trackid' : 'somevalue', +# 'track': { +# 'storeId': 'usuallysamevalue', +# ... +# }, +# +# The merged version, for non-user entries, discards the data from the +# playlist wrapper, and uses trackId or storeId from the embedded +# track object. (trackId is usually not set inside the track record +# and track['storeId'] appears to be the same as the wrapper trackId) +# +# Note that there is also an 'id' entry in the playlist wrapper, which +# was never tried. +def _parse_track(data, album=None): + artist_name = entryOrUnknown(data, 'artist') + albartist_name = entryOrUnknown(data, 'albumArtist', None) + #uplog("_parse_track: artist %s albartist %s"%(artist_name,albartist_name)) + artistid = data["artistId"][0] if "artistId" in data else None + artist = Artist(id=artistid, name = artist_name) + albartist = Artist(id=artistid, name=albartist_name) if \ + albartist_name is not None else artist + albid = entryOrUnknown(data, 'albumId', None) + + if album is None: + #alb_artist = data['albumArtist'] if 'albumArtist' in data else "" + alb_art= data['albumArtRef'][0]["url"] if 'albumArtRef' in data else "" + alb_tt = entryOrUnknown(data, 'album') + album = Album(id=albid, name=alb_tt, image=alb_art, artist=artist) + + if 'id' in data: + trackid = data['id'] + elif 'trackId' in data: + trackid = data['trackId'] + elif 'storeId' in data: + trackid = data['storeId'] + elif 'nid' in data: + trackid = data['nid'] + else: + trackid = '' + + kwargs = { + 'id': trackid, + 'name': data['title'], + 'duration': int(data['durationMillis'])/1000, + 'track_num': data['trackNumber'], + 'disc_num': data['discNumber'], + 'artist': artist, + 'album': album, + #'artists': artists, + } + if 'genre' in data: + kwargs['genre'] = data['genre'] + return Track(**kwargs) + + +def _parse_ln_album(data): + artist = Artist(id=data['artist_metajam_id'], name=data['artist_name']) + kwargs = { + 'id': data['id']['metajamCompactKey'], + 'name' : data['id']['title'], + 'artist' : artist, + } + if 'images' in data: + kwargs['image'] = data['images'][0]['url'] + + return Album(**kwargs) + + +def _parse_album(data, artist=None): + if artist is None: + artist_name = "Unknown" + if 'artist' in data: + artist_name = data['artist'] + elif 'albumArtist' in data: + artist_name = data['albumArtist'] + artist = Artist(name=artist_name) + + kwargs = { + 'id': data['albumId'], + 'name': data['name'], + 'artist': artist, + } + if 'albumArtRef' in data: + kwargs['image'] = data['albumArtRef'] + + if 'year' in data: + kwargs['release_date'] = data['year'] + + return Album(**kwargs) diff --git a/var/local/www/commandw/spotevent.sh b/var/local/www/commandw/spotevent.sh new file mode 100755 index 000000000..158a10651 --- /dev/null +++ b/var/local/www/commandw/spotevent.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# +# moOde audio player (C) 2014 Tim Curtis +# http://moodeaudio.org +# +# This Program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# This Program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# 2018-09-27 TC moOde 4.3 +# - initial version +# + +SQLDB=/var/local/www/db/moode-sqlite3.db + +RESULT=$(sqlite3 $SQLDB "select value from cfg_system where param='alsavolume' or param='amixname' or param='mpdmixer' or param='rsmafterspot'") +readarray -t arr <<<"$RESULT" +ALSAVOLUME=${arr[0]} +AMIXNAME=${arr[1]} +MPDMIXER=${arr[2]} +RSMAFTERSPOT=${arr[3]} + +if [[ $PLAYER_EVENT == "start" ]]; then + /usr/bin/mpc stop > /dev/null + # allow time for ui update + sleep 1 + # set active flag true + $(sqlite3 $SQLDB "update cfg_system set value='1' where param='spotactive'") + + if [[ $ALSAVOLUME != "none" ]]; then + /var/www/command/util.sh set-alsavol "$AMIXNAME" 100 + fi +fi + +if [[ $PLAYER_EVENT == "stop" ]]; then + # set active flag false + $(sqlite3 $SQLDB "update cfg_system set value='0' where param='spotactive'") + + # restore 0dB (100%) hardware volume when mpd configured as below + if [[ $MPDMIXER == "software" || $MPDMIXER == "disabled" ]]; then + if [[ $ALSAVOLUME != "none" ]]; then + /var/www/command/util.sh set-alsavol "$AMIXNAME" 100 + fi + fi + + # restore volume + /var/www/vol.sh restore + + # resume playback if indicated + if [[ $RSMAFTERSPOT == "Yes" ]]; then + /usr/bin/mpc play > /dev/null + fi +fi diff --git a/var/local/www/db/moode-sqlite3.db.default b/var/local/www/db/moode-sqlite3.db.default index d5442f25e..82172c2bf 100755 Binary files a/var/local/www/db/moode-sqlite3.db.default and b/var/local/www/db/moode-sqlite3.db.default differ diff --git a/var/local/www/db/moode-sqlite3.db.schema b/var/local/www/db/moode-sqlite3.db.schema index 84ee4e424..f80424eff 100755 --- a/var/local/www/db/moode-sqlite3.db.schema +++ b/var/local/www/db/moode-sqlite3.db.schema @@ -10,4 +10,5 @@ CREATE TABLE cfg_upnp (id INTEGER PRIMARY KEY, param CHAR (32), value CHAR (32)) CREATE TABLE cfg_eqalsa (id INTEGER PRIMARY KEY, curve_name CHAR (32), curve_values CHAR (32)); CREATE TABLE cfg_eqfa4p (id INTEGER PRIMARY KEY, curve_name CHAR (32), master_gain CHAR (32), band1_params CHAR (32), band2_params CHAR (32), band3_params CHAR (32), band4_params CHAR (32)); CREATE TABLE cfg_theme (id INTEGER PRIMARY KEY, theme_name CHAR (32), tx_color CHAR (32), bg_color CAR (32), mbg_color CHAR (32)); +CREATE TABLE cfg_spotify (id INTEGER PRIMARY KEY, param CHAR (32), value CHAR (32)); CREATE UNIQUE INDEX IndexName ON cfg_source (name); diff --git a/var/local/www/header.php b/var/local/www/header.php index df68294ca..209d47dbf 100644 --- a/var/local/www/header.php +++ b/var/local/www/header.php @@ -28,6 +28,10 @@ * - new tabs, other code for newui v2 * - CoverView * - font-awesome 5 + * 2018-09-27 TC moOde 4.3 + * - rm + * - fix external link for Music tab + * - comment out manifest link cuz it breaks IOS Add to Home * */ --> @@ -51,62 +55,67 @@ - + - - - - - - - + + + + + + + + + - -
@@ -117,6 +121,51 @@

Allo Piano 2.1 (Dual PCM5142)

+ +
+

Allo Katana DAC (ESS Sabre ES9038Q2M)

+
+
+ +
+ + + Filter is applied to PCM samples at oversampling stage.
+ Playback is automatically restarted to make filter change effective.
+ Default: Apodizing Fast Roll-off Filter +
+
+
+ +
+ +
+ + + De-emphasis can be set to Bypass, 32 44.1 eor 48 kHz.
+ Default: Bypass +
+
+
+ +
+ +
+ + + Enable or disable support for DSD over PCM (DoP) protocol.
+ Default: On +
+
+
+
+
diff --git a/www/templates/eqg-config.html b/www/templates/eqg-config.html index 2f312e3b6..e1c406385 100644 --- a/www/templates/eqg-config.html +++ b/www/templates/eqg-config.html @@ -145,7 +145,7 @@

$_selected_curve

diff --git a/www/templates/eqp-config.html b/www/templates/eqp-config.html index b5156adbc..61033adf9 100644 --- a/www/templates/eqp-config.html +++ b/www/templates/eqp-config.html @@ -185,7 +185,7 @@

$_selected_curve

diff --git a/www/templates/nas-config.html b/www/templates/nas-config.html index f547c9805..2e944ebe2 100644 --- a/www/templates/nas-config.html +++ b/www/templates/nas-config.html @@ -25,6 +25,8 @@ * - change to modal-sm2 for confirm modals except for the remove modal since it needs to be wider * - change mount flags help to indicate sec=ntlm is optional * - font-awesome 5 + * 2018-09-27 TC moOde 4.3 + * - addition (i) info text for userid/password * */ --> @@ -95,6 +97,7 @@

NAS Source

+ Try userid = Guest and a blank password unless your NAS requires a specific userid/password.
NOTE: Single and double quotes are not allowed.
@@ -176,7 +179,7 @@
Remove NAS source: $_name
diff --git a/www/templates/net-config.html b/www/templates/net-config.html index bbd24e546..2a8322a02 100644 --- a/www/templates/net-config.html +++ b/www/templates/net-config.html @@ -268,7 +268,7 @@
Reset network configuration to eth0 and DHCP?
diff --git a/www/templates/snd-config.html b/www/templates/snd-config.html index 91412df2b..d9763086c 100644 --- a/www/templates/snd-config.html +++ b/www/templates/snd-config.html @@ -24,6 +24,9 @@ * - minor format cleanup * - change to modal-sm2 for confirm modals * - font-awesome 5 + * 2018-09-27 TC moOde 4.3 + * - improve logic for chp/device options button + * - spotify * */ --> @@ -60,7 +63,6 @@

Audio Config

glb_mclk: If enabled, Kali MCLK is used and PLL for Piano 2.1 is disabled for best SQ.
slave: If enabled, Boss and MiniBOSS DAC operate in slave mode required by Kali.
-
@@ -68,7 +70,7 @@

Audio Config

- Edit settings
+
@@ -110,7 +112,7 @@

Audio Config

- + Enable a rotary encoder device to control volume.
Driver uses WiringPi library and defaults to using GPIO 23,24,GND.
Params: DELAY ACCEL STEP PIN-A PIN-B
@@ -135,7 +137,7 @@

Audio Config

- + When the last song in the Playlist has finished playing Auto-shuffle adds a random song from the music library to the end of the Playlist, then removes it when it finishes playing. This creates a continuous stream of music without growing the Playlist. NOTE: Auto-shuffle replaces MPD random play as the method for the 'Random' button.
@@ -156,7 +158,7 @@

Audio Config

- + Play the last played item after system boots up.
@@ -230,7 +232,7 @@

Audio Config

- + Renderers
@@ -248,11 +250,10 @@

Audio Config

- - Bluetooth controller by Johan Hedberg (BlueZ) and Arkadiusz Bokowy (ALSA backend)
+ + Bluetooth controller by Johan Hedberg (BlueZ) and Arkadiusz Bokowy (Bluez-alsa)
-
@@ -265,7 +266,7 @@

Audio Config

- + Allow the Bluetooth speaker to be shared by multiple clients.
@@ -287,7 +288,6 @@

Audio Config

-
@@ -302,12 +302,11 @@

Audio Config

- - Shairport-sync by Mike Brady, the wonderful fork of
- the original Shairport by James "Abrasive" Laird. + + Shairport-sync by Mike Brady, the wonderful fork of the original Shairport by James "Abrasive" Laird.
- +
+ + +
+ + librespot-org by Sasha Hilton, the wonderful fork of the original librespot by Paul Lietar
+
+ + +
+ + + + + + + Resume MPD playback after Spotify Connect client stops playing.
+
+ + CONFIGURE Spotify
+ RESTART Spotify
+
+
+
@@ -339,7 +373,7 @@

Audio Config

- + Squeezelite renderer by Adrian Smith and Ralph Irving (triode).
@@ -361,10 +395,14 @@

Audio Config

+
+ + + UPnP/DLNA +
- UPnP - +
@@ -377,7 +415,7 @@

Audio Config

- + upmpdcli (UPnP Client for MPD) by Jean-Francois Dockes.
- Supports Open Home Media (ohMedia).
- Provides album art via upexplorer.
@@ -405,7 +443,7 @@

Audio Config

- + DLNA server (miniDLNA) by Justin Maggard.
@@ -462,7 +500,7 @@
Rebuild DLNA database?
@@ -479,7 +517,7 @@
Restart MPD service?
@@ -496,7 +534,24 @@
Restart Airplay receiver?
+ + + +
+
@@ -513,7 +568,7 @@
Restart Bluetooth controller?
@@ -530,7 +585,7 @@
Restart Squeezelite renderer?
@@ -547,7 +602,7 @@
Restart UPnP renderer?
diff --git a/www/templates/spo-config.html b/www/templates/spo-config.html new file mode 100644 index 000000000..165b02b87 --- /dev/null +++ b/www/templates/spo-config.html @@ -0,0 +1,99 @@ + +
+
+

Spotify Config

+ +

+ BACK to Audio config +

+ +
+ Settings   + +
+
+ +
+ + + + Default is 160 kHz.
+
+
+
+ +
+ +
+ (%) + + + Initial volume once connected [0-100].
+
+
+
+ +
+ +
+ + + + Logarithmic or linear volume curve. Default is Logarithmic.
+
+
+
+ +
+ +
+ + + + Adjusts the volume of all songs played so that they sound as though they are of equal loudness.
+
+
+
+ +
+ +
+ (dB) + + + Volume boost in dB [0-10].
+
+
+
+
+
+
+
diff --git a/www/templates/src-config.html b/www/templates/src-config.html index d9cbf9e87..39dd28259 100644 --- a/www/templates/src-config.html +++ b/www/templates/src-config.html @@ -22,6 +22,8 @@ * 2018-01-26 TC moOde 4.0 * 2018-07-11 TC moOde 4.2 * - minor cleanup + * 2018-09-27 TC moOde 4.3 + * - thumbnail cache * */ --> @@ -37,39 +39,43 @@

Music Source Config

List of configured NAS sources (click to edit)

-
+ $_mounts

  All NAS sources - - Re-mounts all NAS sources. Does not run MPD database update.
-
+ + Re-mount all NAS sources. +

- USB and SD Card Sources + MPD Database

- Use UPDATE to initially add USB and SD Card sources to the Browse panel. RESCAN can be used to rebuild the MPD database by scanning all content in all music sources. +   MPD database

-   MPD database - - Updates the music database: find new files, remove deleted files, update modified files.
-
+   MPD database + + Update or regenerate the MPD database. + +

+ Music Library +

+   Library tag cache + + Clear the Library tag cache. It will be regenerated next time the Library is opened. +

-   Music sources - - Same as update, but also rescans unmodified files.
-
+   Album cover thumbnail cache

- Music Library Cache

-   Library cache - - Resets the Library cache. It will be rebuilt next time the Library is opened.
-
+   Album cover thumbnail cache + + Update or regenerate the album cover thumbnail cache.
+ VIEW cache status:  $_thmcache_status +

diff --git a/www/templates/sys-config.html b/www/templates/sys-config.html index e32a763ec..c191f53d6 100644 --- a/www/templates/sys-config.html +++ b/www/templates/sys-config.html @@ -24,6 +24,8 @@ * - minor format cleanup * - change to modal-sm2 for confirm modals * - font-awesome 5 + * 2018-07-18 TC moOde 4.2 update + * - add singleton to #view-pkgcontent footer btn * */ --> @@ -505,7 +507,7 @@

Package content

@@ -525,7 +527,7 @@
Clear LocalUI Browser cache?
@@ -542,7 +544,7 @@
Clear playback history?
@@ -559,7 +561,7 @@
Clear system logs?
@@ -576,7 +578,7 @@
Compact sqlite database?
@@ -593,7 +595,7 @@
Expand root file system?
@@ -610,7 +612,7 @@
Enable USB boot?
diff --git a/www/themes/alizarin/indextpl.html b/www/themes/alizarin/indextpl.html index 907ff8549..bf0d2ad15 100644 --- a/www/themes/alizarin/indextpl.html +++ b/www/themes/alizarin/indextpl.html @@ -26,7 +26,7 @@ * - live timeline fpr mobile * - new volume controls * - remove data-validate="parsley" - * 2018-07-11 TC moOde 4.2 + * 2018-07-08 TC moOde 4.2 * - add 'Mute toggle' title to volume-display * - new tabs, other code for newui v2 * - move google search from cover to song title @@ -35,13 +35,44 @@ * - screen saver * - adv browse search * - font-awesome 5 + * 2018-09-27 TC moOde 4.3 + * - new mobile layout + * - favorites feature + * - clear/add for saved playlists + * - refresh btn for browse and radio panels + * - playback ctrls HUD for screen saver + * - album cover view + * - HUD playlist * */ -->
- +
+
+
+
+
+ + + +
+
+ + + +
+
+ +
+
+
+
    +
    +
    +
    +
    @@ -54,25 +85,26 @@
    +
    - -
    - + +
    + +
    -
    - +
    @@ -81,12 +113,15 @@
      - +
      + + +
      - +
      @@ -95,26 +130,6 @@
      - -
      -
      - - -
      -
      - - - -
      -
      - - -
      -
      - -
      -
      @@ -139,11 +154,12 @@ +
      - +
      @@ -156,17 +172,38 @@
      + +
      +
      + + + +
      +
      + + + + + + +
      +
      +
      - +
      -
      - - +
      + + +
      @@ -179,8 +216,8 @@
      @@ -189,44 +226,48 @@
      -
      -
    • -
      Genres
      -
    • -
      -
      -
        -
        -
        -
      • -
        Artists
        -
      • -
        -
        -
          -
          -
          -
        • -
          Albums
          -
          -
        • -
          -
          -
            -
            +
            +
          • +
            Genres
            +
          • +
            +
            +
              +
              +
              +
            • +
              Artists
              +
            • +
              +
              +
                +
                +
                +
              • +
                Albums
                +
                +
              • +
                +
                +
                  +
                  +
                  +
                  +
                • +
                  Albums by Artist
                  +
                  +
                • +
                  +
                  +
                    -
                    -
                  • -
                    Tracks
                    -
                  • -
                    -
                    +
                    -
                    +
                    @@ -241,18 +282,25 @@
                    - +
                    -
                    +
                    +
                    - + - + + +
                    +
                    + +
                    +
                    @@ -262,14 +310,15 @@
                    -
                    + +
                    @@ -282,7 +331,7 @@
                    - +
                    @@ -523,7 +573,7 @@

                    @@ -553,7 +603,7 @@

                    Remove playlist items

                    @@ -588,7 +638,7 @@

                    Move playlist items

                    @@ -603,7 +653,7 @@

                    @@ -617,6 +667,7 @@

                    Playback history