1 from PyQt4
import QtGui
, QtCore
2 from PyQt4
.QtCore
import QVariant
3 from traceback
import print_exc
8 from clPlugin
import Plugin
9 from misc
import ORGNAME
, APPNAME
13 AC_FETCH_LOCAL_DIR
= 1
16 class wgAlbumCover(QtGui
.QLabel
):
17 " container for the image"
24 def __init__(self
, p
, parent
=None):
25 QtGui
.QWidget
.__init
__(self
,parent
)
27 self
.setAlignment(QtCore
.Qt
.AlignCenter
)
30 self
.menu
= QtGui
.QMenu("album")
31 select_file_action
= self
.menu
.addAction('Select cover file...')
32 self
.connect(select_file_action
, QtCore
.SIGNAL('triggered()'), self
.select_cover_file
)
33 fetch_amazon_action
= self
.menu
.addAction('Fetch cover from Amazon.')
34 self
.connect(fetch_amazon_action
, QtCore
.SIGNAL('triggered()'), self
.fetch_amazon
)
36 def mousePressEvent(self
, event
):
37 if event
.button()==QtCore
.Qt
.RightButton
:
38 self
.menu
.popup(event
.globalPos())
40 def select_cover_file(self
):
42 song
= self
.p
.monty
.getCurrentSong()
43 file = QtGui
.QFileDialog
.getOpenFileName(self
44 , "Select album cover for %s - %s"%(song
.getArtist(), song
.getAlbum())
49 shutil
.copy(file, self
.cover_filepath
)
53 logging
.info("Error setting cover file.")
61 def refresh(self
, params
= None):
62 logging
.info("refreshing cover")
63 song
= self
.p
.monty
.getCurrentSong()
69 dirname
= unicode(self
.p
.settings
.value(self
.p
.getName() + '/coverdir').toString())
70 self
.cover_dirname
= dirname
.replace('$musicdir', self
.p
.settings
.value('MPD/music_dir').toString()).replace('$songdir', os
.path
.dirname(song
.getFilepath()))
71 filebase
= unicode(self
.p
.settings
.value(self
.p
.getName() + '/covername').toString())
72 self
.cover_filepath
= os
.path
.join(self
.cover_dirname
, song
.expand_tags(filebase
).replace(os
.path
.sep
, '_'))
75 def fetchCover(self
, song
):
76 """Fetch cover (from internet or local dir)"""
79 if not os
.path
.exists(self
.cover_filepath
):
82 src
= self
.p
.settings
.value(self
.p
.getName() + '/action%i'%i).toInt()[0]
83 if src
!= AC_NO_FETCH
:
84 if self
.fetchCoverSrc(song
, src
):
88 self
.imgLoaded
= False
89 self
.setPixmap(QtGui
.QPixmap('gfx/no-cd-cover.png').scaled(self
.size(), QtCore
.Qt
.KeepAspectRatio
,
90 QtCore
.Qt
.SmoothTransformation
))
94 self
.setPixmap(QtGui
.QPixmap(self
.cover_filepath
).scaled(self
.size(), QtCore
.Qt
.KeepAspectRatio
,
95 QtCore
.Qt
.SmoothTransformation
))
97 logging
.info("cover set!")
99 logging
.warning("Error loading album cover" + self
.cover_filepath
)
103 def getLocalACPath(self
, song
):
104 """Get the local path of an albumcover."""
105 covers
= ['cover', 'album', 'front']
107 # fetch gfx extensions
108 exts
= QtGui
.QImageReader().supportedImageFormats()
109 exts
= map(lambda ext
: '*.' + unicode(ext
), exts
)
111 # fetch cover album titles
115 filter.append(cover
.strip() + ext
)
117 dir = QtCore
.QDir(self
.cover_dirname
)
119 logging
.warning('Error opening directory' + self
.cover_dirname
)
121 dir.setNameFilters(filter)
122 files
= dir.entryList()
124 return unicode(dir.filePath(files
[0]))
125 # if this failed, try any supported image
126 dir.setNameFilters(exts
)
127 files
= dir.entryList()
129 return unicode(dir.filePath(files
[0]))
130 logging
.info("done probing: no matching albumcover found")
133 def fetch_amazon(self
):
134 self
.fetchCoverSrc(self
.p
.monty
.getCurrentSong(), AC_FETCH_INTERNET
)
137 def fetchCoverSrc(self
, song
, src
):
138 """Fetch the album cover for $song from $src."""
139 if not src
in [AC_FETCH_INTERNET
, AC_FETCH_LOCAL_DIR
]:
140 logging
.warning("wgAlbumCover::fetchCover - invalid source "+str(src
))
143 if src
== AC_FETCH_INTERNET
:
144 # look on the internetz!
146 if not song
.getArtist() or not song
.getAlbum():
148 # get the url from amazon WS
149 coverURL
=AmazonAlbumImage(song
.getArtist(), song
.getAlbum()).fetch()
150 logging
.info("fetch from Amazon")
152 logging
.info("not found on Amazon")
154 # read the url, i.e. retrieve image data
155 img
=urllib
.urlopen(coverURL
)
156 # open file, and write the read of img!
157 f
=open(self
.cover_filepath
,'wb')
162 logging
.info("failed to download cover from Amazon")
166 if src
== AC_FETCH_LOCAL_DIR
:
167 file=self
.getLocalACPath(song
)
169 shutil
.copy(file, self
.cover_filepath
)
172 logging
.info("Failed to create cover file")
175 class pluginAlbumCover(Plugin
):
177 DEFAULTS
= {'coverdir' : '$musicdir/$songdir', 'covername' : '.cover_monty_$artist_$album',
178 'action0' : 1, 'action1' : 1}
179 def __init__(self
, winMain
):
180 Plugin
.__init
__(self
, winMain
, 'AlbumCover')
183 self
.o
= wgAlbumCover(self
, None)
184 self
.monty
.add_listener('onSongChange' , self
.o
.refresh
)
185 self
.monty
.add_listener('onReady' , self
.o
.refresh
)
186 self
.monty
.add_listener('onDisconnect' , self
.o
.refresh
)
187 self
.monty
.add_listener('onStateChange', self
.o
.refresh
)
192 return "Display the album cover of the currently playing album."
193 def getExtInfo(self
):
194 return "Displays the album cover of the currently playing album in a widget.\n" \
195 "This album cover can be fetched from various locations:\n" \
196 " local dir: the directory in which the album is located;\n" \
197 " internet: look on amazon for the album and corresponding cover\n" \
199 " albumcover.fetch$i: what source to fetch from on step $i. If step $i fails, move on to step $i+1;\n" \
200 " albumcover.downloadto: where to download album covers from internet to. This string can contain the normal tags of the current playing song, plus $music_dir and $cover.\n" \
201 " albumcover.files: comma separated list of filenames (without extension)to be considered an album cover. Extensions jpg, jpeg, png, gif and bmp are used.\n"
206 def _getDockWidget(self
):
207 return self
._createDock
(self
.o
)
209 class SettingsWidgetAlbumCover(Plugin
.SettingsWidget
):
214 def __init__(self
, plugin
):
215 Plugin
.SettingsWidget
.__init
__(self
, plugin
)
216 self
.settings
.beginGroup(self
.plugin
.getName())
218 self
.actions
= [QtGui
.QComboBox(), QtGui
.QComboBox()]
219 for i
,action
in enumerate(self
.actions
):
220 action
.addItem("No action.")
221 action
.addItem("Local dir")
222 action
.addItem("Amazon")
223 action
.setCurrentIndex(self
.settings
.value('action' + str(i
)).toInt()[0])
225 self
.coverdir
= QtGui
.QLineEdit(self
.settings
.value('coverdir').toString())
226 self
.covername
= QtGui
.QLineEdit(self
.settings
.value('covername').toString())
228 self
.setLayout(QtGui
.QVBoxLayout())
229 self
._add
_widget
(self
.actions
[0], 'Action 0')
230 self
._add
_widget
(self
.actions
[1], 'Action 1')
231 self
._add
_widget
(self
.coverdir
, 'Cover directory',
232 'Where should %s store covers.\n'
233 '$musicdir will be expanded to path to MPD music library\n'
234 '$songdir will be expanded to path to the song (relative to $musicdir'
236 self
._add
_widget
(self
.covername
, 'Cover filename', 'Filename for %s cover files.'%APPNAME
)
237 self
.settings
.endGroup()
239 def save_settings(self
):
240 self
.settings
.beginGroup(self
.plugin
.getName())
241 self
.settings
.setValue('action0', QVariant(self
.actions
[0].currentIndex()))
242 self
.settings
.setValue('action1', QVariant(self
.actions
[1].currentIndex()))
243 self
.settings
.setValue('coverdir', QVariant(self
.coverdir
.text()))
244 self
.settings
.setValue('covername', QVariant(self
.covername
.text()))
245 self
.settings
.endGroup()
246 self
.plugin
.o
.refresh()
248 def get_settings_widget(self
):
249 return self
.SettingsWidgetAlbumCover(self
)
252 # This is the amazon cover fetcher using their webservice api
253 # Thank you, http://www.semicomplete.com/scripts/albumcover.py
257 AMAZON_AWS_ID
= "0K4RZZKHSB5N2XYJWF02"
259 class AmazonAlbumImage(object):
260 awsurl
= "http://ecs.amazonaws.com/onca/xml"
261 def __init__(self
, artist
, album
):
266 url
= self
._GetResultURL
(self
._SearchAmazon
())
269 img_re
= re
.compile(r
'''registerImage\("original_image", "([^"]+)"''')
271 prod_data
= urllib
.urlopen(url
).read()
273 self
.important("timeout opening %s"%(url))
275 m
= img_re
.search(prod_data
)
281 def _SearchAmazon(self
):
283 "Service": "AWSECommerceService",
284 "Version": "2005-03-23",
285 "Operation": "ItemSearch",
286 "ContentType": "text/xml",
287 "SubscriptionId": AMAZON_AWS_ID
,
288 "SearchIndex": "Music",
289 "ResponseGroup": "Small",
292 data
["Artist"] = self
.artist
293 data
["Keywords"] = self
.album
295 fd
= urllib
.urlopen("%s?%s" % (self
.awsurl
, urllib
.urlencode(data
)))
299 def _GetResultURL(self
, xmldata
):
302 url_re
= re
.compile(r
"<DetailPageURL>([^<]+)</DetailPageURL>")
303 m
= url_re
.search(xmldata
)
304 return m
and m
.group(1)