Implement downloading cover from LyricWiki
This commit is contained in:
parent
7400534d20
commit
15fc9b5f66
|
@ -22,6 +22,8 @@ SongDescription::SongDescription() :
|
|||
cover(nullptr)
|
||||
{}
|
||||
|
||||
map<QString, QByteArray> QueryResultsModel::m_coverData = map<QString, QByteArray>();
|
||||
|
||||
QueryResultsModel::QueryResultsModel(QObject *parent) :
|
||||
QAbstractTableModel(parent),
|
||||
m_resultsAvailable(false),
|
||||
|
@ -282,7 +284,7 @@ QNetworkReply *HttpResultsModel::evaluateReplyResults(QNetworkReply *reply, QByt
|
|||
m_replies.removeAll(reply);
|
||||
|
||||
if(reply->error() == QNetworkReply::NoError) {
|
||||
QVariant redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
|
||||
const QVariant redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
|
||||
if(!redirectionTarget.isNull()) {
|
||||
// there's a redirection available
|
||||
// -> resolve new URL
|
||||
|
@ -328,6 +330,35 @@ void HttpResultsModel::abort()
|
|||
}
|
||||
}
|
||||
|
||||
void HttpResultsModel::handleCoverReplyFinished(QNetworkReply *reply, const QString &albumId, int row)
|
||||
{
|
||||
QByteArray data;
|
||||
if(auto *newReply = evaluateReplyResults(reply, data, true)) {
|
||||
addReply(newReply, bind(&HttpResultsModel::handleCoverReplyFinished, this, newReply, albumId, row));
|
||||
} else {
|
||||
if(!data.isEmpty()) {
|
||||
parseCoverResults(albumId, row, data);
|
||||
}
|
||||
setResultsAvailable(true);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpResultsModel::parseCoverResults(const QString &albumId, int row, const QByteArray &data)
|
||||
{
|
||||
// add cover -> determine album ID and row
|
||||
if(!albumId.isEmpty() && row < m_results.size()) {
|
||||
if(!data.isEmpty()) {
|
||||
m_coverData[albumId] = data;
|
||||
m_results[row].cover = data;
|
||||
emit coverAvailable(index(row, 0));
|
||||
}
|
||||
} else {
|
||||
m_errorList << tr("Internal error: context for cover reply invalid");
|
||||
setResultsAvailable(true);
|
||||
}
|
||||
setFetchingCover(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#include "dbquery.moc"
|
||||
|
|
|
@ -35,6 +35,7 @@ struct SongDescription
|
|||
int32 disk;
|
||||
QByteArray cover;
|
||||
QString lyrics;
|
||||
QString coverUrl;
|
||||
};
|
||||
|
||||
class QueryResultsModel : public QAbstractTableModel
|
||||
|
@ -83,6 +84,7 @@ protected:
|
|||
QStringList m_errorList;
|
||||
bool m_resultsAvailable;
|
||||
bool m_fetchingCover;
|
||||
static std::map<QString, QByteArray> m_coverData;
|
||||
};
|
||||
|
||||
inline const QList<SongDescription> &QueryResultsModel::results() const
|
||||
|
@ -119,6 +121,9 @@ protected:
|
|||
virtual void parseInitialResults(const QByteArray &data) = 0;
|
||||
QNetworkReply *evaluateReplyResults(QNetworkReply *reply, QByteArray &data, bool alwaysFollowRedirection = false);
|
||||
|
||||
void handleCoverReplyFinished(QNetworkReply *reply, const QString &albumId, int row);
|
||||
void parseCoverResults(const QString &albumId, int row, const QByteArray &data);
|
||||
|
||||
private slots:
|
||||
void handleInitialReplyFinished();
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <QNetworkRequest>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QTextDocument>
|
||||
#include <QStringBuilder>
|
||||
|
||||
#include <functional>
|
||||
|
||||
|
@ -29,6 +30,39 @@ LyricsWikiaResultsModel::LyricsWikiaResultsModel(SongDescription &&initialSongDe
|
|||
HttpResultsModel(move(initialSongDescription), reply)
|
||||
{}
|
||||
|
||||
bool LyricsWikiaResultsModel::fetchCover(const QModelIndex &index)
|
||||
{
|
||||
if(!index.parent().isValid() && index.row() < m_results.size()) {
|
||||
SongDescription &desc = m_results[index.row()];
|
||||
if(!desc.cover.isEmpty()) {
|
||||
// cover is already available -> nothing to do
|
||||
} else if(!desc.albumId.isEmpty()) {
|
||||
try {
|
||||
// the item belongs to an album which cover has already been fetched
|
||||
desc.cover = m_coverData.at(desc.albumId);
|
||||
} catch(const out_of_range &) {
|
||||
if(desc.coverUrl.isEmpty()) {
|
||||
// request the cover URL
|
||||
auto *reply = requestAlbumDetails(desc);
|
||||
addReply(reply, bind(&LyricsWikiaResultsModel::handleAlbumDetailsReplyFinished, this, reply, index.row()));
|
||||
setFetchingCover(true);
|
||||
return false;
|
||||
} else {
|
||||
// request the cover art
|
||||
auto *reply = networkAccessManager().get(QNetworkRequest(QUrl(desc.coverUrl)));
|
||||
addReply(reply, bind(&LyricsWikiaResultsModel::handleCoverReplyFinished, this, reply, desc.albumId, index.row()));
|
||||
setFetchingCover(true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_errorList << tr("Unable to fetch cover: Album ID unknown");
|
||||
emit resultsAvailable();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LyricsWikiaResultsModel::fetchLyrics(const QModelIndex &index)
|
||||
{
|
||||
if(!index.parent().isValid() && index.row() < m_results.size()) {
|
||||
|
@ -49,7 +83,7 @@ bool LyricsWikiaResultsModel::fetchLyrics(const QModelIndex &index)
|
|||
|
||||
void LyricsWikiaResultsModel::parseInitialResults(const QByteArray &data)
|
||||
{
|
||||
// prepare parsing MusicBrainz meta data
|
||||
// prepare parsing LyricsWikia meta data
|
||||
beginResetModel();
|
||||
m_results.clear();
|
||||
QXmlStreamReader xmlReader(data);
|
||||
|
@ -101,7 +135,11 @@ void LyricsWikiaResultsModel::parseInitialResults(const QByteArray &data)
|
|||
} else_skip
|
||||
}
|
||||
for(SongDescription &song : m_results) {
|
||||
// set the arist which is the same for all results
|
||||
song.artist = artist;
|
||||
// set the album ID (album is identified by its artist, year and name)
|
||||
song.albumId = artist % QChar(':') % song.album % QChar('_') % QChar('(') % song.year % QChar(')');
|
||||
song.albumId.replace(QChar(' '), QChar('_'));
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
|
@ -131,7 +169,7 @@ QNetworkReply *LyricsWikiaResultsModel::requestSongDetails(const SongDescription
|
|||
query.addQueryItem(QStringLiteral("artist"), songDescription.artist);
|
||||
query.addQueryItem(QStringLiteral("title"), songDescription.title);
|
||||
if(!songDescription.album.isEmpty()) {
|
||||
// specifying album seems to have no effect but also don't hurt
|
||||
// specifying album seems to have no effect but also doesn't hurt
|
||||
query.addQueryItem(QStringLiteral("album"), songDescription.album);
|
||||
}
|
||||
QUrl url(lyricsWikiaApiUrl());
|
||||
|
@ -140,6 +178,13 @@ QNetworkReply *LyricsWikiaResultsModel::requestSongDetails(const SongDescription
|
|||
return Utility::networkAccessManager().get(QNetworkRequest(url));
|
||||
}
|
||||
|
||||
QNetworkReply *LyricsWikiaResultsModel::requestAlbumDetails(const SongDescription &songDescription)
|
||||
{
|
||||
QUrl url(lyricsWikiaApiUrl());
|
||||
url.setPath(QStringLiteral("/wiki/") + songDescription.albumId);
|
||||
return Utility::networkAccessManager().get(QNetworkRequest(url));
|
||||
}
|
||||
|
||||
void LyricsWikiaResultsModel::handleSongDetailsFinished(QNetworkReply *reply, int row)
|
||||
{
|
||||
QByteArray data;
|
||||
|
@ -237,22 +282,80 @@ void LyricsWikiaResultsModel::parseLyricsResults(int row, const QByteArray &data
|
|||
}
|
||||
SongDescription &assocDesc = m_results[row];
|
||||
|
||||
// convert data to QString
|
||||
const QString html(data);
|
||||
|
||||
// parse lyrics from HTML
|
||||
QString html(data);
|
||||
int lyricsStart = html.indexOf("<div class='lyricbox'>");
|
||||
if(!lyricsStart) {
|
||||
const int lyricsStart = html.indexOf(QLatin1String("<div class='lyricbox'>"));
|
||||
if(lyricsStart > 0) {
|
||||
m_errorList << tr("Song details requested for %1/%2 do not contain lyrics").arg(assocDesc.artist, assocDesc.title);
|
||||
setResultsAvailable(true);
|
||||
return;
|
||||
}
|
||||
int lyricsEnd = html.indexOf("<div class='lyricsbreak'></div>", lyricsStart);
|
||||
const int lyricsEnd = html.indexOf(QLatin1String("<div class='lyricsbreak'></div>"), lyricsStart);
|
||||
QTextDocument textDoc;
|
||||
textDoc.setHtml(html.mid(lyricsStart, (lyricsEnd > lyricsStart) ? (lyricsEnd - lyricsStart) : -1));
|
||||
|
||||
assocDesc.lyrics = textDoc.toPlainText();
|
||||
|
||||
emit lyricsAvailable(index(row, 0));
|
||||
}
|
||||
|
||||
void LyricsWikiaResultsModel::handleAlbumDetailsReplyFinished(QNetworkReply *reply, int row)
|
||||
{
|
||||
QByteArray data;
|
||||
if(auto *newReply = evaluateReplyResults(reply, data, true)) {
|
||||
addReply(newReply, bind(&LyricsWikiaResultsModel::handleAlbumDetailsReplyFinished, this, newReply, row));
|
||||
} else {
|
||||
parseAlbumDetailsAndFetchCover(row, data);
|
||||
}
|
||||
}
|
||||
|
||||
void LyricsWikiaResultsModel::parseAlbumDetailsAndFetchCover(int row, const QByteArray &data)
|
||||
{
|
||||
// check whether the request failed (sufficient error message already emitted in this case)
|
||||
if(data.isEmpty()) {
|
||||
setFetchingCover(false);
|
||||
setResultsAvailable(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// find associated result/desc
|
||||
if(row >= m_results.size()) {
|
||||
m_errorList << tr("Internal error: context for LyricsWikia page reply invalid");
|
||||
setFetchingCover(false);
|
||||
setResultsAvailable(true);
|
||||
return;
|
||||
}
|
||||
SongDescription &assocDesc = m_results[row];
|
||||
|
||||
// convert data to QString
|
||||
const QString html(data);
|
||||
|
||||
// parse cover URL from HTML
|
||||
const int coverDivStart = html.indexOf(QLatin1String("<div class=\"plainlinks\" style=\"clear:right; float:right;")) + 56;
|
||||
if(coverDivStart > 56) {
|
||||
const int coverHrefStart = html.indexOf(QLatin1String("href=\""), coverDivStart) + 6;
|
||||
if(coverHrefStart > coverDivStart + 6) {
|
||||
const int coverHrefEnd = html.indexOf(QLatin1String("\""), coverHrefStart);
|
||||
if(coverHrefEnd > 0) {
|
||||
assocDesc.coverUrl = html.mid(coverHrefStart, coverHrefEnd - coverHrefStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle error case (cover URL not found)
|
||||
if(assocDesc.coverUrl.isEmpty()) {
|
||||
m_errorList << tr("Album details requested for %1/%2 do not contain cover").arg(assocDesc.artist, assocDesc.album);
|
||||
setFetchingCover(false);
|
||||
setResultsAvailable(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// request the cover art
|
||||
auto *reply = networkAccessManager().get(QNetworkRequest(QUrl(assocDesc.coverUrl)));
|
||||
addReply(reply, bind(&LyricsWikiaResultsModel::handleCoverReplyFinished, this, reply, assocDesc.albumId, row));
|
||||
}
|
||||
|
||||
QueryResultsModel *queryLyricsWikia(SongDescription &&songDescription)
|
||||
{
|
||||
// compose URL
|
||||
|
|
|
@ -13,6 +13,7 @@ class LyricsWikiaResultsModel : public HttpResultsModel
|
|||
|
||||
public:
|
||||
LyricsWikiaResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply);
|
||||
bool fetchCover(const QModelIndex &index);
|
||||
bool fetchLyrics(const QModelIndex &index);
|
||||
|
||||
protected:
|
||||
|
@ -20,11 +21,13 @@ protected:
|
|||
|
||||
private:
|
||||
QNetworkReply *requestSongDetails(const SongDescription &songDescription);
|
||||
QNetworkReply *requestAlbumDetails(const SongDescription &songDescription);
|
||||
void handleSongDetailsFinished(QNetworkReply *reply, int row);
|
||||
void parseSongDetails(int row, const QByteArray &data);
|
||||
void handleLyricsReplyFinished(QNetworkReply *reply, int row);
|
||||
void parseLyricsResults(int row, const QByteArray &data);
|
||||
|
||||
void handleAlbumDetailsReplyFinished(QNetworkReply *reply, int row);
|
||||
void parseAlbumDetailsAndFetchCover(int row, const QByteArray &data);
|
||||
};
|
||||
|
||||
} // namespace QtGui
|
||||
|
|
|
@ -18,8 +18,6 @@ using namespace Utility;
|
|||
|
||||
namespace QtGui {
|
||||
|
||||
map<QString, QByteArray> MusicBrainzResultsModel::m_coverData = map<QString, QByteArray>();
|
||||
|
||||
MusicBrainzResultsModel::MusicBrainzResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply) :
|
||||
HttpResultsModel(move(initialSongDescription), reply)
|
||||
{}
|
||||
|
@ -155,35 +153,6 @@ void MusicBrainzResultsModel::parseInitialResults(const QByteArray &data)
|
|||
endResetModel();
|
||||
}
|
||||
|
||||
void MusicBrainzResultsModel::handleCoverReplyFinished(QNetworkReply *reply, const QString &albumId, int row)
|
||||
{
|
||||
QByteArray data;
|
||||
if(auto *newReply = evaluateReplyResults(reply, data, true)) {
|
||||
addReply(newReply, bind(&MusicBrainzResultsModel::handleCoverReplyFinished, this, newReply, albumId, row));
|
||||
} else {
|
||||
if(!data.isEmpty()) {
|
||||
parseCoverResults(albumId, row, data);
|
||||
}
|
||||
setResultsAvailable(true);
|
||||
}
|
||||
}
|
||||
|
||||
void MusicBrainzResultsModel::parseCoverResults(const QString &albumId, int row, const QByteArray &data)
|
||||
{
|
||||
// add cover -> determine album ID and row
|
||||
if(!albumId.isEmpty() && row < m_results.size()) {
|
||||
if(!data.isEmpty()) {
|
||||
m_coverData[albumId] = data;
|
||||
m_results[row].cover = data;
|
||||
emit coverAvailable(index(row, 0));
|
||||
}
|
||||
} else {
|
||||
m_errorList << tr("Internal error: context for cover reply invalid");
|
||||
setResultsAvailable(true);
|
||||
}
|
||||
setFetchingCover(false);
|
||||
}
|
||||
|
||||
QueryResultsModel *queryMusicBrainz(SongDescription &&songDescription)
|
||||
{
|
||||
static const QString defaultMusicBrainzUrl(QStringLiteral("https://musicbrainz.org/ws/2/recording/"));
|
||||
|
|
|
@ -26,11 +26,6 @@ protected:
|
|||
void parseInitialResults(const QByteArray &data);
|
||||
|
||||
private:
|
||||
void handleCoverReplyFinished(QNetworkReply *reply, const QString &albumId, int row);
|
||||
void parseCoverResults(const QString &albumId, int row, const QByteArray &data);
|
||||
|
||||
private:
|
||||
static std::map<QString, QByteArray> m_coverData;
|
||||
What m_what;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue