Changeset View
Changeset View
Standalone View
Standalone View
src/lyrics/LyricsManager.cpp
- This file was added.
1 | /**************************************************************************************** | ||||
---|---|---|---|---|---|
2 | * Copyright (c) 2007 Leo Franchi <lfranchi@kde.org> * | ||||
3 | * Copyright (c) 2009 Seb Ruiz <ruiz@kde.org> * | ||||
4 | * * | ||||
5 | * This program is free software; you can redistribute it and/or modify it under * | ||||
6 | * the terms of the GNU General Public License as published by the Free Software * | ||||
7 | * Foundation; either version 2 of the License, or (at your option) any later * | ||||
8 | * version. * | ||||
9 | * * | ||||
10 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY * | ||||
11 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * | ||||
12 | * PARTICULAR PURPOSE. See the GNU General Public License for more details. * | ||||
13 | * * | ||||
14 | * You should have received a copy of the GNU General Public License along with * | ||||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. * | ||||
16 | ****************************************************************************************/ | ||||
17 | | ||||
18 | #define DEBUG_PREFIX "LyricsManager" | ||||
19 | | ||||
20 | #include "LyricsManager.h" | ||||
21 | | ||||
22 | #include "EngineController.h" | ||||
23 | #include "core/meta/Meta.h" | ||||
24 | #include "core/support/Debug.h" | ||||
25 | #include "core-impl/collections/support/CollectionManager.h" | ||||
26 | | ||||
27 | #include <QDomDocument> | ||||
28 | #include <QTextEdit> | ||||
29 | #include <QXmlStreamReader> | ||||
30 | | ||||
31 | #include <KLocalizedString> | ||||
32 | | ||||
33 | | ||||
34 | #define APIURL "http://lyrics.wikia.com/api.php?action=query&prop=revisions&rvprop=content&format=xml&titles=" | ||||
35 | | ||||
36 | | ||||
37 | LyricsManager* LyricsManager::s_self = 0; | ||||
38 | | ||||
39 | LyricsManager::LyricsManager() | ||||
40 | { | ||||
41 | s_self = this; | ||||
42 | connect( The::engineController(), &EngineController::trackChanged, this, &LyricsManager::newTrack ); | ||||
43 | } | ||||
44 | | ||||
45 | void | ||||
46 | LyricsManager::newTrack( Meta::TrackPtr track ) | ||||
47 | { | ||||
48 | loadLyrics( track ); | ||||
49 | } | ||||
50 | | ||||
51 | void | ||||
52 | LyricsManager::lyricsResult( const QByteArray& lyricsXML, Meta::TrackPtr track ) //SLOT | ||||
53 | { | ||||
54 | DEBUG_BLOCK | ||||
55 | | ||||
56 | QXmlStreamReader xml( lyricsXML ); | ||||
57 | while( !xml.atEnd() ) | ||||
58 | { | ||||
59 | xml.readNext(); | ||||
60 | | ||||
61 | if( xml.name() == QStringLiteral("lyric") || xml.name() == QStringLiteral( "lyrics" ) ) | ||||
62 | { | ||||
63 | QString lyrics( xml.readElementText() ); | ||||
64 | if( !isEmpty( lyrics ) ) | ||||
65 | { | ||||
66 | // overwrite cached lyrics (as either there were no lyircs available previously OR | ||||
67 | // the user exlicitly agreed to overwrite the lyrics) | ||||
68 | debug() << "setting cached lyrics..."; | ||||
69 | track->setCachedLyrics( lyrics ); // TODO: setLyricsByPath? | ||||
70 | emit newLyrics( track ); | ||||
71 | } | ||||
72 | else | ||||
73 | { | ||||
74 | ::error() << i18n("Retrieved lyrics is empty"); | ||||
75 | return; | ||||
76 | } | ||||
77 | } | ||||
78 | else if( xml.name() == QLatin1String("suggestions") ) | ||||
79 | { | ||||
80 | QVariantList suggestions; | ||||
81 | while( xml.readNextStartElement() ) | ||||
82 | { | ||||
83 | if( xml.name() != QLatin1String("suggestion") ) | ||||
84 | continue; | ||||
85 | | ||||
86 | const QXmlStreamAttributes &a = xml.attributes(); | ||||
87 | | ||||
88 | QString artist = a.value( QLatin1String("artist") ).toString(); | ||||
89 | QString title = a.value( QLatin1String("title") ).toString(); | ||||
90 | QString url = a.value( QLatin1String("url") ).toString(); | ||||
91 | | ||||
92 | if( !url.isEmpty() ) | ||||
93 | suggestions << ( QStringList() << title << artist << url ); | ||||
94 | | ||||
95 | xml.skipCurrentElement(); | ||||
96 | } | ||||
97 | | ||||
98 | debug() << "got" << suggestions.size() << "suggestions"; | ||||
99 | | ||||
100 | if( !suggestions.isEmpty() ) | ||||
101 | emit newSuggestions( suggestions ); | ||||
102 | | ||||
103 | return; | ||||
104 | } | ||||
105 | } | ||||
106 | | ||||
107 | if( xml.hasError() ) | ||||
108 | { | ||||
109 | warning() << "errors occurred during reading lyrics xml result:" << xml.errorString(); | ||||
110 | emit error( i18n("Lyrics data could not be parsed") ); | ||||
111 | } | ||||
112 | } | ||||
113 | | ||||
114 | void LyricsManager::loadLyrics( Meta::TrackPtr track, bool overwrite ) | ||||
115 | { | ||||
116 | DEBUG_BLOCK | ||||
117 | | ||||
118 | if( !track ) | ||||
119 | { | ||||
120 | debug() << "no current track"; | ||||
121 | return; | ||||
122 | } | ||||
123 | | ||||
124 | // -- get current title and artist | ||||
125 | QString title = track->name(); | ||||
126 | QString artist = track->artist() ? track->artist()->name() : QString(); | ||||
127 | | ||||
128 | sanitizeTitle( title ); | ||||
129 | sanitizeArtist( artist ); | ||||
130 | | ||||
131 | if( !isEmpty( track->cachedLyrics() ) && !overwrite ) | ||||
132 | { | ||||
133 | debug() << "Lyrics already cached."; | ||||
134 | return; | ||||
135 | } | ||||
136 | | ||||
137 | QUrl url( APIURL + artist + ':' + title ); | ||||
138 | m_trackMap.insert( url, track ); | ||||
139 | NetworkAccessManagerProxy::instance()->getData( url, this, &LyricsManager::lyricsLoaded ); | ||||
140 | } | ||||
141 | | ||||
142 | void LyricsManager::lyricsLoaded( const QUrl& url, const QByteArray& data, NetworkAccessManagerProxy::Error err ) | ||||
143 | { | ||||
144 | DEBUG_BLOCK | ||||
145 | | ||||
146 | if( err.code ) | ||||
147 | { | ||||
148 | warning() << "A network error occurred:" << err.description; | ||||
149 | return; | ||||
150 | } | ||||
151 | | ||||
152 | Meta::TrackPtr track = m_trackMap.take( url ); | ||||
153 | if( !track ) | ||||
154 | { | ||||
155 | warning() << "No track belongs to this url:" << url.url(); | ||||
156 | return; | ||||
157 | } | ||||
158 | | ||||
159 | QDomDocument document; | ||||
160 | document.setContent( data ); | ||||
161 | auto list = document.elementsByTagName( QStringLiteral( "rev" ) ); | ||||
162 | if( list.isEmpty() ) | ||||
163 | { | ||||
164 | if( track->album() && track->album()->albumArtist() ) | ||||
165 | { | ||||
166 | QString albumArtist = track->album()->albumArtist()->name(); | ||||
167 | QString artist = track->artist() ? track->artist()->name() : QString(); | ||||
168 | QString title = track->name(); | ||||
169 | sanitizeTitle( title ); | ||||
170 | sanitizeArtist( artist ); | ||||
171 | sanitizeArtist( albumArtist ); | ||||
172 | | ||||
173 | //Try with album artist | ||||
174 | if( url == QUrl( APIURL + artist + ':' + title ) && albumArtist != artist ) | ||||
175 | { | ||||
176 | debug() << "Try again with album artist."; | ||||
177 | | ||||
178 | QUrl newUrl( APIURL + albumArtist + ':' + title ); | ||||
179 | m_trackMap.insert( newUrl, track ); | ||||
180 | NetworkAccessManagerProxy::instance()->getData( newUrl, this, &LyricsManager::lyricsLoaded ); | ||||
181 | return; | ||||
182 | } | ||||
183 | } | ||||
184 | | ||||
185 | debug() << "No lyrics found for track:" << track->name(); | ||||
186 | return; | ||||
187 | } | ||||
188 | | ||||
189 | QString rev = list.at( 0 ).toElement().text(); | ||||
190 | if( rev.contains( QStringLiteral( "lyrics" ) ) ) | ||||
191 | { | ||||
192 | int lindex = rev.indexOf( QStringLiteral( "<lyrics>" ) ); | ||||
193 | int rindex = rev.indexOf( QStringLiteral( "</lyrics>" ) ); | ||||
194 | lyricsResult( (rev.mid( lindex, rindex - lindex ) + "</lyrics>" ).toUtf8(), track ); | ||||
195 | } | ||||
196 | else if( rev.contains( QStringLiteral( "lyric" ) ) ) | ||||
197 | { | ||||
198 | int lindex = rev.indexOf( QStringLiteral( "<lyric>" ) ); | ||||
199 | int rindex = rev.indexOf( QStringLiteral( "</lyric>" ) ); | ||||
200 | lyricsResult( (rev.mid( lindex, rindex - lindex ) + "</lyric>" ).toUtf8(), track ); | ||||
201 | } | ||||
202 | else if( rev.contains( QStringLiteral( "#REDIRECT" ) ) ) | ||||
203 | { | ||||
204 | debug() << "Redirect:" << data; | ||||
205 | | ||||
206 | int lindex = rev.indexOf( QStringLiteral( "#REDIRECT [[" ) ) + 12; | ||||
207 | int rindex = rev.indexOf( QStringLiteral( "]]" ) ); | ||||
208 | QStringList list = rev.mid( lindex, rindex - lindex ).split( ':' ); | ||||
209 | if( list.size() == 2 ) | ||||
210 | { | ||||
211 | list[0] = list[0].replace( '&', QStringLiteral( "%26" ) ); | ||||
212 | list[1] = list[1].replace( '&', QStringLiteral( "%26" ) ); | ||||
213 | QUrl newUrl( APIURL + list.join( ':' ) ); | ||||
214 | m_trackMap.insert( newUrl, track ); | ||||
215 | NetworkAccessManagerProxy::instance()->getData( newUrl, this, &LyricsManager::lyricsLoaded ); | ||||
216 | } | ||||
217 | } | ||||
218 | else | ||||
219 | warning() << "No lyrics found in data:" << data; | ||||
220 | } | ||||
221 | | ||||
222 | void LyricsManager::sanitizeTitle( QString& title ) | ||||
223 | { | ||||
224 | const QString magnatunePreviewString = QLatin1String( "PREVIEW: buy it at www.magnatune.com" ); | ||||
225 | | ||||
226 | if( title.contains(magnatunePreviewString, Qt::CaseSensitive) ) | ||||
227 | title = title.remove( " (" + magnatunePreviewString + ')' ); | ||||
228 | | ||||
229 | title = title.remove( QStringLiteral( "(Live)" ) ); | ||||
230 | title = title.remove( QStringLiteral( "(live)" ) ); | ||||
231 | title = title.replace( '`', QStringLiteral( "'" ) ); | ||||
232 | title = title.replace( '&', QStringLiteral( "%26" ) ); | ||||
233 | } | ||||
234 | | ||||
235 | void LyricsManager::sanitizeArtist( QString& artist ) | ||||
236 | { | ||||
237 | const QString magnatunePreviewString = QLatin1String( "PREVIEW: buy it at www.magnatune.com" ); | ||||
238 | | ||||
239 | if( artist.contains(magnatunePreviewString, Qt::CaseSensitive) ) | ||||
240 | artist = artist.remove( " (" + magnatunePreviewString + ')' ); | ||||
241 | | ||||
242 | // strip "featuring <someone else>" from the artist | ||||
243 | int strip = artist.toLower().indexOf( " ft. "); | ||||
244 | if ( strip != -1 ) | ||||
245 | artist = artist.mid( 0, strip ); | ||||
246 | | ||||
247 | strip = artist.toLower().indexOf( " feat. " ); | ||||
248 | if ( strip != -1 ) | ||||
249 | artist = artist.mid( 0, strip ); | ||||
250 | | ||||
251 | strip = artist.toLower().indexOf( " featuring " ); | ||||
252 | if ( strip != -1 ) | ||||
253 | artist = artist.mid( 0, strip ); | ||||
254 | | ||||
255 | artist = artist.replace( '`', QStringLiteral( "'" ) ); | ||||
256 | artist = artist.replace( '&', QStringLiteral( "%26" ) ); | ||||
257 | } | ||||
258 | | ||||
259 | bool LyricsManager::isEmpty( const QString &lyrics ) const | ||||
260 | { | ||||
261 | QTextEdit testItem; | ||||
262 | | ||||
263 | // Set the text of the TextItem. | ||||
264 | if( Qt::mightBeRichText( lyrics ) ) | ||||
265 | testItem.setHtml( lyrics ); | ||||
266 | else | ||||
267 | testItem.setPlainText( lyrics ); | ||||
268 | | ||||
269 | // Get the plaintext content. | ||||
270 | // We use toPlainText() to strip all Html formatting, | ||||
271 | // so we can test if there's any text given. | ||||
272 | QString testText = testItem.toPlainText().trimmed(); | ||||
273 | | ||||
274 | return testText.isEmpty(); | ||||
275 | } |