Add ChartLyrics provider

This commit is contained in:
Jonas Kvinge 2019-04-14 18:02:51 +02:00
parent fd26137ad2
commit 380b84195f
16 changed files with 235 additions and 32 deletions

View File

@ -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)

View File

@ -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

2
dist/debian/control vendored
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
)

View File

@ -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

View File

@ -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<CurrentArtLoader> current_art_loader_;
Lazy<LyricsProviders> lyrics_providers_;
Lazy<InternetServices> internet_services_;
#ifdef HAVE_STREAM_TIDAL
#ifdef HAVE_TIDAL
Lazy<InternetSearch> tidal_search_;
#endif
Lazy<AudioScrobbler> 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(); }

View File

@ -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

View File

@ -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

View File

@ -35,17 +35,15 @@
#include <QJsonArray>
#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);

View File

@ -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();
}

View File

@ -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);
};

View File

@ -0,0 +1,138 @@
/*
* 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 <QObject>
#include <QByteArray>
#include <QList>
#include <QPair>
#include <QMap>
#include <QSet>
#include <QVariant>
#include <QString>
#include <QStringBuilder>
#include <QStringList>
#include <QUrl>
#include <QUrlQuery>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QXmlStreamReader>
#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<QString, QString> Param;
typedef QList<Param> ParamList;
typedef QPair<QByteArray, QByteArray> EncodedParam;
typedef QList<EncodedParam> EncodedParamList;
ParamList params = ParamList() << Param("artist", artist)
<< Param("song", title);
QUrlQuery url_query;
QUrl url(kUrlSearch);
for (const Param &param : 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);
}

View File

@ -0,0 +1,58 @@
/*
* 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 CHARTLYRICSPROVIDER_H
#define CHARTLYRICSPROVIDER_H
#include "config.h"
#include <stdbool.h>
#include <QObject>
#include <QHash>
#include <QMetaType>
#include <QString>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#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

View File

@ -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