From 380b84195ff1effd29f64c0c9d368b6955a4369f Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Sun, 14 Apr 2019 18:02:51 +0200 Subject: [PATCH] Add ChartLyrics provider --- CMakeLists.txt | 2 +- README.md | 2 +- dist/debian/control | 2 +- dist/man/strawberry.1 | 2 +- dist/rpm/strawberry.spec.in | 2 +- src/CMakeLists.txt | 8 +- src/config.h.in | 2 +- src/core/application.cpp | 16 +-- src/core/application.h | 2 +- src/core/mainwindow.cpp | 12 +-- src/covermanager/tidalcoverprovider.h | 4 +- src/lyrics/auddlyricsprovider.cpp | 9 +- src/lyrics/auddlyricsprovider.h | 2 +- src/lyrics/chartlyricsprovider.cpp | 138 ++++++++++++++++++++++++++ src/lyrics/chartlyricsprovider.h | 58 +++++++++++ src/settings/settingsdialog.cpp | 6 +- 16 files changed, 235 insertions(+), 32 deletions(-) create mode 100644 src/lyrics/chartlyricsprovider.cpp create mode 100644 src/lyrics/chartlyricsprovider.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fc294ae0f..d76a0b639 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -343,7 +343,7 @@ optional_component(TRANSLATIONS ON "Translations" DEPENDS "Qt5LinguistTools" Qt5LinguistTools_FOUND ) -optional_component(STREAM_TIDAL ON "Streaming: Tidal support") +optional_component(TIDAL ON "Tidal support") if(APPLE) option(USE_BUNDLE "Bundle macOS dependencies" OFF) diff --git a/README.md b/README.md index d40373d08..f01873e3f 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Strawberry is a music player and music collection organizer. It is a fork of Cle * Edit tags on music files * Fetch tags from MusicBrainz * Album cover art from Last.fm, Musicbrainz, Discogs, Deezer and Tidal - * Song lyrics from AudD + * Song lyrics from AudD and ChartLyrics * Support for multiple backends * Audio analyzer * Audio equalizer diff --git a/dist/debian/control b/dist/debian/control index c430a86bc..74a07d0d6 100644 --- a/dist/debian/control +++ b/dist/debian/control @@ -59,7 +59,7 @@ Description: Audio player and music collection organizer - Edit tags on music files - Fetch tags from MusicBrainz - Album cover art from Lastfm, Musicbrainz, Discogs, Deezer and Tidal - - Song lyrics from AudD + - Song lyrics from AudD and ChartLyrics - Support for multiple backends - Audio analyzer - Audio equalizer diff --git a/dist/man/strawberry.1 b/dist/man/strawberry.1 index 889968a77..3aac59771 100644 --- a/dist/man/strawberry.1 +++ b/dist/man/strawberry.1 @@ -27,7 +27,7 @@ Features: .br - Album cover art from Lastfm, Musicbrainz, Discogs, Deezer and Tidal .br -- Song lyrics from AudD +- Song lyrics from AudD and ChartLyrics .br - Support for multiple backends .br diff --git a/dist/rpm/strawberry.spec.in b/dist/rpm/strawberry.spec.in index 7c921abee..1d7eb5c51 100644 --- a/dist/rpm/strawberry.spec.in +++ b/dist/rpm/strawberry.spec.in @@ -97,7 +97,7 @@ Features: - Edit tags on music files - Fetch tags from MusicBrainz - Album cover art from Last.fm, Musicbrainz, Discogs, Deezer and Tidal - - Song lyrics from AudD + - Song lyrics from AudD and ChartLyrics - Support for multiple backends - Audio analyzer - Audio equalizer diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9c2c57d06..b439bb6ee 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -199,13 +199,13 @@ set(SOURCES covermanager/musicbrainzcoverprovider.cpp covermanager/discogscoverprovider.cpp covermanager/deezercoverprovider.cpp - covermanager/tidalcoverprovider.cpp lyrics/lyricsproviders.cpp lyrics/lyricsprovider.cpp lyrics/lyricsfetcher.cpp lyrics/lyricsfetchersearch.cpp lyrics/auddlyricsprovider.cpp + lyrics/chartlyricsprovider.cpp settings/settingsdialog.cpp settings/settingspage.cpp @@ -376,13 +376,13 @@ set(HEADERS covermanager/musicbrainzcoverprovider.h covermanager/discogscoverprovider.h covermanager/deezercoverprovider.h - covermanager/tidalcoverprovider.h lyrics/lyricsproviders.h lyrics/lyricsprovider.h lyrics/lyricsfetcher.h lyrics/lyricsfetchersearch.h lyrics/auddlyricsprovider.h + lyrics/chartlyricsprovider.h settings/settingsdialog.h settings/settingspage.h @@ -863,15 +863,17 @@ optional_source(WIN32 widgets/osd_win.cpp ) -optional_source(HAVE_STREAM_TIDAL +optional_source(HAVE_TIDAL SOURCES tidal/tidalservice.cpp tidal/tidalurlhandler.cpp settings/tidalsettingspage.cpp + covermanager/tidalcoverprovider.cpp HEADERS tidal/tidalservice.h tidal/tidalurlhandler.h settings/tidalsettingspage.h + covermanager/tidalcoverprovider.h UI settings/tidalsettingspage.ui ) diff --git a/src/config.h.in b/src/config.h.in index 7d9fcfb7f..a244388bc 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -48,7 +48,7 @@ #cmakedefine HAVE_XINE #cmakedefine HAVE_PHONON -#cmakedefine HAVE_STREAM_TIDAL +#cmakedefine HAVE_TIDAL #cmakedefine HAVE_KEYSYMDEF_H #cmakedefine HAVE_XF86KEYSYM_H diff --git a/src/core/application.cpp b/src/core/application.cpp index a29427f0d..353305d03 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -55,17 +55,18 @@ #include "covermanager/discogscoverprovider.h" #include "covermanager/musicbrainzcoverprovider.h" #include "covermanager/deezercoverprovider.h" -#include "covermanager/tidalcoverprovider.h" #include "lyrics/lyricsproviders.h" #include "lyrics/lyricsprovider.h" #include "lyrics/auddlyricsprovider.h" +#include "lyrics/chartlyricsprovider.h" #include "internet/internetservices.h" #include "internet/internetsearch.h" -#ifdef HAVE_STREAM_TIDAL +#ifdef HAVE_TIDAL # include "tidal/tidalservice.h" +# include "covermanager/tidalcoverprovider.h" #endif #include "scrobbler/audioscrobbler.h" @@ -108,7 +109,9 @@ class ApplicationImpl { cover_providers->AddProvider(new DiscogsCoverProvider(app, app)); cover_providers->AddProvider(new MusicbrainzCoverProvider(app, app)); cover_providers->AddProvider(new DeezerCoverProvider(app, app)); +#ifdef HAVE_TIDAL cover_providers->AddProvider(new TidalCoverProvider(app, app)); +#endif return cover_providers; }), album_cover_loader_([=]() { @@ -120,16 +123,17 @@ class ApplicationImpl { lyrics_providers_([=]() { LyricsProviders *lyrics_providers = new LyricsProviders(app); lyrics_providers->AddProvider(new AuddLyricsProvider(app)); + lyrics_providers->AddProvider(new ChartLyricsProvider(app)); return lyrics_providers; }), internet_services_([=]() { InternetServices *internet_services = new InternetServices(app); -#ifdef HAVE_STREAM_TIDAL +#ifdef HAVE_TIDAL internet_services->AddService(new TidalService(app, internet_services)); #endif return internet_services; }), -#ifdef HAVE_STREAM_TIDAL +#ifdef HAVE_TIDAL tidal_search_([=]() { return new InternetSearch(app, Song::Source_Tidal, app); }), #endif scrobbler_([=]() { return new AudioScrobbler(app, app); }) @@ -152,7 +156,7 @@ class ApplicationImpl { Lazy current_art_loader_; Lazy lyrics_providers_; Lazy internet_services_; -#ifdef HAVE_STREAM_TIDAL +#ifdef HAVE_TIDAL Lazy tidal_search_; #endif Lazy scrobbler_; @@ -223,7 +227,7 @@ LyricsProviders *Application::lyrics_providers() const { return p_->lyrics_provi PlaylistBackend *Application::playlist_backend() const { return p_->playlist_backend_.get(); } PlaylistManager *Application::playlist_manager() const { return p_->playlist_manager_.get(); } InternetServices *Application::internet_services() const { return p_->internet_services_.get(); } -#ifdef HAVE_STREAM_TIDAL +#ifdef HAVE_TIDAL InternetSearch *Application::tidal_search() const { return p_->tidal_search_.get(); } #endif AudioScrobbler *Application::scrobbler() const { return p_->scrobbler_.get(); } diff --git a/src/core/application.h b/src/core/application.h index 2173c87ce..1275fe1d8 100644 --- a/src/core/application.h +++ b/src/core/application.h @@ -93,7 +93,7 @@ class Application : public QObject { LyricsProviders *lyrics_providers() const; InternetServices *internet_services() const; -#ifdef HAVE_STREAM_TIDAL +#ifdef HAVE_TIDAL InternetSearch *tidal_search() const; #endif diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp index bf8899741..4282d8937 100644 --- a/src/core/mainwindow.cpp +++ b/src/core/mainwindow.cpp @@ -133,7 +133,7 @@ #include "settings/behavioursettingspage.h" #include "settings/backendsettingspage.h" #include "settings/playlistsettingspage.h" -#ifdef HAVE_STREAM_TIDAL +#ifdef HAVE_TIDAL # include "settings/tidalsettingspage.h" #endif @@ -201,7 +201,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co dialog->SetDestinationModel(app->collection()->model()->directory_model()); return dialog; }), -#ifdef HAVE_STREAM_TIDAL +#ifdef HAVE_TIDAL tidal_search_view_(new InternetSearchView(app_, app_->tidal_search(), TidalSettingsPage::kSettingsGroup, SettingsDialog::Page_Tidal, this)), #endif playlist_menu_(new QMenu(this)), @@ -257,7 +257,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co #ifndef Q_OS_WIN ui_->tabs->addTab(device_view_, IconLoader::Load("device"), tr("Devices")); #endif -#ifdef HAVE_STREAM_TIDAL +#ifdef HAVE_TIDAL ui_->tabs->addTab(tidal_search_view_, IconLoader::Load("tidal"), tr("Tidal")); #endif @@ -535,7 +535,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co collection_view_->filter()->AddMenuAction(separator); collection_view_->filter()->AddMenuAction(collection_config_action); -#ifdef HAVE_STREAM_TIDAL +#ifdef HAVE_TIDAL connect(tidal_search_view_, SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); #endif @@ -837,7 +837,7 @@ void MainWindow::ReloadSettings() { } } -#ifdef HAVE_STREAM_TIDAL +#ifdef HAVE_TIDAL settings.beginGroup(TidalSettingsPage::kSettingsGroup); bool enable_tidal = settings.value("enabled", false).toBool(); settings.endGroup(); @@ -862,7 +862,7 @@ void MainWindow::ReloadAllSettings() { ui_->playlist->view()->ReloadSettings(); album_cover_choice_controller_->ReloadSettings(); if (cover_manager_.get()) cover_manager_->ReloadSettings(); -#ifdef HAVE_STREAM_TIDAL +#ifdef HAVE_TIDAL tidal_search_view_->ReloadSettings(); #endif diff --git a/src/covermanager/tidalcoverprovider.h b/src/covermanager/tidalcoverprovider.h index 28e30e4d8..d77e89351 100644 --- a/src/covermanager/tidalcoverprovider.h +++ b/src/covermanager/tidalcoverprovider.h @@ -35,17 +35,15 @@ #include #include "coverprovider.h" -#include "tidal/tidalservice.h" class Application; +class TidalService; 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); diff --git a/src/lyrics/auddlyricsprovider.cpp b/src/lyrics/auddlyricsprovider.cpp index 238b3b916..184c1fe65 100644 --- a/src/lyrics/auddlyricsprovider.cpp +++ b/src/lyrics/auddlyricsprovider.cpp @@ -92,7 +92,7 @@ void AuddLyricsProvider::HandleSearchReply(QNetworkReply *reply, quint64 id, con reply->deleteLater(); - QJsonArray json_result = ExtractResult(reply, id); + QJsonArray json_result = ExtractResult(reply, id, artist, title); if (json_result.isEmpty()) { return; } @@ -131,6 +131,9 @@ void AuddLyricsProvider::HandleSearchReply(QNetworkReply *reply, quint64 id, con results << result; } + if (results.isEmpty()) qLog(Debug) << "AudDLyrics: No lyrics for" << artist << title; + else qLog(Debug) << "AudDLyrics: Got lyrics for" << artist << title; + emit SearchFinished(id, results); } @@ -173,7 +176,7 @@ QJsonObject AuddLyricsProvider::ExtractJsonObj(QNetworkReply *reply, quint64 id) } -QJsonArray AuddLyricsProvider::ExtractResult(QNetworkReply *reply, quint64 id) { +QJsonArray AuddLyricsProvider::ExtractResult(QNetworkReply *reply, const quint64 id, const QString &artist, const QString &title) { QJsonObject json_obj = ExtractJsonObj(reply, id); if (json_obj.isEmpty()) return QJsonArray(); @@ -206,7 +209,7 @@ QJsonArray AuddLyricsProvider::ExtractResult(QNetworkReply *reply, quint64 id) { QJsonArray json_result = json_obj["result"].toArray(); if (json_result.isEmpty()) { - Error(id, "No match."); + Error(id, QString("No lyrics for %1 %2").arg(artist).arg(title)); return QJsonArray(); } diff --git a/src/lyrics/auddlyricsprovider.h b/src/lyrics/auddlyricsprovider.h index 1405cf12b..29f293ce0 100644 --- a/src/lyrics/auddlyricsprovider.h +++ b/src/lyrics/auddlyricsprovider.h @@ -54,7 +54,7 @@ class AuddLyricsProvider : public LyricsProvider { void Error(quint64 id, QString error, QVariant debug = QVariant()); QJsonObject ExtractJsonObj(QNetworkReply *reply, quint64 id); - QJsonArray ExtractResult(QNetworkReply *reply, quint64 id); + QJsonArray ExtractResult(QNetworkReply *reply, const quint64 id, const QString &artist, const QString &title); }; diff --git a/src/lyrics/chartlyricsprovider.cpp b/src/lyrics/chartlyricsprovider.cpp new file mode 100644 index 000000000..982531d89 --- /dev/null +++ b/src/lyrics/chartlyricsprovider.cpp @@ -0,0 +1,138 @@ +/* + * Strawberry Music Player + * Copyright 2018, 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 +#include +#include +#include +#include + +#include "core/closure.h" +#include "core/logging.h" +#include "core/network.h" +#include "core/utilities.h" +#include "lyricsprovider.h" +#include "lyricsfetcher.h" +#include "chartlyricsprovider.h" + +const char *ChartLyricsProvider::kUrlSearch = "http://api.chartlyrics.com/apiv1.asmx/SearchLyricDirect"; +const int ChartLyricsProvider::kMaxLength = 6000; + +ChartLyricsProvider::ChartLyricsProvider(QObject *parent) : LyricsProvider("ChartLyrics", parent), network_(new NetworkAccessManager(this)) {} + +bool ChartLyricsProvider::StartSearch(const QString &artist, const QString &album, const QString &title, quint64 id) { + + typedef QPair Param; + typedef QList ParamList; + + typedef QPair EncodedParam; + typedef QList EncodedParamList; + + ParamList params = ParamList() << Param("artist", artist) + << Param("song", title); + + QUrlQuery url_query; + QUrl url(kUrlSearch); + + for (const Param ¶m : params) { + EncodedParam encoded_param(QUrl::toPercentEncoding(param.first), QUrl::toPercentEncoding(param.second)); + url_query.addQueryItem(encoded_param.first, encoded_param.second); + } + + url.setQuery(url_query); + QNetworkReply *reply = network_->get(QNetworkRequest(url)); + NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleSearchReply(QNetworkReply*, quint64, QString, QString)), reply, id, artist, title); + + return true; + +} + +void ChartLyricsProvider::CancelSearch(quint64 id) { +} + +void ChartLyricsProvider::HandleSearchReply(QNetworkReply *reply, quint64 id, const QString artist, const QString title) { + + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()); + Error(id, failure_reason); + return; + } + + QXmlStreamReader reader(reply); + LyricsSearchResults results; + LyricsSearchResult result; + + while (!reader.atEnd()) { + QXmlStreamReader::TokenType type = reader.readNext(); + QStringRef name = reader.name(); + if (type == QXmlStreamReader::StartElement) { + if (name == "GetLyricResult") { + result = LyricsSearchResult(); + } + if (name == "LyricArtist") { + result.artist = reader.readElementText(); + } + else if (name == "LyricSong") { + result.title = reader.readElementText(); + } + else if (name == "Lyric") { + result.lyrics = reader.readElementText(); + } + } + else if (type == QXmlStreamReader::EndElement) { + if (name == "GetLyricResult") { + if (!result.artist.isEmpty() && !result.title.isEmpty() && !result.lyrics.isEmpty()) { + result.score = 0.0; + if (result.artist.toLower() == artist.toLower()) result.score += 1.0; + if (result.title.toLower() == title.toLower()) result.score += 1.0; + results << result; + } + result = LyricsSearchResult(); + } + } + } + + if (results.isEmpty()) qLog(Debug) << "ChartLyrics: No lyrics for" << artist << title; + else qLog(Debug) << "ChartLyrics: Got lyrics for" << artist << title; + + emit SearchFinished(id, results); + +} + +void ChartLyricsProvider::Error(quint64 id, QString error, QVariant debug) { + qLog(Error) << "ChartLyrics:" << error; + if (debug.isValid()) qLog(Debug) << debug; + LyricsSearchResults results; + emit SearchFinished(id, results); +} diff --git a/src/lyrics/chartlyricsprovider.h b/src/lyrics/chartlyricsprovider.h new file mode 100644 index 000000000..0594690e1 --- /dev/null +++ b/src/lyrics/chartlyricsprovider.h @@ -0,0 +1,58 @@ +/* + * Strawberry Music Player + * Copyright 2018, 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 CHARTLYRICSPROVIDER_H +#define CHARTLYRICSPROVIDER_H + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "lyricsprovider.h" +#include "lyricsfetcher.h" + +class ChartLyricsProvider : public LyricsProvider { + Q_OBJECT + + public: + explicit ChartLyricsProvider(QObject *parent = nullptr); + + bool StartSearch(const QString &artist, const QString &album, const QString &title, quint64 id); + void CancelSearch(quint64 id); + + private slots: + void HandleSearchReply(QNetworkReply *reply, quint64 id, const QString artist, const QString title); + + private: + static const char *kUrlSearch; + static const int kMaxLength; + QNetworkAccessManager *network_; + void Error(quint64 id, QString error, QVariant debug = QVariant()); + +}; + +#endif // AUDDLYRICSPROVIDER_H + diff --git a/src/settings/settingsdialog.cpp b/src/settings/settingsdialog.cpp index c94b211d0..2afb02fbe 100644 --- a/src/settings/settingsdialog.cpp +++ b/src/settings/settingsdialog.cpp @@ -62,7 +62,7 @@ #include "transcodersettingspage.h" #include "networkproxysettingspage.h" #include "scrobblersettingspage.h" -#ifdef HAVE_STREAM_TIDAL +#ifdef HAVE_TIDAL # include "tidalsettingspage.h" #endif @@ -133,10 +133,10 @@ SettingsDialog::SettingsDialog(Application *app, QWidget *parent) AddPage(Page_GlobalShortcuts, new GlobalShortcutsSettingsPage(this), iface); #endif -#if defined(HAVE_STREAM_TIDAL) +#if defined(HAVE_TIDAL) QTreeWidgetItem *streaming = AddCategory(tr("Streaming")); #endif -#ifdef HAVE_STREAM_TIDAL +#ifdef HAVE_TIDAL AddPage(Page_Tidal, new TidalSettingsPage(this), streaming); #endif