2 # Copyright (C) 2009 Anton Khirnov <wyskas@gmail.com>
4 # Nephilim is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # Nephilim is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Nephilim. If not, see <http://www.gnu.org/licenses/>.
18 from PyQt4
import QtGui
, QtCore
19 from PyQt4
.QtCore
import QVariant
23 from ..plugin
import Plugin
30 from lxml
import etree
31 _available_sites
.append('lyricwiki')
32 _available_sites
.append('animelyrics')
34 print 'Lyrics: error importing LyricWiki. Make sure that lxml is installed.'
36 class wgLyrics(QtGui
.QWidget
):
37 txtView
= None # text-object
40 def __init__(self
, p
, parent
=None):
41 QtGui
.QWidget
.__init
__(self
, parent
)
43 self
.logger
= p
.logger
46 self
.txtView
= QtGui
.QTextEdit(self
)
47 self
.txtView
.setReadOnly(True)
49 self
.setLayout(QtGui
.QVBoxLayout())
50 self
.layout().setSpacing(0)
51 self
.layout().setMargin(0)
52 self
.layout().addWidget(self
.txtView
)
54 self
.connect(self
.p
, QtCore
.SIGNAL('new_lyrics_fetched'), self
.set_lyrics
)
56 def set_lyrics(self
, song
, lyrics
, flags
= 0):
58 return self
.txtView
.clear()
60 if song
!= self
.p
.mpclient
.current_song():
65 self
.txtView
.insertHtml('<b>%s</b>\n<br /><u>%s</u><br />'\
66 '<br />\n\n'%(song
.title(), song
.artist()))
71 self
.logger
.info('Setting new lyrics.')
72 self
.txtView
.insertPlainText(lyrics
.decode('utf-8'))
74 self
.logger
.info('Lyrics not found.')
75 self
.txtView
.insertPlainText('Lyrics not found.')
83 DEFAULTS
= {'sites' : QtCore
.QStringList(['lyricwiki', 'animelyrics']), 'lyricdir' : '$musicdir/$songdir',
84 'lyricname' : '.lyrics_nephilim_$artist_$album_$title', 'store' : True}
87 self
.o
= wgLyrics(self
)
88 for site
in _available_sites
:
89 if site
in self
.settings().value('%s/sites'%self
.name
).toStringList():
90 self
.sites
.append(site
)
91 self
.connect(self
.mpclient
, QtCore
.SIGNAL('song_changed'), self
.refresh
)
95 self
.disconnect(self
.mpclient
, QtCore
.SIGNAL('song_changed'), self
.refresh
)
97 return "Show (and fetch) the lyrics of the currently playing song."
99 def _get_dock_widget(self
):
100 return self
._create
_dock
(self
.o
)
102 class FetchThread(QtCore
.QThread
):
103 def __init__(self
, parent
, fetch_func
, song
):
104 QtCore
.QThread
.__init
__(self
)
105 self
.setParent(parent
)
106 self
.fetch_func
= fetch_func
109 self
.fetch_func(self
.song
)
112 self
.logger
.info('Autorefreshing lyrics.')
113 song
= self
.mpclient
.current_song()
115 return self
.o
.set_lyrics(None, None)
117 (self
.lyrics_dir
, self
.lyrics_path
) = misc
.generate_metadata_path(song
,
118 self
.settings().value(self
.name
+ '/lyricdir').toString(),
119 self
.settings().value(self
.name
+ '/lyricname').toString())
121 self
.logger
.info('Trying to read lyrics from file %s.'%self
.lyrics_path
)
122 file = open(self
.lyrics_path
, 'r')
126 return self
.emit(QtCore
.SIGNAL('new_lyrics_fetched'), song
, lyrics
)
128 self
.logger
.info('Error reading lyrics file: %s.'%e)
131 thread
= self
.FetchThread(self
, self
._fetch
_lyrics
, song
)
134 def _fetch_lyrics(self
, song
):
135 self
.logger
.info('Trying to download lyrics from internet.')
137 for site
in self
.sites
:
138 self
.logger
.info('Trying %s.'%site
)
139 lyrics
= eval('self.fetch_%s(song)'%site
)
142 file = open(self
.lyrics_path
, 'w')
146 self
.logger
.error('Error saving lyrics: %s'%e)
147 return self
.emit(QtCore
.SIGNAL('new_lyrics_fetched'), song
, lyrics
)
149 self
.emit(QtCore
.SIGNAL('new_lyrics_fetched'), song
, None)
151 def fetch_lyricwiki(self
, song
):
152 url
= 'http://lyricwiki.org/api.php?%s' %urllib
.urlencode({'func':'getSong',
153 'artist':song
.artist().encode('utf-8'), 'song':song
.title().encode('utf-8'),
157 tree
= etree
.HTML(urllib
.urlopen(url
).read())
158 if tree
.find('.//lyrics').text
== 'Not found':
160 #get page with lyrics and change <br> tags to newlines
161 url
= tree
.find('.//url').text
162 page
= re
.sub('<br>|<br/>|<br />', '\n', urllib
.urlopen(url
).read())
163 html
= etree
.HTML(page
)
165 for elem
in html
.iterfind('.//div'):
166 if elem
.get('class') == 'lyricbox':
167 lyrics
+= etree
.tostring(elem
, method
= 'text', encoding
= 'utf-8')
169 except socket
.error
, e
:
170 self
.logger
.error('Error downloading lyrics from LyricWiki: %s.'%e)
173 def fetch_animelyrics(self
, song
):
174 url
= 'http://www.animelyrics.com/search.php?%s'%urllib
.urlencode({'q':song
.artist().encode('utf-8'),
178 self
.logger
.info('Searching Animelyrics: %s.'%url
)
179 tree
= etree
.HTML(urllib
.urlopen(url
).read())
181 for elem
in tree
.iterfind('.//a'):
182 if ('href' in elem
.attrib
) and elem
.text
and (song
.title() in elem
.text
):
183 url
= 'http://www.animelyrics.com/%s'%elem
.get('href')
188 self
.logger
.info('Found song URL: %s.'%url
)
189 tree
= etree
.HTML(urllib
.urlopen(url
).read())
191 for elem
in tree
.iterfind('.//pre'):
192 if elem
.get('class') == 'lyrics':
193 ret
+= '%s\n\n'%etree
.tostring(elem
, method
= 'text', encoding
= 'utf-8')
195 except socket
.error
, e
:
196 self
.logger
.error('Error downloading lyrics from Animelyrics: %s.'%e)
198 except AttributeError:
202 class SettingsWidgetLyrics(Plugin
.SettingsWidget
):
207 def __init__(self
, plugin
):
208 Plugin
.SettingsWidget
.__init
__(self
, plugin
)
209 self
.settings().beginGroup(self
.plugin
.name
)
212 # store lyrics groupbox
213 self
.store
= QtGui
.QGroupBox('Store lyrics.')
214 self
.store
.setToolTip('Should %s store its own copy of lyrics?'%misc
.APPNAME
)
215 self
.store
.setCheckable(True)
216 self
.store
.setChecked(self
.settings().value('store').toBool())
217 self
.store
.setLayout(QtGui
.QGridLayout())
220 self
.lyricdir
= QtGui
.QLineEdit(self
.settings().value('lyricdir').toString())
221 self
.lyricdir
.setToolTip('Where should %s store lyrics.\n'
222 '$musicdir will be expanded to path to MPD music library (as set by user)\n'
223 '$songdir will be expanded to path to the song (relative to $musicdir\n'
224 'other tags same as in lyricname'
226 self
.lyricname
= QtGui
.QLineEdit(self
.settings().value('lyricname').toString())
227 self
.lyricname
.setToolTip('Filename for %s lyricsfiles.\n'
228 'All tags supported by MPD will be expanded to their\n'
229 'values for current song, e.g. $title, $track, $artist,\n'
230 '$album, $genre etc.'%misc
.APPNAME
)
231 self
.store
.layout().addWidget(QtGui
.QLabel('Lyrics directory'), 0, 0)
232 self
.store
.layout().addWidget(self
.lyricdir
, 0, 1)
233 self
.store
.layout().addWidget(QtGui
.QLabel('Lyrics filename'), 1, 0)
234 self
.store
.layout().addWidget(self
.lyricname
, 1, 1)
236 self
.setLayout(QtGui
.QVBoxLayout())
237 self
.layout().addWidget(self
.store
)
239 self
.settings().endGroup()
241 def save_settings(self
):
242 self
.settings().beginGroup(self
.plugin
.name
)
243 self
.settings().setValue('lyricdir', QVariant(self
.lyricdir
.text()))
244 self
.settings().setValue('lyricname', QVariant(self
.lyricname
.text()))
245 self
.settings().setValue('store', QVariant(self
.store
.isChecked()))
246 self
.settings().endGroup()
247 self
.plugin
.refresh()
249 def get_settings_widget(self
):
250 return self
.SettingsWidgetLyrics(self
)