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 }