All pastes #569287 Raw Edit

Unnamed

public text v1 · immutable
·published 2007-06-16 03:07 UTC
rendered paste body
Index: configure.ac
===================================================================
--- configure.ac	(revision 5177)
+++ configure.ac	(working copy)
@@ -974,6 +974,7 @@ plugins/mtpdevice/Makefile
 plugins/iradio/Makefile
 plugins/lirc/Makefile
 plugins/lyrics/Makefile
+plugins/lyrics/lyrics/Makefile
 plugins/sample-python/Makefile
 plugins/sample-vala/Makefile
 plugins/pythonconsole/Makefile
Index: data/rhythmbox.schemas
===================================================================
--- data/rhythmbox.schemas	(revision 5177)
+++ data/rhythmbox.schemas	(working copy)
@@ -1192,5 +1192,28 @@
 	<long>True if the media player keys plugin is hidden.</long>
 	</locale>
       </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/plugins/lyrics/engines</key>
+	<applyto>/apps/rhythmbox/plugins/lyrics/engines</applyto>
+	<owner>rhythmbox</owner>
+        <type>list</type>
+        <list_type>string</list_type>
+        <default>[astraweb.com,lyrc.com.ar,leoslyrics.com]</default>
+	<locale name="C">
+	  <short>The list of sites to search for lyrics.</short>
+	  <long>The list of sites to search for lyrics.</long>
+	</locale>
+      </schema>
+      <schema>
+        <key>/schemas/apps/rhythmbox/plugins/lyrics/folder</key>
+	<applyto>/apps/rhythmbox/plugins/lyrics/folder</applyto>
+	<owner>rhythmbox</owner>
+        <type>string</type>
+        <default>~/.lyrics</default>
+	<locale name="C">
+	  <short>The directory to store lyrics in</short>
+	  <long>The directory to store lyrics in</long>
+	</locale>
+      </schema>
     </schemalist>
 </gconfschemafile>
Index: plugins/lyrics/lyrics.rb-plugin.desktop.in
===================================================================
--- plugins/lyrics/lyrics.rb-plugin.desktop.in	(revision 5177)
+++ plugins/lyrics/lyrics.rb-plugin.desktop.in	(working copy)
@@ -4,6 +4,6 @@ Module=lyrics
 IAge=1
 _Name=Song Lyrics
 _Description=Fetch song lyrics from the Internet
-Authors=Jonathan Matthew, Eduardo Gonzalez
-Copyright=Copyright © 2005-2006 Jonathan Matthew, Eduardo Gonzalez
+Authors=Jonathan Matthew, Eduardo Gonzalez, Sirio Bolaños
+Copyright=Copyright © 2005-2007 Jonathan Matthew, Eduardo Gonzalez, Sirio Bolaños
 Website=http://www.rhythmbox.org/
Index: plugins/lyrics/lyrics/__init__.py
===================================================================
--- plugins/lyrics/lyrics/__init__.py	(revision 0)
+++ plugins/lyrics/lyrics/__init__.py	(revision 0)
@@ -0,0 +1,367 @@
+# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
+#
+# Copyright (C) 2006 Jonathan Matthew
+# Copyright (C) 2007 James Livingston
+# Copyright (C) 2007 Sirio Bolaños Puchet
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+#
+# TODO:
+# - check that the lyrics returned even remotely match the request?
+
+import os, re
+import gtk, gtk.glade
+import gconf
+import rhythmdb, rb
+
+import LyricsParse
+from LyricsConfigureDialog import LyricsConfigureDialog
+
+ui_str = """
+<ui>
+  <menubar name="MenuBar">
+	<menu name="ViewMenu" action="View">
+	  <menuitem name="ViewSongLyrics" action="ViewSongLyrics"/>
+	</menu>
+  </menubar>
+</ui>
+"""
+
+LYRIC_TITLE_STRIP=["\(live[^\)]*\)", "\(acoustic[^\)]*\)", "\([^\)]*mix\)", "\([^\)]*version\)", "\([^\)]*edit\)", "\(feat[^\)]*\)", "\([^\)]*\)"]
+LYRIC_TITLE_REPLACE=[("/", "-"), (" & ", " and ")]
+LYRIC_ARTIST_REPLACE=[("/", "-"), (" & ", " and ")]
+
+gconf_keys = {	'engines' : '/apps/rhythmbox/plugins/lyrics/engines',
+		'folder': '/apps/rhythmbox/plugins/lyrics/folder'
+	     }
+
+
+def create_lyrics_view():
+	tview = gtk.TextView()
+	tview.set_wrap_mode(gtk.WRAP_WORD)
+	tview.set_editable(False)
+
+	sw = gtk.ScrolledWindow()
+	sw.add(tview)
+	sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+	sw.set_shadow_type(gtk.SHADOW_IN)
+
+	vbox = gtk.VBox(spacing=12)
+	vbox.pack_start(sw, expand=True)
+	
+	return (vbox, tview.get_buffer(), tview)
+
+def parse_song_data(artist, title):
+	
+	# replace ampersands and the like
+	for exp in LYRIC_ARTIST_REPLACE:
+		artist = re.sub(exp[0], exp[1], artist)
+	for exp in LYRIC_TITLE_REPLACE:
+		title = re.sub(exp[0], exp[1], title)
+
+	# strip things like "(live at Somewhere)", "(accoustic)", etc
+	for exp in LYRIC_TITLE_STRIP:
+		title = re.sub (exp, '', title)
+
+	# compress spaces
+	title = title.strip()
+	artist = artist.strip()	
+	
+	return (artist, title)
+	
+def build_cache_path(artist, title):
+	folder = gconf.client_get_default().get_string(gconf_keys['folder'])
+	if folder is None:
+		return None
+
+	lyrics_folder = os.path.expanduser (folder)
+	if not os.path.exists (lyrics_folder):
+		os.mkdir (lyrics_folder)
+
+	artist_folder = lyrics_folder + '/' + artist[:128]
+	if not os.path.exists (artist_folder):
+		os.mkdir (artist_folder)
+
+	return artist_folder + '/' + title[:128] + '.lyric'
+
+class LyricGrabber(object):
+	def __init__(self, db, entry):
+		self.loader = rb.Loader ()
+		self.db = db
+		self.entry = entry
+		
+		self.artist = self.db.entry_get(self.entry, rhythmdb.PROP_ARTIST).lower()
+		self.title = self.db.entry_get(self.entry, rhythmdb.PROP_TITLE).lower()
+
+		(self.artist, self.title) = parse_song_data(self.artist, self.title)
+
+		self.cache_path = build_cache_path(self.artist, self.title)
+
+	def verify_lyric(self):
+		return os.path.exists(self.cache_path)
+	  
+	def search_lyrics(self, callback, mode=None):
+		self.callback = callback
+		
+		status = self.verify_lyric()
+		
+		if status:
+			self.loader.get_url(self.cache_path, callback)
+		else:
+			if mode is not None:
+				self.callback(_("No lyrics found"))
+			else:
+				def lyric_callback (text):
+					if text is not None:
+						f = file (self.cache_path, 'w')
+						f.write (text)
+						f.close ()
+						self.callback(text)
+					else:
+						self.callback(_("No lyrics found"))
+
+				parser = LyricsParse.Parser(gconf_keys, self.artist, self.title)
+				parser.get_lyrics(lyric_callback)
+
+class LyricPane(object):
+	def __init__(self, db, song_info):
+		self.db = db
+		self.song_info = song_info
+		self.entry = self.song_info.get_property("current-entry")
+		
+		self.build_path()
+		
+		def save_lyrics(cache_path, text):
+			f = file (cache_path, 'w')
+			f.write (text)
+			f.close ()
+		
+		def erase_lyrics(cache_path):
+			f = file (cache_path, 'w')
+			f.write ("")
+			f.close ()
+		
+		def save_callback():
+			buf = self.buffer
+			startiter = buf.get_start_iter()
+			enditer = buf.get_end_iter()
+			text = buf.get_text(startiter, enditer)
+			save_lyrics(self.cache_path, text)
+			self.get_lyrics()
+		
+		def edit_callback(widget):
+			if self.edit.get_active() == 1:
+				self.tview.set_editable(True)
+				self.edit.set_label("_Save")
+			else:
+				if self.cache_path is not None:
+					save_callback()
+				self.tview.set_editable(False)
+				self.edit.set_label("_Edit")
+
+		def discard_callback(widget):
+			if self.cache_path is not None and os.path.exists(self.cache_path):
+				os.remove(self.cache_path)
+			self.get_lyrics()
+		
+		def clear_callback(widget):
+			if self.cache_path is not None and os.path.exists (self.cache_path):
+				erase_lyrics(self.cache_path)
+			self.get_lyrics()
+	   
+
+		self.edit = gtk.ToggleButton("_Edit")
+		self.edit.connect('toggled', edit_callback)
+		self.discard = gtk.Button("_Search again")
+		self.discard.connect('clicked', discard_callback)
+		self.clear = gtk.Button(stock=gtk.STOCK_CLEAR)
+		self.clear.connect('clicked', clear_callback)
+		self.hbox = gtk.HBox()
+		self.hbox.pack_end(self.edit, expand=False)
+		self.hbox.pack_end(self.clear, expand=False)
+		self.hbox.pack_start(self.discard, expand=False)
+		
+		(self.view, self.buffer, self.tview) = create_lyrics_view()
+
+		self.view.pack_start(self.hbox, expand=False, fill=False)
+		self.view.set_spacing(2)
+	
+		self.view.show_all()
+		self.page_num = song_info.append_page(_("Lyrics"), self.view)
+		self.have_lyrics = 0
+		self.visible = 0
+
+		self.entry_change_id = song_info.connect('notify::current-entry', self.entry_changed)
+		nb = self.view.get_parent()
+		self.switch_page_id = nb.connect('switch-page', self.switch_page_cb)
+		
+		#self.get_lyrics()
+
+	def build_path(self):
+
+		artist = self.db.entry_get(self.entry, rhythmdb.PROP_ARTIST).lower()
+		title = self.db.entry_get(self.entry, rhythmdb.PROP_TITLE).lower()
+		(artist, title) = parse_song_data(artist, title)
+		cache_path = build_cache_path(artist, title)
+		self.cache_path = cache_path
+
+	def entry_changed(self, pspec, duh):
+		self.entry = self.song_info.get_property("current-entry")
+		self.have_lyrics = 0
+		if self.visible != 0:
+			self.build_path()
+			self.get_lyrics()
+
+	def switch_page_cb(self, notebook, page, page_num):
+		if self.have_lyrics != 0:
+			return
+
+		if page_num != self.page_num:
+			self.visible = 0
+			return
+
+		self.visible = 1
+		self.get_lyrics()
+
+	def get_lyrics(self):
+		if self.entry is None:
+			return
+
+		self.buffer.set_text(_("Searching for lyrics..."));
+		lyrics_grabber = LyricGrabber(self.db, self.entry)
+		lyrics_grabber.search_lyrics(self.buffer.set_text)
+			
+
+class LyricWindow (gtk.Window):
+
+	def __init__(self):
+		gtk.Window.__init__(self)
+		self.set_border_width(12)
+
+		close = gtk.Button(stock=gtk.STOCK_CLOSE)
+		close.connect('clicked', lambda w: self.destroy())
+	
+		(lyrics_view, buffer, tview) = create_lyrics_view()
+		self.buffer = buffer
+		bbox = gtk.HButtonBox()
+		bbox.set_layout(gtk.BUTTONBOX_END)
+		bbox.pack_start(close)
+		lyrics_view.pack_start(bbox, expand=False)
+	
+		self.add(lyrics_view)
+		self.set_default_size(400, 300)
+		self.show_all()
+
+	def s_title(self, title, artist):
+		self.set_title(title + " - " + artist + " - Lyrics")
+
+class LyricsDisplayPlugin(rb.Plugin):
+
+	def __init__ (self):
+		rb.Plugin.__init__ (self)
+		self.window = None
+
+	def activate (self, shell):
+		self.shell = shell
+		self.action = gtk.Action ('ViewSongLyrics', _('Song L_yrics'),
+					  _('Display lyrics for the playing song'),
+					  'rb-song-lyrics')
+		self.activate_id = self.action.connect ('activate', self.show_song_lyrics, shell)
+		
+		self.action_group = gtk.ActionGroup ('SongLyricsPluginActions')
+		self.action_group.add_action_with_accel (self.action, "<control>L")
+		
+		uim = shell.get_ui_manager ()
+		uim.insert_action_group (self.action_group, 0)
+		self.ui_id = uim.add_ui_from_string (ui_str)
+		uim.ensure_update ()
+
+		sp = shell.get_player ()
+		self.pec_id = sp.connect('playing-song-changed', self.playing_entry_changed)
+		self.current_entry = None
+		self.playing_entry_changed (sp, sp.get_playing_entry ())
+
+		self.csi_id = shell.connect('create_song_info', self.create_song_info)
+
+	def deactivate (self, shell):
+			
+		uim = shell.get_ui_manager()
+		uim.remove_ui (self.ui_id)
+		uim.remove_action_group (self.action_group)
+
+		self.action_group = None
+		self.action = None
+
+		sp = shell.get_player ()
+		sp.disconnect (self.pec_id)
+
+		if self.window is not None:
+			self.window.destroy ()
+			self.window = None
+
+	def create_configure_dialog(self, dialog=None):
+		if not dialog:
+			glade_file = self.find_file("lyrics-prefs.glade")
+			dialog = LyricsConfigureDialog (glade_file, gconf_keys).get_dialog()
+		dialog.present()
+		return dialog
+	
+	def playing_entry_changed (self, sp, entry):
+		if entry is not None:
+			self.action.set_sensitive (True)
+			self.update_song_lyrics(entry)
+		else:
+			self.action.set_sensitive (False)
+
+	def update_song_lyrics(self, entry):
+		if entry == self.current_entry:
+			return
+		
+		db = self.shell.get_property ("db")
+		
+		if self.window is None:
+			return
+
+		title = db.entry_get(entry, rhythmdb.PROP_TITLE)
+		artist = db.entry_get(entry, rhythmdb.PROP_ARTIST)
+
+		self.window.s_title(title, artist)
+		lyrics_grabber = LyricGrabber(db, entry)
+		lyrics_grabber.search_lyrics(self.window.buffer.set_text, True)
+
+	def show_song_lyrics (self, action, shell):
+
+		if self.window is not None:
+			self.window.destroy ()
+
+		db = shell.get_property ("db")
+		sp = shell.get_player ()
+		entry = sp.get_playing_entry ()
+
+		if entry is None:
+			return
+		
+		title = db.entry_get(entry, rhythmdb.PROP_TITLE)
+		artist = db.entry_get(entry, rhythmdb.PROP_ARTIST)
+
+		self.window = LyricWindow()
+		self.window.s_title(title, artist)
+		lyrics_grabber = LyricGrabber(db, entry)
+		lyrics_grabber.search_lyrics(self.window.buffer.set_text)
+	
+	def create_song_info (self, shell, song_info, is_multiple):
+
+		if is_multiple is False:
+			x = LyricPane(shell.get_property ("db"), song_info)
Index: plugins/lyrics/lyrics/LyricsParse.py
===================================================================
--- plugins/lyrics/lyrics/LyricsParse.py	(revision 0)
+++ plugins/lyrics/lyrics/LyricsParse.py	(revision 0)
@@ -0,0 +1,63 @@
+# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
+#
+# Copyright (C) 2007 James Livingston
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+
+
+import urllib
+import re
+import gconf
+import rb
+
+from LyrcParser import LyrcParser
+from AstrawebParser import AstrawebParser
+from LeoslyricsParser import LeoslyricsParser
+
+
+engines_map = {
+	'lyrc.com.ar': LyrcParser,
+	'astraweb.com': AstrawebParser,
+	'leoslyrics.com': LeoslyricsParser
+}
+
+
+class Parser (object):
+	def __init__(self, gconf_keys, artist, title):
+		self.title = title
+		self.artist = artist
+		self.engines = gconf.client_get_default().get_list(gconf_keys['engines'], gconf.VALUE_STRING)
+
+	def searcher(self, plexer, callback, *data):
+		for e in self.engines:
+			plexer.clear()
+			if e in engines_map:
+				parser = engines_map[e] (self.artist, self.title)
+				print "searching " + e + " for lyrics"
+
+				parser.search(plexer.send())
+				yield None
+
+				_, (lyrics,) = plexer.receive()
+				if lyrics is not None:
+					callback (lyrics, *data)
+					return
+
+		callback (None, *data)
+
+	def get_lyrics(self, callback, *data):
+		rb.Coroutine (self.searcher, callback, *data).begin ()
+
+
Index: plugins/lyrics/lyrics/Makefile.am
===================================================================
--- plugins/lyrics/lyrics/Makefile.am	(revision 0)
+++ plugins/lyrics/lyrics/Makefile.am	(revision 0)
@@ -0,0 +1,10 @@
+# Search Lyrics Python Plugin
+
+plugindir = $(PLUGINDIR)/lyrics
+plugin_PYTHON =			\
+       LyricsParse.py			\
+       LyricsConfigureDialog.py		\
+       __init__.py			\
+       LyrcParser.py			\
+       AstrawebParse.py			\
+       LyeoslyricsParser.py
Index: plugins/lyrics/lyrics/AstrawebParser.py
===================================================================
--- plugins/lyrics/lyrics/AstrawebParser.py	(revision 0)
+++ plugins/lyrics/lyrics/AstrawebParser.py	(revision 0)
@@ -0,0 +1,82 @@
+# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
+#
+# Copyright (C) 2007 James Livingston
+# Copyright (C) 2007 Sirio Bolaños Puchet
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+
+import urllib
+import re
+import rb
+
+class AstrawebParser (object):
+	def __init__(self, artist, title):
+		self.artist = artist
+		self.title = title
+		
+	def search(self, callback, *data):
+		wartist = re.sub('%20', '+', urllib.quote(self.artist))
+		wtitle = re.sub('%20', '+', urllib.quote(self.title))
+
+		wurl = 'http://search.lyrics.astraweb.com/?word=%s+%s' % (wartist, wtitle)
+
+		loader = rb.Loader()
+		loader.get_url (wurl, self.got_results, callback, *data)
+
+	def got_results (self, result, callback, *data):
+		if result is None:
+			callback (None, *data)
+			return
+
+		results = re.sub('\n', '', re.sub('\r', '', result))
+
+		if re.search('(<tr><td bgcolor="#BBBBBB".*)(More Songs &gt)', results) is not None:
+			body = re.split('(<tr><td bgcolor="#BBBBBB".*)(More Songs &gt)', results)[1]
+			entries = re.split('<tr><td bgcolor="#BBBBBB"', body)
+			entries.pop(0)
+			for entry in entries:
+				url = re.split('(\/display[^"]*)', entry)[1]
+				artist = re.split('(Artist:.*html">)([^<]*)', entry)[2]
+				title = re.split('(\/display[^>]*)([^<]*)', entry)[2][1:]
+							
+				if not ((re.search(self.title.lower().strip(), title.lower().strip()) is None)):
+					if not (re.search(self.artist.lower().strip(), artist.lower().strip()) is None):
+						loader = rb.Loader()
+						loader.get_url ('http://display.lyrics.astraweb.com' + url, self.parse_lyrics, callback, *data)
+						return
+				continue
+
+		callback (None, *data)
+		return
+
+	def parse_lyrics(self, result, callback, *data):
+		if result is None:
+			callback (None, *data)
+			return
+
+		result = re.sub('\n', '', re.sub('\r', '', result))
+	   
+		artist_title = re.split('(<title>Lyrics: )([^<]*)', result)[2]
+		artist = artist_title.split( " - " )[0]
+		title  = artist_title.split( " - " )[1]
+		
+		title = "%s - %s\n\n" % (artist, title)
+		lyrics = re.split('(<font face=arial size=2>)(.*)(<\/font><br></td><td*)', result)[2]
+		lyrics = title + lyrics
+		lyrics = re.sub('<[Bb][Rr][^>]*>', '\n', lyrics)
+		lyrics += "\n\nLyrics provided by lyrics.astraweb.com"
+
+		callback (lyrics, *data)
+
Index: plugins/lyrics/lyrics/LeoslyricsParser.py
===================================================================
--- plugins/lyrics/lyrics/LeoslyricsParser.py	(revision 0)
+++ plugins/lyrics/lyrics/LeoslyricsParser.py	(revision 0)
@@ -0,0 +1,107 @@
+# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
+#
+# Copyright (C) 2005 Eduardo Gonzalez
+# Copyright (C) 2006 Jonathan Matthew
+# Copyright (C) 2007 James Livingston
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+
+
+import urllib
+import re
+import rb
+from xml.dom import minidom
+
+
+class LeoslyricsParser(object):
+	def __init__(self, artist, title):
+		self.artist = artist
+		self.title = title
+	
+	def search(self, callback, *data):
+		artist = urllib.quote(self.artist)
+		title = urllib.quote(self.title)
+
+		htstring = 'http://api.leoslyrics.com/api_search.php?auth=Rhythmbox&artist=%s&songtitle=%s' % (artist, title)
+			
+		loader = rb.Loader()
+		loader.get_url (htstring, self.got_lyrics, callback, *data)
+
+	def got_lyrics (self, lyrics, callback, *data):
+		if lyrics is None:
+			callback (None, *data)
+			return
+
+		try:
+			xmldoc = minidom.parseString(lyrics).documentElement
+		except e:
+			print e
+			callback (None, *data)
+			return
+
+		result_code = xmldoc.getElementsByTagName('response')[0].getAttribute('code')
+		if result_code != '0':
+			xmldoc.unlink()
+			callback (None, *data)
+			return
+		
+		matches = xmldoc.getElementsByTagName('result')[:10]
+		
+		i = 0
+		for match in matches:
+			title = match.getElementsByTagName('title')[0].firstChild.data
+			artist = match.getElementsByTagName('name')[0].firstChild.data
+			
+			if (re.search(self.title.lower().strip(), title.lower().strip()) and
+					re.search(self.artist.lower().strip(), artist.lower().strip())):
+				continue
+
+			matches = matches[i:]
+			i += 1
+		
+		hids = map(lambda x: x.getAttribute('hid'), matches)
+
+		if len(hids) == 0:
+			xmldoc.unlink()
+			callback (None, *data)
+			return
+
+		xmldoc.unlink()
+
+		lurl = "http://api.leoslyrics.com/api_lyrics.php?auth=Rhythmbox&hid=%s" % (urllib.quote(hids[0].encode('utf-8')))
+		loader = rb.Loader()
+		loader.get_url (lurl, self.parse_lyrics, callback, *data)
+
+
+	def parse_lyrics(self, result, callback, *data):
+		if result is None:
+			callback (None, *data)
+			return
+
+		try:
+			xmldoc = minidom.parseString(result).documentElement
+		except e:
+			print e
+			callback (None, *data)
+			return
+
+		lyrics = xmldoc.getElementsByTagName('title')[0].firstChild.nodeValue
+		lyrics += ' - ' + xmldoc.getElementsByTagName('artist')[0].getElementsByTagName('name')[0].firstChild.nodeValue + '\n\n'
+		lyrics += xmldoc.getElementsByTagName('text')[0].firstChild.nodeValue
+		xmldoc.unlink()
+
+		lyrics += "\n\nLyrics provided by leoslyrics.com"
+
+		callback (lyrics, *data)
Index: plugins/lyrics/lyrics/LyrcParser.py
===================================================================
--- plugins/lyrics/lyrics/LyrcParser.py	(revision 0)
+++ plugins/lyrics/lyrics/LyrcParser.py	(revision 0)
@@ -0,0 +1,78 @@
+# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
+#
+# Copyright (C) 2007 James Livingston
+# Copyright (C) 2007 Sirio Bolaños Puchet
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+
+import urllib
+import re
+import rb
+
+EXPS = ['\n', '\r', '<[iI][mM][gG][^>]*>', '<[aA][^>]*>[^<]*<\/[aA]>',
+	'<[sS][cC][rR][iI][pP][tT][^>]*>[^<]*(<!--[^>]*>)*[^<]*<\/[sS][cC][rR][iI][pP][tT]>',
+	'<[sS][tT][yY][lL][eE][^>]*>[^<]*(<!--[^>]*>)*[^<]*<\/[sS][tT][yY][lL][eE]>']
+CEXPS = [re.compile (exp) for exp in EXPS]
+
+SEPARATOR_RE = re.compile("<[fF][oO][nN][tT][ ]*[sS][iI][zZ][eE][ ]*='2'[ ]*>")
+
+
+class LyrcParser (object):
+	def __init__(self, artist, title):
+		self.artist = artist
+		self.title = title
+	
+	def search(self, callback, *data):
+		path = 'http://www.lyrc.com.ar/en/'
+
+		wartist = urllib.quote(self.artist)
+		wtitle = urllib.quote(self.title)
+		wurl = 'tema1en.php?artist=%s&songname=%s' % (wartist, wtitle)
+		
+		loader = rb.Loader()
+		loader.get_url (path + wurl, self.got_lyrics, callback, *data)
+
+	def got_lyrics(self, lyrics, callback, *data):
+		if lyrics is None:
+			callback (None, *data)
+			return
+
+		for exp in CEXPS:
+			lyrics = exp.sub('', lyrics)
+
+		lyricIndex = SEPARATOR_RE.search(lyrics)
+
+		if lyricIndex is not None:
+			callback(self.parse_lyrics(SEPARATOR_RE.split(lyrics, 1)[1]), *data)
+		else:
+			callback (None, *data)
+
+	def parse_lyrics(self, lyrics):
+		if re.search('<p><hr', lyrics):
+			lyrics = re.split('<p><hr', lyrics, 1)[0]
+		else:
+			lyrics = re.split('<br><br>', lyrics, 1)[0]
+		
+		lyrics = re.sub('<[fF][oO][nN][tT][^>]*>', '', lyrics)
+		title = re.split('(<[bB]>)([^<]*)', lyrics)[2]
+		artist = re.split('(<[uU]>)([^<]*)', lyrics)[2]
+		lyrics = re.sub('<[bB]>[^<].*<\/[tT][aA][bB][lL][eE]>', '', lyrics)
+		lyrics = re.sub('<[Bb][Rr][^>]*>', '\n', lyrics)
+		titl = "%s - %s\n\n" % (artist, title)
+		lyrics = titl + lyrics
+		lyrics += "\n\nLyrics provided by lyrc.com.ar"
+
+		return lyrics
+
Index: plugins/lyrics/lyrics/LyricsConfigureDialog.py
===================================================================
--- plugins/lyrics/lyrics/LyricsConfigureDialog.py	(revision 0)
+++ plugins/lyrics/lyrics/LyricsConfigureDialog.py	(revision 0)
@@ -0,0 +1,101 @@
+# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
+#
+# Copyright (C) 2007 James Livingston
+# Copyright (C) 2007 Sirio Bolaños Puchet
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
+
+
+
+import gtk, gtk.glade
+import gconf
+from os import system, path
+
+class LyricsConfigureDialog (object):
+	def __init__(self, glade_file, gconf_keys):
+		self.gconf = gconf.client_get_default()
+		self.gconf_keys = gconf_keys
+		self.gladexml = gtk.glade.XML(glade_file)
+			
+		self.dialog = self.gladexml.get_widget("preferences_dialog")
+
+		self.toggle1 = self.gladexml.get_widget("engine1")
+		self.toggle2 = self.gladexml.get_widget("engine2")
+		self.toggle3 = self.gladexml.get_widget("engine3")
+		self.choose_button = self.gladexml.get_widget("choose_button")
+		self.path_display = self.gladexml.get_widget("path_display")
+
+		self.choose_button.connect("clicked", self.choose_callback)
+		self.dialog.connect("response", self.dialog_response)
+
+		# set fields from gconf
+		engines, self.folder = self.get_prefs()
+		self.path_display.set_text(self.folder)
+		self.toggle1.set_active('leoslyrics.com' in engines)
+		self.toggle2.set_active('lyrc.com.ar' in engines)
+		self.toggle3.set_active('astraweb.com' in engines)
+
+	def dialog_response(self, dialog, response):
+		if response == gtk.RESPONSE_OK:
+			self.set_values()
+			self.dialog.hide()
+		elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
+			self.dialog.hide()
+		else:
+			print "unexpected response type"
+
+
+	def set_values(self):
+		engines = []
+		if self.toggle1.get_active():
+			engines.append('astraweb.com')
+		if self.toggle2.get_active():
+			engines.append('lyrc.com.ar')
+		if self.toggle3.get_active():
+			engines.append('leoslyrics.com')
+
+		if len(self.path_display.get_text()) is not 0:
+			self.folder = self.path_display.get_text()
+
+		self.gconf.set_list(self.gconf_keys['engines'], gconf.VALUE_STRING, engines)
+		self.gconf.set_string(self.gconf_keys['folder'], self.folder)
+
+	def choose_callback(self, widget):
+		def response_handler(widget, response):
+			if response == gtk.RESPONSE_OK:
+				path = self.chooser.get_filename()
+				self.chooser.destroy()
+				self.path_display.set_text(path)
+			else:
+				self.chooser.destroy()
+
+		buttons = (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE,
+				gtk.STOCK_OK, gtk.RESPONSE_OK)
+		self.chooser = gtk.FileChooserDialog(title="Choose lyrics folder...",
+					parent=None,
+					action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
+					buttons=buttons)
+		self.chooser.connect("response", response_handler)
+		self.chooser.set_modal(True)
+		self.chooser.set_transient_for(self.dialog)
+		self.chooser.present()
+
+	def get_dialog (self):
+		return self.dialog
+	
+	def get_prefs (self):
+		engines = gconf.client_get_default().get_list(self.gconf_keys['engines'], gconf.VALUE_STRING)
+		folder = gconf.client_get_default().get_string(self.gconf_keys['folder'])
+		return (engines, folder)
Index: plugins/lyrics/Makefile.am
===================================================================
--- plugins/lyrics/Makefile.am	(revision 5177)
+++ plugins/lyrics/Makefile.am	(working copy)
@@ -1,13 +1,20 @@
-# Lyric display plugin
+# Search Lyrics Python Plugin
+
+SUBDIRS = lyrics
+
 plugindir = $(PLUGINDIR)/lyrics
-plugin_PYTHON = lyrics.py
 
-plugin_in_files = lyrics.rb-plugin.desktop.in
 %.rb-plugin: %.rb-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
-
 plugin_DATA = $(plugin_in_files:.rb-plugin.desktop.in=.rb-plugin)
 
-EXTRA_DIST = $(plugin_in_files)
 
+plugin_in_files = lyrics.rb-plugin.desktop.in
+
+glade_DATA =	\
+		lyrics-prefs.glade
+
+gladedir = $(plugindir)
+EXTRA_DIST = $(plugin_in_files) $(glade_DATA)
 CLEANFILES = $(plugin_DATA)
 DISTCLEANFILES = $(plugin_DATA)
+
Index: plugins/lyrics/lyrics.py
===================================================================
--- plugins/lyrics/lyrics.py	(revision 5177)
+++ plugins/lyrics/lyrics.py	(working copy)
@@ -1,319 +0,0 @@
-# -*- Mode: python; coding: utf-8; tab-width: 8; indent-tabs-mode: t; -*-
-#
-# Copyright 2005 Eduardo Gonzalez
-# Copyright (C) 2006 Jonathan Matthew
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2, or (at your option)
-# any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
-#
-# TODO:
-# - multiple lyrics sites (lyrc.com.ar, etc.)
-# - handle multiple results usefully
-# - save lyrics to disk?
-# - check that the lyrics returned even remotely match the request?
-# - share URL grabbing code with other plugins
-
-import os
-import gtk, gobject
-import urllib
-import re
-from xml.dom import minidom
-import rb
-import rhythmdb
-
-ui_str = """
-<ui>
-  <menubar name="MenuBar">
-    <menu name="ViewMenu" action="View">
-      <menuitem name="ViewSongLyrics" action="ViewSongLyrics"/>
-    </menu>
-  </menubar>
-</ui>
-"""
-
-LYRICS_FOLDER="~/.lyrics"
-LYRIC_TITLE_STRIP=["\(live[^\)]*\)", "\(acoustic[^\)]*\)", "\([^\)]*mix\)", "\([^\)]*version\)", "\([^\)]*edit\)", "\(feat[^\)]*\)"]
-LYRIC_TITLE_REPLACE=[("/", "-"), (" & ", " and ")]
-LYRIC_ARTIST_REPLACE=[("/", "-"), (" & ", " and ")]
-
-
-def create_lyrics_view():
-    view = gtk.TextView()
-    view.set_wrap_mode(gtk.WRAP_WORD)
-    view.set_editable(False)
-
-    sw = gtk.ScrolledWindow()
-    sw.add(view)
-    sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
-    sw.set_shadow_type(gtk.SHADOW_IN)
-
-    vbox = gtk.VBox(spacing=12)
-    vbox.pack_start(sw, expand=True)
-    return (vbox, view.get_buffer())
-
-class LyricWindow(gtk.Window):
-    def __init__(self, db, parent, entry):
-        gtk.Window.__init__(self)
-        self.set_border_width(12)
-	self.set_transient_for(parent)
-
-	title = db.entry_get(entry, rhythmdb.PROP_TITLE)
-	artist = db.entry_get(entry, rhythmdb.PROP_ARTIST)
-        self.set_title(title + " - " + artist + " - Lyrics")
-
-	close = gtk.Button(stock=gtk.STOCK_CLOSE)
-	close.connect('clicked', lambda w: self.destroy())
-
-	(lyrics_view, buffer) = create_lyrics_view()
-	self.buffer = buffer
-        bbox = gtk.HButtonBox()
-        bbox.set_layout(gtk.BUTTONBOX_END)
-        bbox.pack_start(close)
-        lyrics_view.pack_start(bbox, expand=False)
-
-        self.buffer.set_text(_("Searching for lyrics..."))
-        self.add(lyrics_view)
-        self.set_default_size(400, 300)
-        self.show_all()
-
-class LyricPane(object):
-    def __init__(self, db, song_info):
-    	(self.view, self.buffer) = create_lyrics_view()
-	self.view.show_all()
-	self.page_num = song_info.append_page(_("Lyrics"), self.view)
-	self.db = db
-	self.song_info = song_info
-	self.have_lyrics = 0
-	self.visible = 0
-        self.entry = self.song_info.get_property("current-entry")
-
-	self.entry_change_id = song_info.connect('notify::current-entry', self.entry_changed)
-	nb = self.view.get_parent()
-	self.switch_page_id = nb.connect('switch-page', self.switch_page_cb)
-
-    def entry_changed(self, pspec, duh):
-        self.entry = self.song_info.get_property("current-entry")
-	self.have_lyrics = 0
-	if self.visible != 0:
-	    self.get_lyrics()
-
-    def switch_page_cb(self, notebook, page, page_num):
-        if self.have_lyrics != 0:
-	    return
-
-        if page_num != self.page_num:
-	    self.visible = 0
-	    return
-
-	self.visible = 1
-	self.get_lyrics()
-
-    def get_lyrics(self):
-        if self.entry is None:
-	    return
-
-	self.buffer.set_text(_("Searching for lyrics..."));
-	lyrics_grabber = LyricGrabber()
-	lyrics_grabber.get_lyrics(self.db, self.entry, self.buffer.set_text)
-
-
-
-class LyricGrabber(object):
-    def __init__(self):
-    	self.loader = rb.Loader ()
-
-    def _build_cache_path(self, artist, title):
-        lyrics_folder = os.path.expanduser (LYRICS_FOLDER)
-        if not os.path.exists (lyrics_folder):
-            os.mkdir (lyrics_folder)
-
-	artist_folder = lyrics_folder + '/' + artist[:128]
-	if not os.path.exists (artist_folder):
-	    os.mkdir (artist_folder)
-
-	return artist_folder + '/' + title[:128] + '.lyric'
-
-    def get_lyrics(self, db, entry, callback):
-	self.callback = callback
-        artist = db.entry_get(entry, rhythmdb.PROP_ARTIST).lower()
-        title = db.entry_get(entry, rhythmdb.PROP_TITLE).lower()
-
-	# replace ampersands and the like
-	for exp in LYRIC_ARTIST_REPLACE:
-		p = re.compile (exp[0])
-		artist = p.sub(exp[1], artist)
-	for exp in LYRIC_TITLE_REPLACE:
-		p = re.compile (exp[0])
-		title = p.sub(exp[1], title)
-
-        # strip things like "(live at Somewhere)", "(accoustic)", etc
-        for exp in LYRIC_TITLE_STRIP:
-            p = re.compile (exp)
-            title = p.sub ('', title)
-
-	# compress spaces
-	title = title.strip()
-	artist = artist.strip()
-
-	self.cache_path = self._build_cache_path(artist, title)
-
-	if os.path.exists (self.cache_path):
-            self.loader.get_url(self.cache_path, callback)
-            return;
-
-	url = "http://api.leoslyrics.com/api_search.php?auth=Rhythmbox&artist=%s&songtitle=%s" % (
-		urllib.quote(artist.encode('utf-8')),
-		urllib.quote(title.encode('utf-8')))
-	self.loader.get_url(url, self.search_results)
-
-    def search_results(self, data):
-    	if data is None:
-	    self.callback("Server did not respond.")
-	    return
-
-	try:
-	    xmldoc = minidom.parseString(data).documentElement
-	except:
-	    self.callback("Couldn't parse search results.")
-	    return
-
-	result_code = xmldoc.getElementsByTagName('response')[0].getAttribute('code')
-	if result_code != '0':
-	    self.callback("Server is busy, try again later.")
-	    xmldoc.unlink()
-	    return
-
-	# We don't really need the top 100 matches, so I'm limiting it to ten
-	matches = xmldoc.getElementsByTagName('result')[:10]
-	#songs = map(lambda x:
-	#	    x.getElementsByTagName('name')[0].firstChild.nodeValue
-	#	    + " - " +
-	#	    x.getElementsByTagName('title')[0].firstChild.nodeValue,
-	#	    matches)
-	hids = map(lambda x: x.getAttribute('hid'), matches)
-	#exacts = map(lambda x: x.getAttribute('exactMatch'), matches)
-
-	if len(hids) == 0:
-	    # FIXME show other matches
-	    self.callback("Unable to find lyrics for this track.")
-	    xmldoc.unlink()
-	    return
-
-	#songlist = []
-	#for i in range(len(hids)):
-	#    songlist.append((songs[i], hids[i], exacts[i]))
-
-	xmldoc.unlink()
-	url = "http://api.leoslyrics.com/api_lyrics.php?auth=Rhythmbox&hid=%s" % (urllib.quote(hids[0].encode('utf-8')))
-	self.loader.get_url(url, self.lyrics)
-
-
-    def lyrics(self, data):
-        if data is None:
-	    self.callback("Unable to find lyrics for this track.")
-	    return
-
-	try:
-	    xmldoc = minidom.parseString(data).documentElement
-	except:
-	    self.callback("Unable to parse the lyrics returned.")
-	    return
-
-	text = xmldoc.getElementsByTagName('title')[0].firstChild.nodeValue
-	text += ' - ' + xmldoc.getElementsByTagName('artist')[0].getElementsByTagName('name')[0].firstChild.nodeValue + '\n\n'
-	text += xmldoc.getElementsByTagName('text')[0].firstChild.nodeValue
-	xmldoc.unlink()
-
-	text += "\n\n"+_("Lyrics provided by leoslyrics.com")
-
-
-        f = file (self.cache_path, 'w')
-        f.write (text)
-        f.close ()
-
-	self.callback(text)
-
-
-
-class LyricsDisplayPlugin(rb.Plugin):
-
-    def __init__ (self):
-	rb.Plugin.__init__ (self)
-	self.window = None
-
-    def activate (self, shell):
-	self.action = gtk.Action ('ViewSongLyrics', _('Song L_yrics'),
-				  _('Display lyrics for the playing song'),
-				  'rb-song-lyrics')
-	self.activate_id = self.action.connect ('activate', self.show_song_lyrics, shell)
-
-	self.action_group = gtk.ActionGroup ('SongLyricsPluginActions')
-	self.action_group.add_action (self.action)
-
-    	uim = shell.get_ui_manager ()
-	uim.insert_action_group (self.action_group, 0)
-	self.ui_id = uim.add_ui_from_string (ui_str)
-	uim.ensure_update ()
-
-	sp = shell.get_player ()
-	self.pec_id = sp.connect('playing-song-changed', self.playing_entry_changed)
-	self.playing_entry_changed (sp, sp.get_playing_entry ())
-
-	self.csi_id = shell.connect('create_song_info', self.create_song_info)
-
-    def deactivate (self, shell):
-
-	uim = shell.get_ui_manager()
-	uim.remove_ui (self.ui_id)
-	uim.remove_action_group (self.action_group)
-
-	self.action_group = None
-	self.action = None
-
-	shell.disconnect (self.csi_id)
-
-	sp = shell.get_player ()
-	sp.disconnect (self.pec_id)
-
-	if self.window is not None:
-	    self.window.destroy ()
-
-
-    def playing_entry_changed (self, sp, entry):
-    	if entry is not None:
-	    self.action.set_sensitive (True)
-	else:
-	    self.action.set_sensitive (False)
-
-    def show_song_lyrics (self, action, shell):
-
-	if self.window is not None:
-	    self.window.destroy ()
-
-	db = shell.get_property ("db")
-	sp = shell.get_player ()
-	entry = sp.get_playing_entry ()
-
-	if entry is None:
-	    return
-
-	self.window = LyricWindow(db, shell.get_property ("window"), entry)
-	lyrics_grabber = LyricGrabber()
-	lyrics_grabber.get_lyrics(db, entry, self.window.buffer.set_text)
-
-    def create_song_info (self, shell, song_info, is_multiple):
-
-	if is_multiple is False:
-	    x = LyricPane(shell.get_property ("db"), song_info)
-
Index: plugins/lyrics/lyrics-prefs.glade
===================================================================
--- plugins/lyrics/lyrics-prefs.glade	(revision 0)
+++ plugins/lyrics/lyrics-prefs.glade	(revision 0)
@@ -0,0 +1,669 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--*- mode: xml -*-->
+<glade-interface>
+  <widget class="GtkDialog" id="preferences_dialog">
+    <property name="visible">True</property>
+    <property name="title" translatable="yes">Lyrics Plugin Preferences</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkVBox" id="lyrics_vbox">
+            <property name="visible">True</property>
+            <property name="border_width">5</property>
+            <property name="spacing">10</property>
+            <child>
+              <widget class="GtkFrame" id="frame1">
+                <property name="visible">True</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">GTK_SHADOW_NONE</property>
+                <child>
+                  <widget class="GtkAlignment" id="alignment1">
+                    <property name="visible">True</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <widget class="GtkVBox" id="vbox1">
+                        <property name="visible">True</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <widget class="GtkCheckButton" id="engine1">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="label">Astraweb (astraweb.com)</property>
+                            <property name="use_underline">True</property>
+                            <property name="draw_indicator">True</property>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkCheckButton" id="engine2">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="label">Lyrc (lyrc.com.ar)</property>
+                            <property name="use_underline">True</property>
+                            <property name="draw_indicator">True</property>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkCheckButton" id="engine3">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="label">Leo's Lyrics (leoslyrics.com)</property>
+                            <property name="use_underline">True</property>
+                            <property name="draw_indicator">True</property>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">2</property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label58">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">&lt;b&gt;Search engines&lt;/b&gt;</property>
+                    <property name="use_markup">True</property>
+                  </widget>
+                  <packing>
+                    <property name="type">label_item</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkFrame" id="frame2">
+                <property name="visible">True</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">GTK_SHADOW_NONE</property>
+                <child>
+                  <widget class="GtkAlignment" id="alignment2">
+                    <property name="visible">True</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <widget class="GtkHBox" id="hbox23">
+                        <property name="visible">True</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <widget class="GtkEntry" id="path_display">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                          </widget>
+                        </child>
+                        <child>
+                          <widget class="GtkButton" id="choose_button">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <child>
+                              <widget class="GtkAlignment" id="alignment3">
+                                <property name="visible">True</property>
+                                <property name="xscale">0</property>
+                                <property name="yscale">0</property>
+                                <child>
+                                  <widget class="GtkHBox" id="hbox24">
+                                    <property name="visible">True</property>
+                                    <property name="spacing">2</property>
+                                    <child>
+                                      <widget class="GtkImage" id="image1">
+                                        <property name="visible">True</property>
+                                        <property name="stock">gtk-open</property>
+                                      </widget>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <widget class="GtkLabel" id="label60">
+                                        <property name="visible">True</property>
+                                        <property name="label" translatable="yes">Browse...</property>
+                                        <property name="use_underline">True</property>
+                                      </widget>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
+                                  </widget>
+                                </child>
+                              </widget>
+                            </child>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label59">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">&lt;b&gt;Lyrics Folder&lt;/b&gt;</property>
+                    <property name="use_markup">True</property>
+                  </widget>
+                  <packing>
+                    <property name="type">label_item</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_EDGE</property>
+            <child>
+              <widget class="GtkButton" id="cancel_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">gtk-cancel</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-6</property>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkButton" id="ok_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">gtk-ok</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-5</property>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+</glade-interface>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--*- mode: xml -*-->
+<glade-interface>
+  <widget class="GtkDialog" id="preferences_dialog">
+    <property name="visible">True</property>
+    <property name="title" translatable="yes">Lyrics Plugin Preferences</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkVBox" id="lyrics_vbox">
+            <property name="visible">True</property>
+            <property name="border_width">5</property>
+            <property name="spacing">10</property>
+            <child>
+              <widget class="GtkFrame" id="frame1">
+                <property name="visible">True</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">GTK_SHADOW_NONE</property>
+                <child>
+                  <widget class="GtkAlignment" id="alignment1">
+                    <property name="visible">True</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <widget class="GtkVBox" id="vbox1">
+                        <property name="visible">True</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <widget class="GtkCheckButton" id="engine1">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="label">Astraweb (astraweb.com)</property>
+                            <property name="use_underline">True</property>
+                            <property name="draw_indicator">True</property>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkCheckButton" id="engine2">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="label">Lyrc (lyrc.com.ar)</property>
+                            <property name="use_underline">True</property>
+                            <property name="draw_indicator">True</property>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkCheckButton" id="engine3">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="label">Leo's Lyrics (leoslyrics.com)</property>
+                            <property name="use_underline">True</property>
+                            <property name="draw_indicator">True</property>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">2</property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label58">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">&lt;b&gt;Search engines&lt;/b&gt;</property>
+                    <property name="use_markup">True</property>
+                  </widget>
+                  <packing>
+                    <property name="type">label_item</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkFrame" id="frame2">
+                <property name="visible">True</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">GTK_SHADOW_NONE</property>
+                <child>
+                  <widget class="GtkAlignment" id="alignment2">
+                    <property name="visible">True</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <widget class="GtkHBox" id="hbox23">
+                        <property name="visible">True</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <widget class="GtkEntry" id="path_display">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                          </widget>
+                        </child>
+                        <child>
+                          <widget class="GtkButton" id="choose_button">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <child>
+                              <widget class="GtkAlignment" id="alignment3">
+                                <property name="visible">True</property>
+                                <property name="xscale">0</property>
+                                <property name="yscale">0</property>
+                                <child>
+                                  <widget class="GtkHBox" id="hbox24">
+                                    <property name="visible">True</property>
+                                    <property name="spacing">2</property>
+                                    <child>
+                                      <widget class="GtkImage" id="image1">
+                                        <property name="visible">True</property>
+                                        <property name="stock">gtk-open</property>
+                                      </widget>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <widget class="GtkLabel" id="label60">
+                                        <property name="visible">True</property>
+                                        <property name="label" translatable="yes">Browse...</property>
+                                        <property name="use_underline">True</property>
+                                      </widget>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
+                                  </widget>
+                                </child>
+                              </widget>
+                            </child>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label59">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">&lt;b&gt;Lyrics Folder&lt;/b&gt;</property>
+                    <property name="use_markup">True</property>
+                  </widget>
+                  <packing>
+                    <property name="type">label_item</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_EDGE</property>
+            <child>
+              <widget class="GtkButton" id="cancel_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">gtk-cancel</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-6</property>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkButton" id="ok_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">gtk-ok</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-5</property>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+</glade-interface>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--*- mode: xml -*-->
+<glade-interface>
+  <widget class="GtkDialog" id="preferences_dialog">
+    <property name="visible">True</property>
+    <property name="title" translatable="yes">Lyrics Plugin Preferences</property>
+    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox1">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <widget class="GtkVBox" id="lyrics_vbox">
+            <property name="visible">True</property>
+            <property name="border_width">5</property>
+            <property name="spacing">10</property>
+            <child>
+              <widget class="GtkFrame" id="frame1">
+                <property name="visible">True</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">GTK_SHADOW_NONE</property>
+                <child>
+                  <widget class="GtkAlignment" id="alignment1">
+                    <property name="visible">True</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <widget class="GtkVBox" id="vbox1">
+                        <property name="visible">True</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <widget class="GtkCheckButton" id="engine1">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="label">Astraweb (astraweb.com)</property>
+                            <property name="use_underline">True</property>
+                            <property name="draw_indicator">True</property>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkCheckButton" id="engine2">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="label">Lyrc (lyrc.com.ar)</property>
+                            <property name="use_underline">True</property>
+                            <property name="draw_indicator">True</property>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkCheckButton" id="engine3">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="label">Leo's Lyrics (leoslyrics.com)</property>
+                            <property name="use_underline">True</property>
+                            <property name="draw_indicator">True</property>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">2</property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label58">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">&lt;b&gt;Search engines&lt;/b&gt;</property>
+                    <property name="use_markup">True</property>
+                  </widget>
+                  <packing>
+                    <property name="type">label_item</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkFrame" id="frame2">
+                <property name="visible">True</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">GTK_SHADOW_NONE</property>
+                <child>
+                  <widget class="GtkAlignment" id="alignment2">
+                    <property name="visible">True</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <widget class="GtkHBox" id="hbox23">
+                        <property name="visible">True</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <widget class="GtkEntry" id="path_display">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                          </widget>
+                        </child>
+                        <child>
+                          <widget class="GtkButton" id="choose_button">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <child>
+                              <widget class="GtkAlignment" id="alignment3">
+                                <property name="visible">True</property>
+                                <property name="xscale">0</property>
+                                <property name="yscale">0</property>
+                                <child>
+                                  <widget class="GtkHBox" id="hbox24">
+                                    <property name="visible">True</property>
+                                    <property name="spacing">2</property>
+                                    <child>
+                                      <widget class="GtkImage" id="image1">
+                                        <property name="visible">True</property>
+                                        <property name="stock">gtk-open</property>
+                                      </widget>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <widget class="GtkLabel" id="label60">
+                                        <property name="visible">True</property>
+                                        <property name="label" translatable="yes">Browse...</property>
+                                        <property name="use_underline">True</property>
+                                      </widget>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
+                                  </widget>
+                                </child>
+                              </widget>
+                            </child>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+                <child>
+                  <widget class="GtkLabel" id="label59">
+                    <property name="visible">True</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">&lt;b&gt;Lyrics Folder&lt;/b&gt;</property>
+                    <property name="use_markup">True</property>
+                  </widget>
+                  <packing>
+                    <property name="type">label_item</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="layout_style">GTK_BUTTONBOX_EDGE</property>
+            <child>
+              <widget class="GtkButton" id="cancel_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">gtk-cancel</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-6</property>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkButton" id="ok_button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                <property name="label" translatable="yes">gtk-ok</property>
+                <property name="use_stock">True</property>
+                <property name="response_id">-5</property>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">GTK_PACK_END</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+</glade-interface>