Allow retrieving lyrics from LyricsWikia
This commit is contained in:
parent
958cd9960c
commit
0062384bad
|
@ -265,7 +265,8 @@ KnownFieldModel &dbQueryFields()
|
|||
<< KnownFieldModel::mkItem(KnownField::Album)
|
||||
<< KnownFieldModel::mkItem(KnownField::Year)
|
||||
<< KnownFieldModel::mkItem(KnownField::Genre)
|
||||
<< KnownFieldModel::mkItem(KnownField::Cover, Qt::Unchecked));
|
||||
<< KnownFieldModel::mkItem(KnownField::Cover, Qt::Unchecked)
|
||||
<< KnownFieldModel::mkItem(KnownField::Lyrics, Qt::Unchecked));
|
||||
return v;
|
||||
}
|
||||
|
||||
|
|
|
@ -78,6 +78,8 @@ TagValue QueryResultsModel::fieldValue(int row, KnownField knownField) const
|
|||
return tagValue;
|
||||
}
|
||||
break;
|
||||
case KnownField::Lyrics:
|
||||
returnValue(lyrics);
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
@ -195,18 +197,51 @@ const QByteArray *QueryResultsModel::cover(const QModelIndex &index) const
|
|||
|
||||
/*!
|
||||
* \brief Fetches the cover the specified \a index.
|
||||
* \returns True if the cover is immidiately available; false is the cover is fetched asynchronously.
|
||||
* \returns
|
||||
* - true if the cover is immidiately available or an error occurs immidiately
|
||||
* - and false if the cover will be fetched asynchronously.
|
||||
*
|
||||
* If the cover is fetched asynchronously the coverAvailable() signal is emitted, when the cover
|
||||
* is available.
|
||||
* becomes available.
|
||||
*
|
||||
* The resultsAvailable() signal is emitted if errors have been added to errorList().
|
||||
*/
|
||||
bool QueryResultsModel::fetchCover(const QModelIndex &)
|
||||
bool QueryResultsModel::fetchCover(const QModelIndex &index)
|
||||
{
|
||||
m_errorList << tr("Fetching the cover is not implemented for the selected provider.");
|
||||
Q_UNUSED(index)
|
||||
m_errorList << tr("Fetching cover is not implemented for this provider");
|
||||
emit resultsAvailable();
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const QString *QueryResultsModel::lyrics(const QModelIndex &index) const
|
||||
{
|
||||
if(!index.parent().isValid() && index.row() < m_results.size()) {
|
||||
const QString &lyrics = m_results.at(index.row()).lyrics;
|
||||
if(!lyrics.isEmpty()) {
|
||||
return &lyrics;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Fetches the lyrics the specified \a index.
|
||||
* \returns
|
||||
* - true if the lyrics are immidiately available or an error occurs immidiately
|
||||
* - and false if the lyrics will be fetched asynchronously.
|
||||
*
|
||||
* If the lyrics are fetched asynchronously the lyricsAvailable() signal is emitted, when the lyrics
|
||||
* become available.
|
||||
*
|
||||
* The resultsAvailable() signal is emitted if errors have been added to errorList().
|
||||
*/
|
||||
bool QueryResultsModel::fetchLyrics(const QModelIndex &index)
|
||||
{
|
||||
Q_UNUSED(index)
|
||||
m_errorList << tr("Fetching lyrics is not implemented for this provider");
|
||||
emit resultsAvailable();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -216,7 +251,7 @@ bool QueryResultsModel::fetchCover(const QModelIndex &)
|
|||
HttpResultsModel::HttpResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply) :
|
||||
m_initialDescription(initialSongDescription)
|
||||
{
|
||||
addReply(reply);
|
||||
addReply(reply, this, &HttpResultsModel::handleInitialReplyFinished);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -227,77 +262,61 @@ HttpResultsModel::~HttpResultsModel()
|
|||
qDeleteAll(m_replies);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Helper to copy a property from one QObject to another.
|
||||
* \remarks Used to transfer reply properties to new reply in case a second reply is required.
|
||||
*/
|
||||
void copyProperty(const char *property, const QObject *from, QObject *to)
|
||||
{
|
||||
to->setProperty(property, from->property(property));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Evaluates request results.
|
||||
* \remarks Calls parseResults() if the requested data is available. Handles errors/redirections otherwise.
|
||||
*/
|
||||
void HttpResultsModel::handleReplyFinished()
|
||||
void HttpResultsModel::handleInitialReplyFinished()
|
||||
{
|
||||
auto *reply = static_cast<QNetworkReply *>(sender());
|
||||
QByteArray data;
|
||||
if(auto *newReply = evaluateReplyResults(reply, data, false)) {
|
||||
addReply(newReply, this, &HttpResultsModel::handleInitialReplyFinished);
|
||||
} else {
|
||||
if(!data.isEmpty()) {
|
||||
parseInitialResults(data);
|
||||
}
|
||||
setResultsAvailable(true); // update status, emit resultsAvailable()
|
||||
}
|
||||
}
|
||||
|
||||
QNetworkReply *HttpResultsModel::evaluateReplyResults(QNetworkReply *reply, QByteArray &data, bool alwaysFollowRedirection)
|
||||
{
|
||||
// delete reply (later)
|
||||
reply->deleteLater();
|
||||
m_replies.removeAll(reply);
|
||||
|
||||
if(reply->error() == QNetworkReply::NoError) {
|
||||
QVariant redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
|
||||
if(!redirectionTarget.isNull()) {
|
||||
// there's a redirection available
|
||||
// -> resolve new URL
|
||||
const QUrl newUrl = reply->url().resolved(redirectionTarget.toUrl());
|
||||
// -> always follow when retrieving cover art, otherwise ask user
|
||||
bool follow = reply->property("coverArt").toBool();
|
||||
if(!follow) {
|
||||
// -> ask user whether to follow redirection unless alwaysFollowRedirection is true
|
||||
if(!alwaysFollowRedirection) {
|
||||
const QString message = tr("<p>Do you want to redirect form <i>%1</i> to <i>%2</i>?</p>").arg(
|
||||
reply->url().toString(), newUrl.toString());
|
||||
follow = QMessageBox::question(nullptr, tr("Search"), message, QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes;
|
||||
alwaysFollowRedirection = QMessageBox::question(nullptr, tr("Search"), message, QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes;
|
||||
}
|
||||
if(follow) {
|
||||
QNetworkReply *newReply = networkAccessManager().get(QNetworkRequest(newUrl));
|
||||
// retain possible assigned dynamic properties (TODO: implement it in a better way)
|
||||
copyProperty("coverArt", reply, newReply);
|
||||
copyProperty("albumId", reply, newReply);
|
||||
copyProperty("row", reply, newReply);
|
||||
addReply(newReply);
|
||||
return;
|
||||
if(alwaysFollowRedirection) {
|
||||
return networkAccessManager().get(QNetworkRequest(newUrl));
|
||||
} else {
|
||||
m_errorList << tr("Redirection to: ") + newUrl.toString();
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
QByteArray data = reply->readAll();
|
||||
if((data = reply->readAll()).isEmpty()) {
|
||||
m_errorList << tr("Server replied no data.");
|
||||
}
|
||||
#ifdef DEBUG_BUILD
|
||||
std::cerr << "Results from HTTP query:" << std::endl;
|
||||
std::cerr << data.data() << std::endl;
|
||||
cerr << "Results from HTTP query:" << endl;
|
||||
cerr << data.data() << endl;
|
||||
#endif
|
||||
parseResults(reply, data);
|
||||
}
|
||||
} else {
|
||||
m_errorList << reply->errorString();
|
||||
}
|
||||
|
||||
// delete reply
|
||||
reply->deleteLater();
|
||||
m_replies.removeAll(reply);
|
||||
|
||||
// update status, emit resultsAvailable()
|
||||
setResultsAvailable(true);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Adds a reply.
|
||||
* \remarks Called within c'tor and handleReplyFinished() in case of redirection. Might be called when subclassing to do further requests.
|
||||
*/
|
||||
void HttpResultsModel::addReply(QNetworkReply *reply)
|
||||
{
|
||||
m_replies << reply;
|
||||
#ifdef DEBUG_BUILD
|
||||
std::cerr << "HTTP query: " << reply->url().toString().toLocal8Bit().data() << std::endl;
|
||||
#endif
|
||||
connect(reply, &QNetworkReply::finished, this, &HttpResultsModel::handleReplyFinished);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
|
||||
#include <QAbstractTableModel>
|
||||
|
||||
#ifdef DEBUG_BUILD
|
||||
# include <QNetworkReply>
|
||||
# include <iostream>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QNetworkReply)
|
||||
|
||||
namespace Media {
|
||||
|
@ -28,6 +33,7 @@ struct SongDescription
|
|||
unsigned int totalTracks;
|
||||
unsigned int disk;
|
||||
QByteArray cover;
|
||||
QString lyrics;
|
||||
};
|
||||
|
||||
class QueryResultsModel : public QAbstractTableModel
|
||||
|
@ -58,11 +64,14 @@ public:
|
|||
int columnCount(const QModelIndex &parent) const;
|
||||
const QByteArray *cover(const QModelIndex &index) const;
|
||||
virtual bool fetchCover(const QModelIndex &index);
|
||||
const QString *lyrics(const QModelIndex &index) const;
|
||||
virtual bool fetchLyrics(const QModelIndex &index);
|
||||
virtual void abort();
|
||||
|
||||
signals:
|
||||
void resultsAvailable();
|
||||
void coverAvailable(const QModelIndex &index);
|
||||
void lyricsAvailable(const QModelIndex &index);
|
||||
|
||||
protected:
|
||||
QueryResultsModel(QObject *parent = nullptr);
|
||||
|
@ -104,17 +113,41 @@ public:
|
|||
|
||||
protected:
|
||||
HttpResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply);
|
||||
void addReply(QNetworkReply *reply);
|
||||
virtual void parseResults(QNetworkReply *reply, const QByteArray &data) = 0;
|
||||
template<class Object, class Function> void addReply(QNetworkReply *reply, Object object, Function handler);
|
||||
template<class Function> void addReply(QNetworkReply *reply, Function handler);
|
||||
virtual void parseInitialResults(const QByteArray &data) = 0;
|
||||
QNetworkReply *evaluateReplyResults(QNetworkReply *reply, QByteArray &data, bool alwaysFollowRedirection = false);
|
||||
|
||||
private slots:
|
||||
void handleReplyFinished();
|
||||
void handleInitialReplyFinished();
|
||||
|
||||
protected:
|
||||
QList<QNetworkReply *> m_replies;
|
||||
const SongDescription m_initialDescription;
|
||||
};
|
||||
|
||||
template<class Object, class Function>
|
||||
inline void HttpResultsModel::addReply(QNetworkReply *reply, Object object, Function handler)
|
||||
{
|
||||
(m_replies << reply), connect(reply, &QNetworkReply::finished, object, handler);
|
||||
#ifdef DEBUG_BUILD
|
||||
std::cerr << "HTTP query: " << reply->url().toString().toLocal8Bit().data() << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Adds a reply.
|
||||
* \remarks Called within c'tor and handleReplyFinished() in case of redirection. Might be called when subclassing to do further requests.
|
||||
*/
|
||||
template<class Function>
|
||||
inline void HttpResultsModel::addReply(QNetworkReply *reply, Function handler)
|
||||
{
|
||||
(m_replies << reply), connect(reply, &QNetworkReply::finished, handler);
|
||||
#ifdef DEBUG_BUILD
|
||||
std::cerr << "HTTP query: " << reply->url().toString().toLocal8Bit().data() << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
QueryResultsModel *queryMusicBrainz(SongDescription &&songDescription);
|
||||
QueryResultsModel *queryLyricsWikia(SongDescription &&songDescription);
|
||||
QNetworkReply *queryCoverArtArchive(const QString &albumId);
|
||||
|
|
|
@ -6,27 +6,49 @@
|
|||
#include <QUrlQuery>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QTextDocument>
|
||||
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::placeholders;
|
||||
using namespace Utility;
|
||||
|
||||
namespace QtGui {
|
||||
|
||||
static const QString defaultLyricsWikiaUrl(QStringLiteral("https://lyrics.wikia.com"));
|
||||
|
||||
LyricsWikiaResultsModel::LyricsWikiaResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply) :
|
||||
HttpResultsModel(move(initialSongDescription), reply)
|
||||
{}
|
||||
|
||||
void LyricsWikiaResultsModel::parseResults(QNetworkReply *reply, const QByteArray &data)
|
||||
bool LyricsWikiaResultsModel::fetchLyrics(const QModelIndex &index)
|
||||
{
|
||||
Q_UNUSED(reply)
|
||||
if(!index.parent().isValid() && index.row() < m_results.size()) {
|
||||
SongDescription &desc = m_results[index.row()];
|
||||
if(!desc.lyrics.isEmpty()) {
|
||||
// lyrics already available -> nothing to do
|
||||
} else if(!desc.artist.isEmpty() && !desc.title.isEmpty()) {
|
||||
auto *reply = requestSongDetails(desc);
|
||||
addReply(reply, bind(&LyricsWikiaResultsModel::handleSongDetailsFinished, this, reply, index.row()));
|
||||
return false;
|
||||
} else {
|
||||
m_errorList << tr("Unable to fetch lyrics: Artist or title is unknown.");
|
||||
emit resultsAvailable();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LyricsWikiaResultsModel::parseInitialResults(const QByteArray &data)
|
||||
{
|
||||
// prepare parsing MusicBrainz meta data
|
||||
beginResetModel();
|
||||
m_results.clear();
|
||||
m_reader.addData(data);
|
||||
QXmlStreamReader xmlReader(data);
|
||||
|
||||
// parse XML tree
|
||||
#define xmlReader m_reader
|
||||
#include <qtutilities/misc/xmlparsermacros.h>
|
||||
children {
|
||||
iftag("getArtistResponse") {
|
||||
|
@ -53,18 +75,18 @@ void LyricsWikiaResultsModel::parseResults(QNetworkReply *reply, const QByteArra
|
|||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
// need to filter results manually because the filtering provided by Lyrica Wiki API doesn't work
|
||||
if((m_initialDescription.album.isEmpty() || m_initialDescription.album == album)
|
||||
&& (m_initialDescription.year.isEmpty() || m_initialDescription.year == year)
|
||||
&& (!m_initialDescription.totalTracks || m_initialDescription.totalTracks == static_cast<unsigned int>(songs.size()))) {
|
||||
for(SongDescription &song : songs) {
|
||||
if((m_initialDescription.title.isEmpty() || m_initialDescription.title == song.title)
|
||||
&& (!m_initialDescription.track || m_initialDescription.track == static_cast<unsigned int>(songs.size()))) {
|
||||
song.album = album;
|
||||
song.year = year;
|
||||
song.totalTracks = static_cast<unsigned int>(songs.size());
|
||||
m_results << move(song);
|
||||
}
|
||||
}
|
||||
// need to filter results manually because the filtering provided by Lyrica Wiki API doesn't work
|
||||
if((m_initialDescription.album.isEmpty() || m_initialDescription.album == album)
|
||||
&& (m_initialDescription.year.isEmpty() || m_initialDescription.year == year)
|
||||
&& (!m_initialDescription.totalTracks || m_initialDescription.totalTracks == static_cast<unsigned int>(songs.size()))) {
|
||||
for(SongDescription &song : songs) {
|
||||
if((m_initialDescription.title.isEmpty() || m_initialDescription.title == song.title)
|
||||
&& (!m_initialDescription.track || m_initialDescription.track == song.track)) {
|
||||
song.album = album;
|
||||
song.year = year;
|
||||
song.totalTracks = static_cast<unsigned int>(songs.size());
|
||||
m_results << move(song);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,24 +102,155 @@ void LyricsWikiaResultsModel::parseResults(QNetworkReply *reply, const QByteArra
|
|||
#include <qtutilities/misc/undefxmlparsermacros.h>
|
||||
|
||||
// check for parsing errors
|
||||
switch(m_reader.error()) {
|
||||
switch(xmlReader.error()) {
|
||||
case QXmlStreamReader::NoError:
|
||||
case QXmlStreamReader::PrematureEndOfDocumentError:
|
||||
break;
|
||||
default:
|
||||
m_errorList << m_reader.errorString();
|
||||
m_errorList << xmlReader.errorString();
|
||||
}
|
||||
|
||||
// promote changes
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QNetworkReply *LyricsWikiaResultsModel::requestSongDetails(const SongDescription &songDescription)
|
||||
{
|
||||
// compose URL
|
||||
QUrl url((Settings::lyricsWikiaUrl().isEmpty() ? defaultLyricsWikiaUrl : Settings::lyricsWikiaUrl()) + QStringLiteral("/api.php"));
|
||||
QUrlQuery query;
|
||||
query.addQueryItem(QStringLiteral("func"), QStringLiteral("getSong"));
|
||||
query.addQueryItem(QStringLiteral("action"), QStringLiteral("lyrics"));
|
||||
query.addQueryItem(QStringLiteral("fmt"), QStringLiteral("xml"));
|
||||
query.addQueryItem(QStringLiteral("fixXML"), QString());
|
||||
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
|
||||
query.addQueryItem(QStringLiteral("album"), songDescription.album);
|
||||
}
|
||||
url.setQuery(query);
|
||||
|
||||
return Utility::networkAccessManager().get(QNetworkRequest(url));
|
||||
}
|
||||
|
||||
void LyricsWikiaResultsModel::handleSongDetailsFinished(QNetworkReply *reply, int row)
|
||||
{
|
||||
QByteArray data;
|
||||
if(auto *newReply = evaluateReplyResults(reply, data, true)) {
|
||||
addReply(newReply, bind(&LyricsWikiaResultsModel::handleSongDetailsFinished, this, newReply, row));
|
||||
} else if(!data.isEmpty()) {
|
||||
parseSongDetails(row, data);
|
||||
}
|
||||
}
|
||||
|
||||
void LyricsWikiaResultsModel::parseSongDetails(int row, const QByteArray &data)
|
||||
{
|
||||
// find associated result/desc
|
||||
if(row >= m_results.size()) {
|
||||
m_errorList << tr("Internal error: context for song details reply invalid");
|
||||
setResultsAvailable(true);
|
||||
return;
|
||||
}
|
||||
const SongDescription &assocDesc = m_results.at(row);
|
||||
|
||||
QUrl parsedUrl;
|
||||
|
||||
// parse XML tree
|
||||
QXmlStreamReader xmlReader(data);
|
||||
#include <qtutilities/misc/xmlparsermacros.h>
|
||||
children {
|
||||
iftag("LyricsResult") {
|
||||
SongDescription parsedDesc;
|
||||
children {
|
||||
iftag("artist") {
|
||||
parsedDesc.artist = text;
|
||||
} eliftag("song") {
|
||||
parsedDesc.title = text;
|
||||
} eliftag("url") {
|
||||
parsedUrl = text;
|
||||
} else_skip
|
||||
}
|
||||
|
||||
// verify whether parsed results match what was requested
|
||||
if(!parsedUrl.isEmpty() && assocDesc.title == parsedDesc.title && assocDesc.artist == parsedDesc.artist) {
|
||||
break;
|
||||
} else {
|
||||
parsedUrl.clear();
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
#include <qtutilities/misc/undefxmlparsermacros.h>
|
||||
|
||||
// check for parsing errors
|
||||
switch(xmlReader.error()) {
|
||||
case QXmlStreamReader::NoError:
|
||||
case QXmlStreamReader::PrematureEndOfDocumentError:
|
||||
break;
|
||||
default:
|
||||
m_errorList << tr("Unable to parse song details: ") + xmlReader.errorString();
|
||||
setResultsAvailable(true);
|
||||
if(parsedUrl.isEmpty()) {
|
||||
return; // don't complain about missing URL when the XML isn't even valid
|
||||
}
|
||||
}
|
||||
|
||||
// requets lyrics (seem to be incomplete in XML response, so just get the regular Wiki page)
|
||||
if(parsedUrl.isEmpty()) {
|
||||
m_errorList << tr("Song details requested for %1/%2 do not contain URL for Wiki page").arg(assocDesc.artist, assocDesc.title);
|
||||
setResultsAvailable(true);
|
||||
return;
|
||||
}
|
||||
// do not use parsed URL directly to avoid unintended requests
|
||||
QUrl requestUrl(Settings::lyricsWikiaUrl().isEmpty() ? defaultLyricsWikiaUrl : Settings::lyricsWikiaUrl());
|
||||
requestUrl.setPath(parsedUrl.path());
|
||||
auto *reply = Utility::networkAccessManager().get(QNetworkRequest(parsedUrl));
|
||||
addReply(reply, bind(&LyricsWikiaResultsModel::handleLyricsReplyFinished, this, reply, row));
|
||||
}
|
||||
|
||||
void LyricsWikiaResultsModel::handleLyricsReplyFinished(QNetworkReply *reply, int row)
|
||||
{
|
||||
QByteArray data;
|
||||
if(auto *newReply = evaluateReplyResults(reply, data, true)) {
|
||||
addReply(newReply, bind(&LyricsWikiaResultsModel::handleLyricsReplyFinished, this, newReply, row));
|
||||
} else {
|
||||
if(!data.isEmpty()) {
|
||||
parseLyricsResults(row, data);
|
||||
}
|
||||
setResultsAvailable(true);
|
||||
}
|
||||
}
|
||||
|
||||
void LyricsWikiaResultsModel::parseLyricsResults(int row, const QByteArray &data)
|
||||
{
|
||||
// find associated result/desc
|
||||
if(row >= m_results.size()) {
|
||||
m_errorList << tr("Internal error: context for LyricsWikia page reply invalid");
|
||||
setResultsAvailable(true);
|
||||
return;
|
||||
}
|
||||
SongDescription &assocDesc = m_results[row];
|
||||
|
||||
// parse lyrics from HTML
|
||||
QString html(data);
|
||||
int lyricsStart = html.indexOf("<div class='lyricbox'>");
|
||||
if(!lyricsStart) {
|
||||
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);
|
||||
QTextDocument textDoc;
|
||||
textDoc.setHtml(html.mid(lyricsStart, (lyricsEnd > lyricsStart) ? (lyricsEnd - lyricsStart) : -1));
|
||||
|
||||
assocDesc.lyrics = textDoc.toPlainText();
|
||||
emit lyricsAvailable(index(row, 0));
|
||||
}
|
||||
|
||||
QueryResultsModel *queryLyricsWikia(SongDescription &&songDescription)
|
||||
{
|
||||
static QString defaultUrl(QStringLiteral("https://lyrics.wikia.com/api.php"));
|
||||
|
||||
// compose URL
|
||||
QUrl url(Settings::lyricsWikiaUrl().isEmpty() ? defaultUrl : Settings::lyricsWikiaUrl());
|
||||
QUrl url((Settings::lyricsWikiaUrl().isEmpty() ? defaultLyricsWikiaUrl : Settings::lyricsWikiaUrl()) + QStringLiteral("/api.php"));
|
||||
QUrlQuery query;
|
||||
query.addQueryItem(QStringLiteral("func"), QStringLiteral("getArtist"));
|
||||
query.addQueryItem(QStringLiteral("fmt"), QStringLiteral("xml"));
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#include "./dbquery.h"
|
||||
|
||||
#include <QXmlStreamReader>
|
||||
#include <map>
|
||||
|
||||
namespace QtGui {
|
||||
|
||||
|
@ -13,12 +13,17 @@ class LyricsWikiaResultsModel : public HttpResultsModel
|
|||
|
||||
public:
|
||||
LyricsWikiaResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply);
|
||||
bool fetchLyrics(const QModelIndex &index);
|
||||
|
||||
protected:
|
||||
void parseResults(QNetworkReply *reply, const QByteArray &data);
|
||||
void parseInitialResults(const QByteArray &data);
|
||||
|
||||
private:
|
||||
QXmlStreamReader m_reader;
|
||||
QNetworkReply *requestSongDetails(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);
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -8,8 +8,12 @@
|
|||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QUrlQuery>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::placeholders;
|
||||
using namespace Utility;
|
||||
|
||||
namespace QtGui {
|
||||
|
@ -32,147 +36,158 @@ bool MusicBrainzResultsModel::fetchCover(const QModelIndex &index)
|
|||
desc.cover = m_coverData.at(desc.albumId);
|
||||
} catch(const out_of_range &) {
|
||||
// request the cover art
|
||||
QNetworkReply *reply = queryCoverArtArchive(desc.albumId);
|
||||
addReply(reply);
|
||||
reply->setProperty("coverArt", true);
|
||||
reply->setProperty("albumId", desc.albumId);
|
||||
reply->setProperty("row", index.row());
|
||||
auto *reply = queryCoverArtArchive(desc.albumId);
|
||||
addReply(reply, bind(&MusicBrainzResultsModel::handleCoverReplyFinished, this, reply, desc.albumId, index.row()));
|
||||
setFetchingCover(true);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
m_errorList << tr("Unable to fetch cover: Album ID is unknown.");
|
||||
m_errorList << tr("Unable to fetch cover: Album ID unknown");
|
||||
emit resultsAvailable();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MusicBrainzResultsModel::parseResults(QNetworkReply *reply, const QByteArray &data)
|
||||
void MusicBrainzResultsModel::parseInitialResults(const QByteArray &data)
|
||||
{
|
||||
if(reply->property("coverArt").toBool()) {
|
||||
// add cover -> determine album ID and row
|
||||
bool ok;
|
||||
QString albumId = reply->property("albumId").toString();
|
||||
int row = reply->property("row").toInt(&ok);
|
||||
if(!albumId.isEmpty() && ok && row < m_results.size()) {
|
||||
// prepare parsing MusicBrainz meta data
|
||||
beginResetModel();
|
||||
m_results.clear();
|
||||
QXmlStreamReader xmlReader(data);
|
||||
|
||||
// parse XML tree
|
||||
#include <qtutilities/misc/xmlparsermacros.h>
|
||||
children {
|
||||
iftag("metadata") {
|
||||
children {
|
||||
iftag("recording-list") {
|
||||
children {
|
||||
iftag("recording") {
|
||||
SongDescription currentDescription;
|
||||
children {
|
||||
iftag("title") {
|
||||
currentDescription.title = text;
|
||||
} eliftag("artist-credit") {
|
||||
children {
|
||||
iftag("name-credit") {
|
||||
children {
|
||||
iftag("artist") {
|
||||
children {
|
||||
iftag("name") {
|
||||
currentDescription.artist = text;
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} eliftag("release-list") {
|
||||
children {
|
||||
iftag("release") {
|
||||
if(currentDescription.albumId.isEmpty()) {
|
||||
currentDescription.albumId = attribute("id").toString();
|
||||
}
|
||||
children {
|
||||
iftag("title") {
|
||||
currentDescription.album = text;
|
||||
} eliftag("date") {
|
||||
currentDescription.year = text;
|
||||
} eliftag("medium-list") {
|
||||
children {
|
||||
iftag("medium") {
|
||||
children {
|
||||
iftag("position") {
|
||||
currentDescription.disk = text.toUInt();
|
||||
} eliftag("track-list") {
|
||||
currentDescription.totalTracks = attribute("count").toUInt();
|
||||
children {
|
||||
iftag("track") {
|
||||
children {
|
||||
iftag("number") {
|
||||
currentDescription.track = text.toUInt();
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} eliftag("tag-list") {
|
||||
children {
|
||||
iftag("tag") {
|
||||
children {
|
||||
iftag("name") {
|
||||
if(!currentDescription.genre.isEmpty()) {
|
||||
currentDescription.genre.append(QLatin1Char(' '));
|
||||
}
|
||||
currentDescription.genre.append(text);
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
m_results << currentDescription;
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
#include <qtutilities/misc/undefxmlparsermacros.h>
|
||||
|
||||
// check for parsing errors
|
||||
switch(xmlReader.error()) {
|
||||
case QXmlStreamReader::NoError:
|
||||
case QXmlStreamReader::PrematureEndOfDocumentError:
|
||||
break;
|
||||
default:
|
||||
m_errorList << xmlReader.errorString();
|
||||
}
|
||||
|
||||
// promote changes
|
||||
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
|
||||
bool ok;
|
||||
if(!albumId.isEmpty() && ok && 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("Cover reply is invalid (internal error).");
|
||||
}
|
||||
setFetchingCover(false);
|
||||
} else {
|
||||
// prepare parsing MusicBrainz meta data
|
||||
beginResetModel();
|
||||
m_results.clear();
|
||||
m_reader.addData(data);
|
||||
|
||||
// parse XML tree
|
||||
#define xmlReader m_reader
|
||||
#include <qtutilities/misc/xmlparsermacros.h>
|
||||
children {
|
||||
iftag("metadata") {
|
||||
children {
|
||||
iftag("recording-list") {
|
||||
children {
|
||||
iftag("recording") {
|
||||
SongDescription currentDescription;
|
||||
children {
|
||||
iftag("title") {
|
||||
currentDescription.title = text;
|
||||
} eliftag("artist-credit") {
|
||||
children {
|
||||
iftag("name-credit") {
|
||||
children {
|
||||
iftag("artist") {
|
||||
children {
|
||||
iftag("name") {
|
||||
currentDescription.artist = text;
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} eliftag("release-list") {
|
||||
children {
|
||||
iftag("release") {
|
||||
if(currentDescription.albumId.isEmpty()) {
|
||||
currentDescription.albumId = attribute("id").toString();
|
||||
}
|
||||
children {
|
||||
iftag("title") {
|
||||
currentDescription.album = text;
|
||||
} eliftag("date") {
|
||||
currentDescription.year = text;
|
||||
} eliftag("medium-list") {
|
||||
children {
|
||||
iftag("medium") {
|
||||
children {
|
||||
iftag("position") {
|
||||
currentDescription.disk = text.toUInt();
|
||||
} eliftag("track-list") {
|
||||
currentDescription.totalTracks = attribute("count").toUInt();
|
||||
children {
|
||||
iftag("track") {
|
||||
children {
|
||||
iftag("number") {
|
||||
currentDescription.track = text.toUInt();
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} eliftag("tag-list") {
|
||||
children {
|
||||
iftag("tag") {
|
||||
children {
|
||||
iftag("name") {
|
||||
if(!currentDescription.genre.isEmpty()) {
|
||||
currentDescription.genre.append(QLatin1Char(' '));
|
||||
}
|
||||
currentDescription.genre.append(text);
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
m_results << currentDescription;
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
} else_skip
|
||||
}
|
||||
#include <qtutilities/misc/undefxmlparsermacros.h>
|
||||
|
||||
// check for parsing errors
|
||||
switch(m_reader.error()) {
|
||||
case QXmlStreamReader::NoError:
|
||||
case QXmlStreamReader::PrematureEndOfDocumentError:
|
||||
break;
|
||||
default:
|
||||
m_errorList << m_reader.errorString();
|
||||
}
|
||||
|
||||
// promote changes
|
||||
endResetModel();
|
||||
m_errorList << tr("Internal error: context for cover reply invalid");
|
||||
setResultsAvailable(true);
|
||||
}
|
||||
setFetchingCover(false);
|
||||
}
|
||||
|
||||
QueryResultsModel *queryMusicBrainz(SongDescription &&songDescription)
|
||||
{
|
||||
static QString defaultMusicBrainzUrl(QStringLiteral("https://musicbrainz.org/ws/2/recording/"));
|
||||
static const QString defaultMusicBrainzUrl(QStringLiteral("https://musicbrainz.org/ws/2/recording/"));
|
||||
|
||||
// compose parts
|
||||
QStringList parts;
|
||||
|
@ -202,7 +217,7 @@ QueryResultsModel *queryMusicBrainz(SongDescription &&songDescription)
|
|||
|
||||
QNetworkReply *queryCoverArtArchive(const QString &albumId)
|
||||
{
|
||||
static QString defaultArchiveUrl(QStringLiteral("https://coverartarchive.org"));
|
||||
static const QString defaultArchiveUrl(QStringLiteral("https://coverartarchive.org"));
|
||||
return networkAccessManager().get(QNetworkRequest(QUrl((Settings::coverArtArchiveUrl().isEmpty() ? defaultArchiveUrl : Settings::coverArtArchiveUrl()) % QStringLiteral("/release/") % albumId % QStringLiteral("/front"))));
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
#include "./dbquery.h"
|
||||
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include <map>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QNetworkRequest)
|
||||
|
@ -23,14 +21,16 @@ private:
|
|||
public:
|
||||
MusicBrainzResultsModel(SongDescription &&initialSongDescription, QNetworkReply *reply);
|
||||
bool fetchCover(const QModelIndex &index);
|
||||
static QNetworkRequest coverRequest(const QString &albumId);
|
||||
|
||||
protected:
|
||||
void parseResults(QNetworkReply *reply, const QByteArray &data);
|
||||
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;
|
||||
QXmlStreamReader m_reader;
|
||||
What m_what;
|
||||
};
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <QDialog>
|
||||
#include <QGraphicsView>
|
||||
#include <QGraphicsItem>
|
||||
#include <QTextBrowser>
|
||||
|
||||
using namespace ConversionUtilities;
|
||||
using namespace Dialogs;
|
||||
|
@ -36,6 +37,7 @@ DbQueryWidget::DbQueryWidget(TagEditorWidget *tagEditorWidget, QWidget *parent)
|
|||
m_tagEditorWidget(tagEditorWidget),
|
||||
m_model(nullptr),
|
||||
m_coverIndex(-1),
|
||||
m_lyricsIndex(-1),
|
||||
m_menu(new QMenu(parent))
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
@ -117,12 +119,22 @@ void DbQueryWidget::insertSearchTermsFromTagEdit(TagEdit *tagEdit)
|
|||
}
|
||||
}
|
||||
|
||||
SongDescription DbQueryWidget::currentSongDescription() const
|
||||
{
|
||||
SongDescription desc;
|
||||
desc.title = m_ui->titleLineEdit->text();
|
||||
desc.album = m_ui->albumLineEdit->text();
|
||||
desc.artist = m_ui->artistLineEdit->text();
|
||||
desc.track = static_cast<unsigned int>(m_ui->trackSpinBox->value());
|
||||
return desc;
|
||||
}
|
||||
|
||||
void DbQueryWidget::searchMusicBrainz()
|
||||
{
|
||||
// check whether enough search terms are supplied
|
||||
if(m_ui->titleLineEdit->text().isEmpty() && m_ui->albumLineEdit->text().isEmpty() && m_ui->artistLineEdit->text().isEmpty()) {
|
||||
m_ui->notificationLabel->setNotificationType(NotificationType::Critical);
|
||||
m_ui->notificationLabel->setText(tr("Insufficient search criteria supplied"));
|
||||
m_ui->notificationLabel->setText(tr("Insufficient search criteria supplied - at least title, album or artist must be specified"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -132,18 +144,11 @@ void DbQueryWidget::searchMusicBrainz()
|
|||
|
||||
// show status
|
||||
m_ui->notificationLabel->setNotificationType(NotificationType::Progress);
|
||||
m_ui->notificationLabel->setText(tr("Retrieving meta data ..."));
|
||||
m_ui->notificationLabel->setText(tr("Retrieving meta data from MusicBrainz ..."));
|
||||
setStatus(false);
|
||||
|
||||
// get song description
|
||||
SongDescription desc;
|
||||
desc.title = m_ui->titleLineEdit->text();
|
||||
desc.album = m_ui->albumLineEdit->text();
|
||||
desc.artist = m_ui->artistLineEdit->text();
|
||||
desc.track = m_ui->trackSpinBox->value();
|
||||
|
||||
// do actual query
|
||||
m_ui->resultsTreeView->setModel(m_model = queryMusicBrainz(std::move(desc)));
|
||||
m_ui->resultsTreeView->setModel(m_model = queryMusicBrainz(currentSongDescription()));
|
||||
connect(m_model, &QueryResultsModel::resultsAvailable, this, &DbQueryWidget::showResults);
|
||||
connect(m_model, &QueryResultsModel::coverAvailable, this, &DbQueryWidget::showCoverFromIndex);
|
||||
}
|
||||
|
@ -153,7 +158,7 @@ void DbQueryWidget::searchLyricsWikia()
|
|||
// check whether enough search terms are supplied
|
||||
if(m_ui->artistLineEdit->text().isEmpty()) {
|
||||
m_ui->notificationLabel->setNotificationType(NotificationType::Critical);
|
||||
m_ui->notificationLabel->setText(tr("Insufficient search criteria supplied"));
|
||||
m_ui->notificationLabel->setText(tr("Insufficient search criteria supplied - artist is mandatory"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -163,20 +168,13 @@ void DbQueryWidget::searchLyricsWikia()
|
|||
|
||||
// show status
|
||||
m_ui->notificationLabel->setNotificationType(NotificationType::Progress);
|
||||
m_ui->notificationLabel->setText(tr("Retrieving meta data ..."));
|
||||
m_ui->notificationLabel->setText(tr("Retrieving meta data from LyricsWikia ..."));
|
||||
setStatus(false);
|
||||
|
||||
// get song description
|
||||
SongDescription desc;
|
||||
desc.title = m_ui->titleLineEdit->text();
|
||||
desc.album = m_ui->albumLineEdit->text();
|
||||
desc.artist = m_ui->artistLineEdit->text();
|
||||
desc.track = static_cast<unsigned int>(m_ui->trackSpinBox->value());
|
||||
|
||||
// do actual query
|
||||
m_ui->resultsTreeView->setModel(m_model = queryLyricsWikia(std::move(desc)));
|
||||
m_ui->resultsTreeView->setModel(m_model = queryLyricsWikia(currentSongDescription()));
|
||||
connect(m_model, &QueryResultsModel::resultsAvailable, this, &DbQueryWidget::showResults);
|
||||
connect(m_model, &QueryResultsModel::coverAvailable, this, &DbQueryWidget::showCoverFromIndex);
|
||||
connect(m_model, &QueryResultsModel::lyricsAvailable, this, &DbQueryWidget::showLyricsFromIndex);
|
||||
}
|
||||
|
||||
void DbQueryWidget::abortSearch()
|
||||
|
@ -261,27 +259,56 @@ void DbQueryWidget::applyResults()
|
|||
int row = selection.front().row();
|
||||
TagValue value = m_model->fieldValue(row, field);
|
||||
|
||||
if(value.isEmpty() && field == KnownField::Cover) {
|
||||
// cover value is empty -> cover still needs to be fetched
|
||||
if(m_model->fetchCover(selection.front())) {
|
||||
// cover is available now
|
||||
tagEdit->setValue(KnownField::Cover, m_model->fieldValue(row, KnownField::Cover), previousValueHandling);
|
||||
} else {
|
||||
// cover is fetched asynchronously
|
||||
// -> show status
|
||||
m_ui->notificationLabel->setNotificationType(NotificationType::Progress);
|
||||
m_ui->notificationLabel->setText(tr("Retrieving cover art to be applied ..."));
|
||||
setStatus(false);
|
||||
// -> apply cover when available
|
||||
connect(m_model, &QueryResultsModel::coverAvailable, [this, row, previousValueHandling](const QModelIndex &index) {
|
||||
if(row == index.row()) {
|
||||
if(TagEdit *tagEdit = m_tagEditorWidget->activeTagEdit()) {
|
||||
tagEdit->setValue(KnownField::Cover, m_model->fieldValue(row, KnownField::Cover), previousValueHandling);
|
||||
if(value.isEmpty()) {
|
||||
// cover and lyrics might be fetched belated
|
||||
switch(field) {
|
||||
case KnownField::Cover:
|
||||
if(m_model->fetchCover(selection.front())) {
|
||||
// cover is available now
|
||||
tagEdit->setValue(KnownField::Cover, m_model->fieldValue(row, KnownField::Cover), previousValueHandling);
|
||||
} else {
|
||||
// cover is fetched asynchronously
|
||||
// -> show status
|
||||
m_ui->notificationLabel->setNotificationType(NotificationType::Progress);
|
||||
m_ui->notificationLabel->appendLine(tr("Retrieving cover art to be applied ..."));
|
||||
setStatus(false);
|
||||
// -> apply cover when available
|
||||
connect(m_model, &QueryResultsModel::coverAvailable, [this, row, previousValueHandling](const QModelIndex &index) {
|
||||
if(row == index.row()) {
|
||||
if(TagEdit *tagEdit = m_tagEditorWidget->activeTagEdit()) {
|
||||
tagEdit->setValue(KnownField::Cover, m_model->fieldValue(row, KnownField::Cover), previousValueHandling);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case KnownField::Lyrics:
|
||||
if(m_model->fetchLyrics(selection.front())) {
|
||||
// lyrics are available now
|
||||
tagEdit->setValue(KnownField::Lyrics, m_model->fieldValue(row, KnownField::Lyrics), previousValueHandling);
|
||||
} else {
|
||||
// lyrics are fetched asynchronously
|
||||
// -> show status
|
||||
m_ui->notificationLabel->setNotificationType(NotificationType::Progress);
|
||||
m_ui->notificationLabel->appendLine(tr("Retrieving lyrics to be applied ..."));
|
||||
setStatus(false);
|
||||
// -> apply cover when available
|
||||
connect(m_model, &QueryResultsModel::lyricsAvailable, [this, row, previousValueHandling](const QModelIndex &index) {
|
||||
if(row == index.row()) {
|
||||
if(TagEdit *tagEdit = m_tagEditorWidget->activeTagEdit()) {
|
||||
tagEdit->setValue(KnownField::Lyrics, m_model->fieldValue(row, KnownField::Lyrics), previousValueHandling);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
;
|
||||
}
|
||||
} else {
|
||||
// any other fields are just set
|
||||
tagEdit->setValue(field, value, previousValueHandling);
|
||||
}
|
||||
}
|
||||
|
@ -308,6 +335,7 @@ void DbQueryWidget::showResultsContextMenu()
|
|||
}
|
||||
if(m_model && m_model->areResultsAvailable()) {
|
||||
contextMenu.addAction(QIcon::fromTheme(QStringLiteral("view-preview")), tr("Show cover"), this, SLOT(fetchAndShowCoverForSelection()));
|
||||
contextMenu.addAction(QIcon::fromTheme(QStringLiteral("view-media-lyrics")), tr("Show lyrics"), this, SLOT(fetchAndShowLyricsForSelection()));
|
||||
}
|
||||
contextMenu.exec(QCursor::pos());
|
||||
}
|
||||
|
@ -328,7 +356,7 @@ void DbQueryWidget::fetchAndShowCoverForSelection()
|
|||
if(const QByteArray *cover = m_model->cover(selectedIndex)) {
|
||||
showCover(*cover);
|
||||
} else {
|
||||
// cover couldn't be fetched
|
||||
// cover couldn't be fetched, error tracks via resultsAvailable() signal so nothing to do
|
||||
}
|
||||
} else {
|
||||
// cover is fetched asynchronously
|
||||
|
@ -345,9 +373,39 @@ void DbQueryWidget::fetchAndShowCoverForSelection()
|
|||
}
|
||||
}
|
||||
|
||||
void DbQueryWidget::fetchAndShowLyricsForSelection()
|
||||
{
|
||||
if(m_model) {
|
||||
if(const QItemSelectionModel *selectionModel = m_ui->resultsTreeView->selectionModel()) {
|
||||
const QModelIndexList selection = selectionModel->selection().indexes();
|
||||
if(!selection.isEmpty()) {
|
||||
const QModelIndex &selectedIndex = selection.at(0);
|
||||
if(const QString *lyrics = m_model->lyrics(selectedIndex)) {
|
||||
showLyrics(*lyrics);
|
||||
} else {
|
||||
if(m_model->fetchLyrics(selectedIndex)) {
|
||||
if(const QByteArray *cover = m_model->cover(selectedIndex)) {
|
||||
showLyrics(*cover);
|
||||
} else {
|
||||
// lyrics couldn't be fetched, error tracks via resultsAvailable() signal so nothing to do
|
||||
}
|
||||
} else {
|
||||
// lyrics are fetched asynchronously
|
||||
// -> memorize index to be shown
|
||||
m_lyricsIndex = selectedIndex.row();
|
||||
// -> show status
|
||||
m_ui->notificationLabel->setNotificationType(NotificationType::Progress);
|
||||
m_ui->notificationLabel->setText(tr("Retrieving lyrics ..."));
|
||||
setStatus(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DbQueryWidget::showCover(const QByteArray &data)
|
||||
{
|
||||
// show cover
|
||||
QDialog dlg;
|
||||
dlg.setWindowFlags(Qt::Tool);
|
||||
dlg.setWindowTitle(tr("Cover - %1").arg(QApplication::applicationName()));
|
||||
|
@ -371,6 +429,29 @@ void DbQueryWidget::showCoverFromIndex(const QModelIndex &index)
|
|||
}
|
||||
}
|
||||
|
||||
void DbQueryWidget::showLyrics(const QString &data)
|
||||
{
|
||||
QDialog dlg;
|
||||
dlg.setWindowFlags(Qt::Tool);
|
||||
dlg.setWindowTitle(tr("Lyrics - %1").arg(QApplication::applicationName()));
|
||||
QBoxLayout layout(QBoxLayout::Up);
|
||||
layout.setMargin(0);
|
||||
QTextBrowser textBrowser;
|
||||
layout.addWidget(&textBrowser);
|
||||
textBrowser.setText(data);
|
||||
dlg.setLayout(&layout);
|
||||
dlg.resize(400, 400);
|
||||
dlg.exec();
|
||||
}
|
||||
|
||||
void DbQueryWidget::showLyricsFromIndex(const QModelIndex &index)
|
||||
{
|
||||
if(m_lyricsIndex == index.row()) {
|
||||
m_lyricsIndex = -1;
|
||||
showLyrics(*m_model->lyrics(index));
|
||||
}
|
||||
}
|
||||
|
||||
void DbQueryWidget::clearSearchCriteria()
|
||||
{
|
||||
m_ui->titleLineEdit->clear();
|
||||
|
|
|
@ -22,6 +22,7 @@ class DbQueryWidget;
|
|||
class QueryResultsModel;
|
||||
class TagEditorWidget;
|
||||
class TagEdit;
|
||||
struct SongDescription;
|
||||
|
||||
class DbQueryWidget : public QWidget
|
||||
{
|
||||
|
@ -32,6 +33,7 @@ public:
|
|||
~DbQueryWidget();
|
||||
|
||||
void insertSearchTermsFromTagEdit(TagEdit *tagEdit);
|
||||
SongDescription currentSongDescription() const;
|
||||
|
||||
public slots:
|
||||
void searchMusicBrainz();
|
||||
|
@ -47,8 +49,11 @@ private slots:
|
|||
void fileStatusChanged(bool opened, bool hasTags);
|
||||
void showResultsContextMenu();
|
||||
void fetchAndShowCoverForSelection();
|
||||
void fetchAndShowLyricsForSelection();
|
||||
void showCover(const QByteArray &data);
|
||||
void showCoverFromIndex(const QModelIndex &index);
|
||||
void showLyrics(const QString &data);
|
||||
void showLyricsFromIndex(const QModelIndex &index);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
|
@ -57,7 +62,7 @@ private:
|
|||
std::unique_ptr<Ui::DbQueryWidget> m_ui;
|
||||
TagEditorWidget *m_tagEditorWidget;
|
||||
QueryResultsModel *m_model;
|
||||
int m_coverIndex;
|
||||
int m_coverIndex, m_lyricsIndex;
|
||||
QMenu *m_menu;
|
||||
QAction *m_insertPresentDataAction;
|
||||
};
|
||||
|
|
|
@ -2,14 +2,6 @@
|
|||
<ui version="4.0">
|
||||
<class>QtGui::DbQueryWidget</class>
|
||||
<widget class="QWidget" name="QtGui::DbQueryWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>731</width>
|
||||
<height>619</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MusicBrainz/LyricsWikia search</string>
|
||||
</property>
|
||||
|
@ -218,7 +210,7 @@
|
|||
<item>
|
||||
<widget class="QCheckBox" name="overrideCheckBox">
|
||||
<property name="text">
|
||||
<string>override existing values</string>
|
||||
<string>Override existing values</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
|
|
|
@ -110,10 +110,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
|
||||
// dbquery dock widget
|
||||
if(Settings::dbQueryWidgetShown()) {
|
||||
toggleDbQueryWidget();
|
||||
} else {
|
||||
// ensure the dock widget is invisible
|
||||
m_ui->dbQueryDockWidget->setVisible(false);
|
||||
m_ui->dbQueryDockWidget->setWidget(m_dbQueryWidget = new DbQueryWidget(m_ui->tagEditorWidget, this));
|
||||
}
|
||||
|
||||
// restore locked
|
||||
|
|
|
@ -173,18 +173,19 @@ QSize NotificationLabel::minimumSizeHint() const
|
|||
|
||||
void NotificationLabel::setText(const QString &text)
|
||||
{
|
||||
const bool updateTooltip = toolTip().isEmpty() || toolTip() == m_text;
|
||||
m_text = text;
|
||||
updateGeometry();
|
||||
update(textRect());
|
||||
if(toolTip().isEmpty()) {
|
||||
setToolTip(text);
|
||||
if(updateTooltip) {
|
||||
setToolTip(m_text);
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationLabel::clearText()
|
||||
{
|
||||
if(toolTip() == m_text) {
|
||||
toolTip().clear();
|
||||
setToolTip(QString());
|
||||
}
|
||||
m_text.clear();
|
||||
updateGeometry();
|
||||
|
|
|
@ -719,44 +719,52 @@ row</source>
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="48"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="50"/>
|
||||
<source>Search hasn't been started</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="66"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="68"/>
|
||||
<source>Insert present data</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="70"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="72"/>
|
||||
<source>Clear search criteria</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="125"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="156"/>
|
||||
<source>Insufficient search criteria supplied</source>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="137"/>
|
||||
<source>Insufficient search criteria supplied - at least title, album or artist must be specified</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="135"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="166"/>
|
||||
<source>Retrieving meta data ...</source>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="147"/>
|
||||
<source>Retrieving meta data from MusicBrainz ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="196"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="161"/>
|
||||
<source>Insufficient search criteria supplied - artist is mandatory</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="171"/>
|
||||
<source>Retrieving meta data from LyricsWikia ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="194"/>
|
||||
<source>Aborted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="208"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="206"/>
|
||||
<source>No results available</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../gui/dbquerywidget.cpp" line="210"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="208"/>
|
||||
<source>%1 result(s) available</source>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
|
@ -769,25 +777,45 @@ row</source>
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="307"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="294"/>
|
||||
<source>Retrieving lyrics to be applied ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="334"/>
|
||||
<source>Use selected row</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="310"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="337"/>
|
||||
<source>Show cover</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="339"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="338"/>
|
||||
<source>Show lyrics</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="367"/>
|
||||
<source>Retrieving cover art ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="353"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="398"/>
|
||||
<source>Retrieving lyrics ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="411"/>
|
||||
<source>Cover - %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="436"/>
|
||||
<source>Lyrics - %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QtGui::EditorAutoCorrectionOptionPage</name>
|
||||
|
@ -1578,22 +1606,27 @@ another position would prevent rewriting the entire file</source>
|
|||
<context>
|
||||
<name>QtGui::HttpResultsModel</name>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="255"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="297"/>
|
||||
<source><p>Do you want to redirect form <i>%1</i> to <i>%2</i>?</p></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="257"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="299"/>
|
||||
<source>Search</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="268"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="304"/>
|
||||
<source>Redirection to: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="312"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="309"/>
|
||||
<source>Server replied no data.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="331"/>
|
||||
<source>Aborted by user.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1723,6 +1756,39 @@ another position would prevent rewriting the entire file</source>
|
|||
<translation></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QtGui::LyricsWikiaResultsModel</name>
|
||||
<message>
|
||||
<location filename="../dbquery/lyricswikia.cpp" line="37"/>
|
||||
<source>Unable to fetch lyrics: Artist or title is unknown.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/lyricswikia.cpp" line="151"/>
|
||||
<source>Internal error: context for song details reply invalid</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/lyricswikia.cpp" line="191"/>
|
||||
<source>Unable to parse song details: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/lyricswikia.cpp" line="200"/>
|
||||
<source>Song details requested for %1/%2 do not contain URL for Wiki page</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/lyricswikia.cpp" line="228"/>
|
||||
<source>Internal error: context for LyricsWikia page reply invalid</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/lyricswikia.cpp" line="238"/>
|
||||
<source>Song details requested for %1/%2 do not contain lyrics</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QtGui::MainWindow</name>
|
||||
<message>
|
||||
|
@ -1846,7 +1912,7 @@ another position would prevent rewriting the entire file</source>
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="202"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="199"/>
|
||||
<source>Lock layout</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1922,63 +1988,63 @@ another position would prevent rewriting the entire file</source>
|
|||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="335"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="332"/>
|
||||
<source>No file opened.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="356"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="353"/>
|
||||
<source>A tag editing utility supporting ID3, MP4 (iTunes style), Vorbis and Matroska tags.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="473"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="470"/>
|
||||
<source>Unable to show the next file because it can't be found anymore.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="483"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="480"/>
|
||||
<source>Open file - </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="494"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="491"/>
|
||||
<source>Save changes as - </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="516"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="513"/>
|
||||
<source>Save file information - </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="535"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="532"/>
|
||||
<source>No file is opened.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="538"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="535"/>
|
||||
<source>Unable to save file information because the current process hasn't been finished yet.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="525"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="522"/>
|
||||
<source>Unable to write to file.
|
||||
%1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="197"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="194"/>
|
||||
<source>Unlock layout</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="528"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="525"/>
|
||||
<source>Unable to open file.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="532"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="529"/>
|
||||
<source>No file information available.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1986,13 +2052,13 @@ another position would prevent rewriting the entire file</source>
|
|||
<context>
|
||||
<name>QtGui::MusicBrainzResultsModel</name>
|
||||
<message>
|
||||
<location filename="../dbquery/musicbrainz.cpp" line="44"/>
|
||||
<source>Unable to fetch cover: Album ID is unknown.</source>
|
||||
<location filename="../dbquery/musicbrainz.cpp" line="45"/>
|
||||
<source>Unable to fetch cover: Album ID unknown</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/musicbrainz.cpp" line="63"/>
|
||||
<source>Cover reply is invalid (internal error).</source>
|
||||
<location filename="../dbquery/musicbrainz.cpp" line="182"/>
|
||||
<source>Internal error: context for cover reply invalid</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
|
@ -2315,43 +2381,48 @@ another position would prevent rewriting the entire file</source>
|
|||
<context>
|
||||
<name>QtGui::QueryResultsModel</name>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="148"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="150"/>
|
||||
<source>Song title</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="150"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="152"/>
|
||||
<source>Album</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="152"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="154"/>
|
||||
<source>Artist</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="154"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="156"/>
|
||||
<source>Year</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="156"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="158"/>
|
||||
<source>Track</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="158"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="160"/>
|
||||
<source>Total tracks</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="160"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="162"/>
|
||||
<source>Genre</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="207"/>
|
||||
<source>Fetching the cover is not implemented for the selected provider.</source>
|
||||
<location filename="../dbquery/dbquery.cpp" line="212"/>
|
||||
<source>Fetching cover is not implemented for this provider</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="242"/>
|
||||
<source>Fetching lyrics is not implemented for this provider</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
|
|
|
@ -648,44 +648,52 @@
|
|||
<context>
|
||||
<name>QtGui::DbQueryWidget</name>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="48"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="50"/>
|
||||
<source>Search hasn't been started</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="66"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="68"/>
|
||||
<source>Insert present data</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="70"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="72"/>
|
||||
<source>Clear search criteria</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="125"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="156"/>
|
||||
<source>Insufficient search criteria supplied</source>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="137"/>
|
||||
<source>Insufficient search criteria supplied - at least title, album or artist must be specified</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="135"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="166"/>
|
||||
<source>Retrieving meta data ...</source>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="147"/>
|
||||
<source>Retrieving meta data from MusicBrainz ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="196"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="161"/>
|
||||
<source>Insufficient search criteria supplied - artist is mandatory</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="171"/>
|
||||
<source>Retrieving meta data from LyricsWikia ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="194"/>
|
||||
<source>Aborted</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="208"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="206"/>
|
||||
<source>No results available</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<location filename="../gui/dbquerywidget.cpp" line="210"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="208"/>
|
||||
<source>%1 result(s) available</source>
|
||||
<translation type="unfinished">
|
||||
<numerusform>%1 result available</numerusform>
|
||||
|
@ -698,25 +706,45 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="307"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="294"/>
|
||||
<source>Retrieving lyrics to be applied ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="334"/>
|
||||
<source>Use selected row</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="310"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="337"/>
|
||||
<source>Show cover</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="339"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="338"/>
|
||||
<source>Show lyrics</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="367"/>
|
||||
<source>Retrieving cover art ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="353"/>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="398"/>
|
||||
<source>Retrieving lyrics ...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="411"/>
|
||||
<source>Cover - %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.cpp" line="436"/>
|
||||
<source>Lyrics - %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/dbquerywidget.ui" line="14"/>
|
||||
<source>MusicBrainz/LyricsWikia search</source>
|
||||
|
@ -1578,22 +1606,27 @@ another position would prevent rewriting the entire file</source>
|
|||
<context>
|
||||
<name>QtGui::HttpResultsModel</name>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="255"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="297"/>
|
||||
<source><p>Do you want to redirect form <i>%1</i> to <i>%2</i>?</p></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="257"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="299"/>
|
||||
<source>Search</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="268"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="304"/>
|
||||
<source>Redirection to: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="312"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="309"/>
|
||||
<source>Server replied no data.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="331"/>
|
||||
<source>Aborted by user.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1723,6 +1756,39 @@ another position would prevent rewriting the entire file</source>
|
|||
<translation></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QtGui::LyricsWikiaResultsModel</name>
|
||||
<message>
|
||||
<location filename="../dbquery/lyricswikia.cpp" line="37"/>
|
||||
<source>Unable to fetch lyrics: Artist or title is unknown.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/lyricswikia.cpp" line="151"/>
|
||||
<source>Internal error: context for song details reply invalid</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/lyricswikia.cpp" line="191"/>
|
||||
<source>Unable to parse song details: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/lyricswikia.cpp" line="200"/>
|
||||
<source>Song details requested for %1/%2 do not contain URL for Wiki page</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/lyricswikia.cpp" line="228"/>
|
||||
<source>Internal error: context for LyricsWikia page reply invalid</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/lyricswikia.cpp" line="238"/>
|
||||
<source>Song details requested for %1/%2 do not contain lyrics</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QtGui::MainWindow</name>
|
||||
<message>
|
||||
|
@ -1912,7 +1978,7 @@ another position would prevent rewriting the entire file</source>
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="202"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="199"/>
|
||||
<source>Lock layout</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1922,63 +1988,63 @@ another position would prevent rewriting the entire file</source>
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="197"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="194"/>
|
||||
<source>Unlock layout</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="335"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="332"/>
|
||||
<source>No file opened.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="356"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="353"/>
|
||||
<source>A tag editing utility supporting ID3, MP4 (iTunes style), Vorbis and Matroska tags.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="473"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="470"/>
|
||||
<source>Unable to show the next file because it can't be found anymore.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="483"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="480"/>
|
||||
<source>Open file - </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="494"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="491"/>
|
||||
<source>Save changes as - </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="516"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="513"/>
|
||||
<source>Save file information - </source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="525"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="522"/>
|
||||
<source>Unable to write to file.
|
||||
%1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="528"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="525"/>
|
||||
<source>Unable to open file.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="532"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="529"/>
|
||||
<source>No file information available.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="535"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="532"/>
|
||||
<source>No file is opened.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../gui/mainwindow.cpp" line="538"/>
|
||||
<location filename="../gui/mainwindow.cpp" line="535"/>
|
||||
<source>Unable to save file information because the current process hasn't been finished yet.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -1986,13 +2052,13 @@ another position would prevent rewriting the entire file</source>
|
|||
<context>
|
||||
<name>QtGui::MusicBrainzResultsModel</name>
|
||||
<message>
|
||||
<location filename="../dbquery/musicbrainz.cpp" line="44"/>
|
||||
<source>Unable to fetch cover: Album ID is unknown.</source>
|
||||
<location filename="../dbquery/musicbrainz.cpp" line="45"/>
|
||||
<source>Unable to fetch cover: Album ID unknown</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/musicbrainz.cpp" line="63"/>
|
||||
<source>Cover reply is invalid (internal error).</source>
|
||||
<location filename="../dbquery/musicbrainz.cpp" line="182"/>
|
||||
<source>Internal error: context for cover reply invalid</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
|
@ -2315,43 +2381,48 @@ another position would prevent rewriting the entire file</source>
|
|||
<context>
|
||||
<name>QtGui::QueryResultsModel</name>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="148"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="150"/>
|
||||
<source>Song title</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="150"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="152"/>
|
||||
<source>Album</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="152"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="154"/>
|
||||
<source>Artist</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="154"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="156"/>
|
||||
<source>Year</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="156"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="158"/>
|
||||
<source>Track</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="158"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="160"/>
|
||||
<source>Total tracks</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="160"/>
|
||||
<location filename="../dbquery/dbquery.cpp" line="162"/>
|
||||
<source>Genre</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="207"/>
|
||||
<source>Fetching the cover is not implemented for the selected provider.</source>
|
||||
<location filename="../dbquery/dbquery.cpp" line="212"/>
|
||||
<source>Fetching cover is not implemented for this provider</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../dbquery/dbquery.cpp" line="242"/>
|
||||
<source>Fetching lyrics is not implemented for this provider</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
|
|
Loading…
Reference in New Issue