summaryrefslogtreecommitdiff
path: root/plugins/Lyrics.py
blob: 518940d64cc8ce990ab077a3797fe203d4cb325a (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
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
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 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=\
    'absolutelyrics.com <div id="realText">(.*?)</div>\n'\
    'azlyrics.com   <br><br>.*?<br><br>(.*?)<br><br>\n'\
    'oldielyrics.com    song_in_top2.*?<br>(.*?)<script\n'\
    'lyricstime.com phone-left.gif.*?<p>(.*?)</p>\n'\
    'lyricsfire.com class="lyric">.*?Song.*?<br>(.*?)</pre>\n'\
    'lyricsfreak.com    <div.*?id="content".*?>(.*?)<blockquote\n'\
    'artists.letssingit.com <pre>(.*?)</pre>\n'\
    'gugalyrics.com </h1>(.*?)<a\n'\
    'lyricsmania.com    </strong> :(.*?)&#91;\n'\
    'leoslyrics.com <font face=.*?size=-1>(.*?)</font>\n'\
    'bluesforpeace.com  <blockquote>(.*?)</blockquote>\n'


LYRICS_DOWNLOADTO_DEFAULT='~/.lyrics/$artist/$artist - $title.txt'
LYRICS_AUTOSCROLL_DEFAULT='1'

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
    monty = None
    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.monty = p.monty
        
        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()))
    
    def autoScroll(self, time):
        t=self.txtView
        max=t.verticalScrollBar().maximum()
        if max<=0:
            return
        t.verticalScrollBar().setValue((max+t.height()/t.currentFont().pointSize())*time/monty.getCurrentSong()._data['time'])
        


class pluginLyrics(Plugin):
    o=None
    monty = 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)
        self.addMontyListener('onTimeChange', self.onTimeChange)
        self.monty = winMain.monty
    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 onTimeChange(self, params):
        if self.getSetting("autoScroll")!="0" and self.o.editMode==False:
            # could be done better, but this is just plain simple :)
            self.o.autoScroll(params['newTime'])


    def _getSettings(self):
        sites=QtGui.QTextEdit()
        sites.insertPlainText(self.getSetting('sites'))
        autoscroll=QtGui.QCheckBox("Autoscroll")
        autoscroll.setCheckState(QtCore.Qt.Checked if self.getSetting("autoScroll")!=0 else QtCore.Qt.Unchecked)
        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'))],
            ['autoScroll', 'Autoscroll', 'Check this box to scroll the contents automatically as the time advances', autoscroll],
        ]
    def afterSaveSettings(self):
        self.o.refresh()