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)