def base64_encode_image(filename): """:return the base64 encoded image if filename was a image else None""" try: imgfile = open(filename, "rb") img = imgfile.read(1024) except IOError as err: log("Artwork '{0}' won't be displayed: {2}". format(filename, err.message), min_verbosity=1, error=True) return None mimetype = APP.magic.buffer(img).lower() mimere = re.compile(r'(?P<mimetype>image/(png|jpeg|jpg|bmp|gif)).*') mimes = mimere.findall(mimetype) if len(mimes) == 1: mimetype = mimes[0][0] img += imgfile.read() # read remeaning file imgfile.close() imgstr = str(base64.b64encode(img).decode('ascii')) imgstr = imgstr.replace('\n', '') return "data:{mime};base64,{base64}".format( mime=mimetype, base64=imgstr) else: log("Artwork '{0}' rejected: invalid mimetype '{1}'". format(filename, mimetype), min_verbosity=1, error=True) imgfile.close() return None
def open(self): """when a client connects, add this socket to list""" APP.clients.append(self) mpris_prop_change_handler() self.send_status() log("WebSocket opened. {0} child(s) connected". format(len(APP.clients)), min_verbosity=1)
def glib_loop_thread(self): """ runs the GLib main loop to get notified about player status changes """ self.mloop.run() log("GLib loop exited", min_verbosity=2) thread.exit_thread()
def __init__(self, prop_changed_handler): """ :param properties_changed_handler: a function that will be executed on status changes like `def handler(self, *args, **kw)` """ # initialize DBus: DBusGMainLoop(set_as_default=True) self.prop_changed_handler = prop_changed_handler self.try_connect() if not self.player: log("No MPRIS player available. " + "Please start your favourite music player with MPRIS support.", min_verbosity=0, error=True) self.mloop = gi.repository.GLib.MainLoop() thread.start_new_thread(self.glib_loop_thread, ()) thread.start_new_thread(self.check_connection_thread, ()) signal.signal(signal.SIGINT, self.sigint_handler)
def check_connection_thread(self): """ checks if the currently connected player is connected and tries to reconnect if not """ while True: time.sleep(5) try: self.player.CanControl # -> AttributeError when None # -> DBusException when not connected anymore # I found no other method to test this # -> better sorry than safe log("Player is still connected", min_verbosity=5) # except AttributeError, dbus.exceptions.DBusException: except (AttributeError, dbus.exceptions.DBusException): log("Player is NOT connected", min_verbosity=5) self.try_connect() self.prop_changed_handler()
def main(): """the main function starts the server""" global APP APP = make_app() APP.args = parse_args() APP.clients = [] # global list of all connected websocket clients APP.mpris_wrapper = MPRISWrapper(mpris_prop_change_handler) log("Loading magic file for mimetypes. Please wait.", min_verbosity=1) APP.magic = magic.open(magic.MAGIC_MIME_TYPE) APP.magic.load() log("Loading finished.", min_verbosity=2) APP.listen(APP.args.port, address=APP.args.ip) log("App will listen on http://{ip}:{port}".format( ip=APP.args.ip, port=APP.args.port)) tornado.ioloop.IOLoop.current().start()
def try_connect(self): """ Sets self.player to a player and sets the prop_changed_handler if there is a mpris player on dbus or sets self.player to None """ log("Try to connect to player", min_verbosity=4) uris = list(mpris2.get_players_uri()) if len(uris): self.player = mpris2.Player(dbus_interface_info={ 'dbus_uri': uris[0] # connects to the first player }) self.player.PropertiesChanged = self.prop_changed_handler log("Connected to player", min_verbosity=4) else: log("Can't connect. No MPRIS player available", min_verbosity=4, error=True) self.player = None
def mpris_prop_change_handler(*args, **kw): """function will be executed on mpris player property changes""" meta = { "titles": { "current": APP.mpris_wrapper.get_current_title(), "next": APP.mpris_wrapper.get_next_title(), }, "trackMetadata": APP.mpris_wrapper.get_current_metadata(), "status": APP.mpris_wrapper.get_playback_status(), "player": { "canControl": APP.mpris_wrapper.get_can_control(), "canGoNext": APP.mpris_wrapper.get_can_go_next(), "canGoPrevious": APP.mpris_wrapper.get_can_go_previous(), "canPlay": APP.mpris_wrapper.get_can_play(), "canPause": APP.mpris_wrapper.get_can_pause(), }, "volume": APP.mpris_wrapper.volume, } if 'trackMetadata' in meta.keys() and \ 'artUri' in meta['trackMetadata'].keys() and \ meta['trackMetadata']['artUri']: arturi = meta['trackMetadata']['artUri'] arturi = arturi.strip().replace("file://", '') if arturi and os.path.isfile(arturi): image = base64_encode_image(arturi) if image: meta['trackMetadata']['art'] = image else: log("Artwork '{0}' won't be displayed: could not open file". format(arturi), min_verbosity=1, error=True) else: log("Artwork '{0}' won't be displayed: file not found". format(arturi), min_verbosity=3, error=True) del meta['trackMetadata']['artUri'] else: log("Track has no Artwork", min_verbosity=4) APP.meta_status = meta for client in APP.clients: client.send_status()
def on_message(self, message): """new message received""" try: msg = json.loads(message) except ValueError: log("Client sent an invalid message: {0}".format(message), error=True) return if 'action' not in msg: log("invalid message received from client: '{0}'". format(str(msg)), min_verbosity=2, error=True) return elif msg['action'] == 'backward': APP.mpris_wrapper.previous() elif msg['action'] == 'play': APP.mpris_wrapper.play() elif msg['action'] == 'pause': APP.mpris_wrapper.pause() elif msg['action'] == 'stop': APP.mpris_wrapper.stop() elif msg['action'] == 'forward': APP.mpris_wrapper.next() elif msg['action'] == 'volume': if 'value' in msg.keys(): try: APP.mpris_wrapper.volume = float(msg['value']) except ValueError as err: log(("Invalid value '{0}' for volume " + "in message from client: {1}"). format(msg['value'], err.message), min_verbosity=1, error=True) else: log("Missing value for action 'volume' in message from client", min_verbosity=1, error=True) else: log("invalid action '{0}' received from client". format(msg['action']), min_verbosity=1, error=True) log("received: " + message, min_verbosity=2)
def on_close(self): """the client of this socket leaved, remove this socket from list""" APP.clients.remove(self) log("WebSocket closed. {0} child(s) connected". format(len(APP.clients)), min_verbosity=1)
def send_status(self): """sends the current and next tracks to the client""" self.write_message(json.dumps(APP.meta_status), binary=False) log("send: {0}".format(APP.meta_status), min_verbosity=3)
def sigint_handler(self, sig, frame): """Stop on sigint (Ctrl+C)""" log("Signal {0} in frame {1} caught".format(sig, frame), min_verbosity=1) self.mloop.quit() exit(0)