Merge lp:~keirangtp/dockmanager/clementine_helper into lp:dockmanager
- clementine_helper
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 92 | ||||
Proposed branch: | lp:~keirangtp/dockmanager/clementine_helper | ||||
Merge into: | lp:dockmanager | ||||
Diff against target: |
727 lines (+696/-0) 4 files modified
metadata/Makefile.am (+1/-0) metadata/clementine_control.py.info (+6/-0) scripts/Makefile.am (+1/-0) scripts/clementine_control.py (+688/-0) |
||||
To merge this branch: | bzr merge lp:~keirangtp/dockmanager/clementine_helper | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Rico Tzschichholz | Needs Fixing | ||
Review via email: mp+50639@code.launchpad.net |
Commit message
Description of the change
Features:
- cover art as icon
- now-playing tooltip
- now-playing's current time badge
- controls (next, previous, play / pause, stop)
Is this all the Banshee helper does? I tried to mimick it (in terms of functionality at least).
Paweł Bara (keirangtp) wrote : | # |
you mean the weird vinyl overlay stuff?
Rico Tzschichholz (ricotz) wrote : | # |
Yes, this would create a consistent behaviour and look and feel of the Mediaplayer helpers.
But infact it would also be great if you could make this helper generic and let it work for all Mediaplayer which implement the MPRIS Mediaplayer2 DBus Interface. (like Rhythmbox, Spotify, Clementine, ...)
- 84. By keirangtp
-
vinyl overlay in album covers
Paweł Bara (keirangtp) wrote : | # |
I've just commited the code that overlays covers.
As for the generic helper, I unfortunately have no time to do it anymore. When I've started writing the helper, I wasn't working on Clementine yet but I am now. ;)
Anyway, as far as I can see there are no hardcoded dependencies on Clementine in my code (except for the "branding") so if somebody would like to generalize it - please do! It wouldn't hurt to simplify it a bit while you're at it because 600 lines for a stupid remote control is a whole lot of code!
As for me, I (and all the Clementine + Docky pair users) will be happy if you decide to include this non-generic Clementine helper into your codebase. Of course I can try to fix other things first if you have any more comments.
Paweł Bara (keirangtp) wrote : | # |
Are there any news on this?
Preview Diff
1 | === modified file 'metadata/Makefile.am' |
2 | --- metadata/Makefile.am 2010-10-19 18:14:46 +0000 |
3 | +++ metadata/Makefile.am 2011-02-23 17:37:14 +0000 |
4 | @@ -2,6 +2,7 @@ |
5 | |
6 | dist_script_DATA = \ |
7 | banshee_control.py.info \ |
8 | + clementine_control.py.info \ |
9 | deluge_badge.py.info \ |
10 | emesene_control.py.info \ |
11 | gajim_badge.py.info \ |
12 | |
13 | === added file 'metadata/clementine_control.py.info' |
14 | --- metadata/clementine_control.py.info 1970-01-01 00:00:00 +0000 |
15 | +++ metadata/clementine_control.py.info 2011-02-23 17:37:14 +0000 |
16 | @@ -0,0 +1,6 @@ |
17 | +[DockmanagerHelper] |
18 | +Name=Clementine Controls |
19 | +Description=Control Clementine media playback |
20 | +Icon=application-x-clementine |
21 | +AppName=clementine |
22 | +DBusName=org.mpris.MediaPlayer2.clementine |
23 | |
24 | === modified file 'scripts/Makefile.am' |
25 | --- scripts/Makefile.am 2010-10-19 18:14:46 +0000 |
26 | +++ scripts/Makefile.am 2011-02-23 17:37:14 +0000 |
27 | @@ -2,6 +2,7 @@ |
28 | |
29 | dist_script_SCRIPTS = \ |
30 | banshee_control.py \ |
31 | + clementine_control.py \ |
32 | deluge_badge.py \ |
33 | emesene_control.py \ |
34 | gajim_badge.py \ |
35 | |
36 | === added file 'scripts/clementine_control.py' |
37 | --- scripts/clementine_control.py 1970-01-01 00:00:00 +0000 |
38 | +++ scripts/clementine_control.py 2011-02-23 17:37:14 +0000 |
39 | @@ -0,0 +1,688 @@ |
40 | +#!/usr/bin/env python |
41 | + |
42 | +# |
43 | +# Copyright (C) 2010 Pawel Bara |
44 | +# |
45 | +# This program is free software: you can redistribute it and/or modify |
46 | +# it under the terms of the GNU General Public License as published by |
47 | +# the Free Software Foundation, either version 3 of the License, or |
48 | +# (at your option) any later version. |
49 | +# |
50 | +# This program is distributed in the hope that it will be useful, |
51 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
52 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
53 | +# GNU General Public License for more details. |
54 | +# |
55 | +# You should have received a copy of the GNU General Public License |
56 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
57 | +# |
58 | + |
59 | +import atexit |
60 | +import dbus |
61 | +import gobject |
62 | +import os |
63 | + |
64 | +try: |
65 | + import gtk |
66 | + from dockmanager.dockmanager import DockManagerItem, DockManagerSink |
67 | + from dockmanager.dockmanager import DOCKITEM_IFACE, RESOURCESDIR |
68 | + from signal import signal, SIGTERM |
69 | + from sys import exit |
70 | +except ImportError, e: |
71 | + print e |
72 | + exit() |
73 | + |
74 | + |
75 | +# cover overlay related constants |
76 | +album_art_tmpfile = "/tmp/dockmanager_%s_clementine_control.png" % os.getenv('USERNAME') |
77 | + |
78 | +# 0 - none, 1 - jewel, 2 - vinyl |
79 | +overlay = 2 |
80 | + |
81 | + |
82 | +freedesktop_iface_name = 'org.freedesktop.DBus.Properties' |
83 | + |
84 | +def connect_to_dbus_properties_changed(dbus_object, callback): |
85 | + """ |
86 | + Connects the callback with the 'PropertiesChanged' signal of the |
87 | + dbus_object. |
88 | + """ |
89 | + |
90 | + property_iface = dbus.Interface(dbus_object, dbus_interface=freedesktop_iface_name) |
91 | + return property_iface.connect_to_signal('PropertiesChanged', callback) |
92 | + |
93 | +def get_dbus_property(dbus_object, property_name): |
94 | + """ |
95 | + Gets current value of the property property_name from the dbus_object. |
96 | + """ |
97 | + |
98 | + property_iface = dbus.Interface(dbus_object, dbus_interface=freedesktop_iface_name) |
99 | + return property_iface.Get(dbus_object.dbus_interface, property_name) |
100 | + |
101 | + |
102 | +class ClementineItem(DockManagerItem): |
103 | + """ |
104 | + Clementine's helper. It's working while Clementine's on and pauses when |
105 | + Clementine is being closed. |
106 | + |
107 | + It's functionalities are: |
108 | + - control menu for Clementine. It always has the "next", "previous" and |
109 | + "stop" controls. It also has a "play" or "pause" control, depending on |
110 | + the current PlaybackStatus of Clementine |
111 | + - tooltip with information about the current song |
112 | + - badge with the now-playing time of the current song |
113 | + - the current cover art as the Clementine's icon on the dock |
114 | + """ |
115 | + |
116 | + def __init__(self, sink, path): |
117 | + DockManagerItem.__init__(self, sink, path) |
118 | + |
119 | + self.timer = None |
120 | + |
121 | + self.synch_counter = 0 |
122 | + self.pos = 0 |
123 | + |
124 | + self.clementine = ClementineDBusFacade(self.turn_helper_on, |
125 | + self.turn_helper_off, |
126 | + self.on_playback_status_changed, |
127 | + self.on_song_changed, |
128 | + self.on_cover_art_changed, |
129 | + self.on_seeked) |
130 | + |
131 | + def turn_helper_on(self): |
132 | + """ |
133 | + Effectively turn's helper on (after every Clementine's |
134 | + startup). |
135 | + """ |
136 | + |
137 | + self.prepare_menu() |
138 | + |
139 | + self.update_tooltip() |
140 | + |
141 | + self.update_badge() |
142 | + self.set_timer() |
143 | + |
144 | + self.change_cover_art() |
145 | + |
146 | + def turn_helper_off(self): |
147 | + """ |
148 | + Effectively turn's helper off (after every Clementine's |
149 | + shutdown). |
150 | + """ |
151 | + |
152 | + self.reset_icon() |
153 | + |
154 | + self.stop_timer() |
155 | + self.reset_badge() |
156 | + |
157 | + self.reset_tooltip() |
158 | + |
159 | + self.clear_menu() |
160 | + |
161 | + def prepare_menu(self, is_playing=None): |
162 | + """ |
163 | + Recreates the menu of the helper. |
164 | + |
165 | + The is_playing flag indicates which button will be shown (play or |
166 | + pause). If the flag's value is not given, it will be determined by |
167 | + the method. |
168 | + """ |
169 | + |
170 | + self.clear_menu() |
171 | + |
172 | + try: |
173 | + |
174 | + if is_playing is None: |
175 | + is_playing = self.clementine.check_is_playing() |
176 | + |
177 | + except ClementineTurnedOffError: |
178 | + |
179 | + # leave menu empty if Clementine's off |
180 | + return |
181 | + |
182 | + self.add_menu_item("Previous", "media-skip-backward") |
183 | + |
184 | + if is_playing: |
185 | + self.add_menu_item("Pause", "media-playback-pause") |
186 | + else: |
187 | + self.add_menu_item("Play", "media-playback-start") |
188 | + |
189 | + self.add_menu_item("Stop", "media-playback-stop") |
190 | + self.add_menu_item("Next", "media-skip-forward") |
191 | + |
192 | + def clear_menu(self): |
193 | + """ |
194 | + Clears menu of the helper. |
195 | + """ |
196 | + |
197 | + for k in self.id_map.keys(): |
198 | + self.remove_menu_item(k) |
199 | + |
200 | + def on_seeked(self, position): |
201 | + """ |
202 | + Moves time marker when Clementine's been seeked. |
203 | + """ |
204 | + |
205 | + self.pos = position |
206 | + self.update_badge() |
207 | + |
208 | + def update_badge(self): |
209 | + """ |
210 | + Updates the helper's badge with the now-playing time. The method |
211 | + can be used from timers (returns boolean indicating whether |
212 | + something went wrong). |
213 | + """ |
214 | + |
215 | + try: |
216 | + |
217 | + # synchronize position with Clementine every tenth timer's tick |
218 | + if(self.synch_counter % 10 == 0): |
219 | + self.pos = self.clementine.get_play_time() |
220 | + else: |
221 | + self.pos += 1000000 |
222 | + |
223 | + self.synch_counter += 1 |
224 | + |
225 | + except ClementineTurnedOffError: |
226 | + |
227 | + # if Clementine's off, reset the badge |
228 | + self.reset_badge() |
229 | + return False |
230 | + |
231 | + if self.pos == 0: |
232 | + self.reset_badge() |
233 | + else: |
234 | + pos = self.pos / 1000000 |
235 | + badge_text = '%i:%02i' % (pos / 60, pos % 60) |
236 | + |
237 | + self.set_badge(badge_text) |
238 | + |
239 | + return True |
240 | + |
241 | + def update_tooltip(self, song_info=None): |
242 | + """ |
243 | + Updates the helper's tooltip with basic information about |
244 | + the currently playing song. |
245 | + |
246 | + If the information (song's metadata) is not given, |
247 | + it will try to determine those itself. |
248 | + """ |
249 | + |
250 | + try: |
251 | + if song_info is None: |
252 | + song_info = self.clementine.get_current_song_metadata() |
253 | + except ClementineTurnedOffError: |
254 | + # if Clementine's off, reset the tooltip |
255 | + self.reset_tooltip() |
256 | + return |
257 | + |
258 | + if self.proper_metadata(song_info): |
259 | + # first artist from artist's list or Unknown if there's none |
260 | + artist = song_info.get("xesam:artist", ["Unknown"])[0] |
261 | + if len(artist) == 0: |
262 | + artist = "Unknown" |
263 | + |
264 | + # do we know the length of the song? |
265 | + if 'mpris:length' in song_info: |
266 | + # duration is in microseconds - translating it to seconds |
267 | + duration = song_info['mpris:length'] / 1000000 |
268 | + |
269 | + final_info = '%s - %s (%i:%02i)' % (artist, |
270 | + song_info.get("xesam:title", "Unknown"), |
271 | + duration / 60, duration % 60) |
272 | + else: |
273 | + final_info = '%s - %s (?)' % (artist, |
274 | + song_info.get("xesam:title", "Unknown")) |
275 | + |
276 | + self.set_tooltip(final_info) |
277 | + |
278 | + else: |
279 | + |
280 | + self.reset_tooltip() |
281 | + |
282 | + def proper_metadata(self, metadata): |
283 | + return metadata != None and len(metadata) > 0 |
284 | + |
285 | + def start_timer(self): |
286 | + """ |
287 | + Starts the timer that updates the "current time" badge. |
288 | + """ |
289 | + |
290 | + self.stop_timer() |
291 | + |
292 | + if not self.timer: |
293 | + self.timer = gobject.timeout_add (1000, self.update_badge) |
294 | + |
295 | + def stop_timer(self): |
296 | + """ |
297 | + Stops the timer that updates the "current time" badge. |
298 | + """ |
299 | + |
300 | + self.synch_counter = 0 |
301 | + self.pos = 0 |
302 | + |
303 | + if self.timer: |
304 | + gobject.source_remove (self.timer) |
305 | + self.timer = None |
306 | + |
307 | + def change_cover_art(self, cover_art_url=None): |
308 | + """ |
309 | + Changes the helper's icon to icon from file 'cover_art_url'. |
310 | + """ |
311 | + |
312 | + try: |
313 | + |
314 | + # initial phase - 'pull' the overlayed covert art's path (if any) |
315 | + if cover_art_url is None: |
316 | + cover_art_url = self.clementine.get_path_for_overlayed_cover() |
317 | + |
318 | + except ClementineTurnedOffError: |
319 | + |
320 | + # reset icon if Clementine's off or no song is currently playing |
321 | + self.reset_icon() |
322 | + return |
323 | + |
324 | + if cover_art_url is not None: |
325 | + self.set_icon(cover_art_url) |
326 | + else: |
327 | + self.reset_icon() |
328 | + |
329 | + def reset(self): |
330 | + """ |
331 | + Resets the helper. |
332 | + """ |
333 | + |
334 | + self.reset_icon() |
335 | + self.stop_timer() |
336 | + self.reset_badge() |
337 | + self.reset_tooltip() |
338 | + |
339 | + def on_playback_status_changed(self, new_playback_status): |
340 | + """ |
341 | + The callback for changes of Clementine's PlaybackStatus. |
342 | + """ |
343 | + |
344 | + self.set_timer(new_playback_status) |
345 | + |
346 | + is_playing = self.clementine.is_playing(new_playback_status) |
347 | + |
348 | + # reset the helper if Clementine's been stopped |
349 | + if self.clementine.is_stopped(new_playback_status): |
350 | + self.reset() |
351 | + |
352 | + # recreate the helper's menu |
353 | + self.prepare_menu(is_playing) |
354 | + |
355 | + def set_timer(self, playback_status=None): |
356 | + """ |
357 | + Turns the badge's timer on or off depending on the Clementine's |
358 | + current PlaybackStatus. |
359 | + """ |
360 | + |
361 | + try: |
362 | + if playback_status == None: |
363 | + is_playing = self.clementine.check_is_playing() |
364 | + else: |
365 | + is_playing = self.clementine.is_playing(playback_status) |
366 | + |
367 | + # pausing the badge's timer when current song is |
368 | + # paused or stopped |
369 | + if is_playing: |
370 | + self.start_timer() |
371 | + else: |
372 | + self.stop_timer() |
373 | + |
374 | + except ClementineTurnedOffError: |
375 | + # Clementine's off - stop the badge timer |
376 | + self.stop_timer() |
377 | + |
378 | + def on_song_changed(self, new_metadata): |
379 | + """ |
380 | + The callback for changes of the currently playing Clementine's song. |
381 | + """ |
382 | + |
383 | + if 'mpris:trackid' in new_metadata: |
384 | + self.update_tooltip(new_metadata) |
385 | + self.reset_badge() |
386 | + |
387 | + # change the cover art too if it's already present in the message |
388 | + try: |
389 | + if 'mpris:artUrl' in new_metadata: |
390 | + overlayed = self.clementine.get_path_for_overlayed_cover() |
391 | + self.change_cover_art(overlayed) |
392 | + except ClementineTurnedOffError: |
393 | + self.reset_icon() |
394 | + else: |
395 | + self.reset() |
396 | + |
397 | + def on_cover_art_changed(self, cover_art_url): |
398 | + """ |
399 | + The callback for changes of the current cover art in Clementine. |
400 | + """ |
401 | + |
402 | + if cover_art_url != None: |
403 | + self.change_cover_art(cover_art_url) |
404 | + else: |
405 | + self.reset_icon() |
406 | + |
407 | + def menu_pressed(self, menu_id): |
408 | + """ |
409 | + Performs an action invoked by clicking one of the helper's context |
410 | + menu items. |
411 | + """ |
412 | + |
413 | + try: |
414 | + |
415 | + if self.id_map[menu_id] == "Previous": |
416 | + self.clementine.prev() |
417 | + elif self.id_map[menu_id] == "Stop": |
418 | + self.clementine.stop() |
419 | + elif self.id_map[menu_id] == "Play": |
420 | + self.clementine.play() |
421 | + elif self.id_map[menu_id] == "Pause": |
422 | + self.clementine.pause() |
423 | + elif self.id_map[menu_id] == "Next": |
424 | + self.clementine.next() |
425 | + |
426 | + except ClementineTurnedOffError: |
427 | + |
428 | + # Clementine's off - do nothing |
429 | + return |
430 | + |
431 | + |
432 | +clementine_bus_name = "org.mpris.MediaPlayer2.clementine" |
433 | + |
434 | +player_object_path = "/org/mpris/MediaPlayer2" |
435 | +player_iface_name = "org.mpris.MediaPlayer2.Player" |
436 | + |
437 | +tlist_object_path = "/org/mpris/MediaPlayer2" |
438 | +tlist_iface_name = "org.mpris.MediaPlayer2.Tracklist" |
439 | + |
440 | +class ClementineDBusFacade: |
441 | + |
442 | + def __init__(self, on_callback, off_callback, playback_status_changed_callback, |
443 | + song_changed_callback, cover_art_changed_callback, seeked_callback): |
444 | + self.on_callback = on_callback |
445 | + self.off_callback = off_callback |
446 | + self.playback_status_changed_callback = playback_status_changed_callback |
447 | + self.song_changed_callback = song_changed_callback |
448 | + self.cover_art_changed_callback = cover_art_changed_callback |
449 | + self.seeked_callback = seeked_callback |
450 | + |
451 | + self.cl_player = None |
452 | + self.cl_tlist = None |
453 | + |
454 | + self.bus = dbus.SessionBus() |
455 | + # we track Clementine's on / off status |
456 | + self.bus.watch_name_owner(clementine_bus_name, self.on_dbus_name_change) |
457 | + |
458 | + def is_on(self): |
459 | + """ |
460 | + Returns a flag saying whether Clementine's on. |
461 | + """ |
462 | + |
463 | + return self.cl_player is not None |
464 | + |
465 | + def prev(self): |
466 | + """ |
467 | + Changes the current song to the previous one. Might throw |
468 | + ClementineTurnedOffError if Clementine's been turned off. |
469 | + """ |
470 | + |
471 | + if not self.is_on(): |
472 | + raise ClementineTurnedOffError() |
473 | + |
474 | + self.cl_player.Previous() |
475 | + |
476 | + def stop(self): |
477 | + """ |
478 | + Stops Clementine's playback. Might throw ClementineTurnedOffError |
479 | + if Clementine's been turned off. |
480 | + """ |
481 | + |
482 | + if not self.is_on(): |
483 | + raise ClementineTurnedOffError() |
484 | + |
485 | + self.cl_player.Stop() |
486 | + |
487 | + def pause(self): |
488 | + """ |
489 | + Pauses Clementine's playback. Might throw ClementineTurnedOffError |
490 | + if Clementine's been turned off. |
491 | + """ |
492 | + |
493 | + if not self.is_on(): |
494 | + raise ClementineTurnedOffError() |
495 | + |
496 | + self.cl_player.Pause() |
497 | + |
498 | + def play(self): |
499 | + """ |
500 | + Starts Clementine's playback. Might throw ClementineTurnedOffError |
501 | + if Clementine's been turned off. |
502 | + """ |
503 | + |
504 | + if not self.is_on(): |
505 | + raise ClementineTurnedOffError() |
506 | + |
507 | + self.cl_player.Play() |
508 | + |
509 | + def next(self): |
510 | + """ |
511 | + Changes the current song to the next one. Might throw |
512 | + ClementineTurnedOffError if Clementine's been turned off. |
513 | + """ |
514 | + |
515 | + if not self.is_on(): |
516 | + raise ClementineTurnedOffError() |
517 | + |
518 | + self.cl_player.Next() |
519 | + |
520 | + def get_play_time(self): |
521 | + """ |
522 | + Gets the current song's now-playing position in microseconds. Might |
523 | + throw ClementineTurnedOffError if Clementine's been turned off. |
524 | + """ |
525 | + |
526 | + if not self.is_on(): |
527 | + raise ClementineTurnedOffError() |
528 | + |
529 | + return get_dbus_property(self.cl_player, 'Position') |
530 | + |
531 | + def get_current_song_metadata(self): |
532 | + """ |
533 | + Returns all of the current song's metadata. Might throw |
534 | + ClementineTurnedOffError if Clementine's been turned off. |
535 | + """ |
536 | + |
537 | + if not self.is_on(): |
538 | + raise ClementineTurnedOffError() |
539 | + |
540 | + return get_dbus_property(self.cl_player, 'Metadata') |
541 | + |
542 | + def check_is_playing(self): |
543 | + """ |
544 | + Checks whether Clementine is currently playing a song. Might |
545 | + throw ClementineTurnedOffError if Clementine's been turned off. |
546 | + """ |
547 | + |
548 | + if not self.is_on(): |
549 | + raise ClementineTurnedOffError() |
550 | + |
551 | + playback_status = get_dbus_property(self.cl_player, 'PlaybackStatus') |
552 | + return self.is_playing(playback_status) |
553 | + |
554 | + def is_playing(self, status): |
555 | + """ |
556 | + Decodes Clementine's status and returns a flag saying whether it's |
557 | + currently playing a song. |
558 | + """ |
559 | + |
560 | + return status == 'Playing' |
561 | + |
562 | + def is_stopped(self, status): |
563 | + """ |
564 | + Decodes Clementine's status and returns a flag saying whether it's |
565 | + currently stopped. |
566 | + """ |
567 | + |
568 | + return status == 'Stopped' |
569 | + |
570 | + def on_properties_changed(self, iface_name, changed_props, invalidated_props): |
571 | + """ |
572 | + A callback for the Clementine's 'PropertiesChanged' signal. |
573 | + """ |
574 | + |
575 | + if iface_name == player_iface_name: |
576 | + |
577 | + if('PlaybackStatus' in changed_props): |
578 | + self.playback_status_changed_callback(changed_props['PlaybackStatus']) |
579 | + |
580 | + if('Metadata' in changed_props): |
581 | + self.song_changed_callback(changed_props['Metadata']) |
582 | + |
583 | + try: |
584 | + cover_art = changed_props['Metadata']['mpris:artUrl'] |
585 | + cover_art = cover_art[len('file://'):] |
586 | + |
587 | + # only if it's a valid file |
588 | + if os.path.isfile(cover_art): |
589 | + mtime = os.stat(cover_art).st_mtime |
590 | + |
591 | + # only if there really was a change |
592 | + if self.last_cover_art != cover_art or self.last_cover_art_mtime != mtime: |
593 | + self.last_cover_art = cover_art |
594 | + self.last_cover_art_mtime = mtime |
595 | + |
596 | + self.cover_art_changed_callback(self.get_album_art_overlay_path(cover_art)) |
597 | + else: |
598 | + raise KeyError |
599 | + |
600 | + except KeyError: |
601 | + # Clementine is now not playing anything or the current |
602 | + # song has no cover |
603 | + self.last_cover_art = None |
604 | + self.last_cover_art_mtime = None |
605 | + |
606 | + self.cover_art_changed_callback(None) |
607 | + |
608 | + def get_path_for_overlayed_cover(self): |
609 | + """ |
610 | + Overlays the cover of currently playing song and then returns the path |
611 | + to the overlayed cover file. Returns 'None' if there's nothing playing. |
612 | + Might throw ClementineTurnedOffError if Clementine's been turned off. |
613 | + """ |
614 | + |
615 | + metadata = self.get_current_song_metadata() |
616 | + |
617 | + try: |
618 | + cover_art = metadata['mpris:artUrl'] |
619 | + cover_art = cover_art[len('file://'):] |
620 | + return self.get_album_art_overlay_path(cover_art) |
621 | + except KeyError: |
622 | + return None |
623 | + |
624 | + # taken from rhythmbox_control.py |
625 | + def get_album_art_overlay_path(self, picfile): |
626 | + """ |
627 | + Adds an overlay to the cover art. |
628 | + """ |
629 | + |
630 | + if overlay == 0: |
631 | + return picfile |
632 | + |
633 | + try: |
634 | + pb = gtk.gdk.pixbuf_new_from_file(picfile) |
635 | + except Exception, e: |
636 | + print e |
637 | + return picfile |
638 | + |
639 | + pb_result = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 250, 250) |
640 | + pb_result.fill(0x00000000) |
641 | + |
642 | + if overlay == 1: |
643 | + overlayfile = os.path.join(RESOURCESDIR, "albumoverlay_jewel.png") |
644 | + pb.composite(pb_result, 30, 21, 200, 200, 30, 21, 200.0 / pb.get_width(), 200.0 / pb.get_height(), gtk.gdk.INTERP_BILINEAR, 255) |
645 | + elif overlay == 2: |
646 | + overlayfile = os.path.join(RESOURCESDIR, "albumoverlay_vinyl.png") |
647 | + pb.composite(pb_result, 3, 26, 190, 190, 3, 26, 190.0 / pb.get_width(), 190.0 / pb.get_height(), gtk.gdk.INTERP_BILINEAR, 255) |
648 | + else: |
649 | + return picfile |
650 | + |
651 | + pb_overlay = gtk.gdk.pixbuf_new_from_file_at_size(overlayfile, 250, 250) |
652 | + pb_overlay.composite(pb_result, 0, 0, 250, 250, 0, 0, 1, 1, gtk.gdk.INTERP_BILINEAR, 255) |
653 | + pb_result.save(album_art_tmpfile, "png", {}) |
654 | + |
655 | + return album_art_tmpfile |
656 | + |
657 | + def on_dbus_name_change(self, connection_name): |
658 | + """ |
659 | + This method effectively tracks down the events of Clementine app starting |
660 | + and shutting down. When the app shuts down, this callback nullifies our |
661 | + Clementine's proxies and when the app starts, the callback sets the valid |
662 | + proxies again. |
663 | + """ |
664 | + |
665 | + if len(connection_name) != 0: |
666 | + bus_object = self.bus.get_object(connection_name, player_object_path) |
667 | + self.cl_player = dbus.Interface(bus_object, player_iface_name) |
668 | + |
669 | + bus_object = self.bus.get_object(connection_name, player_object_path) |
670 | + self.cl_player.connect_to_signal('Seeked', self.seeked_callback) |
671 | + |
672 | + bus_object = self.bus.get_object(connection_name, tlist_object_path) |
673 | + self.cl_tlist = dbus.Interface(bus_object, tlist_iface_name) |
674 | + |
675 | + try: |
676 | + cover_path = get_dbus_property(self.cl_player, 'Metadata')['mpris:artUrl'] |
677 | + |
678 | + if os.path.isfile(cover_path): |
679 | + self.last_cover_art = self.get_album_art_overlay_path(cover_path) |
680 | + self.last_cover_art_mtime = os.stat(cover_path).st_mtime |
681 | + else: |
682 | + raise KeyError |
683 | + |
684 | + except KeyError, DBusException: |
685 | + self.last_cover_art = None |
686 | + self.last_cover_art_mtime = None |
687 | + |
688 | + connect_to_dbus_properties_changed(self.cl_player, self.on_properties_changed) |
689 | + |
690 | + self.on_callback() |
691 | + |
692 | + else: |
693 | + self.cl_player = None |
694 | + self.cl_tlist = None |
695 | + |
696 | + self.off_callback() |
697 | + |
698 | + |
699 | +class ClementineTurnedOffError(Exception): |
700 | + """ |
701 | + Indicates that user requested an operation that requires Clementine |
702 | + to be on while it was turned off. |
703 | + """ |
704 | + |
705 | + pass |
706 | + |
707 | + |
708 | +class ClementineSink(DockManagerSink): |
709 | + |
710 | + def item_path_found(self, pathtoitem, item): |
711 | + if item.Get(DOCKITEM_IFACE, "DesktopFile", dbus_interface="org.freedesktop.DBus.Properties").endswith("clementine.desktop"): |
712 | + self.items[pathtoitem] = ClementineItem(self, pathtoitem) |
713 | + |
714 | + |
715 | +clementine_sink = ClementineSink() |
716 | + |
717 | +def cleanup (): |
718 | + clementine_sink.dispose () |
719 | + |
720 | +if __name__ == "__main__": |
721 | + mainloop = gobject.MainLoop(is_running=True) |
722 | + |
723 | + atexit.register (cleanup) |
724 | + signal(SIGTERM, lambda signum, stack_frame: exit(1)) |
725 | + |
726 | + while mainloop.is_running(): |
727 | + mainloop.run() |
Could you add the same cover-art modification like rhythmbox and banshee helpers have.