| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | ## src/music_track_listener.py |
|---|
| 3 | ## |
|---|
| 4 | ## Copyright (C) 2006 Gustavo Carneiro <gjcarneiro AT gmail.com> |
|---|
| 5 | ## Nikos Kouremenos <kourem AT gmail.com> |
|---|
| 6 | ## Copyright (C) 2006-2008 Yann Leboulanger <asterix AT lagaule.org> |
|---|
| 7 | ## Copyright (C) 2008 Jean-Marie Traissard <jim AT lapin.org> |
|---|
| 8 | ## Jonathan Schleifer <js-gajim AT webkeks.org> |
|---|
| 9 | ## Stephan Erb <steve-e AT h3c.de> |
|---|
| 10 | ## |
|---|
| 11 | ## This file is part of Gajim. |
|---|
| 12 | ## |
|---|
| 13 | ## Gajim is free software; you can redistribute it and/or modify |
|---|
| 14 | ## it under the terms of the GNU General Public License as published |
|---|
| 15 | ## by the Free Software Foundation; version 3 only. |
|---|
| 16 | ## |
|---|
| 17 | ## Gajim is distributed in the hope that it will be useful, |
|---|
| 18 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 19 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 20 | ## GNU General Public License for more details. |
|---|
| 21 | ## |
|---|
| 22 | ## You should have received a copy of the GNU General Public License |
|---|
| 23 | ## along with Gajim. If not, see <http://www.gnu.org/licenses/>. |
|---|
| 24 | ## |
|---|
| 25 | |
|---|
| 26 | import gobject |
|---|
| 27 | if __name__ == '__main__': |
|---|
| 28 | # install _() func before importing dbus_support |
|---|
| 29 | from common import i18n |
|---|
| 30 | |
|---|
| 31 | from common import dbus_support |
|---|
| 32 | if dbus_support.supported: |
|---|
| 33 | import dbus |
|---|
| 34 | import dbus.glib |
|---|
| 35 | |
|---|
| 36 | class MusicTrackInfo(object): |
|---|
| 37 | __slots__ = ['title', 'album', 'artist', 'duration', 'track_number', |
|---|
| 38 | 'paused'] |
|---|
| 39 | |
|---|
| 40 | class MusicTrackListener(gobject.GObject): |
|---|
| 41 | __gsignals__ = { |
|---|
| 42 | 'music-track-changed': (gobject.SIGNAL_RUN_LAST, None, (object,)), |
|---|
| 43 | } |
|---|
| 44 | |
|---|
| 45 | _instance = None |
|---|
| 46 | @classmethod |
|---|
| 47 | def get(cls): |
|---|
| 48 | if cls._instance is None: |
|---|
| 49 | cls._instance = cls() |
|---|
| 50 | return cls._instance |
|---|
| 51 | |
|---|
| 52 | def __init__(self): |
|---|
| 53 | super(MusicTrackListener, self).__init__() |
|---|
| 54 | self._last_playing_music = None |
|---|
| 55 | |
|---|
| 56 | bus = dbus.SessionBus() |
|---|
| 57 | |
|---|
| 58 | ## MPRIS |
|---|
| 59 | bus.add_signal_receiver(self._mpris_music_track_change_cb, 'TrackChange', |
|---|
| 60 | 'org.freedesktop.MediaPlayer') |
|---|
| 61 | bus.add_signal_receiver(self._mpris_playing_changed_cb, 'StatusChange', |
|---|
| 62 | 'org.freedesktop.MediaPlayer') |
|---|
| 63 | bus.add_signal_receiver(self._player_name_owner_changed, |
|---|
| 64 | 'NameOwnerChanged', 'org.freedesktop.DBus', |
|---|
| 65 | arg0='org.freedesktop.MediaPlayer') |
|---|
| 66 | |
|---|
| 67 | ## Muine |
|---|
| 68 | bus.add_signal_receiver(self._muine_music_track_change_cb, 'SongChanged', |
|---|
| 69 | 'org.gnome.Muine.Player') |
|---|
| 70 | bus.add_signal_receiver(self._player_name_owner_changed, |
|---|
| 71 | 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Muine') |
|---|
| 72 | bus.add_signal_receiver(self._player_playing_changed_cb, 'StateChanged', |
|---|
| 73 | 'org.gnome.Muine.Player') |
|---|
| 74 | |
|---|
| 75 | ## Rhythmbox |
|---|
| 76 | bus.add_signal_receiver(self._player_name_owner_changed, |
|---|
| 77 | 'NameOwnerChanged', 'org.freedesktop.DBus', arg0='org.gnome.Rhythmbox') |
|---|
| 78 | bus.add_signal_receiver(self._rhythmbox_playing_changed_cb, |
|---|
| 79 | 'playingChanged', 'org.gnome.Rhythmbox.Player') |
|---|
| 80 | bus.add_signal_receiver(self._player_playing_song_property_changed_cb, |
|---|
| 81 | 'playingSongPropertyChanged', 'org.gnome.Rhythmbox.Player') |
|---|
| 82 | |
|---|
| 83 | ## Banshee |
|---|
| 84 | bus.add_signal_receiver(self._banshee_state_changed_cb, |
|---|
| 85 | 'StateChanged', 'org.bansheeproject.Banshee.PlayerEngine') |
|---|
| 86 | bus.add_signal_receiver(self._player_name_owner_changed, |
|---|
| 87 | 'NameOwnerChanged', 'org.freedesktop.DBus', |
|---|
| 88 | arg0='org.bansheeproject.Banshee') |
|---|
| 89 | |
|---|
| 90 | ## Quod Libet |
|---|
| 91 | bus.add_signal_receiver(self._quodlibet_state_change_cb, |
|---|
| 92 | 'SongStarted', 'net.sacredchao.QuodLibet') |
|---|
| 93 | bus.add_signal_receiver(self._quodlibet_state_change_cb, |
|---|
| 94 | 'Paused', 'net.sacredchao.QuodLibet') |
|---|
| 95 | bus.add_signal_receiver(self._quodlibet_state_change_cb, |
|---|
| 96 | 'Unpaused', 'net.sacredchao.QuodLibet') |
|---|
| 97 | bus.add_signal_receiver(self._player_name_owner_changed, |
|---|
| 98 | 'NameOwnerChanged', 'org.freedesktop.DBus', |
|---|
| 99 | arg0='net.sacredchao.QuodLibet') |
|---|
| 100 | |
|---|
| 101 | def _player_name_owner_changed(self, name, old, new): |
|---|
| 102 | if not new: |
|---|
| 103 | self.emit('music-track-changed', None) |
|---|
| 104 | |
|---|
| 105 | def _player_playing_changed_cb(self, playing): |
|---|
| 106 | if playing: |
|---|
| 107 | self.emit('music-track-changed', self._last_playing_music) |
|---|
| 108 | else: |
|---|
| 109 | self.emit('music-track-changed', None) |
|---|
| 110 | |
|---|
| 111 | def _player_playing_song_property_changed_cb(self, a, b, c, d): |
|---|
| 112 | if b == 'rb:stream-song-title': |
|---|
| 113 | self.emit('music-track-changed', self._last_playing_music) |
|---|
| 114 | |
|---|
| 115 | def _mpris_properties_extract(self, song): |
|---|
| 116 | info = MusicTrackInfo() |
|---|
| 117 | info.title = song.get('title', '') |
|---|
| 118 | info.album = song.get('album', '') |
|---|
| 119 | info.artist = song.get('artist', '') |
|---|
| 120 | info.duration = int(song.get('length', 0)) |
|---|
| 121 | return info |
|---|
| 122 | |
|---|
| 123 | def _mpris_playing_changed_cb(self, playing): |
|---|
| 124 | if playing == 0: |
|---|
| 125 | self.emit('music-track-changed', self._last_playing_music) |
|---|
| 126 | else: |
|---|
| 127 | self.emit('music-track-changed', None) |
|---|
| 128 | |
|---|
| 129 | def _mpris_music_track_change_cb(self, arg): |
|---|
| 130 | self._last_playing_music = self._mpris_properties_extract(arg) |
|---|
| 131 | self.emit('music-track-changed', self._last_playing_music) |
|---|
| 132 | |
|---|
| 133 | def _muine_properties_extract(self, song_string): |
|---|
| 134 | d = dict((x.strip() for x in s1.split(':', 1)) for s1 in \ |
|---|
| 135 | song_string.split('\n')) |
|---|
| 136 | info = MusicTrackInfo() |
|---|
| 137 | info.title = d['title'] |
|---|
| 138 | info.album = d['album'] |
|---|
| 139 | info.artist = d['artist'] |
|---|
| 140 | info.duration = int(d['duration']) |
|---|
| 141 | info.track_number = int(d['track_number']) |
|---|
| 142 | return info |
|---|
| 143 | |
|---|
| 144 | def _muine_music_track_change_cb(self, arg): |
|---|
| 145 | info = self._muine_properties_extract(arg) |
|---|
| 146 | self.emit('music-track-changed', info) |
|---|
| 147 | |
|---|
| 148 | def _rhythmbox_playing_changed_cb(self, playing): |
|---|
| 149 | if playing: |
|---|
| 150 | info = self.get_playing_track() |
|---|
| 151 | self.emit('music-track-changed', info) |
|---|
| 152 | else: |
|---|
| 153 | self.emit('music-track-changed', None) |
|---|
| 154 | |
|---|
| 155 | def _rhythmbox_properties_extract(self, props): |
|---|
| 156 | info = MusicTrackInfo() |
|---|
| 157 | info.title = props.get('title', None) |
|---|
| 158 | info.album = props.get('album', None) |
|---|
| 159 | info.artist = props.get('artist', None) |
|---|
| 160 | info.duration = int(props.get('duration', 0)) |
|---|
| 161 | info.track_number = int(props.get('track-number', 0)) |
|---|
| 162 | return info |
|---|
| 163 | |
|---|
| 164 | def _banshee_state_changed_cb(self, state): |
|---|
| 165 | if state == 'playing': |
|---|
| 166 | bus = dbus.SessionBus() |
|---|
| 167 | banshee = bus.get_object('org.bansheeproject.Banshee', |
|---|
| 168 | '/org/bansheeproject/Banshee/PlayerEngine') |
|---|
| 169 | currentTrack = banshee.GetCurrentTrack() |
|---|
| 170 | self._last_playing_music = self._banshee_properties_extract( |
|---|
| 171 | currentTrack) |
|---|
| 172 | self.emit('music-track-changed', self._last_playing_music) |
|---|
| 173 | elif state == 'paused': |
|---|
| 174 | self.emit('music-track-changed', None) |
|---|
| 175 | |
|---|
| 176 | def _banshee_properties_extract(self, props): |
|---|
| 177 | info = MusicTrackInfo() |
|---|
| 178 | info.title = props.get('name', None) |
|---|
| 179 | info.album = props.get('album', None) |
|---|
| 180 | info.artist = props.get('artist', None) |
|---|
| 181 | info.duration = int(props.get('length', 0)) |
|---|
| 182 | return info |
|---|
| 183 | |
|---|
| 184 | def _quodlibet_state_change_cb(self, state=None): |
|---|
| 185 | info = self.get_playing_track() |
|---|
| 186 | if info: |
|---|
| 187 | self.emit('music-track-changed', info) |
|---|
| 188 | else: |
|---|
| 189 | self.emit('music-track-changed', None) |
|---|
| 190 | |
|---|
| 191 | def _quodlibet_properties_extract(self, props): |
|---|
| 192 | info = MusicTrackInfo() |
|---|
| 193 | info.title = props.get('title', None) |
|---|
| 194 | info.album = props.get('album', None) |
|---|
| 195 | info.artist = props.get('artist', None) |
|---|
| 196 | info.duration = int(props.get('~#length', 0)) |
|---|
| 197 | return info |
|---|
| 198 | |
|---|
| 199 | def get_playing_track(self): |
|---|
| 200 | '''Return a MusicTrackInfo for the currently playing |
|---|
| 201 | song, or None if no song is playing''' |
|---|
| 202 | |
|---|
| 203 | bus = dbus.SessionBus() |
|---|
| 204 | |
|---|
| 205 | ## Check Muine playing track |
|---|
| 206 | test = False |
|---|
| 207 | if hasattr(bus, 'name_has_owner'): |
|---|
| 208 | if bus.name_has_owner('org.gnome.Muine'): |
|---|
| 209 | test = True |
|---|
| 210 | elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), |
|---|
| 211 | 'org.gnome.Muine'): |
|---|
| 212 | test = True |
|---|
| 213 | if test: |
|---|
| 214 | obj = bus.get_object('org.gnome.Muine', '/org/gnome/Muine/Player') |
|---|
| 215 | player = dbus.Interface(obj, 'org.gnome.Muine.Player') |
|---|
| 216 | if player.GetPlaying(): |
|---|
| 217 | song_string = player.GetCurrentSong() |
|---|
| 218 | song = self._muine_properties_extract(song_string) |
|---|
| 219 | self._last_playing_music = song |
|---|
| 220 | return song |
|---|
| 221 | |
|---|
| 222 | ## Check Rhythmbox playing song |
|---|
| 223 | test = False |
|---|
| 224 | if hasattr(bus, 'name_has_owner'): |
|---|
| 225 | if bus.name_has_owner('org.gnome.Rhythmbox'): |
|---|
| 226 | test = True |
|---|
| 227 | elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), |
|---|
| 228 | 'org.gnome.Rhythmbox'): |
|---|
| 229 | test = True |
|---|
| 230 | if test: |
|---|
| 231 | rbshellobj = bus.get_object('org.gnome.Rhythmbox', |
|---|
| 232 | '/org/gnome/Rhythmbox/Shell') |
|---|
| 233 | player = dbus.Interface( |
|---|
| 234 | bus.get_object('org.gnome.Rhythmbox', |
|---|
| 235 | '/org/gnome/Rhythmbox/Player'), 'org.gnome.Rhythmbox.Player') |
|---|
| 236 | rbshell = dbus.Interface(rbshellobj, 'org.gnome.Rhythmbox.Shell') |
|---|
| 237 | try: |
|---|
| 238 | uri = player.getPlayingUri() |
|---|
| 239 | except dbus.DBusException: |
|---|
| 240 | uri = None |
|---|
| 241 | if not uri: |
|---|
| 242 | return None |
|---|
| 243 | props = rbshell.getSongProperties(uri) |
|---|
| 244 | info = self._rhythmbox_properties_extract(props) |
|---|
| 245 | self._last_playing_music = info |
|---|
| 246 | return info |
|---|
| 247 | |
|---|
| 248 | ## Check Banshee playing track |
|---|
| 249 | test = False |
|---|
| 250 | if hasattr(bus, 'name_has_owner'): |
|---|
| 251 | if bus.name_has_owner('org.bansheeproject.Banshee'): |
|---|
| 252 | test = True |
|---|
| 253 | elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), |
|---|
| 254 | 'org.bansheeproject.Banshee'): |
|---|
| 255 | test = True |
|---|
| 256 | if test: |
|---|
| 257 | banshee = bus.get_object('org.bansheeproject.Banshee', |
|---|
| 258 | '/org/bansheeproject/Banshee/PlayerEngine') |
|---|
| 259 | currentTrack = banshee.GetCurrentTrack() |
|---|
| 260 | if currentTrack: |
|---|
| 261 | song = self._banshee_properties_extract(currentTrack) |
|---|
| 262 | self._last_playing_music = song |
|---|
| 263 | return song |
|---|
| 264 | |
|---|
| 265 | ## Check Quod Libet playing track |
|---|
| 266 | test = False |
|---|
| 267 | if hasattr(bus, 'name_has_owner'): |
|---|
| 268 | if bus.name_has_owner('net.sacredchao.QuodLibet'): |
|---|
| 269 | test = True |
|---|
| 270 | elif dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), |
|---|
| 271 | 'net.sacredchao.QuodLibet'): |
|---|
| 272 | test = True |
|---|
| 273 | if test: |
|---|
| 274 | quodlibet = bus.get_object('net.sacredchao.QuodLibet', |
|---|
| 275 | '/net/sacredchao/QuodLibet') |
|---|
| 276 | if quodlibet.IsPlaying(): |
|---|
| 277 | currentTrack = quodlibet.CurrentSong() |
|---|
| 278 | song = self._quodlibet_properties_extract(currentTrack) |
|---|
| 279 | self._last_playing_music = song |
|---|
| 280 | return song |
|---|
| 281 | |
|---|
| 282 | return None |
|---|
| 283 | |
|---|
| 284 | # here we test :) |
|---|
| 285 | if __name__ == '__main__': |
|---|
| 286 | def music_track_change_cb(listener, music_track_info): |
|---|
| 287 | if music_track_info is None: |
|---|
| 288 | print 'Stop!' |
|---|
| 289 | else: |
|---|
| 290 | print music_track_info.title |
|---|
| 291 | listener = MusicTrackListener.get() |
|---|
| 292 | listener.connect('music-track-changed', music_track_change_cb) |
|---|
| 293 | track = listener.get_playing_track() |
|---|
| 294 | if track is None: |
|---|
| 295 | print 'Now not playing anything' |
|---|
| 296 | else: |
|---|
| 297 | print 'Now playing: "%s" by %s' % (track.title, track.artist) |
|---|
| 298 | gobject.MainLoop().run() |
|---|
| 299 | |
|---|
| 300 | # vim: se ts=3: |
|---|