Ticket #4479: mythmusic_lyrics.patch

File mythmusic_lyrics.patch, 13.7 KB (added by Mike Rice <mikerice1969@…>, 14 years ago)

MythMusic Lyrics Visualization

  • visualize.cpp

     
    449449    }
    450450}AlbumArtFactory;
    451451
     452Lyrics::Lyrics(MainVisual *parent)
     453{
     454    m_pParent = parent;
     455
     456    findFrontCover();
     457
     458    Decoder *dec = m_pParent->decoder();
     459    if (dec)
     460        m_filename = dec->getFilename();
     461
     462    fps = 1;
     463}
     464
     465void Lyrics::findFrontCover(void)
     466{
     467    // if a front cover image is available show that first
     468    AlbumArtImages albumArt(m_pParent->metadata());
     469    if (albumArt.isImageAvailable(IT_FRONTCOVER))
     470        m_currImageType = IT_FRONTCOVER;
     471    else
     472    {
     473        // not available so just show the first image available
     474        if (albumArt.getImageCount() > 0)
     475            m_currImageType = albumArt.getImageAt(0).imageType;
     476        else
     477            m_currImageType = IT_UNKNOWN;
     478    }
     479}
     480
     481Lyrics::~Lyrics()
     482{
     483}
     484
     485void Lyrics::resize(const QSize &newsize)
     486{
     487    m_size = newsize;
     488}
     489
     490bool Lyrics::process(VisualNode *node)
     491{
     492    node = node;
     493    return true;
     494}
     495
     496void Lyrics::handleKeyPress(const QString &action)
     497{
     498    if (action == "DOWN")
     499    {
     500        if (m_start + 1 + m_visible_lines < int(m_lyrics.size()))
     501            m_start++;
     502    }
     503
     504    if (action == "UP")
     505    {
     506        if (m_start != 0)
     507            m_start--;
     508    }
     509
     510    if (action == "PAGEDOWN")
     511    {
     512        if (m_start + m_page_amount + m_visible_lines < int(m_lyrics.size()))
     513            m_start += m_page_amount;
     514        else
     515            m_start = int(m_lyrics.size()) - m_page_amount;
     516    }
     517
     518    if (action == "PAGEUP")
     519    {
     520        if (m_start < m_page_amount)
     521            m_start = 0;
     522        else
     523            m_start -= m_page_amount;
     524    }
     525
     526    if (action == "SELECT")
     527    {
     528        AlbumArtImages albumArt(m_pParent->metadata());
     529
     530        int newType = m_currImageType;
     531        if (albumArt.getImageCount() > 0)
     532        {
     533            while(!albumArt.isImageAvailable((ImageType) ++newType))
     534                if (newType == IT_LAST)
     535                    newType = IT_UNKNOWN;
     536        }
     537
     538        if (newType != m_currImageType)
     539        {
     540            m_currImageType = (ImageType) newType;
     541            // force an update
     542            m_cursize = QSize(0, 0);
     543        }
     544    }
     545}
     546
     547bool Lyrics::needsUpdate()
     548{
     549    if (m_cursize != m_size)
     550        return true;
     551
     552    if (m_filename != m_pParent->decoder()->getFilename())
     553    {
     554        m_filename = m_pParent->decoder()->getFilename();
     555        findFrontCover();
     556        return true;
     557    }
     558
     559    return false;
     560}
     561
     562void Lyrics::calculateScreenInfo(QPainter *p)
     563{
     564    m_start = 0;
     565    m_indentx = 0;
     566    m_indenty = 0;
     567    m_max_line_width = 0;
     568    m_visible_lines = 0;
     569    m_page_amount = 0;
     570    m_font_height = 0;
     571    m_lyrics_too_wide = false;
     572    m_image_too_small = false;
     573
     574    if (!m_lyrics.empty())
     575    {
     576        p->setFont(gContext->GetMediumFont());
     577        QFontMetrics fm(p->font());
     578        m_font_height = fm.height();
     579
     580        for (QValueVector<QString>::iterator i = m_lyrics.begin();
     581                                             i != m_lyrics.end(); i++)
     582        {
     583            int width = fm.width(*i);
     584            if (width > m_max_line_width) m_max_line_width = width;
     585        }
     586
     587        m_indentx = int(0.03 * m_size.width());
     588        m_indenty = int(0.03 * m_size.height());
     589
     590        if ((m_max_line_width + 2 * m_indentx) > m_size.width())
     591        {
     592            // Lyrics don't fit.  Remove them.
     593            m_lyrics.clear();
     594            m_max_line_width = 0;
     595            m_indentx = 0;
     596            m_indenty = 0;
     597            m_lyrics_too_wide = true;
     598        }
     599        else
     600        {
     601            m_visible_lines = (m_size.height() - 2 * m_indenty) / m_font_height;
     602            m_page_amount = m_visible_lines / 2;
     603            if (m_size.width() < m_max_line_width + 3 * m_indentx +
     604                                     int(0.1 * m_size.width()))
     605            {
     606                // Don't draw really small images
     607                m_image_too_small = true;
     608            }
     609        }
     610    }
     611}
     612
     613bool Lyrics::draw(QPainter *p, const QColor &back)
     614{
     615    if (!m_pParent->decoder())
     616        return false;
     617
     618    // If the song has changed or the size, reload
     619    if (needsUpdate())
     620    {
     621        m_lyrics = m_pParent->metadata()->getUnsynchronizedLyrics();
     622        calculateScreenInfo(p);
     623
     624        // Now we know how much room we have for the image.
     625        QImage art(m_pParent->metadata()->getAlbumArt(m_currImageType));
     626        if (art.isNull())
     627        {
     628            m_cursize = m_size;
     629            m_image = QImage();
     630        }
     631        else if (!m_image_too_small)
     632        {
     633            int height_adjustment = m_lyrics_too_wide ?  m_font_height : 0;
     634            QSize a_size(m_size.width() - m_max_line_width - 3 * m_indentx,
     635                         m_size.height() - height_adjustment);
     636            m_image = art.smoothScale(a_size, QImage::ScaleMin);
     637        }
     638    }
     639
     640    if (m_image.isNull() && !m_image_too_small && m_lyrics.empty())
     641    {
     642        drawWarning(p, back, m_size, QObject::tr("?"));
     643        return true;
     644    }
     645
     646    p->fillRect(0, 0, m_size.width(), m_size.height(), back);
     647
     648    // Paint the image
     649    if (!m_image_too_small)
     650    {
     651        int xpos = (m_size.width() - m_max_line_width -
     652                                   m_indentx - m_image.width()) /2;
     653        int ypos = m_lyrics_too_wide ? 0 :
     654                               (m_size.height() - m_image.height()) / 2;
     655        p->drawPixmap(xpos, ypos, m_image);
     656    }
     657
     658    // Paint the Lyrics
     659    p->setFont(gContext->GetMediumFont());
     660    if (m_lyrics_too_wide)
     661    {
     662        // Lyrics are too wide to fit on the display.  Just write "Lyrics"
     663        // at the bottom on the display to indicate they are available.
     664        QFontMetrics fm(p->font());
     665        QString l = "Lyrics";
     666        int width = fm.width(l);
     667        int height = m_font_height;
     668        int x = m_size.width() / 2 - width / 2;
     669        int y = m_size.height() - height;
     670        p->setPen(Qt::white);
     671        p->drawText(x, y, width, height, Qt::AlignCenter, l);
     672    }
     673    else
     674    {
     675        int textWidth = m_max_line_width;
     676        int textHeight = m_font_height * m_lyrics.size();
     677        int x = m_size.width() - m_max_line_width - m_indentx;
     678        int y = m_indenty;
     679   
     680        int line = m_start;
     681        for (int offset = 0;
     682             line <= m_start + m_visible_lines-1 && line < int(m_lyrics.size());
     683             offset += m_font_height, line++)
     684        {
     685            QString l = m_lyrics[line] + "\n";
     686            p->setPen(Qt::white);
     687            p->drawText(x, y + offset, textWidth, textHeight, Qt::AlignLeft, l);
     688        }
     689    }
     690
     691    // Store our new size
     692    m_cursize = m_size;
     693
     694    return true;
     695}
     696
     697static class LyricsFactory : public VisFactory
     698{
     699  public:
     700    const QString &name(void) const
     701    {
     702        static QString name("Lyrics");
     703        return name;
     704    }
     705
     706    uint plugins(QStringList *list) const
     707    {
     708        *list << name();
     709        return 1;
     710    }
     711
     712    VisualBase *create(MainVisual *parent, long int winid, const QString &pluginName) const
     713    {
     714        (void)winid;
     715        (void)pluginName;
     716        return new Lyrics(parent);
     717    }
     718}LyricsFactory;
     719
    452720Blank::Blank()
    453721    : VisualBase(true)
    454722{
  • metadata.cpp

     
    725725    return image;
    726726}
    727727
     728QValueVector<QString> Metadata::getUnsynchronizedLyrics()
     729{
     730    QValueVector<QString> ulyrics;
     731
     732    ulyrics = MetaIOTagLib::getUnsynchronizedLyrics(m_filename);
     733
     734    return ulyrics;
     735}
     736
    728737MetadataLoadingThread::MetadataLoadingThread(AllMusic *parent_ptr)
    729738{
    730739    parent = parent_ptr;
  • metadata.h

     
    185185    static QStringList fillFieldList(QString field);
    186186
    187187    QImage getAlbumArt(ImageType type);
     188    QValueVector<QString> getUnsynchronizedLyrics();
    188189
    189190  private:
    190191    void setCompilationFormatting(bool cd = false);
  • metaiotaglib.h

     
    2727    Metadata* read(QString filename);
    2828
    2929    static QImage getAlbumArt(QString filename, ImageType type);
     30    static QValueVector<QString> getUnsynchronizedLyrics(QString filename);
    3031
    3132private:
    3233
  • metaiotaglib.cpp

     
    22
    33#include "metaiotaglib.h"
    44#include "metadata.h"
     5#include "unknownframe.h"
    56
    67#include <mythtv/mythcontext.h>
    78
     
    292293}
    293294
    294295/*!
     296 * \brief Read unsynchronized lyrics from the file
     297 *
     298 * \param filename The filename to read
     299 * \returns A vector of QStrings containing the lines of lyrics.
     300 */
     301QValueVector<QString> MetaIOTagLib::getUnsynchronizedLyrics(QString filename)
     302{
     303  QValueVector<QString> ulyrics;
     304
     305  File *taglib = new TagLib::MPEG::File(filename.local8Bit());
     306  if (taglib != NULL)
     307  {
     308      TagLib::ID3v2::Tag *tag = taglib->ID3v2Tag();
     309
     310      if (tag != NULL && !tag->frameListMap()["USLT"].isEmpty())
     311      {
     312          TagLib::ID3v2::FrameList ulyricframes = tag->frameListMap()["USLT"];
     313
     314          // No special support for multiple USLT frames.  Just concatenate
     315          // them all into one vector.
     316          for(TagLib::ID3v2::FrameList::Iterator it = ulyricframes.begin();
     317                                               it != ulyricframes.end(); ++it)
     318          {
     319              TagLib::ID3v2::UnknownFrame *frame =
     320                            static_cast<TagLib::ID3v2::UnknownFrame *>(*it);
     321              TagLib::ByteVector v = frame->data();
     322
     323              int i = 0;
     324              // $xx Text encoding:
     325              // $00 ISO-8859-1.  Terminator with $00
     326              // $01 UTF-16 with BOM.  Terminated with $00 00.
     327              // $02 UTF-16BE without BOM.  Terminated with $00 00.
     328              // $03 UTF-8.  Terminated with $00.
     329
     330              QString (*byte_decoder)(const char *, int) = NULL;
     331              switch (v[i++])
     332              {
     333                  case 0x00:
     334                      byte_decoder = QString::fromLatin1;
     335                      break;
     336                  case 0x01:
     337                      // ok, will use QString::fromUcs2
     338                      break;
     339                  case 0x02:
     340                      // Currently not supported
     341                      continue;
     342                  case 0x03:
     343                      byte_decoder = QString::fromUtf8;
     344                      break;
     345                  default:
     346                      // Unknown
     347                      continue;
     348              }
     349
     350              // $xx $xx $xx Language: For now just skip.
     351              i += 3;
     352
     353              // <text string according to encoding> $00 (00): skip over it.
     354              if (byte_decoder)
     355              {
     356                  QString cd = byte_decoder(v.data() + i, -1);
     357                  i += cd.length() + 1;
     358              }
     359              else
     360              {
     361                  QString cd = QString::fromUcs2(
     362                                             (unsigned short *)(v.data() + i));
     363                  i += (cd.length() + 1) * 2;
     364              }
     365
     366              QString lstr;
     367              if (byte_decoder)
     368                  lstr = byte_decoder(v.data() + i, v.size() - i);
     369              else
     370              {
     371                  i += 2; // skip BOM
     372                  // fromUcs2 doesn't have a size argument and some encoders
     373                  // do not write the terminator.  So write one to be sure.
     374                  v[v.size() - 1] = 0;
     375                  v[v.size() - 2] = 0;
     376                  lstr = QString::fromUcs2((unsigned short *)(v.data() + i));
     377              }
     378              QStringList sl = QStringList::split("\n", lstr, TRUE);
     379              for (QStringList::Iterator it = sl.begin(); it != sl.end(); ++it)
     380                  ulyrics.push_back(*it);
     381          }
     382      }
     383      delete taglib;
     384  }
     385  if (ulyrics.size() == 1)
     386  {
     387      // It is useful to use only one line of the lyrics when it has no lyrics
     388      // (classical music for example) to quiet tools that look for missing
     389      // lyrics.  Just clear that line so it isn't displayed.
     390      ulyrics.clear();
     391  }
     392  return ulyrics;
     393}
     394
     395/*!
    295396 * \brief Read the albumart image from the file
    296397 *
    297398 * \param tag The ID3v2 tag object in which to look for Album Art
  • visualize.h

     
    100100    QImage m_image;
    101101};
    102102
     103class Lyrics : public VisualBase
     104{
     105  public:
     106    Lyrics(MainVisual *parent);
     107    virtual ~Lyrics();
     108
     109    void resize(const QSize &size);
     110    bool process(VisualNode *node = 0);
     111    bool draw(QPainter *p, const QColor &back = Qt::black);
     112    void handleKeyPress(const QString &action);
     113
     114  private:
     115    bool needsUpdate(void);
     116    void findFrontCover(void);
     117    void calculateScreenInfo(QPainter *p);
     118
     119    QSize m_size, m_cursize;
     120    QString m_filename;
     121    ImageType m_currImageType;
     122    MainVisual *m_pParent;
     123    QImage m_image;
     124    QValueVector<QString> m_lyrics;
     125    int m_start;
     126    int m_page_amount;
     127    int m_indentx;
     128    int m_indenty;
     129    int m_visible_lines;
     130    int m_max_line_width;
     131    int m_font_height;
     132    bool m_lyrics_too_wide;
     133    bool m_image_too_small;
     134};
     135
    103136class Blank : public VisualBase
    104137{
    105138    // This draws ... well ... nothing