Topic: Sonata lyrics fix

Hey everyone,
First time poster, long-time lurker. I'm a huge fan of sonata and mpd, but I never had much luck getting lyrics fetching to work in Sonata. After some frustration, I stumbled across a patch on the debain bugreport site. (http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=631375)
Basically, you just need to update the lyrics site in /usr/lib/pymodules/python2.6/sonata/info.py . To make life easy, here's how the new info.py should look:

import sys, os, locale, urllib
import threading # get_lyrics_start starts a thread get_lyrics_thread

import gtk, gobject

import ui, misc
import mpdhelper as mpdh
from consts import consts
import re

class Info(object):
    def __init__(self, config, info_image, linkcolor, on_link_click_cb, library_return_search_items, get_playing_song, TAB_INFO, on_image_activate, on_image_motion_cb, on_image_drop_cb, album_return_artist_and_tracks, new_tab):
        self.config = config
        self.info_image = info_image
        self.linkcolor = linkcolor
        self.on_link_click_cb = on_link_click_cb
        self.library_return_search_items = library_return_search_items
        self.get_playing_song = get_playing_song
        self.on_image_activate = on_image_activate
        self.on_image_motion_cb = on_image_motion_cb
        self.on_image_drop_cb = on_image_drop_cb
        self.album_return_artist_and_tracks = album_return_artist_and_tracks

        try:
            self.enc = locale.getpreferredencoding()
        except:
            print "Locale cannot be found; please set your system's locale. Aborting..."
            sys.exit(1)

        self.last_info_bitrate = None

        self.info_boxes_in_more = None
        self.info_editlabel = None
        self.info_editlyricslabel = None
        self.info_labels = None
        self.info_left_label = None
        self.info_lyrics = None
        self.info_morelabel = None
        self.info_searchlabel = None
        self.info_tagbox = None
        self.info_type = None

        self.lyricsText = None
        self.albumText = None

        # Info tab
        self.info_area = ui.scrollwindow(shadow=gtk.SHADOW_NONE)

        if self.config.info_art_enlarged:
            self.info_imagebox = ui.eventbox()
        else:
            self.info_imagebox = ui.eventbox(w=152)

        self.info_imagebox.add(self.info_image)

        self.tab = new_tab(self.info_area, gtk.STOCK_JUSTIFY_FILL, TAB_INFO, self.info_area)

        self.info_imagebox.drag_dest_set(gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP, [("text/uri-list", 0, 80), ("text/plain", 0, 80)], gtk.gdk.ACTION_DEFAULT)
        self.info_imagebox.connect('button_press_event', self.on_image_activate)
        self.info_imagebox.connect('drag_motion', self.on_image_motion_cb)
        self.info_imagebox.connect('drag_data_received', self.on_image_drop_cb)

        self.widgets_initialize(self.info_area)

    def widgets_initialize(self, info_scrollwindow):

        vert_spacing = 1
        horiz_spacing = 2
        margin = 5
        outter_hbox = gtk.HBox()
        outter_vbox = gtk.VBox()

        # Song info
        info_song = ui.expander(markup="<b>" + _("Song Info") + "</b>", expand=self.config.info_song_expanded, can_focus=False)
        info_song.connect("activate", self.info_expanded, "song")
        inner_hbox = gtk.HBox()

        inner_hbox.pack_start(self.info_imagebox, False, False, horiz_spacing)

        self.info_tagbox = gtk.VBox()

        labels_left = []
        self.info_type = {}
        self.info_labels = []
        self.info_boxes_in_more = []
        labels = [(_("Title"), 'title', False, "", False),
            (_("Artist"), 'artist', True,
                _("Launch artist in Wikipedia"), False),
            (_("Album"), 'album', True,
                 _("Launch album in Wikipedia"), False),
            (_("Date"), 'date', False, "", False),
            (_("Track"), 'track', False, "", False),
            (_("Genre"), 'genre', False, "", False),
            (_("File"), 'file', False, "", True),
            (_("Bitrate"), 'bitrate', False, "", True)]

        for i,(text, name, link, tooltip, in_more) in enumerate(labels):
            self.info_type[name] = i
            tmphbox = gtk.HBox()
            if in_more:
                self.info_boxes_in_more += [tmphbox]
            tmplabel = ui.label(markup="<b>%s:</b>" % text, y=0)
            if i == 0:
                self.info_left_label = tmplabel
            # Using set_selectable overrides the hover cursor that
            # sonata tries to set for the links, and I can't figure
            # out how to stop that. So we'll disable set_selectable
            # for those labels until it's figured out.
            tmplabel2 = ui.label(wrap=True, y=0, select=not link)
            if link:
                tmpevbox = ui.eventbox(add=tmplabel2)
                self.info_apply_link_signals(tmpevbox, name, tooltip)
            tmphbox.pack_start(tmplabel, False, False, horiz_spacing)
            to_pack = tmpevbox if link else tmplabel2
            tmphbox.pack_start(to_pack, False, False, horiz_spacing)
            self.info_labels += [tmplabel2]
            labels_left += [tmplabel]
            self.info_tagbox.pack_start(tmphbox, False, False, vert_spacing)
        ui.set_widths_equal(labels_left)
        
        mischbox = gtk.HBox()
        self.info_morelabel = ui.label(y=0)
        moreevbox = ui.eventbox(add=self.info_morelabel)
        self.info_apply_link_signals(moreevbox, 'more', _("Toggle extra tags"))
        self.info_editlabel = ui.label(y=0)
        editevbox = ui.eventbox(add=self.info_editlabel)
        self.info_apply_link_signals(editevbox, 'edit', _("Edit song tags"))
        mischbox.pack_start(moreevbox, False, False, horiz_spacing)
        mischbox.pack_start(editevbox, False, False, horiz_spacing)

        self.info_tagbox.pack_start(mischbox, False, False, vert_spacing)
        inner_hbox.pack_start(self.info_tagbox, False, False, horiz_spacing)
        info_song.add(inner_hbox)
        outter_vbox.pack_start(info_song, False, False, margin)
        
        # Lyrics
        self.info_lyrics = ui.expander(markup="<b>" + _("Lyrics") + "</b>", expand=self.config.info_lyrics_expanded, can_focus=False)
        self.info_lyrics.connect("activate", self.info_expanded, "lyrics")
        lyricsbox = gtk.VBox()
        lyricsbox_top = gtk.HBox()
        self.lyricsText = ui.label(markup=" ", y=0, select=True, wrap=True)
        lyricsbox_top.pack_start(self.lyricsText, True, True, horiz_spacing)
        lyricsbox.pack_start(lyricsbox_top, True, True, vert_spacing)
        lyricsbox_bottom = gtk.HBox()
        self.info_searchlabel = ui.label(y=0)
        self.info_editlyricslabel = ui.label(y=0)
        searchevbox = ui.eventbox(add=self.info_searchlabel)
        editlyricsevbox = ui.eventbox(add=self.info_editlyricslabel)
        self.info_apply_link_signals(searchevbox, 'search', _("Search Lyricwiki.org for lyrics"))
        self.info_apply_link_signals(editlyricsevbox, 'editlyrics', _("Edit lyrics at Lyricwiki.org"))
        lyricsbox_bottom.pack_start(searchevbox, False, False, horiz_spacing)
        lyricsbox_bottom.pack_start(editlyricsevbox, False, False, horiz_spacing)
        lyricsbox.pack_start(lyricsbox_bottom, False, False, vert_spacing)
        self.info_lyrics.add(lyricsbox)
        outter_vbox.pack_start(self.info_lyrics, False, False, margin)
        
        # Album info
        info_album = ui.expander(markup="<b>" + _("Album Info") + "</b>", expand=self.config.info_album_expanded, can_focus=False)
        info_album.connect("activate", self.info_expanded, "album")
        albumbox = gtk.VBox()
        albumbox_top = gtk.HBox()
        self.albumText = ui.label(markup=" ", y=0, select=True, wrap=True)
        albumbox_top.pack_start(self.albumText, False, False, horiz_spacing)
        albumbox.pack_start(albumbox_top, False, False, vert_spacing)
        info_album.add(albumbox)
        outter_vbox.pack_start(info_album, False, False, margin)
        
        # Finish..
        if not self.config.show_lyrics:
            ui.hide(self.info_lyrics)
        if not self.config.show_covers:
            ui.hide(self.info_imagebox)
        # self.config.info_song_more will be overridden on on_link_click, so
        # store it in a temporary var..
        temp = self.config.info_song_more
        self.on_link_click(moreevbox, None, 'more')
        self.config.info_song_more = temp
        if self.config.info_song_more:
            self.on_link_click(moreevbox, None, 'more')
        outter_hbox.pack_start(outter_vbox, False, False, margin)
        info_scrollwindow.add_with_viewport(outter_hbox)

    def get_widgets(self):
        return self.info_area

    def get_info_imagebox(self):
        return self.info_imagebox

    def show_lyrics_updated(self):
        if self.config.show_lyrics:
            ui.show(self.info_lyrics)
        else:
            ui.hide(self.info_lyrics)

    def info_apply_link_signals(self, widget, linktype, tooltip):
        widget.connect("enter-notify-event", self.on_link_enter)
        widget.connect("leave-notify-event", self.on_link_leave)
        widget.connect("button-press-event", self.on_link_click, linktype)
        widget.set_tooltip_text(tooltip)

    def on_link_enter(self, widget, _event):
        if widget.get_children()[0].get_use_markup():
            ui.change_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
        
    def on_link_leave(self, _widget, _event):
        ui.change_cursor(None)
    
    def on_link_click(self, _widget, _event, linktype):
        if linktype == 'more':
            previous_is_more = (self.info_morelabel.get_text() == "(" + _("more") + ")")
            if previous_is_more:
                self.info_morelabel.set_markup(misc.link_markup(_("hide"), True, True, self.linkcolor))
                self.config.info_song_more = True
            else:
                self.info_morelabel.set_markup(misc.link_markup(_("more"), True, True, self.linkcolor))
                self.config.info_song_more = False
            if self.config.info_song_more:
                for hbox in self.info_boxes_in_more:
                    ui.show(hbox)
            else:
                for hbox in self.info_boxes_in_more:
                    ui.hide(hbox)
        else:
            self.on_link_click_cb(linktype)

    def info_expanded(self, expander, infotype):
        expanded = not expander.get_expanded()
        if infotype == "song":
            self.config.info_song_expanded = expanded
        elif infotype == "lyrics":
            self.config.info_lyrics_expanded = expanded
        elif infotype == "album":
            self.config.info_album_expanded = expanded

    def info_update(self, playing_or_paused, newbitrate, songinfo, update_all, blank_window=False, skip_lyrics=False):
        # update_all = True means that every tag should update. This is
        # only the case on song and status changes. Otherwise we only
        # want to update the minimum number of widgets so the user can
        # do things like select label text.
        if playing_or_paused:
            bitratelabel = self.info_labels[self.info_type['bitrate']]
            titlelabel = self.info_labels[self.info_type['title']]
            artistlabel = self.info_labels[self.info_type['artist']]
            albumlabel = self.info_labels[self.info_type['album']]
            datelabel = self.info_labels[self.info_type['date']]
            genrelabel = self.info_labels[self.info_type['genre']]
            tracklabel = self.info_labels[self.info_type['track']]
            filelabel = self.info_labels[self.info_type['file']]
            if not self.last_info_bitrate or self.last_info_bitrate != newbitrate:
                bitratelabel.set_text(newbitrate)
            self.last_info_bitrate = newbitrate
            if update_all:
                # Use artist/album Wikipedia links?
                artist_use_link = False
                if 'artist' in songinfo:
                    artist_use_link = True
                album_use_link = False
                if 'album' in songinfo:
                    album_use_link = True
                titlelabel.set_text(mpdh.get(songinfo, 'title'))
                if artist_use_link:
                    artistlabel.set_markup(misc.link_markup(misc.escape_html(mpdh.get(songinfo, 'artist')), False, False, self.linkcolor))
                else:
                    artistlabel.set_text(mpdh.get(songinfo, 'artist'))
                if album_use_link:
                    albumlabel.set_markup(misc.link_markup(misc.escape_html(mpdh.get(songinfo, 'album')), False, False, self.linkcolor))
                else:
                    albumlabel.set_text(mpdh.get(songinfo, 'album'))
                datelabel.set_text(mpdh.get(songinfo, 'date'))
                genrelabel.set_text(mpdh.get(songinfo, 'genre'))
                if 'track' in songinfo:
                    tracklabel.set_text(mpdh.getnum(songinfo, 'track', '0', False, 0))
                else:
                    tracklabel.set_text("")
                path = misc.file_from_utf8(self.config.musicdir[self.config.profile_num] + os.path.dirname(mpdh.get(songinfo, 'file')))
                if os.path.exists(path):
                    filelabel.set_text(self.config.musicdir[self.config.profile_num] + mpdh.get(songinfo, 'file'))
                    self.info_editlabel.set_markup(misc.link_markup(_("edit tags"), True, True, self.linkcolor))
                else:
                    filelabel.set_text(mpdh.get(songinfo, 'file'))
                    self.info_editlabel.set_text("")
                if 'album' in songinfo:
                    # Update album info:
                    artist, tracks = self.album_return_artist_and_tracks()
                    trackinfo = ""
                    album = mpdh.get(songinfo, 'album')
                    year = mpdh.get(songinfo, 'date', None)
                    if album is not None:
                        albuminfo = album + "\n"
                    playtime = 0
                    if tracks is not None and len(tracks) > 0:
                        tracks.sort(key=lambda x: int(mpdh.getnum(x, 'track', '0', True, 0)))
                        for track in tracks:
                            playtime += int(mpdh.get(track, 'time', 0))
                            if 'title' in track:
                                trackinfo = trackinfo + mpdh.getnum(track, 'track', '0', False, 2) + '. ' + mpdh.get(track, 'title') + '\n'
                            else:
                                trackinfo = trackinfo + mpdh.getnum(track, 'track', '0', False, 2) + '. ' + mpdh.get(track, 'file').split('/')[-1] + '\n'
                        if artist is not None:
                            albuminfo += artist + "\n"
                        if year is not None:
                            albuminfo += year + "\n"
                        albuminfo += misc.convert_time(playtime) + "\n"
                        albuminfo += "\n" + trackinfo
                    else:
                        albuminfo = _("Album info not found.")
                    self.albumText.set_markup(misc.escape_html(albuminfo))
                else:
                    self.albumText.set_text(_("Album name not set."))
                # Update lyrics:
                if self.config.show_lyrics and not skip_lyrics:
                    if 'artist' in songinfo and 'title' in songinfo:
                        self.get_lyrics_start(mpdh.get(songinfo, 'artist'), mpdh.get(songinfo, 'title'), mpdh.get(songinfo, 'artist'), mpdh.get(songinfo, 'title'), os.path.dirname(mpdh.get(songinfo, 'file')))
                    else:
                        self.info_searchlabel.set_text("")
                        self.info_show_lyrics(_("Artist or song title not set."), "", "", True)
        else:
            blank_window = True
        if blank_window:
            for label in self.info_labels:
                label.set_text("")
            self.info_editlabel.set_text("")
            if self.config.show_lyrics:
                self.info_searchlabel.set_text("")
                self.info_editlyricslabel.set_text("")
                self.info_show_lyrics("", "", "", True)
            self.albumText.set_text("")
            self.last_info_bitrate = ""

    def info_check_for_local_lyrics(self, artist, title, song_dir):
        locations = [consts.LYRICS_LOCATION_HOME,
                consts.LYRICS_LOCATION_PATH,
                consts.LYRICS_LOCATION_HOME_ALT,
                consts.LYRICS_LOCATION_PATH_ALT]
        for location in locations:
            filename = self.target_lyrics_filename(artist, title,
                                song_dir, location)
            if os.path.exists(filename):
                return filename

    def get_lyrics_start(self, *args):
        lyricThread = threading.Thread(target=self.get_lyrics_thread, args=args)
        lyricThread.setDaemon(True)
        lyricThread.start()

    def lyricwiki_format(self, text):
        return urllib.quote(str(unicode(text).title()))

    def lyricwiki_editlink(self, songinfo):
        artist, title = [self.lyricwiki_format(mpdh.get(songinfo, key))
                 for key in ('artist', 'title')]
        return "http://lyrics.wikia.com/index.php?title=%s:%s&action=edit" % (artist, title)

    def get_lyrics_thread(self, search_artist, search_title, filename_artist, filename_title, song_dir):
        filename_artist = misc.strip_all_slashes(filename_artist)
        filename_title = misc.strip_all_slashes(filename_title)
        filename = self.info_check_for_local_lyrics(filename_artist, filename_title, song_dir)
        search_str = misc.link_markup(_("search"), True, True, self.linkcolor)
        edit_str = misc.link_markup(_("edit"), True, True, self.linkcolor)
        if filename:
            # If the lyrics only contain "not found", delete the file and try to
            # fetch new lyrics. If there is a bug in Sonata/SZI/LyricWiki that
            # prevents lyrics from being found, storing the "not found" will
            # prevent a future release from correctly fetching the lyrics.
            f = open(filename, 'r')
            lyrics = f.read()
            f.close()
            if lyrics == _("Lyrics not found"):
                misc.remove_file(filename)
                filename = self.info_check_for_local_lyrics(filename_artist, filename_title, song_dir)
        if filename:
            # Re-use lyrics from file:
            f = open(filename, 'r')
            lyrics = f.read()
            f.close()
            # Strip artist - title line from file if it exists, since we
            # now have that information visible elsewhere.
            header = filename_artist + " - " + filename_title + "\n\n"
            if lyrics[:len(header)] == header:
                lyrics = lyrics[len(header):]
            gobject.idle_add(self.info_show_lyrics, lyrics, filename_artist, filename_title)
            gobject.idle_add(self.info_searchlabel.set_markup, search_str)
            gobject.idle_add(self.info_editlyricslabel.set_markup, edit_str)
        else:
            # Use default filename:
            filename = self.target_lyrics_filename(filename_artist, filename_title, song_dir)
            # Fetch lyrics from lyrics.wikia.com (formerly lyricwiki.org)
            gobject.idle_add(self.info_show_lyrics, _("Fetching lyrics..."), filename_artist, filename_title)
            try:
                lyricpage = urllib.urlopen("http://lyrics.wikia.com/index.php?title=%s:%s&action=edit" % (self.lyricwiki_format(search_artist), self.lyricwiki_format(search_title))).read()
                content = re.split("<textarea[^>]*>", lyricpage)[1].split("</textarea>")[0]
                if content.startswith("#REDIRECT [["):
                    addr = "http://lyrics.wikia.com/index.php?title=%s&action=edit" % urllib.quote(content.split("[[")[1].split("]]")[0])
                    content = urllib.urlopen(addr).read()
                # To avoid problems with mixed escaped/unescaped characters unescape before extracting lyrics (fixes bug #631375)
                content = misc.unescape_html(content)
                lyrics = content.split("<lyrics>")[1].split("</lyrics>")[0]
                if lyrics.strip() != "<!-- PUT LYRICS HERE (and delete this entire line) -->":                
                    lyrics = misc.wiki_to_html(lyrics)
                    lyrics = lyrics.decode("utf-8")
# Save lyrics to file:
                    misc.create_dir('~/.lyrics/')
                    f = open(filename, 'w')
                    f.write(lyrics)
                    f.close()
                else:
                    lyrics = _("Lyrics not found")
                gobject.idle_add(self.info_show_lyrics, lyrics, filename_artist, filename_title)
            except:
                lyrics = _("Fetching lyrics failed")
                gobject.idle_add(self.info_show_lyrics, lyrics, filename_artist, filename_title)
            gobject.idle_add(self.info_searchlabel.set_markup, search_str)
            gobject.idle_add(self.info_editlyricslabel.set_markup, edit_str)

    def info_show_lyrics(self, lyrics, artist, title, force=False):
        if force:
            # For error messages where there is no appropriate artist or 
            # title, we pass force=True:
            self.lyricsText.set_text(lyrics)
        elif self.get_playing_song():
            # Verify that we are displaying the correct lyrics:
            songinfo = self.get_playing_song()
            try:
                if misc.strip_all_slashes(mpdh.get(songinfo, 'artist')) == artist and misc.strip_all_slashes(mpdh.get(songinfo, 'title')) == title:
                    try:
                        self.lyricsText.set_markup(misc.escape_html(lyrics))
                    except:
                        self.lyricsText.set_text(lyrics)
            except:
                pass

    def resize_elements(self, notebook_allocation):
        # Resize labels in info tab to prevent horiz scrollbar:
        if self.config.show_covers:
            labelwidth = notebook_allocation.width - self.info_left_label.allocation.width - self.info_imagebox.allocation.width - 60 # 60 accounts for vert scrollbar, box paddings, etc..
        else:
            labelwidth = notebook_allocation.width - self.info_left_label.allocation.width - 60 # 60 accounts for vert scrollbar, box paddings, etc..
        if labelwidth > 100:
            for label in self.info_labels:
                label.set_size_request(labelwidth, -1)
        # Resize lyrics/album gtk labels:
        labelwidth = notebook_allocation.width - 45 # 45 accounts for vert scrollbar, box paddings, etc..
        self.lyricsText.set_size_request(labelwidth, -1)
        self.albumText.set_size_request(labelwidth, -1)

    def target_lyrics_filename(self, artist, title, song_dir, force_location=None):
        # FIXME Why did we have this condition here: if self.conn:
        lyrics_loc = force_location if force_location else self.config.lyrics_location
        # Note: *_ALT searching is for compatibility with other mpd clients (like ncmpcpp):
        if lyrics_loc == consts.LYRICS_LOCATION_HOME:
            targetfile = os.path.expanduser("~/.lyrics/" + artist + "-" + title + ".txt")
        elif lyrics_loc == consts.LYRICS_LOCATION_PATH:
            targetfile = self.config.musicdir[self.config.profile_num] + song_dir + "/" + artist + "-" + title + ".txt"
        elif lyrics_loc == consts.LYRICS_LOCATION_HOME_ALT:
            targetfile = os.path.expanduser("~/.lyrics/" + artist + " - " + title + ".txt")
        elif lyrics_loc == consts.LYRICS_LOCATION_PATH_ALT:
            targetfile = self.config.musicdir[self.config.profile_num] + song_dir + "/" + artist + " - " + title + ".txt"
        targetfile = misc.file_exists_insensitive(targetfile)
        return misc.file_from_utf8(targetfile)
    

Re: Sonata lyrics fix

Welcome to #! runslaughter, enjoy the spicy talapia!

Nice find..  I like Sonata as well.. thanks!