From e967d15b4e06de8dd8f47fb211c56c7590aa983e Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Tue, 7 Mar 2023 22:45:20 +0100 Subject: [PATCH] Add AudD lyrics provider --- src/CMakeLists.txt | 2 + src/core/application.cpp | 2 + src/lyrics/auddlyricsprovider.cpp | 178 ++++++++++++++++++++++++++++++ src/lyrics/auddlyricsprovider.h | 62 +++++++++++ 4 files changed, 244 insertions(+) create mode 100644 src/lyrics/auddlyricsprovider.cpp create mode 100644 src/lyrics/auddlyricsprovider.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 016b2b46c..974d53348 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -174,6 +174,7 @@ set(SOURCES lyrics/lyricsfetcher.cpp lyrics/lyricsfetchersearch.cpp lyrics/jsonlyricsprovider.cpp + lyrics/auddlyricsprovider.cpp lyrics/ovhlyricsprovider.cpp lyrics/lololyricsprovider.cpp lyrics/geniuslyricsprovider.cpp @@ -410,6 +411,7 @@ set(HEADERS lyrics/lyricsfetcher.h lyrics/lyricsfetchersearch.h lyrics/jsonlyricsprovider.h + lyrics/auddlyricsprovider.h lyrics/ovhlyricsprovider.h lyrics/lololyricsprovider.h lyrics/geniuslyricsprovider.h diff --git a/src/core/application.cpp b/src/core/application.cpp index 2f92c378f..50c88ff92 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -57,6 +57,7 @@ #include "covermanager/spotifycoverprovider.h" #include "lyrics/lyricsproviders.h" +#include "lyrics/auddlyricsprovider.h" #include "lyrics/geniuslyricsprovider.h" #include "lyrics/ovhlyricsprovider.h" #include "lyrics/lololyricsprovider.h" @@ -148,6 +149,7 @@ class ApplicationImpl { lyrics_providers_([app]() { LyricsProviders *lyrics_providers = new LyricsProviders(app); // Initialize the repository of lyrics providers. + lyrics_providers->AddProvider(new AuddLyricsProvider(lyrics_providers->network(), app)); lyrics_providers->AddProvider(new GeniusLyricsProvider(lyrics_providers->network(), app)); lyrics_providers->AddProvider(new OVHLyricsProvider(lyrics_providers->network(), app)); lyrics_providers->AddProvider(new LoloLyricsProvider(lyrics_providers->network(), app)); diff --git a/src/lyrics/auddlyricsprovider.cpp b/src/lyrics/auddlyricsprovider.cpp new file mode 100644 index 000000000..78c63ee7f --- /dev/null +++ b/src/lyrics/auddlyricsprovider.cpp @@ -0,0 +1,178 @@ +/* + * Strawberry Music Player + * Copyright 2018-2023, Jonas Kvinge + * + * 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 . + * + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/logging.h" +#include "core/networkaccessmanager.h" +#include "utilities/strutils.h" +#include "lyricssearchrequest.h" +#include "lyricssearchresult.h" +#include "auddlyricsprovider.h" + +const char *AuddLyricsProvider::kUrlSearch = "https://api.audd.io/findLyrics/"; +const int AuddLyricsProvider::kMaxLength = 6000; + +AuddLyricsProvider::AuddLyricsProvider(NetworkAccessManager *network, QObject *parent) : JsonLyricsProvider("AudD", true, false, network, parent) {} + +AuddLyricsProvider::~AuddLyricsProvider() { + + while (!replies_.isEmpty()) { + QNetworkReply *reply = replies_.takeFirst(); + QObject::disconnect(reply, nullptr, this, nullptr); + reply->abort(); + reply->deleteLater(); + } + +} + +bool AuddLyricsProvider::StartSearch(const int id, const LyricsSearchRequest &request) { + + QUrl url(kUrlSearch); + QUrlQuery url_query; + url_query.addQueryItem("q", QUrl::toPercentEncoding(QString(request.artist + " " + request.title))); + url.setQuery(url_query); + QNetworkRequest req(url); + req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); + QNetworkReply *reply = network_->get(req); + replies_ << reply; + QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, id, request]() { HandleSearchReply(reply, id, request); }); + + return true; + +} + +void AuddLyricsProvider::CancelSearch(const int id) { Q_UNUSED(id); } + +void AuddLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id, const LyricsSearchRequest &request) { + + if (!replies_.contains(reply)) return; + replies_.removeAll(reply); + QObject::disconnect(reply, nullptr, this, nullptr); + reply->deleteLater(); + + const QByteArray data = ExtractData(reply); + if (data.isEmpty()) { + emit SearchFinished(id); + return; + } + + QJsonArray json_result = ExtractResult(data); + if (json_result.isEmpty()) { + qLog(Debug) << "AudDLyrics: No lyrics for" << request.artist << request.title; + emit SearchFinished(id); + return; + } + + LyricsSearchResults results; + for (const QJsonValueRef value : json_result) { + if (!value.isObject()) { + qLog(Error) << "AudDLyrics: Invalid Json reply, result is not an object."; + qLog(Debug) << value; + continue; + } + QJsonObject json_obj = value.toObject(); + if ( + !json_obj.contains("song_id") || + !json_obj.contains("artist_id") || + !json_obj.contains("title") || + !json_obj.contains("artist") || + !json_obj.contains("lyrics") + ) { + qLog(Error) << "AudDLyrics: Invalid Json reply, result is missing data."; + qLog(Debug) << value; + continue; + } + LyricsSearchResult result; + result.artist = json_obj["artist"].toString(); + result.title = json_obj["title"].toString(); + if (result.artist.compare(request.albumartist, Qt::CaseInsensitive) != 0 && + result.artist.compare(request.artist, Qt::CaseInsensitive) != 0 && + result.title.compare(request.title, Qt::CaseInsensitive) != 0) { + continue; + } + result.lyrics = json_obj["lyrics"].toString(); + if (result.lyrics.isEmpty() || result.lyrics.length() > kMaxLength || result.lyrics == "error") continue; + result.lyrics = Utilities::DecodeHtmlEntities(result.lyrics); + results << result; + } + + if (results.isEmpty()) { + qLog(Debug) << "AudDLyrics: No lyrics for" << request.artist << request.title; + } + else { + qLog(Debug) << "AudDLyrics: Got lyrics for" << request.artist << request.title; + } + + emit SearchFinished(id, results); + +} + +QJsonArray AuddLyricsProvider::ExtractResult(const QByteArray &data) { + + QJsonObject json_obj = ExtractJsonObj(data); + if (json_obj.isEmpty()) return QJsonArray(); + + if (!json_obj.contains("status")) { + Error("Json reply is missing status.", json_obj); + return QJsonArray(); + } + + if (json_obj["status"].toString() == "error") { + if (!json_obj.contains("error")) { + Error("Json reply is missing error status.", json_obj); + return QJsonArray(); + } + QJsonObject json_error = json_obj["error"].toObject(); + if (!json_error.contains("error_code") || !json_error.contains("error_message")) { + Error("Json reply is missing error code or message.", json_error); + return QJsonArray(); + } + QString error_message = json_error["error_message"].toString(); + Error(error_message); + return QJsonArray(); + } + + if (!json_obj.contains("result") || !json_obj["result"].isArray()) { + Error("Json reply is missing result array.", json_obj); + return QJsonArray(); + } + + return json_obj["result"].toArray(); + +} + +void AuddLyricsProvider::Error(const QString &error, const QVariant &debug) { + + qLog(Error) << "AudDLyrics:" << error; + if (debug.isValid()) qLog(Debug) << debug; + +} diff --git a/src/lyrics/auddlyricsprovider.h b/src/lyrics/auddlyricsprovider.h new file mode 100644 index 000000000..8162a8a0f --- /dev/null +++ b/src/lyrics/auddlyricsprovider.h @@ -0,0 +1,62 @@ +/* + * Strawberry Music Player + * Copyright 2018-2023, Jonas Kvinge + * + * 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 . + * + */ + +#ifndef AUDDLYRICSPROVIDER_H +#define AUDDLYRICSPROVIDER_H + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "jsonlyricsprovider.h" +#include "lyricsfetcher.h" + +class QNetworkReply; +class NetworkAccessManager; + +class AuddLyricsProvider : public JsonLyricsProvider { + Q_OBJECT + + public: + explicit AuddLyricsProvider(NetworkAccessManager *network, QObject *parent = nullptr); + ~AuddLyricsProvider() override; + + bool StartSearch(const int id, const LyricsSearchRequest &request) override; + void CancelSearch(const int id) override; + + private: + void Error(const QString &error, const QVariant &debug = QVariant()) override; + QJsonArray ExtractResult(const QByteArray &data); + + private slots: + void HandleSearchReply(QNetworkReply *reply, const int id, const LyricsSearchRequest &request); + + private: + static const char *kUrlSearch; + static const int kMaxLength; + QList replies_; + +}; + +#endif // AUDDLYRICSPROVIDER_H