Index: visualize.cpp
===================================================================
--- visualize.cpp	(revision 14795)
+++ visualize.cpp	(working copy)
@@ -449,6 +449,274 @@
     }
 }AlbumArtFactory;
 
+Lyrics::Lyrics(MainVisual *parent)
+{
+    m_pParent = parent;
+
+    findFrontCover();
+
+    Decoder *dec = m_pParent->decoder();
+    if (dec)
+        m_filename = dec->getFilename();
+
+    fps = 1;
+}
+
+void Lyrics::findFrontCover(void)
+{
+    // if a front cover image is available show that first
+    AlbumArtImages albumArt(m_pParent->metadata());
+    if (albumArt.isImageAvailable(IT_FRONTCOVER))
+        m_currImageType = IT_FRONTCOVER;
+    else
+    {
+        // not available so just show the first image available
+        if (albumArt.getImageCount() > 0)
+            m_currImageType = albumArt.getImageAt(0).imageType;
+        else
+            m_currImageType = IT_UNKNOWN;
+    }
+}
+
+Lyrics::~Lyrics()
+{
+}
+
+void Lyrics::resize(const QSize &newsize)
+{
+    m_size = newsize;
+}
+
+bool Lyrics::process(VisualNode *node)
+{
+    node = node;
+    return true;
+}
+
+void Lyrics::handleKeyPress(const QString &action)
+{
+    if (action == "DOWN")
+    {
+        if (m_start + 1 + m_visible_lines < int(m_lyrics.size()))
+            m_start++;
+    }
+
+    if (action == "UP")
+    {
+        if (m_start != 0)
+            m_start--;
+    }
+
+    if (action == "PAGEDOWN")
+    {
+        if (m_start + m_page_amount + m_visible_lines < int(m_lyrics.size()))
+            m_start += m_page_amount;
+        else
+            m_start = int(m_lyrics.size()) - m_page_amount;
+    }
+
+    if (action == "PAGEUP")
+    {
+        if (m_start < m_page_amount)
+            m_start = 0;
+        else
+            m_start -= m_page_amount;
+    }
+
+    if (action == "SELECT")
+    {
+        AlbumArtImages albumArt(m_pParent->metadata());
+
+        int newType = m_currImageType;
+        if (albumArt.getImageCount() > 0)
+        {
+            while(!albumArt.isImageAvailable((ImageType) ++newType))
+                if (newType == IT_LAST)
+                    newType = IT_UNKNOWN;
+        }
+
+        if (newType != m_currImageType)
+        {
+            m_currImageType = (ImageType) newType;
+            // force an update
+            m_cursize = QSize(0, 0);
+        }
+    }
+}
+
+bool Lyrics::needsUpdate() 
+{
+    if (m_cursize != m_size)
+        return true;
+
+    if (m_filename != m_pParent->decoder()->getFilename()) 
+    {
+        m_filename = m_pParent->decoder()->getFilename();
+        findFrontCover();
+        return true;
+    }
+
+    return false;
+}
+
+void Lyrics::calculateScreenInfo(QPainter *p)
+{
+    m_start = 0;
+    m_indentx = 0;
+    m_indenty = 0;
+    m_max_line_width = 0;
+    m_visible_lines = 0;
+    m_page_amount = 0;
+    m_font_height = 0;
+    m_lyrics_too_wide = false;
+    m_image_too_small = false;
+
+    if (!m_lyrics.empty())
+    {
+        p->setFont(gContext->GetMediumFont());
+        QFontMetrics fm(p->font());
+        m_font_height = fm.height();
+
+        for (QValueVector<QString>::iterator i = m_lyrics.begin();
+                                             i != m_lyrics.end(); i++)
+        {
+            int width = fm.width(*i);
+            if (width > m_max_line_width) m_max_line_width = width;
+        }
+
+        m_indentx = int(0.03 * m_size.width());
+        m_indenty = int(0.03 * m_size.height());
+
+        if ((m_max_line_width + 2 * m_indentx) > m_size.width())
+        {
+            // Lyrics don't fit.  Remove them.
+            m_lyrics.clear();
+            m_max_line_width = 0;
+            m_indentx = 0;
+            m_indenty = 0;
+            m_lyrics_too_wide = true;
+        }
+        else
+        {
+            m_visible_lines = (m_size.height() - 2 * m_indenty) / m_font_height;
+            m_page_amount = m_visible_lines / 2;
+            if (m_size.width() < m_max_line_width + 3 * m_indentx +
+                                     int(0.1 * m_size.width()))
+            {
+                // Don't draw really small images
+                m_image_too_small = true;
+            }
+        }
+    }
+}
+
+bool Lyrics::draw(QPainter *p, const QColor &back)
+{
+    if (!m_pParent->decoder())
+        return false;
+
+    // If the song has changed or the size, reload
+    if (needsUpdate())
+    {
+        m_lyrics = m_pParent->metadata()->getUnsynchronizedLyrics();
+        calculateScreenInfo(p);
+
+        // Now we know how much room we have for the image.
+        QImage art(m_pParent->metadata()->getAlbumArt(m_currImageType));
+        if (art.isNull())
+        {
+            m_cursize = m_size;
+            m_image = QImage();
+        }
+        else if (!m_image_too_small)
+        {
+            int height_adjustment = m_lyrics_too_wide ?  m_font_height : 0;
+            QSize a_size(m_size.width() - m_max_line_width - 3 * m_indentx,
+                         m_size.height() - height_adjustment);
+            m_image = art.smoothScale(a_size, QImage::ScaleMin);
+        }
+    }
+
+    if (m_image.isNull() && !m_image_too_small && m_lyrics.empty())
+    {
+        drawWarning(p, back, m_size, QObject::tr("?"));
+        return true;
+    }
+
+    p->fillRect(0, 0, m_size.width(), m_size.height(), back);
+
+    // Paint the image
+    if (!m_image_too_small)
+    {
+        int xpos = (m_size.width() - m_max_line_width -
+                                   m_indentx - m_image.width()) /2;
+        int ypos = m_lyrics_too_wide ? 0 :
+                               (m_size.height() - m_image.height()) / 2;
+        p->drawPixmap(xpos, ypos, m_image);
+    }
+
+    // Paint the Lyrics
+    p->setFont(gContext->GetMediumFont());
+    if (m_lyrics_too_wide)
+    {
+        // Lyrics are too wide to fit on the display.  Just write "Lyrics"
+        // at the bottom on the display to indicate they are available.
+        QFontMetrics fm(p->font());
+        QString l = "Lyrics";
+        int width = fm.width(l);
+        int height = m_font_height;
+        int x = m_size.width() / 2 - width / 2;
+        int y = m_size.height() - height;
+        p->setPen(Qt::white);
+        p->drawText(x, y, width, height, Qt::AlignCenter, l);
+    }
+    else
+    {
+        int textWidth = m_max_line_width;
+        int textHeight = m_font_height * m_lyrics.size();
+        int x = m_size.width() - m_max_line_width - m_indentx;
+        int y = m_indenty;
+   
+        int line = m_start;
+        for (int offset = 0;
+             line <= m_start + m_visible_lines-1 && line < int(m_lyrics.size());
+             offset += m_font_height, line++)
+        {
+            QString l = m_lyrics[line] + "\n";
+            p->setPen(Qt::white);
+            p->drawText(x, y + offset, textWidth, textHeight, Qt::AlignLeft, l);
+        }
+    }
+
+    // Store our new size
+    m_cursize = m_size;
+
+    return true;
+}
+
+static class LyricsFactory : public VisFactory
+{
+  public:
+    const QString &name(void) const
+    {
+        static QString name("Lyrics");
+        return name;
+    }
+
+    uint plugins(QStringList *list) const
+    {
+        *list << name();
+        return 1;
+    }
+
+    VisualBase *create(MainVisual *parent, long int winid, const QString &pluginName) const
+    {
+        (void)winid;
+        (void)pluginName;
+        return new Lyrics(parent);
+    }
+}LyricsFactory;
+
 Blank::Blank()
     : VisualBase(true)
 {
Index: metadata.cpp
===================================================================
--- metadata.cpp	(revision 14795)
+++ metadata.cpp	(working copy)
@@ -725,6 +725,15 @@
     return image;
 }
 
+QValueVector<QString> Metadata::getUnsynchronizedLyrics()
+{
+    QValueVector<QString> ulyrics;
+
+    ulyrics = MetaIOTagLib::getUnsynchronizedLyrics(m_filename);
+
+    return ulyrics;
+}
+
 MetadataLoadingThread::MetadataLoadingThread(AllMusic *parent_ptr)
 {
     parent = parent_ptr;
Index: metadata.h
===================================================================
--- metadata.h	(revision 14795)
+++ metadata.h	(working copy)
@@ -185,6 +185,7 @@
     static QStringList fillFieldList(QString field);
 
     QImage getAlbumArt(ImageType type);
+    QValueVector<QString> getUnsynchronizedLyrics();
 
   private:
     void setCompilationFormatting(bool cd = false);
Index: metaiotaglib.h
===================================================================
--- metaiotaglib.h	(revision 14795)
+++ metaiotaglib.h	(working copy)
@@ -27,6 +27,7 @@
     Metadata* read(QString filename);
 
     static QImage getAlbumArt(QString filename, ImageType type);
+    static QValueVector<QString> getUnsynchronizedLyrics(QString filename);
 
 private:
 
Index: metaiotaglib.cpp
===================================================================
--- metaiotaglib.cpp	(revision 14795)
+++ metaiotaglib.cpp	(working copy)
@@ -2,6 +2,7 @@
 
 #include "metaiotaglib.h"
 #include "metadata.h"
+#include "unknownframe.h"
 
 #include <mythtv/mythcontext.h>
 
@@ -292,6 +293,106 @@
 }
 
 /*!
+ * \brief Read unsynchronized lyrics from the file
+ *
+ * \param filename The filename to read
+ * \returns A vector of QStrings containing the lines of lyrics.
+ */
+QValueVector<QString> MetaIOTagLib::getUnsynchronizedLyrics(QString filename)
+{
+  QValueVector<QString> ulyrics;
+
+  File *taglib = new TagLib::MPEG::File(filename.local8Bit());
+  if (taglib != NULL)
+  {
+      TagLib::ID3v2::Tag *tag = taglib->ID3v2Tag();
+
+      if (tag != NULL && !tag->frameListMap()["USLT"].isEmpty())
+      {
+          TagLib::ID3v2::FrameList ulyricframes = tag->frameListMap()["USLT"];
+
+          // No special support for multiple USLT frames.  Just concatenate
+          // them all into one vector.
+          for(TagLib::ID3v2::FrameList::Iterator it = ulyricframes.begin();
+                                               it != ulyricframes.end(); ++it)
+          {
+              TagLib::ID3v2::UnknownFrame *frame =
+                            static_cast<TagLib::ID3v2::UnknownFrame *>(*it);
+              TagLib::ByteVector v = frame->data();
+
+              int i = 0;
+              // $xx Text encoding:
+              // $00 ISO-8859-1.  Terminator with $00
+              // $01 UTF-16 with BOM.  Terminated with $00 00.
+              // $02 UTF-16BE without BOM.  Terminated with $00 00.
+              // $03 UTF-8.  Terminated with $00.
+
+              QString (*byte_decoder)(const char *, int) = NULL;
+              switch (v[i++])
+              {
+                  case 0x00:
+                      byte_decoder = QString::fromLatin1;
+                      break;
+                  case 0x01:
+                      // ok, will use QString::fromUcs2
+                      break;
+                  case 0x02:
+                      // Currently not supported
+                      continue;
+                  case 0x03:
+                      byte_decoder = QString::fromUtf8;
+                      break;
+                  default:
+                      // Unknown
+                      continue;
+              }
+
+              // $xx $xx $xx Language: For now just skip.
+              i += 3;
+
+              // <text string according to encoding> $00 (00): skip over it.
+              if (byte_decoder)
+              {
+                  QString cd = byte_decoder(v.data() + i, -1);
+                  i += cd.length() + 1;
+              }
+              else
+              {
+                  QString cd = QString::fromUcs2(
+                                             (unsigned short *)(v.data() + i));
+                  i += (cd.length() + 1) * 2;
+              }
+
+              QString lstr;
+              if (byte_decoder)
+                  lstr = byte_decoder(v.data() + i, v.size() - i); 
+              else
+              {
+                  i += 2; // skip BOM
+                  // fromUcs2 doesn't have a size argument and some encoders
+                  // do not write the terminator.  So write one to be sure.
+                  v[v.size() - 1] = 0;
+                  v[v.size() - 2] = 0;
+                  lstr = QString::fromUcs2((unsigned short *)(v.data() + i));
+              }
+              QStringList sl = QStringList::split("\n", lstr, TRUE);
+              for (QStringList::Iterator it = sl.begin(); it != sl.end(); ++it)
+                  ulyrics.push_back(*it);
+          }
+      }
+      delete taglib;
+  }
+  if (ulyrics.size() == 1)
+  {
+      // It is useful to use only one line of the lyrics when it has no lyrics
+      // (classical music for example) to quiet tools that look for missing
+      // lyrics.  Just clear that line so it isn't displayed. 
+      ulyrics.clear();
+  }
+  return ulyrics;
+}
+
+/*!
  * \brief Read the albumart image from the file
  *
  * \param tag The ID3v2 tag object in which to look for Album Art
Index: visualize.h
===================================================================
--- visualize.h	(revision 14795)
+++ visualize.h	(working copy)
@@ -100,6 +100,39 @@
     QImage m_image;
 };
 
+class Lyrics : public VisualBase
+{
+  public:
+    Lyrics(MainVisual *parent);
+    virtual ~Lyrics();
+
+    void resize(const QSize &size);
+    bool process(VisualNode *node = 0);
+    bool draw(QPainter *p, const QColor &back = Qt::black);
+    void handleKeyPress(const QString &action);
+
+  private:
+    bool needsUpdate(void);
+    void findFrontCover(void);
+    void calculateScreenInfo(QPainter *p);
+
+    QSize m_size, m_cursize;
+    QString m_filename;
+    ImageType m_currImageType;
+    MainVisual *m_pParent;
+    QImage m_image;
+    QValueVector<QString> m_lyrics;
+    int m_start;
+    int m_page_amount;
+    int m_indentx;
+    int m_indenty;
+    int m_visible_lines;
+    int m_max_line_width;
+    int m_font_height;
+    bool m_lyrics_too_wide;
+    bool m_image_too_small;
+};
+
 class Blank : public VisualBase
 {
     // This draws ... well ... nothing	
