summaryrefslogtreecommitdiff
path: root/plugins/Lyrics.py
blob: 97c3c7171388ce7a8d9361737d1c0dc29103e1d9 (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
254
255
256
257
258
259
260
from PyQt4 import QtGui,QtCore

import re
import os
import os.path
from thread import start_new_thread
from traceback import print_exc
import webbrowser
import urllib

from misc import *
from clMonty import monty
from clPlugin import *

class ResetEvent(QtCore.QEvent):
	song=None
	def __init__(self, song=None):
		QtCore.QEvent.__init__(self,QtCore.QEvent.User)
		self.song=song
class AddHtmlEvent(QtCore.QEvent):
	html=None
	def __init__(self,html):
		QtCore.QEvent.__init__(self,QtCore.QEvent.User)
		self.html=html
class AddTextEvent(QtCore.QEvent):
	text=None
	def __init__(self,text):
		QtCore.QEvent.__init__(self,QtCore.QEvent.User)
		self.text=text

LYRICS_ENGINE_DEFAULT='http://www.google.com/search?q=lyrics+"$artist"+"$title"'
LYRICS_SITES_DEFAULT='azlyrics.com	<br><br>.*?<br><br>(.*?)<br><br>\n'\
	'oldielyrics.com	song_in_top2.*?<br><br>(.*?)<script\n'\
	'lyricstime.com	phone-left.gif.*?<p>(.*?)</p>\n'\
	'lyricsfire.com	class="lyric">.*?Song.*?<br>(.*?)</pre>\n'\
	'lyricsfreak.com	<div.*?id="content".*?>(.*?)</div>\n'
LYRICS_DOWNLOADTO_DEFAULT='~/.lyrics/$artist/$artist - $title.txt'

class wgLyrics(QtGui.QWidget):
	" contains the lyrics"
	txtView=None	# text-object
	curLyrics=None	# current lyrics
	btnEdit=None
	btnRefetch=None
	btnSave=None
	btnSearch=None
	editMode=False
	lyFormat=None
	p=None	# plugin
	def __init__(self, p, parent=None):
		QtGui.QWidget.__init__(self, parent)
		self.p=p
		self.curLyrics=""
		self.btnEdit=Button("Edit lyrics", self.onBtnEditClick)
		self.btnRefetch=Button("Refetch", self.onBtnRefetchClick)
		self.btnSave=Button("Save lyrics", self.onBtnSaveClick)
		self.btnSearch=Button("Search www", self.onBtnSearch)
		
		self.txtView=QtGui.QTextEdit(parent)
		self.txtView.setReadOnly(True)
		
		self.setMode(False)
		
		layout=QtGui.QVBoxLayout()
		hlayout=QtGui.QHBoxLayout()
		layout.addLayout(hlayout)
		hlayout.addWidget(self.btnEdit)
		hlayout.addWidget(self.btnSave)
		hlayout.addWidget(self.btnRefetch)
		hlayout.addWidget(self.btnSearch)
		layout.addWidget(self.txtView)
		self.setLayout(layout)

	
	def setMode(self, mode):
		self.editMode=mode
		self.btnSave.setVisible(mode)
		self.btnEdit.setVisible(not mode)
		self.txtView.setReadOnly(not mode)
		
	
	def onBtnSearch(self):
		SE=self.p.getSetting('engine')
		f=format.compile(SE)
		SE_url=toAscii(f(format.params(monty.getCurrentSong())))
		webbrowser.open(urllib.quote(SE_url, ":/+?="))
	
	def onBtnEditClick(self):
		self.setMode(True)
		self.txtView.setPlainText(self.curLyrics)
	
	def onBtnSaveClick(self):
		self.setMode(False)
		self.curLyrics=self.txtView.toPlainText()
		file=open(self.getLyricsFilePath(monty.getCurrentSong()), 'w')
		file.write(self.curLyrics)
		file.close()
		
		# just to test if everything's still fine
		self.fetchLyrics(monty.getCurrentSong())
			
	def onBtnRefetchClick(self):
		# force refetch
		lyFName=self.getLyricsFilePath(monty.getCurrentSong())
		try:
			os.remove(lyFName)
		except:
			pass
		self.refresh()
	
	def refresh(self):
		# if we're editing while song changes, too bad: we don't save, for the moment!
		if self.editMode:
			self.setMode(False)
		
		self.lyFormat=format.compile(self.p.getSetting('downloadto'))
		song=monty.getCurrentSong()
		try:
			song._data['file']
		except:
			self.resetTxt()
			return
		
		self.resetTxt(song)
		start_new_thread(self.fetchLyrics, (song,))
	
	def customEvent(self, event):
		if isinstance(event,ResetEvent):
			self.resetTxt(event.song)
		elif isinstance(event,AddTextEvent):
			self.txtView.insertPlainText(event.text)
		elif isinstance(event,AddHtmlEvent):
			self.txtView.insertHtml(event.html)

	def getLyricsFilePath(self, song):
		params={'music_dir': mpdSettings.get('music_directory')}
		path=self.lyFormat(format.params(song, params))
		return toAscii(os.path.expanduser(path))
	
	_mutex=QtCore.QMutex()
	_fetchCnt=0
	def fetchLyrics(self, song):
		# only allow 1 instance to look lyrics!
		self._mutex.lock()
		if self._fetchCnt:
			self._mutex.unlock()
			return
		self._fetchCnt=1
		self._mutex.unlock()

		QtCore.QCoreApplication.postEvent(self, ResetEvent(song))
		
		lyFName=self.getLyricsFilePath(song)
		self.p.debug("checking %s"%(lyFName))
		self.curLyrics=""
		# does the file exist? if yes, read that one!
		try:
			file=open(lyFName, 'r')
			self.curLyrics=file.read()
			file.close()
			QtCore.QCoreApplication.postEvent(self, AddTextEvent(self.curLyrics))
			self._fetchCnt=0
			self.p.extended("Fetch lyrics from file")
			return
		except Exception, e:
			self.p.debug("fail - %s"%(str(e)))

		# fetch from inet
		QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('<i>Searching lyrics ...</i>'))
		self.p.extended("Fetch lyrics from internet")
		
		lines=self.p.getSetting('sites').split('\n')
		sites={}
		for line in lines:
			if line.strip():
				sites[line[0:line.find('\t')]]=line[line.find('\t'):].strip()
		# construct URL to search!
		SE=self.p.getSetting('engine')
		try:
			ret=fetch(SE, sites, song, {})
			QtCore.QCoreApplication.postEvent(self, ResetEvent(song))
			if ret:
				self.p.extended("Success!")
				self.curLyrics=ret[0]
				# save for later use!
				if lyFName:
					# we can't save if the path isn't correct
					try:
						self.p.extended("Saving to %s"%(lyFName))
						try:
							# fails when dir exists
							os.makedirs(os.path.dirname(os.path.expanduser(lyFName)))
						except Exception, e:
							pass
						file=open(lyFName, 'w')
						file.write(self.curLyrics)
						file.close()
					except Exception, e:
						# probably a wrong path!
						self.p.normal("Failed to write lyrics %s"%(str(e)))
			else:
				self.curLyrics=""
				txt="No lyrics found :'("
				self.p.extended("Lyrics not found!")
				QtCore.QCoreApplication.postEvent(self, AddTextEvent(txt))
				
			QtCore.QCoreApplication.postEvent(self, AddTextEvent(self.curLyrics))
			if ret:
				QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('<br /><br /><a href="%s">%s</a>'%(ret[1],ret[1])))

		except Exception, e:
			QtCore.QCoreApplication.postEvent(self, ResetEvent(song))
			QtCore.QCoreApplication.postEvent(self, AddHtmlEvent('Woops, error! Possible causes:'\
					'<br />no internet connection?'\
					'<br />site unavailable'\
					'<br />an error in the fetching regular expression'\
					'<br />(exception: %s)'%(str(e))))
		self._fetchCnt=0

	def resetTxt(self, song=None):
		self.txtView.clear()
		if song:
			self.txtView.insertHtml('<b>%s</b>\n<br /><u>%s</u><br />'\
					'<br />\n\n'%(song.getTitle(), song.getArtist()))


class pluginLyrics(Plugin):
	o=None
	def __init__(self, winMain):
		Plugin.__init__(self, winMain, 'Lyrics')
		self.addMontyListener('onSongChange', self.refresh)
		self.addMontyListener('onReady', self.refresh)
		self.addMontyListener('onDisconnect', self.onDisconnect)
	def _load(self):
		self.o=wgLyrics(self, None)
		self.o.refresh()
	def _unload(self):
		self.o=None
	def getInfo(self):
		return "Show (and fetch) the lyrics of the currently playing song."
	
	def _getDockWidget(self):
		return self._createDock(self.o)

	def refresh(self, params):
		self.o.refresh()
	def onDisconnect(self, params):
		self.o.resetTxt()


	def _getSettings(self):
		sites=QtGui.QTextEdit()
		sites.insertPlainText(self.getSetting('sites'))
		return [
			['engine', 'Search engine', 'The URL that is used to search. $artist, $title and $album are replaced in the URL.', QtGui.QLineEdit(self.getSetting('engine'))],
			['sites', 'Sites & regexes', 'This field contains all sites, together with the regex needed to fetch the lyrics.\nEvery line must look like this: $domain $regex-start(.*?)$regex-end\n$domain is the domain of the lyrics website, $regex-start is the regex indicating the start of the lyrics, $regex-end indicates the end. E.g. foolyrics.org <lyrics>(.*?)</lyrics>', sites],
			['downloadto', 'Target', 'Specifies where to save lyrics fetched from internet to.\nPossible tags: music_dir, file, artist, title, album', QtGui.QLineEdit(self.getSetting('downloadto'))],
		]
	def afterSaveSettings(self):
		self.o.refresh()