]> git.sesse.net Git - vlc/blob - extras/misc/mpris.py
demux: ts: rewrite/split IOD parsing
[vlc] / extras / misc / mpris.py
1 #!/usr/bin/env python
2 # -*- coding: utf8 -*-
3 #
4 # Copyright © 2006-2011 Rafaël Carré <funman at videolanorg>
5 #
6 #  This program is free software; you can redistribute it and/or modify
7 #  it under the terms of the GNU General Public License as published by
8 #  the Free Software Foundation; either version 2 of the License, or
9 #  (at your option) any later version.
10 #
11 #  This program is distributed in the hope that it will be useful,
12 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #  GNU General Public License for more details.
15 #
16 #  You should have received a copy of the GNU General Public License
17 #  along with this program; if not, write to the Free Software
18 #  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 #
20
21 #
22 # NOTE: This controller is a SAMPLE, and thus doesn't use all the
23 # Media Player Remote Interface Specification (MPRIS for short) capabilities
24 #
25 # MPRIS: http://www.mpris.org/2.1/spec/
26 #
27 # You'll need pygtk >= 2.12
28 #
29 # TODO
30 #   Ability to choose the Media Player if several are connected to the bus
31
32 # core dbus stuff
33 import dbus
34 import dbus.glib
35
36 # core interface stuff
37 import gtk
38
39 # timer
40 from gobject import timeout_add
41
42 # file loading
43 import os
44
45 global win_position # store the window position on the screen
46
47 global playing
48 playing = False
49
50 global shuffle
51
52 global root
53 global player
54 global tracklist
55 global props
56
57 global bus          # Connection to the session bus
58
59 mpris='org.mpris.MediaPlayer2'
60
61 # If a Media Player connects to the bus, we'll use it
62 # Note that we forget the previous Media Player we were connected to
63 def NameOwnerChanged(name, new, old):
64     if old != '' and mpris in name:
65         Connect(name)
66
67 def PropGet(prop):
68     global props
69     return props.Get(mpris + '.Player', prop)
70
71 def PropSet(prop, val):
72     global props
73     props.Set(mpris + '.Player', prop, val)
74
75 # Callback for when 'TrackChange' signal is emitted
76 def TrackChange(Track):
77     try:
78         a = Track['xesam:artist']
79     except:
80         a = ''
81     try:
82         t = Track['xesam:title']
83     except:
84         t = Track['xesam:url']
85     try:
86         length = Track['mpris:length']
87     except:
88         length = 0
89     if length > 0:
90         time_s.set_range(0, length)
91         time_s.set_sensitive(True)
92     else:
93         # disable the position scale if length isn't available
94         time_s.set_sensitive(False)
95     # update the labels
96     l_artist.set_text(a)
97     l_title.set_text(t)
98
99 # Connects to the Media Player we detected
100 def Connect(name):
101     global root, player, tracklist, props
102     global playing, shuffle
103
104     root_o = bus.get_object(name, '/org/mpris/MediaPlayer2')
105     root        = dbus.Interface(root_o, mpris)
106     tracklist   = dbus.Interface(root_o, mpris + '.TrackList')
107     player      = dbus.Interface(root_o, mpris + '.Player')
108     props       = dbus.Interface(root_o, dbus.PROPERTIES_IFACE)
109
110     # FIXME : doesn't exist anymore in mpris 2.1
111     # connect to the TrackChange signal
112     # root_o.connect_to_signal('TrackChange', TrackChange, dbus_interface=mpris)
113
114     # determine if the Media Player is playing something
115     if PropGet('PlaybackStatus') == 'Playing':
116         playing = True
117         TrackChange(PropGet('Metadata'))
118
119     window.set_title(props.Get(mpris, 'Identity'))
120
121 #plays an element
122 def AddTrack(widget):
123     mrl = e_mrl.get_text()
124     if mrl != None and mrl != '':
125         tracklist.AddTrack(mrl, '/', True)
126         e_mrl.set_text('')
127     else:
128         mrl = bt_file.get_filename()
129         if mrl != None and mrl != '':
130             tracklist.AddTrack('directory://' + mrl, '/', True)
131     update(0)
132
133 # basic control
134
135 def Next(widget):
136     player.Next(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
137     update(0)
138
139 def Prev(widget):
140     player.Prev(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
141     update(0)
142
143 def Stop(widget):
144     player.Stop(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
145     update(0)
146
147 def Quit(widget):
148     global props
149     if props.Get(mpris, 'CanQuit'):
150         root.Quit(reply_handler=(lambda *args: None), error_handler=(lambda *args: None))
151         l_title.set_text('')
152         window.set_title('')
153
154 def Pause(widget):
155     player.PlayPause()
156     if PropGet('PlaybackStatus') == 'Playing':
157         icon = gtk.STOCK_MEDIA_PAUSE
158     else:
159         icon = gtk.STOCK_MEDIA_PLAY
160     img_bt_toggle.set_from_stock(icon, gtk.ICON_SIZE_SMALL_TOOLBAR)
161     update(0)
162
163 def Shuffle(widget):
164     global shuffle
165     shuffle = not shuffle
166     PropSet('Shuffle', shuffle)
167
168 # update status display
169 def update(widget):
170     Track = PropGet('Metadata')
171     vol.set_value(PropGet('Volume') * 100.0)
172     TrackChange(Track)
173     GetPlayStatus(0)
174
175 # callback for volume change
176 def volchange(widget):
177     PropSet('Volume', vol.get_value_as_int() / 100.0)
178
179 # callback for position change
180 def timechange(widget, x=None, y=None):
181     player.SetPosition(PropGet('Metadata')['mpris:trackid'],
182                 time_s.get_value(),
183                 reply_handler=(lambda *args: None),
184                 error_handler=(lambda *args: None))
185
186 # refresh position change
187 def timeset():
188     global playing
189     if playing == True:
190         try:
191             time_s.set_value(PropGet('Position'))
192         except:
193             playing = False
194     return True
195
196 # toggle simple/full display
197 def expander(widget):
198     if exp.get_expanded() == False:
199         exp.set_label('Less')
200     else:
201         exp.set_label('More')
202
203 # close event : hide in the systray
204 def delete_event(self, widget):
205     self.hide()
206     return True
207
208 # shouldn't happen
209 def destroy(widget):
210     gtk.main_quit()
211
212 # hide the controller when 'Esc' is pressed
213 def key_release(widget, event):
214     if event.keyval == gtk.keysyms.Escape:
215         global win_position
216         win_position = window.get_position()
217         widget.hide()
218
219 # callback for click on the tray icon
220 def tray_button(widget):
221     global win_position
222     if window.get_property('visible'):
223         # store position
224         win_position = window.get_position()
225         window.hide()
226     else:
227         # restore position
228         window.move(win_position[0], win_position[1])
229         window.show()
230
231 # hack: update position, volume, and metadata
232 def icon_clicked(widget, event):
233     update(0)
234
235 # get playing status, modify the Play/Pause button accordingly
236 def GetPlayStatus(widget):
237     global playing
238     global shuffle
239
240     playing = PropGet('PlaybackStatus') == 'Playing'
241     if playing:
242         img_bt_toggle.set_from_stock('gtk-media-pause', gtk.ICON_SIZE_SMALL_TOOLBAR)
243     else:
244         img_bt_toggle.set_from_stock('gtk-media-play', gtk.ICON_SIZE_SMALL_TOOLBAR)
245     shuffle = PropGet('Shuffle')
246     bt_shuffle.set_active( shuffle )
247
248 # loads UI file from the directory where the script is,
249 # so we can use /path/to/mpris.py to execute it.
250 import sys
251 xml = gtk.Builder()
252 gtk.Builder.add_from_file(xml, os.path.join(os.path.dirname(sys.argv[0]) , 'mpris.xml'))
253
254 # ui setup
255 bt_close    = xml.get_object('close')
256 bt_quit     = xml.get_object('quit')
257 bt_file     = xml.get_object('ChooseFile')
258 bt_next     = xml.get_object('next')
259 bt_prev     = xml.get_object('prev')
260 bt_stop     = xml.get_object('stop')
261 bt_toggle   = xml.get_object('toggle')
262 bt_mrl      = xml.get_object('AddMRL')
263 bt_shuffle  = xml.get_object('shuffle')
264 l_artist    = xml.get_object('l_artist')
265 l_title     = xml.get_object('l_title')
266 e_mrl       = xml.get_object('mrl')
267 window      = xml.get_object('window1')
268 img_bt_toggle=xml.get_object('image6')
269 exp         = xml.get_object('expander2')
270 expvbox     = xml.get_object('expandvbox')
271 audioicon   = xml.get_object('eventicon')
272 vol         = xml.get_object('vol')
273 time_s      = xml.get_object('time_s')
274 time_l      = xml.get_object('time_l')
275
276 # connect to the different callbacks
277
278 window.connect('delete_event',  delete_event)
279 window.connect('destroy',       destroy)
280 window.connect('key_release_event', key_release)
281
282 tray = gtk.status_icon_new_from_icon_name('audio-x-generic')
283 tray.connect('activate', tray_button)
284
285 bt_close.connect('clicked',     destroy)
286 bt_quit.connect('clicked',      Quit)
287 bt_mrl.connect('clicked',       AddTrack)
288 bt_toggle.connect('clicked',    Pause)
289 bt_next.connect('clicked',      Next)
290 bt_prev.connect('clicked',      Prev)
291 bt_stop.connect('clicked',      Stop)
292 bt_shuffle.connect('clicked',   Shuffle)
293 exp.connect('activate',         expander)
294 vol.connect('changed',          volchange)
295 time_s.connect('adjust-bounds', timechange)
296 audioicon.set_events(gtk.gdk.BUTTON_PRESS_MASK) # hack for the bottom right icon
297 audioicon.connect('button_press_event', icon_clicked)
298 time_s.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
299
300 library = '/media/mp3' # editme
301
302 # set the Directory chooser to a default location
303 try:
304     os.chdir(library)
305     bt_file.set_current_folder(library)
306 except:
307     bt_file.set_current_folder(os.path.expanduser('~'))
308
309 # connect to the bus
310 bus = dbus.SessionBus()
311 dbus_names = bus.get_object( 'org.freedesktop.DBus', '/org/freedesktop/DBus' )
312 dbus_names.connect_to_signal('NameOwnerChanged', NameOwnerChanged, dbus_interface='org.freedesktop.DBus') # to detect new Media Players
313
314 dbus_o = bus.get_object('org.freedesktop.DBus', '/')
315 dbus_intf = dbus.Interface(dbus_o, 'org.freedesktop.DBus')
316
317 # connect to the first Media Player found
318 for n in dbus_intf.ListNames():
319     if mpris in n:
320         Connect(n)
321         vol.set_value(PropGet('Volume') * 100.0)
322         update(0)
323         break
324
325 # run a timer to update position
326 timeout_add( 1000, timeset)
327
328 window.set_icon_name('audio-x-generic')
329 window.show()
330
331 window.set_icon(gtk.icon_theme_get_default().load_icon('audio-x-generic',24,0))
332 win_position = window.get_position()
333
334 gtk.main() # execute the main loop