|
1
|
1
|
# -*- coding: utf-8 -*-
|
|
2
|
|
-## src/music_track_listener.py
|
|
|
2
|
+## gajim/music_track_listener.py
|
|
3
|
3
|
##
|
|
4
|
4
|
## Copyright (C) 2006 Gustavo Carneiro <gjcarneiro AT gmail.com>
|
|
5
|
5
|
## Nikos Kouremenos <kourem AT gmail.com>
|
| ... |
... |
@@ -23,25 +23,40 @@ |
|
23
|
23
|
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
24
|
24
|
##
|
|
25
|
25
|
|
|
|
26
|
+import logging
|
|
|
27
|
+
|
|
26
|
28
|
from gi.repository import GObject
|
|
27
|
|
-if __name__ == '__main__':
|
|
28
|
|
- # install _() func before importing dbus_support
|
|
29
|
|
- from gajim.common import i18n
|
|
|
29
|
+from gi.repository import Gio, GLib
|
|
|
30
|
+
|
|
|
31
|
+log = logging.getLogger('gajim.music_track_listener')
|
|
|
32
|
+
|
|
|
33
|
+
|
|
|
34
|
+def _get_music_players():
|
|
|
35
|
+ players = [
|
|
|
36
|
+ 'org.mpris.MediaPlayer2.audacious',
|
|
|
37
|
+ 'org.mpris.MediaPlayer2.bmp',
|
|
|
38
|
+ 'org.mpris.MediaPlayer2.GnomeMusic',
|
|
|
39
|
+ 'org.mpris.MediaPlayer2.quodlibet',
|
|
|
40
|
+ 'org.mpris.MediaPlayer2.rhythmbox',
|
|
|
41
|
+ 'org.mpris.MediaPlayer2.vlc',
|
|
|
42
|
+ 'org.mpris.MediaPlayer2.xmms2'
|
|
|
43
|
+ ]
|
|
|
44
|
+
|
|
|
45
|
+ return players
|
|
30
|
46
|
|
|
31
|
|
-from gajim.common import dbus_support
|
|
32
|
|
-if dbus_support.supported:
|
|
33
|
|
- import dbus
|
|
34
|
47
|
|
|
35
|
48
|
class MusicTrackInfo(object):
|
|
36
|
49
|
__slots__ = ['title', 'album', 'artist', 'duration', 'track_number',
|
|
37
|
|
- 'paused']
|
|
|
50
|
+ 'paused']
|
|
|
51
|
+
|
|
38
|
52
|
|
|
39
|
53
|
class MusicTrackListener(GObject.GObject):
|
|
40
|
54
|
__gsignals__ = {
|
|
41
|
|
- 'music-track-changed': (GObject.SignalFlags.RUN_LAST, None, (object,)),
|
|
|
55
|
+ 'music-track-changed': (GObject.SignalFlags.RUN_LAST, None, (object,)),
|
|
42
|
56
|
}
|
|
43
|
57
|
|
|
44
|
58
|
_instance = None
|
|
|
59
|
+
|
|
45
|
60
|
@classmethod
|
|
46
|
61
|
def get(cls):
|
|
47
|
62
|
if cls._instance is None:
|
| ... |
... |
@@ -51,253 +66,117 @@ class MusicTrackListener(GObject.GObject): |
|
51
|
66
|
def __init__(self):
|
|
52
|
67
|
super(MusicTrackListener, self).__init__()
|
|
53
|
68
|
self._last_playing_music = None
|
|
|
69
|
+ self.con = {}
|
|
|
70
|
+
|
|
|
71
|
+ players = _get_music_players()
|
|
|
72
|
+ for name in players:
|
|
|
73
|
+ Gio.bus_watch_name(
|
|
|
74
|
+ Gio.BusType.SESSION,
|
|
|
75
|
+ name,
|
|
|
76
|
+ Gio.BusNameWatcherFlags.NONE,
|
|
|
77
|
+ self._appeared,
|
|
|
78
|
+ self._vanished)
|
|
|
79
|
+
|
|
|
80
|
+ def _appeared(self, connection, name, name_owner, *user_data):
|
|
|
81
|
+ '''Set up a listener for music player signals'''
|
|
|
82
|
+ log.info('%s appeared', name)
|
|
|
83
|
+ self.con[name] = connection.signal_subscribe(
|
|
|
84
|
+ name,
|
|
|
85
|
+ 'org.freedesktop.DBus.Properties',
|
|
|
86
|
+ 'PropertiesChanged',
|
|
|
87
|
+ '/org/mpris/MediaPlayer2',
|
|
|
88
|
+ None,
|
|
|
89
|
+ Gio.DBusSignalFlags.NONE,
|
|
|
90
|
+ self._signal_received,
|
|
|
91
|
+ name)
|
|
|
92
|
+
|
|
|
93
|
+ info = self.get_playing_track(name)
|
|
|
94
|
+ if info is not None:
|
|
|
95
|
+ self._last_playing_music = info
|
|
|
96
|
+ self.emit('music-track-changed', info)
|
|
54
|
97
|
|
|
55
|
|
- bus = dbus.SessionBus()
|
|
56
|
|
-
|
|
57
|
|
- ## MPRIS
|
|
58
|
|
- bus.add_signal_receiver(self._mpris_music_track_change_cb, 'TrackChange',
|
|
59
|
|
- 'org.freedesktop.MediaPlayer')
|
|
60
|
|
- bus.add_signal_receiver(self._mpris_playing_changed_cb, 'StatusChange',
|
|
61
|
|
- 'org.freedesktop.MediaPlayer')
|
|
62
|
|
- bus.add_signal_receiver(self._player_name_owner_changed,
|
|
63
|
|
- 'NameOwnerChanged', 'org.freedesktop.DBus',
|
|
64
|
|
- arg0='org.freedesktop.MediaPlayer')
|
|
65
|
|
-
|
|
66
|
|
- ## Muine
|
|
67
|
|
- bus.add_signal_receiver(self._muine_music_track_change_cb, 'SongChanged',
|
|
68
|
|
- 'org.gnome.Muine.Player')
|
|
69
|
|
- bus.add_signal_receiver(self._player_name_owner_changed,
|
|
70
|
|
- 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Muine')
|
|
71
|
|
- bus.add_signal_receiver(self._player_playing_changed_cb, 'StateChanged',
|
|
72
|
|
- 'org.gnome.Muine.Player')
|
|
73
|
|
-
|
|
74
|
|
- ## Rhythmbox
|
|
75
|
|
- bus.add_signal_receiver(self._player_name_owner_changed,
|
|
76
|
|
- 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Rhythmbox')
|
|
77
|
|
- bus.add_signal_receiver(self._rhythmbox_playing_changed_cb,
|
|
78
|
|
- 'playingChanged', 'org.gnome.Rhythmbox.Player')
|
|
79
|
|
- bus.add_signal_receiver(self._player_playing_song_property_changed_cb,
|
|
80
|
|
- 'playingSongPropertyChanged', 'org.gnome.Rhythmbox.Player')
|
|
81
|
|
-
|
|
82
|
|
- ## Banshee
|
|
83
|
|
- bus.add_signal_receiver(self._banshee_state_changed_cb,
|
|
84
|
|
- 'StateChanged', 'org.bansheeproject.Banshee.PlayerEngine')
|
|
85
|
|
- bus.add_signal_receiver(self._player_name_owner_changed,
|
|
86
|
|
- 'NameOwnerChanged', 'org.freedesktop.DBus',
|
|
87
|
|
- arg0='org.bansheeproject.Banshee')
|
|
88
|
|
-
|
|
89
|
|
- ## Quod Libet
|
|
90
|
|
- bus.add_signal_receiver(self._quodlibet_state_change_cb,
|
|
91
|
|
- 'SongStarted', 'net.sacredchao.QuodLibet')
|
|
92
|
|
- bus.add_signal_receiver(self._quodlibet_state_change_cb,
|
|
93
|
|
- 'Paused', 'net.sacredchao.QuodLibet')
|
|
94
|
|
- bus.add_signal_receiver(self._quodlibet_state_change_cb,
|
|
95
|
|
- 'Unpaused', 'net.sacredchao.QuodLibet')
|
|
96
|
|
- bus.add_signal_receiver(self._player_name_owner_changed,
|
|
97
|
|
- 'NameOwnerChanged', 'org.freedesktop.DBus',
|
|
98
|
|
- arg0='net.sacredchao.QuodLibet')
|
|
99
|
|
-
|
|
100
|
|
- def _player_name_owner_changed(self, name, old, new):
|
|
101
|
|
- if not new:
|
|
102
|
|
- self.emit('music-track-changed', None)
|
|
|
98
|
+ def _vanished(self, connection, name, *user_data):
|
|
|
99
|
+ log.info('%s vanished', name)
|
|
|
100
|
+ if name in self.con:
|
|
|
101
|
+ connection.signal_unsubscribe(
|
|
|
102
|
+ self.con[name])
|
|
|
103
|
+ self.con.pop(name)
|
|
103
|
104
|
|
|
104
|
|
- def _player_playing_changed_cb(self, playing):
|
|
105
|
|
- if playing:
|
|
106
|
|
- self.emit('music-track-changed', self._last_playing_music)
|
|
107
|
|
- else:
|
|
108
|
105
|
self.emit('music-track-changed', None)
|
|
109
|
106
|
|
|
110
|
|
- def _player_playing_song_property_changed_cb(self, a, b, c, d):
|
|
111
|
|
- if b == 'rb:stream-song-title':
|
|
112
|
|
- self.emit('music-track-changed', self._last_playing_music)
|
|
|
107
|
+ def _signal_received(self, connection, sender_name, object_path,
|
|
|
108
|
+ interface_name, signal_name, parameters, *user_data):
|
|
|
109
|
+ '''Signal handler for PropertiesChanged event'''
|
|
113
|
110
|
|
|
114
|
|
- def _mpris_properties_extract(self, song):
|
|
115
|
|
- info = MusicTrackInfo()
|
|
116
|
|
- info.title = song.get('title', '')
|
|
117
|
|
- info.album = song.get('album', '')
|
|
118
|
|
- info.artist = song.get('artist', '')
|
|
119
|
|
- info.duration = int(song.get('length', 0))
|
|
120
|
|
- return info
|
|
121
|
|
-
|
|
122
|
|
- def _mpris_playing_changed_cb(self, playing):
|
|
123
|
|
- if type(playing) is dbus.Struct:
|
|
124
|
|
- if playing[0]:
|
|
125
|
|
- self.emit('music-track-changed', None)
|
|
126
|
|
- else:
|
|
127
|
|
- self.emit('music-track-changed', self._last_playing_music)
|
|
128
|
|
- else: # Workaround for e.g. Audacious
|
|
129
|
|
- if playing:
|
|
130
|
|
- self.emit('music-track-changed', None)
|
|
131
|
|
- else:
|
|
132
|
|
- self.emit('music-track-changed', self._last_playing_music)
|
|
|
111
|
+ if 'PlaybackStatus' not in parameters[1]:
|
|
|
112
|
+ return
|
|
133
|
113
|
|
|
134
|
|
- def _mpris_music_track_change_cb(self, arg):
|
|
135
|
|
- self._last_playing_music = self._mpris_properties_extract(arg)
|
|
136
|
|
- self.emit('music-track-changed', self._last_playing_music)
|
|
|
114
|
+ log.info('Signal received: %s - %s', interface_name, parameters)
|
|
137
|
115
|
|
|
138
|
|
- def _muine_properties_extract(self, song_string):
|
|
139
|
|
- d = dict((x.strip() for x in s1.split(':', 1)) for s1 in \
|
|
140
|
|
- song_string.split('\n'))
|
|
141
|
|
- info = MusicTrackInfo()
|
|
142
|
|
- info.title = d['title']
|
|
143
|
|
- info.album = d['album']
|
|
144
|
|
- info.artist = d['artist']
|
|
145
|
|
- info.duration = int(d['duration'])
|
|
146
|
|
- info.track_number = int(d['track_number'])
|
|
147
|
|
- return info
|
|
|
116
|
+ info = self.get_playing_track(user_data[0])
|
|
|
117
|
+ self._last_playing_music = info
|
|
148
|
118
|
|
|
149
|
|
- def _muine_music_track_change_cb(self, arg):
|
|
150
|
|
- info = self._muine_properties_extract(arg)
|
|
151
|
119
|
self.emit('music-track-changed', info)
|
|
152
|
120
|
|
|
153
|
|
- def _rhythmbox_playing_changed_cb(self, playing):
|
|
154
|
|
- if playing:
|
|
155
|
|
- info = self.get_playing_track()
|
|
156
|
|
- self.emit('music-track-changed', info)
|
|
157
|
|
- else:
|
|
158
|
|
- self.emit('music-track-changed', None)
|
|
159
|
|
-
|
|
160
|
|
- def _rhythmbox_properties_extract(self, props):
|
|
161
|
|
- info = MusicTrackInfo()
|
|
162
|
|
- info.title = props.get('title', None)
|
|
163
|
|
- info.album = props.get('album', None)
|
|
164
|
|
- info.artist = props.get('artist', None)
|
|
165
|
|
- info.duration = int(props.get('duration', 0))
|
|
166
|
|
- info.track_number = int(props.get('track-number', 0))
|
|
167
|
|
- return info
|
|
168
|
|
-
|
|
169
|
|
- def _banshee_state_changed_cb(self, state):
|
|
170
|
|
- if state == 'playing':
|
|
171
|
|
- bus = dbus.SessionBus()
|
|
172
|
|
- banshee = bus.get_object('org.bansheeproject.Banshee',
|
|
173
|
|
- '/org/bansheeproject/Banshee/PlayerEngine')
|
|
174
|
|
- currentTrack = banshee.GetCurrentTrack()
|
|
175
|
|
- self._last_playing_music = self._banshee_properties_extract(
|
|
176
|
|
- currentTrack)
|
|
177
|
|
- self.emit('music-track-changed', self._last_playing_music)
|
|
178
|
|
- elif state == 'paused':
|
|
179
|
|
- self.emit('music-track-changed', None)
|
|
|
121
|
+ def _properties_extract(self, properties):
|
|
|
122
|
+ meta = properties.get('Metadata')
|
|
|
123
|
+ if meta is None or not meta:
|
|
|
124
|
+ return None
|
|
180
|
125
|
|
|
181
|
|
- def _banshee_properties_extract(self, props):
|
|
182
|
126
|
info = MusicTrackInfo()
|
|
183
|
|
- info.title = props.get('name', None)
|
|
184
|
|
- info.album = props.get('album', None)
|
|
185
|
|
- info.artist = props.get('artist', None)
|
|
186
|
|
- info.duration = int(props.get('length', 0))
|
|
187
|
|
- return info
|
|
188
|
|
-
|
|
189
|
|
- def _quodlibet_state_change_cb(self, state=None):
|
|
190
|
|
- info = self.get_playing_track()
|
|
191
|
|
- if info:
|
|
192
|
|
- self.emit('music-track-changed', info)
|
|
|
127
|
+ info.title = meta.get('xesam:title')
|
|
|
128
|
+ info.album = meta.get('xesam:album')
|
|
|
129
|
+ artist = meta.get('xesam:artist')
|
|
|
130
|
+ if artist is not None and len(artist):
|
|
|
131
|
+ info.artist = artist[0]
|
|
193
|
132
|
else:
|
|
194
|
|
- self.emit('music-track-changed', None)
|
|
|
133
|
+ info.artist = None
|
|
|
134
|
+ info.duration = float(meta.get('mpris:length', 0))
|
|
|
135
|
+ info.track_number = meta.get('xesam:trackNumber', 0)
|
|
|
136
|
+
|
|
|
137
|
+ status = properties.get('PlaybackStatus')
|
|
|
138
|
+ info.paused = status is not None and status == 'Paused'
|
|
195
|
139
|
|
|
196
|
|
- def _quodlibet_properties_extract(self, props):
|
|
197
|
|
- info = MusicTrackInfo()
|
|
198
|
|
- info.title = props.get('title', None)
|
|
199
|
|
- info.album = props.get('album', None)
|
|
200
|
|
- info.artist = props.get('artist', None)
|
|
201
|
|
- info.duration = float(props.get('~#length', 0))
|
|
202
|
140
|
return info
|
|
203
|
141
|
|
|
204
|
|
- def get_playing_track(self):
|
|
|
142
|
+ def get_playing_track(self, name):
|
|
205
|
143
|
'''Return a MusicTrackInfo for the currently playing
|
|
206
|
144
|
song, or None if no song is playing'''
|
|
207
|
|
-
|
|
208
|
|
- bus = dbus.SessionBus()
|
|
209
|
|
-
|
|
210
|
|
- ## Check Muine playing track
|
|
211
|
|
- test = False
|
|
212
|
|
- if hasattr(bus, 'name_has_owner'):
|
|
213
|
|
- if bus.name_has_owner('org.gnome.Muine'):
|
|
214
|
|
- test = True
|
|
215
|
|
- elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
|
|
216
|
|
- 'org.gnome.Muine'):
|
|
217
|
|
- test = True
|
|
218
|
|
- if test:
|
|
219
|
|
- obj = bus.get_object('org.gnome.Muine', '/org/gnome/Muine/Player')
|
|
220
|
|
- player = dbus.Interface(obj, 'org.gnome.Muine.Player')
|
|
221
|
|
- if player.GetPlaying():
|
|
222
|
|
- song_string = player.GetCurrentSong()
|
|
223
|
|
- song = self._muine_properties_extract(song_string)
|
|
224
|
|
- self._last_playing_music = song
|
|
225
|
|
- return song
|
|
226
|
|
-
|
|
227
|
|
- ## Check Rhythmbox playing song
|
|
228
|
|
- test = False
|
|
229
|
|
- if hasattr(bus, 'name_has_owner'):
|
|
230
|
|
- if bus.name_has_owner('org.gnome.Rhythmbox'):
|
|
231
|
|
- test = True
|
|
232
|
|
- elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
|
|
233
|
|
- 'org.gnome.Rhythmbox'):
|
|
234
|
|
- test = True
|
|
235
|
|
- if test:
|
|
236
|
|
- rbshellobj = bus.get_object('org.gnome.Rhythmbox',
|
|
237
|
|
- '/org/gnome/Rhythmbox/Shell')
|
|
238
|
|
- player = dbus.Interface(
|
|
239
|
|
- bus.get_object('org.gnome.Rhythmbox',
|
|
240
|
|
- '/org/gnome/Rhythmbox/Player'), 'org.gnome.Rhythmbox.Player')
|
|
241
|
|
- rbshell = dbus.Interface(rbshellobj, 'org.gnome.Rhythmbox.Shell')
|
|
242
|
|
- try:
|
|
243
|
|
- uri = player.getPlayingUri()
|
|
244
|
|
- except dbus.DBusException:
|
|
245
|
|
- uri = None
|
|
246
|
|
- if not uri:
|
|
|
145
|
+ proxy = Gio.DBusProxy.new_for_bus_sync(
|
|
|
146
|
+ Gio.BusType.SESSION,
|
|
|
147
|
+ Gio.DBusProxyFlags.NONE,
|
|
|
148
|
+ None,
|
|
|
149
|
+ name,
|
|
|
150
|
+ '/org/mpris/MediaPlayer2',
|
|
|
151
|
+ 'org.freedesktop.DBus.Properties',
|
|
|
152
|
+ None)
|
|
|
153
|
+
|
|
|
154
|
+ try:
|
|
|
155
|
+ result = proxy.call_sync(
|
|
|
156
|
+ "GetAll",
|
|
|
157
|
+ GLib.Variant('(s)', ('org.mpris.MediaPlayer2.Player',)),
|
|
|
158
|
+ Gio.DBusCallFlags.NONE,
|
|
|
159
|
+ -1,
|
|
|
160
|
+ None)
|
|
|
161
|
+ except GLib.Error as e:
|
|
|
162
|
+ if e.domain == 'g-dbus-error-quark':
|
|
|
163
|
+ log.debug("Could not enable music listener: %s", e.message)
|
|
247
|
164
|
return None
|
|
248
|
|
- props = rbshell.getSongProperties(uri)
|
|
249
|
|
- info = self._rhythmbox_properties_extract(props)
|
|
|
165
|
+ else:
|
|
|
166
|
+ raise
|
|
|
167
|
+ else:
|
|
|
168
|
+ info = self._properties_extract(result[0])
|
|
250
|
169
|
self._last_playing_music = info
|
|
251
|
170
|
return info
|
|
252
|
171
|
|
|
253
|
|
- ## Check Banshee playing track
|
|
254
|
|
- test = False
|
|
255
|
|
- if hasattr(bus, 'name_has_owner'):
|
|
256
|
|
- if bus.name_has_owner('org.bansheeproject.Banshee'):
|
|
257
|
|
- test = True
|
|
258
|
|
- elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
|
|
259
|
|
- 'org.bansheeproject.Banshee'):
|
|
260
|
|
- test = True
|
|
261
|
|
- if test:
|
|
262
|
|
- banshee = bus.get_object('org.bansheeproject.Banshee',
|
|
263
|
|
- '/org/bansheeproject/Banshee/PlayerEngine')
|
|
264
|
|
- currentTrack = banshee.GetCurrentTrack()
|
|
265
|
|
- if currentTrack:
|
|
266
|
|
- song = self._banshee_properties_extract(currentTrack)
|
|
267
|
|
- self._last_playing_music = song
|
|
268
|
|
- return song
|
|
269
|
|
-
|
|
270
|
|
- ## Check Quod Libet playing track
|
|
271
|
|
- test = False
|
|
272
|
|
- if hasattr(bus, 'name_has_owner'):
|
|
273
|
|
- if bus.name_has_owner('net.sacredchao.QuodLibet'):
|
|
274
|
|
- test = True
|
|
275
|
|
- elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(),
|
|
276
|
|
- 'net.sacredchao.QuodLibet'):
|
|
277
|
|
- test = True
|
|
278
|
|
- if test:
|
|
279
|
|
- quodlibet = bus.get_object('net.sacredchao.QuodLibet',
|
|
280
|
|
- '/net/sacredchao/QuodLibet')
|
|
281
|
|
- if quodlibet.IsPlaying():
|
|
282
|
|
- currentTrack = quodlibet.CurrentSong()
|
|
283
|
|
- song = self._quodlibet_properties_extract(currentTrack)
|
|
284
|
|
- self._last_playing_music = song
|
|
285
|
|
- return song
|
|
286
|
|
-
|
|
287
|
|
- return None
|
|
288
|
172
|
|
|
289
|
173
|
# here we test :)
|
|
290
|
174
|
if __name__ == '__main__':
|
|
291
|
175
|
def music_track_change_cb(listener, music_track_info):
|
|
292
|
|
- if music_track_info is None:
|
|
|
176
|
+ if music_track_info is None or music_track_info.paused:
|
|
293
|
177
|
print('Stop!')
|
|
294
|
178
|
else:
|
|
295
|
179
|
print(music_track_info.title)
|
|
296
|
180
|
listener = MusicTrackListener.get()
|
|
297
|
181
|
listener.connect('music-track-changed', music_track_change_cb)
|
|
298
|
|
- track = listener.get_playing_track()
|
|
299
|
|
- if track is None:
|
|
300
|
|
- print('Now not playing anything')
|
|
301
|
|
- else:
|
|
302
|
|
- print('Now playing: "%s" by %s' % (track.title, track.artist))
|
|
303
|
182
|
GObject.MainLoop().run()
|