File indexing completed on 2024-05-05 04:48:33

0001 /****************************************************************************************
0002  * Copyright (c) 2007 Leo Franchi <lfranchi@kde.org>                                    *
0003  * Copyright (c) 2009 Seb Ruiz <ruiz@kde.org>                                           *
0004  *                                                                                      *
0005  * This program is free software; you can redistribute it and/or modify it under        *
0006  * the terms of the GNU General Public License as published by the Free Software        *
0007  * Foundation; either version 2 of the License, or (at your option) any later           *
0008  * version.                                                                             *
0009  *                                                                                      *
0010  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0011  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0012  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0013  *                                                                                      *
0014  * You should have received a copy of the GNU General Public License along with         *
0015  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0016  ****************************************************************************************/
0017 
0018 #define DEBUG_PREFIX "LyricsManager"
0019 
0020 #include "LyricsManager.h"
0021 
0022 #include "EngineController.h"
0023 #include "core/meta/Meta.h"
0024 #include "core/support/Debug.h"
0025 #include "core-impl/collections/support/CollectionManager.h"
0026 
0027 #include <QDomDocument>
0028 #include <QTextEdit>
0029 #include <QXmlStreamReader>
0030 
0031 #include <KLocalizedString>
0032 
0033 
0034 #define APIURL "https://lyrics.fandom.com/api.php?action=query&prop=revisions&rvprop=content&format=xml&titles="
0035 
0036 
0037 LyricsManager* LyricsManager::s_self = nullptr;
0038 
0039 LyricsManager::LyricsManager()
0040 {
0041     s_self = this;
0042     connect( The::engineController(), &EngineController::trackChanged, this, &LyricsManager::newTrack );
0043 }
0044 
0045 void
0046 LyricsManager::newTrack( const Meta::TrackPtr &track )
0047 {
0048     loadLyrics( track );
0049 }
0050 
0051 void
0052 LyricsManager::lyricsResult( const QByteArray& lyricsXML, Meta::TrackPtr track ) //SLOT
0053 {
0054     DEBUG_BLOCK
0055 
0056     QXmlStreamReader xml( lyricsXML );
0057     while( !xml.atEnd() )
0058     {
0059         xml.readNext();
0060 
0061         if( xml.name() == QStringLiteral("lyric") || xml.name() == QStringLiteral( "lyrics" ) )
0062         {
0063             QString lyrics( xml.readElementText() );
0064             if( !isEmpty( lyrics ) )
0065             {
0066                 // overwrite cached lyrics (as either there were no lyrics available previously OR
0067                 // the user explicitly agreed to overwrite the lyrics)
0068                 debug() << "setting cached lyrics...";
0069                 track->setCachedLyrics( lyrics ); // TODO: setLyricsByPath?
0070                 Q_EMIT newLyrics( track );
0071             }
0072             else
0073             {
0074                 ::error() << i18n("Retrieved lyrics is empty");
0075                 return;
0076             }
0077         }
0078         else if( xml.name() == QLatin1String("suggestions") )
0079         {
0080             QVariantList suggestions;
0081             while( xml.readNextStartElement() )
0082             {
0083                 if( xml.name() != QLatin1String("suggestion") )
0084                     continue;
0085 
0086                 const QXmlStreamAttributes &a = xml.attributes();
0087 
0088                 QString artist = a.value( QLatin1String("artist") ).toString();
0089                 QString title = a.value( QLatin1String("title") ).toString();
0090                 QString url = a.value( QLatin1String("url") ).toString();
0091 
0092                 if( !url.isEmpty() )
0093                     suggestions << ( QStringList() << title << artist << url );
0094 
0095                 xml.skipCurrentElement();
0096             }
0097 
0098             debug() << "got" << suggestions.size() << "suggestions";
0099 
0100             if( !suggestions.isEmpty() )
0101                 Q_EMIT newSuggestions( suggestions );
0102 
0103             return;
0104         }
0105     }
0106 
0107     if( xml.hasError() )
0108     {
0109         warning() << "errors occurred during reading lyrics xml result:" << xml.errorString();
0110         Q_EMIT error( i18n("Lyrics data could not be parsed") );
0111     }
0112 }
0113 
0114 void LyricsManager::loadLyrics( Meta::TrackPtr track, bool overwrite )
0115 {
0116     DEBUG_BLOCK
0117 
0118     if( !track )
0119     {
0120         debug() << "no current track";
0121         return;
0122     }
0123 
0124     // -- get current title and artist
0125     QString title = track->name();
0126     QString artist = track->artist() ? track->artist()->name() : QString();
0127 
0128     sanitizeTitle( title );
0129     sanitizeArtist( artist );
0130 
0131     if( !isEmpty( track->cachedLyrics() ) && !overwrite )
0132     {
0133         debug() << "Lyrics already cached.";
0134         return;
0135     }
0136 
0137     QUrl url( APIURL + artist + QLatin1Char(':') + title );
0138     m_trackMap.insert( url, track );
0139 
0140     connect( NetworkAccessManagerProxy::instance(), &NetworkAccessManagerProxy::requestRedirectedUrl,
0141              this, &LyricsManager::updateRedirectedUrl);
0142 
0143     NetworkAccessManagerProxy::instance()->getData( url, this, &LyricsManager::lyricsLoaded );
0144 }
0145 
0146 void LyricsManager::lyricsLoaded( const QUrl& url, const QByteArray& data, const NetworkAccessManagerProxy::Error &err )
0147 {
0148     DEBUG_BLOCK
0149 
0150     if( err.code )
0151     {
0152         warning() << "A network error occurred:" << err.description;
0153         return;
0154     }
0155 
0156     Meta::TrackPtr track = m_trackMap.take( url );
0157     if( !track )
0158     {
0159         warning() << "No track belongs to this url:" << url.url();
0160         return;
0161     }
0162 
0163     QDomDocument document;
0164     document.setContent( data );
0165     auto list = document.elementsByTagName( QStringLiteral( "rev" ) );
0166     if( list.isEmpty() )
0167     {
0168         if( track->album() && track->album()->albumArtist() )
0169         {
0170             QString albumArtist = track->album()->albumArtist()->name();
0171             QString artist = track->artist() ? track->artist()->name() : QString();
0172             QString title = track->name();
0173             sanitizeTitle( title );
0174             sanitizeArtist( artist );
0175             sanitizeArtist( albumArtist );
0176 
0177             //Try with album artist
0178             if( url == QUrl( APIURL + artist + QLatin1Char(':') + title ) && albumArtist != artist )
0179             {
0180                 debug() << "Try again with album artist.";
0181 
0182                 QUrl newUrl( APIURL + albumArtist + QLatin1Char(':') + title );
0183                 m_trackMap.insert( newUrl, track );
0184                 NetworkAccessManagerProxy::instance()->getData( newUrl, this, &LyricsManager::lyricsLoaded );
0185                 return;
0186             }
0187         }
0188 
0189         debug() << "No lyrics found for track:" << track->name();
0190         return;
0191     }
0192 
0193     QString rev = list.at( 0 ).toElement().text();
0194     if( rev.contains( QStringLiteral( "lyrics" ) ) )
0195     {
0196         int lindex = rev.indexOf( QStringLiteral( "<lyrics>" ) );
0197         int rindex = rev.indexOf( QStringLiteral( "</lyrics>" ) );
0198         lyricsResult( (rev.mid( lindex, rindex - lindex ) + "</lyrics>" ).toUtf8(), track );
0199     }
0200     else if( rev.contains( QStringLiteral( "lyric" ) ) )
0201     {
0202         int lindex = rev.indexOf( QStringLiteral( "<lyric>" ) );
0203         int rindex = rev.indexOf( QStringLiteral( "</lyric>" ) );
0204         lyricsResult( (rev.mid( lindex, rindex - lindex ) + "</lyric>" ).toUtf8(), track );
0205     }
0206     else if( rev.contains( QStringLiteral( "#REDIRECT" ) ) )
0207     {
0208         debug() << "Redirect:" << data;
0209 
0210         int lindex = rev.indexOf( QStringLiteral( "#REDIRECT [[" ) ) + 12;
0211         int rindex = rev.indexOf( QStringLiteral( "]]" ) );
0212         QStringList list = rev.mid( lindex, rindex - lindex ).split( QLatin1Char(':') );
0213         if( list.size() == 2 )
0214         {
0215             list[0] = list[0].replace( '&', QStringLiteral( "%26" ) );
0216             list[1] = list[1].replace( '&', QStringLiteral( "%26" ) );
0217             QUrl newUrl( APIURL + list.join( QLatin1Char(':') ) );
0218             m_trackMap.insert( newUrl, track );
0219             NetworkAccessManagerProxy::instance()->getData( newUrl, this, &LyricsManager::lyricsLoaded );
0220         }
0221     }
0222     else
0223         warning() << "No lyrics found in data:" << data;
0224 }
0225 
0226 void LyricsManager::sanitizeTitle( QString& title )
0227 {
0228     const QString magnatunePreviewString = QStringLiteral( "PREVIEW: buy it at www.magnatune.com" );
0229 
0230     if( title.contains(magnatunePreviewString, Qt::CaseSensitive) )
0231         title = title.remove( " (" + magnatunePreviewString + ')' );
0232 
0233     title = title.remove( QStringLiteral( "(Live)" ) );
0234     title = title.remove( QStringLiteral( "(live)" ) );
0235     title = title.replace( '`', QStringLiteral( "'" ) );
0236     title = title.replace( '&', QStringLiteral( "%26" ) );
0237 }
0238 
0239 void LyricsManager::sanitizeArtist( QString& artist )
0240 {
0241     const QString magnatunePreviewString = QStringLiteral( "PREVIEW: buy it at www.magnatune.com" );
0242 
0243     if( artist.contains(magnatunePreviewString, Qt::CaseSensitive) )
0244         artist = artist.remove( " (" + magnatunePreviewString + ')' );
0245 
0246     // strip "featuring <someone else>" from the artist
0247     int strip = artist.toLower().indexOf( QLatin1String(" ft. "));
0248     if ( strip != -1 )
0249         artist = artist.mid( 0, strip );
0250 
0251     strip = artist.toLower().indexOf( QLatin1String(" feat. ") );
0252     if ( strip != -1 )
0253         artist = artist.mid( 0, strip );
0254 
0255     strip = artist.toLower().indexOf( QLatin1String(" featuring ") );
0256     if ( strip != -1 )
0257         artist = artist.mid( 0, strip );
0258 
0259     artist = artist.replace( '`', QStringLiteral( "'" ) );
0260     artist = artist.replace( '&', QStringLiteral( "%26" ) );
0261 }
0262 
0263 bool LyricsManager::isEmpty( const QString &lyrics ) const
0264 {
0265     QTextEdit testItem;
0266 
0267     // Set the text of the TextItem.
0268     if( Qt::mightBeRichText( lyrics ) )
0269         testItem.setHtml( lyrics );
0270     else
0271         testItem.setPlainText( lyrics );
0272 
0273     // Get the plaintext content.
0274     // We use toPlainText() to strip all Html formatting,
0275     // so we can test if there's any text given.
0276     QString testText = testItem.toPlainText().trimmed();
0277 
0278     return testText.isEmpty();
0279 }
0280 
0281 void LyricsManager::updateRedirectedUrl(const QUrl& oldUrl, const QUrl& newUrl)
0282 {
0283     if( m_trackMap.contains( oldUrl ) && !m_trackMap.contains( newUrl ) )
0284     {
0285         // Get track for the old URL.
0286         Meta::TrackPtr track = m_trackMap.value( oldUrl );
0287 
0288         // Replace with redirected url for correct lookup
0289         m_trackMap.insert( newUrl, track );
0290         m_trackMap.remove( oldUrl );
0291     }
0292 }