2022-02-06 10:19:03 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
__name__ = "Luniebox"
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import logging
|
|
|
|
import subprocess
|
|
|
|
import time
|
|
|
|
from enum import Enum
|
|
|
|
from configparser import ConfigParser
|
|
|
|
import RPi.GPIO as GPIO
|
|
|
|
from spotifydl import SpotifyDL, SpotifyDLStatus
|
|
|
|
from mpd import MPDClient
|
|
|
|
from ExtendedMFRC522 import ExtendedMFRC522
|
|
|
|
|
|
|
|
from spotify import Spotify
|
|
|
|
import util
|
|
|
|
|
|
|
|
|
|
|
|
defaultConfigFilePath = '../config/luniebox.cfg'
|
|
|
|
|
|
|
|
|
|
|
|
class PlayerService(Enum):
|
|
|
|
NONE = 0
|
|
|
|
SPOTIFY = 1
|
|
|
|
MPD = 2
|
|
|
|
|
|
|
|
|
|
|
|
class Luniebox(object):
|
|
|
|
|
|
|
|
def __init__(self, configFilePath=defaultConfigFilePath):
|
|
|
|
self.configFilePath = configFilePath
|
|
|
|
self.read_config()
|
|
|
|
|
|
|
|
GPIO.setwarnings(False)
|
|
|
|
self.reader = ExtendedMFRC522(encoding="ascii")
|
|
|
|
|
|
|
|
loglevel = 'INFO'
|
|
|
|
if self.get_setting('logging', 'level'):
|
|
|
|
loglevel = self.get_setting('logging', 'level')
|
|
|
|
|
|
|
|
logger = logging.getLogger('luniebox')
|
|
|
|
logger.setLevel(logging._nameToLevel[loglevel])
|
|
|
|
logFormatter = logging.Formatter(
|
|
|
|
style='{', datefmt='%Y-%m-%d %H:%M:%S', fmt='{asctime} {levelname}: {message}')
|
|
|
|
logstdoutHandler = logging.StreamHandler(sys.stdout)
|
|
|
|
logstdoutHandler.setFormatter(logFormatter)
|
|
|
|
logger.addHandler(logstdoutHandler)
|
|
|
|
|
|
|
|
if self.get_setting('mpd', 'disabled', '') != 'True':
|
|
|
|
self.mpd = MPDClient()
|
|
|
|
self.mpd.host = "localhost"
|
|
|
|
self.mpd.port = 6600
|
|
|
|
self.mpd.timeout = 10
|
|
|
|
if self.mpd_connect():
|
|
|
|
logging.getLogger('luniebox').debug("connected to mpd")
|
|
|
|
else:
|
|
|
|
logging.getLogger('luniebox').info("mpd disabled")
|
|
|
|
self.mpd = False
|
|
|
|
|
|
|
|
if self.get_setting('spotify', 'disabled', '') != 'True':
|
|
|
|
self.spotify = Spotify(luniebox=self)
|
|
|
|
if self.spotify_connect():
|
|
|
|
logging.getLogger('luniebox').debug("connected to spotify")
|
|
|
|
|
|
|
|
self.zspotify_path = False
|
|
|
|
self.spotifydl = False
|
|
|
|
self.zspotify_path = self.get_setting('spotify', 'zspotify_path')
|
|
|
|
if self.zspotify_path and self.mpd:
|
|
|
|
self.spotifydl_connect()
|
|
|
|
else:
|
|
|
|
logging.getLogger('luniebox').info("spotify disabled")
|
|
|
|
self.spotify = False
|
|
|
|
|
|
|
|
if not self.config.has_option('api', 'api_key'):
|
|
|
|
self.set_setting('api', 'api_key', util.randomString(64))
|
|
|
|
|
|
|
|
self.current = self.get_setting('luniebox', 'current')
|
|
|
|
|
|
|
|
self.service = PlayerService.NONE
|
|
|
|
|
|
|
|
if self.current != None:
|
2022-02-11 15:54:54 +01:00
|
|
|
if self.current.startswith('spotify:'):
|
2022-02-06 10:19:03 +01:00
|
|
|
self.service = PlayerService.SPOTIFY
|
2022-02-11 15:54:54 +01:00
|
|
|
elif self.current.startswith('mpd:'):
|
2022-02-06 10:19:03 +01:00
|
|
|
self.service = PlayerService.MPD
|
|
|
|
|
|
|
|
self.volume_max = int(self.get_setting('luniebox', 'volume_max', 100))
|
|
|
|
self.volume = int(self.get_setting(
|
|
|
|
'luniebox', 'volume', self.volume_max))
|
|
|
|
self.volume_step = int(self.get_setting('luniebox', 'volume_step', 5))
|
|
|
|
self.wind_step = int(self.get_setting('luniebox', 'wind_step', 10))
|
|
|
|
self.resume = True
|
|
|
|
|
|
|
|
def mpd_connect(self):
|
|
|
|
if not self.get_setting('luniebox', 'setup'):
|
|
|
|
return False
|
|
|
|
|
|
|
|
if not self.mpd:
|
|
|
|
logging.getLogger('luniebox').warn("mpd disabled!")
|
|
|
|
return False
|
|
|
|
|
|
|
|
try:
|
|
|
|
self.mpd.disconnect()
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
tries = 0
|
|
|
|
while tries < 5:
|
|
|
|
try:
|
|
|
|
self.mpd.connect(self.mpd.host, self.mpd.port)
|
|
|
|
return True
|
|
|
|
except:
|
|
|
|
subprocess.call(["systemctl", "restart", "mpd"])
|
|
|
|
time.sleep(1)
|
|
|
|
tries += 1
|
|
|
|
|
|
|
|
logging.getLogger('luniebox').error(
|
|
|
|
"Could not connect to MPD service!")
|
|
|
|
return False
|
|
|
|
|
|
|
|
def spotify_connect(self, restart=False, max_tries=5):
|
|
|
|
if not self.get_setting('luniebox', 'setup'):
|
|
|
|
return False
|
|
|
|
|
|
|
|
if not self.spotify:
|
|
|
|
logging.getLogger('luniebox').warn("spotify disabled!")
|
|
|
|
return False
|
|
|
|
|
|
|
|
if restart:
|
|
|
|
subprocess.run(["sudo", "systemctl", "restart", "spotifyd"])
|
2022-02-13 12:09:17 +01:00
|
|
|
time.sleep(20)
|
2022-02-06 10:19:03 +01:00
|
|
|
|
|
|
|
tries = 0
|
|
|
|
spotifyd_status = subprocess.call(
|
|
|
|
["systemctl", "is-active", "--quiet", "spotifyd"])
|
|
|
|
|
|
|
|
while spotifyd_status != 0 and tries < max_tries:
|
|
|
|
subprocess.call(["systemctl", "restart", "spotifyd"])
|
2022-02-13 12:09:17 +01:00
|
|
|
tries = tries + 1
|
2022-02-10 16:44:22 +01:00
|
|
|
time.sleep(10 * tries)
|
2022-02-06 10:19:03 +01:00
|
|
|
spotifyd_status = subprocess.call(
|
|
|
|
["systemctl", "is-active", "--quiet", "spotifyd"])
|
|
|
|
|
|
|
|
if spotifyd_status == 0:
|
|
|
|
return True
|
|
|
|
|
|
|
|
logging.getLogger('luniebox').error("spotifyd service not running!")
|
|
|
|
return False
|
|
|
|
|
|
|
|
def spotifydl_connect(self):
|
|
|
|
if not self.get_setting('luniebox', 'setup'):
|
|
|
|
return False
|
|
|
|
|
|
|
|
if self.spotifydl:
|
|
|
|
return True
|
|
|
|
|
|
|
|
if not self.zspotify_path or not self.mpd:
|
|
|
|
logging.getLogger('luniebox').warn("spotifydl disabled!")
|
|
|
|
return False
|
|
|
|
|
|
|
|
try:
|
2022-02-11 16:00:12 +01:00
|
|
|
self.spotifydl = SpotifyDL(self)
|
2022-02-06 10:19:03 +01:00
|
|
|
logging.getLogger('luniebox').info("spotifydl enabled!")
|
|
|
|
return True
|
2022-02-08 20:09:26 +01:00
|
|
|
except Exception as exception:
|
2022-02-06 10:19:03 +01:00
|
|
|
logging.getLogger('luniebox').warning(
|
2022-02-08 20:09:26 +01:00
|
|
|
"error on setup spotifydl: " + str(exception))
|
2022-02-06 10:19:03 +01:00
|
|
|
return False
|
|
|
|
|
|
|
|
def read_config(self):
|
|
|
|
configParser = ConfigParser()
|
|
|
|
dataset = configParser.read(self.configFilePath)
|
|
|
|
if len(dataset) != 1:
|
|
|
|
raise ValueError(
|
|
|
|
"Config file {} not found!".format(self.configFilePath))
|
|
|
|
self.config = configParser
|
|
|
|
|
|
|
|
def get_setting(self, section, key, default=None):
|
|
|
|
if self.config.has_option(section, key):
|
|
|
|
return self.config[section][key]
|
|
|
|
return default
|
|
|
|
|
|
|
|
def set_setting(self, section, key, value):
|
|
|
|
if not self.config.has_section(section):
|
|
|
|
self.config.add_section(section)
|
|
|
|
self.config.set(section, key, str(value))
|
|
|
|
|
|
|
|
with open(self.configFilePath, 'w') as configfile:
|
|
|
|
self.config.write(configfile)
|
|
|
|
|
|
|
|
def rfid_write(self, data):
|
|
|
|
self.reader.write(data.strip())
|
|
|
|
|
|
|
|
def rfid_readOnce(self):
|
|
|
|
id, text = self.reader.read()
|
|
|
|
logging.getLogger('luniebox').debug(
|
|
|
|
"Once ID: %s Text: %s" % (id, text))
|
|
|
|
return text.strip()
|
|
|
|
|
|
|
|
def vol_up(self):
|
|
|
|
self.change_volume(self.volume_step)
|
|
|
|
|
|
|
|
def vol_down(self):
|
|
|
|
self.change_volume(-1 * self.volume_step)
|
|
|
|
|
|
|
|
def change_volume(self, change):
|
|
|
|
self.volume += change
|
|
|
|
|
|
|
|
if self.volume > self.volume_max:
|
|
|
|
self.volume = self.volume_max
|
|
|
|
|
|
|
|
if self.volume < 0:
|
|
|
|
self.volume = 0
|
|
|
|
|
|
|
|
if self.service == PlayerService.SPOTIFY and self.spotify_connect():
|
|
|
|
self.spotify.volume(self.volume)
|
|
|
|
elif self.service == PlayerService.MPD and self.mpd_connect():
|
|
|
|
self.mpd.setvol(self.volume)
|
|
|
|
|
|
|
|
self.set_setting('luniebox', 'volume', str(self.volume))
|
|
|
|
logging.getLogger('luniebox').debug("vol: " + str(self.volume))
|
|
|
|
|
|
|
|
def fastforward(self):
|
|
|
|
if not self.resume:
|
|
|
|
logging.getLogger('luniebox').debug('seek ff')
|
|
|
|
|
|
|
|
if self.service == PlayerService.SPOTIFY and self.spotify_connect():
|
|
|
|
self.spotify.fastforward(self.wind_step)
|
|
|
|
elif self.service == PlayerService.MPD and self.mpd_connect():
|
|
|
|
self.mpd.seekcur(self.wind_step)
|
|
|
|
|
|
|
|
def rewind(self):
|
|
|
|
if not self.resume:
|
|
|
|
logging.getLogger('luniebox').debug('seek rew')
|
|
|
|
|
|
|
|
if self.service == PlayerService.SPOTIFY and self.spotify_connect():
|
|
|
|
self.spotify.rewind(self.wind_step)
|
|
|
|
elif self.service == PlayerService.MPD and self.mpd_connect():
|
|
|
|
self.mpd.seekcur(-1 * self.wind_step)
|
|
|
|
|
|
|
|
def next(self):
|
|
|
|
if not self.resume:
|
|
|
|
logging.getLogger('luniebox').debug('next')
|
|
|
|
|
|
|
|
if self.service == PlayerService.SPOTIFY and self.spotify_connect():
|
|
|
|
self.spotify.next()
|
|
|
|
elif self.service == PlayerService.MPD and self.mpd_connect():
|
|
|
|
self.mpd.next()
|
|
|
|
|
|
|
|
def previous(self):
|
|
|
|
if not self.resume:
|
|
|
|
logging.getLogger('luniebox').debug('prev')
|
|
|
|
|
|
|
|
if self.service == PlayerService.SPOTIFY and self.spotify_connect():
|
|
|
|
self.spotify.previous()
|
|
|
|
elif self.service == PlayerService.MPD and self.mpd_connect():
|
|
|
|
self.mpd.previous()
|
|
|
|
|
|
|
|
def pause(self):
|
|
|
|
if not self.resume:
|
|
|
|
if self.mpd_connect():
|
|
|
|
self.mpd.pause(1)
|
|
|
|
logging.getLogger('luniebox').debug("pause mpd")
|
|
|
|
if self.spotify_connect(max_tries=2):
|
|
|
|
self.spotify.pause()
|
|
|
|
logging.getLogger('luniebox').debug("pause spotify")
|
|
|
|
|
|
|
|
self.resume = True
|
|
|
|
|
|
|
|
def play(self, text):
|
|
|
|
if text != "":
|
2022-02-11 15:54:54 +01:00
|
|
|
if text.startswith('spotify:') and self.spotify:
|
2022-02-06 10:19:03 +01:00
|
|
|
self.service = PlayerService.SPOTIFY
|
2022-02-11 15:54:54 +01:00
|
|
|
elif text.startswith('mpd:') and self.mpd:
|
2022-02-06 10:19:03 +01:00
|
|
|
self.service = PlayerService.MPD
|
2022-02-11 15:54:54 +01:00
|
|
|
else:
|
|
|
|
self.service = PlayerService.NONE
|
2022-02-06 10:19:03 +01:00
|
|
|
|
|
|
|
if self.service == PlayerService.SPOTIFY and self.spotifydl_connect():
|
|
|
|
downloadStatus = self.spotifydl.downloadStatus(text)
|
2022-02-11 15:54:54 +01:00
|
|
|
if downloadStatus == SpotifyDLStatus.FINISHED and self.mpd_connect():
|
|
|
|
self.mpd.update()
|
|
|
|
self.mpd.idle('update')
|
2022-02-06 10:19:03 +01:00
|
|
|
self.service = PlayerService.MPD
|
|
|
|
elif self.get_setting('spotify', 'auto_download') == 'True' and downloadStatus == SpotifyDLStatus.NONE or downloadStatus == SpotifyDLStatus.ERROR:
|
|
|
|
self.spotifydl.download(text)
|
|
|
|
|
2022-02-11 15:54:54 +01:00
|
|
|
if self.service == PlayerService.SPOTIFY and self.spotify_connect():
|
2022-02-06 10:19:03 +01:00
|
|
|
if text != self.current:
|
2022-02-11 15:54:54 +01:00
|
|
|
self.spotify.volume(self.volume)
|
|
|
|
if self.spotify.play(text):
|
2022-02-06 10:19:03 +01:00
|
|
|
self.current = text
|
2022-02-11 15:54:54 +01:00
|
|
|
self.set_setting(
|
|
|
|
'luniebox', 'current', self.current)
|
2022-02-06 10:19:03 +01:00
|
|
|
self.resume = False
|
2022-02-11 15:54:54 +01:00
|
|
|
logging.getLogger('luniebox').debug(
|
|
|
|
"play spotify: " + self.current)
|
|
|
|
else:
|
|
|
|
logging.getLogger('luniebox').warn(
|
|
|
|
"cannot play spotify: " + self.current)
|
2022-02-06 10:19:03 +01:00
|
|
|
elif self.resume and text == self.current:
|
2022-02-11 15:54:54 +01:00
|
|
|
self.spotify.volume(self.volume)
|
|
|
|
play = self.current
|
|
|
|
if self.spotify.is_active():
|
|
|
|
play = None
|
|
|
|
if self.spotify.play(play):
|
2022-02-06 10:19:03 +01:00
|
|
|
self.resume = False
|
2022-02-11 15:54:54 +01:00
|
|
|
logging.getLogger('luniebox').debug(
|
|
|
|
"resume spotify: " + self.current)
|
|
|
|
else:
|
|
|
|
logging.getLogger('luniebox').warn(
|
|
|
|
"cannot resume spotify: " + self.current)
|
|
|
|
return True
|
|
|
|
elif self.service == PlayerService.MPD and self.mpd_connect():
|
|
|
|
if text != self.current:
|
|
|
|
try:
|
|
|
|
self.mpd.setvol(self.volume)
|
|
|
|
self.mpd.clear()
|
|
|
|
mpd_uri = text.replace('mpd:', '')
|
|
|
|
self.mpd.add(mpd_uri)
|
|
|
|
self.mpd.play()
|
|
|
|
except Exception as exception:
|
|
|
|
logging.getLogger('luniebox').warning(
|
|
|
|
"cannot not play mpd '" + text + "': " + str(exception))
|
|
|
|
return False
|
|
|
|
self.current = text
|
|
|
|
self.set_setting('luniebox', 'current', self.current)
|
|
|
|
self.resume = False
|
|
|
|
if text.startswith('spotify:'):
|
|
|
|
logging.getLogger('luniebox').debug(
|
|
|
|
"play spotify from mpd: " + text)
|
|
|
|
else:
|
|
|
|
logging.getLogger('luniebox').debug(
|
|
|
|
"play mpd: " + self.current)
|
|
|
|
elif self.resume and text == self.current:
|
|
|
|
try:
|
|
|
|
self.mpd.setvol(self.volume)
|
|
|
|
self.mpd.play()
|
|
|
|
except Exception as exception:
|
|
|
|
logging.getLogger('luniebox').warning(
|
|
|
|
"cannot not resume mpd '" + self.current + "': " + str(exception))
|
|
|
|
return False
|
|
|
|
self.resume = False
|
|
|
|
if text.startswith('spotify:'):
|
|
|
|
logging.getLogger('luniebox').debug(
|
|
|
|
"resume spotify from mpd: " + text)
|
|
|
|
else:
|
|
|
|
logging.getLogger('luniebox').debug(
|
|
|
|
"resume mpd: " + self.current)
|
2022-02-08 20:09:26 +01:00
|
|
|
return True
|
|
|
|
elif text != None:
|
|
|
|
logging.getLogger('luniebox').info(
|
|
|
|
"invalid value(?): " + str(text))
|
|
|
|
return False
|
|
|
|
return False
|
2022-02-06 10:19:03 +01:00
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
self.pause()
|
|
|
|
|
|
|
|
def sort_mpd(self, object):
|
|
|
|
if 'directory' in object:
|
|
|
|
return object['directory']
|
|
|
|
elif 'file' in object:
|
|
|
|
return object['file']
|
|
|
|
return ''
|
|
|
|
|
|
|
|
def mpd_list(self, path=''):
|
|
|
|
if self.mpd_connect():
|
|
|
|
try:
|
2022-02-24 16:15:15 +01:00
|
|
|
self.mpd.update(path)
|
2022-02-06 10:19:03 +01:00
|
|
|
result = self.mpd.listfiles(path)
|
|
|
|
return sorted(result, key=self.sort_mpd)
|
|
|
|
except:
|
|
|
|
return []
|
|
|
|
return None
|
|
|
|
|
|
|
|
def mpd_status(self):
|
|
|
|
if self.mpd_connect():
|
|
|
|
status = self.mpd.status()
|
|
|
|
status['song'] = self.mpd.currentsong()
|
|
|
|
return status
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
luniebox = Luniebox()
|