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:
parent
ab2ffd9ac1
commit
a2c0e4d4b1
@ -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>
|
||||
|
BIN
data/icons/22x22/document-open-remote.png
Normal file
BIN
data/icons/22x22/document-open-remote.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
data/icons/32x32/document-open-remote.png
Normal file
BIN
data/icons/32x32/document-open-remote.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
BIN
data/icons/48x48/document-open-remote.png
Normal file
BIN
data/icons/48x48/document-open-remote.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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); }
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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_;
|
||||
|
@ -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_);
|
||||
|
@ -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_;
|
||||
|
@ -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>
|
||||
|
@ -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");
|
||||
|
@ -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_);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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_;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
@ -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_;
|
||||
|
||||
|
@ -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
|
||||
|
52
src/covermanager/albumcoverloaderresult.h
Normal file
52
src/covermanager/albumcoverloaderresult.h
Normal 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
|
@ -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_++;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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) {}
|
||||
|
@ -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_;
|
||||
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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_;
|
||||
|
@ -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);
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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('"', "\\\""));
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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:
|
||||
|
54
src/dialogs/addstreamdialog.cpp
Normal file
54
src/dialogs/addstreamdialog.cpp
Normal 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());
|
||||
|
||||
}
|
51
src/dialogs/addstreamdialog.h
Normal file
51
src/dialogs/addstreamdialog.h
Normal 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
|
98
src/dialogs/addstreamdialog.ui
Normal file
98
src/dialogs/addstreamdialog.ui
Normal 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>
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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]);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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_;
|
||||
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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_;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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_;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user