1
0
mirror of https://github.com/strawberrymusicplayer/strawberry synced 2024-12-18 11:39:31 +01:00

Improve album cover loader, lyrics search and streaming support

- Improve album cover loader
- Add album cover loader result struct
- Move album cover thumbnail scaling to album cover loader
- Make init art manual look for album cover images in song directory
- Make album cover search work for songs outside of collection and
  streams
- Make album cover search work based on artist + title if album is not
  present
- Update art manual in playlist for local files, devices and CDDA
- Make lyrics search work for streams
- Add stream dialog to menu
- Remove dead code in InternetSearchModel
- Simplify code in InternetSearchView
This commit is contained in:
Jonas Kvinge 2020-04-20 18:03:18 +02:00
parent ab2ffd9ac1
commit a2c0e4d4b1
77 changed files with 1057 additions and 584 deletions

View File

@ -203,6 +203,7 @@
<file>icons/48x48/document-download.png</file>
<file>icons/48x48/document-new.png</file>
<file>icons/48x48/document-open-folder.png</file>
<file>icons/48x48/document-open-remote.png</file>
<file>icons/48x48/document-open.png</file>
<file>icons/48x48/document-save.png</file>
<file>icons/48x48/document-search.png</file>
@ -297,6 +298,7 @@
<file>icons/32x32/document-download.png</file>
<file>icons/32x32/document-new.png</file>
<file>icons/32x32/document-open-folder.png</file>
<file>icons/32x32/document-open-remote.png</file>
<file>icons/32x32/document-open.png</file>
<file>icons/32x32/document-save.png</file>
<file>icons/32x32/document-search.png</file>
@ -391,6 +393,7 @@
<file>icons/22x22/document-download.png</file>
<file>icons/22x22/document-new.png</file>
<file>icons/22x22/document-open-folder.png</file>
<file>icons/22x22/document-open-remote.png</file>
<file>icons/22x22/document-open.png</file>
<file>icons/22x22/document-save.png</file>
<file>icons/22x22/document-search.png</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

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

View File

@ -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<CollectionModel::Grouping>();
model_->SetGroupBy(g);
}
void CollectionFilterWidget::GroupingChanged(const CollectionModel::Grouping &g) {

View File

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

View File

@ -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<CollectionModel::QueryResult> 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.

View File

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

View File

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

View File

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

View File

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

View File

@ -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<CollectionItem> {
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);

View File

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

View File

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

View File

@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
* Copyright 2013-2020, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -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<QUrl>() << 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_);

View File

@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
* Copyright 2013-2020, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -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<TranscodeDialog> transcode_dialog_;
#endif
Lazy<AddStreamDialog> add_stream_dialog_;
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
std::unique_ptr<TagFetcher> tag_fetcher_;

View File

@ -469,6 +469,7 @@
</property>
<addaction name="action_add_file"/>
<addaction name="action_add_folder"/>
<addaction name="action_add_stream"/>
<addaction name="separator"/>
<addaction name="action_shuffle_mode"/>
<addaction name="action_repeat_mode"/>
@ -827,6 +828,11 @@
<string>Rescan songs(s)</string>
</property>
</action>
<action name="action_add_stream">
<property name="text">
<string>Add stream...</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>

View File

@ -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<PlaylistItemPtr> >("QList<PlaylistItemPtr>");
qRegisterMetaType<PlaylistSequence::RepeatMode>("PlaylistSequence::RepeatMode");
qRegisterMetaType<PlaylistSequence::ShuffleMode>("PlaylistSequence::ShuffleMode");
qRegisterMetaType<AlbumCoverLoaderResult>("AlbumCoverLoaderResult");
qRegisterMetaType<AlbumCoverLoaderResult::Type>("AlbumCoverLoaderResult::Type");
qRegisterMetaType<CoverSearchResult>("CoverSearchResult");
qRegisterMetaType<QList<CoverSearchResult> >("QList<CoverSearchResult>");
qRegisterMetaType<CoverSearchResults>("CoverSearchResults");

View File

@ -58,14 +58,13 @@
#include "playlist/playlistmanager.h"
#include "playlist/playlistsequence.h"
#include "covermanager/currentalbumcoverloader.h"
#include "covermanager/albumcoverloaderresult.h"
#include <core/mpris2_player.h>
#include <core/mpris2_playlists.h>
#include <core/mpris2_root.h>
#include <core/mpris2_tracklist.h>
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_);

View File

@ -39,6 +39,7 @@
#include <QJsonObject>
#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();

View File

@ -30,6 +30,7 @@
#include <QObject>
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <QSharedData>
#include <QHash>
#include <QByteArray>
@ -42,6 +43,7 @@
#include <QIcon>
#include <QTextCodec>
#include <QSqlQuery>
#include <QStandardPaths>
#include <QtDebug>
#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());
}
}
}
}

View File

@ -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<Song> *songs);

View File

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

View File

@ -26,9 +26,11 @@
#include <QtGlobal>
#include <QObject>
#include <QMap>
#include <QUrl>
#include <QImage>
#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_;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<quint64> &ids) {
@ -228,21 +220,25 @@ void AlbumCoverLoader::CancelTasks(const QSet<quint64> &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<QImage, QImage> 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<QImage, QImage> 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<QImage, QImage> 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<QImage, QImage> 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);
}

View File

@ -27,6 +27,7 @@
#include <QtGlobal>
#include <QObject>
#include <QMutex>
#include <QPair>
#include <QSet>
#include <QMap>
#include <QQueue>
@ -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<quint64> &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<QImage, QImage> 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_;

View File

@ -24,17 +24,24 @@
#include "config.h"
#include <QImage>
#include <QSize>
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

View File

@ -0,0 +1,52 @@
/*
* Strawberry Music Player
* Copyright 2020, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ALBUMCOVERLOADERRESULT_H
#define ALBUMCOVERLOADERRESULT_H
#include "config.h"
#include <QImage>
#include <QUrl>
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019-2020, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -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);
}

View File

@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019-2020, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -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_;

View File

@ -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<QString, QString> Param;
typedef QList<Param> Params;
typedef QPair<QByteArray, QByteArray> 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);

View File

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

View File

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

View File

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

View File

@ -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<QString, QString> Param;
typedef QPair<QByteArray, QByteArray> EncodedParam;

View File

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

View File

@ -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('"', "\\\""));

View File

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

View File

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

View File

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

View File

@ -0,0 +1,54 @@
/*
* Strawberry Music Player
* Copyright 2020, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "addstreamdialog.h"
#include "ui_addstreamdialog.h"
#include <QSettings>
#include <QUrl>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QDialogButtonBox>
#include <QShowEvent>
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());
}

View File

@ -0,0 +1,51 @@
/*
* Strawberry Music Player
* Copyright 2020, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ADDSTREAMDIALOG_H
#define ADDSTREAMDIALOG_H
#include <QDialog>
#include <QString>
#include <QUrl>
#include <QLineEdit>
#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

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AddStreamDialog</class>
<widget class="QDialog" name="AddStreamDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>120</height>
</rect>
</property>
<property name="windowTitle">
<string>Add Stream</string>
</property>
<property name="windowIcon">
<iconset resource="../../data/icons.qrc">
<normaloff>:/icons/48x48/document-open-remote.png</normaloff>:/icons/48x48/document-open-remote.png</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Enter the URL of a stream:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="url"/>
</item>
<item>
<spacer name="spacer_bottom">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="button_box">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>url</tabstop>
<tabstop>button_box</tabstop>
</tabstops>
<resources>
<include location="../../data/data.qrc"/>
<include location="../../data/icons.qrc"/>
</resources>
<connections>
<connection>
<sender>button_box</sender>
<signal>accepted()</signal>
<receiver>AddStreamDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>257</x>
<y>158</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>167</y>
</hint>
</hints>
</connection>
<connection>
<sender>button_box</sender>
<signal>rejected()</signal>
<receiver>AddStreamDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>325</x>
<y>158</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>167</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<InternetSearchView*>(view_)->LazyLoadAlbumCover(index);
const_cast<InternetSearchView*>(view_)->LazyLoadAlbumCover(idx);
CollectionItemDelegate::paint(painter, option, index);
CollectionItemDelegate::paint(painter, option, idx);
}

View File

@ -2,6 +2,7 @@
* Strawberry Music Player
* This code was part of Clementine (GlobalSearch)
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -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<InternetSearchView::Result>());
}
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<QUrl> 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<QUrl> urls;
for (const InternetSearchView::Result &result : results) {
urls << result.metadata_.url();
}
mime_data->setUrls(urls);
return mime_data;

View File

@ -2,6 +2,7 @@
* Strawberry Music Player
* This code was part of Clementine (GlobalSearch)
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -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<const QStandardItem*> *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]);

View File

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

View File

@ -21,6 +21,7 @@
#include "config.h"
#include <memory>
#include <utility>
#include <QtGlobal>
@ -32,6 +33,7 @@
#include <QApplication>
#include <QWidget>
#include <QTimer>
#include <QPair>
#include <QList>
#include <QMap>
#include <QVariant>
@ -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<InternetSearchView::Result>();
const InternetSearchView::Result result = item_song->data(InternetSearchModel::Role_Result).value<InternetSearchView::Result>();
// 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<QModelIndex, QString> 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);
}
}
}

View File

@ -24,10 +24,13 @@
#include "config.h"
#include <memory>
#include <QtGlobal>
#include <QObject>
#include <QWidget>
#include <QSet>
#include <QPair>
#include <QList>
#include <QMap>
#include <QString>
@ -36,12 +39,12 @@
#include <QImage>
#include <QPixmap>
#include <QPixmapCache>
#include <QScopedPointer>
#include <QMetaType>
#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<GroupByDialog> group_by_dialog_;
std::unique_ptr<GroupByDialog> group_by_dialog_;
QMenu *context_menu_;
QList<QAction*> 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<int, DelayedSearch> delayed_searches_;
QMap<int, PendingState> pending_searches_;
QMap<int, QString> pending_art_searches_;
QMap<int, QModelIndex> art_requests_;
AlbumCoverLoaderOptions cover_loader_options_;
QMap<quint64, quint64> cover_loader_tasks_;
QMap<quint64, QPair<QModelIndex, QString>> cover_loader_tasks_;
QPixmapCache pixmap_cache_;
};

View File

@ -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<void> future = item_at(index.row())->BackgroundReload();
PlaylistItemPtr item = item_at(index.row());
if (!item) return;
QFuture<void> 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<SongPlaylistItem*>(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<PlaylistItem>(new InternetPlaylistItem(service, song));
playlist_items << std::shared_ptr<PlaylistItem>(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<Song> 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<PlaylistItem> _a, shared_ptr<PlaylistItem> _b) {
bool Playlist::CompareItems(int column, Qt::SortOrder order, std::shared_ptr<PlaylistItem> _a, std::shared_ptr<PlaylistItem> _b) {
shared_ptr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
shared_ptr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
std::shared_ptr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
std::shared_ptr<PlaylistItem> 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<Playlist
}
bool Playlist::ComparePathDepths(Qt::SortOrder order, shared_ptr<PlaylistItem> _a, shared_ptr<PlaylistItem> _b) {
bool Playlist::ComparePathDepths(Qt::SortOrder order, std::shared_ptr<PlaylistItem> _a, std::shared_ptr<PlaylistItem> _b) {
shared_ptr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
shared_ptr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
std::shared_ptr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
std::shared_ptr<PlaylistItem> 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<int> rows_to_remove;
for (int row = 0; row < items_.count(); ++row) {
@ -1892,7 +1911,7 @@ struct SongSimilarEqual {
void Playlist::RemoveDuplicateSongs() {
QList<int> rows_to_remove;
unordered_map<Song, int, SongSimilarHash, SongSimilarEqual> unique_songs;
std::unordered_map<Song, int, SongSimilarHash, SongSimilarEqual> 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();
}
}
}

View File

@ -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<PlaylistItemList> 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<int> virtual_items_;
// A map of collection ID to playlist item - for fast lookups when collection items change.
QMultiMap<int, PlaylistItemPtr> collection_items_by_id_;

View File

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

View File

@ -49,7 +49,6 @@ class PlaylistItem : public std::enable_shared_from_this<PlaylistItem> {
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<PlaylistItem> {
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;

View File

@ -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<CollectionPlaylistItem*>(item.get())->SetMetadata(song);
item->UpdateTemporaryMetadata(song);

View File

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

View File

@ -47,6 +47,7 @@
#include <QCommonStyle>
#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();

View File

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

View File

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

View File

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

View File

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