Merge lp:~gabor-karsay/awn-extras/awnmediaplayers into lp:awn-extras

Proposed by Gabor Karsay
Status: Merged
Merged at revision: 1527
Proposed branch: lp:~gabor-karsay/awn-extras/awnmediaplayers
Merge into: lp:awn-extras
Diff against target: 302 lines (+189/-14)
1 file modified
shared/python/awnmediaplayers.py (+189/-14)
To merge this branch: bzr merge lp:~gabor-karsay/awn-extras/awnmediaplayers
Reviewer Review Type Date Requested Status
Michal Hruby Pending
Review via email: mp+46206@code.launchpad.net

Description of the change

mhr3, do you approve?

It's only about awnmediaplayes.py.

* It adds support for mediaplayers Clementine and Guayadeque and detects and launches already supported players Amarok, Audacious and VLC (searching for the binaries in path). I could have added more, but they are either not in Ubuntu repositories, so I couldn't/didn't want to test, or they have no GUI and are run from commandline. (Amarok launches, but it goes crazy in my Gnome environment.)

* Rhythmbox does not show artwork sometimes. I found the main reason, at least on my system: It doesn't show any artwork that has non-ASCII characters in its path. That is an encoding issue. It's also present in Dockmanager, I will report it later there. Onox reported a similar bug in Dockmanager, might be the same, it's bug #680681.

I've also taken and changed some code from Dockmanager to search in more places for artwork: in the song folder and in ID3 tags. This includes a new optional dependency on Mutagen. I wonder if that should be reported somewhere so that packagers can add it as a proposed dependency?

And a minor bugfix (catching an error while launching via DBus).

To post a comment you must log in.
Revision history for this message
Julien Lavergne (gilir) wrote :

Is a review really necessary ? I think mhr3 would already complain if there is a problem :)

Revision history for this message
Gabor Karsay (gabor-karsay) wrote :

I sort of forgot this one and merged it now since I got only encouraging comments :)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'shared/python/awnmediaplayers.py'
2--- shared/python/awnmediaplayers.py 2011-01-07 20:51:06 +0000
3+++ shared/python/awnmediaplayers.py 2011-01-13 23:06:58 +0000
4@@ -20,6 +20,8 @@
5
6 import sys
7 import os
8+import subprocess
9+import atexit
10
11 import gobject
12 import pygtk
13@@ -31,9 +33,32 @@
14 from dbus.mainloop.glib import DBusGMainLoop
15 import string
16
17+try:
18+ import mutagen.mp3
19+ import mutagen.mp4
20+ from mutagen.id3 import ID3
21+ import tempfile
22+ album_art_file = "%s/awnmediaplayer_%s.png" % (tempfile.gettempdir(), os.getenv('USERNAME'))
23+ art_icon_from_tag = True
24+except ImportError:
25+ art_icon_from_tag = False
26+
27+if gtk.gtk_version >= (2, 18):
28+ from urllib import unquote
29+
30 DBusGMainLoop(set_as_default=True)
31
32
33+def cleanup():
34+ if art_icon_from_tag:
35+ try:
36+ os.remove(album_art_file)
37+ except OSError:
38+ pass
39+
40+atexit.register(cleanup)
41+
42+
43 def get_app_name():
44 player_name = None
45 bus_obj = dbus.SessionBus().get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
46@@ -67,9 +92,35 @@
47 player_name = "DragonPlayer"
48 elif bus_obj.NameHasOwner('org.freedesktop.MediaPlayer') == True:
49 player_name = "mpDris"
50+ elif bus_obj.NameHasOwner('org.mpris.clementine') == True:
51+ player_name = "Clementine"
52+ elif bus_obj.NameHasOwner('org.mpris.guayadeque') == True:
53+ player_name = "Guayadeque"
54 return player_name
55
56
57+def player_available(executable):
58+ """Check if player is installed if it's not in 'Activatable Services' on DBus"""
59+
60+ for path in os.getenv('PATH').split(':'):
61+ if path == '':
62+ continue
63+ if os.path.isfile(os.path.join(path, executable)):
64+ return True
65+ return False
66+
67+
68+def launch_player(args):
69+ """Launch player if this can't be done via DBus"""
70+
71+ try:
72+ subprocess.Popen(args)
73+ except OSError, e:
74+ print "awnmediaplayer: error launching %s: %s" % (args, e)
75+ return False
76+ return True
77+
78+
79 class GenericPlayer(object):
80 """Insert the level of support here"""
81
82@@ -120,11 +171,13 @@
83 """
84 if (self.dbus_base_name != None):
85 object_path = '/' + self.dbus_base_name.replace('.', '/')
86- bus = dbus.SessionBus()
87- obj = bus.get_object(self.dbus_base_name, object_path)
88- return True
89- else:
90- return False
91+ try:
92+ bus = dbus.SessionBus()
93+ obj = bus.get_object(self.dbus_base_name, object_path)
94+ return True
95+ except Exception, e:
96+ print "awnmediaplayer: error launching %s: %s" % (self.__class__.__name__, e)
97+ return False
98
99 def get_dbus_name(self):
100 """
101@@ -231,7 +284,6 @@
102 if info['arturl'][0:7] == "file://":
103 result['album-art'] = str(info['arturl'][7:])
104 if gtk.gtk_version >= (2, 18):
105- from urllib import unquote
106 result['album-art'] = unquote(result['album-art'])
107 else:
108 print "Don't understand the album art location: %s" % info['arturl']
109@@ -286,7 +338,8 @@
110 def get_media_info(self):
111 self.dbus_driver()
112 ret_dict = {}
113- result = self.rbShell.getSongProperties(self.player.getPlayingUri())
114+ playinguri = self.player.getPlayingUri()
115+ result = self.rbShell.getSongProperties(playinguri)
116
117 # Currently Playing Title
118 if result['artist'] != '':
119@@ -304,19 +357,77 @@
120
121 # cover-art
122 if 'rb:coverArt-uri' in result:
123- albumart_exact = result['rb:coverArt-uri']
124+ albumart_exact = result['rb:coverArt-uri'].encode('utf8')
125 # bug in rhythmbox 0.11.6 - returns uri, but not properly encoded,
126 # but it's enough to remove the file:// prefix
127 albumart_exact = albumart_exact.replace('file://', '', 1)
128 if gtk.gtk_version >= (2, 18):
129- from urllib import unquote
130 albumart_exact = unquote(albumart_exact)
131- ret_dict['album-art'] = albumart_exact
132+ # Sanity check if encoding and unquoting did work
133+ if os.path.isfile(albumart_exact):
134+ ret_dict['album-art'] = albumart_exact
135+ return ret_dict
136+ else:
137+ print "awnmediaplayers: Unquoting error:\n%s\ndoes not match\n%s" % (result['rb:coverArt-uri'], albumart_exact)
138+
139+ # perhaps it's in the cache folder
140+ if 'album' in result and 'artist' in result:
141+ cache_dir = os.path.expanduser("~/.cache/rhythmbox/covers")
142+ cache_file = '%s/%s - %s.jpg' % (cache_dir, result['artist'], result['album'])
143+ if os.path.isfile(cache_file):
144+ ret_dict['album-art'] = cache_file
145+ return ret_dict
146+
147+ # The following is based on code from Dockmanager
148+ # Copyright (C) 2009-2010 Jason Smith, Rico Tzschichholz, Robert Dyer
149+
150+ # Look in song folder
151+ filename = playinguri.encode('utf8').replace('file://', '', 1)
152+ if gtk.gtk_version >= (2, 18):
153+ filename = unquote(filename)
154+ coverdir = os.path.dirname(filename)
155+ if os.path.isdir(coverdir):
156+ covernames = ["cover", "album", "albumart", ".folder", "folder"]
157+ extensions = [".jpg", ".jpeg", ".png"]
158+ for f in os.listdir(coverdir):
159+ for ext in extensions:
160+ if f.lower().endswith(ext):
161+ for name in covernames:
162+ if f.lower() == (name + ext):
163+ ret_dict['album-art'] = os.path.join(coverdir, f)
164+ return ret_dict
165 else:
166- # perhaps it's in the cache folder
167- if 'album' in result and 'artist' in result:
168- cache_dir = ".cache/rhythmbox/covers"
169- ret_dict['album-art'] = '%s/%s - %s.jpg' % (cache_dir, result['artist'], result['album'])
170+ print "awnmediaplayers: Unquoting error:\n%s (file)\ndoes not match\n%s (directory)" % (playinguri, coverdir)
171+
172+ # Look for image in tags
173+ if art_icon_from_tag and 'mimetype' in result:
174+ image_data = None
175+ if result['mimetype'] == "application/x-id3":
176+ try:
177+ f = ID3(filename)
178+ apicframes = f.getall("APIC")
179+ if len(apicframes) >= 1:
180+ frame = apicframes[0]
181+ image_data = frame.data
182+ except:
183+ pass
184+ elif result['mimetype'] == "audio/x-aac":
185+ try:
186+ f = mutagen.mp4.MP4(filename)
187+ if "covr" in f.tags:
188+ covertag = f.tags["covr"][0]
189+ image_data = covertag
190+ except:
191+ pass
192+ if image_data:
193+ try:
194+ loader = gtk.gdk.PixbufLoader()
195+ loader.write(image_data)
196+ loader.close()
197+ loader.get_pixbuf().save(album_art_file, "png", {})
198+ ret_dict['album-art'] = album_art_file
199+ except:
200+ pass
201
202 return ret_dict
203
204@@ -627,6 +738,7 @@
205
206
207 class Songbird(MPRISPlayer):
208+ """Discontinued in 2010"""
209
210 def __init__(self):
211 MPRISPlayer.__init__(self, 'org.mpris.songbird')
212@@ -637,14 +749,27 @@
213 def __init__(self):
214 MPRISPlayer.__init__(self, 'org.mpris.vlc')
215
216+ def is_available(self):
217+ return player_available('vlc')
218+
219+ def start(self):
220+ return launch_player(['vlc', '--control', 'dbus'])
221+
222
223 class Audacious(MPRISPlayer):
224
225 def __init__(self):
226 MPRISPlayer.__init__(self, 'org.mpris.audacious')
227
228+ def is_available(self):
229+ return player_available('audacious')
230+
231+ def start(self):
232+ return launch_player('audacious')
233+
234
235 class BMP(MPRISPlayer):
236+ """Beep Media Player, discontinued"""
237
238 def __init__(self):
239 MPRISPlayer.__init__(self, 'org.mpris.bmp')
240@@ -662,8 +787,15 @@
241 def __init__(self):
242 MPRISPlayer.__init__(self, 'org.mpris.amarok')
243
244+ def is_available(self):
245+ return player_available('amarok')
246+
247+ def start(self):
248+ return launch_player('amarok')
249+
250
251 class Aeon(MPRISPlayer):
252+ """Discontinued"""
253
254 def __init__(self):
255 MPRISPlayer.__init__(self, 'org.mpris.aeon')
256@@ -681,3 +813,46 @@
257
258 def __init__(self):
259 MPRISPlayer.__init__(self, 'org.freedesktop.MediaPlayer')
260+
261+
262+class Clementine(MPRISPlayer):
263+
264+ def __init__(self):
265+ MPRISPlayer.__init__(self, 'org.mpris.clementine')
266+
267+ def is_available(self):
268+ return player_available('clementine')
269+
270+ def start(self):
271+ return launch_player('clementine')
272+
273+ def previous(self):
274+ self.player.Prev()
275+ # We have to emit song changed signal ourselves (Clementine 0.5)
276+ self.song_changed_emitter()
277+
278+ def next(self):
279+ self.player.Next()
280+ # We have to emit song changed signal ourselves (Clementine 0.5)
281+ self.song_changed_emitter()
282+
283+
284+class Guayadeque(MPRISPlayer):
285+
286+ def __init__(self):
287+ MPRISPlayer.__init__(self, 'org.mpris.guayadeque')
288+
289+ def dbus_driver(self):
290+ bus_obj = dbus.SessionBus().get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
291+ if bus_obj.NameHasOwner(self.dbus_base_name) == True:
292+ self.session_bus = dbus.SessionBus()
293+ self.proxy_obj = self.session_bus.get_object(self.dbus_base_name, '/Player')
294+ self.player = dbus.Interface(self.proxy_obj, 'org.freedesktop.MediaPlayer')
295+ self.player.connect_to_signal('TrackChange', self.song_changed_emitter, member_keyword='member')
296+ self.player.connect_to_signal('StatusChange', self.playing_changed_emitter)
297+
298+ def is_available(self):
299+ return player_available('guayadeque')
300+
301+ def start(self):
302+ return launch_player('guayadeque')

Subscribers

People subscribed via source and target branches