Clean ups

This commit is contained in:
badaix 2021-06-02 08:49:59 +02:00
parent 7c11cb7559
commit 4ffecbb9c1
2 changed files with 148 additions and 158 deletions

View file

@ -175,7 +175,6 @@ tag_mapping = {
# Default url handlers if MPD doesn't support 'urlhandlers' command
urlhandlers = ['http://']
downloaded_covers = ['~/.covers/%s-%s.jpg']
def send(json_msg):
@ -201,18 +200,10 @@ class MPDWrapper(object):
self._watch_id = None
self._idling = False
self._status = {
'state': None,
'volume': None,
'random': None,
'repeat': None,
}
self._metadata = {}
self._temp_song_url = None
self._temp_cover = None
self._status = {}
self._currentsong = {}
self._position = 0
self._time = 0
self._currentsong = None
def run(self):
"""
@ -318,11 +309,6 @@ class MPDWrapper(object):
self.run()
def disconnect(self):
self._temp_song_url = None
if self._temp_cover:
self._temp_cover.close()
self._temp_cover = None
self.client.disconnect()
def init_state(self):
@ -429,7 +415,7 @@ class MPDWrapper(object):
self.single(0)
else:
send({"jsonrpc": "2.0", "error": {"code": -32601,
"message": "Method not found"}, "id": id})
"message": "Method not found"}, "id": id})
success = False
else:
send({"jsonrpc": "2.0", "error": {"code": -32601,
@ -476,8 +462,7 @@ class MPDWrapper(object):
if subsystem in ("player", "mixer", "options", "playlist"):
if not updated:
logger.info(f'Subsystem: {subsystem}')
self._update_properties(
force=subsystem == 'player')
self._update_properties(force=True)
updated = True
self.idle_enter()
return True
@ -554,8 +539,8 @@ class MPDWrapper(object):
except KeyError:
logger.warning(f'tag "{key}" not supported')
except (ValueError, TypeError):
logger.warning("Can't cast value %r to %s" %
(value, tag_mapping[key][1]))
logger.warning(
f"Can't cast value {value} to {tag_mapping[key][1]}")
logger.debug(f'snapcast meta: {snapmeta}')
@ -576,16 +561,47 @@ class MPDWrapper(object):
send({"jsonrpc": "2.0", "method": "Player.Metadata", "params": snapmeta})
self.update_albumart(snapmeta)
def __diff_map(self, old_map, new_map):
diff = {}
for key, value in new_map.items():
if not key in old_map:
diff[key] = [None, value]
elif value != old_map[key]:
diff[key] = [old_map[key], value]
for key, value in old_map.items():
if not key in new_map:
diff[key] = [value, None]
return diff
def _update_properties(self, force=False):
old_status = self._status
old_position = self._position
old_time = self._time
currentsong = self.client.currentsong()
if self._currentsong != currentsong:
self._currentsong = currentsong
force = True
new_song = self.client.currentsong()
if not new_song:
logger.debug("_update_properties: failed to get current song")
return
new_status = self.client.status()
if not new_status:
logger.debug("_update_properties: failed to get new status")
return
changed_status = self.__diff_map(self._status, new_status)
if len(changed_status) > 0:
self._status = new_status
changed_song = self.__diff_map(self._currentsong, new_song)
if len(changed_song) > 0:
self._currentsong = new_song
if len(changed_song) == 0 and len(changed_status) == 0:
logger.debug('nothing to do')
return
logger.info(f'new status: {new_status}')
logger.info(f'changed_status: {changed_status}')
logger.info(f'changed_song: {changed_song}')
self._time = new_time = int(time.time())
snapstatus = {}
@ -597,10 +613,10 @@ class MPDWrapper(object):
logger.debug(
f'key: {key}, value: {value}, mapped key: {mapped_key}, mapped value: {mapped_val}')
except KeyError:
logger.warning(f'tag "{key}" not supported')
logger.debug(f'tag "{key}" not supported')
except (ValueError, TypeError):
logger.warning("Can't cast value %r to %s" %
(value, status_mapping[key][1]))
logger.warning(
f"Can't cast value {value} to {status_mapping[key][1]}")
snapstatus['canGoNext'] = True
snapstatus['canGoPrevious'] = True
@ -610,15 +626,6 @@ class MPDWrapper(object):
snapstatus['canControl'] = True
send({"jsonrpc": "2.0", "method": "Player.Properties", "params": snapstatus})
if not new_status:
logger.debug("_update_properties: failed to get new status")
return
self._status = new_status
logger.debug("_update_properties: current song = %r" %
self._currentsong)
logger.debug("_update_properties: current status = %r" % self._status)
if 'elapsed' in new_status:
new_position = float(new_status['elapsed'])
elif 'time' in new_status:
@ -630,13 +637,7 @@ class MPDWrapper(object):
# "player" subsystem
if old_status['state'] != new_status['state']:
logger.info('state changed')
if not force:
old_id = old_status.get('songid', None)
new_id = new_status.get('songid', None)
force = (old_id != new_id)
force = len(changed_song) > 0
if not force:
if new_status['state'] == 'play':
@ -652,26 +653,8 @@ class MPDWrapper(object):
else:
# Update current song metadata
old_meta = self._metadata.copy()
self.update_metadata()
# "mixer" subsystem
if old_status.get('volume') != new_status.get('volume'):
logger.info('volume changed')
# "options" subsystem
# also triggered if consume, crossfade or ReplayGain are updated
if old_status['random'] != new_status['random']:
logger.info('random changed')
if (old_status['repeat'] != new_status['repeat']
or old_status.get('single', 0) != new_status.get('single', 0)):
logger.info('repeat changed')
if ("nextsongid" in old_status) != ("nextsongid" in new_status):
logger.info('nextsongid changed')
# Compatibility functions
# Fedora 17 still has python-mpd 0.2, which lacks fileno().

View file

@ -25,12 +25,9 @@ import websocket
import logging
import threading
import json
from configparser import ConfigParser
import webbrowser
import os
import sys
import re
import shlex
import getopt
import time
import socket
@ -106,31 +103,6 @@ defaults = {
notification = None
# MPRIS allowed metadata tags
allowed_tags = {
'mpris:trackid': dbus.ObjectPath,
'mpris:length': dbus.Int64,
'mpris:artUrl': str,
'xesam:album': str,
'xesam:albumArtist': list,
'xesam:artist': list,
'xesam:asText': str,
'xesam:audioBPM': int,
'xesam:comment': list,
'xesam:composer': list,
'xesam:contentCreated': str,
'xesam:discNumber': int,
'xesam:firstUsed': str,
'xesam:genre': list,
'xesam:lastUsed': str,
'xesam:lyricist': str,
'xesam:title': str,
'xesam:trackNumber': int,
'xesam:url': str,
'xesam:useCount': int,
'xesam:userRating': float,
}
# python dbus bindings don't include annotations and properties
MPRIS2_INTROSPECTION = """<node name="/org/mpris/MediaPlayer2">
@ -259,28 +231,42 @@ class SnapcastRpcListener:
tag_mapping = {
'trackId': 'mpris:trackid',
'artUrl': 'mpris:artUrl',
'duration': 'mpris:length',
'album': 'xesam:album',
'albumArtist': 'xesam:albumArtist',
'artist': 'xesam:artist',
'asText': 'xesam:asText',
'audioBPM': 'xesam:audioBPM',
'autoRating': 'xesam:autoRating',
'comment': 'xesam:comment',
'composer': 'xesam:composer',
'contentCreated': 'xesam:contentCreated',
'discNumber': 'xesam:discNumber',
'firstUsed': 'xesam:firstUsed',
'genre': 'xesam:genre',
'lastUsed': 'xesam:lastUsed',
'lyricist': 'xesam:lyricist',
'title': 'xesam:title',
'trackNumber': 'xesam:trackNumber',
'url': 'xesam:url',
'useCount': 'xesam:useCount',
'userRating': 'xesam:userRating',
'trackId': ['mpris:trackid', lambda val: dbus.ObjectPath(f'/org/mpris/MediaPlayer2/Track/{val}')],
'artUrl': ['mpris:artUrl', str],
'duration': ['mpris:length', lambda val: int(val * 1000000)],
'album': ['xesam:album', str],
'albumArtist': ['xesam:albumArtist', list],
'artist': ['xesam:artist', list],
'asText': ['xesam:asText', str],
'audioBPM': ['xesam:audioBPM', int],
'autoRating': ['xesam:autoRating', float],
'comment': ['xesam:comment', list],
'composer': ['xesam:composer', list],
'contentCreated': ['xesam:contentCreated', str],
'discNumber': ['xesam:discNumber', int],
'firstUsed': ['xesam:firstUsed', str],
'genre': ['xesam:genre', list],
'lastUsed': ['xesam:lastUsed', str],
'lyricist': ['xesam:lyricist', str],
'title': ['xesam:title', str],
'trackNumber': ['xesam:trackNumber', int],
'url': ['xesam:url', str],
'useCount': ['xesam:useCount', int],
'userRating': ['xesam:userRating', float]
}
property_mapping = {
'playbackStatus': 'PlaybackStatus',
'loopStatus': 'LoopStatus',
'shuffle': 'Shuffle',
'volume': 'Volume',
'canGoNext': 'CanGoNext',
'canGoPrevious': 'CanGoPrevious',
'canPlay': 'CanPlay',
'canPause': 'CanPause',
'canSeek': 'CanSeek',
'canControl': 'CanControl'
}
@ -334,8 +320,7 @@ class MPDWrapper(object):
logger.info("Ending SnapcastRpcWebsocketWrapper loop")
def on_ws_message(self, ws, message):
logger.debug("Snapcast RPC websocket message received")
logger.debug(message)
logger.debug(f'Snapcast RPC websocket message received: {message}')
jmsg = json.loads(message)
if jmsg["method"] == "Stream.OnMetadata":
logger.info(f'Stream meta changed for "{jmsg["params"]["id"]}"')
@ -348,14 +333,14 @@ class MPDWrapper(object):
for key, value in meta.items():
if key in tag_mapping:
self._metadata[tag_mapping[key]] = value
if 'mpris:length' in self._metadata:
self._metadata['mpris:length'] = int(1000 * 1000 *
self._metadata['mpris:length'])
if 'mpris:trackid' in self._metadata:
self._metadata[
'mpris:trackid'] = f'/org/mpris/MediaPlayer2/Track/{self._metadata["mpris:trackid"]}'
try:
self._metadata[tag_mapping[key][0]
] = tag_mapping[key][1](value)
except KeyError:
logger.warning(f'tag "{key}" not supported')
except (ValueError, TypeError):
logger.warning(
f"Can't cast value {value} to {tag_mapping[key][1]}")
if not 'mpris:artUrl' in self._metadata:
self._metadata['mpris:artUrl'] = f'http://{self._params["host"]}:{self._params["port"]}/launcher-icon.png'
@ -375,21 +360,10 @@ class MPDWrapper(object):
props['received'] = time.time()
changed_properties = self.update_properties(props)
logger.info(f'Changed properties: "{changed_properties}"')
if 'playbackStatus' in changed_properties:
self._dbus_service.update_property(
'org.mpris.MediaPlayer2.Player', 'PlaybackStatus')
if 'loopStatus' in changed_properties:
self._dbus_service.update_property(
'org.mpris.MediaPlayer2.Player', 'LoopStatus')
if 'shuffle' in changed_properties:
self._dbus_service.update_property(
'org.mpris.MediaPlayer2.Player', 'Shuffle')
if 'volume' in changed_properties:
self._dbus_service.update_property(
'org.mpris.MediaPlayer2.Player', 'Volume')
if 'canGoNext' in changed_properties:
self._dbus_service.update_property(
'org.mpris.MediaPlayer2.Player', 'CanGoNext')
for key, value in changed_properties.items():
if key in property_mapping:
self._dbus_service.update_property(
'org.mpris.MediaPlayer2.Player', property_mapping[key])
def on_ws_error(self, ws, error):
logger.error("Snapcast RPC websocket error")
@ -407,7 +381,7 @@ class MPDWrapper(object):
# '/org/mpris/MediaPlayer2')
self._dbus_service.acquire_name()
self.send({"id": 1, "jsonrpc": "2.0", "method": "Server.GetStatus"})
self.send_request("Server.GetStatus")
def on_ws_close(self, ws):
logger.info("Snapcast RPC websocket closed")
@ -415,8 +389,14 @@ class MPDWrapper(object):
self._dbus_service.release_name()
# self._dbus_service.remove_from_connection()
def send(self, json_msg):
self.websocket.send(json.dumps(json_msg))
def send_request(self, method, params=None):
j = {"id": self._req_id, "jsonrpc": "2.0", "method": str(method)}
if not params is None:
j["params"] = params
logger.info(f'send_request: {j}')
url = f'http://{self._params["host"]}:{self._params["port"]}/jsonrpc'
self._req_id += 1
self.websocket.send(json.dumps(j))
def stop(self):
self.websocket.keep_running = False
@ -490,25 +470,15 @@ class MPDWrapper(object):
# return self._currentsong.copy()
def control(self, command, params={}):
j = {"id": self._req_id, "jsonrpc": "2.0", "method": "Stream.Control",
"params": {"id": "Pipe", "command": command, "params": params}}
logger.info(f'Control: {command}, json: {j}')
url = f'http://{self._params["host"]}:{self._params["port"]}/jsonrpc'
logger.info(f'url: {url}')
self._req_id += 1
self.send(j)
self.send_request("Stream.Control", {
"id": "Pipe", "command": command, "params": params})
def set_property(self, property, value):
properties = {}
properties[property] = value
logger.info(f'set_properties {properties}')
j = {"id": self._req_id, "jsonrpc": "2.0", "method": "Stream.SetProperties",
"params": {"id": "Pipe", "properties": properties}}
logger.info(f'Set properties: {properties}, json: {j}')
url = f'http://{self._params["host"]}:{self._params["port"]}/jsonrpc'
logger.info(f'url: {url}')
self._req_id += 1
self.send(j)
self.send_request("Stream.SetProperties", {
"id": "Pipe", "properties": properties})
@property
def metadata(self):
@ -526,7 +496,7 @@ class MPDWrapper(object):
elif value != self._properties[key]:
changed_properties[key] = [self._properties[key], value]
for key, value in self._properties.items():
if not key in self._properties:
if not key in new_properties:
changed_properties[key] = [value, None]
self._properties = new_properties
return changed_properties
@ -823,7 +793,7 @@ class MPRISInterface(dbus.service.Object):
__root_interface = "org.mpris.MediaPlayer2"
__root_props = {
"CanQuit": (False, None),
"CanRaise": (False, None),
"CanRaise": (True, None),
"DesktopEntry": ("mpdris2", None),
"HasTrackList": (False, None),
"Identity": (identity, None),
@ -880,6 +850,36 @@ class MPRISInterface(dbus.service.Object):
logger.debug(f'get_position: {position}')
return dbus.Int64(position)
def __get_can_go_next():
can_go_next = snapcast_wrapper.property('canGoNext', False)
logger.debug(f'get_can_go_next "{can_go_next}"')
return can_go_next
def __get_can_go_previous():
can_go_previous = snapcast_wrapper.property('canGoPrevious', False)
logger.debug(f'get_can_go_previous "{can_go_previous}"')
return can_go_previous
def __get_can_play():
can_play = snapcast_wrapper.property('canPlay', False)
logger.debug(f'get_can_play "{can_play}"')
return can_play
def __get_can_pause():
can_pause = snapcast_wrapper.property('canPause', False)
logger.debug(f'get_can_pause "{can_pause}"')
return can_pause
def __get_can_seek():
can_seek = snapcast_wrapper.property('canSeek', False)
logger.debug(f'get_can_seek "{can_seek}"')
return can_seek
def __get_can_control():
can_control = snapcast_wrapper.property('canControl', False)
logger.debug(f'get_can_control "{can_control}"')
return can_control
__player_interface = "org.mpris.MediaPlayer2.Player"
__player_props = {
"PlaybackStatus": (__get_playback_status, None),
@ -897,6 +897,12 @@ class MPRISInterface(dbus.service.Object):
"CanPause": (True, None),
"CanSeek": (True, None),
"CanControl": (True, None),
"CanGoNext": (__get_can_go_next, None),
"CanGoPrevious": (__get_can_go_previous, None),
"CanPlay": (__get_can_play, None),
"CanPause": (__get_can_pause, None),
"CanSeek": (__get_can_seek, None),
"CanControl": (__get_can_control, None),
}
__prop_mapping = {
@ -953,6 +959,7 @@ class MPRISInterface(dbus.service.Object):
@ dbus.service.method(__root_interface, in_signature='', out_signature='')
def Raise(self):
logger.info('Raise')
webbrowser.open(url=f'http://{params["host"]}:{params["port"]}', new=1)
return
@ dbus.service.method(__root_interface, in_signature='', out_signature='')