rendered paste bodyIndex: 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 >)', results) is not None:
+ body = re.split('(<tr><td bgcolor="#BBBBBB".*)(More Songs >)', 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"><b>Search engines</b></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"><b>Lyrics Folder</b></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"><b>Search engines</b></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"><b>Lyrics Folder</b></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"><b>Search engines</b></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"><b>Lyrics Folder</b></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>