mirror of
https://github.com/strawberrymusicplayer/strawberry
synced 2024-12-10 15:55:29 +01:00
Add tidal cover provider
This commit is contained in:
parent
36dccc8157
commit
1ad163aac3
@ -19,7 +19,7 @@ Strawberry is a music player and music collection organizer. It is a fork of Cle
|
||||
* Advanced audio output and device configuration for bit-perfect playback on Linux
|
||||
* Edit tags on music files
|
||||
* Fetch tags from MusicBrainz
|
||||
* Album cover art from Last.fm, Musicbrainz, Discogs and Deezer
|
||||
* Album cover art from Last.fm, Musicbrainz, Discogs, Deezer and Tidal
|
||||
* Song lyrics from AudD
|
||||
* Support for multiple backends
|
||||
* Audio analyzer
|
||||
|
2
dist/debian/control
vendored
2
dist/debian/control
vendored
@ -58,7 +58,7 @@ Description: Audio player and music collection organizer
|
||||
- Advanced audio output and device configuration for bit-perfect playback on Linux
|
||||
- Edit tags on music files
|
||||
- Fetch tags from MusicBrainz
|
||||
- Album cover art from Lastfm, Musicbrainz, Discogs and Deezer
|
||||
- Album cover art from Lastfm, Musicbrainz, Discogs, Deezer and Tidal
|
||||
- Song lyrics from AudD
|
||||
- Support for multiple backends
|
||||
- Audio analyzer
|
||||
|
2
dist/man/strawberry.1
vendored
2
dist/man/strawberry.1
vendored
@ -25,7 +25,7 @@ Features:
|
||||
.br
|
||||
- Fetch tags from MusicBrainz
|
||||
.br
|
||||
- Album cover art from Lastfm, Musicbrainz, Discogs and Deezer
|
||||
- Album cover art from Lastfm, Musicbrainz, Discogs, Deezer and Tidal
|
||||
.br
|
||||
- Song lyrics from AudD
|
||||
.br
|
||||
|
2
dist/rpm/strawberry.spec.in
vendored
2
dist/rpm/strawberry.spec.in
vendored
@ -96,7 +96,7 @@ Features:
|
||||
- Advanced audio output and device configuration for bit-perfect playback on Linux
|
||||
- Edit tags on music files
|
||||
- Fetch tags from MusicBrainz
|
||||
- Album cover art from Last.fm, Musicbrainz, Discogs and Deezer
|
||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Deezer and Tidal
|
||||
- Song lyrics from AudD
|
||||
- Support for multiple backends
|
||||
- Audio analyzer
|
||||
|
@ -199,6 +199,7 @@ set(SOURCES
|
||||
covermanager/musicbrainzcoverprovider.cpp
|
||||
covermanager/discogscoverprovider.cpp
|
||||
covermanager/deezercoverprovider.cpp
|
||||
covermanager/tidalcoverprovider.cpp
|
||||
|
||||
lyrics/lyricsproviders.cpp
|
||||
lyrics/lyricsprovider.cpp
|
||||
@ -375,6 +376,7 @@ set(HEADERS
|
||||
covermanager/musicbrainzcoverprovider.h
|
||||
covermanager/discogscoverprovider.h
|
||||
covermanager/deezercoverprovider.h
|
||||
covermanager/tidalcoverprovider.h
|
||||
|
||||
lyrics/lyricsproviders.h
|
||||
lyrics/lyricsprovider.h
|
||||
|
@ -55,6 +55,7 @@
|
||||
#include "covermanager/discogscoverprovider.h"
|
||||
#include "covermanager/musicbrainzcoverprovider.h"
|
||||
#include "covermanager/deezercoverprovider.h"
|
||||
#include "covermanager/tidalcoverprovider.h"
|
||||
|
||||
#include "lyrics/lyricsproviders.h"
|
||||
#include "lyrics/lyricsprovider.h"
|
||||
@ -103,10 +104,11 @@ class ApplicationImpl {
|
||||
cover_providers_([=]() {
|
||||
CoverProviders *cover_providers = new CoverProviders(app);
|
||||
// Initialize the repository of cover providers.
|
||||
cover_providers->AddProvider(new LastFmCoverProvider(app));
|
||||
cover_providers->AddProvider(new DiscogsCoverProvider(app));
|
||||
cover_providers->AddProvider(new MusicbrainzCoverProvider(app));
|
||||
cover_providers->AddProvider(new DeezerCoverProvider(app));
|
||||
cover_providers->AddProvider(new LastFmCoverProvider(app, app));
|
||||
cover_providers->AddProvider(new DiscogsCoverProvider(app, app));
|
||||
cover_providers->AddProvider(new MusicbrainzCoverProvider(app, app));
|
||||
cover_providers->AddProvider(new DeezerCoverProvider(app, app));
|
||||
cover_providers->AddProvider(new TidalCoverProvider(app, app));
|
||||
return cover_providers;
|
||||
}),
|
||||
album_cover_loader_([=]() {
|
||||
|
@ -23,7 +23,8 @@
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "coverprovider.h"
|
||||
|
||||
CoverProvider::CoverProvider(const QString &name, const bool &fetchall, QObject *parent)
|
||||
: QObject(parent), name_(name), fetchall_(fetchall) {}
|
||||
CoverProvider::CoverProvider(const QString &name, const bool &fetchall, Application *app, QObject *parent)
|
||||
: QObject(parent), app_(app), name_(name), fetchall_(fetchall) {}
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
class Application;
|
||||
struct CoverSearchResult;
|
||||
|
||||
// Each implementation of this interface downloads covers from one online service.
|
||||
@ -36,8 +37,8 @@ struct CoverSearchResult;
|
||||
class CoverProvider : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CoverProvider(const QString &name, const bool &fetchall, QObject *parent);
|
||||
public:
|
||||
explicit CoverProvider(const QString &name, const bool &fetchall, Application *app, QObject *parent);
|
||||
|
||||
// A name (very short description) of this provider, like "last.fm".
|
||||
QString name() const { return name_; }
|
||||
@ -50,10 +51,11 @@ public:
|
||||
|
||||
virtual void CancelSearch(int id) {}
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void SearchFinished(int id, const QList<CoverSearchResult>& results);
|
||||
|
||||
private:
|
||||
private:
|
||||
Application *app_;
|
||||
QString name_;
|
||||
bool fetchall_;
|
||||
|
||||
|
@ -61,7 +61,7 @@ class CoverProviders : public QObject {
|
||||
private:
|
||||
Q_DISABLE_COPY(CoverProviders);
|
||||
|
||||
QMap<CoverProvider *, QString> cover_providers_;
|
||||
QMap<CoverProvider*, QString> cover_providers_;
|
||||
QMutex mutex_;
|
||||
|
||||
QAtomicInt next_id_;
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/network.h"
|
||||
#include "core/logging.h"
|
||||
@ -47,7 +48,7 @@
|
||||
const char *DeezerCoverProvider::kApiUrl = "https://api.deezer.com";
|
||||
const int DeezerCoverProvider::kLimit = 10;
|
||||
|
||||
DeezerCoverProvider::DeezerCoverProvider(QObject *parent): CoverProvider("Deezer", true, parent), network_(new NetworkAccessManager(this)) {}
|
||||
DeezerCoverProvider::DeezerCoverProvider(Application *app, QObject *parent): CoverProvider("Deezer", true, app, parent), network_(new NetworkAccessManager(this)) {}
|
||||
|
||||
bool DeezerCoverProvider::StartSearch(const QString &artist, const QString &album, int id) {
|
||||
|
||||
|
@ -36,11 +36,13 @@
|
||||
|
||||
#include "coverprovider.h"
|
||||
|
||||
class Application;
|
||||
|
||||
class DeezerCoverProvider : public CoverProvider {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DeezerCoverProvider(QObject *parent = nullptr);
|
||||
explicit DeezerCoverProvider(Application *app, QObject *parent = nullptr);
|
||||
bool StartSearch(const QString &artist, const QString &album, int id);
|
||||
void CancelSearch(int id);
|
||||
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include <QJsonValue>
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
@ -53,7 +54,7 @@ const char *DiscogsCoverProvider::kUrlReleases = "https://api.discogs.com/releas
|
||||
const char *DiscogsCoverProvider::kAccessKeyB64 = "dGh6ZnljUGJlZ1NEeXBuSFFxSVk=";
|
||||
const char *DiscogsCoverProvider::kSecretKeyB64 = "ZkFIcmlaSER4aHhRSlF2U3d0bm5ZVmdxeXFLWUl0UXI=";
|
||||
|
||||
DiscogsCoverProvider::DiscogsCoverProvider(QObject *parent) : CoverProvider("Discogs", false, parent), network_(new NetworkAccessManager(this)) {}
|
||||
DiscogsCoverProvider::DiscogsCoverProvider(Application *app, QObject *parent) : CoverProvider("Discogs", false, app, parent), network_(new NetworkAccessManager(this)) {}
|
||||
|
||||
bool DiscogsCoverProvider::StartSearch(const QString &artist, const QString &album, int s_id) {
|
||||
|
||||
|
@ -36,6 +36,8 @@
|
||||
#include "coverprovider.h"
|
||||
#include "albumcoverfetcher.h"
|
||||
|
||||
class Application;
|
||||
|
||||
// This struct represents a single search-for-cover request. It identifies and describes the request.
|
||||
struct DiscogsCoverSearchContext {
|
||||
|
||||
@ -66,7 +68,7 @@ class DiscogsCoverProvider : public CoverProvider {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DiscogsCoverProvider(QObject *parent = nullptr);
|
||||
explicit DiscogsCoverProvider(Application *app, QObject *parent = nullptr);
|
||||
|
||||
bool StartSearch(const QString &artist, const QString &album, int s_id);
|
||||
|
||||
|
@ -35,6 +35,7 @@
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/network.h"
|
||||
#include "core/logging.h"
|
||||
@ -47,7 +48,7 @@ const char *LastFmCoverProvider::kUrl = "https://ws.audioscrobbler.com/2.0/";
|
||||
const char *LastFmCoverProvider::kApiKey = "211990b4c96782c05d1536e7219eb56e";
|
||||
const char *LastFmCoverProvider::kSecret = "80fd738f49596e9709b1bf9319c444a8";
|
||||
|
||||
LastFmCoverProvider::LastFmCoverProvider(QObject *parent) : CoverProvider("last.fm", true, parent), network_(new NetworkAccessManager(this)) {}
|
||||
LastFmCoverProvider::LastFmCoverProvider(Application *app, QObject *parent) : CoverProvider("last.fm", true, app, parent), network_(new NetworkAccessManager(this)) {}
|
||||
|
||||
bool LastFmCoverProvider::StartSearch(const QString &artist, const QString &album, int id) {
|
||||
|
||||
|
@ -36,11 +36,13 @@
|
||||
|
||||
#include "coverprovider.h"
|
||||
|
||||
class Application;
|
||||
|
||||
class LastFmCoverProvider : public CoverProvider {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LastFmCoverProvider(QObject *parent = nullptr);
|
||||
explicit LastFmCoverProvider(Application *app, QObject *parent = nullptr);
|
||||
bool StartSearch(const QString &artist, const QString &album, int id);
|
||||
|
||||
private slots:
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/network.h"
|
||||
#include "core/logging.h"
|
||||
@ -48,7 +49,7 @@ const char *MusicbrainzCoverProvider::kReleaseSearchUrl = "https://musicbrainz.o
|
||||
const char *MusicbrainzCoverProvider::kAlbumCoverUrl = "https://coverartarchive.org/release/%1/front";
|
||||
const int MusicbrainzCoverProvider::kLimit = 8;
|
||||
|
||||
MusicbrainzCoverProvider::MusicbrainzCoverProvider(QObject *parent): CoverProvider("MusicBrainz", true, parent), network_(new NetworkAccessManager(this)) {}
|
||||
MusicbrainzCoverProvider::MusicbrainzCoverProvider(Application *app, QObject *parent): CoverProvider("MusicBrainz", true, app, parent), network_(new NetworkAccessManager(this)) {}
|
||||
|
||||
bool MusicbrainzCoverProvider::StartSearch(const QString &artist, const QString &album, int id) {
|
||||
|
||||
|
@ -35,10 +35,12 @@
|
||||
#include "coverprovider.h"
|
||||
#include "albumcoverfetcher.h"
|
||||
|
||||
class Application;
|
||||
|
||||
class MusicbrainzCoverProvider : public CoverProvider {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MusicbrainzCoverProvider(QObject *parent = nullptr);
|
||||
explicit MusicbrainzCoverProvider(Application *app, QObject *parent = nullptr);
|
||||
|
||||
bool StartSearch(const QString &artist, const QString &album, int id);
|
||||
void CancelSearch(int id);
|
||||
|
276
src/covermanager/tidalcoverprovider.cpp
Normal file
276
src/covermanager/tidalcoverprovider.cpp
Normal file
@ -0,0 +1,276 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonValue>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QSettings>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/network.h"
|
||||
#include "core/logging.h"
|
||||
#include "settings/tidalsettingspage.h"
|
||||
#include "tidal/tidalservice.h"
|
||||
#include "albumcoverfetcher.h"
|
||||
#include "coverprovider.h"
|
||||
#include "tidalcoverprovider.h"
|
||||
|
||||
const char *TidalCoverProvider::kApiUrl = "https://listen.tidal.com/v1";
|
||||
const char *TidalCoverProvider::kResourcesUrl = "http://resources.tidal.com";
|
||||
const char *TidalCoverProvider::kApiTokenB64 = "UDVYYmVvNUxGdkVTZUR5Ng==";
|
||||
const int TidalCoverProvider::kLimit = 10;
|
||||
|
||||
TidalCoverProvider::TidalCoverProvider(Application *app, QObject *parent) :
|
||||
CoverProvider("Tidal", true, app, parent),
|
||||
service_(app->internet_services()->Service<TidalService>()),
|
||||
network_(new NetworkAccessManager(this)) {
|
||||
|
||||
}
|
||||
|
||||
bool TidalCoverProvider::StartSearch(const QString &artist, const QString &album, int id) {
|
||||
|
||||
if (!service_ || !service_->authenticated()) return false;
|
||||
|
||||
QList<Param> parameters;
|
||||
parameters << Param("query", QString(artist + " " + album));
|
||||
parameters << Param("limit", QString::number(kLimit));
|
||||
QNetworkReply *reply = CreateRequest("search/albums", parameters);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleSearchReply(QNetworkReply*, int)), reply, id);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void TidalCoverProvider::CancelSearch(int id) {}
|
||||
|
||||
QNetworkReply *TidalCoverProvider::CreateRequest(const QString &ressource_name, const QList<Param> ¶ms_supplied) {
|
||||
|
||||
typedef QPair<QString, QString> Param;
|
||||
typedef QList<Param> ParamList;
|
||||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
||||
typedef QList<EncodedParam> EncodedParamList;
|
||||
|
||||
ParamList parameters = ParamList()
|
||||
<< params_supplied
|
||||
<< Param("sessionId", service_->session_id())
|
||||
<< Param("countryCode", service_->country_code());
|
||||
|
||||
QStringList query_items;
|
||||
QUrlQuery url_query;
|
||||
for (const Param& param : parameters) {
|
||||
EncodedParam encoded_param(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second));
|
||||
query_items << QString(encoded_param.first + "=" + encoded_param.second);
|
||||
url_query.addQueryItem(encoded_param.first, encoded_param.second);
|
||||
}
|
||||
|
||||
QUrl url(kApiUrl + QString("/") + ressource_name);
|
||||
url.setQuery(url_query);
|
||||
QNetworkRequest req(url);
|
||||
req.setRawHeader("Origin", "http://listen.tidal.com");
|
||||
req.setRawHeader("X-Tidal-SessionId", service_->session_id().toUtf8());
|
||||
QNetworkReply *reply = network_->get(req);
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
QByteArray TidalCoverProvider::GetReplyData(QNetworkReply *reply, QString &error) {
|
||||
|
||||
QByteArray data;
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
error = Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "userMessage" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError parse_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &parse_error);
|
||||
int status = 0;
|
||||
int sub_status = 0;
|
||||
QString failure_reason;
|
||||
if (parse_error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (!json_obj.isEmpty() && json_obj.contains("status") && json_obj.contains("userMessage")) {
|
||||
status = json_obj["status"].toInt();
|
||||
sub_status = json_obj["subStatus"].toInt();
|
||||
QString user_message = json_obj["userMessage"].toString();
|
||||
failure_reason = QString("%1 (%2) (%3)").arg(user_message).arg(status).arg(sub_status);
|
||||
}
|
||||
}
|
||||
if (failure_reason.isEmpty()) {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
if (status == 401 && sub_status == 6001) { // User does not have a valid session
|
||||
service_->Logout();
|
||||
}
|
||||
error = Error(failure_reason);
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
QJsonObject TidalCoverProvider::ExtractJsonObj(QByteArray &data, QString &error) {
|
||||
|
||||
QJsonParseError json_error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &json_error);
|
||||
|
||||
if (json_error.error != QJsonParseError::NoError) {
|
||||
error = Error("Reply from server missing Json data.", data);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (json_doc.isNull() || json_doc.isEmpty()) {
|
||||
error = Error("Received empty Json document.", data);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (!json_doc.isObject()) {
|
||||
error = Error("Json document is not an object.", json_doc);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.isEmpty()) {
|
||||
error = Error("Received empty Json object.", json_doc);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
return json_obj;
|
||||
|
||||
}
|
||||
|
||||
QJsonValue TidalCoverProvider::ExtractItems(QByteArray &data, QString &error) {
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
if (json_obj.isEmpty()) return QJsonValue();
|
||||
return ExtractItems(json_obj, error);
|
||||
|
||||
}
|
||||
|
||||
QJsonValue TidalCoverProvider::ExtractItems(QJsonObject &json_obj, QString &error) {
|
||||
|
||||
if (!json_obj.contains("items")) {
|
||||
error = Error("Json reply is missing items.", json_obj);
|
||||
return QJsonArray();
|
||||
}
|
||||
QJsonValue json_items = json_obj["items"];
|
||||
return json_items;
|
||||
|
||||
}
|
||||
|
||||
void TidalCoverProvider::HandleSearchReply(QNetworkReply *reply, int id) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
CoverSearchResults results;
|
||||
QString error;
|
||||
|
||||
QByteArray data = GetReplyData(reply, error);
|
||||
if (data.isEmpty()) {
|
||||
emit SearchFinished(id, results);
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data, error);
|
||||
if (json_obj.isEmpty()) {
|
||||
emit SearchFinished(id, results);
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonValue json_value = ExtractItems(json_obj, error);
|
||||
if (!json_value.isArray()) {
|
||||
emit SearchFinished(id, results);
|
||||
return;
|
||||
}
|
||||
QJsonArray json_items = json_value.toArray();
|
||||
if (json_items.isEmpty()) {
|
||||
emit SearchFinished(id, results);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const QJsonValue &value : json_items) {
|
||||
if (!value.isObject()) {
|
||||
Error("Invalid Json reply, item not a object.", value);
|
||||
continue;
|
||||
}
|
||||
QJsonObject json_obj = value.toObject();
|
||||
|
||||
if (!json_obj.contains("artist") || !json_obj.contains("type") || !json_obj.contains("id") || !json_obj.contains("title") || !json_obj.contains("cover")) {
|
||||
Error("Invalid Json reply, item missing id, type, album or cover.", json_obj);
|
||||
continue;
|
||||
}
|
||||
QString album = json_obj["title"].toString();
|
||||
QString cover = json_obj["cover"].toString();
|
||||
|
||||
QJsonValue json_value_artist = json_obj["artist"];
|
||||
if (!json_value_artist.isObject()) {
|
||||
Error("Invalid Json reply, item artist is not a object.", json_value_artist);
|
||||
continue;
|
||||
}
|
||||
QJsonObject json_artist = json_value_artist.toObject();
|
||||
if (!json_artist.contains("name")) {
|
||||
Error("Invalid Json reply, item artist missing name.", json_artist);
|
||||
continue;
|
||||
}
|
||||
QString artist = json_artist["name"].toString();
|
||||
|
||||
cover = cover.replace("-", "/");
|
||||
QUrl cover_url (QString("%1/images/%2/%3.jpg").arg(kResourcesUrl).arg(cover).arg("1280x1280"));
|
||||
|
||||
CoverSearchResult cover_result;
|
||||
cover_result.description = artist + " " + album;
|
||||
cover_result.image_url = cover_url;
|
||||
results << cover_result;
|
||||
|
||||
}
|
||||
emit SearchFinished(id, results);
|
||||
|
||||
}
|
||||
|
||||
QString TidalCoverProvider::Error(QString error, QVariant debug) {
|
||||
qLog(Error) << "Tidal:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
return error;
|
||||
}
|
83
src/covermanager/tidalcoverprovider.h
Normal file
83
src/covermanager/tidalcoverprovider.h
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TIDALCOVERPROVIDER_H
|
||||
#define TIDALCOVERPROVIDER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonValue>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "coverprovider.h"
|
||||
#include "tidal/tidalservice.h"
|
||||
|
||||
class Application;
|
||||
|
||||
class TidalCoverProvider : public CoverProvider {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TidalCoverProvider(Application *app, QObject *parent = nullptr);
|
||||
void SetService(TidalService *service);
|
||||
void ReloadSettings();
|
||||
bool StartSearch(const QString &artist, const QString &album, int id);
|
||||
void CancelSearch(int id);
|
||||
|
||||
private slots:
|
||||
void HandleSearchReply(QNetworkReply *reply, int id);
|
||||
|
||||
private:
|
||||
typedef QPair<QString, QString> Param;
|
||||
static const char *kApiUrl;
|
||||
static const char *kResourcesUrl;
|
||||
static const char *kApiTokenB64;
|
||||
static const int kLimit;
|
||||
|
||||
//QString username_;
|
||||
//QString password_;
|
||||
//QString session_id_;
|
||||
//quint64 user_id_;
|
||||
//QString country_code_;
|
||||
|
||||
#if 0
|
||||
void LoadSessionID();
|
||||
#endif
|
||||
QNetworkReply *CreateRequest(const QString &ressource_name, const QList<Param> ¶ms_supplied);
|
||||
QByteArray GetReplyData(QNetworkReply *reply, QString &error);
|
||||
QJsonObject ExtractJsonObj(QByteArray &data, QString &error);
|
||||
QJsonValue ExtractItems(QByteArray &data, QString &error);
|
||||
QJsonValue ExtractItems(QJsonObject &json_obj, QString &error);
|
||||
QString Error(QString error, QVariant debug = QVariant());
|
||||
|
||||
TidalService *service_;
|
||||
QNetworkAccessManager *network_;
|
||||
|
||||
};
|
||||
|
||||
#endif // TIDALCOVERPROVIDER_H
|
@ -62,6 +62,9 @@ class TidalService : public InternetService {
|
||||
const bool login_sent() { return login_sent_; }
|
||||
const bool authenticated() { return (!session_id_.isEmpty() && !country_code_.isEmpty()); }
|
||||
|
||||
QString session_id() { return session_id_; }
|
||||
QString country_code() { return country_code_; }
|
||||
|
||||
void GetStreamURL(const QUrl &url);
|
||||
|
||||
signals:
|
||||
|
Loading…
Reference in New Issue
Block a user