Remove mpris stuff

This commit is contained in:
badaix 2021-05-19 08:54:01 +02:00
parent 91fe1c7722
commit c300367abb

View file

@ -28,8 +28,6 @@ import shlex
import socket import socket
import getopt import getopt
import mpd import mpd
import dbus
import dbus.service
from dbus.mainloop.glib import DBusGMainLoop from dbus.mainloop.glib import DBusGMainLoop
import logging import logging
import gettext import gettext
@ -90,14 +88,9 @@ params = {
'host': None, 'host': None,
'port': None, 'port': None,
'password': None, 'password': None,
'bus_name': None,
# Library # Library
'music_dir': '', 'music_dir': '',
'cover_regex': None, 'cover_regex': None,
# Bling
'mmkeys': True,
'notify': (using_gi_notify or using_old_notify),
'notify_urgency': 0,
} }
defaults = { defaults = {
@ -110,32 +103,6 @@ defaults = {
'cover_regex': r'^(album|cover|\.?folder|front).*\.(gif|jpeg|jpg|png)$', 'cover_regex': r'^(album|cover|\.?folder|front).*\.(gif|jpeg|jpg|png)$',
} }
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,
}
# MPD to Snapcast tag mapping # MPD to Snapcast tag mapping
@ -166,7 +133,6 @@ tag_mapping = {
'userrating': ['userRating', float, False], 'userrating': ['userRating', float, False],
'duration': ['duration', float, False], 'duration': ['duration', float, False],
'track': ['track', int, False],
'name': ['name', str, False], 'name': ['name', str, False],
'originaldate': ['originalDate', str, False], 'originaldate': ['originalDate', str, False],
'performer': ['performer', str, True], 'performer': ['performer', str, True],
@ -182,113 +148,6 @@ tag_mapping = {
'musicbrainz_workid': ['musicbrainzWorkId', str, False], 'musicbrainz_workid': ['musicbrainzWorkId', str, False],
} }
# python dbus bindings don't include annotations and properties
MPRIS2_INTROSPECTION = """<node name="/org/mpris/MediaPlayer2">
<interface name="org.freedesktop.DBus.Introspectable">
<method name="Introspect">
<arg direction="out" name="xml_data" type="s"/>
</method>
</interface>
<interface name="org.freedesktop.DBus.Properties">
<method name="Get">
<arg direction="in" name="interface_name" type="s"/>
<arg direction="in" name="property_name" type="s"/>
<arg direction="out" name="value" type="v"/>
</method>
<method name="GetAll">
<arg direction="in" name="interface_name" type="s"/>
<arg direction="out" name="properties" type="a{sv}"/>
</method>
<method name="Set">
<arg direction="in" name="interface_name" type="s"/>
<arg direction="in" name="property_name" type="s"/>
<arg direction="in" name="value" type="v"/>
</method>
<signal name="PropertiesChanged">
<arg name="interface_name" type="s"/>
<arg name="changed_properties" type="a{sv}"/>
<arg name="invalidated_properties" type="as"/>
</signal>
</interface>
<interface name="org.mpris.MediaPlayer2">
<method name="Raise"/>
<method name="Quit"/>
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
<property name="CanQuit" type="b" access="read"/>
<property name="CanRaise" type="b" access="read"/>
<property name="HasTrackList" type="b" access="read"/>
<property name="Identity" type="s" access="read"/>
<property name="DesktopEntry" type="s" access="read"/>
<property name="SupportedUriSchemes" type="as" access="read"/>
<property name="SupportedMimeTypes" type="as" access="read"/>
</interface>
<interface name="org.mpris.MediaPlayer2.Player">
<method name="Next"/>
<method name="Previous"/>
<method name="Pause"/>
<method name="PlayPause"/>
<method name="Stop"/>
<method name="Play"/>
<method name="Seek">
<arg direction="in" name="Offset" type="x"/>
</method>
<method name="SetPosition">
<arg direction="in" name="TrackId" type="o"/>
<arg direction="in" name="Position" type="x"/>
</method>
<method name="OpenUri">
<arg direction="in" name="Uri" type="s"/>
</method>
<signal name="Seeked">
<arg name="Position" type="x"/>
</signal>
<property name="PlaybackStatus" type="s" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property>
<property name="LoopStatus" type="s" access="readwrite">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property>
<property name="Rate" type="d" access="readwrite">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property>
<property name="Shuffle" type="b" access="readwrite">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property>
<property name="Metadata" type="a{sv}" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property>
<property name="Volume" type="d" access="readwrite">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
</property>
<property name="Position" type="x" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
</property>
<property name="MinimumRate" type="d" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property>
<property name="MaximumRate" type="d" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property>
<property name="CanGoNext" type="b" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property>
<property name="CanGoPrevious" type="b" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property>
<property name="CanPlay" type="b" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property>
<property name="CanPause" type="b" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property>
<property name="CanSeek" type="b" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
</property>
<property name="CanControl" type="b" access="read">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="false"/>
</property>
</interface>
</node>"""
# Default url handlers if MPD doesn't support 'urlhandlers' command # Default url handlers if MPD doesn't support 'urlhandlers' command
urlhandlers = ['http://'] urlhandlers = ['http://']
@ -303,9 +162,7 @@ class MPDWrapper(object):
def __init__(self, params): def __init__(self, params):
self.client = mpd.MPDClient() self.client = mpd.MPDClient()
self._dbus = dbus
self._params = params self._params = params
self._dbus_service = None
self._can_single = False self._can_single = False
self._can_idle = False self._can_idle = False
@ -327,16 +184,12 @@ class MPDWrapper(object):
self._position = 0 self._position = 0
self._time = 0 self._time = 0
self._bus = dbus.SessionBus()
if self._params['mmkeys']:
self.setup_mediakeys()
def run(self): def run(self):
""" """
Try to connect to MPD; retry every 5 seconds on failure. Try to connect to MPD; retry every 5 seconds on failure.
""" """
if self.my_connect(): if self.my_connect():
GLib.timeout_add_seconds(5, self.my_connect) # GLib.timeout_add_seconds(5, self.my_connect)
return False return False
else: else:
return True return True
@ -373,21 +226,12 @@ class MPDWrapper(object):
self._can_single = True self._can_single = True
if self._errors > 0: if self._errors > 0:
notification.notify(identity, _('Reconnected'))
logger.info('Reconnected to MPD server.') logger.info('Reconnected to MPD server.')
else: else:
logger.debug('Connected to MPD server.') logger.debug('Connected to MPD server.')
# Make the socket non blocking to detect deconnections # Make the socket non blocking to detect deconnections
self.client._sock.settimeout(5.0) self.client._sock.settimeout(5.0)
# Export our DBUS service
if not self._dbus_service:
self._dbus_service = MPRISInterface(self._params)
else:
# Add our service to the session bus
# self._dbus_service.add_to_connection(dbus.SessionBus(),
# '/org/mpris/MediaPlayer2')
self._dbus_service.acquire_name()
# Init internal state to throw events at start # Init internal state to throw events at start
self.init_state() self.init_state()
@ -424,20 +268,6 @@ class MPDWrapper(object):
def reconnect(self): def reconnect(self):
logger.warning("Disconnected") logger.warning("Disconnected")
notification.notify(identity, _('Disconnected'), 'error')
# Release the DBus name and disconnect from bus
if self._dbus_service is not None:
self._dbus_service.release_name()
# self._dbus_service.remove_from_connection()
# Stop monitoring
if self._poll_id:
GLib.source_remove(self._poll_id)
self._poll_id = None
if self._watch_id:
GLib.source_remove(self._watch_id)
self._watch_id = None
# Clean mpd client state # Clean mpd client state
try: try:
@ -519,28 +349,12 @@ class MPDWrapper(object):
# subsystem list: <http://www.musicpd.org/doc/protocol/ch03.html> # subsystem list: <http://www.musicpd.org/doc/protocol/ch03.html>
if subsystem in ("player", "mixer", "options", "playlist"): if subsystem in ("player", "mixer", "options", "playlist"):
if not updated: if not updated:
logger.info(f'Subsystem: {subsystem}')
self._update_properties(force=True) self._update_properties(force=True)
updated = True updated = True
self.idle_enter() self.idle_enter()
return True return True
def mediakey_callback(self, appname, key):
""" GNOME media key handler """
logger.debug('Got GNOME mmkey "%s" for "%s"' % (key, appname))
if key == 'Play':
if self._status['state'] == 'play':
self.client.pause(1)
self.notify_about_state('pause')
else:
self.play()
self.notify_about_state('play')
elif key == 'Next':
self.client.next()
elif key == 'Previous':
self.client.previous()
elif key == 'Stop':
self.client.stop()
self.notify_about_state('stop')
def last_currentsong(self): def last_currentsong(self):
return self._currentsong.copy() return self._currentsong.copy()
@ -587,123 +401,6 @@ class MPDWrapper(object):
"id": "Spotify", "meta": snapmeta}}) "id": "Spotify", "meta": snapmeta}})
print(r) print(r)
self._metadata = {}
for tag in ('album', 'title'):
if tag in mpd_meta:
self._metadata['xesam:%s' % tag] = mpd_meta[tag]
if 'id' in mpd_meta:
self._metadata['mpris:trackid'] = "/org/mpris/MediaPlayer2/Track/%s" % \
mpd_meta['id']
if 'time' in mpd_meta:
self._metadata['mpris:length'] = int(mpd_meta['time']) * 1000000
if 'date' in mpd_meta:
self._metadata['xesam:contentCreated'] = mpd_meta['date'][0:4]
if 'track' in mpd_meta:
# TODO: Is it even *possible* for mpd_meta['track'] to be a list?
if type(mpd_meta['track']) == list and len(mpd_meta['track']) > 0:
track = str(mpd_meta['track'][0])
else:
track = str(mpd_meta['track'])
m = re.match('^([0-9]+)', track)
if m:
self._metadata['xesam:trackNumber'] = int(m.group(1))
# Ensure the integer is signed 32bit
if self._metadata['xesam:trackNumber'] & 0x80000000:
self._metadata['xesam:trackNumber'] += -0x100000000
else:
self._metadata['xesam:trackNumber'] = 0
if 'disc' in mpd_meta:
# TODO: Same as above. When is it a list?
if type(mpd_meta['disc']) == list and len(mpd_meta['disc']) > 0:
disc = str(mpd_meta['disc'][0])
else:
disc = str(mpd_meta['disc'])
m = re.match('^([0-9]+)', disc)
if m:
self._metadata['xesam:discNumber'] = int(m.group(1))
if 'artist' in mpd_meta:
if type(mpd_meta['artist']) == list:
self._metadata['xesam:artist'] = mpd_meta['artist']
else:
self._metadata['xesam:artist'] = [mpd_meta['artist']]
if 'composer' in mpd_meta:
if type(mpd_meta['composer']) == list:
self._metadata['xesam:composer'] = mpd_meta['composer']
else:
self._metadata['xesam:composer'] = [mpd_meta['composer']]
# Stream: populate some missings tags with stream's name
if 'name' in mpd_meta:
if 'xesam:title' not in self._metadata:
self._metadata['xesam:title'] = mpd_meta['name']
elif 'xesam:album' not in self._metadata:
self._metadata['xesam:album'] = mpd_meta['name']
if 'file' in mpd_meta:
song_url = mpd_meta['file']
if not any([song_url.startswith(prefix) for prefix in urlhandlers]):
song_url = os.path.join(self._params['music_dir'], song_url)
self._metadata['xesam:url'] = song_url
cover = self.find_cover(song_url)
if cover:
self._metadata['mpris:artUrl'] = cover
# Cast self._metadata to the correct type, or discard it
for key, value in self._metadata.items():
try:
self._metadata[key] = allowed_tags[key](value)
except ValueError:
del self._metadata[key]
logger.error("Can't cast value %r to %s" %
(value, allowed_tags[key]))
# if 'xesam:title' in self._metadata and 'xesam:album' in self._metadata:
# result = musicbrainzngs.search_releases(artist=self._metadata['xesam:title'], release=self._metadata['xesam:album'],
# limit=1)
# if result['release-list']:
# self._metadata[
# 'mpris:artUrl'] = f"http://coverartarchive.org/release/{result['release-list'][0]['id']}/front-250"
# print(self._metadata['mpris:artUrl'])
def notify_about_track(self, meta, state='play'):
uri = 'sound'
if 'mpris:artUrl' in meta:
uri = meta['mpris:artUrl']
title = 'Unknown Title'
if 'xesam:title' in meta:
title = meta['xesam:title']
elif 'xesam:url' in meta:
title = meta['xesam:url'].split('/')[-1]
artist = 'Unknown Artist'
if 'xesam:artist' in meta:
artist = ", ".join(meta['xesam:artist'])
body = _('by %s') % artist
if state == 'pause':
uri = 'media-playback-pause-symbolic'
body += ' (%s)' % _('Paused')
notification.notify(title, body, uri)
def notify_about_state(self, state):
if state == 'stop':
notification.notify(identity, _('Stopped'),
'media-playback-stop-symbolic')
else:
self.notify_about_track(self.metadata, state)
def find_cover(self, song_url): def find_cover(self, song_url):
if song_url.startswith('file://'): if song_url.startswith('file://'):
@ -805,6 +502,7 @@ class MPDWrapper(object):
old_time = self._time old_time = self._time
self._currentsong = self.client.currentsong() self._currentsong = self.client.currentsong()
new_status = self.client.status() new_status = self.client.status()
logger.info(f'new status: {new_status}')
self._time = new_time = int(time.time()) self._time = new_time = int(time.time())
if not new_status: if not new_status:
@ -828,8 +526,7 @@ class MPDWrapper(object):
# "player" subsystem # "player" subsystem
if old_status['state'] != new_status['state']: if old_status['state'] != new_status['state']:
self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', logger.info('state changed')
'PlaybackStatus')
if not force: if not force:
old_id = old_status.get('songid', None) old_id = old_status.get('songid', None)
@ -846,81 +543,29 @@ class MPDWrapper(object):
expected_position, new_position, new_position - expected_position)) expected_position, new_position, new_position - expected_position))
logger.debug("Old position was %r at %r (%r seconds ago)" % ( logger.debug("Old position was %r at %r (%r seconds ago)" % (
old_position, old_time, new_time - old_time)) old_position, old_time, new_time - old_time))
self._dbus_service.Seeked(new_position * 1000000) # self._dbus_service.Seeked(new_position * 1000000)
else: else:
# Update current song metadata # Update current song metadata
old_meta = self._metadata.copy() old_meta = self._metadata.copy()
self.update_metadata() self.update_metadata()
new_meta = self._dbus_service.update_property('org.mpris.MediaPlayer2.Player',
'Metadata')
if self._params['notify'] and new_status['state'] != 'stop':
if old_meta.get('xesam:artist', None) != new_meta.get('xesam:artist', None) \
or old_meta.get('xesam:album', None) != new_meta.get('xesam:album', None) \
or old_meta.get('xesam:title', None) != new_meta.get('xesam:title', None) \
or old_meta.get('xesam:url', None) != new_meta.get('xesam:url', None):
self.notify_about_track(new_meta, new_status['state'])
# "mixer" subsystem # "mixer" subsystem
if old_status.get('volume') != new_status.get('volume'): if old_status.get('volume') != new_status.get('volume'):
self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', logger.info('volume changed')
'Volume')
# "options" subsystem # "options" subsystem
# also triggered if consume, crossfade or ReplayGain are updated # also triggered if consume, crossfade or ReplayGain are updated
if old_status['random'] != new_status['random']: if old_status['random'] != new_status['random']:
self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', logger.info('random changed')
'Shuffle')
if (old_status['repeat'] != new_status['repeat'] if (old_status['repeat'] != new_status['repeat']
or old_status.get('single', 0) != new_status.get('single', 0)): or old_status.get('single', 0) != new_status.get('single', 0)):
self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', logger.info('repeat changed')
'LoopStatus')
if ("nextsongid" in old_status) != ("nextsongid" in new_status): if ("nextsongid" in old_status) != ("nextsongid" in new_status):
self._dbus_service.update_property('org.mpris.MediaPlayer2.Player', logger.info('nextsongid changed')
'CanGoNext')
# Media keys
def setup_mediakeys(self):
self.register_mediakeys()
self._dbus_obj = self._bus.get_object("org.freedesktop.DBus",
"/org/freedesktop/DBus")
self._dbus_obj.connect_to_signal("NameOwnerChanged",
self.gsd_name_owner_changed_callback,
arg0="org.gnome.SettingsDaemon")
def register_mediakeys(self):
try:
try:
gsd_object = self._bus.get_object("org.gnome.SettingsDaemon.MediaKeys",
"/org/gnome/SettingsDaemon/MediaKeys")
except:
# Try older name.
gsd_object = self._bus.get_object("org.gnome.SettingsDaemon",
"/org/gnome/SettingsDaemon/MediaKeys")
gsd_object.GrabMediaPlayerKeys("mpDris2", 0,
dbus_interface="org.gnome.SettingsDaemon.MediaKeys")
except:
logger.warning(
"Failed to connect to GNOME Settings Daemon. Media keys won't work.")
else:
self._bus.remove_signal_receiver(self.mediakey_callback)
gsd_object.connect_to_signal(
"MediaPlayerKeyPressed", self.mediakey_callback)
def gsd_name_owner_changed_callback(self, bus_name, old_owner, new_owner):
if bus_name == "org.gnome.SettingsDaemon" and new_owner != "":
def reregister():
logger.debug(
"Re-registering with GNOME Settings Daemon (owner %s)" % new_owner)
self.register_mediakeys()
return False
# Timeout is necessary since g-s-d takes some time to load all plugins.
GLib.timeout_add(600, reregister)
# Compatibility functions # Compatibility functions
@ -999,355 +644,6 @@ class MPDWrapper(object):
return False return False
class NotifyWrapper(object):
def __init__(self, params):
self._notification = None
self._enabled = True
if params["notify"]:
self._notification = self._bootstrap_notifications()
if not self._notification:
logger.error(
"No notification service provider could be found; disabling notifications")
else:
self._enabled = False
def _bootstrap_notifications(self):
# Check if someone is providing the notification service
bus = dbus.SessionBus()
try:
bus.get_name_owner("org.freedesktop.Notifications")
except dbus.exceptions.DBusException:
return None
notif = None
# Bootstrap whatever notifications system we are using
if using_gi_notify:
logger.debug("Initializing GObject.Notify")
if Notify.init(identity):
notif = Notify.Notification()
notif.set_hint("desktop-entry", GLib.Variant("s", "mpdris2"))
notif.set_hint("transient", GLib.Variant("b", True))
else:
logger.error(
"Failed to init libnotify; disabling notifications")
elif using_old_notify:
logger.debug("Initializing old pynotify")
if pynotify.init(identity):
notif = pynotify.Notification("", "", "")
notif.set_hint("desktop-entry", "mpdris2")
notif.set_hint("transient", True)
else:
logger.error(
"Failed to init libnotify; disabling notifications")
return notif
def notify(self, title, body, uri=''):
if not self._enabled:
return
# If we did not yet manage to get a notification service,
# try again
if not self._notification:
logger.info(
'Retrying to acquire a notification service provider...')
self._notification = self._bootstrap_notifications()
if self._notification:
logger.info('Notification service provider acquired!')
if self._notification:
try:
self._notification.set_urgency(params['notify_urgency'])
self._notification.update(title, body, uri)
self._notification.show()
except GLib.GError as err:
logger.error("Failed to show notification: %s" % err)
class MPRISInterface(dbus.service.Object):
''' The base object of an MPRIS player '''
__path = "/org/mpris/MediaPlayer2"
__introspect_interface = "org.freedesktop.DBus.Introspectable"
__prop_interface = dbus.PROPERTIES_IFACE
def __init__(self, params):
dbus.service.Object.__init__(self, dbus.SessionBus(),
MPRISInterface.__path)
self._params = params or {}
self._name = self._params["bus_name"] or "org.mpris.MediaPlayer2.mpd"
if not self._name.startswith("org.mpris.MediaPlayer2."):
logger.warn(
"Configured bus name %r is outside MPRIS2 namespace" % self._name)
self._bus = dbus.SessionBus()
self._uname = self._bus.get_unique_name()
self._dbus_obj = self._bus.get_object("org.freedesktop.DBus",
"/org/freedesktop/DBus")
self._dbus_obj.connect_to_signal("NameOwnerChanged",
self._name_owner_changed_callback,
arg0=self._name)
self.acquire_name()
def _name_owner_changed_callback(self, name, old_owner, new_owner):
if name == self._name and old_owner == self._uname and new_owner != "":
try:
pid = self._dbus_obj.GetConnectionUnixProcessID(new_owner)
except:
pid = None
logger.info("Replaced by %s (PID %s)" %
(new_owner, pid or "unknown"))
loop.quit()
def acquire_name(self):
self._bus_name = dbus.service.BusName(self._name,
bus=self._bus,
allow_replacement=True,
replace_existing=True)
def release_name(self):
if hasattr(self, "_bus_name"):
del self._bus_name
__root_interface = "org.mpris.MediaPlayer2"
__root_props = {
"CanQuit": (False, None),
"CanRaise": (False, None),
"DesktopEntry": ("mpdris2", None),
"HasTrackList": (False, None),
"Identity": (identity, None),
"SupportedUriSchemes": (dbus.Array(signature="s"), None),
"SupportedMimeTypes": (dbus.Array(signature="s"), None)
}
def __get_playback_status():
status = mpd_wrapper.last_status()
return {'play': 'Playing', 'pause': 'Paused', 'stop': 'Stopped'}[status['state']]
def __set_loop_status(value):
if value == "Playlist":
mpd_wrapper.repeat(1)
if mpd_wrapper._can_single:
mpd_wrapper.single(0)
elif value == "Track":
if mpd_wrapper._can_single:
mpd_wrapper.repeat(1)
mpd_wrapper.single(1)
elif value == "None":
mpd_wrapper.repeat(0)
if mpd_wrapper._can_single:
mpd_wrapper.single(0)
else:
raise dbus.exceptions.DBusException("Loop mode %r not supported" %
value)
return
def __get_loop_status():
status = mpd_wrapper.last_status()
if int(status['repeat']) == 1:
if int(status.get('single', 0)) == 1:
return "Track"
else:
return "Playlist"
else:
return "None"
def __set_shuffle(value):
mpd_wrapper.random(value)
return
def __get_shuffle():
if int(mpd_wrapper.last_status()['random']) == 1:
return True
else:
return False
def __get_metadata():
return dbus.Dictionary(mpd_wrapper.metadata, signature='sv')
def __get_volume():
vol = float(mpd_wrapper.last_status().get('volume', 0))
if vol > 0:
return vol / 100.0
else:
return 0.0
def __set_volume(value):
if value >= 0 and value <= 1:
mpd_wrapper.setvol(int(value * 100))
return
def __get_position():
status = mpd_wrapper.last_status()
if 'time' in status:
current, end = status['time'].split(':')
return dbus.Int64((int(current) * 1000000))
else:
return dbus.Int64(0)
__player_interface = "org.mpris.MediaPlayer2.Player"
__player_props = {
"PlaybackStatus": (__get_playback_status, None),
"LoopStatus": (__get_loop_status, __set_loop_status),
"Rate": (1.0, None),
"Shuffle": (__get_shuffle, __set_shuffle),
"Metadata": (__get_metadata, None),
"Volume": (__get_volume, __set_volume),
"Position": (__get_position, None),
"MinimumRate": (1.0, None),
"MaximumRate": (1.0, None),
"CanGoNext": (True, None),
"CanGoPrevious": (True, None),
"CanPlay": (True, None),
"CanPause": (True, None),
"CanSeek": (True, None),
"CanControl": (True, None),
}
__tracklist_interface = "org.mpris.MediaPlayer2.TrackList"
__prop_mapping = {
__player_interface: __player_props,
__root_interface: __root_props,
}
@dbus.service.method(__introspect_interface)
def Introspect(self):
return MPRIS2_INTROSPECTION
@dbus.service.signal(__prop_interface, signature="sa{sv}as")
def PropertiesChanged(self, interface, changed_properties,
invalidated_properties):
pass
@dbus.service.method(__prop_interface,
in_signature="ss", out_signature="v")
def Get(self, interface, prop):
getter, setter = self.__prop_mapping[interface][prop]
if callable(getter):
return getter()
return getter
@dbus.service.method(__prop_interface,
in_signature="ssv", out_signature="")
def Set(self, interface, prop, value):
getter, setter = self.__prop_mapping[interface][prop]
if setter is not None:
setter(value)
@dbus.service.method(__prop_interface,
in_signature="s", out_signature="a{sv}")
def GetAll(self, interface):
read_props = {}
props = self.__prop_mapping[interface]
for key, (getter, setter) in props.items():
if callable(getter):
getter = getter()
read_props[key] = getter
return read_props
def update_property(self, interface, prop):
getter, setter = self.__prop_mapping[interface][prop]
if callable(getter):
value = getter()
else:
value = getter
logger.debug('Updated property: %s = %s' % (prop, value))
self.PropertiesChanged(interface, {prop: value}, [])
return value
# Root methods
@dbus.service.method(__root_interface, in_signature='', out_signature='')
def Raise(self):
return
@dbus.service.method(__root_interface, in_signature='', out_signature='')
def Quit(self):
return
# Player methods
@dbus.service.method(__player_interface, in_signature='', out_signature='')
def Next(self):
mpd_wrapper.next()
return
@dbus.service.method(__player_interface, in_signature='', out_signature='')
def Previous(self):
mpd_wrapper.previous()
return
@dbus.service.method(__player_interface, in_signature='', out_signature='')
def Pause(self):
mpd_wrapper.pause(1)
mpd_wrapper.notify_about_state('pause')
return
@dbus.service.method(__player_interface, in_signature='', out_signature='')
def PlayPause(self):
status = mpd_wrapper.status()
if status['state'] == 'play':
mpd_wrapper.pause(1)
mpd_wrapper.notify_about_state('pause')
else:
mpd_wrapper.play()
mpd_wrapper.notify_about_state('play')
return
@dbus.service.method(__player_interface, in_signature='', out_signature='')
def Stop(self):
mpd_wrapper.stop()
mpd_wrapper.notify_about_state('stop')
return
@dbus.service.method(__player_interface, in_signature='', out_signature='')
def Play(self):
mpd_wrapper.play()
mpd_wrapper.notify_about_state('play')
return
@dbus.service.method(__player_interface, in_signature='x', out_signature='')
def Seek(self, offset):
status = mpd_wrapper.status()
current, end = status['time'].split(':')
current = int(current)
end = int(end)
offset = int(offset) / 1000000
if current + offset <= end:
position = current + offset
if position < 0:
position = 0
mpd_wrapper.seekid(int(status['songid']), position)
self.Seeked(position * 1000000)
return
@dbus.service.method(__player_interface, in_signature='ox', out_signature='')
def SetPosition(self, trackid, position):
song = mpd_wrapper.last_currentsong()
# FIXME: use real dbus objects
if str(trackid) != '/org/mpris/MediaPlayer2/Track/%s' % song['id']:
return
# Convert position to seconds
position = int(position) / 1000000
if position <= int(song['time']):
mpd_wrapper.seekid(int(song['id']), position)
self.Seeked(position * 1000000)
return
@dbus.service.signal(__player_interface, signature='x')
def Seeked(self, position):
logger.debug("Seeked to %i" % position)
return float(position)
@dbus.service.method(__player_interface, in_signature='', out_signature='')
def OpenUri(self):
# TODO
return
def each_xdg_config(suffix): def each_xdg_config(suffix):
""" """
Return each location matching XDG_CONFIG_DIRS/suffix in descending Return each location matching XDG_CONFIG_DIRS/suffix in descending
@ -1438,7 +734,7 @@ if __name__ == '__main__':
# Parse command line # Parse command line
try: try:
(opts, args) = getopt.getopt(sys.argv[1:], 'c:dh:jp:v', (opts, args) = getopt.getopt(sys.argv[1:], 'c:dh:jp:v',
['help', 'bus-name=', 'config=', ['help', 'config=',
'debug', 'host=', 'music-dir=', 'debug', 'host=', 'music-dir=',
'use-journal', 'path=', 'port=', 'use-journal', 'path=', 'port=',
'version']) 'version'])
@ -1453,8 +749,6 @@ if __name__ == '__main__':
if opt in ['--help']: if opt in ['--help']:
usage(params) usage(params)
sys.exit() sys.exit()
elif opt in ['--bus-name']:
params['bus_name'] = arg
elif opt in ['-c', '--config']: elif opt in ['-c', '--config']:
config_file = arg config_file = arg
elif opt in ['-d', '--debug']: elif opt in ['-d', '--debug']:
@ -1520,7 +814,7 @@ if __name__ == '__main__':
config.read(['/etc/mpDris2.conf'] + config.read(['/etc/mpDris2.conf'] +
list(reversed(each_xdg_config('mpDris2/mpDris2.conf')))) list(reversed(each_xdg_config('mpDris2/mpDris2.conf'))))
for p in ['host', 'port', 'password', 'bus_name']: for p in ['host', 'port', 'password']:
if not params[p]: if not params[p]:
# TODO: switch to get(fallback=…) when possible # TODO: switch to get(fallback=…) when possible
if config.has_option('Connection', p): if config.has_option('Connection', p):
@ -1533,13 +827,6 @@ if __name__ == '__main__':
params['host'] = os.path.expanduser(params['host']) params['host'] = os.path.expanduser(params['host'])
for p in ['mmkeys', 'notify']:
if config.has_option('Bling', p):
params[p] = config.getboolean('Bling', p)
if config.has_option('Bling', 'notify_urgency'):
params['notify_urgency'] = int(config.get('Bling', 'notify_urgency'))
if not music_dir: if not music_dir:
if config.has_option('Library', 'music_dir'): if config.has_option('Library', 'music_dir'):
music_dir = config.get('Library', 'music_dir') music_dir = config.get('Library', 'music_dir')
@ -1585,9 +872,6 @@ if __name__ == '__main__':
logger.debug('Using legacy pygobject2 main loop.') logger.debug('Using legacy pygobject2 main loop.')
loop = GLib.MainLoop() loop = GLib.MainLoop()
# Wrapper to send notifications
notification = NotifyWrapper(params)
# Create wrapper to handle connection failures with MPD more gracefully # Create wrapper to handle connection failures with MPD more gracefully
mpd_wrapper = MPDWrapper(params) mpd_wrapper = MPDWrapper(params)
mpd_wrapper.run() mpd_wrapper.run()