diff --git a/data/icons.qrc b/data/icons.qrc
index 9993765b1..89503e12e 100644
--- a/data/icons.qrc
+++ b/data/icons.qrc
@@ -203,6 +203,7 @@
icons/48x48/document-download.png
icons/48x48/document-new.png
icons/48x48/document-open-folder.png
+ icons/48x48/document-open-remote.png
icons/48x48/document-open.png
icons/48x48/document-save.png
icons/48x48/document-search.png
@@ -297,6 +298,7 @@
icons/32x32/document-download.png
icons/32x32/document-new.png
icons/32x32/document-open-folder.png
+ icons/32x32/document-open-remote.png
icons/32x32/document-open.png
icons/32x32/document-save.png
icons/32x32/document-search.png
@@ -391,6 +393,7 @@
icons/22x22/document-download.png
icons/22x22/document-new.png
icons/22x22/document-open-folder.png
+ icons/22x22/document-open-remote.png
icons/22x22/document-open.png
icons/22x22/document-save.png
icons/22x22/document-search.png
diff --git a/data/icons/22x22/document-open-remote.png b/data/icons/22x22/document-open-remote.png
new file mode 100644
index 000000000..01273d8e4
Binary files /dev/null and b/data/icons/22x22/document-open-remote.png differ
diff --git a/data/icons/32x32/document-open-remote.png b/data/icons/32x32/document-open-remote.png
new file mode 100644
index 000000000..c48683fe2
Binary files /dev/null and b/data/icons/32x32/document-open-remote.png differ
diff --git a/data/icons/48x48/document-open-remote.png b/data/icons/48x48/document-open-remote.png
new file mode 100644
index 000000000..18b19e2c1
Binary files /dev/null and b/data/icons/48x48/document-open-remote.png differ
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 623c46603..28614dc03 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -254,6 +254,7 @@ set(SOURCES
dialogs/errordialog.cpp
dialogs/edittagdialog.cpp
dialogs/trackselectiondialog.cpp
+ dialogs/addstreamdialog.cpp
widgets/autoexpandingtreeview.cpp
widgets/busyindicator.cpp
@@ -439,6 +440,7 @@ set(HEADERS
dialogs/console.h
dialogs/edittagdialog.h
dialogs/trackselectiondialog.h
+ dialogs/addstreamdialog.h
widgets/autoexpandingtreeview.h
widgets/busyindicator.h
@@ -537,6 +539,7 @@ set(UI
dialogs/console.ui
dialogs/edittagdialog.ui
dialogs/trackselectiondialog.ui
+ dialogs/addstreamdialog.ui
widgets/trackslider.ui
widgets/osdpretty.ui
diff --git a/src/collection/collectionfilterwidget.cpp b/src/collection/collectionfilterwidget.cpp
index ec92b22b8..be32fb0ce 100644
--- a/src/collection/collectionfilterwidget.cpp
+++ b/src/collection/collectionfilterwidget.cpp
@@ -297,6 +297,7 @@ void CollectionFilterWidget::SetCollectionModel(CollectionModel *model) {
}
void CollectionFilterWidget::GroupByClicked(QAction *action) {
+
if (action->property("group_by").isNull()) {
group_by_dialog_->show();
return;
@@ -304,6 +305,7 @@ void CollectionFilterWidget::GroupByClicked(QAction *action) {
CollectionModel::Grouping g = action->property("group_by").value();
model_->SetGroupBy(g);
+
}
void CollectionFilterWidget::GroupingChanged(const CollectionModel::Grouping &g) {
diff --git a/src/collection/collectionmodel.cpp b/src/collection/collectionmodel.cpp
index 71b1dbad3..7c69bef9c 100644
--- a/src/collection/collectionmodel.cpp
+++ b/src/collection/collectionmodel.cpp
@@ -66,10 +66,9 @@
#include "playlist/playlistmanager.h"
#include "playlist/songmimedata.h"
#include "covermanager/albumcoverloader.h"
+#include "covermanager/albumcoverloaderresult.h"
#include "settings/collectionsettingspage.h"
-using std::bind;
-using std::sort;
using std::placeholders::_1;
using std::placeholders::_2;
@@ -116,7 +115,7 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q
cover_loader_options_.scale_output_image_ = true;
if (app_)
- connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(AlbumCoverLoaded(quint64, QUrl, QImage)));
+ connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)));
QIcon nocover = IconLoader::Load("cdcase");
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
@@ -613,9 +612,7 @@ QVariant CollectionModel::AlbumIcon(const QModelIndex &idx) {
}
-void CollectionModel::AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) {
-
- Q_UNUSED(cover_url);
+void CollectionModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) {
if (!pending_art_.contains(id)) return;
@@ -628,26 +625,26 @@ void CollectionModel::AlbumCoverLoaded(const quint64 id, const QUrl &cover_url,
pending_cache_keys_.remove(cache_key);
// Insert this image in the cache.
- if (image.isNull()) {
+ if (result.image_scaled.isNull()) {
// Set the no_cover image so we don't continually try to load art.
QPixmapCache::insert(cache_key, no_cover_icon_);
}
else {
QPixmap image_pixmap;
- image_pixmap = QPixmap::fromImage(image);
+ image_pixmap = QPixmap::fromImage(result.image_scaled);
QPixmapCache::insert(cache_key, image_pixmap);
}
// If we have a valid cover not already in the disk cache
if (use_disk_cache_) {
std::unique_ptr cached_img(sIconCache->data(QUrl(cache_key)));
- if (!cached_img && !image.isNull()) {
+ if (!cached_img && !result.image_scaled.isNull()) {
QNetworkCacheMetaData item_metadata;
item_metadata.setSaveToDisk(true);
item_metadata.setUrl(QUrl(cache_key));
QIODevice* cache = sIconCache->prepare(item_metadata);
if (cache) {
- image.save(cache, "XPM");
+ result.image_scaled.save(cache, "XPM");
sIconCache->insert(cache);
}
}
diff --git a/src/collection/collectionmodel.h b/src/collection/collectionmodel.h
index e8b8b9112..68e4dbe56 100644
--- a/src/collection/collectionmodel.h
+++ b/src/collection/collectionmodel.h
@@ -44,6 +44,7 @@
#include "core/simpletreemodel.h"
#include "core/song.h"
+#include "covermanager/albumcoverloader.h"
#include "collectionquery.h"
#include "collectionitem.h"
#include "sqlrow.h"
@@ -210,7 +211,7 @@ signals:
// Called after ResetAsync
void ResetAsyncQueryFinished(QFuture future);
- void AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
+ void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
private:
// Provides some optimisations for loading the list of items in the root.
diff --git a/src/collection/collectionplaylistitem.cpp b/src/collection/collectionplaylistitem.cpp
index 666321de9..5fd437327 100644
--- a/src/collection/collectionplaylistitem.cpp
+++ b/src/collection/collectionplaylistitem.cpp
@@ -43,20 +43,33 @@ void CollectionPlaylistItem::Reload() {
}
bool CollectionPlaylistItem::InitFromQuery(const SqlRow &query) {
+
// Rows from the songs tables come first
song_.InitFromQuery(query, true);
song_.set_source(Song::Source_Collection);
return song_.is_valid();
+
}
QVariant CollectionPlaylistItem::DatabaseValue(DatabaseColumn column) const {
+
switch (column) {
case Column_CollectionId: return song_.id();
default: return PlaylistItem::DatabaseValue(column);
}
+
}
Song CollectionPlaylistItem::Metadata() const {
+
if (HasTemporaryMetadata()) return temp_metadata_;
return song_;
+
+}
+
+void CollectionPlaylistItem::SetArtManual(const QUrl &cover_url) {
+
+ song_.set_art_manual(cover_url);
+ temp_metadata_.set_art_manual(cover_url);
+
}
diff --git a/src/collection/collectionplaylistitem.h b/src/collection/collectionplaylistitem.h
index 965c69de1..b9c6ac7df 100644
--- a/src/collection/collectionplaylistitem.h
+++ b/src/collection/collectionplaylistitem.h
@@ -46,6 +46,8 @@ class CollectionPlaylistItem : public PlaylistItem {
bool IsLocalCollectionItem() const { return true; }
+ void SetArtManual(const QUrl &cover_url);
+
protected:
QVariant DatabaseValue(DatabaseColumn column) const;
Song DatabaseSongMetadata() const { return Song(Song::Source_Collection); }
diff --git a/src/context/contextalbum.cpp b/src/context/contextalbum.cpp
index 08a836896..fc7f1b9d1 100644
--- a/src/context/contextalbum.cpp
+++ b/src/context/contextalbum.cpp
@@ -57,7 +57,8 @@ ContextAlbum::ContextAlbum(QWidget *parent) :
cover_loader_options_.desired_height_ = 600;
cover_loader_options_.pad_output_image_ = true;
cover_loader_options_.scale_output_image_ = true;
- pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_strawberry_));
+ QPair images = AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_strawberry_);
+ pixmap_current_ = QPixmap::fromImage(images.first);
connect(timeline_fade_, SIGNAL(valueChanged(qreal)), SLOT(FadePreviousTrack(qreal)));
timeline_fade_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
@@ -89,7 +90,7 @@ void ContextAlbum::DrawImage(QPainter *p) {
if (width() != prev_width_) {
cover_loader_options_.desired_height_ = width() - kWidgetSpacing;
- pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_));
+ pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_).first);
prev_width_ = width();
}
@@ -118,7 +119,7 @@ void ContextAlbum::FadePreviousTrack(const qreal value) {
void ContextAlbum::ScaleCover() {
cover_loader_options_.desired_height_ = width() - kWidgetSpacing;
- pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_));
+ pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_).first);
prev_width_ = width();
update();
diff --git a/src/context/contextalbumsmodel.cpp b/src/context/contextalbumsmodel.cpp
index 07bdb7a3b..3a8b0e5a2 100644
--- a/src/context/contextalbumsmodel.cpp
+++ b/src/context/contextalbumsmodel.cpp
@@ -48,11 +48,11 @@
#include "playlist/playlistmanager.h"
#include "playlist/songmimedata.h"
#include "covermanager/albumcoverloader.h"
+#include "covermanager/albumcoverloaderoptions.h"
+#include "covermanager/albumcoverloaderresult.h"
#include "contextalbumsmodel.h"
-using std::bind;
-using std::sort;
using std::placeholders::_1;
using std::placeholders::_2;
@@ -71,7 +71,7 @@ ContextAlbumsModel::ContextAlbumsModel(CollectionBackend *backend, Application *
cover_loader_options_.pad_output_image_ = true;
cover_loader_options_.scale_output_image_ = true;
- connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(AlbumCoverLoaded(quint64, QUrl, QImage)));
+ connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)));
QIcon nocover = IconLoader::Load("cdcase");
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
@@ -146,9 +146,7 @@ QVariant ContextAlbumsModel::AlbumIcon(const QModelIndex &index) {
}
-void ContextAlbumsModel::AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) {
-
- Q_UNUSED(cover_url);
+void ContextAlbumsModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) {
if (!pending_art_.contains(id)) return;
@@ -161,13 +159,13 @@ void ContextAlbumsModel::AlbumCoverLoaded(const quint64 id, const QUrl &cover_ur
pending_cache_keys_.remove(cache_key);
// Insert this image in the cache.
- if (image.isNull()) {
+ if (result.image_scaled.isNull()) {
// Set the no_cover image so we don't continually try to load art.
QPixmapCache::insert(cache_key, no_cover_icon_);
}
else {
QPixmap image_pixmap;
- image_pixmap = QPixmap::fromImage(image);
+ image_pixmap = QPixmap::fromImage(result.image_scaled);
QPixmapCache::insert(cache_key, image_pixmap);
}
diff --git a/src/context/contextalbumsmodel.h b/src/context/contextalbumsmodel.h
index 9eb1bc3aa..3541af3be 100644
--- a/src/context/contextalbumsmodel.h
+++ b/src/context/contextalbumsmodel.h
@@ -45,6 +45,7 @@
#include "collection/collectionitem.h"
#include "collection/sqlrow.h"
#include "covermanager/albumcoverloaderoptions.h"
+#include "covermanager/albumcoverloaderresult.h"
class QMimeData;
@@ -99,7 +100,7 @@ class ContextAlbumsModel : public SimpleTreeModel {
void LazyPopulate(CollectionItem *item, bool signal);
private slots:
- void AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
+ void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
private:
QueryResult RunQuery(CollectionItem *parent);
diff --git a/src/context/contextview.cpp b/src/context/contextview.cpp
index 912c528f8..eff944d8a 100644
--- a/src/context/contextview.cpp
+++ b/src/context/contextview.cpp
@@ -124,6 +124,7 @@ ContextView::ContextView(QWidget *parent) :
label_device_icon_(new QLabel(this)),
label_engine_icon_(new QLabel(this)),
spacer_bottom_(new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Expanding)),
+ lyrics_tried_(false),
lyrics_id_(-1),
prev_width_(0)
{
@@ -365,19 +366,22 @@ void ContextView::Error() {}
void ContextView::SongChanged(const Song &song) {
- if (widget_stacked_->currentWidget() == widget_play_ && song_playing_.is_valid() && song == song_playing_) {
+ if (widget_stacked_->currentWidget() == widget_play_ && song_playing_.is_valid() && song == song_playing_ && song.title() == song_playing_.title() && song.album() == song_playing_.album() && song.artist() == song_playing_.artist()) {
UpdateSong(song);
}
else {
song_prev_ = song_playing_;
+ song_playing_ = song;
lyrics_ = song.lyrics();
lyrics_id_ = -1;
- song_playing_ = song;
+ lyrics_tried_ = false;
SetSong();
- if (lyrics_.isEmpty() && action_show_lyrics_->isChecked() && !song.artist().isEmpty() && !song.title().isEmpty()) {
- lyrics_fetcher_->Clear();
- lyrics_id_ = lyrics_fetcher_->Search(song.effective_albumartist(), song.album(), song.title());
- }
+ }
+
+ if (lyrics_.isEmpty() && action_show_lyrics_->isChecked() && !song.artist().isEmpty() && !song.title().isEmpty() && !lyrics_tried_ && lyrics_id_ == -1) {
+ lyrics_fetcher_->Clear();
+ lyrics_tried_ = true;
+ lyrics_id_ = lyrics_fetcher_->Search(song.effective_albumartist(), song.album(), song.title());
}
}
@@ -684,9 +688,7 @@ void ContextView::dropEvent(QDropEvent *e) {
}
-void ContextView::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image) {
-
- Q_UNUSED(cover_url);
+void ContextView::AlbumCoverLoaded(const Song &song, const QImage &image) {
if (song != song_playing_ || image == image_original_) return;
diff --git a/src/context/contextview.h b/src/context/contextview.h
index cbb7f5c05..265a342ed 100644
--- a/src/context/contextview.h
+++ b/src/context/contextview.h
@@ -91,7 +91,7 @@ class ContextView : public QWidget {
void Stopped();
void Error();
void SongChanged(const Song &song);
- void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image);
+ void AlbumCoverLoaded(const Song &song, const QImage &image);
void FadeStopFinished();
void UpdateLyrics(const quint64 id, const QString &provider, const QString &lyrics);
@@ -164,6 +164,7 @@ class ContextView : public QWidget {
Song song_playing_;
Song song_prev_;
QImage image_original_;
+ bool lyrics_tried_;
qint64 lyrics_id_;
QString lyrics_;
QString title_fmt_;
diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp
index 449b96df1..f2cdf2faf 100644
--- a/src/core/mainwindow.cpp
+++ b/src/core/mainwindow.cpp
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
- * Copyright 2013, Jonas Kvinge
+ * Copyright 2013-2020, 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
@@ -96,6 +96,7 @@
#include "dialogs/console.h"
#include "dialogs/trackselectiondialog.h"
#include "dialogs/edittagdialog.h"
+#include "dialogs/addstreamdialog.h"
#include "organise/organisedialog.h"
#include "widgets/fancytabwidget.h"
#include "widgets/playingwidget.h"
@@ -131,7 +132,7 @@
#endif
#include "covermanager/albumcovermanager.h"
#include "covermanager/albumcoverchoicecontroller.h"
-#include "covermanager/albumcoverloader.h"
+#include "covermanager/albumcoverloaderresult.h"
#include "covermanager/currentalbumcoverloader.h"
#ifndef Q_OS_WIN
# include "device/devicemanager.h"
@@ -178,10 +179,6 @@
# include "windows7thumbbar.h"
#endif
-using std::bind;
-using std::floor;
-using std::stable_sort;
-
const char *MainWindow::kSettingsGroup = "MainWindow";
const char *MainWindow::kAllFilesFilterSpec = QT_TR_NOOP("All Files (*)");
@@ -231,6 +228,11 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
TranscodeDialog *dialog = new TranscodeDialog(this);
return dialog;
}),
+ add_stream_dialog_([=]() {
+ AddStreamDialog *add_stream_dialog = new AddStreamDialog;
+ connect(add_stream_dialog, SIGNAL(accepted()), this, SLOT(AddStreamAccepted()));
+ return add_stream_dialog;
+ }),
#ifdef HAVE_SUBSONIC
subsonic_view_(new InternetSongsView(app_, app->internet_services()->ServiceBySource(Song::Source_Subsonic), SubsonicSettingsPage::kSettingsGroup, SettingsDialog::Page_Subsonic, this)),
#endif
@@ -260,7 +262,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
// Initialise the UI
ui_->setupUi(this);
- connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, QUrl, QImage)), SLOT(AlbumCoverLoaded(Song, QUrl, QImage)));
+ connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)));
album_cover_choice_controller_->Init(app);
connect(album_cover_choice_controller_->cover_from_file_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromFile()));
connect(album_cover_choice_controller_->cover_to_file_action(), SIGNAL(triggered()), this, SLOT(SaveCoverToFile()));
@@ -361,6 +363,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
ui_->action_add_file->setIcon(IconLoader::Load("document-open"));
ui_->action_add_folder->setIcon(IconLoader::Load("document-open-folder"));
+ ui_->action_add_stream->setIcon(IconLoader::Load("document-open-remote"));
ui_->action_shuffle_mode->setIcon(IconLoader::Load("media-playlist-shuffle"));
ui_->action_repeat_mode->setIcon(IconLoader::Load("media-playlist-repeat"));
ui_->action_new_playlist->setIcon(IconLoader::Load("document-new"));
@@ -431,6 +434,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
connect(ui_->action_open_cd, SIGNAL(triggered()), SLOT(AddCDTracks()));
connect(ui_->action_add_file, SIGNAL(triggered()), SLOT(AddFile()));
connect(ui_->action_add_folder, SIGNAL(triggered()), SLOT(AddFolder()));
+ connect(ui_->action_add_stream, SIGNAL(triggered()), SLOT(AddStream()));
connect(ui_->action_cover_manager, SIGNAL(triggered()), SLOT(ShowCoverManager()));
connect(ui_->action_equalizer, SIGNAL(triggered()), equalizer_.get(), SLOT(show()));
#if defined(HAVE_GSTREAMER)
@@ -705,7 +709,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
connect(app_->player(), SIGNAL(Playing()), context_view_, SLOT(Playing()));
connect(app_->player(), SIGNAL(Stopped()), context_view_, SLOT(Stopped()));
connect(app_->player(), SIGNAL(Error()), context_view_, SLOT(Error()));
- connect(this, SIGNAL(AlbumCoverReady(Song, QUrl, QImage)), context_view_, SLOT(AlbumCoverLoaded(Song, QUrl, QImage)));
+ connect(this, SIGNAL(AlbumCoverReady(Song, QImage)), context_view_, SLOT(AlbumCoverLoaded(Song, QImage)));
connect(this, SIGNAL(SearchCoverInProgress()), context_view_->album_widget(), SLOT(SearchCoverInProgress()));
connect(context_view_, SIGNAL(AlbumEnabledChanged()), SLOT(TabSwitched()));
connect(context_view_->albums_widget(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
@@ -738,7 +742,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
connect(app_->player(), SIGNAL(Stopped()), ui_->widget_playing, SLOT(Stopped()));
connect(app_->player(), SIGNAL(Error()), ui_->widget_playing, SLOT(Error()));
connect(ui_->widget_playing, SIGNAL(ShowAboveStatusBarChanged(bool)), SLOT(PlayingWidgetPositionChanged(bool)));
- connect(this, SIGNAL(AlbumCoverReady(Song, QUrl, QImage)), ui_->widget_playing, SLOT(AlbumCoverLoaded(Song, QUrl, QImage)));
+ connect(this, SIGNAL(AlbumCoverReady(Song, QImage)), ui_->widget_playing, SLOT(AlbumCoverLoaded(Song, QImage)));
connect(this, SIGNAL(SearchCoverInProgress()), ui_->widget_playing, SLOT(SearchCoverInProgress()));
connect(ui_->action_console, SIGNAL(triggered()), SLOT(ShowConsole()));
@@ -1777,7 +1781,7 @@ void MainWindow::EditTracks() {
void MainWindow::EditTagDialogAccepted() {
- for (const PlaylistItemPtr item : edit_tag_dialog_->playlist_items()) {
+ for (PlaylistItemPtr item : edit_tag_dialog_->playlist_items()) {
item->Reload();
}
@@ -1923,6 +1927,16 @@ void MainWindow::AddCDTracks() {
}
+void MainWindow::AddStream() { add_stream_dialog_->show(); }
+
+void MainWindow::AddStreamAccepted() {
+
+ MimeData* data = new MimeData;
+ data->setUrls(QList() << add_stream_dialog_->url());
+ AddToPlaylist(data);
+
+}
+
void MainWindow::ShowInCollection() {
// Show the first valid selected track artist/album in CollectionView
@@ -2512,7 +2526,7 @@ void MainWindow::AutoCompleteTags() {
void MainWindow::AutoCompleteTagsAccepted() {
- for (const PlaylistItemPtr item : autocomplete_tag_items_) {
+ for (PlaylistItemPtr item : autocomplete_tag_items_) {
item->Reload();
}
autocomplete_tag_items_.clear();
@@ -2600,14 +2614,14 @@ void MainWindow::SearchCoverAutomatically() {
}
-void MainWindow::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image) {
+void MainWindow::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) {
- if (song.effective_albumartist() != song_playing_.effective_albumartist() || song.effective_album() != song_playing_.effective_album() || song.title() != song_playing_.title()) return;
+ if (song != song_playing_) return;
song_ = song;
- image_original_ = image;
+ image_original_ = result.image_original;
- emit AlbumCoverReady(song, cover_url, image);
+ emit AlbumCoverReady(song, result.image_original);
GetCoverAutomatically();
@@ -2617,13 +2631,12 @@ void MainWindow::GetCoverAutomatically() {
// Search for cover automatically?
bool search =
- song_.source() == Song::Source_Collection &&
- album_cover_choice_controller_->search_cover_auto_action()->isChecked() &&
- !song_.has_manually_unset_cover() &&
- !song_.art_automatic_is_valid() &&
- !song_.art_manual_is_valid() &&
- !song_.effective_albumartist().isEmpty() &&
- !song_.effective_album().isEmpty();
+ album_cover_choice_controller_->search_cover_auto_action()->isChecked() &&
+ !song_.has_manually_unset_cover() &&
+ !song_.art_automatic_is_valid() &&
+ !song_.art_manual_is_valid() &&
+ !song_.effective_albumartist().isEmpty() &&
+ !song_.effective_album().isEmpty();
if (search) {
album_cover_choice_controller_->SearchCoverAutomatically(song_);
diff --git a/src/core/mainwindow.h b/src/core/mainwindow.h
index 8b68c6326..77d9f02dd 100644
--- a/src/core/mainwindow.h
+++ b/src/core/mainwindow.h
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
- * Copyright 2013, Jonas Kvinge
+ * Copyright 2013-2020, 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
@@ -59,8 +59,7 @@
#include "playlist/playlistitem.h"
#include "settings/settingsdialog.h"
#include "settings/behavioursettingspage.h"
-
-using std::unique_ptr;
+#include "covermanager/albumcoverloaderresult.h"
class About;
class AlbumCoverManager;
@@ -95,6 +94,7 @@ class InternetTabsView;
#ifdef Q_OS_WIN
class Windows7ThumbBar;
#endif
+class AddStreamDialog;
class MainWindow : public QMainWindow, public PlatformInterface {
Q_OBJECT
@@ -126,7 +126,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
bool LoadUrl(const QString& url);
signals:
- void AlbumCoverReady(const Song &song, const QUrl &cover_url, const QImage &image);
+ void AlbumCoverReady(const Song &song, const QImage &image);
void SearchCoverInProgress();
// Signals that stop playing after track was toggled.
void StopAfterToggled(bool stop);
@@ -210,6 +210,8 @@ class MainWindow : public QMainWindow, public PlatformInterface {
void AddFile();
void AddFolder();
void AddCDTracks();
+ void AddStream();
+ void AddStreamAccepted();
void CommandlineOptionsReceived(const quint32 instanceId, const QByteArray &string_options);
@@ -251,7 +253,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
void UnsetCover();
void ShowCover();
void SearchCoverAutomatically();
- void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image);
+ void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result);
void ScrobblingEnabledChanged(const bool value);
void ScrobbleButtonVisibilityChanged(const bool value);
@@ -308,6 +310,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
#ifdef HAVE_GSTREAMER
Lazy transcode_dialog_;
#endif
+ Lazy add_stream_dialog_;
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
std::unique_ptr tag_fetcher_;
diff --git a/src/core/mainwindow.ui b/src/core/mainwindow.ui
index f683e9542..ff3211a61 100644
--- a/src/core/mainwindow.ui
+++ b/src/core/mainwindow.ui
@@ -469,6 +469,7 @@
+
@@ -827,6 +828,11 @@
Rescan songs(s)
+
+
+ Add stream...
+
+
diff --git a/src/core/metatypes.cpp b/src/core/metatypes.cpp
index 0ed47731d..40de20b86 100644
--- a/src/core/metatypes.cpp
+++ b/src/core/metatypes.cpp
@@ -53,6 +53,7 @@
#include "collection/directory.h"
#include "playlist/playlistitem.h"
#include "playlist/playlistsequence.h"
+#include "covermanager/albumcoverloaderresult.h"
#include "covermanager/albumcoverfetcher.h"
#include "equalizer/equalizer.h"
@@ -98,6 +99,8 @@ void RegisterMetaTypes() {
qRegisterMetaType >("QList");
qRegisterMetaType("PlaylistSequence::RepeatMode");
qRegisterMetaType("PlaylistSequence::ShuffleMode");
+ qRegisterMetaType("AlbumCoverLoaderResult");
+ qRegisterMetaType("AlbumCoverLoaderResult::Type");
qRegisterMetaType("CoverSearchResult");
qRegisterMetaType >("QList");
qRegisterMetaType("CoverSearchResults");
diff --git a/src/core/mpris2.cpp b/src/core/mpris2.cpp
index ee0b7f789..8757cde9e 100644
--- a/src/core/mpris2.cpp
+++ b/src/core/mpris2.cpp
@@ -58,14 +58,13 @@
#include "playlist/playlistmanager.h"
#include "playlist/playlistsequence.h"
#include "covermanager/currentalbumcoverloader.h"
+#include "covermanager/albumcoverloaderresult.h"
#include
#include
#include
#include
-using std::reverse;
-
QDBusArgument &operator<<(QDBusArgument &arg, const MprisPlaylist &playlist) {
arg.beginStructure();
arg << playlist.id << playlist.name << playlist.icon;
@@ -122,7 +121,7 @@ Mpris2::Mpris2(Application *app, QObject *parent)
return;
}
- connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, QUrl, QImage)), SLOT(AlbumCoverLoaded(Song, QUrl, QImage)));
+ connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)));
connect(app_->player()->engine(), SIGNAL(StateChanged(Engine::State)), SLOT(EngineStateChanged(Engine::State)));
connect(app_->player(), SIGNAL(VolumeChanged(int)), SLOT(VolumeChanged()));
@@ -378,7 +377,7 @@ QString Mpris2::current_track_id() const {
// We send Metadata change notification as soon as the process of changing song starts...
void Mpris2::CurrentSongChanged(const Song &song) {
- AlbumCoverLoaded(song, QUrl(), QImage());
+ AlbumCoverLoaded(song);
EmitNotification("CanPlay");
EmitNotification("CanPause");
EmitNotification("CanGoNext", CanGoNext());
@@ -388,9 +387,7 @@ void Mpris2::CurrentSongChanged(const Song &song) {
}
// ... and we add the cover information later, when it's available.
-void Mpris2::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image) {
-
- Q_UNUSED(image);
+void Mpris2::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) {
last_metadata_ = QVariantMap();
song.ToXesam(&last_metadata_);
@@ -398,9 +395,14 @@ void Mpris2::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QIm
using mpris::AddMetadata;
AddMetadata("mpris:trackid", current_track_id(), &last_metadata_);
- if (cover_url.isValid()) {
- AddMetadata("mpris:artUrl", cover_url.toString(), &last_metadata_);
+ QUrl cover_url;
+ if (result.cover_url.isValid() && result.cover_url.isLocalFile()) {
+ cover_url = result.cover_url;
}
+ else if (result.temp_cover_url.isValid() && result.temp_cover_url.isLocalFile()) {
+ cover_url = result.temp_cover_url;
+ }
+ if (cover_url.isValid()) AddMetadata("mpris:artUrl", result.cover_url.toString(), &last_metadata_);
AddMetadata("year", song.year(), &last_metadata_);
AddMetadata("bitrate", song.bitrate(), &last_metadata_);
diff --git a/src/core/mpris2.h b/src/core/mpris2.h
index 244fa9bd8..d882ff9a9 100644
--- a/src/core/mpris2.h
+++ b/src/core/mpris2.h
@@ -39,6 +39,7 @@
#include
#include "engine/engine_fwd.h"
+#include "covermanager/albumcoverloaderresult.h"
class Application;
class Song;
@@ -204,7 +205,7 @@ signals:
void PlaylistChanged(const MprisPlaylist &playlist);
private slots:
- void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image);
+ void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result = AlbumCoverLoaderResult());
void EngineStateChanged(Engine::State newState);
void VolumeChanged();
diff --git a/src/core/song.cpp b/src/core/song.cpp
index 7553ff11b..c8f9c4a08 100644
--- a/src/core/song.cpp
+++ b/src/core/song.cpp
@@ -30,6 +30,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -42,6 +43,7 @@
#include
#include
#include
+#include
#include
#ifdef HAVE_LIBGPOD
@@ -66,7 +68,6 @@
#include "covermanager/albumcoverloader.h"
#include "tagreadermessages.pb.h"
-using std::sort;
#ifndef USE_SYSTEM_TAGLIB
using namespace Strawberry_TagLib;
#endif
@@ -353,7 +354,8 @@ bool Song::is_cdda() const { return d->source_ == Source_CDDA; }
bool Song::is_compilation() const { return (d->compilation_ || d->compilation_detected_ || d->compilation_on_) && !d->compilation_off_; }
bool Song::art_automatic_is_valid() const {
- return (
+ return !d->art_automatic_.isEmpty() &&
+ (
(d->art_automatic_.path() == kManuallyUnsetCover) ||
(d->art_automatic_.path() == kEmbeddedCover) ||
(d->art_automatic_.isValid() && !d->art_automatic_.isLocalFile()) ||
@@ -363,7 +365,8 @@ bool Song::art_automatic_is_valid() const {
}
bool Song::art_manual_is_valid() const {
- return (
+ return !d->art_manual_.isEmpty() &&
+ (
(d->art_manual_.path() == kManuallyUnsetCover) ||
(d->art_manual_.path() == kEmbeddedCover) ||
(d->art_manual_.isValid() && !d->art_manual_.isLocalFile()) ||
@@ -655,6 +658,29 @@ Song::FileType Song::FiletypeByExtension(const QString &ext) {
}
+QString Song::ImageCacheDir(const Song::Source source) {
+
+ switch (source) {
+ case Song::Source_Collection:
+ return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/collectionalbumcovers";
+ case Song::Source_Subsonic:
+ return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/subsonicalbumcovers";
+ case Song::Source_Tidal:
+ return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/tidalalbumcovers";
+ case Song::Source_Qobuz:
+ return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/qobuzalbumcovers";
+ case Song::Source_LocalFile:
+ case Song::Source_CDDA:
+ case Song::Source_Device:
+ case Song::Source_Stream:
+ case Song::Source_Unknown:
+ return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/albumcovers";
+ }
+
+ return QString();
+
+}
+
int CompareSongsName(const Song &song1, const Song &song2) {
return song1.PrettyTitleWithArtist().localeAwareCompare(song2.PrettyTitleWithArtist()) < 0;
}
@@ -997,16 +1023,24 @@ void Song::InitFromFilePartial(const QString &filename) {
void Song::InitArtManual() {
- QString album = d->album_;
+ QString album = effective_album();
album.remove(Song::kAlbumRemoveDisc);
// If we don't have an art, check if we have one in the cache
- if (d->art_manual_.isEmpty() && d->art_automatic_.isEmpty()) {
+ if (d->art_manual_.isEmpty() && d->art_automatic_.isEmpty() && !effective_albumartist().isEmpty() && !album.isEmpty()) {
QString filename(Utilities::Sha1CoverHash(effective_albumartist(), album).toHex() + ".jpg");
- QString path(AlbumCoverLoader::ImageCacheDir(d->source_) + "/" + filename);
+ QString path(ImageCacheDir(d->source_) + "/" + filename);
if (QFile::exists(path)) {
d->art_manual_ = QUrl::fromLocalFile(path);
}
+ else if (d->url_.isLocalFile()) { // Pick the first image file in the album directory.
+ QFileInfo file(d->url_.toLocalFile());
+ QDir dir(file.path());
+ QStringList files = dir.entryList(QStringList() << "*.jpg" << "*.png" << "*.gif" << "*.jpeg", QDir::Files|QDir::Readable, QDir::Name);
+ if (files.count() > 0) {
+ d->art_manual_ = QUrl::fromLocalFile(file.path() + QDir::separator() + files.first());
+ }
+ }
}
}
diff --git a/src/core/song.h b/src/core/song.h
index d85c08a3f..24152baf9 100644
--- a/src/core/song.h
+++ b/src/core/song.h
@@ -147,6 +147,7 @@ class Song {
static FileType FiletypeByMimetype(const QString &mimetype);
static FileType FiletypeByDescription(const QString &text);
static FileType FiletypeByExtension(const QString &ext);
+ static QString ImageCacheDir(const Song::Source source);
// Sort songs alphabetically using their pretty title
static void SortSongsListAlphabetically(QList *songs);
diff --git a/src/core/standarditemiconloader.cpp b/src/core/standarditemiconloader.cpp
index 468851251..783b865af 100644
--- a/src/core/standarditemiconloader.cpp
+++ b/src/core/standarditemiconloader.cpp
@@ -40,7 +40,7 @@ StandardItemIconLoader::StandardItemIconLoader(AlbumCoverLoader *cover_loader, Q
cover_options_.desired_height_ = 16;
- connect(cover_loader_, SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(ImageLoaded(quint64, QUrl, QImage)));
+ connect(cover_loader_, SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)));
}
void StandardItemIconLoader::SetModel(QAbstractItemModel *model) {
@@ -96,15 +96,13 @@ void StandardItemIconLoader::ModelReset() {
}
-void StandardItemIconLoader::ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) {
-
- Q_UNUSED(cover_url);
+void StandardItemIconLoader::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult result) {
QStandardItem *item = pending_covers_.take(id);
if (!item) return;
- if (!image.isNull()) {
- item->setIcon(QIcon(QPixmap::fromImage(image)));
+ if (!result.image_scaled.isNull()) {
+ item->setIcon(QIcon(QPixmap::fromImage(result.image_scaled)));
}
}
diff --git a/src/core/standarditemiconloader.h b/src/core/standarditemiconloader.h
index 11569aaee..564d9fc7b 100644
--- a/src/core/standarditemiconloader.h
+++ b/src/core/standarditemiconloader.h
@@ -26,9 +26,11 @@
#include
#include
#include
+#include
#include
#include "covermanager/albumcoverloaderoptions.h"
+#include "covermanager/albumcoverloaderresult.h"
class QAbstractItemModel;
class QStandardItem;
@@ -52,12 +54,12 @@ class StandardItemIconLoader : public QObject {
void LoadIcon(const QUrl &art_automatic, const QUrl &art_manual, QStandardItem *for_item);
void LoadIcon(const Song &song, QStandardItem *for_item);
-private slots:
- void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
+ private slots:
+ void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult result);
void RowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end);
void ModelReset();
-private:
+ private:
AlbumCoverLoader *cover_loader_;
AlbumCoverLoaderOptions cover_options_;
diff --git a/src/covermanager/albumcoverchoicecontroller.cpp b/src/covermanager/albumcoverchoicecontroller.cpp
index 89252fa54..4d0f54d25 100644
--- a/src/covermanager/albumcoverchoicecontroller.cpp
+++ b/src/covermanager/albumcoverchoicecontroller.cpp
@@ -312,12 +312,14 @@ void AlbumCoverChoiceController::ShowCover(const Song &song, const QPixmap &pixm
}
-void AlbumCoverChoiceController::SearchCoverAutomatically(const Song &song) {
+qint64 AlbumCoverChoiceController::SearchCoverAutomatically(const Song &song) {
- qint64 id = cover_fetcher_->FetchAlbumCover(song.effective_albumartist(), song.effective_album(), true);
+ qint64 id = cover_fetcher_->FetchAlbumCover(song.effective_albumartist(), song.album(), song.title(), true);
cover_fetching_tasks_[id] = song;
+ return id;
+
}
void AlbumCoverChoiceController::AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics) {
@@ -371,7 +373,7 @@ void AlbumCoverChoiceController::SaveCoverToSong(Song *song, const QUrl &cover_u
}
- if (song->url() == app_->current_albumcover_loader()->last_song().url()) {
+ if (*song == app_->current_albumcover_loader()->last_song()) {
app_->current_albumcover_loader()->LoadAlbumCover(*song);
}
diff --git a/src/covermanager/albumcoverchoicecontroller.h b/src/covermanager/albumcoverchoicecontroller.h
index 47f30731e..8900e59db 100644
--- a/src/covermanager/albumcoverchoicecontroller.h
+++ b/src/covermanager/albumcoverchoicecontroller.h
@@ -110,7 +110,7 @@ class AlbumCoverChoiceController : public QWidget {
void ShowCover(const Song &song, const QPixmap &pixmap);
// Search for covers automatically
- void SearchCoverAutomatically(const Song &song);
+ qint64 SearchCoverAutomatically(const Song &song);
// Saves the chosen cover as manual cover path of this song in collection.
void SaveCoverToSong(Song *song, const QUrl &cover_url);
@@ -124,7 +124,7 @@ class AlbumCoverChoiceController : public QWidget {
static bool CanAcceptDrag(const QDragEnterEvent *e);
-signals:
+ signals:
void AutomaticCoverSearchDone();
private slots:
diff --git a/src/covermanager/albumcoverfetcher.cpp b/src/covermanager/albumcoverfetcher.cpp
index edf523cc1..29731f83a 100644
--- a/src/covermanager/albumcoverfetcher.cpp
+++ b/src/covermanager/albumcoverfetcher.cpp
@@ -44,14 +44,15 @@ AlbumCoverFetcher::AlbumCoverFetcher(CoverProviders *cover_providers, QObject *p
connect(request_starter_, SIGNAL(timeout()), SLOT(StartRequests()));
}
-quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString &album, bool fetchall) {
+quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString &album, const QString &title, bool fetchall) {
CoverSearchRequest request;
request.id = next_id_++;
request.artist = artist;
request.album = album;
- request.album.remove(Song::kAlbumRemoveDisc);
- request.album.remove(Song::kAlbumRemoveMisc);
+ request.album = request.album.remove(Song::kAlbumRemoveDisc);
+ request.album = request.album.remove(Song::kAlbumRemoveMisc);
+ request.title = title;
request.search = false;
request.fetchall = fetchall;
@@ -60,14 +61,15 @@ quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString
}
-quint64 AlbumCoverFetcher::SearchForCovers(const QString &artist, const QString &album) {
+quint64 AlbumCoverFetcher::SearchForCovers(const QString &artist, const QString &album, const QString &title) {
CoverSearchRequest request;
request.id = next_id_++;
request.artist = artist;
request.album = album;
- request.album.remove(Song::kAlbumRemoveDisc);
- request.album.remove(Song::kAlbumRemoveMisc);
+ request.album = request.album.remove(Song::kAlbumRemoveDisc);
+ request.album = request.album.remove(Song::kAlbumRemoveMisc);
+ request.title = title;
request.search = true;
request.fetchall = false;
diff --git a/src/covermanager/albumcoverfetcher.h b/src/covermanager/albumcoverfetcher.h
index d25b58477..12509218b 100644
--- a/src/covermanager/albumcoverfetcher.h
+++ b/src/covermanager/albumcoverfetcher.h
@@ -50,6 +50,7 @@ struct CoverSearchRequest {
// A search query
QString artist;
QString album;
+ QString title;
// Is this only a search request or should we also fetch the first cover that's found?
bool search;
@@ -92,12 +93,12 @@ class AlbumCoverFetcher : public QObject {
static const int kMaxConcurrentRequests;
- quint64 SearchForCovers(const QString &artist, const QString &album);
- quint64 FetchAlbumCover(const QString &artist, const QString &album, const bool fetchall);
+ quint64 SearchForCovers(const QString &artist, const QString &album, const QString &title = QString());
+ quint64 FetchAlbumCover(const QString &artist, const QString &album, const QString &title, const bool fetchall);
void Clear();
-signals:
+ signals:
void AlbumCoverFetched(const quint64 request_id, const QUrl &cover_url, const QImage &cover, const CoverSearchStatistics &statistics);
void SearchFinished(const quint64 request_id, const CoverSearchResults &results, const CoverSearchStatistics &statistics);
diff --git a/src/covermanager/albumcoverfetchersearch.cpp b/src/covermanager/albumcoverfetchersearch.cpp
index ae3c206e4..a1fca01cf 100644
--- a/src/covermanager/albumcoverfetchersearch.cpp
+++ b/src/covermanager/albumcoverfetchersearch.cpp
@@ -42,11 +42,6 @@
#include "coverprovider.h"
#include "coverproviders.h"
-using std::min;
-using std::max;
-using std::stable_sort;
-using std::sqrt;
-
const int AlbumCoverFetcherSearch::kSearchTimeoutMs = 25000;
const int AlbumCoverFetcherSearch::kImageLoadTimeoutMs = 3000;
const int AlbumCoverFetcherSearch::kTargetSize = 500;
@@ -81,13 +76,16 @@ void AlbumCoverFetcherSearch::Start(CoverProviders *cover_providers) {
// Skip provider if it does not have fetchall set, and we are doing fetchall - "Fetch Missing Covers".
if (!provider->fetchall() && request_.fetchall) {
- //qLog(Debug) << "Skipping provider" << provider->name();
+ continue;
+ }
+ // If album is missing, check if we can still use this provider by searching using artist + title.
+ if (!provider->allow_missing_album() && request_.album.isEmpty()) {
continue;
}
connect(provider, SIGNAL(SearchFinished(int, CoverSearchResults)), SLOT(ProviderSearchFinished(int, CoverSearchResults)));
const int id = cover_providers->NextId();
- const bool success = provider->StartSearch(request_.artist, request_.album, id);
+ const bool success = provider->StartSearch(request_.artist, request_.album, request_.title, id);
if (success) {
pending_requests_[id] = provider;
@@ -112,7 +110,7 @@ void AlbumCoverFetcherSearch::ProviderSearchFinished(const int id, const CoverSe
CoverProvider *provider = pending_requests_.take(id);
CoverSearchResults results_copy(results);
- for (int i = 0; i < results_copy.count(); ++i) {
+ for (int i = 0 ; i < results_copy.count() ; ++i) {
results_copy[i].provider = provider->name();
results_copy[i].score = provider->quality();
if (results_copy[i].artist.toLower() == request_.artist.toLower()) {
@@ -170,7 +168,7 @@ void AlbumCoverFetcherSearch::FetchMoreImages() {
// Try the first one in each category.
QString last_provider;
- for (int i = 0; i < results_.count(); ++i) {
+ for (int i = 0 ; i < results_.count() ; ++i) {
if (results_[i].provider == last_provider) {
continue;
}
diff --git a/src/covermanager/albumcoverloader.cpp b/src/covermanager/albumcoverloader.cpp
index 36a4ea7bc..c5c2ff667 100644
--- a/src/covermanager/albumcoverloader.cpp
+++ b/src/covermanager/albumcoverloader.cpp
@@ -50,6 +50,7 @@
#include "organise/organiseformat.h"
#include "albumcoverloader.h"
#include "albumcoverloaderoptions.h"
+#include "albumcoverloaderresult.h"
AlbumCoverLoader::AlbumCoverLoader(QObject *parent)
: QObject(parent),
@@ -98,89 +99,7 @@ void AlbumCoverLoader::ReloadSettings() {
}
-QString AlbumCoverLoader::ImageCacheDir(const Song::Source source) {
-
- switch (source) {
- case Song::Source_LocalFile:
- case Song::Source_Collection:
- case Song::Source_CDDA:
- case Song::Source_Device:
- case Song::Source_Stream:
- case Song::Source_Unknown:
- return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/albumcovers";
- case Song::Source_Tidal:
- return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/tidalalbumcovers";
- case Song::Source_Qobuz:
- return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/qobuzalbumcovers";
- case Song::Source_Subsonic:
- return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/subsonicalbumcovers";
- }
-
- return QString();
-
-}
-
-QString AlbumCoverLoader::CoverFilePath(const Song::Source source, const QString &artist, QString album, const QString &album_id, const QString &album_dir, const QUrl &cover_url) {
-
- album.remove(Song::kAlbumRemoveDisc);
-
- QString path;
- if (source == Song::Source_Collection && cover_album_dir_ && !album_dir.isEmpty()) {
- path = album_dir;
- }
- else {
- path = AlbumCoverLoader::ImageCacheDir(source);
- }
-
- if (path.right(1) == QDir::separator()) {
- path.chop(1);
- }
-
- QDir dir;
- if (!dir.mkpath(path)) {
- qLog(Error) << "Unable to create directory" << path;
- return QString();
- }
-
- QString filename;
- if (source == Song::Source_Collection && cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern && !cover_pattern_.isEmpty()) {
- filename = CreateCoverFilename(artist, album) + ".jpg";
- filename.remove(OrganiseFormat::kInvalidFatCharacters);
- if (cover_lowercase_) filename = filename.toLower();
- if (cover_replace_spaces_) filename.replace(QRegExp("\\s"), "-");
- }
- else {
- switch (source) {
- case Song::Source_Tidal:
- filename = album_id + "-" + cover_url.fileName();
- break;
- case Song::Source_Subsonic:
- case Song::Source_Qobuz:
- filename = AlbumCoverFileName(artist, album);
- if (filename.length() > 8 && (filename.length() - 5) >= (artist.length() + album.length() - 2)) {
- break;
- }
- // fallthrough
- case Song::Source_Collection:
- case Song::Source_LocalFile:
- case Song::Source_CDDA:
- case Song::Source_Device:
- case Song::Source_Stream:
- case Song::Source_Unknown:
- filename = Utilities::Sha1CoverHash(artist, album).toHex() + ".jpg";
- break;
- }
- }
-
- if (filename.isEmpty()) return QString();
-
- QString filepath(path + "/" + filename);
-
- return filepath;
-
-}
-
-QString AlbumCoverLoader::AlbumCoverFileName(QString artist, QString album) {
+QString AlbumCoverLoader::AlbumCoverFilename(QString artist, QString album) {
artist.remove('/');
album.remove('/');
@@ -196,7 +115,79 @@ QString AlbumCoverLoader::AlbumCoverFileName(QString artist, QString album) {
}
-QString AlbumCoverLoader::CreateCoverFilename(const QString &artist, const QString &album) {
+QString AlbumCoverLoader::CoverFilePath(const Song &song, const QString &album_dir, const QUrl &cover_url) {
+ return CoverFilePath(song.source(), song.effective_albumartist(), song.album(), song.album_id(), album_dir, cover_url);
+}
+
+QString AlbumCoverLoader::CoverFilePath(const Song::Source source, const QString &artist, QString album, const QString &album_id, const QString &album_dir, const QUrl &cover_url) {
+
+ album.remove(Song::kAlbumRemoveDisc);
+
+ QString path;
+ if (source == Song::Source_Collection && cover_album_dir_ && !album_dir.isEmpty()) {
+ path = album_dir;
+ }
+ else {
+ path = Song::ImageCacheDir(source);
+ }
+
+ if (path.right(1) == QDir::separator()) {
+ path.chop(1);
+ }
+
+ QDir dir;
+ if (!dir.mkpath(path)) {
+ qLog(Error) << "Unable to create directory" << path;
+ path = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
+ }
+
+ QString filename;
+ if (source == Song::Source_Collection && cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern && !cover_pattern_.isEmpty()) {
+ filename = CoverFilenameFromVariable(artist, album) + ".jpg";
+ filename.remove(OrganiseFormat::kInvalidFatCharacters);
+ if (cover_lowercase_) filename = filename.toLower();
+ if (cover_replace_spaces_) filename.replace(QRegExp("\\s"), "-");
+ }
+ else {
+ filename = CoverFilenameFromSource(source, cover_url, artist, album, album_id);
+ }
+
+ QString filepath(path + "/" + filename);
+
+ return filepath;
+
+}
+
+QString AlbumCoverLoader::CoverFilenameFromSource(const Song::Source source, const QUrl &cover_url, const QString &artist, const QString &album, const QString &album_id) {
+
+ QString filename;
+
+ switch (source) {
+ case Song::Source_Tidal:
+ filename = album_id + "-" + cover_url.fileName();
+ break;
+ case Song::Source_Subsonic:
+ case Song::Source_Qobuz:
+ filename = AlbumCoverFilename(artist, album);
+ if (filename.length() > 8 && (filename.length() - 5) >= (artist.length() + album.length() - 2)) {
+ break;
+ }
+ // fallthrough
+ case Song::Source_Collection:
+ case Song::Source_LocalFile:
+ case Song::Source_CDDA:
+ case Song::Source_Device:
+ case Song::Source_Stream:
+ case Song::Source_Unknown:
+ filename = Utilities::Sha1CoverHash(artist, album).toHex() + ".jpg";
+ break;
+ }
+
+ return filename;
+
+}
+
+QString AlbumCoverLoader::CoverFilenameFromVariable(const QString &artist, const QString &album) {
QString filename(cover_pattern_);
filename.replace("%albumartist", artist);
@@ -215,6 +206,7 @@ void AlbumCoverLoader::CancelTask(const quint64 id) {
break;
}
}
+
}
void AlbumCoverLoader::CancelTasks(const QSet &ids) {
@@ -228,21 +220,25 @@ void AlbumCoverLoader::CancelTasks(const QSet &ids) {
++it;
}
}
+
}
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions& options, const Song &song) {
- return LoadImageAsync(options, song.art_automatic(), song.art_manual(), song.url().toLocalFile(), song.image());
+ return LoadImageAsync(options, song.art_automatic(), song.art_manual(), song.url(), song, song.image());
}
-quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QString &song_filename, const QImage &embedded_image) {
+quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QUrl &song_url, const Song song, const QImage &embedded_image) {
Task task;
task.options = options;
- task.art_automatic = art_automatic;
+ task.song = song;
+ task.song_url = song_url;
task.art_manual = art_manual;
- task.song_filename = song_filename;
+ task.art_automatic = art_automatic;
+ task.art_updated = false;
task.embedded_image = embedded_image;
- task.state = State_TryingManual;
+ task.type = AlbumCoverLoaderResult::Type_None;
+ task.state = State_Manual;
{
QMutexLocker l(&mutex_);
@@ -269,20 +265,20 @@ void AlbumCoverLoader::ProcessTasks() {
ProcessTask(&task);
}
+
}
void AlbumCoverLoader::ProcessTask(Task *task) {
- TryLoadResult result = TryLoadImage(*task);
+ TryLoadResult result = TryLoadImage(task);
if (result.started_async) {
// The image is being loaded from a remote URL, we'll carry on later when it's done
return;
}
if (result.loaded_success) {
- QImage scaled = ScaleAndPad(task->options, result.image);
- emit ImageLoaded(task->id, result.cover_url, scaled);
- emit ImageLoaded(task->id, result.cover_url, scaled, result.image);
+ QPair images = ScaleAndPad(task->options, result.image);
+ emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(result.type, result.cover_url, result.image, images.first, images.second, task->art_updated));
return;
}
@@ -292,64 +288,85 @@ void AlbumCoverLoader::ProcessTask(Task *task) {
void AlbumCoverLoader::NextState(Task *task) {
- if (task->state == State_TryingManual) {
+ if (task->state == State_Manual) {
// Try the automatic one next
- task->state = State_TryingAuto;
+ task->state = State_Automatic;
ProcessTask(task);
}
else {
// Give up
- emit ImageLoaded(task->id, QUrl(), task->options.default_output_image_);
- emit ImageLoaded(task->id, QUrl(), task->options.default_output_image_, task->options.default_output_image_);
+ emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(AlbumCoverLoaderResult::Type_None, QUrl(), task->options.default_output_image_, task->options.default_output_image_, task->options.default_thumbnail_image_, task->art_updated));
}
}
-AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(const Task &task) {
+AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(Task *task) {
// An image embedded in the song itself takes priority
- if (!task.embedded_image.isNull())
- return TryLoadResult(false, true, QUrl(), ScaleAndPad(task.options, task.embedded_image));
+ if (!task->embedded_image.isNull()) {
+ QPair images = ScaleAndPad(task->options, task->embedded_image);
+ return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, QUrl(), images.first);
+ }
+ // Use cached album cover if possible.
+ if (task->state == State_Manual &&
+ !task->song.art_manual_is_valid() &&
+ task->art_manual.isEmpty() &&
+ task->song.source() != Song::Source_Collection &&
+ !task->options.scale_output_image_ &&
+ !task->options.pad_output_image_) {
+ task->song.InitArtManual();
+ if (task->art_manual != task->song.art_manual()) {
+ task->art_manual = task->song.art_manual();
+ task->art_updated = true;
+ }
+ }
+
+ AlbumCoverLoaderResult::Type type(AlbumCoverLoaderResult::Type_None);
QUrl cover_url;
-
- switch (task.state) {
- case State_TryingAuto: cover_url = task.art_automatic; break;
- case State_TryingManual: cover_url = task.art_manual; break;
+ switch (task->state) {
+ case State_None:
+ case State_Automatic:
+ type = AlbumCoverLoaderResult::Type_Automatic;
+ cover_url = task->art_automatic;
+ break;
+ case State_Manual:
+ type = AlbumCoverLoaderResult::Type_Manual;
+ cover_url = task->art_manual;
+ break;
}
+ task->type = type;
- if (cover_url.path() == Song::kManuallyUnsetCover)
- return TryLoadResult(false, true, QUrl(), task.options.default_output_image_);
-
- else if (cover_url.path() == Song::kEmbeddedCover && !task.song_filename.isEmpty()) {
- const QImage taglib_image = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task.song_filename);
-
- if (!taglib_image.isNull())
- return TryLoadResult(false, true, QUrl(), ScaleAndPad(task.options, taglib_image));
- }
-
- if (cover_url.path().isEmpty()) {
- return TryLoadResult(false, false, cover_url, task.options.default_output_image_);
- }
- else {
- if (cover_url.isLocalFile()) {
+ if (!cover_url.isEmpty() && !cover_url.path().isEmpty()) {
+ if (cover_url.path() == Song::kManuallyUnsetCover) {
+ return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_ManuallyUnset, QUrl(), task->options.default_output_image_);
+ }
+ else if (cover_url.path() == Song::kEmbeddedCover && task->song_url.isLocalFile()) {
+ const QImage taglib_image = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song_url.toLocalFile());
+ if (!taglib_image.isNull()) {
+ return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, QUrl(), ScaleAndPad(task->options, taglib_image).first);
+ }
+ }
+ else if (cover_url.isLocalFile()) {
QImage image(cover_url.toLocalFile());
- return TryLoadResult(false, !image.isNull(), cover_url, image.isNull() ? task.options.default_output_image_ : image);
+ return TryLoadResult(false, !image.isNull(), type, cover_url, image.isNull() ? task->options.default_output_image_ : image);
}
else if (cover_url.scheme().isEmpty()) { // Assume a local file with no scheme.
QImage image(cover_url.path());
- return TryLoadResult(false, !image.isNull(), cover_url, image.isNull() ? task.options.default_output_image_ : image);
+ return TryLoadResult(false, !image.isNull(), type, cover_url, image.isNull() ? task->options.default_output_image_ : image);
}
else if (network_->supportedSchemes().contains(cover_url.scheme())) { // Remote URL
- QNetworkReply *reply = network_->get(QNetworkRequest(cover_url));
+ QNetworkRequest request(cover_url);
+ request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
+ QNetworkReply *reply = network_->get(request);
NewClosure(reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*, QUrl)), reply, cover_url);
- remote_tasks_.insert(reply, task);
- return TryLoadResult(true, false, cover_url, QImage());
+ remote_tasks_.insert(reply, *task);
+ return TryLoadResult(true, false, type, cover_url, QImage());
}
}
- return TryLoadResult(false, false, cover_url, task.options.default_output_image_);
+ return TryLoadResult(false, false, AlbumCoverLoaderResult::Type_None, cover_url, task->options.default_output_image_);
}
@@ -367,6 +384,7 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply, const QUrl &cov
return; // Give up.
}
QNetworkRequest request = reply->request();
+ request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
request.setUrl(redirect.toUrl());
QNetworkReply *redirected_reply = network_->get(request);
NewClosure(redirected_reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*, QUrl)), redirected_reply, redirect.toUrl());
@@ -379,41 +397,67 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply, const QUrl &cov
// Try to load the image
QImage image;
if (image.load(reply, 0)) {
- QImage scaled = ScaleAndPad(task.options, image);
- emit ImageLoaded(task.id, cover_url, scaled);
- emit ImageLoaded(task.id, cover_url, scaled, image);
+ QPair images = ScaleAndPad(task.options, image);
+ emit AlbumCoverLoaded(task.id, AlbumCoverLoaderResult(task.type, cover_url, image, images.first, images.second, task.art_updated));
return;
}
+ else {
+ qLog(Error) << "Unable to load album cover image" << cover_url;
+ }
+ }
+ else {
+ qLog(Error) << "Unable to get album cover" << cover_url << reply->error() << reply->errorString();
}
NextState(&task);
}
-QImage AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image) {
+QPair AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image) {
- if (image.isNull()) return image;
+ if (image.isNull()) return qMakePair(image, image);
// Scale the image down
- QImage copy;
+ QImage image_scaled;
if (options.scale_output_image_) {
- copy = image.scaled(QSize(options.desired_height_, options.desired_height_), Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ image_scaled = image.scaled(QSize(options.desired_height_, options.desired_height_), Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
else {
- copy = image;
+ image_scaled = image;
}
- if (!options.pad_output_image_) return copy;
+ // Pad the image to height x height
+ if (options.pad_output_image_) {
+ QImage image_padded(options.desired_height_, options.desired_height_, QImage::Format_ARGB32);
+ image_padded.fill(0);
- // Pad the image to height_ x height_
- QImage padded_image(options.desired_height_, options.desired_height_, QImage::Format_ARGB32);
- padded_image.fill(0);
+ QPainter p(&image_padded);
+ p.drawImage((options.desired_height_ - image_scaled.width()) / 2, (options.desired_height_ - image_scaled.height()) / 2, image_scaled);
+ p.end();
- QPainter p(&padded_image);
- p.drawImage((options.desired_height_ - copy.width()) / 2, (options.desired_height_ - copy.height()) / 2, copy);
- p.end();
+ image_scaled = image_padded;
+ }
- return padded_image;
+ // Create thumbnail
+ QImage image_thumbnail;
+ if (options.create_thumbnail_) {
+ if (options.pad_thumbnail_image_) {
+ image_thumbnail = image.scaled(options.thumbnail_size_, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ QImage image_padded(options.thumbnail_size_, QImage::Format_ARGB32_Premultiplied);
+ image_padded.fill(0);
+
+ QPainter p(&image_padded);
+ p.drawImage((image_padded.width() - image_thumbnail.width()) / 2, (image_padded.height() - image_thumbnail.height()) / 2, image_thumbnail);
+ p.end();
+
+ image_thumbnail = image_padded;
+ }
+ else {
+ image_thumbnail = image.scaledToHeight(options.thumbnail_size_.height(), Qt::SmoothTransformation);
+ }
+ }
+
+ return qMakePair(image_scaled, image_thumbnail);
}
diff --git a/src/covermanager/albumcoverloader.h b/src/covermanager/albumcoverloader.h
index e01b7d0f4..f5d20c4ab 100644
--- a/src/covermanager/albumcoverloader.h
+++ b/src/covermanager/albumcoverloader.h
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -37,6 +38,7 @@
#include "core/song.h"
#include "settings/collectionsettingspage.h"
#include "albumcoverloaderoptions.h"
+#include "albumcoverloaderresult.h"
class QThread;
class QNetworkReply;
@@ -48,29 +50,36 @@ class AlbumCoverLoader : public QObject {
public:
explicit AlbumCoverLoader(QObject *parent = nullptr);
+ enum State {
+ State_None,
+ State_Manual,
+ State_Automatic,
+ };
+
void ReloadSettings();
void ExitAsync();
void Stop() { stop_requested_ = true; }
- static QString ImageCacheDir(const Song::Source source);
- QString CreateCoverFilename(const QString &artist, const QString &album);
+ static QString AlbumCoverFilename(QString artist, QString album);
+
+ QString CoverFilenameFromSource(const Song::Source source, const QUrl &cover_url, const QString &artist, const QString &album, const QString &album_id);
+ QString CoverFilenameFromVariable(const QString &artist, const QString &album);
+ QString CoverFilePath(const Song &song, const QString &album_dir, const QUrl &cover_url);
QString CoverFilePath(const Song::Source source, const QString &artist, QString album, const QString &album_id, const QString &album_dir, const QUrl &cover_url);
- QString AlbumCoverFileName(QString artist, QString album);
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const Song &song);
- virtual quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QString &song_filename = QString(), const QImage &embedded_image = QImage());
+ virtual quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QUrl &song_url = QUrl(), const Song song = Song(), const QImage &embedded_image = QImage());
void CancelTask(const quint64 id);
void CancelTasks(const QSet &ids);
static QPixmap TryLoadPixmap(const QUrl &automatic, const QUrl &manual, const QUrl &url = QUrl());
- static QImage ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image);
+ static QPair ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image);
signals:
void ExitFinished();
- void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
- void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &scaled, const QImage &original);
+ void AlbumCoverLoaded(quint64 id, AlbumCoverLoaderResult result);
protected slots:
void Exit();
@@ -78,38 +87,38 @@ class AlbumCoverLoader : public QObject {
void RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url);
protected:
- enum State {
- State_TryingManual,
- State_TryingAuto,
- };
struct Task {
- explicit Task() : redirects(0) {}
+ explicit Task() : id(0), state(State_None), type(AlbumCoverLoaderResult::Type_None), art_updated(false), redirects(0) {}
AlbumCoverLoaderOptions options;
quint64 id;
- QUrl art_automatic;
QUrl art_manual;
- QString song_filename;
+ QUrl art_automatic;
+ QUrl song_url;
+ Song song;
QImage embedded_image;
State state;
+ AlbumCoverLoaderResult::Type type;
+ bool art_updated;
int redirects;
};
struct TryLoadResult {
- explicit TryLoadResult(bool async, bool success, const QUrl &_cover_url, const QImage &_image) : started_async(async), loaded_success(success), cover_url(_cover_url), image(_image) {}
+ explicit TryLoadResult(const bool _started_async = false, const bool _loaded_success = false, const AlbumCoverLoaderResult::Type _type = AlbumCoverLoaderResult::Type_None, const QUrl &_cover_url = QUrl(), const QImage &_image = QImage()) : started_async(_started_async), loaded_success(_loaded_success), type(_type), cover_url(_cover_url), image(_image) {}
bool started_async;
bool loaded_success;
+ AlbumCoverLoaderResult::Type type;
QUrl cover_url;
QImage image;
};
void ProcessTask(Task *task);
void NextState(Task *task);
- TryLoadResult TryLoadImage(const Task &task);
+ TryLoadResult TryLoadImage(Task *task);
bool stop_requested_;
diff --git a/src/covermanager/albumcoverloaderoptions.h b/src/covermanager/albumcoverloaderoptions.h
index 41cdef7b7..4c714a206 100644
--- a/src/covermanager/albumcoverloaderoptions.h
+++ b/src/covermanager/albumcoverloaderoptions.h
@@ -24,17 +24,24 @@
#include "config.h"
#include
+#include
struct AlbumCoverLoaderOptions {
explicit AlbumCoverLoaderOptions()
: desired_height_(120),
scale_output_image_(true),
- pad_output_image_(true) {}
+ pad_output_image_(true),
+ create_thumbnail_(false),
+ pad_thumbnail_image_(false) {}
int desired_height_;
+ QSize thumbnail_size_;
bool scale_output_image_;
bool pad_output_image_;
+ bool create_thumbnail_;
+ bool pad_thumbnail_image_;
QImage default_output_image_;
+ QImage default_thumbnail_image_;
};
#endif // ALBUMCOVERLOADEROPTIONS_H
diff --git a/src/covermanager/albumcoverloaderresult.h b/src/covermanager/albumcoverloaderresult.h
new file mode 100644
index 000000000..a11eea2a8
--- /dev/null
+++ b/src/covermanager/albumcoverloaderresult.h
@@ -0,0 +1,52 @@
+/*
+ * Strawberry Music Player
+ * Copyright 2020, 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 ALBUMCOVERLOADERRESULT_H
+#define ALBUMCOVERLOADERRESULT_H
+
+#include "config.h"
+
+#include
+#include
+
+struct AlbumCoverLoaderResult {
+
+ enum Type {
+ Type_None,
+ Type_ManuallyUnset,
+ Type_Embedded,
+ Type_Automatic,
+ Type_Manual,
+ Type_Remote,
+ };
+
+ explicit AlbumCoverLoaderResult(const Type _type = Type_None, const QUrl &_cover_url = QUrl(), const QImage &_image_original = QImage(), const QImage &_image_scaled = QImage(), const QImage &_image_thumbnail = QImage(), const bool _updated = false) : type(_type), cover_url(_cover_url), image_original(_image_original), image_scaled(_image_scaled), image_thumbnail(_image_thumbnail), updated(_updated) {}
+
+ Type type;
+ QUrl cover_url;
+ QImage image_original;
+ QImage image_scaled;
+ QImage image_thumbnail;
+ bool updated;
+
+ QUrl temp_cover_url;
+
+};
+
+#endif // ALBUMCOVERLOADERRESULT_H
diff --git a/src/covermanager/albumcovermanager.cpp b/src/covermanager/albumcovermanager.cpp
index 7305df40c..990cdc48b 100644
--- a/src/covermanager/albumcovermanager.cpp
+++ b/src/covermanager/albumcovermanager.cpp
@@ -77,6 +77,8 @@
#include "albumcoverexporter.h"
#include "albumcoverfetcher.h"
#include "albumcoverloader.h"
+#include "albumcoverloaderoptions.h"
+#include "albumcoverloaderresult.h"
#include "albumcovermanagerlist.h"
#include "coversearchstatistics.h"
#include "coversearchstatisticsdialog.h"
@@ -216,7 +218,7 @@ void AlbumCoverManager::Init() {
ui_->splitter->setSizes(QList() << 200 << width() - 200);
}
- connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(CoverImageLoaded(quint64, QUrl, QImage)));
+ connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)));
cover_searcher_->Init(cover_fetcher_);
@@ -392,7 +394,7 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
}
if (!info.art_automatic.isEmpty() || !info.art_manual.isEmpty()) {
- quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, info.art_automatic, info.art_manual, info.first_url.toLocalFile());
+ quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, info.art_automatic, info.art_manual, info.first_url);
item->setData(Role_PathAutomatic, info.art_automatic);
item->setData(Role_PathManual, info.art_manual);
cover_loading_tasks_[id] = item;
@@ -403,17 +405,15 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
}
-void AlbumCoverManager::CoverImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) {
-
- Q_UNUSED(cover_url);
+void AlbumCoverManager::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) {
if (!cover_loading_tasks_.contains(id)) return;
QListWidgetItem *item = cover_loading_tasks_.take(id);
- if (image.isNull()) return;
+ if (result.image_scaled.isNull()) return;
- item->setIcon(QPixmap::fromImage(image));
+ item->setIcon(QPixmap::fromImage(result.image_scaled));
UpdateFilter();
}
@@ -488,7 +488,7 @@ void AlbumCoverManager::FetchAlbumCovers() {
if (item->isHidden()) continue;
if (ItemHasCover(*item)) continue;
- quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), true);
+ quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), QString(), true);
cover_fetching_tasks_[id] = item;
jobs_++;
}
@@ -623,7 +623,7 @@ void AlbumCoverManager::ShowCover() {
void AlbumCoverManager::FetchSingleCover() {
for (QListWidgetItem *item : context_menu_items_) {
- quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), false);
+ quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), QString(), false);
cover_fetching_tasks_[id] = item;
jobs_++;
}
diff --git a/src/covermanager/albumcovermanager.h b/src/covermanager/albumcovermanager.h
index 90bbb0334..a100c893c 100644
--- a/src/covermanager/albumcovermanager.h
+++ b/src/covermanager/albumcovermanager.h
@@ -36,6 +36,7 @@
#include "core/song.h"
#include "albumcoverloaderoptions.h"
+#include "albumcoverloaderresult.h"
#include "coversearchstatistics.h"
class QWidget;
@@ -132,7 +133,7 @@ class AlbumCoverManager : public QMainWindow {
private slots:
void ArtistChanged(QListWidgetItem *current);
- void CoverImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
+ void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
void UpdateFilter();
void FetchAlbumCovers();
void ExportCovers();
diff --git a/src/covermanager/albumcoversearcher.cpp b/src/covermanager/albumcoversearcher.cpp
index 93fd9b407..521daaaaa 100644
--- a/src/covermanager/albumcoversearcher.cpp
+++ b/src/covermanager/albumcoversearcher.cpp
@@ -47,6 +47,7 @@
#include "core/application.h"
#include "core/utilities.h"
+#include "core/logging.h"
#include "widgets/busyindicator.h"
#include "widgets/forcescrollperpixel.h"
#include "widgets/groupediconview.h"
@@ -55,6 +56,7 @@
#include "albumcoverfetcher.h"
#include "albumcoverloader.h"
#include "albumcoverloaderoptions.h"
+#include "albumcoverloaderresult.h"
#include "ui_albumcoversearcher.h"
const int SizeOverlayDelegate::kMargin = 4;
@@ -129,8 +131,11 @@ AlbumCoverSearcher::AlbumCoverSearcher(const QIcon &no_cover_icon, Application *
options_.scale_output_image_ = false;
options_.pad_output_image_ = false;
+ options_.create_thumbnail_ = true;
+ options_.pad_thumbnail_image_ = true;
+ options_.thumbnail_size_ = ui_->covers->iconSize();
- connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(ImageLoaded(quint64, QUrl, QImage)));
+ connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)));
connect(ui_->search, SIGNAL(clicked()), SLOT(Search()));
connect(ui_->covers, SIGNAL(doubleClicked(QModelIndex)), SLOT(CoverDoubleClicked(QModelIndex)));
@@ -235,37 +240,25 @@ void AlbumCoverSearcher::SearchFinished(const quint64 id, const CoverSearchResul
}
-void AlbumCoverSearcher::ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) {
-
- Q_UNUSED(cover_url);
+void AlbumCoverSearcher::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) {
if (!cover_loading_tasks_.contains(id)) return;
QStandardItem *item = cover_loading_tasks_.take(id);
if (cover_loading_tasks_.isEmpty()) ui_->busy->hide();
- if (image.isNull()) {
+ if (result.image_original.isNull()) {
model_->removeRow(item->row());
return;
}
- QIcon icon(QPixmap::fromImage(image));
-
- // Create a pixmap that's padded and exactly the right size for the icon.
- QImage scaled_image(image.scaled(ui_->covers->iconSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
-
- QImage padded_image(ui_->covers->iconSize(), QImage::Format_ARGB32_Premultiplied);
- padded_image.fill(0);
-
- QPainter p(&padded_image);
- p.drawImage((padded_image.width() - scaled_image.width()) / 2, (padded_image.height() - scaled_image.height()) / 2, scaled_image);
- p.end();
-
- icon.addPixmap(QPixmap::fromImage(padded_image));
+ QIcon icon;
+ icon.addPixmap(QPixmap::fromImage(result.image_original));
+ icon.addPixmap(QPixmap::fromImage(result.image_thumbnail));
item->setData(true, Role_ImageFetchFinished);
- item->setData(image.width() * image.height(), Role_ImageDimensions);
- item->setData(image.size(), Role_ImageSize);
+ item->setData(result.image_original.width() * result.image_original.height(), Role_ImageDimensions);
+ item->setData(result.image_original.size(), Role_ImageSize);
item->setIcon(icon);
}
diff --git a/src/covermanager/albumcoversearcher.h b/src/covermanager/albumcoversearcher.h
index 87432cd0b..a959c649d 100644
--- a/src/covermanager/albumcoversearcher.h
+++ b/src/covermanager/albumcoversearcher.h
@@ -36,6 +36,7 @@
#include "albumcoverfetcher.h"
#include "albumcoverloaderoptions.h"
+#include "albumcoverloaderresult.h"
class QWidget;
class QStandardItem;
@@ -88,7 +89,7 @@ class AlbumCoverSearcher : public QDialog {
private slots:
void Search();
void SearchFinished(const quint64 id, const CoverSearchResults &results);
- void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
+ void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
void CoverDoubleClicked(const QModelIndex &index);
diff --git a/src/covermanager/coverprovider.cpp b/src/covermanager/coverprovider.cpp
index 232e007e3..debe03c6e 100644
--- a/src/covermanager/coverprovider.cpp
+++ b/src/covermanager/coverprovider.cpp
@@ -26,5 +26,5 @@
#include "core/application.h"
#include "coverprovider.h"
-CoverProvider::CoverProvider(const QString &name, const float &quality, const bool &fetchall, Application *app, QObject *parent)
- : QObject(parent), app_(app), name_(name), quality_(quality), fetchall_(fetchall) {}
+CoverProvider::CoverProvider(const QString &name, const float quality, const bool fetchall, const bool allow_missing_album, Application *app, QObject *parent)
+ : QObject(parent), app_(app), name_(name), quality_(quality), fetchall_(fetchall), allow_missing_album_(allow_missing_album) {}
diff --git a/src/covermanager/coverprovider.h b/src/covermanager/coverprovider.h
index 5ffc6f8bf..7426d96e0 100644
--- a/src/covermanager/coverprovider.h
+++ b/src/covermanager/coverprovider.h
@@ -37,17 +37,18 @@ class CoverProvider : public QObject {
Q_OBJECT
public:
- explicit CoverProvider(const QString &name, const float &quality, const bool &fetchall, Application *app, QObject *parent);
+ explicit CoverProvider(const QString &name, const float quality, const bool fetchall, const bool allow_missing_album, Application *app, QObject *parent);
// A name (very short description) of this provider, like "last.fm".
QString name() const { return name_; }
bool quality() const { return quality_; }
bool fetchall() const { return fetchall_; }
+ bool allow_missing_album() const { return allow_missing_album_; }
// Starts searching for covers matching the given query text.
// Returns true if the query has been started, or false if an error occurred.
// The provider should remember the ID and emit it along with the result when it finishes.
- virtual bool StartSearch(const QString &artist, const QString &album, int id) = 0;
+ virtual bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id) = 0;
virtual void CancelSearch(int id) { Q_UNUSED(id); }
@@ -59,6 +60,7 @@ class CoverProvider : public QObject {
QString name_;
float quality_;
bool fetchall_;
+ bool allow_missing_album_;
};
diff --git a/src/covermanager/currentalbumcoverloader.cpp b/src/covermanager/currentalbumcoverloader.cpp
index 8d90276e3..e45909056 100644
--- a/src/covermanager/currentalbumcoverloader.cpp
+++ b/src/covermanager/currentalbumcoverloader.cpp
@@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
+ * Copyright 2019-2020, 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
@@ -32,6 +33,7 @@
#include "playlist/playlistmanager.h"
#include "albumcoverloader.h"
#include "albumcoverloaderoptions.h"
+#include "albumcoverloaderresult.h"
#include "currentalbumcoverloader.h"
CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *parent)
@@ -43,56 +45,73 @@ CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *pare
options_.scale_output_image_ = false;
options_.pad_output_image_ = false;
+ options_.create_thumbnail_ = true;
+ options_.thumbnail_size_ = QSize(120, 120);
options_.default_output_image_ = QImage(":/pictures/cdcase.png");
+ options_.default_thumbnail_image_ = options_.default_output_image_.scaledToHeight(120, Qt::SmoothTransformation);
- connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(TempAlbumCoverLoaded(quint64, QUrl, QImage)));
+ connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(TempAlbumCoverLoaded(quint64, AlbumCoverLoaderResult)));
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(LoadAlbumCover(Song)));
}
CurrentAlbumCoverLoader::~CurrentAlbumCoverLoader() {
+
if (temp_cover_) temp_cover_->remove();
if (temp_cover_thumbnail_) temp_cover_thumbnail_->remove();
+
}
void CurrentAlbumCoverLoader::LoadAlbumCover(const Song &song) {
+
last_song_ = song;
id_ = app_->album_cover_loader()->LoadImageAsync(options_, last_song_);
+
}
-void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, const QUrl &remote_url, const QImage &image) {
-
- Q_UNUSED(remote_url);
+void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, AlbumCoverLoaderResult result) {
if (id != id_) return;
id_ = 0;
- QUrl cover_url;
- QUrl thumbnail_url;
- QImage thumbnail;
-
- if (!image.isNull()) {
-
- QString filename;
-
+ if (!result.image_scaled.isNull()) {
temp_cover_.reset(new QTemporaryFile(temp_file_pattern_));
temp_cover_->setAutoRemove(true);
- temp_cover_->open();
-
- image.save(temp_cover_->fileName(), "JPEG");
-
- // Scale the image down to make a thumbnail. It's a bit crap doing it here since it's the GUI thread, but the alternative is hard.
- temp_cover_thumbnail_.reset(new QTemporaryFile(temp_file_pattern_));
- temp_cover_thumbnail_->open();
- temp_cover_thumbnail_->setAutoRemove(true);
- thumbnail = image.scaledToHeight(120, Qt::SmoothTransformation);
- thumbnail.save(temp_cover_thumbnail_->fileName(), "JPEG");
-
- cover_url = QUrl::fromLocalFile(temp_cover_->fileName());
- thumbnail_url = QUrl::fromLocalFile(temp_cover_thumbnail_->fileName());
+ if (temp_cover_->open()) {
+ if (result.image_scaled.save(temp_cover_->fileName(), "JPEG")) {
+ result.temp_cover_url = QUrl::fromLocalFile(temp_cover_->fileName());
+ }
+ else {
+ qLog(Error) << "Unable to save cover image to" << temp_cover_->fileName();
+ }
+ }
+ else {
+ qLog(Error) << "Unable to open" << temp_cover_->fileName();
+ }
}
- emit AlbumCoverLoaded(last_song_, cover_url, image);
- emit ThumbnailLoaded(last_song_, thumbnail_url, thumbnail);
+ QUrl thumbnail_url;
+ if (!result.image_thumbnail.isNull()) {
+ temp_cover_thumbnail_.reset(new QTemporaryFile(temp_file_pattern_));
+ temp_cover_thumbnail_->setAutoRemove(true);
+ if (temp_cover_thumbnail_->open()) {
+ if (result.image_thumbnail.save(temp_cover_thumbnail_->fileName(), "JPEG")) {
+ thumbnail_url = QUrl::fromLocalFile(temp_cover_thumbnail_->fileName());
+ }
+ else {
+ qLog(Error) << "Unable to save cover thumbnail image to" << temp_cover_thumbnail_->fileName();
+ }
+ }
+ else {
+ qLog(Error) << "Unable to open" << temp_cover_thumbnail_->fileName();
+ }
+ }
+
+ if (result.updated) {
+ last_song_.set_art_manual(result.cover_url);
+ }
+
+ emit AlbumCoverLoaded(last_song_, result);
+ emit ThumbnailLoaded(last_song_, thumbnail_url, result.image_thumbnail);
}
diff --git a/src/covermanager/currentalbumcoverloader.h b/src/covermanager/currentalbumcoverloader.h
index 69156eb8a..eebd6507f 100644
--- a/src/covermanager/currentalbumcoverloader.h
+++ b/src/covermanager/currentalbumcoverloader.h
@@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
+ * Copyright 2019-2020, 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
@@ -33,6 +34,7 @@
#include "core/song.h"
#include "albumcoverloaderoptions.h"
+#include "albumcoverloaderresult.h"
class Application;
@@ -50,11 +52,11 @@ class CurrentAlbumCoverLoader : public QObject {
void LoadAlbumCover(const Song &song);
signals:
- void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image);
- void ThumbnailLoaded(const Song &song, const QUrl &thumbnail_uri, const QImage &image);
+ void AlbumCoverLoaded(Song song, AlbumCoverLoaderResult result);
+ void ThumbnailLoaded(Song song, QUrl thumbnail_uri, QImage image);
private slots:
- void TempAlbumCoverLoaded(const quint64 id, const QUrl &remote_url, const QImage &image);
+ void TempAlbumCoverLoaded(const quint64 id, AlbumCoverLoaderResult result);
private:
Application *app_;
diff --git a/src/covermanager/deezercoverprovider.cpp b/src/covermanager/deezercoverprovider.cpp
index c2a3a5855..15c2de35d 100644
--- a/src/covermanager/deezercoverprovider.cpp
+++ b/src/covermanager/deezercoverprovider.cpp
@@ -50,16 +50,27 @@
const char *DeezerCoverProvider::kApiUrl = "https://api.deezer.com";
const int DeezerCoverProvider::kLimit = 10;
-DeezerCoverProvider::DeezerCoverProvider(Application *app, QObject *parent): CoverProvider("Deezer", 2.0, true, app, parent), network_(new NetworkAccessManager(this)) {}
+DeezerCoverProvider::DeezerCoverProvider(Application *app, QObject *parent): CoverProvider("Deezer", 2.0, true, true, app, parent), network_(new NetworkAccessManager(this)) {}
-bool DeezerCoverProvider::StartSearch(const QString &artist, const QString &album, const int id) {
+bool DeezerCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) {
typedef QPair Param;
typedef QList Params;
typedef QPair EncodedParam;
+ QUrl url(kApiUrl);
+ QString search;
+ if (album.isEmpty()) {
+ url.setPath("/search/track");
+ search = artist + " " + title;
+ }
+ else {
+ url.setPath("/search/album");
+ search = artist + " " + album;
+ }
+
const Params params = Params() << Param("output", "json")
- << Param("q", QString(artist + " " + album))
+ << Param("q", search)
<< Param("limit", QString::number(kLimit));
QUrlQuery url_query;
@@ -68,7 +79,6 @@ bool DeezerCoverProvider::StartSearch(const QString &artist, const QString &albu
url_query.addQueryItem(encoded_param.first, encoded_param.second);
}
- QUrl url(kApiUrl + QString("/search/album"));
url.setQuery(url_query);
QNetworkRequest req(url);
req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
@@ -220,19 +230,27 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
for (const QJsonValue &value : json_data) {
if (!value.isObject()) {
- Error("Invalid Json reply, data is not an object.", value);
+ Error("Invalid Json reply, data in array is not a object.", value);
continue;
}
QJsonObject json_obj = value.toObject();
+ QJsonObject json_album = json_obj;
+ if (json_obj.contains("album") && json_obj["album"].isObject()) { // Song search, so extract the album.
+ json_album = json_obj["album"].toObject();
+ }
- if (!json_obj.contains("id") || !json_obj.contains("type")) {
- Error("Invalid Json reply, item is missing ID or type.", json_obj);
+ if (!json_obj.contains("id") || !json_album.contains("id")) {
+ Error("Invalid Json reply, object is missing ID.", json_obj);
continue;
}
- QString type = json_obj["type"].toString();
+ if (!json_album.contains("type")) {
+ Error("Invalid Json reply, album object is missing type.", json_album);
+ continue;
+ }
+ QString type = json_album["type"].toString();
if (type != "album") {
- Error("Invalid Json reply, incorrect type returned", json_obj);
+ Error("Invalid Json reply, incorrect type returned", json_album);
continue;
}
@@ -242,7 +260,7 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
}
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);
+ Error("Invalid Json reply, artist is not a object.", json_value_artist);
continue;
}
QJsonObject json_artist = json_value_artist.toObject();
@@ -253,27 +271,27 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
}
QString artist = json_artist["name"].toString();
- if (!json_obj.contains("title")) {
- Error("Invalid Json reply, data missing title.", json_obj);
+ if (!json_album.contains("title")) {
+ Error("Invalid Json reply, data missing title.", json_album);
continue;
}
- QString album = json_obj["title"].toString();
+ QString album = json_album["title"].toString();
QString cover;
- if (json_obj.contains("cover_xl")) {
- cover = json_obj["cover_xl"].toString();
+ if (json_album.contains("cover_xl")) {
+ cover = json_album["cover_xl"].toString();
}
- else if (json_obj.contains("cover_big")) {
- cover = json_obj["cover_big"].toString();
+ else if (json_album.contains("cover_big")) {
+ cover = json_album["cover_big"].toString();
}
- else if (json_obj.contains("cover_medium")) {
- cover = json_obj["cover_medium"].toString();
+ else if (json_album.contains("cover_medium")) {
+ cover = json_album["cover_medium"].toString();
}
- else if (json_obj.contains("cover_small")) {
- cover = json_obj["cover_small"].toString();
+ else if (json_album.contains("cover_small")) {
+ cover = json_album["cover_small"].toString();
}
else {
- Error("Invalid Json reply, data missing cover.", json_obj);
+ Error("Invalid Json reply, album missing cover.", json_album);
continue;
}
QUrl url(cover);
diff --git a/src/covermanager/deezercoverprovider.h b/src/covermanager/deezercoverprovider.h
index f44500e3d..5858fe82c 100644
--- a/src/covermanager/deezercoverprovider.h
+++ b/src/covermanager/deezercoverprovider.h
@@ -40,7 +40,7 @@ class DeezerCoverProvider : public CoverProvider {
public:
explicit DeezerCoverProvider(Application *app, QObject *parent = nullptr);
- bool StartSearch(const QString &artist, const QString &album, const int id);
+ bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id);
void CancelSearch(const int id);
private slots:
diff --git a/src/covermanager/discogscoverprovider.cpp b/src/covermanager/discogscoverprovider.cpp
index 092d7fc7d..e89dedc21 100644
--- a/src/covermanager/discogscoverprovider.cpp
+++ b/src/covermanager/discogscoverprovider.cpp
@@ -59,9 +59,11 @@ const char *DiscogsCoverProvider::kUrlReleases = "https://api.discogs.com/releas
const char *DiscogsCoverProvider::kAccessKeyB64 = "dGh6ZnljUGJlZ1NEeXBuSFFxSVk=";
const char *DiscogsCoverProvider::kSecretKeyB64 = "ZkFIcmlaSER4aHhRSlF2U3d0bm5ZVmdxeXFLWUl0UXI=";
-DiscogsCoverProvider::DiscogsCoverProvider(Application *app, QObject *parent) : CoverProvider("Discogs", 0.0, false, app, parent), network_(new NetworkAccessManager(this)) {}
+DiscogsCoverProvider::DiscogsCoverProvider(Application *app, QObject *parent) : CoverProvider("Discogs", 0.0, false, false, app, parent), network_(new NetworkAccessManager(this)) {}
-bool DiscogsCoverProvider::StartSearch(const QString &artist, const QString &album, const int s_id) {
+bool DiscogsCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int s_id) {
+
+ Q_UNUSED(title);
DiscogsCoverSearchContext *s_ctx = new DiscogsCoverSearchContext;
diff --git a/src/covermanager/discogscoverprovider.h b/src/covermanager/discogscoverprovider.h
index 8c429c9c0..1fd9c735c 100644
--- a/src/covermanager/discogscoverprovider.h
+++ b/src/covermanager/discogscoverprovider.h
@@ -73,7 +73,7 @@ class DiscogsCoverProvider : public CoverProvider {
public:
explicit DiscogsCoverProvider(Application *app, QObject *parent = nullptr);
- bool StartSearch(const QString &artist, const QString &album, const int s_id);
+ bool StartSearch(const QString &artist, const QString &album, const QString &title, const int s_id);
void CancelSearch(const int id);
diff --git a/src/covermanager/lastfmcoverprovider.cpp b/src/covermanager/lastfmcoverprovider.cpp
index 643a7c875..e58f857f9 100644
--- a/src/covermanager/lastfmcoverprovider.cpp
+++ b/src/covermanager/lastfmcoverprovider.cpp
@@ -52,9 +52,11 @@ const char *LastFmCoverProvider::kUrl = "https://ws.audioscrobbler.com/2.0/";
const char *LastFmCoverProvider::kApiKey = "211990b4c96782c05d1536e7219eb56e";
const char *LastFmCoverProvider::kSecret = "80fd738f49596e9709b1bf9319c444a8";
-LastFmCoverProvider::LastFmCoverProvider(Application *app, QObject *parent) : CoverProvider("last.fm", 1.0, true, app, parent), network_(new NetworkAccessManager(this)) {}
+LastFmCoverProvider::LastFmCoverProvider(Application *app, QObject *parent) : CoverProvider("last.fm", 1.0, true, false, app, parent), network_(new NetworkAccessManager(this)) {}
-bool LastFmCoverProvider::StartSearch(const QString &artist, const QString &album, const int id) {
+bool LastFmCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) {
+
+ Q_UNUSED(title);
typedef QPair Param;
typedef QPair EncodedParam;
diff --git a/src/covermanager/lastfmcoverprovider.h b/src/covermanager/lastfmcoverprovider.h
index 8acaeeba1..e456cc50b 100644
--- a/src/covermanager/lastfmcoverprovider.h
+++ b/src/covermanager/lastfmcoverprovider.h
@@ -40,7 +40,7 @@ class LastFmCoverProvider : public CoverProvider {
public:
explicit LastFmCoverProvider(Application *app, QObject *parent = nullptr);
- bool StartSearch(const QString &artist, const QString &album, const int id);
+ bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id);
private slots:
void QueryFinished(QNetworkReply *reply, const int id);
diff --git a/src/covermanager/musicbrainzcoverprovider.cpp b/src/covermanager/musicbrainzcoverprovider.cpp
index 28dad534e..c0ac44d89 100644
--- a/src/covermanager/musicbrainzcoverprovider.cpp
+++ b/src/covermanager/musicbrainzcoverprovider.cpp
@@ -47,9 +47,11 @@ const char *MusicbrainzCoverProvider::kReleaseSearchUrl = "https://musicbrainz.o
const char *MusicbrainzCoverProvider::kAlbumCoverUrl = "https://coverartarchive.org/release/%1/front";
const int MusicbrainzCoverProvider::kLimit = 8;
-MusicbrainzCoverProvider::MusicbrainzCoverProvider(Application *app, QObject *parent): CoverProvider("MusicBrainz", 1.5, true, app, parent), network_(new NetworkAccessManager(this)) {}
+MusicbrainzCoverProvider::MusicbrainzCoverProvider(Application *app, QObject *parent): CoverProvider("MusicBrainz", 1.5, true, false, app, parent), network_(new NetworkAccessManager(this)) {}
-bool MusicbrainzCoverProvider::StartSearch(const QString &artist, const QString &album, const int id) {
+bool MusicbrainzCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) {
+
+ Q_UNUSED(title);
QString query = QString("release:\"%1\" AND artist:\"%2\"").arg(album.trimmed().replace('"', "\\\"")).arg(artist.trimmed().replace('"', "\\\""));
diff --git a/src/covermanager/musicbrainzcoverprovider.h b/src/covermanager/musicbrainzcoverprovider.h
index 972289c51..905695d98 100644
--- a/src/covermanager/musicbrainzcoverprovider.h
+++ b/src/covermanager/musicbrainzcoverprovider.h
@@ -39,7 +39,7 @@ class MusicbrainzCoverProvider : public CoverProvider {
public:
explicit MusicbrainzCoverProvider(Application *app, QObject *parent = nullptr);
- bool StartSearch(const QString &artist, const QString &album, const int id);
+ bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id);
void CancelSearch(const int id);
private slots:
diff --git a/src/covermanager/tidalcoverprovider.cpp b/src/covermanager/tidalcoverprovider.cpp
index 7a6658d42..6d404b9e1 100644
--- a/src/covermanager/tidalcoverprovider.cpp
+++ b/src/covermanager/tidalcoverprovider.cpp
@@ -52,13 +52,15 @@ const char *TidalCoverProvider::kResourcesUrl = "https://resources.tidal.com";
const int TidalCoverProvider::kLimit = 10;
TidalCoverProvider::TidalCoverProvider(Application *app, QObject *parent) :
- CoverProvider("Tidal", 2.0, true, app, parent),
+ CoverProvider("Tidal", 2.0, true, false, app, parent),
service_(app->internet_services()->Service()),
network_(new NetworkAccessManager(this)) {
}
-bool TidalCoverProvider::StartSearch(const QString &artist, const QString &album, const int id) {
+bool TidalCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) {
+
+ Q_UNUSED(title);
if (!service_ || !service_->authenticated()) return false;
diff --git a/src/covermanager/tidalcoverprovider.h b/src/covermanager/tidalcoverprovider.h
index 9eb3ee2e4..763e45c61 100644
--- a/src/covermanager/tidalcoverprovider.h
+++ b/src/covermanager/tidalcoverprovider.h
@@ -44,7 +44,7 @@ class TidalCoverProvider : public CoverProvider {
public:
explicit TidalCoverProvider(Application *app, QObject *parent = nullptr);
- bool StartSearch(const QString &artist, const QString &album, const int id);
+ bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id);
void CancelSearch(int id);
private slots:
diff --git a/src/dialogs/addstreamdialog.cpp b/src/dialogs/addstreamdialog.cpp
new file mode 100644
index 000000000..fbcf7ecd4
--- /dev/null
+++ b/src/dialogs/addstreamdialog.cpp
@@ -0,0 +1,54 @@
+/*
+ * Strawberry Music Player
+ * Copyright 2020, 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 "addstreamdialog.h"
+#include "ui_addstreamdialog.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+AddStreamDialog::AddStreamDialog(QWidget* parent) : QDialog(parent), ui_(new Ui_AddStreamDialog) {
+
+ ui_->setupUi(this);
+
+ connect(ui_->url, SIGNAL(textChanged(QString)), SLOT(TextChanged(QString)));
+ TextChanged(QString());
+
+}
+
+AddStreamDialog::~AddStreamDialog() { delete ui_; }
+
+void AddStreamDialog::showEvent(QShowEvent*) {
+
+ ui_->url->setFocus();
+ ui_->url->selectAll();
+
+}
+
+void AddStreamDialog::TextChanged(const QString &text) {
+
+ QUrl url(text);
+ ui_->button_box->button(QDialogButtonBox::Ok)->setEnabled(url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty());
+
+}
diff --git a/src/dialogs/addstreamdialog.h b/src/dialogs/addstreamdialog.h
new file mode 100644
index 000000000..c04cbdf1c
--- /dev/null
+++ b/src/dialogs/addstreamdialog.h
@@ -0,0 +1,51 @@
+/*
+ * Strawberry Music Player
+ * Copyright 2020, 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 ADDSTREAMDIALOG_H
+#define ADDSTREAMDIALOG_H
+
+#include
+#include
+#include
+#include
+
+#include "ui_addstreamdialog.h"
+
+class AddStreamDialog : public QDialog {
+ Q_OBJECT
+
+ public:
+ AddStreamDialog(QWidget *parent = nullptr);
+ ~AddStreamDialog();
+
+ QUrl url() const { return QUrl(ui_->url->text()); }
+ void set_url(const QUrl &url) { ui_->url->setText(url.toString());}
+
+ protected:
+ void showEvent(QShowEvent*);
+
+ private slots:
+ void TextChanged(const QString &text);
+
+ private:
+ Ui_AddStreamDialog *ui_;
+
+};
+
+#endif // ADDSTREAMDIALOG_H
diff --git a/src/dialogs/addstreamdialog.ui b/src/dialogs/addstreamdialog.ui
new file mode 100644
index 000000000..304f29209
--- /dev/null
+++ b/src/dialogs/addstreamdialog.ui
@@ -0,0 +1,98 @@
+
+
+ AddStreamDialog
+
+
+
+ 0
+ 0
+ 400
+ 120
+
+
+
+ Add Stream
+
+
+
+ :/icons/48x48/document-open-remote.png:/icons/48x48/document-open-remote.png
+
+
+ -
+
+
+ Enter the URL of a stream:
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
+
+
+ url
+ button_box
+
+
+
+
+
+
+
+ button_box
+ accepted()
+ AddStreamDialog
+ accept()
+
+
+ 257
+ 158
+
+
+ 157
+ 167
+
+
+
+
+ button_box
+ rejected()
+ AddStreamDialog
+ reject()
+
+
+ 325
+ 158
+
+
+ 286
+ 167
+
+
+
+
+
diff --git a/src/dialogs/edittagdialog.cpp b/src/dialogs/edittagdialog.cpp
index e9f5bd5ba..bd871779a 100644
--- a/src/dialogs/edittagdialog.cpp
+++ b/src/dialogs/edittagdialog.cpp
@@ -81,6 +81,8 @@
#endif
#include "covermanager/albumcoverchoicecontroller.h"
#include "covermanager/albumcoverloader.h"
+#include "covermanager/albumcoverloaderoptions.h"
+#include "covermanager/albumcoverloaderresult.h"
#include "covermanager/coverproviders.h"
#include "edittagdialog.h"
#include "trackselectiondialog.h"
@@ -106,9 +108,9 @@ EditTagDialog::EditTagDialog(Application *app, QWidget *parent)
pending_(0)
{
- cover_options_.default_output_image_ = AlbumCoverLoader::ScaleAndPad(cover_options_, QImage(":/pictures/cdcase.png"));
+ cover_options_.default_output_image_ = AlbumCoverLoader::ScaleAndPad(cover_options_, QImage(":/pictures/cdcase.png")).first;
- connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage, QImage)), SLOT(AlbumCoverLoaded(quint64, QUrl, QImage, QImage)));
+ connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)));
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
connect(tag_fetcher_, SIGNAL(ResultAvailable(Song, SongList)), results_dialog_, SLOT(FetchTagFinished(Song, SongList)), Qt::QueuedConnection);
@@ -562,13 +564,11 @@ void EditTagDialog::UpdateStatisticsTab(const Song &song) {
}
-void EditTagDialog::AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &scaled, const QImage &original) {
-
- Q_UNUSED(cover_url);
+void EditTagDialog::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) {
if (id == cover_art_id_) {
- ui_->art->setPixmap(QPixmap::fromImage(scaled));
- original_ = original;
+ ui_->art->setPixmap(QPixmap::fromImage(result.image_scaled));
+ original_ = result.image_original;
}
}
diff --git a/src/dialogs/edittagdialog.h b/src/dialogs/edittagdialog.h
index 7b2ced00f..39f684284 100644
--- a/src/dialogs/edittagdialog.h
+++ b/src/dialogs/edittagdialog.h
@@ -38,6 +38,7 @@
#include "core/tagreaderclient.h"
#include "playlist/playlistitem.h"
#include "covermanager/albumcoverloaderoptions.h"
+#include "covermanager/albumcoverloaderresult.h"
class QWidget;
class QMenu;
@@ -110,7 +111,7 @@ class EditTagDialog : public QDialog {
void FetchTag();
void FetchTagSongChosen(const Song &original_song, const Song &new_metadata);
- void AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &scaled, const QImage &original);
+ void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
void LoadCoverFromFile();
void SaveCoverToFile();
diff --git a/src/engine/gstenginepipeline.cpp b/src/engine/gstenginepipeline.cpp
index d30fe4169..71dd73cc5 100644
--- a/src/engine/gstenginepipeline.cpp
+++ b/src/engine/gstenginepipeline.cpp
@@ -839,6 +839,16 @@ void GstEnginePipeline::TagMessageReceived(GstMessage *msg) {
bundle.bitrate = ParseUIntTag(taglist, GST_TAG_BITRATE) / 1000;
bundle.lyrics = ParseStrTag(taglist, GST_TAG_LYRICS);
+ if (!bundle.title.isEmpty() && bundle.artist.isEmpty() && bundle.album.isEmpty() && bundle.title.contains(" - ")) {
+ QStringList title_splitted = bundle.title.split(" - ");
+ if (title_splitted.count() == 2) {
+ bundle.artist = title_splitted.first();
+ bundle.title = title_splitted.last();
+ bundle.artist = bundle.artist.trimmed();
+ bundle.title = bundle.title.trimmed();
+ }
+ }
+
gst_tag_list_free(taglist);
emit MetadataFound(id(), bundle);
diff --git a/src/internet/internetplaylistitem.cpp b/src/internet/internetplaylistitem.cpp
index 432d5ab3f..15649fd5f 100644
--- a/src/internet/internetplaylistitem.cpp
+++ b/src/internet/internetplaylistitem.cpp
@@ -46,9 +46,11 @@ InternetPlaylistItem::InternetPlaylistItem(InternetService *service, const Song
}
bool InternetPlaylistItem::InitFromQuery(const SqlRow &query) {
+
metadata_.InitFromQuery(query, false, (Song::kColumns.count() + 1) * 1);
InitMetadata();
return true;
+
}
QVariant InternetPlaylistItem::DatabaseValue(DatabaseColumn column) const {
@@ -56,15 +58,26 @@ QVariant InternetPlaylistItem::DatabaseValue(DatabaseColumn column) const {
}
void InternetPlaylistItem::InitMetadata() {
+
if (metadata_.title().isEmpty()) metadata_.set_title(metadata_.url().toString());
if (metadata_.source() == Song::Source_Unknown) metadata_.set_source(Song::Source_Stream);
if (metadata_.filetype() == Song::FileType_Unknown) metadata_.set_filetype(Song::FileType_Stream);
metadata_.set_valid(true);
+
}
Song InternetPlaylistItem::Metadata() const {
+
if (HasTemporaryMetadata()) return temp_metadata_;
return metadata_;
+
}
QUrl InternetPlaylistItem::Url() const { return metadata_.url(); }
+
+void InternetPlaylistItem::SetArtManual(const QUrl &cover_url) {
+
+ metadata_.set_art_manual(cover_url);
+ temp_metadata_.set_art_manual(cover_url);
+
+}
diff --git a/src/internet/internetplaylistitem.h b/src/internet/internetplaylistitem.h
index 0570a68dc..bb5373ed6 100644
--- a/src/internet/internetplaylistitem.h
+++ b/src/internet/internetplaylistitem.h
@@ -36,10 +36,11 @@ class InternetPlaylistItem : public PlaylistItem {
public:
explicit InternetPlaylistItem(const Song::Source &type);
- InternetPlaylistItem(InternetService *service, const Song &metadata);
+ explicit InternetPlaylistItem(InternetService *service, const Song &metadata);
bool InitFromQuery(const SqlRow &query);
Song Metadata() const;
QUrl Url() const;
+ void SetArtManual(const QUrl &cover_url);
protected:
QVariant DatabaseValue(DatabaseColumn) const;
diff --git a/src/internet/internetsearchitemdelegate.cpp b/src/internet/internetsearchitemdelegate.cpp
index 5165b8ef3..87c447efb 100644
--- a/src/internet/internetsearchitemdelegate.cpp
+++ b/src/internet/internetsearchitemdelegate.cpp
@@ -27,11 +27,11 @@
InternetSearchItemDelegate::InternetSearchItemDelegate(InternetSearchView *view)
: CollectionItemDelegate(view), view_(view) {}
-void InternetSearchItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
+void InternetSearchItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const {
// Tell the view we painted this item so it can lazy load some art.
- const_cast(view_)->LazyLoadAlbumCover(index);
+ const_cast(view_)->LazyLoadAlbumCover(idx);
- CollectionItemDelegate::paint(painter, option, index);
+ CollectionItemDelegate::paint(painter, option, idx);
}
diff --git a/src/internet/internetsearchmodel.cpp b/src/internet/internetsearchmodel.cpp
index c4785e9c5..c4a418175 100644
--- a/src/internet/internetsearchmodel.cpp
+++ b/src/internet/internetsearchmodel.cpp
@@ -2,6 +2,7 @@
* Strawberry Music Player
* This code was part of Clementine (GlobalSearch)
* Copyright 2012, David Sansome
+ * 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
@@ -56,21 +57,17 @@ InternetSearchModel::InternetSearchModel(InternetService *service, QObject *pare
void InternetSearchModel::AddResults(const InternetSearchView::ResultList &results) {
- int sort_index = 0;
-
for (const InternetSearchView::Result &result : results) {
QStandardItem *parent = invisibleRootItem();
// Find (or create) the container nodes for this result if we can.
ContainerKey key;
- key.provider_index_ = sort_index;
parent = BuildContainers(result.metadata_, parent, &key);
// Create the item
QStandardItem *item = new QStandardItem;
item->setText(result.metadata_.TitleWithCompilationArtist());
item->setData(QVariant::fromValue(result), Role_Result);
- item->setData(sort_index, Role_ProviderIndex);
parent->appendRow(item);
@@ -78,7 +75,7 @@ void InternetSearchModel::AddResults(const InternetSearchView::ResultList &resul
}
-QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem *parent, ContainerKey *key, int level) {
+QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem *parent, ContainerKey *key, const int level) {
if (level >= 3) {
return parent;
@@ -249,7 +246,6 @@ QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem
QStandardItem *container = containers_[*key];
if (!container) {
container = new QStandardItem(display_text);
- container->setData(key->provider_index_, Role_ProviderIndex);
container->setData(sort_text, CollectionModel::Role_SortText);
container->setData(group_by_[level], CollectionModel::Role_ContainerType);
@@ -275,8 +271,10 @@ QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem
}
void InternetSearchModel::Clear() {
+
containers_.clear();
clear();
+
}
InternetSearchView::ResultList InternetSearchModel::GetChildResults(const QModelIndexList &indexes) const {
@@ -314,7 +312,7 @@ void InternetSearchModel::GetChildResults(const QStandardItem *item, InternetSea
const QModelIndex parent_proxy_index = proxy_->mapFromSource(item->index());
// Yes - visit all the children, but do so through the proxy so we get them in the right order.
- for (int i = 0; i < item->rowCount(); ++i) {
+ for (int i = 0 ; i < item->rowCount() ; ++i) {
const QModelIndex proxy_index = parent_proxy_index.model()->index(i, 0, parent_proxy_index);
const QModelIndex index = proxy_->mapToSource(proxy_index);
GetChildResults(itemFromIndex(index), results, visited);
@@ -326,21 +324,6 @@ void InternetSearchModel::GetChildResults(const QStandardItem *item, InternetSea
if (result.isValid()) {
results->append(result.value());
}
- else {
- // Maybe it's a provider then?
- bool is_provider;
- const int sort_index = item->data(Role_ProviderIndex).toInt(&is_provider);
- if (is_provider) {
- // Go through all the items (through the proxy to keep them ordered) and add the ones belonging to this provider to our list
- for (int i = 0; i < proxy_->rowCount(invisibleRootItem()->index()); ++i) {
- QModelIndex child_index = proxy_->index(i, 0, invisibleRootItem()->index());
- const QStandardItem *child_item = itemFromIndex(proxy_->mapToSource(child_index));
- if (child_item->data(Role_ProviderIndex).toInt() == sort_index) {
- GetChildResults(child_item, results, visited);
- }
- }
- }
- }
}
}
@@ -360,13 +343,13 @@ void GatherResults(const QStandardItem *parent, InternetSearchView::ResultList *
(*results).append(result);
}
- for (int i = 0; i < parent->rowCount(); ++i) {
+ for (int i = 0 ; i < parent->rowCount() ; ++i) {
GatherResults(parent->child(i), results);
}
}
}
-void InternetSearchModel::SetGroupBy(const CollectionModel::Grouping &grouping, bool regroup_now) {
+void InternetSearchModel::SetGroupBy(const CollectionModel::Grouping &grouping, const bool regroup_now) {
const CollectionModel::Grouping old_group_by = group_by_;
group_by_ = grouping;
@@ -389,24 +372,17 @@ MimeData *InternetSearchModel::LoadTracks(const InternetSearchView::ResultList &
return nullptr;
}
- InternetSearchView::ResultList results_copy;
- for (const InternetSearchView::Result &result : results) {
- results_copy << result;
- }
-
SongList songs;
+ QList urls;
for (const InternetSearchView::Result &result : results) {
songs << result.metadata_;
+ urls << result.metadata_.url();
}
InternetSongMimeData *internet_song_mime_data = new InternetSongMimeData(service_);
internet_song_mime_data->songs = songs;
MimeData *mime_data = internet_song_mime_data;
- QList urls;
- for (const InternetSearchView::Result &result : results) {
- urls << result.metadata_.url();
- }
mime_data->setUrls(urls);
return mime_data;
diff --git a/src/internet/internetsearchmodel.h b/src/internet/internetsearchmodel.h
index 5e8729c23..fd14b45c0 100644
--- a/src/internet/internetsearchmodel.h
+++ b/src/internet/internetsearchmodel.h
@@ -2,6 +2,7 @@
* Strawberry Music Player
* This code was part of Clementine (GlobalSearch)
* Copyright 2012, David Sansome
+ * 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
@@ -54,18 +55,16 @@ class InternetSearchModel : public QStandardItemModel {
enum Role {
Role_Result = CollectionModel::LastRole,
Role_LazyLoadingArt,
- Role_ProviderIndex,
LastRole
};
struct ContainerKey {
- int provider_index_;
QString group_[3];
};
void set_proxy(QSortFilterProxyModel *proxy) { proxy_ = proxy; }
void set_use_pretty_covers(const bool pretty) { use_pretty_covers_ = pretty; }
- void SetGroupBy(const CollectionModel::Grouping &grouping, bool regroup_now);
+ void SetGroupBy(const CollectionModel::Grouping &grouping, const bool regroup_now);
void Clear();
@@ -82,7 +81,7 @@ class InternetSearchModel : public QStandardItemModel {
void AddResults(const InternetSearchView::ResultList &results);
private:
- QStandardItem *BuildContainers(const Song &metadata, QStandardItem *parent, ContainerKey *key, int level = 0);
+ QStandardItem *BuildContainers(const Song &s, QStandardItem *parent, ContainerKey *key, const int level = 0);
void GetChildResults(const QStandardItem *item, InternetSearchView::ResultList *results, QSet *visited) const;
private:
@@ -98,7 +97,7 @@ class InternetSearchModel : public QStandardItemModel {
};
inline uint qHash(const InternetSearchModel::ContainerKey &key) {
- return qHash(key.provider_index_) ^ qHash(key.group_[0]) ^ qHash(key.group_[1]) ^ qHash(key.group_[2]);
+ return qHash(key.group_[0]) ^ qHash(key.group_[1]) ^ qHash(key.group_[2]);
}
inline bool operator<(const InternetSearchModel::ContainerKey &left, const InternetSearchModel::ContainerKey &right) {
@@ -106,7 +105,6 @@ inline bool operator<(const InternetSearchModel::ContainerKey &left, const Inter
if (left.field < right.field) return true; \
if (left.field > right.field) return false
- CMP(provider_index_);
CMP(group_[0]);
CMP(group_[1]);
CMP(group_[2]);
diff --git a/src/internet/internetsearchsortmodel.cpp b/src/internet/internetsearchsortmodel.cpp
index 29aee512e..0fd5de785 100644
--- a/src/internet/internetsearchsortmodel.cpp
+++ b/src/internet/internetsearchsortmodel.cpp
@@ -32,15 +32,9 @@
#include "internetsearchsortmodel.h"
#include "internetsearchview.h"
-InternetSearchSortModel::InternetSearchSortModel(QObject *parent)
- : QSortFilterProxyModel(parent) {}
+InternetSearchSortModel::InternetSearchSortModel(QObject *parent) : QSortFilterProxyModel(parent) {}
bool InternetSearchSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const {
- // Compare the provider sort index first.
- const int index_left = left.data(InternetSearchModel::Role_ProviderIndex).toInt();
- const int index_right = right.data(InternetSearchModel::Role_ProviderIndex).toInt();
- if (index_left < index_right) return true;
- if (index_left > index_right) return false;
// Dividers always go first
if (left.data(CollectionModel::Role_IsDivider).toBool()) return true;
diff --git a/src/internet/internetsearchview.cpp b/src/internet/internetsearchview.cpp
index 35fee56d7..3c3df2ed2 100644
--- a/src/internet/internetsearchview.cpp
+++ b/src/internet/internetsearchview.cpp
@@ -21,6 +21,7 @@
#include "config.h"
+#include
#include
#include
@@ -32,6 +33,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -69,10 +71,13 @@
#include "core/mimedata.h"
#include "core/iconloader.h"
#include "core/song.h"
+#include "core/logging.h"
#include "collection/collectionfilterwidget.h"
#include "collection/collectionmodel.h"
#include "collection/groupbydialog.h"
#include "covermanager/albumcoverloader.h"
+#include "covermanager/albumcoverloaderoptions.h"
+#include "covermanager/albumcoverloaderresult.h"
#include "internetsongmimedata.h"
#include "internetservice.h"
#include "internetsearchitemdelegate.h"
@@ -102,11 +107,11 @@ InternetSearchView::InternetSearchView(QWidget *parent)
back_proxy_(new InternetSearchSortModel(this)),
current_proxy_(front_proxy_),
swap_models_timer_(new QTimer(this)),
+ use_pretty_covers_(true),
search_type_(InternetSearchView::SearchType_Artists),
search_error_(false),
last_search_id_(0),
- searches_next_id_(1),
- art_searches_next_id_(1) {
+ searches_next_id_(1) {
ui_->setupUi(this);
@@ -164,7 +169,7 @@ void InternetSearchView::Init(Application *app, InternetService *service) {
current_model_ = front_model_;
current_proxy_ = front_proxy_;
-
+
// Set up the sorting proxy model
front_proxy_->setSourceModel(front_model_);
front_proxy_->setDynamicSortFilter(true);
@@ -190,6 +195,7 @@ void InternetSearchView::Init(Application *app, InternetService *service) {
connect(ui_->radiobutton_search_albums, SIGNAL(clicked(bool)), SLOT(SearchAlbumsClicked(bool)));
connect(ui_->radiobutton_search_songs, SIGNAL(clicked(bool)), SLOT(SearchSongsClicked(bool)));
connect(group_by_actions_, SIGNAL(triggered(QAction*)), SLOT(GroupByClicked(QAction*)));
+ connect(group_by_actions_, SIGNAL(triggered(QAction*)), SLOT(GroupByClicked(QAction*)));
connect(ui_->search, SIGNAL(textChanged(QString)), SLOT(TextEdited(QString)));
connect(ui_->results, SIGNAL(AddToPlaylistSignal(QMimeData*)), SIGNAL(AddToPlaylist(QMimeData*)));
@@ -201,7 +207,7 @@ void InternetSearchView::Init(Application *app, InternetService *service) {
connect(service_, SIGNAL(SearchResults(int, SongList, QString)), SLOT(SearchDone(int, SongList, QString)));
connect(app_, SIGNAL(SettingsChanged()), SLOT(ReloadSettings()));
- connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(AlbumCoverLoaded(quint64, QUrl, QImage)));
+ connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)));
ReloadSettings();
@@ -214,9 +220,9 @@ void InternetSearchView::ReloadSettings() {
// Collection settings
s.beginGroup(service_->settings_group());
- const bool pretty = s.value("pretty_covers", true).toBool();
- front_model_->set_use_pretty_covers(pretty);
- back_model_->set_use_pretty_covers(pretty);
+ use_pretty_covers_ = s.value("pretty_covers", true).toBool();
+ front_model_->set_use_pretty_covers(use_pretty_covers_);
+ back_model_->set_use_pretty_covers(use_pretty_covers_);
// Internet search settings
@@ -248,12 +254,6 @@ void InternetSearchView::showEvent(QShowEvent *e) {
}
-void InternetSearchView::hideEvent(QHideEvent *e) {
-
- QWidget::hideEvent(e);
-
-}
-
bool InternetSearchView::eventFilter(QObject *object, QEvent *e) {
if (object == ui_->search && e->type() == QEvent::KeyRelease) {
@@ -374,6 +374,7 @@ void InternetSearchView::TextEdited(const QString &text) {
const QString trimmed(text.trimmed());
search_error_ = false;
+ cover_loader_tasks_.clear();
// Add results to the back model, switch models after some delay.
back_model_->Clear();
@@ -387,7 +388,7 @@ void InternetSearchView::TextEdited(const QString &text) {
// If text query is empty, don't start a new search
if (trimmed.isEmpty()) {
last_search_id_ = -1;
- ui_->label_helptext->setText("Enter search terms above to find music");
+ ui_->label_helptext->setText(tr("Enter search terms above to find music"));
ui_->label_status->clear();
ui_->progressbar->hide();
ui_->progressbar->reset();
@@ -401,7 +402,7 @@ void InternetSearchView::TextEdited(const QString &text) {
void InternetSearchView::SwapModels() {
- art_requests_.clear();
+ cover_loader_tasks_.clear();
std::swap(front_model_, back_model_);
std::swap(front_proxy_, back_proxy_);
@@ -488,10 +489,8 @@ void InternetSearchView::SearchDone(const int service_id, const SongList &songs,
results << result;
}
- if (results.isEmpty()) return;
-
// Load cached pixmaps into the results
- for (InternetSearchView::ResultList::iterator it = results.begin(); it != results.end(); ++it) {
+ for (InternetSearchView::ResultList::iterator it = results.begin() ; it != results.end() ; ++it) {
it->pixmap_cache_key_ = PixmapCacheKey(*it);
}
@@ -576,7 +575,7 @@ MimeData *InternetSearchView::SelectedMimeData() {
QModelIndexList indexes = ui_->results->selectionModel()->selectedRows();
if (indexes.isEmpty()) {
// There's nothing selected - take the first thing in the model that isn't a divider.
- for (int i = 0; i < front_proxy_->rowCount(); ++i) {
+ for (int i = 0 ; i < front_proxy_->rowCount() ; ++i) {
QModelIndex index = front_proxy_->index(i, 0);
if (!index.data(CollectionModel::Role_IsDivider).toBool()) {
indexes << index;
@@ -663,7 +662,7 @@ void InternetSearchView::GroupByClicked(QAction *action) {
if (action->property("group_by").isNull()) {
if (!group_by_dialog_) {
group_by_dialog_.reset(new GroupByDialog);
- connect(group_by_dialog_.data(), SIGNAL(Accepted(CollectionModel::Grouping)), SLOT(SetGroupBy(CollectionModel::Grouping)));
+ connect(group_by_dialog_.get(), SIGNAL(Accepted(CollectionModel::Grouping)), SLOT(SetGroupBy(CollectionModel::Grouping)));
}
group_by_dialog_->show();
@@ -679,7 +678,8 @@ void InternetSearchView::SetGroupBy(const CollectionModel::Grouping &g) {
// Clear requests: changing "group by" on the models will cause all the items to be removed/added again,
// so all the QModelIndex here will become invalid. New requests will be created for those
// songs when they will be displayed again anyway (when InternetSearchItemDelegate::paint will call LazyLoadAlbumCover)
- art_requests_.clear();
+ cover_loader_tasks_.clear();
+
// Update the models
front_model_->SetGroupBy(g, true);
back_model_->SetGroupBy(g, false);
@@ -722,10 +722,12 @@ void InternetSearchView::SearchSongsClicked(const bool) {
void InternetSearchView::SetSearchType(const InternetSearchView::SearchType type) {
search_type_ = type;
+
QSettings s;
s.beginGroup(service_->settings_group());
s.setValue("type", int(search_type_));
s.endGroup();
+
TextEdited(ui_->search->text());
}
@@ -761,110 +763,101 @@ void InternetSearchView::AddSongs() {
}
QString InternetSearchView::PixmapCacheKey(const InternetSearchView::Result &result) const {
- return "internet:" % result.metadata_.url().toString();
+
+ if (result.metadata_.art_automatic_is_valid()) {
+ return Song::TextForSource(service_->source()) + "/" + result.metadata_.art_automatic().toString();
+ }
+ else if (!result.metadata_.effective_albumartist().isEmpty() && !result.metadata_.album().isEmpty()) {
+ return Song::TextForSource(service_->source()) + "/" + result.metadata_.effective_albumartist() + "/" + result.metadata_.album();
+ }
+ else {
+ return Song::TextForSource(service_->source()) + "/" + result.metadata_.url().toString();
+ }
+
}
bool InternetSearchView::FindCachedPixmap(const InternetSearchView::Result &result, QPixmap *pixmap) const {
return pixmap_cache_.find(result.pixmap_cache_key_, pixmap);
}
-int InternetSearchView::LoadAlbumCoverAsync(const InternetSearchView::Result &result) {
-
- const int id = art_searches_next_id_++;
-
- pending_art_searches_[id] = result.pixmap_cache_key_;
-
- quint64 loader_id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, result.metadata_);
- cover_loader_tasks_[loader_id] = id;
-
- return id;
-
-}
-
void InternetSearchView::LazyLoadAlbumCover(const QModelIndex &proxy_index) {
if (!proxy_index.isValid() || proxy_index.model() != front_proxy_) {
return;
}
+ const QModelIndex source_index = front_proxy_->mapToSource(proxy_index);
+ if (!source_index.isValid()) {
+ return;
+ }
+
// Already loading art for this item?
- if (proxy_index.data(InternetSearchModel::Role_LazyLoadingArt).isValid()) {
+ if (source_index.data(InternetSearchModel::Role_LazyLoadingArt).isValid()) {
return;
}
// Should we even load art at all?
- if (!app_->collection_model()->use_pretty_covers()) {
+ if (!use_pretty_covers_) {
return;
}
// Is this an album?
const CollectionModel::GroupBy container_type = CollectionModel::GroupBy(proxy_index.data(CollectionModel::Role_ContainerType).toInt());
if (container_type != CollectionModel::GroupBy_Album &&
+ container_type != CollectionModel::GroupBy_AlbumDisc &&
container_type != CollectionModel::GroupBy_YearAlbum &&
+ container_type != CollectionModel::GroupBy_YearAlbumDisc &&
container_type != CollectionModel::GroupBy_OriginalYearAlbum) {
return;
}
// Mark the item as loading art
- const QModelIndex source_index = front_proxy_->mapToSource(proxy_index);
- QStandardItem *item = front_model_->itemFromIndex(source_index);
- item->setData(true, InternetSearchModel::Role_LazyLoadingArt);
+
+ QStandardItem *item_album = front_model_->itemFromIndex(source_index);
+ if (!item_album) {
+ return;
+ }
+ item_album->setData(true, InternetSearchModel::Role_LazyLoadingArt);
// Walk down the item's children until we find a track
- while (item->rowCount()) {
- item = item->child(0);
+ QStandardItem *item_song = item_album;
+ while (item_song->rowCount()) {
+ item_song = item_song->child(0);
}
// Get the track's Result
- const InternetSearchView::Result result = item->data(InternetSearchModel::Role_Result).value();
+ const InternetSearchView::Result result = item_song->data(InternetSearchModel::Role_Result).value();
- // Load the art.
- int id = LoadAlbumCoverAsync(result);
- art_requests_[id] = source_index;
-
-}
-
-void InternetSearchView::AlbumCoverLoaded(const quint64 id, const QUrl&, const QImage &image) {
-
- if (!cover_loader_tasks_.contains(id)) return;
- int orig_id = cover_loader_tasks_.take(id);
-
- const QString key = pending_art_searches_.take(orig_id);
-
- QPixmap pixmap = QPixmap::fromImage(image);
- pixmap_cache_.insert(key, pixmap);
-
- if (!art_requests_.contains(id)) return;
- QModelIndex idx = art_requests_.take(id);
-
- if (!pixmap.isNull() && idx.isValid()) {
- front_model_->itemFromIndex(idx)->setData(pixmap, Qt::DecorationRole);
+ QPixmap cached_pixmap;
+ if (pixmap_cache_.find(result.pixmap_cache_key_, &cached_pixmap)) {
+ item_album->setData(cached_pixmap, Qt::DecorationRole);
+ }
+ else {
+ quint64 loader_id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, result.metadata_);
+ cover_loader_tasks_[loader_id] = qMakePair(source_index, result.pixmap_cache_key_);
}
}
-QImage InternetSearchView::ScaleAndPad(const QImage &image) {
+void InternetSearchView::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &albumcover_result) {
- if (image.isNull()) return QImage();
+ if (!cover_loader_tasks_.contains(id)) {
+ return;
+ }
+ QPair cover_loader_task = cover_loader_tasks_.take(id);
+ QModelIndex idx = cover_loader_task.first;
+ QString key = cover_loader_task.second;
- const QSize target_size = QSize(kArtHeight, kArtHeight);
+ QPixmap pixmap = QPixmap::fromImage(albumcover_result.image_scaled);
+ if (!pixmap.isNull()) {
+ pixmap_cache_.insert(key, pixmap);
+ }
- if (image.size() == target_size) return image;
-
- // Scale the image down
- QImage copy;
- copy = image.scaled(target_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
-
- // Pad the image to kHeight x kHeight
- if (copy.size() == target_size) return copy;
-
- QImage padded_image(kArtHeight, kArtHeight, QImage::Format_ARGB32);
- padded_image.fill(0);
-
- QPainter p(&padded_image);
- p.drawImage((kArtHeight - copy.width()) / 2, (kArtHeight - copy.height()) / 2, copy);
- p.end();
-
- return padded_image;
+ if (idx.isValid()) {
+ QStandardItem *item = front_model_->itemFromIndex(idx);
+ if (item) {
+ item->setData(albumcover_result.image_scaled, Qt::DecorationRole);
+ }
+ }
}
diff --git a/src/internet/internetsearchview.h b/src/internet/internetsearchview.h
index 3b7014c15..58735cb49 100644
--- a/src/internet/internetsearchview.h
+++ b/src/internet/internetsearchview.h
@@ -24,10 +24,13 @@
#include "config.h"
+#include
+
#include
#include
#include
#include
+#include
#include
#include
#include
@@ -36,12 +39,12 @@
#include
#include
#include
-#include
#include
#include "core/song.h"
#include "collection/collectionmodel.h"
#include "covermanager/albumcoverloaderoptions.h"
+#include "covermanager/albumcoverloaderresult.h"
#include "settings/settingsdialog.h"
class QSortFilterProxyModel;
@@ -53,14 +56,12 @@ class QActionGroup;
class QEvent;
class QKeyEvent;
class QShowEvent;
-class QHideEvent;
class QContextMenuEvent;
class QTimerEvent;
class Application;
class MimeData;
class GroupByDialog;
-class AlbumCoverLoader;
class InternetService;
class InternetSearchModel;
class Ui_InternetSearchView;
@@ -104,7 +105,6 @@ class InternetSearchView : public QWidget {
};
void showEvent(QShowEvent *e);
- void hideEvent(QHideEvent *e);
bool eventFilter(QObject *object, QEvent *e);
void timerEvent(QTimerEvent *e);
@@ -135,7 +135,6 @@ class InternetSearchView : public QWidget {
QString PixmapCacheKey(const Result &result) const;
bool FindCachedPixmap(const Result &result, QPixmap *pixmap) const;
- static QImage ScaleAndPad(const QImage &image);
int LoadAlbumCoverAsync(const Result &result);
signals:
@@ -173,7 +172,7 @@ class InternetSearchView : public QWidget {
void GroupByClicked(QAction *action);
void SetGroupBy(const CollectionModel::Grouping &g);
- void AlbumCoverLoaded(const quint64 id, const QUrl&, const QImage &image);
+ void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &albumcover_result);
public slots:
void ReloadSettings();
@@ -187,7 +186,7 @@ class InternetSearchView : public QWidget {
Application *app_;
InternetService *service_;
Ui_InternetSearchView *ui_;
- QScopedPointer group_by_dialog_;
+ std::unique_ptr group_by_dialog_;
QMenu *context_menu_;
QList context_actions_;
@@ -206,18 +205,17 @@ class InternetSearchView : public QWidget {
QTimer *swap_models_timer_;
+ bool use_pretty_covers_;
SearchType search_type_;
bool search_error_;
int last_search_id_;
int searches_next_id_;
- int art_searches_next_id_;
QMap delayed_searches_;
QMap pending_searches_;
- QMap pending_art_searches_;
- QMap art_requests_;
+
AlbumCoverLoaderOptions cover_loader_options_;
- QMap cover_loader_tasks_;
+ QMap> cover_loader_tasks_;
QPixmapCache pixmap_cache_;
};
diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp
index 0d3c6e481..a67812181 100644
--- a/src/playlist/playlist.cpp
+++ b/src/playlist/playlist.cpp
@@ -69,6 +69,7 @@
#include "collection/collection.h"
#include "collection/collectionbackend.h"
#include "collection/collectionplaylistitem.h"
+#include "covermanager/albumcoverloader.h"
#include "queue/queue.h"
#include "playlist.h"
#include "playlistitem.h"
@@ -88,12 +89,6 @@
using std::placeholders::_1;
using std::placeholders::_2;
-using std::shared_ptr;
-using std::unordered_map;
-using std::sort;
-using std::stable_sort;
-using std::greater;
-using std::swap;
const char *Playlist::kCddaMimeType = "x-content/audio-cdda";
const char *Playlist::kRowsMimetype = "application/x-strawberry-playlist-rows";
@@ -256,6 +251,7 @@ bool Playlist::set_column_value(Song &song, Playlist::Column column, const QVari
default:
break;
}
+
return true;
}
@@ -392,7 +388,9 @@ void Playlist::SongSaveComplete(TagReaderReply *reply, const QPersistentModelInd
if (reply->is_successful() && index.isValid()) {
if (reply->message().save_file_response().success()) {
- QFuture future = item_at(index.row())->BackgroundReload();
+ PlaylistItemPtr item = item_at(index.row());
+ if (!item) return;
+ QFuture future = item->BackgroundReload();
NewClosure(future, this, SLOT(ItemReloadComplete(QPersistentModelIndex)), index);
}
else {
@@ -406,6 +404,20 @@ void Playlist::SongSaveComplete(TagReaderReply *reply, const QPersistentModelInd
void Playlist::ItemReloadComplete(const QPersistentModelIndex &index) {
if (index.isValid()) {
+
+ PlaylistItemPtr item = item_at(index.row());
+ if (item) {
+
+ // Update temporary metadata for songs that are not in the collection.
+ // Songs that are in the collection is updated through the collection watcher/backend in playlist manager.
+ if (item->Metadata().source() != Song::Source_Collection) {
+ SongPlaylistItem *song_item = static_cast(item.get());
+ if (song_item) {
+ song_item->UpdateTemporaryMetadata(song_item->DatabaseSongMetadata());
+ }
+ }
+ }
+
emit dataChanged(index, index);
emit EditingFinished(index);
}
@@ -879,7 +891,7 @@ void Playlist::InsertItems(const PlaylistItemList &itemsIn, int pos, bool play_n
// exercise vetoes
SongList songs;
- for (const PlaylistItemPtr item : items) {
+ for (PlaylistItemPtr item : items) {
songs << item->Metadata();
}
@@ -1004,7 +1016,7 @@ void Playlist::InsertInternetItems(InternetService *service, const SongList &son
PlaylistItemList playlist_items;
for (const Song &song : songs) {
- playlist_items << shared_ptr(new InternetPlaylistItem(service, song));
+ playlist_items << std::shared_ptr(new InternetPlaylistItem(service, song));
}
InsertItems(playlist_items, pos, play_now, enqueue, enqueue_next);
@@ -1014,6 +1026,7 @@ void Playlist::InsertInternetItems(InternetService *service, const SongList &son
void Playlist::UpdateItems(const SongList &songs) {
qLog(Debug) << "Updating playlist with new tracks' info";
+
// We first convert our songs list into a linked list (a 'real' list), because removals are faster with QLinkedList.
// Next, we walk through the list of playlist's items then the list of songs
// we want to update: if an item corresponds to the song (we rely on URL for this), we update the item with the new metadata,
@@ -1027,20 +1040,12 @@ void Playlist::UpdateItems(const SongList &songs) {
QMutableLinkedListIterator it(songs_list);
while (it.hasNext()) {
const Song &song = it.next();
- PlaylistItemPtr &item = items_[i];
- if (item->Metadata().url() == song.url() &&
- (
- item->Metadata().source() == Song::Source_Unknown ||
- item->Metadata().filetype() == Song::FileType_Unknown ||
- // Stream may change and may need to be updated too
- item->Metadata().is_stream() ||
- // And CD tracks as well (tags are loaded in a second step)
- item->Metadata().is_cdda()
- )
- ) {
+ const PlaylistItemPtr &item = items_[i];
+ if (item->Metadata().url() == song.url()) {
PlaylistItemPtr new_item;
if (song.is_collection_song()) {
new_item = PlaylistItemPtr(new CollectionPlaylistItem(song));
+ if (collection_items_by_id_.contains(song.id(), item)) collection_items_by_id_.remove(song.id(), item);
collection_items_by_id_.insertMulti(song.id(), new_item);
}
else {
@@ -1103,10 +1108,10 @@ QMimeData *Playlist::mimeData(const QModelIndexList &indexes) const {
}
-bool Playlist::CompareItems(int column, Qt::SortOrder order, shared_ptr _a, shared_ptr _b) {
+bool Playlist::CompareItems(int column, Qt::SortOrder order, std::shared_ptr _a, std::shared_ptr _b) {
- shared_ptr a = order == Qt::AscendingOrder ? _a : _b;
- shared_ptr b = order == Qt::AscendingOrder ? _b : _a;
+ std::shared_ptr a = order == Qt::AscendingOrder ? _a : _b;
+ std::shared_ptr b = order == Qt::AscendingOrder ? _b : _a;
#define cmp(field) return a->Metadata().field() < b->Metadata().field()
#define strcmp(field) return QString::localeAwareCompare(a->Metadata().field().toLower(), b->Metadata().field().toLower()) < 0;
@@ -1154,10 +1159,10 @@ bool Playlist::CompareItems(int column, Qt::SortOrder order, shared_ptr _a, shared_ptr _b) {
+bool Playlist::ComparePathDepths(Qt::SortOrder order, std::shared_ptr _a, std::shared_ptr _b) {
- shared_ptr a = order == Qt::AscendingOrder ? _a : _b;
- shared_ptr b = order == Qt::AscendingOrder ? _b : _a;
+ std::shared_ptr a = order == Qt::AscendingOrder ? _a : _b;
+ std::shared_ptr b = order == Qt::AscendingOrder ? _b : _a;
int a_dir_level = a->Url().path().count('/');
int b_dir_level = b->Url().path().count('/');
@@ -1293,6 +1298,7 @@ void Playlist::SetCurrentIsPaused(bool paused) {
}
void Playlist::Save() const {
+
if (!backend_ || is_loading_) return;
backend_->SavePlaylistAsync(id_, items_, last_played_row());
@@ -1443,7 +1449,7 @@ PlaylistItemList Playlist::RemoveItemsWithoutUndo(int row, int count) {
if (item->source() == Song::Source_Collection) {
int id = item->Metadata().id();
- if (id != -1) {
+ if (id != -1 && collection_items_by_id_.contains(id, item)) {
collection_items_by_id_.remove(id, item);
}
}
@@ -1755,22 +1761,26 @@ void Playlist::set_sequence(PlaylistSequence *v) {
QSortFilterProxyModel *Playlist::proxy() const { return proxy_; }
SongList Playlist::GetAllSongs() const {
+
SongList ret;
- for (const PlaylistItemPtr item : items_) {
+ for (PlaylistItemPtr item : items_) {
ret << item->Metadata();
}
return ret;
+
}
PlaylistItemList Playlist::GetAllItems() const { return items_; }
quint64 Playlist::GetTotalLength() const {
+
quint64 ret = 0;
- for (const PlaylistItemPtr item : items_) {
+ for (PlaylistItemPtr item : items_) {
quint64 length = item->Metadata().length_nanosec();
if (length > 0) ret += length;
}
return ret;
+
}
PlaylistItemList Playlist::collection_items_by_id(int id) const {
@@ -1778,30 +1788,38 @@ PlaylistItemList Playlist::collection_items_by_id(int id) const {
}
void Playlist::TracksAboutToBeDequeued(const QModelIndex&, int begin, int end) {
+
for (int i = begin; i <= end; ++i) {
temp_dequeue_change_indexes_ << queue_->mapToSource(queue_->index(i, Column_Title));
}
+
}
void Playlist::TracksDequeued() {
+
for (const QModelIndex &index : temp_dequeue_change_indexes_) {
emit dataChanged(index, index);
}
temp_dequeue_change_indexes_.clear();
emit QueueChanged();
+
}
void Playlist::TracksEnqueued(const QModelIndex&, int begin, int end) {
+
const QModelIndex &b = queue_->mapToSource(queue_->index(begin, Column_Title));
const QModelIndex &e = queue_->mapToSource(queue_->index(end, Column_Title));
emit dataChanged(b, e);
+
}
void Playlist::QueueLayoutChanged() {
+
for (int i = 0; i < queue_->rowCount(); ++i) {
const QModelIndex &index = queue_->mapToSource(queue_->index(i, Column_Title));
emit dataChanged(index, index);
}
+
}
void Playlist::ItemChanged(PlaylistItemPtr item) {
@@ -1858,6 +1876,7 @@ void Playlist::InvalidateDeletedSongs() {
}
void Playlist::RemoveDeletedSongs() {
+
QList rows_to_remove;
for (int row = 0; row < items_.count(); ++row) {
@@ -1892,7 +1911,7 @@ struct SongSimilarEqual {
void Playlist::RemoveDuplicateSongs() {
QList rows_to_remove;
- unordered_map unique_songs;
+ std::unordered_map unique_songs;
for (int row = 0; row < items_.count(); ++row) {
PlaylistItemPtr item = items_[row];
@@ -2007,3 +2026,16 @@ void Playlist::UpdateScrobblePoint(const qint64 seek_point_nanosec) {
}
+void Playlist::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) {
+
+ // Update art_manual for local songs that are not in the collection.
+ if (result.type == AlbumCoverLoaderResult::Type_Manual && result.cover_url.isLocalFile() && (song.source() == Song::Source_LocalFile || song.source() == Song::Source_CDDA || song.source() == Song::Source_Device)) {
+ PlaylistItemPtr item = current_item();
+ if (item && item->Metadata() == song && !item->Metadata().art_manual_is_valid()) {
+ qLog(Debug) << "Updating art manual for local song" << song.title() << song.album() << song.title() << "to" << result.cover_url << "in playlist.";
+ item->SetArtManual(result.cover_url);
+ Save();
+ }
+ }
+
+}
diff --git a/src/playlist/playlist.h b/src/playlist/playlist.h
index 01c7c061f..f364752e3 100644
--- a/src/playlist/playlist.h
+++ b/src/playlist/playlist.h
@@ -42,6 +42,7 @@
#include "core/song.h"
#include "core/tagreaderclient.h"
+#include "covermanager/albumcoverloaderresult.h"
#include "playlistitem.h"
#include "playlistsequence.h"
@@ -348,6 +349,7 @@ class Playlist : public QAbstractListModel {
void ItemReloadComplete(const QPersistentModelIndex &index);
void ItemsLoaded(QFuture future);
void SongInsertVetoListenerDestroyed();
+ void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result);
private:
bool is_loading_;
@@ -364,8 +366,10 @@ class Playlist : public QAbstractListModel {
bool favorite_;
PlaylistItemList items_;
+
// Contains the indices into items_ in the order that they will be played.
QList virtual_items_;
+
// A map of collection ID to playlist item - for fast lookups when collection items change.
QMultiMap collection_items_by_id_;
diff --git a/src/playlist/playlistitem.cpp b/src/playlist/playlistitem.cpp
index 9c48e31cd..04958ab44 100644
--- a/src/playlist/playlistitem.cpp
+++ b/src/playlist/playlistitem.cpp
@@ -43,21 +43,21 @@ PlaylistItem::~PlaylistItem() {}
PlaylistItem *PlaylistItem::NewFromSource(const Song::Source &source) {
switch (source) {
- case Song::Source_Collection: return new CollectionPlaylistItem();
+ case Song::Source_Collection:
+ return new CollectionPlaylistItem();
+ case Song::Source_Subsonic:
case Song::Source_Tidal:
- case Song::Source_Stream: return new InternetPlaylistItem(source);
- default: return new SongPlaylistItem(source);
+ case Song::Source_Qobuz:
+ case Song::Source_Stream:
+ return new InternetPlaylistItem(source);
+ case Song::Source_LocalFile:
+ case Song::Source_CDDA:
+ case Song::Source_Device:
+ case Song::Source_Unknown:
+ break;
}
-}
-
-PlaylistItem *PlaylistItem::NewFromSongsTable(const QString &table, const Song &song) {
-
- if (table == SCollection::kSongsTable)
- return new CollectionPlaylistItem(song);
-
- qLog(Warning) << "Invalid PlaylistItem songs table:" << table;
- return nullptr;
+ return new SongPlaylistItem(source);
}
diff --git a/src/playlist/playlistitem.h b/src/playlist/playlistitem.h
index 9feebc1c1..075c4984c 100644
--- a/src/playlist/playlistitem.h
+++ b/src/playlist/playlistitem.h
@@ -49,7 +49,6 @@ class PlaylistItem : public std::enable_shared_from_this {
virtual ~PlaylistItem();
static PlaylistItem *NewFromSource(const Song::Source &source);
- static PlaylistItem *NewFromSongsTable(const QString &table, const Song &song);
enum Option {
Default = 0x00,
@@ -87,6 +86,8 @@ class PlaylistItem : public std::enable_shared_from_this {
qint64 effective_beginning_nanosec() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() && temp_metadata_.beginning_nanosec() != -1 ? temp_metadata_.beginning_nanosec() : Metadata().beginning_nanosec(); }
qint64 effective_end_nanosec() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() && temp_metadata_.end_nanosec() != -1 ? temp_metadata_.end_nanosec() : Metadata().end_nanosec(); }
+ virtual void SetArtManual(const QUrl &cover_url) = 0;
+
// Background colors.
void SetBackgroundColor(short priority, const QColor &color);
bool HasBackgroundColor(short priority) const;
diff --git a/src/playlist/playlistmanager.cpp b/src/playlist/playlistmanager.cpp
index 7cf2e409e..0321fb66e 100644
--- a/src/playlist/playlistmanager.cpp
+++ b/src/playlist/playlistmanager.cpp
@@ -48,6 +48,8 @@
#include "core/utilities.h"
#include "collection/collectionbackend.h"
#include "collection/collectionplaylistitem.h"
+#include "covermanager/albumcoverloaderresult.h"
+#include "covermanager/currentalbumcoverloader.h"
#include "playlist.h"
#include "playlistbackend.h"
#include "playlistcontainer.h"
@@ -147,6 +149,7 @@ Playlist *PlaylistManager::AddPlaylist(int id, const QString &name, const QStrin
connect(ret, SIGNAL(Error(QString)), SIGNAL(Error(QString)));
connect(ret, SIGNAL(PlayRequested(QModelIndex)), SIGNAL(PlayRequested(QModelIndex)));
connect(playlist_container_->view(), SIGNAL(ColumnAlignmentChanged(ColumnAlignmentMap)), ret, SLOT(SetColumnAlignment(ColumnAlignmentMap)));
+ connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)), ret, SLOT(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)));
playlists_[id] = Data(ret, name);
@@ -440,7 +443,8 @@ void PlaylistManager::UpdateSummaryText() {
QString summary;
if (selected > 1) {
summary += tr("%1 selected of").arg(selected) + " ";
- } else {
+ }
+ else {
nanoseconds = current()->GetTotalLength();
}
@@ -466,7 +470,7 @@ void PlaylistManager::SongsDiscovered(const SongList &songs) {
for (const Song &song : songs) {
for (const Data &data : playlists_) {
PlaylistItemList items = data.p->collection_items_by_id(song.id());
- for (const PlaylistItemPtr item : items) {
+ for (PlaylistItemPtr item : items) {
if (item->Metadata().directory_id() != song.directory_id()) continue;
static_cast(item.get())->SetMetadata(song);
item->UpdateTemporaryMetadata(song);
diff --git a/src/playlist/playlistview.cpp b/src/playlist/playlistview.cpp
index 52f36b09b..4858596fd 100644
--- a/src/playlist/playlistview.cpp
+++ b/src/playlist/playlistview.cpp
@@ -75,6 +75,7 @@
#include "playlistheader.h"
#include "playlistview.h"
#include "covermanager/currentalbumcoverloader.h"
+#include "covermanager/albumcoverloaderresult.h"
#include "settings/appearancesettingspage.h"
#include "settings/playlistsettingspage.h"
@@ -82,8 +83,6 @@
# include "moodbar/moodbaritemdelegate.h"
#endif
-using std::sort;
-
const int PlaylistView::kGlowIntensitySteps = 24;
const int PlaylistView::kAutoscrollGraceTimeout = 30; // seconds
const int PlaylistView::kDropIndicatorWidth = 2;
@@ -221,7 +220,7 @@ void PlaylistView::SetApplication(Application *app) {
Q_ASSERT(app);
app_ = app;
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), this, SLOT(SongChanged(Song)));
- connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, QUrl, QImage)), SLOT(AlbumCoverLoaded(Song, QUrl, QImage)));
+ connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)));
connect(app_->player(), SIGNAL(Playing()), SLOT(StartGlowing()));
connect(app_->player(), SIGNAL(Paused()), SLOT(StopGlowing()));
connect(app_->player(), SIGNAL(Stopped()), SLOT(Stopped()));
@@ -1261,17 +1260,15 @@ void PlaylistView::Stopped() {
if (song_playing_ == Song()) return;
song_playing_ = Song();
StopGlowing();
- AlbumCoverLoaded(Song(), QUrl(), QImage());
+ AlbumCoverLoaded(Song());
}
-void PlaylistView::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &song_art) {
+void PlaylistView::AlbumCoverLoaded(const Song &song, AlbumCoverLoaderResult result) {
- Q_UNUSED(cover_url);
+ if ((song != Song() && song_playing_ == Song()) || result.image_original == current_song_cover_art_) return;
- if ((song != Song() && song_playing_ == Song()) || song_art == current_song_cover_art_) return;
-
- current_song_cover_art_ = song_art;
+ current_song_cover_art_ = result.image_original;
if (background_image_type_ == AppearanceSettingsPage::BackgroundImageType_Album) {
if (song.art_automatic().isEmpty() && song.art_manual().isEmpty()) {
set_background_image(QImage());
diff --git a/src/playlist/playlistview.h b/src/playlist/playlistview.h
index 79a7aa7ca..c05d9f9ed 100644
--- a/src/playlist/playlistview.h
+++ b/src/playlist/playlistview.h
@@ -47,6 +47,7 @@
#include
#include "core/song.h"
+#include "covermanager/albumcoverloaderresult.h"
#include "settings/appearancesettingspage.h"
#include "playlist.h"
@@ -173,7 +174,7 @@ class PlaylistView : public QTreeView {
void Playing();
void Stopped();
void SongChanged(const Song &song);
- void AlbumCoverLoaded(const Song &new_song, const QUrl &cover_url, const QImage &song_art);
+ void AlbumCoverLoaded(const Song &song, AlbumCoverLoaderResult result = AlbumCoverLoaderResult());
private:
void LoadGeometry();
diff --git a/src/playlist/songplaylistitem.cpp b/src/playlist/songplaylistitem.cpp
index 50e841919..b5e2e6dfe 100644
--- a/src/playlist/songplaylistitem.cpp
+++ b/src/playlist/songplaylistitem.cpp
@@ -48,3 +48,10 @@ Song SongPlaylistItem::Metadata() const {
if (HasTemporaryMetadata()) return temp_metadata_;
return song_;
}
+
+void SongPlaylistItem::SetArtManual(const QUrl &cover_url) {
+
+ song_.set_art_manual(cover_url);
+ temp_metadata_.set_art_manual(cover_url);
+
+}
diff --git a/src/playlist/songplaylistitem.h b/src/playlist/songplaylistitem.h
index 9260f1151..c9f863749 100644
--- a/src/playlist/songplaylistitem.h
+++ b/src/playlist/songplaylistitem.h
@@ -44,8 +44,8 @@ class SongPlaylistItem : public PlaylistItem {
QUrl Url() const;
- protected:
Song DatabaseSongMetadata() const { return song_; }
+ void SetArtManual(const QUrl &cover_url);
private:
Song song_;
diff --git a/src/widgets/playingwidget.cpp b/src/widgets/playingwidget.cpp
index b4570354a..36cf46cb9 100644
--- a/src/widgets/playingwidget.cpp
+++ b/src/widgets/playingwidget.cpp
@@ -48,8 +48,6 @@
#include "covermanager/albumcoverloader.h"
#include "playingwidget.h"
-using std::unique_ptr;
-
const char *PlayingWidget::kSettingsGroup = "PlayingWidget";
// Space between the cover and the details in small mode
@@ -269,9 +267,7 @@ void PlayingWidget::SongChanged(const Song &song) {
song_ = song;
}
-void PlayingWidget::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image) {
-
- Q_UNUSED(cover_url);
+void PlayingWidget::AlbumCoverLoaded(const Song &song, const QImage &image) {
if (!playing_ || song != song_playing_ || (timeline_fade_->state() == QTimeLine::Running && image == image_original_)) return;
@@ -313,7 +309,7 @@ void PlayingWidget::SetImage(const QImage &image) {
}
void PlayingWidget::ScaleCover() {
- pixmap_cover_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_));
+ pixmap_cover_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_).first);
update();
}
diff --git a/src/widgets/playingwidget.h b/src/widgets/playingwidget.h
index f0d5b28d4..c97c3b2a1 100644
--- a/src/widgets/playingwidget.h
+++ b/src/widgets/playingwidget.h
@@ -40,8 +40,6 @@
#include "core/song.h"
#include "covermanager/albumcoverloaderoptions.h"
-using std::unique_ptr;
-
class QTimeLine;
class QTextDocument;
class QPainter;
@@ -98,7 +96,7 @@ class PlayingWidget : public QWidget {
void AutomaticCoverSearchDone();
- void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image);
+ void AlbumCoverLoaded(const Song &song, const QImage &image);
void SetHeight(int height);
void FadePreviousTrack(qreal value);