summaryrefslogtreecommitdiff
path: root/plugins/AlbumCover.py
blob: 943dcebeca63e0b0c3d5ea731e6e3c1028be1b96 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
from clPlugin import *
from traceback import print_exc
from thread import start_new_thread
import format

# FETCH MODES
AC_NO_FETCH='0'
AC_FETCH_LOCAL_DIR='1'
AC_FETCH_INTERNET='2'

AC_DEFAULT_GFX_EXTS='jpg,jpeg,bmp,gif,png'
AC_DEFAULT_FILES='cover,album'
AC_DEFAULT_DOWNTO='$dirname($file)/$cover'

class wgAlbumCover(QtGui.QWidget):
	" container for the image"
	img=None
	imgLoaded=False
	p=None	# plugin
	acFormat=None
	def __init__(self,parent=None):
		QtGui.QWidget.__init__(self,parent)
		self.img=QtGui.QImage()
		self.setMinimumSize(64,64)
	
	def mousePressEvent(self, event):
		self.refresh()
	
	def getIMG(self):
		if self.imgLoaded:
			return self.img
		return None

	def paintEvent(self, event):
		l=min(self.width(),self.height())
		p=QtGui.QPainter(self)
		rect=QtCore.QRectF((self.width()-l)/2,0,l,l)
		p.drawImage(rect,self.img)

	def refresh(self):
		self.p.extended("refreshing cover")
		song=monty.getCurrentSong()
		try:
			song._data['file']
		except:
			self.img.load('')
			self.update()
			return
		start_new_thread(self.fetchCover, (song,))
	
	def fetchCover(self, song):
		# set default cover, in case all else fails!
		self.imgLoaded=False
		self.img.load('gfx/no-cd-cover.png')
		self.acFormat=format.compile(settings.get('albumcover.downloadto', AC_DEFAULT_DOWNTO))
		for i in xrange(2):
			src=settings.get('albumcover.fetch%i'%(i), str(i))
			if src!=AC_NO_FETCH and self.fetchCoverSrc(song, src):
				# ACK!
				self.imgLoaded=True
				break
		self.update()
	
	def getLocalACPath(self, song, probe, covers, exts):
		"""Get the local path of an albumcover. If $probe, then try covers*exts for existing file."""
		params={'music_dir': mpdSettings.get('music_directory'), 'cover':'%s.%s'%(covers[0], exts[0])}
		if probe:
			self.p.debug("probing ...")
			for cover in covers:
				for ext in exts:
					params['cover']='%s.%s'%(cover, ext)
					path=self.acFormat(format.params(song, params))
					self.p.debug("  path: %s"%(path))
					fInfo=QtCore.QFileInfo(path)
					if fInfo.exists():
						self.p.debug("  OK!")
						return path
			self.p.debug("done probing: no matching albumcover found")
			return None
		else:
			self.p.debug("no probing")
			path=self.acFormat(format.params(song, params))
			self.p.debug("  path: %s"%(path))
			return path

	
	def fetchCoverSrc(self, song, src):
		"""Fetch the album cover for $song from $src."""
		if not src in [AC_FETCH_INTERNET, AC_FETCH_LOCAL_DIR]:
			print "wgAlbumCover::fetchCover - invalid source "+str(src)
			return False
		
		# fetch gfx extensions
		exts=settings.get('albumcover.gfx.ext', AC_DEFAULT_GFX_EXTS).split(',')
		exts=map(lambda ext: ext.strip(), exts)
		
		# fetch cover album titles
		coverTitles=settings.get('albumcover.files', AC_DEFAULT_FILES).split(',')
		coverTitles=map(lambda title: title.strip(), coverTitles)
		
		if src==AC_FETCH_INTERNET:
			# look on the internetz!
			try:
				if not song.getArtist() or not song.getAlbum():
					return False
				# get the url from amazon WS
				coverURL=AmazonAlbumImage(song.getArtist(), song.getAlbum()).fetch()
				self.p.extended("fetch from Amazon")
				if not coverURL:
					self.p.normal("not found on Amazon")
					return False
				# read the url, i.e. retrieve image data
				img=urllib.urlopen(coverURL)
				# where do we save to?
				file=self.getLocalACPath(song, False, coverTitles, exts)
				# open file, and write the read of img!
				f=open(file,'wb')
				f.write(img.read())
				f.close()

				# load image; should work now!
				self.img.load(file)
				return True
			except:
				self.p.normal("failed to download cover from Amazon")
				print_exc()
				return False
		
		file=self.getLocalACPath(song, True, coverTitles, exts)
		if file:
			try:
				self.img.load(file)
				self.p.extended("cover set!")
				return True
			except:
				self.p.normal("failed to load %s"%(path))


class pluginAlbumCover(Plugin):
	o=None
	def __init__(self, winMain):
		Plugin.__init__(self, winMain, 'AlbumCover')
		self.addMontyListener('onSongChange', self.onEvent)
		self.addMontyListener('onReady', self.onEvent)
		self.addMontyListener('onDisconnect', self.onEvent)
		self.addMontyListener('onStateChange', self.onEvent)
		
	def _load(self):
		self.o=wgAlbumCover(None)
		self.o.p=self
		self.o.refresh()
	def _unload(self):
		self.o=None
	def getInfo(self):
		return "Display the album cover of the currently playing album."
	def getExtInfo(self):
		return "Displays the album cover of the currently playing album in a widget.\n" \
				"This album cover can be fetched from various locations:\n" \
				"  local dir: the directory in which the album is located;\n" \
				"  internet: look on amazon for the album and corresponding cover\n" \
				"Settings:\n" \
				"  albumcover.fetch$i: what source to fetch from on step $i. If step $i fails, move on to step $i+1;\n" \
				"  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" \
				"  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" 

	def getWidget(self):
		return self.o

	def _getDockWidget(self):
		return self._createDock(self.o)

	def onEvent(self, params):
		self.o.refresh()

	def _getSettings(self):
		ret=[]
		nums=['first', 'second']
		actions=[QtGui.QComboBox(), QtGui.QComboBox()]
		for i,action in enumerate(actions):
			setting='albumcover.fetch%i'%(i)
			action.addItem("On the %s action, Monty rested."%(nums[i]))
			action.addItem("Local dir")
			action.addItem("Internet")
			action.setCurrentIndex(int(settings.get(setting, str(i+1))))
			ret.append([setting, 'Action %i'%(i+1), 'What to do on the %s step.'%(nums[i]), action])

		ret.append(['albumcover.downloadto', 'Local dir', 'Specifies where to save album covers fetched from internet to.\nPossible tags: music_dir, cover, file, artist, title, album', QtGui.QLineEdit(settings.get('albumcover.downloadto', AC_DEFAULT_DOWNTO))])
		ret.append(['albumcover.files', 'Album cover files', 'Comma separated list of titles that are to be considered album covers. E.g. cover,album.', QtGui.QLineEdit(settings.get('albumcover.files', AC_DEFAULT_FILES))])
		
		return ret			
	
	def afterSaveSettings(self):
		self.o.refresh()


# This is the amazon cover fetcher using their webservice api
# Thank you, http://www.semicomplete.com/scripts/albumcover.py
import re
import urllib

AMAZON_AWS_ID = "0K4RZZKHSB5N2XYJWF02"

class AmazonAlbumImage(object):
	awsurl = "http://ecs.amazonaws.com/onca/xml"
	def __init__(self, artist, album):
		self.artist = artist
		self.album = album
	
	def fetch(self):
		url = self._GetResultURL(self._SearchAmazon())
		if not url:
			return None
		img_re = re.compile(r'''registerImage\("original_image", "([^"]+)"''')
		try:
			prod_data = urllib.urlopen(url).read()
		except:
			self.important("timeout opening %s"%(url))
			return None
		m = img_re.search(prod_data)
		if not m:
		  return None
		img_url = m.group(1)
		return img_url

	def _SearchAmazon(self):
		data = {
		  "Service": "AWSECommerceService",
		  "Version": "2005-03-23",
		  "Operation": "ItemSearch",
		  "ContentType": "text/xml",
		  "SubscriptionId": AMAZON_AWS_ID,
		  "SearchIndex": "Music",
		  "ResponseGroup": "Small",
		}
		
		data["Artist"] = self.artist
		data["Keywords"] = self.album
		
		try:
			fd = urllib.urlopen("%s?%s" % (self.awsurl, urllib.urlencode(data)))
			return fd.read()
		except:
			# this is very probable a timeout exception
			self.important("timeout openening %s"%(self.awsurl))
		return None


	def _GetResultURL(self, xmldata):
		if not xmldata:
			return None
		url_re = re.compile(r"<DetailPageURL>([^<]+)</DetailPageURL>")
		m = url_re.search(xmldata)
		return m and m.group(1)