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:
@@ -203,6 +203,7 @@
|
|||||||
<file>icons/48x48/document-download.png</file>
|
<file>icons/48x48/document-download.png</file>
|
||||||
<file>icons/48x48/document-new.png</file>
|
<file>icons/48x48/document-new.png</file>
|
||||||
<file>icons/48x48/document-open-folder.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-open.png</file>
|
||||||
<file>icons/48x48/document-save.png</file>
|
<file>icons/48x48/document-save.png</file>
|
||||||
<file>icons/48x48/document-search.png</file>
|
<file>icons/48x48/document-search.png</file>
|
||||||
@@ -297,6 +298,7 @@
|
|||||||
<file>icons/32x32/document-download.png</file>
|
<file>icons/32x32/document-download.png</file>
|
||||||
<file>icons/32x32/document-new.png</file>
|
<file>icons/32x32/document-new.png</file>
|
||||||
<file>icons/32x32/document-open-folder.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-open.png</file>
|
||||||
<file>icons/32x32/document-save.png</file>
|
<file>icons/32x32/document-save.png</file>
|
||||||
<file>icons/32x32/document-search.png</file>
|
<file>icons/32x32/document-search.png</file>
|
||||||
@@ -391,6 +393,7 @@
|
|||||||
<file>icons/22x22/document-download.png</file>
|
<file>icons/22x22/document-download.png</file>
|
||||||
<file>icons/22x22/document-new.png</file>
|
<file>icons/22x22/document-new.png</file>
|
||||||
<file>icons/22x22/document-open-folder.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-open.png</file>
|
||||||
<file>icons/22x22/document-save.png</file>
|
<file>icons/22x22/document-save.png</file>
|
||||||
<file>icons/22x22/document-search.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/errordialog.cpp
|
||||||
dialogs/edittagdialog.cpp
|
dialogs/edittagdialog.cpp
|
||||||
dialogs/trackselectiondialog.cpp
|
dialogs/trackselectiondialog.cpp
|
||||||
|
dialogs/addstreamdialog.cpp
|
||||||
|
|
||||||
widgets/autoexpandingtreeview.cpp
|
widgets/autoexpandingtreeview.cpp
|
||||||
widgets/busyindicator.cpp
|
widgets/busyindicator.cpp
|
||||||
@@ -439,6 +440,7 @@ set(HEADERS
|
|||||||
dialogs/console.h
|
dialogs/console.h
|
||||||
dialogs/edittagdialog.h
|
dialogs/edittagdialog.h
|
||||||
dialogs/trackselectiondialog.h
|
dialogs/trackselectiondialog.h
|
||||||
|
dialogs/addstreamdialog.h
|
||||||
|
|
||||||
widgets/autoexpandingtreeview.h
|
widgets/autoexpandingtreeview.h
|
||||||
widgets/busyindicator.h
|
widgets/busyindicator.h
|
||||||
@@ -537,6 +539,7 @@ set(UI
|
|||||||
dialogs/console.ui
|
dialogs/console.ui
|
||||||
dialogs/edittagdialog.ui
|
dialogs/edittagdialog.ui
|
||||||
dialogs/trackselectiondialog.ui
|
dialogs/trackselectiondialog.ui
|
||||||
|
dialogs/addstreamdialog.ui
|
||||||
|
|
||||||
widgets/trackslider.ui
|
widgets/trackslider.ui
|
||||||
widgets/osdpretty.ui
|
widgets/osdpretty.ui
|
||||||
|
|||||||
@@ -297,6 +297,7 @@ void CollectionFilterWidget::SetCollectionModel(CollectionModel *model) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CollectionFilterWidget::GroupByClicked(QAction *action) {
|
void CollectionFilterWidget::GroupByClicked(QAction *action) {
|
||||||
|
|
||||||
if (action->property("group_by").isNull()) {
|
if (action->property("group_by").isNull()) {
|
||||||
group_by_dialog_->show();
|
group_by_dialog_->show();
|
||||||
return;
|
return;
|
||||||
@@ -304,6 +305,7 @@ void CollectionFilterWidget::GroupByClicked(QAction *action) {
|
|||||||
|
|
||||||
CollectionModel::Grouping g = action->property("group_by").value<CollectionModel::Grouping>();
|
CollectionModel::Grouping g = action->property("group_by").value<CollectionModel::Grouping>();
|
||||||
model_->SetGroupBy(g);
|
model_->SetGroupBy(g);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionFilterWidget::GroupingChanged(const CollectionModel::Grouping &g) {
|
void CollectionFilterWidget::GroupingChanged(const CollectionModel::Grouping &g) {
|
||||||
|
|||||||
@@ -66,10 +66,9 @@
|
|||||||
#include "playlist/playlistmanager.h"
|
#include "playlist/playlistmanager.h"
|
||||||
#include "playlist/songmimedata.h"
|
#include "playlist/songmimedata.h"
|
||||||
#include "covermanager/albumcoverloader.h"
|
#include "covermanager/albumcoverloader.h"
|
||||||
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
#include "settings/collectionsettingspage.h"
|
#include "settings/collectionsettingspage.h"
|
||||||
|
|
||||||
using std::bind;
|
|
||||||
using std::sort;
|
|
||||||
using std::placeholders::_1;
|
using std::placeholders::_1;
|
||||||
using std::placeholders::_2;
|
using std::placeholders::_2;
|
||||||
|
|
||||||
@@ -116,7 +115,7 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q
|
|||||||
cover_loader_options_.scale_output_image_ = true;
|
cover_loader_options_.scale_output_image_ = true;
|
||||||
|
|
||||||
if (app_)
|
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");
|
QIcon nocover = IconLoader::Load("cdcase");
|
||||||
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
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) {
|
void CollectionModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) {
|
||||||
|
|
||||||
Q_UNUSED(cover_url);
|
|
||||||
|
|
||||||
if (!pending_art_.contains(id)) return;
|
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);
|
pending_cache_keys_.remove(cache_key);
|
||||||
|
|
||||||
// Insert this image in the cache.
|
// 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.
|
// Set the no_cover image so we don't continually try to load art.
|
||||||
QPixmapCache::insert(cache_key, no_cover_icon_);
|
QPixmapCache::insert(cache_key, no_cover_icon_);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
QPixmap image_pixmap;
|
QPixmap image_pixmap;
|
||||||
image_pixmap = QPixmap::fromImage(image);
|
image_pixmap = QPixmap::fromImage(result.image_scaled);
|
||||||
QPixmapCache::insert(cache_key, image_pixmap);
|
QPixmapCache::insert(cache_key, image_pixmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a valid cover not already in the disk cache
|
// If we have a valid cover not already in the disk cache
|
||||||
if (use_disk_cache_) {
|
if (use_disk_cache_) {
|
||||||
std::unique_ptr<QIODevice> cached_img(sIconCache->data(QUrl(cache_key)));
|
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;
|
QNetworkCacheMetaData item_metadata;
|
||||||
item_metadata.setSaveToDisk(true);
|
item_metadata.setSaveToDisk(true);
|
||||||
item_metadata.setUrl(QUrl(cache_key));
|
item_metadata.setUrl(QUrl(cache_key));
|
||||||
QIODevice* cache = sIconCache->prepare(item_metadata);
|
QIODevice* cache = sIconCache->prepare(item_metadata);
|
||||||
if (cache) {
|
if (cache) {
|
||||||
image.save(cache, "XPM");
|
result.image_scaled.save(cache, "XPM");
|
||||||
sIconCache->insert(cache);
|
sIconCache->insert(cache);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
|
|
||||||
#include "core/simpletreemodel.h"
|
#include "core/simpletreemodel.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
#include "covermanager/albumcoverloader.h"
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
#include "collectionitem.h"
|
#include "collectionitem.h"
|
||||||
#include "sqlrow.h"
|
#include "sqlrow.h"
|
||||||
@@ -210,7 +211,7 @@ signals:
|
|||||||
// Called after ResetAsync
|
// Called after ResetAsync
|
||||||
void ResetAsyncQueryFinished(QFuture<CollectionModel::QueryResult> future);
|
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:
|
private:
|
||||||
// Provides some optimisations for loading the list of items in the root.
|
// 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) {
|
bool CollectionPlaylistItem::InitFromQuery(const SqlRow &query) {
|
||||||
|
|
||||||
// Rows from the songs tables come first
|
// Rows from the songs tables come first
|
||||||
song_.InitFromQuery(query, true);
|
song_.InitFromQuery(query, true);
|
||||||
song_.set_source(Song::Source_Collection);
|
song_.set_source(Song::Source_Collection);
|
||||||
return song_.is_valid();
|
return song_.is_valid();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant CollectionPlaylistItem::DatabaseValue(DatabaseColumn column) const {
|
QVariant CollectionPlaylistItem::DatabaseValue(DatabaseColumn column) const {
|
||||||
|
|
||||||
switch (column) {
|
switch (column) {
|
||||||
case Column_CollectionId: return song_.id();
|
case Column_CollectionId: return song_.id();
|
||||||
default: return PlaylistItem::DatabaseValue(column);
|
default: return PlaylistItem::DatabaseValue(column);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Song CollectionPlaylistItem::Metadata() const {
|
Song CollectionPlaylistItem::Metadata() const {
|
||||||
|
|
||||||
if (HasTemporaryMetadata()) return temp_metadata_;
|
if (HasTemporaryMetadata()) return temp_metadata_;
|
||||||
return song_;
|
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; }
|
bool IsLocalCollectionItem() const { return true; }
|
||||||
|
|
||||||
|
void SetArtManual(const QUrl &cover_url);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QVariant DatabaseValue(DatabaseColumn column) const;
|
QVariant DatabaseValue(DatabaseColumn column) const;
|
||||||
Song DatabaseSongMetadata() const { return Song(Song::Source_Collection); }
|
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_.desired_height_ = 600;
|
||||||
cover_loader_options_.pad_output_image_ = true;
|
cover_loader_options_.pad_output_image_ = true;
|
||||||
cover_loader_options_.scale_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)));
|
connect(timeline_fade_, SIGNAL(valueChanged(qreal)), SLOT(FadePreviousTrack(qreal)));
|
||||||
timeline_fade_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
|
timeline_fade_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0
|
||||||
@@ -89,7 +90,7 @@ void ContextAlbum::DrawImage(QPainter *p) {
|
|||||||
|
|
||||||
if (width() != prev_width_) {
|
if (width() != prev_width_) {
|
||||||
cover_loader_options_.desired_height_ = width() - kWidgetSpacing;
|
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();
|
prev_width_ = width();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +119,7 @@ void ContextAlbum::FadePreviousTrack(const qreal value) {
|
|||||||
void ContextAlbum::ScaleCover() {
|
void ContextAlbum::ScaleCover() {
|
||||||
|
|
||||||
cover_loader_options_.desired_height_ = width() - kWidgetSpacing;
|
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();
|
prev_width_ = width();
|
||||||
update();
|
update();
|
||||||
|
|
||||||
|
|||||||
@@ -48,11 +48,11 @@
|
|||||||
#include "playlist/playlistmanager.h"
|
#include "playlist/playlistmanager.h"
|
||||||
#include "playlist/songmimedata.h"
|
#include "playlist/songmimedata.h"
|
||||||
#include "covermanager/albumcoverloader.h"
|
#include "covermanager/albumcoverloader.h"
|
||||||
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
|
|
||||||
#include "contextalbumsmodel.h"
|
#include "contextalbumsmodel.h"
|
||||||
|
|
||||||
using std::bind;
|
|
||||||
using std::sort;
|
|
||||||
using std::placeholders::_1;
|
using std::placeholders::_1;
|
||||||
using std::placeholders::_2;
|
using std::placeholders::_2;
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ ContextAlbumsModel::ContextAlbumsModel(CollectionBackend *backend, Application *
|
|||||||
cover_loader_options_.pad_output_image_ = true;
|
cover_loader_options_.pad_output_image_ = true;
|
||||||
cover_loader_options_.scale_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");
|
QIcon nocover = IconLoader::Load("cdcase");
|
||||||
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
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) {
|
void ContextAlbumsModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) {
|
||||||
|
|
||||||
Q_UNUSED(cover_url);
|
|
||||||
|
|
||||||
if (!pending_art_.contains(id)) return;
|
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);
|
pending_cache_keys_.remove(cache_key);
|
||||||
|
|
||||||
// Insert this image in the cache.
|
// 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.
|
// Set the no_cover image so we don't continually try to load art.
|
||||||
QPixmapCache::insert(cache_key, no_cover_icon_);
|
QPixmapCache::insert(cache_key, no_cover_icon_);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
QPixmap image_pixmap;
|
QPixmap image_pixmap;
|
||||||
image_pixmap = QPixmap::fromImage(image);
|
image_pixmap = QPixmap::fromImage(result.image_scaled);
|
||||||
QPixmapCache::insert(cache_key, image_pixmap);
|
QPixmapCache::insert(cache_key, image_pixmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
#include "collection/collectionitem.h"
|
#include "collection/collectionitem.h"
|
||||||
#include "collection/sqlrow.h"
|
#include "collection/sqlrow.h"
|
||||||
#include "covermanager/albumcoverloaderoptions.h"
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
|
|
||||||
class QMimeData;
|
class QMimeData;
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ class ContextAlbumsModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
void LazyPopulate(CollectionItem *item, bool signal);
|
void LazyPopulate(CollectionItem *item, bool signal);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
|
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QueryResult RunQuery(CollectionItem *parent);
|
QueryResult RunQuery(CollectionItem *parent);
|
||||||
|
|||||||
@@ -124,6 +124,7 @@ ContextView::ContextView(QWidget *parent) :
|
|||||||
label_device_icon_(new QLabel(this)),
|
label_device_icon_(new QLabel(this)),
|
||||||
label_engine_icon_(new QLabel(this)),
|
label_engine_icon_(new QLabel(this)),
|
||||||
spacer_bottom_(new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Expanding)),
|
spacer_bottom_(new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Expanding)),
|
||||||
|
lyrics_tried_(false),
|
||||||
lyrics_id_(-1),
|
lyrics_id_(-1),
|
||||||
prev_width_(0)
|
prev_width_(0)
|
||||||
{
|
{
|
||||||
@@ -365,19 +366,22 @@ void ContextView::Error() {}
|
|||||||
|
|
||||||
void ContextView::SongChanged(const Song &song) {
|
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);
|
UpdateSong(song);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
song_prev_ = song_playing_;
|
song_prev_ = song_playing_;
|
||||||
|
song_playing_ = song;
|
||||||
lyrics_ = song.lyrics();
|
lyrics_ = song.lyrics();
|
||||||
lyrics_id_ = -1;
|
lyrics_id_ = -1;
|
||||||
song_playing_ = song;
|
lyrics_tried_ = false;
|
||||||
SetSong();
|
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) {
|
void ContextView::AlbumCoverLoaded(const Song &song, const QImage &image) {
|
||||||
|
|
||||||
Q_UNUSED(cover_url);
|
|
||||||
|
|
||||||
if (song != song_playing_ || image == image_original_) return;
|
if (song != song_playing_ || image == image_original_) return;
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class ContextView : public QWidget {
|
|||||||
void Stopped();
|
void Stopped();
|
||||||
void Error();
|
void Error();
|
||||||
void SongChanged(const Song &song);
|
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 FadeStopFinished();
|
||||||
void UpdateLyrics(const quint64 id, const QString &provider, const QString &lyrics);
|
void UpdateLyrics(const quint64 id, const QString &provider, const QString &lyrics);
|
||||||
|
|
||||||
@@ -164,6 +164,7 @@ class ContextView : public QWidget {
|
|||||||
Song song_playing_;
|
Song song_playing_;
|
||||||
Song song_prev_;
|
Song song_prev_;
|
||||||
QImage image_original_;
|
QImage image_original_;
|
||||||
|
bool lyrics_tried_;
|
||||||
qint64 lyrics_id_;
|
qint64 lyrics_id_;
|
||||||
QString lyrics_;
|
QString lyrics_;
|
||||||
QString title_fmt_;
|
QString title_fmt_;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* 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
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -96,6 +96,7 @@
|
|||||||
#include "dialogs/console.h"
|
#include "dialogs/console.h"
|
||||||
#include "dialogs/trackselectiondialog.h"
|
#include "dialogs/trackselectiondialog.h"
|
||||||
#include "dialogs/edittagdialog.h"
|
#include "dialogs/edittagdialog.h"
|
||||||
|
#include "dialogs/addstreamdialog.h"
|
||||||
#include "organise/organisedialog.h"
|
#include "organise/organisedialog.h"
|
||||||
#include "widgets/fancytabwidget.h"
|
#include "widgets/fancytabwidget.h"
|
||||||
#include "widgets/playingwidget.h"
|
#include "widgets/playingwidget.h"
|
||||||
@@ -131,7 +132,7 @@
|
|||||||
#endif
|
#endif
|
||||||
#include "covermanager/albumcovermanager.h"
|
#include "covermanager/albumcovermanager.h"
|
||||||
#include "covermanager/albumcoverchoicecontroller.h"
|
#include "covermanager/albumcoverchoicecontroller.h"
|
||||||
#include "covermanager/albumcoverloader.h"
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
#include "covermanager/currentalbumcoverloader.h"
|
#include "covermanager/currentalbumcoverloader.h"
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
# include "device/devicemanager.h"
|
# include "device/devicemanager.h"
|
||||||
@@ -178,10 +179,6 @@
|
|||||||
# include "windows7thumbbar.h"
|
# include "windows7thumbbar.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
using std::bind;
|
|
||||||
using std::floor;
|
|
||||||
using std::stable_sort;
|
|
||||||
|
|
||||||
const char *MainWindow::kSettingsGroup = "MainWindow";
|
const char *MainWindow::kSettingsGroup = "MainWindow";
|
||||||
const char *MainWindow::kAllFilesFilterSpec = QT_TR_NOOP("All Files (*)");
|
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);
|
TranscodeDialog *dialog = new TranscodeDialog(this);
|
||||||
return dialog;
|
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
|
#ifdef HAVE_SUBSONIC
|
||||||
subsonic_view_(new InternetSongsView(app_, app->internet_services()->ServiceBySource(Song::Source_Subsonic), SubsonicSettingsPage::kSettingsGroup, SettingsDialog::Page_Subsonic, this)),
|
subsonic_view_(new InternetSongsView(app_, app->internet_services()->ServiceBySource(Song::Source_Subsonic), SubsonicSettingsPage::kSettingsGroup, SettingsDialog::Page_Subsonic, this)),
|
||||||
#endif
|
#endif
|
||||||
@@ -260,7 +262,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
|||||||
// Initialise the UI
|
// Initialise the UI
|
||||||
ui_->setupUi(this);
|
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);
|
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_from_file_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromFile()));
|
||||||
connect(album_cover_choice_controller_->cover_to_file_action(), SIGNAL(triggered()), this, SLOT(SaveCoverToFile()));
|
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_file->setIcon(IconLoader::Load("document-open"));
|
||||||
ui_->action_add_folder->setIcon(IconLoader::Load("document-open-folder"));
|
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_shuffle_mode->setIcon(IconLoader::Load("media-playlist-shuffle"));
|
||||||
ui_->action_repeat_mode->setIcon(IconLoader::Load("media-playlist-repeat"));
|
ui_->action_repeat_mode->setIcon(IconLoader::Load("media-playlist-repeat"));
|
||||||
ui_->action_new_playlist->setIcon(IconLoader::Load("document-new"));
|
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_open_cd, SIGNAL(triggered()), SLOT(AddCDTracks()));
|
||||||
connect(ui_->action_add_file, SIGNAL(triggered()), SLOT(AddFile()));
|
connect(ui_->action_add_file, SIGNAL(triggered()), SLOT(AddFile()));
|
||||||
connect(ui_->action_add_folder, SIGNAL(triggered()), SLOT(AddFolder()));
|
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_cover_manager, SIGNAL(triggered()), SLOT(ShowCoverManager()));
|
||||||
connect(ui_->action_equalizer, SIGNAL(triggered()), equalizer_.get(), SLOT(show()));
|
connect(ui_->action_equalizer, SIGNAL(triggered()), equalizer_.get(), SLOT(show()));
|
||||||
#if defined(HAVE_GSTREAMER)
|
#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(Playing()), context_view_, SLOT(Playing()));
|
||||||
connect(app_->player(), SIGNAL(Stopped()), context_view_, SLOT(Stopped()));
|
connect(app_->player(), SIGNAL(Stopped()), context_view_, SLOT(Stopped()));
|
||||||
connect(app_->player(), SIGNAL(Error()), context_view_, SLOT(Error()));
|
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(this, SIGNAL(SearchCoverInProgress()), context_view_->album_widget(), SLOT(SearchCoverInProgress()));
|
||||||
connect(context_view_, SIGNAL(AlbumEnabledChanged()), SLOT(TabSwitched()));
|
connect(context_view_, SIGNAL(AlbumEnabledChanged()), SLOT(TabSwitched()));
|
||||||
connect(context_view_->albums_widget(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
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(Stopped()), ui_->widget_playing, SLOT(Stopped()));
|
||||||
connect(app_->player(), SIGNAL(Error()), ui_->widget_playing, SLOT(Error()));
|
connect(app_->player(), SIGNAL(Error()), ui_->widget_playing, SLOT(Error()));
|
||||||
connect(ui_->widget_playing, SIGNAL(ShowAboveStatusBarChanged(bool)), SLOT(PlayingWidgetPositionChanged(bool)));
|
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(this, SIGNAL(SearchCoverInProgress()), ui_->widget_playing, SLOT(SearchCoverInProgress()));
|
||||||
|
|
||||||
connect(ui_->action_console, SIGNAL(triggered()), SLOT(ShowConsole()));
|
connect(ui_->action_console, SIGNAL(triggered()), SLOT(ShowConsole()));
|
||||||
@@ -1777,7 +1781,7 @@ void MainWindow::EditTracks() {
|
|||||||
|
|
||||||
void MainWindow::EditTagDialogAccepted() {
|
void MainWindow::EditTagDialogAccepted() {
|
||||||
|
|
||||||
for (const PlaylistItemPtr item : edit_tag_dialog_->playlist_items()) {
|
for (PlaylistItemPtr item : edit_tag_dialog_->playlist_items()) {
|
||||||
item->Reload();
|
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() {
|
void MainWindow::ShowInCollection() {
|
||||||
|
|
||||||
// Show the first valid selected track artist/album in CollectionView
|
// Show the first valid selected track artist/album in CollectionView
|
||||||
@@ -2512,7 +2526,7 @@ void MainWindow::AutoCompleteTags() {
|
|||||||
|
|
||||||
void MainWindow::AutoCompleteTagsAccepted() {
|
void MainWindow::AutoCompleteTagsAccepted() {
|
||||||
|
|
||||||
for (const PlaylistItemPtr item : autocomplete_tag_items_) {
|
for (PlaylistItemPtr item : autocomplete_tag_items_) {
|
||||||
item->Reload();
|
item->Reload();
|
||||||
}
|
}
|
||||||
autocomplete_tag_items_.clear();
|
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;
|
song_ = song;
|
||||||
image_original_ = image;
|
image_original_ = result.image_original;
|
||||||
|
|
||||||
emit AlbumCoverReady(song, cover_url, image);
|
emit AlbumCoverReady(song, result.image_original);
|
||||||
|
|
||||||
GetCoverAutomatically();
|
GetCoverAutomatically();
|
||||||
|
|
||||||
@@ -2617,13 +2631,12 @@ void MainWindow::GetCoverAutomatically() {
|
|||||||
|
|
||||||
// Search for cover automatically?
|
// Search for cover automatically?
|
||||||
bool search =
|
bool search =
|
||||||
song_.source() == Song::Source_Collection &&
|
album_cover_choice_controller_->search_cover_auto_action()->isChecked() &&
|
||||||
album_cover_choice_controller_->search_cover_auto_action()->isChecked() &&
|
!song_.has_manually_unset_cover() &&
|
||||||
!song_.has_manually_unset_cover() &&
|
!song_.art_automatic_is_valid() &&
|
||||||
!song_.art_automatic_is_valid() &&
|
!song_.art_manual_is_valid() &&
|
||||||
!song_.art_manual_is_valid() &&
|
!song_.effective_albumartist().isEmpty() &&
|
||||||
!song_.effective_albumartist().isEmpty() &&
|
!song_.effective_album().isEmpty();
|
||||||
!song_.effective_album().isEmpty();
|
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
album_cover_choice_controller_->SearchCoverAutomatically(song_);
|
album_cover_choice_controller_->SearchCoverAutomatically(song_);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* 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
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -59,8 +59,7 @@
|
|||||||
#include "playlist/playlistitem.h"
|
#include "playlist/playlistitem.h"
|
||||||
#include "settings/settingsdialog.h"
|
#include "settings/settingsdialog.h"
|
||||||
#include "settings/behavioursettingspage.h"
|
#include "settings/behavioursettingspage.h"
|
||||||
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
using std::unique_ptr;
|
|
||||||
|
|
||||||
class About;
|
class About;
|
||||||
class AlbumCoverManager;
|
class AlbumCoverManager;
|
||||||
@@ -95,6 +94,7 @@ class InternetTabsView;
|
|||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
class Windows7ThumbBar;
|
class Windows7ThumbBar;
|
||||||
#endif
|
#endif
|
||||||
|
class AddStreamDialog;
|
||||||
|
|
||||||
class MainWindow : public QMainWindow, public PlatformInterface {
|
class MainWindow : public QMainWindow, public PlatformInterface {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -126,7 +126,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
bool LoadUrl(const QString& url);
|
bool LoadUrl(const QString& url);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void AlbumCoverReady(const Song &song, const QUrl &cover_url, const QImage &image);
|
void AlbumCoverReady(const Song &song, const QImage &image);
|
||||||
void SearchCoverInProgress();
|
void SearchCoverInProgress();
|
||||||
// Signals that stop playing after track was toggled.
|
// Signals that stop playing after track was toggled.
|
||||||
void StopAfterToggled(bool stop);
|
void StopAfterToggled(bool stop);
|
||||||
@@ -210,6 +210,8 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
void AddFile();
|
void AddFile();
|
||||||
void AddFolder();
|
void AddFolder();
|
||||||
void AddCDTracks();
|
void AddCDTracks();
|
||||||
|
void AddStream();
|
||||||
|
void AddStreamAccepted();
|
||||||
|
|
||||||
void CommandlineOptionsReceived(const quint32 instanceId, const QByteArray &string_options);
|
void CommandlineOptionsReceived(const quint32 instanceId, const QByteArray &string_options);
|
||||||
|
|
||||||
@@ -251,7 +253,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
void UnsetCover();
|
void UnsetCover();
|
||||||
void ShowCover();
|
void ShowCover();
|
||||||
void SearchCoverAutomatically();
|
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 ScrobblingEnabledChanged(const bool value);
|
||||||
void ScrobbleButtonVisibilityChanged(const bool value);
|
void ScrobbleButtonVisibilityChanged(const bool value);
|
||||||
@@ -308,6 +310,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
#ifdef HAVE_GSTREAMER
|
#ifdef HAVE_GSTREAMER
|
||||||
Lazy<TranscodeDialog> transcode_dialog_;
|
Lazy<TranscodeDialog> transcode_dialog_;
|
||||||
#endif
|
#endif
|
||||||
|
Lazy<AddStreamDialog> add_stream_dialog_;
|
||||||
|
|
||||||
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
||||||
std::unique_ptr<TagFetcher> tag_fetcher_;
|
std::unique_ptr<TagFetcher> tag_fetcher_;
|
||||||
|
|||||||
@@ -469,6 +469,7 @@
|
|||||||
</property>
|
</property>
|
||||||
<addaction name="action_add_file"/>
|
<addaction name="action_add_file"/>
|
||||||
<addaction name="action_add_folder"/>
|
<addaction name="action_add_folder"/>
|
||||||
|
<addaction name="action_add_stream"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="action_shuffle_mode"/>
|
<addaction name="action_shuffle_mode"/>
|
||||||
<addaction name="action_repeat_mode"/>
|
<addaction name="action_repeat_mode"/>
|
||||||
@@ -827,6 +828,11 @@
|
|||||||
<string>Rescan songs(s)</string>
|
<string>Rescan songs(s)</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="action_add_stream">
|
||||||
|
<property name="text">
|
||||||
|
<string>Add stream...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<layoutdefault spacing="6" margin="11"/>
|
<layoutdefault spacing="6" margin="11"/>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
#include "collection/directory.h"
|
#include "collection/directory.h"
|
||||||
#include "playlist/playlistitem.h"
|
#include "playlist/playlistitem.h"
|
||||||
#include "playlist/playlistsequence.h"
|
#include "playlist/playlistsequence.h"
|
||||||
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
#include "covermanager/albumcoverfetcher.h"
|
#include "covermanager/albumcoverfetcher.h"
|
||||||
#include "equalizer/equalizer.h"
|
#include "equalizer/equalizer.h"
|
||||||
|
|
||||||
@@ -98,6 +99,8 @@ void RegisterMetaTypes() {
|
|||||||
qRegisterMetaType<QList<PlaylistItemPtr> >("QList<PlaylistItemPtr>");
|
qRegisterMetaType<QList<PlaylistItemPtr> >("QList<PlaylistItemPtr>");
|
||||||
qRegisterMetaType<PlaylistSequence::RepeatMode>("PlaylistSequence::RepeatMode");
|
qRegisterMetaType<PlaylistSequence::RepeatMode>("PlaylistSequence::RepeatMode");
|
||||||
qRegisterMetaType<PlaylistSequence::ShuffleMode>("PlaylistSequence::ShuffleMode");
|
qRegisterMetaType<PlaylistSequence::ShuffleMode>("PlaylistSequence::ShuffleMode");
|
||||||
|
qRegisterMetaType<AlbumCoverLoaderResult>("AlbumCoverLoaderResult");
|
||||||
|
qRegisterMetaType<AlbumCoverLoaderResult::Type>("AlbumCoverLoaderResult::Type");
|
||||||
qRegisterMetaType<CoverSearchResult>("CoverSearchResult");
|
qRegisterMetaType<CoverSearchResult>("CoverSearchResult");
|
||||||
qRegisterMetaType<QList<CoverSearchResult> >("QList<CoverSearchResult>");
|
qRegisterMetaType<QList<CoverSearchResult> >("QList<CoverSearchResult>");
|
||||||
qRegisterMetaType<CoverSearchResults>("CoverSearchResults");
|
qRegisterMetaType<CoverSearchResults>("CoverSearchResults");
|
||||||
|
|||||||
@@ -58,14 +58,13 @@
|
|||||||
#include "playlist/playlistmanager.h"
|
#include "playlist/playlistmanager.h"
|
||||||
#include "playlist/playlistsequence.h"
|
#include "playlist/playlistsequence.h"
|
||||||
#include "covermanager/currentalbumcoverloader.h"
|
#include "covermanager/currentalbumcoverloader.h"
|
||||||
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
|
|
||||||
#include <core/mpris2_player.h>
|
#include <core/mpris2_player.h>
|
||||||
#include <core/mpris2_playlists.h>
|
#include <core/mpris2_playlists.h>
|
||||||
#include <core/mpris2_root.h>
|
#include <core/mpris2_root.h>
|
||||||
#include <core/mpris2_tracklist.h>
|
#include <core/mpris2_tracklist.h>
|
||||||
|
|
||||||
using std::reverse;
|
|
||||||
|
|
||||||
QDBusArgument &operator<<(QDBusArgument &arg, const MprisPlaylist &playlist) {
|
QDBusArgument &operator<<(QDBusArgument &arg, const MprisPlaylist &playlist) {
|
||||||
arg.beginStructure();
|
arg.beginStructure();
|
||||||
arg << playlist.id << playlist.name << playlist.icon;
|
arg << playlist.id << playlist.name << playlist.icon;
|
||||||
@@ -122,7 +121,7 @@ Mpris2::Mpris2(Application *app, QObject *parent)
|
|||||||
return;
|
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()->engine(), SIGNAL(StateChanged(Engine::State)), SLOT(EngineStateChanged(Engine::State)));
|
||||||
connect(app_->player(), SIGNAL(VolumeChanged(int)), SLOT(VolumeChanged()));
|
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...
|
// We send Metadata change notification as soon as the process of changing song starts...
|
||||||
void Mpris2::CurrentSongChanged(const Song &song) {
|
void Mpris2::CurrentSongChanged(const Song &song) {
|
||||||
|
|
||||||
AlbumCoverLoaded(song, QUrl(), QImage());
|
AlbumCoverLoaded(song);
|
||||||
EmitNotification("CanPlay");
|
EmitNotification("CanPlay");
|
||||||
EmitNotification("CanPause");
|
EmitNotification("CanPause");
|
||||||
EmitNotification("CanGoNext", CanGoNext());
|
EmitNotification("CanGoNext", CanGoNext());
|
||||||
@@ -388,9 +387,7 @@ void Mpris2::CurrentSongChanged(const Song &song) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ... and we add the cover information later, when it's available.
|
// ... and we add the cover information later, when it's available.
|
||||||
void Mpris2::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image) {
|
void Mpris2::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) {
|
||||||
|
|
||||||
Q_UNUSED(image);
|
|
||||||
|
|
||||||
last_metadata_ = QVariantMap();
|
last_metadata_ = QVariantMap();
|
||||||
song.ToXesam(&last_metadata_);
|
song.ToXesam(&last_metadata_);
|
||||||
@@ -398,9 +395,14 @@ void Mpris2::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QIm
|
|||||||
using mpris::AddMetadata;
|
using mpris::AddMetadata;
|
||||||
AddMetadata("mpris:trackid", current_track_id(), &last_metadata_);
|
AddMetadata("mpris:trackid", current_track_id(), &last_metadata_);
|
||||||
|
|
||||||
if (cover_url.isValid()) {
|
QUrl cover_url;
|
||||||
AddMetadata("mpris:artUrl", cover_url.toString(), &last_metadata_);
|
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("year", song.year(), &last_metadata_);
|
||||||
AddMetadata("bitrate", song.bitrate(), &last_metadata_);
|
AddMetadata("bitrate", song.bitrate(), &last_metadata_);
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include "engine/engine_fwd.h"
|
#include "engine/engine_fwd.h"
|
||||||
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
|
|
||||||
class Application;
|
class Application;
|
||||||
class Song;
|
class Song;
|
||||||
@@ -204,7 +205,7 @@ signals:
|
|||||||
void PlaylistChanged(const MprisPlaylist &playlist);
|
void PlaylistChanged(const MprisPlaylist &playlist);
|
||||||
|
|
||||||
private slots:
|
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 EngineStateChanged(Engine::State newState);
|
||||||
void VolumeChanged();
|
void VolumeChanged();
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QDir>
|
||||||
#include <QSharedData>
|
#include <QSharedData>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
@@ -42,6 +43,7 @@
|
|||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QTextCodec>
|
#include <QTextCodec>
|
||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
|
#include <QStandardPaths>
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
|
|
||||||
#ifdef HAVE_LIBGPOD
|
#ifdef HAVE_LIBGPOD
|
||||||
@@ -66,7 +68,6 @@
|
|||||||
#include "covermanager/albumcoverloader.h"
|
#include "covermanager/albumcoverloader.h"
|
||||||
#include "tagreadermessages.pb.h"
|
#include "tagreadermessages.pb.h"
|
||||||
|
|
||||||
using std::sort;
|
|
||||||
#ifndef USE_SYSTEM_TAGLIB
|
#ifndef USE_SYSTEM_TAGLIB
|
||||||
using namespace Strawberry_TagLib;
|
using namespace Strawberry_TagLib;
|
||||||
#endif
|
#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::is_compilation() const { return (d->compilation_ || d->compilation_detected_ || d->compilation_on_) && !d->compilation_off_; }
|
||||||
|
|
||||||
bool Song::art_automatic_is_valid() const {
|
bool Song::art_automatic_is_valid() const {
|
||||||
return (
|
return !d->art_automatic_.isEmpty() &&
|
||||||
|
(
|
||||||
(d->art_automatic_.path() == kManuallyUnsetCover) ||
|
(d->art_automatic_.path() == kManuallyUnsetCover) ||
|
||||||
(d->art_automatic_.path() == kEmbeddedCover) ||
|
(d->art_automatic_.path() == kEmbeddedCover) ||
|
||||||
(d->art_automatic_.isValid() && !d->art_automatic_.isLocalFile()) ||
|
(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 {
|
bool Song::art_manual_is_valid() const {
|
||||||
return (
|
return !d->art_manual_.isEmpty() &&
|
||||||
|
(
|
||||||
(d->art_manual_.path() == kManuallyUnsetCover) ||
|
(d->art_manual_.path() == kManuallyUnsetCover) ||
|
||||||
(d->art_manual_.path() == kEmbeddedCover) ||
|
(d->art_manual_.path() == kEmbeddedCover) ||
|
||||||
(d->art_manual_.isValid() && !d->art_manual_.isLocalFile()) ||
|
(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) {
|
int CompareSongsName(const Song &song1, const Song &song2) {
|
||||||
return song1.PrettyTitleWithArtist().localeAwareCompare(song2.PrettyTitleWithArtist()) < 0;
|
return song1.PrettyTitleWithArtist().localeAwareCompare(song2.PrettyTitleWithArtist()) < 0;
|
||||||
}
|
}
|
||||||
@@ -997,16 +1023,24 @@ void Song::InitFromFilePartial(const QString &filename) {
|
|||||||
|
|
||||||
void Song::InitArtManual() {
|
void Song::InitArtManual() {
|
||||||
|
|
||||||
QString album = d->album_;
|
QString album = effective_album();
|
||||||
album.remove(Song::kAlbumRemoveDisc);
|
album.remove(Song::kAlbumRemoveDisc);
|
||||||
|
|
||||||
// If we don't have an art, check if we have one in the cache
|
// 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 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)) {
|
if (QFile::exists(path)) {
|
||||||
d->art_manual_ = QUrl::fromLocalFile(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 FiletypeByMimetype(const QString &mimetype);
|
||||||
static FileType FiletypeByDescription(const QString &text);
|
static FileType FiletypeByDescription(const QString &text);
|
||||||
static FileType FiletypeByExtension(const QString &ext);
|
static FileType FiletypeByExtension(const QString &ext);
|
||||||
|
static QString ImageCacheDir(const Song::Source source);
|
||||||
|
|
||||||
// Sort songs alphabetically using their pretty title
|
// Sort songs alphabetically using their pretty title
|
||||||
static void SortSongsListAlphabetically(QList<Song> *songs);
|
static void SortSongsListAlphabetically(QList<Song> *songs);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ StandardItemIconLoader::StandardItemIconLoader(AlbumCoverLoader *cover_loader, Q
|
|||||||
|
|
||||||
cover_options_.desired_height_ = 16;
|
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) {
|
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) {
|
void StandardItemIconLoader::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult result) {
|
||||||
|
|
||||||
Q_UNUSED(cover_url);
|
|
||||||
|
|
||||||
QStandardItem *item = pending_covers_.take(id);
|
QStandardItem *item = pending_covers_.take(id);
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
if (!image.isNull()) {
|
if (!result.image_scaled.isNull()) {
|
||||||
item->setIcon(QIcon(QPixmap::fromImage(image)));
|
item->setIcon(QIcon(QPixmap::fromImage(result.image_scaled)));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,11 @@
|
|||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
#include <QUrl>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
|
||||||
#include "covermanager/albumcoverloaderoptions.h"
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
|
|
||||||
class QAbstractItemModel;
|
class QAbstractItemModel;
|
||||||
class QStandardItem;
|
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 QUrl &art_automatic, const QUrl &art_manual, QStandardItem *for_item);
|
||||||
void LoadIcon(const Song &song, QStandardItem *for_item);
|
void LoadIcon(const Song &song, QStandardItem *for_item);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
|
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult result);
|
||||||
void RowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end);
|
void RowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end);
|
||||||
void ModelReset();
|
void ModelReset();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AlbumCoverLoader *cover_loader_;
|
AlbumCoverLoader *cover_loader_;
|
||||||
AlbumCoverLoaderOptions cover_options_;
|
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;
|
cover_fetching_tasks_[id] = song;
|
||||||
|
|
||||||
|
return id;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverChoiceController::AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics) {
|
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);
|
app_->current_albumcover_loader()->LoadAlbumCover(*song);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class AlbumCoverChoiceController : public QWidget {
|
|||||||
void ShowCover(const Song &song, const QPixmap &pixmap);
|
void ShowCover(const Song &song, const QPixmap &pixmap);
|
||||||
|
|
||||||
// Search for covers automatically
|
// 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.
|
// Saves the chosen cover as manual cover path of this song in collection.
|
||||||
void SaveCoverToSong(Song *song, const QUrl &cover_url);
|
void SaveCoverToSong(Song *song, const QUrl &cover_url);
|
||||||
@@ -124,7 +124,7 @@ class AlbumCoverChoiceController : public QWidget {
|
|||||||
|
|
||||||
static bool CanAcceptDrag(const QDragEnterEvent *e);
|
static bool CanAcceptDrag(const QDragEnterEvent *e);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void AutomaticCoverSearchDone();
|
void AutomaticCoverSearchDone();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|||||||
@@ -44,14 +44,15 @@ AlbumCoverFetcher::AlbumCoverFetcher(CoverProviders *cover_providers, QObject *p
|
|||||||
connect(request_starter_, SIGNAL(timeout()), SLOT(StartRequests()));
|
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;
|
CoverSearchRequest request;
|
||||||
request.id = next_id_++;
|
request.id = next_id_++;
|
||||||
request.artist = artist;
|
request.artist = artist;
|
||||||
request.album = album;
|
request.album = album;
|
||||||
request.album.remove(Song::kAlbumRemoveDisc);
|
request.album = request.album.remove(Song::kAlbumRemoveDisc);
|
||||||
request.album.remove(Song::kAlbumRemoveMisc);
|
request.album = request.album.remove(Song::kAlbumRemoveMisc);
|
||||||
|
request.title = title;
|
||||||
request.search = false;
|
request.search = false;
|
||||||
request.fetchall = fetchall;
|
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;
|
CoverSearchRequest request;
|
||||||
request.id = next_id_++;
|
request.id = next_id_++;
|
||||||
request.artist = artist;
|
request.artist = artist;
|
||||||
request.album = album;
|
request.album = album;
|
||||||
request.album.remove(Song::kAlbumRemoveDisc);
|
request.album = request.album.remove(Song::kAlbumRemoveDisc);
|
||||||
request.album.remove(Song::kAlbumRemoveMisc);
|
request.album = request.album.remove(Song::kAlbumRemoveMisc);
|
||||||
|
request.title = title;
|
||||||
request.search = true;
|
request.search = true;
|
||||||
request.fetchall = false;
|
request.fetchall = false;
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ struct CoverSearchRequest {
|
|||||||
// A search query
|
// A search query
|
||||||
QString artist;
|
QString artist;
|
||||||
QString album;
|
QString album;
|
||||||
|
QString title;
|
||||||
|
|
||||||
// Is this only a search request or should we also fetch the first cover that's found?
|
// Is this only a search request or should we also fetch the first cover that's found?
|
||||||
bool search;
|
bool search;
|
||||||
@@ -92,12 +93,12 @@ class AlbumCoverFetcher : public QObject {
|
|||||||
|
|
||||||
static const int kMaxConcurrentRequests;
|
static const int kMaxConcurrentRequests;
|
||||||
|
|
||||||
quint64 SearchForCovers(const QString &artist, const QString &album);
|
quint64 SearchForCovers(const QString &artist, const QString &album, const QString &title = QString());
|
||||||
quint64 FetchAlbumCover(const QString &artist, const QString &album, const bool fetchall);
|
quint64 FetchAlbumCover(const QString &artist, const QString &album, const QString &title, const bool fetchall);
|
||||||
|
|
||||||
void Clear();
|
void Clear();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void AlbumCoverFetched(const quint64 request_id, const QUrl &cover_url, const QImage &cover, const CoverSearchStatistics &statistics);
|
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);
|
void SearchFinished(const quint64 request_id, const CoverSearchResults &results, const CoverSearchStatistics &statistics);
|
||||||
|
|
||||||
|
|||||||
@@ -42,11 +42,6 @@
|
|||||||
#include "coverprovider.h"
|
#include "coverprovider.h"
|
||||||
#include "coverproviders.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::kSearchTimeoutMs = 25000;
|
||||||
const int AlbumCoverFetcherSearch::kImageLoadTimeoutMs = 3000;
|
const int AlbumCoverFetcherSearch::kImageLoadTimeoutMs = 3000;
|
||||||
const int AlbumCoverFetcherSearch::kTargetSize = 500;
|
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".
|
// Skip provider if it does not have fetchall set, and we are doing fetchall - "Fetch Missing Covers".
|
||||||
if (!provider->fetchall() && request_.fetchall) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(provider, SIGNAL(SearchFinished(int, CoverSearchResults)), SLOT(ProviderSearchFinished(int, CoverSearchResults)));
|
connect(provider, SIGNAL(SearchFinished(int, CoverSearchResults)), SLOT(ProviderSearchFinished(int, CoverSearchResults)));
|
||||||
const int id = cover_providers->NextId();
|
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) {
|
if (success) {
|
||||||
pending_requests_[id] = provider;
|
pending_requests_[id] = provider;
|
||||||
@@ -112,7 +110,7 @@ void AlbumCoverFetcherSearch::ProviderSearchFinished(const int id, const CoverSe
|
|||||||
CoverProvider *provider = pending_requests_.take(id);
|
CoverProvider *provider = pending_requests_.take(id);
|
||||||
|
|
||||||
CoverSearchResults results_copy(results);
|
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].provider = provider->name();
|
||||||
results_copy[i].score = provider->quality();
|
results_copy[i].score = provider->quality();
|
||||||
if (results_copy[i].artist.toLower() == request_.artist.toLower()) {
|
if (results_copy[i].artist.toLower() == request_.artist.toLower()) {
|
||||||
@@ -170,7 +168,7 @@ void AlbumCoverFetcherSearch::FetchMoreImages() {
|
|||||||
|
|
||||||
// Try the first one in each category.
|
// Try the first one in each category.
|
||||||
QString last_provider;
|
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) {
|
if (results_[i].provider == last_provider) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
#include "organise/organiseformat.h"
|
#include "organise/organiseformat.h"
|
||||||
#include "albumcoverloader.h"
|
#include "albumcoverloader.h"
|
||||||
#include "albumcoverloaderoptions.h"
|
#include "albumcoverloaderoptions.h"
|
||||||
|
#include "albumcoverloaderresult.h"
|
||||||
|
|
||||||
AlbumCoverLoader::AlbumCoverLoader(QObject *parent)
|
AlbumCoverLoader::AlbumCoverLoader(QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
@@ -98,89 +99,7 @@ void AlbumCoverLoader::ReloadSettings() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AlbumCoverLoader::ImageCacheDir(const Song::Source source) {
|
QString AlbumCoverLoader::AlbumCoverFilename(QString artist, QString album) {
|
||||||
|
|
||||||
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) {
|
|
||||||
|
|
||||||
artist.remove('/');
|
artist.remove('/');
|
||||||
album.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_);
|
QString filename(cover_pattern_);
|
||||||
filename.replace("%albumartist", artist);
|
filename.replace("%albumartist", artist);
|
||||||
@@ -215,6 +206,7 @@ void AlbumCoverLoader::CancelTask(const quint64 id) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverLoader::CancelTasks(const QSet<quint64> &ids) {
|
void AlbumCoverLoader::CancelTasks(const QSet<quint64> &ids) {
|
||||||
@@ -228,21 +220,25 @@ void AlbumCoverLoader::CancelTasks(const QSet<quint64> &ids) {
|
|||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions& options, const Song &song) {
|
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 task;
|
||||||
task.options = options;
|
task.options = options;
|
||||||
task.art_automatic = art_automatic;
|
task.song = song;
|
||||||
|
task.song_url = song_url;
|
||||||
task.art_manual = art_manual;
|
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.embedded_image = embedded_image;
|
||||||
task.state = State_TryingManual;
|
task.type = AlbumCoverLoaderResult::Type_None;
|
||||||
|
task.state = State_Manual;
|
||||||
|
|
||||||
{
|
{
|
||||||
QMutexLocker l(&mutex_);
|
QMutexLocker l(&mutex_);
|
||||||
@@ -269,20 +265,20 @@ void AlbumCoverLoader::ProcessTasks() {
|
|||||||
|
|
||||||
ProcessTask(&task);
|
ProcessTask(&task);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AlbumCoverLoader::ProcessTask(Task *task) {
|
void AlbumCoverLoader::ProcessTask(Task *task) {
|
||||||
|
|
||||||
TryLoadResult result = TryLoadImage(*task);
|
TryLoadResult result = TryLoadImage(task);
|
||||||
if (result.started_async) {
|
if (result.started_async) {
|
||||||
// The image is being loaded from a remote URL, we'll carry on later when it's done
|
// The image is being loaded from a remote URL, we'll carry on later when it's done
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.loaded_success) {
|
if (result.loaded_success) {
|
||||||
QImage scaled = ScaleAndPad(task->options, result.image);
|
QPair<QImage, QImage> images = ScaleAndPad(task->options, result.image);
|
||||||
emit ImageLoaded(task->id, result.cover_url, scaled);
|
emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(result.type, result.cover_url, result.image, images.first, images.second, task->art_updated));
|
||||||
emit ImageLoaded(task->id, result.cover_url, scaled, result.image);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,64 +288,85 @@ void AlbumCoverLoader::ProcessTask(Task *task) {
|
|||||||
|
|
||||||
void AlbumCoverLoader::NextState(Task *task) {
|
void AlbumCoverLoader::NextState(Task *task) {
|
||||||
|
|
||||||
if (task->state == State_TryingManual) {
|
if (task->state == State_Manual) {
|
||||||
// Try the automatic one next
|
// Try the automatic one next
|
||||||
task->state = State_TryingAuto;
|
task->state = State_Automatic;
|
||||||
ProcessTask(task);
|
ProcessTask(task);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Give up
|
// Give up
|
||||||
emit ImageLoaded(task->id, QUrl(), 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));
|
||||||
emit ImageLoaded(task->id, QUrl(), task->options.default_output_image_, task->options.default_output_image_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(const Task &task) {
|
AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(Task *task) {
|
||||||
|
|
||||||
// An image embedded in the song itself takes priority
|
// An image embedded in the song itself takes priority
|
||||||
if (!task.embedded_image.isNull())
|
if (!task->embedded_image.isNull()) {
|
||||||
return TryLoadResult(false, true, QUrl(), ScaleAndPad(task.options, task.embedded_image));
|
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;
|
QUrl cover_url;
|
||||||
|
switch (task->state) {
|
||||||
switch (task.state) {
|
case State_None:
|
||||||
case State_TryingAuto: cover_url = task.art_automatic; break;
|
case State_Automatic:
|
||||||
case State_TryingManual: cover_url = task.art_manual; break;
|
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)
|
if (!cover_url.isEmpty() && !cover_url.path().isEmpty()) {
|
||||||
return TryLoadResult(false, true, QUrl(), task.options.default_output_image_);
|
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_filename.isEmpty()) {
|
}
|
||||||
const QImage taglib_image = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task.song_filename);
|
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())
|
if (!taglib_image.isNull()) {
|
||||||
return TryLoadResult(false, true, QUrl(), ScaleAndPad(task.options, taglib_image));
|
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, QUrl(), ScaleAndPad(task->options, taglib_image).first);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (cover_url.path().isEmpty()) {
|
else if (cover_url.isLocalFile()) {
|
||||||
return TryLoadResult(false, false, cover_url, task.options.default_output_image_);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (cover_url.isLocalFile()) {
|
|
||||||
QImage image(cover_url.toLocalFile());
|
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.
|
else if (cover_url.scheme().isEmpty()) { // Assume a local file with no scheme.
|
||||||
QImage image(cover_url.path());
|
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
|
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);
|
NewClosure(reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*, QUrl)), reply, cover_url);
|
||||||
|
|
||||||
remote_tasks_.insert(reply, task);
|
remote_tasks_.insert(reply, *task);
|
||||||
return TryLoadResult(true, false, cover_url, QImage());
|
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.
|
return; // Give up.
|
||||||
}
|
}
|
||||||
QNetworkRequest request = reply->request();
|
QNetworkRequest request = reply->request();
|
||||||
|
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||||
request.setUrl(redirect.toUrl());
|
request.setUrl(redirect.toUrl());
|
||||||
QNetworkReply *redirected_reply = network_->get(request);
|
QNetworkReply *redirected_reply = network_->get(request);
|
||||||
NewClosure(redirected_reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*, QUrl)), redirected_reply, redirect.toUrl());
|
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
|
// Try to load the image
|
||||||
QImage image;
|
QImage image;
|
||||||
if (image.load(reply, 0)) {
|
if (image.load(reply, 0)) {
|
||||||
QImage scaled = ScaleAndPad(task.options, image);
|
QPair<QImage, QImage> images = ScaleAndPad(task.options, image);
|
||||||
emit ImageLoaded(task.id, cover_url, scaled);
|
emit AlbumCoverLoaded(task.id, AlbumCoverLoaderResult(task.type, cover_url, image, images.first, images.second, task.art_updated));
|
||||||
emit ImageLoaded(task.id, cover_url, scaled, image);
|
|
||||||
return;
|
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);
|
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
|
// Scale the image down
|
||||||
QImage copy;
|
QImage image_scaled;
|
||||||
if (options.scale_output_image_) {
|
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 {
|
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_
|
QPainter p(&image_padded);
|
||||||
QImage padded_image(options.desired_height_, options.desired_height_, QImage::Format_ARGB32);
|
p.drawImage((options.desired_height_ - image_scaled.width()) / 2, (options.desired_height_ - image_scaled.height()) / 2, image_scaled);
|
||||||
padded_image.fill(0);
|
p.end();
|
||||||
|
|
||||||
QPainter p(&padded_image);
|
image_scaled = image_padded;
|
||||||
p.drawImage((options.desired_height_ - copy.width()) / 2, (options.desired_height_ - copy.height()) / 2, copy);
|
}
|
||||||
p.end();
|
|
||||||
|
|
||||||
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 <QtGlobal>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
|
#include <QPair>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QQueue>
|
#include <QQueue>
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "settings/collectionsettingspage.h"
|
#include "settings/collectionsettingspage.h"
|
||||||
#include "albumcoverloaderoptions.h"
|
#include "albumcoverloaderoptions.h"
|
||||||
|
#include "albumcoverloaderresult.h"
|
||||||
|
|
||||||
class QThread;
|
class QThread;
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
@@ -48,29 +50,36 @@ class AlbumCoverLoader : public QObject {
|
|||||||
public:
|
public:
|
||||||
explicit AlbumCoverLoader(QObject *parent = nullptr);
|
explicit AlbumCoverLoader(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
State_None,
|
||||||
|
State_Manual,
|
||||||
|
State_Automatic,
|
||||||
|
};
|
||||||
|
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
|
|
||||||
void ExitAsync();
|
void ExitAsync();
|
||||||
void Stop() { stop_requested_ = true; }
|
void Stop() { stop_requested_ = true; }
|
||||||
|
|
||||||
static QString ImageCacheDir(const Song::Source source);
|
static QString AlbumCoverFilename(QString artist, QString album);
|
||||||
QString CreateCoverFilename(const QString &artist, const 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 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);
|
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 CancelTask(const quint64 id);
|
||||||
void CancelTasks(const QSet<quint64> &ids);
|
void CancelTasks(const QSet<quint64> &ids);
|
||||||
|
|
||||||
static QPixmap TryLoadPixmap(const QUrl &automatic, const QUrl &manual, const QUrl &url = QUrl());
|
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:
|
signals:
|
||||||
void ExitFinished();
|
void ExitFinished();
|
||||||
void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image);
|
void AlbumCoverLoaded(quint64 id, AlbumCoverLoaderResult result);
|
||||||
void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &scaled, const QImage &original);
|
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void Exit();
|
void Exit();
|
||||||
@@ -78,38 +87,38 @@ class AlbumCoverLoader : public QObject {
|
|||||||
void RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url);
|
void RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
enum State {
|
|
||||||
State_TryingManual,
|
|
||||||
State_TryingAuto,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Task {
|
struct Task {
|
||||||
explicit Task() : redirects(0) {}
|
explicit Task() : id(0), state(State_None), type(AlbumCoverLoaderResult::Type_None), art_updated(false), redirects(0) {}
|
||||||
|
|
||||||
AlbumCoverLoaderOptions options;
|
AlbumCoverLoaderOptions options;
|
||||||
|
|
||||||
quint64 id;
|
quint64 id;
|
||||||
QUrl art_automatic;
|
|
||||||
QUrl art_manual;
|
QUrl art_manual;
|
||||||
QString song_filename;
|
QUrl art_automatic;
|
||||||
|
QUrl song_url;
|
||||||
|
Song song;
|
||||||
QImage embedded_image;
|
QImage embedded_image;
|
||||||
State state;
|
State state;
|
||||||
|
AlbumCoverLoaderResult::Type type;
|
||||||
|
bool art_updated;
|
||||||
int redirects;
|
int redirects;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TryLoadResult {
|
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 started_async;
|
||||||
bool loaded_success;
|
bool loaded_success;
|
||||||
|
|
||||||
|
AlbumCoverLoaderResult::Type type;
|
||||||
QUrl cover_url;
|
QUrl cover_url;
|
||||||
QImage image;
|
QImage image;
|
||||||
};
|
};
|
||||||
|
|
||||||
void ProcessTask(Task *task);
|
void ProcessTask(Task *task);
|
||||||
void NextState(Task *task);
|
void NextState(Task *task);
|
||||||
TryLoadResult TryLoadImage(const Task &task);
|
TryLoadResult TryLoadImage(Task *task);
|
||||||
|
|
||||||
bool stop_requested_;
|
bool stop_requested_;
|
||||||
|
|
||||||
|
|||||||
@@ -24,17 +24,24 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
|
#include <QSize>
|
||||||
|
|
||||||
struct AlbumCoverLoaderOptions {
|
struct AlbumCoverLoaderOptions {
|
||||||
explicit AlbumCoverLoaderOptions()
|
explicit AlbumCoverLoaderOptions()
|
||||||
: desired_height_(120),
|
: desired_height_(120),
|
||||||
scale_output_image_(true),
|
scale_output_image_(true),
|
||||||
pad_output_image_(true) {}
|
pad_output_image_(true),
|
||||||
|
create_thumbnail_(false),
|
||||||
|
pad_thumbnail_image_(false) {}
|
||||||
|
|
||||||
int desired_height_;
|
int desired_height_;
|
||||||
|
QSize thumbnail_size_;
|
||||||
bool scale_output_image_;
|
bool scale_output_image_;
|
||||||
bool pad_output_image_;
|
bool pad_output_image_;
|
||||||
|
bool create_thumbnail_;
|
||||||
|
bool pad_thumbnail_image_;
|
||||||
QImage default_output_image_;
|
QImage default_output_image_;
|
||||||
|
QImage default_thumbnail_image_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ALBUMCOVERLOADEROPTIONS_H
|
#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 "albumcoverexporter.h"
|
||||||
#include "albumcoverfetcher.h"
|
#include "albumcoverfetcher.h"
|
||||||
#include "albumcoverloader.h"
|
#include "albumcoverloader.h"
|
||||||
|
#include "albumcoverloaderoptions.h"
|
||||||
|
#include "albumcoverloaderresult.h"
|
||||||
#include "albumcovermanagerlist.h"
|
#include "albumcovermanagerlist.h"
|
||||||
#include "coversearchstatistics.h"
|
#include "coversearchstatistics.h"
|
||||||
#include "coversearchstatisticsdialog.h"
|
#include "coversearchstatisticsdialog.h"
|
||||||
@@ -216,7 +218,7 @@ void AlbumCoverManager::Init() {
|
|||||||
ui_->splitter->setSizes(QList<int>() << 200 << width() - 200);
|
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_);
|
cover_searcher_->Init(cover_fetcher_);
|
||||||
|
|
||||||
@@ -392,7 +394,7 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!info.art_automatic.isEmpty() || !info.art_manual.isEmpty()) {
|
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_PathAutomatic, info.art_automatic);
|
||||||
item->setData(Role_PathManual, info.art_manual);
|
item->setData(Role_PathManual, info.art_manual);
|
||||||
cover_loading_tasks_[id] = item;
|
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) {
|
void AlbumCoverManager::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) {
|
||||||
|
|
||||||
Q_UNUSED(cover_url);
|
|
||||||
|
|
||||||
if (!cover_loading_tasks_.contains(id)) return;
|
if (!cover_loading_tasks_.contains(id)) return;
|
||||||
|
|
||||||
QListWidgetItem *item = cover_loading_tasks_.take(id);
|
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();
|
UpdateFilter();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -488,7 +488,7 @@ void AlbumCoverManager::FetchAlbumCovers() {
|
|||||||
if (item->isHidden()) continue;
|
if (item->isHidden()) continue;
|
||||||
if (ItemHasCover(*item)) 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;
|
cover_fetching_tasks_[id] = item;
|
||||||
jobs_++;
|
jobs_++;
|
||||||
}
|
}
|
||||||
@@ -623,7 +623,7 @@ void AlbumCoverManager::ShowCover() {
|
|||||||
void AlbumCoverManager::FetchSingleCover() {
|
void AlbumCoverManager::FetchSingleCover() {
|
||||||
|
|
||||||
for (QListWidgetItem *item : context_menu_items_) {
|
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;
|
cover_fetching_tasks_[id] = item;
|
||||||
jobs_++;
|
jobs_++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "albumcoverloaderoptions.h"
|
#include "albumcoverloaderoptions.h"
|
||||||
|
#include "albumcoverloaderresult.h"
|
||||||
#include "coversearchstatistics.h"
|
#include "coversearchstatistics.h"
|
||||||
|
|
||||||
class QWidget;
|
class QWidget;
|
||||||
@@ -132,7 +133,7 @@ class AlbumCoverManager : public QMainWindow {
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void ArtistChanged(QListWidgetItem *current);
|
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 UpdateFilter();
|
||||||
void FetchAlbumCovers();
|
void FetchAlbumCovers();
|
||||||
void ExportCovers();
|
void ExportCovers();
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
|
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/utilities.h"
|
#include "core/utilities.h"
|
||||||
|
#include "core/logging.h"
|
||||||
#include "widgets/busyindicator.h"
|
#include "widgets/busyindicator.h"
|
||||||
#include "widgets/forcescrollperpixel.h"
|
#include "widgets/forcescrollperpixel.h"
|
||||||
#include "widgets/groupediconview.h"
|
#include "widgets/groupediconview.h"
|
||||||
@@ -55,6 +56,7 @@
|
|||||||
#include "albumcoverfetcher.h"
|
#include "albumcoverfetcher.h"
|
||||||
#include "albumcoverloader.h"
|
#include "albumcoverloader.h"
|
||||||
#include "albumcoverloaderoptions.h"
|
#include "albumcoverloaderoptions.h"
|
||||||
|
#include "albumcoverloaderresult.h"
|
||||||
#include "ui_albumcoversearcher.h"
|
#include "ui_albumcoversearcher.h"
|
||||||
|
|
||||||
const int SizeOverlayDelegate::kMargin = 4;
|
const int SizeOverlayDelegate::kMargin = 4;
|
||||||
@@ -129,8 +131,11 @@ AlbumCoverSearcher::AlbumCoverSearcher(const QIcon &no_cover_icon, Application *
|
|||||||
|
|
||||||
options_.scale_output_image_ = false;
|
options_.scale_output_image_ = false;
|
||||||
options_.pad_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_->search, SIGNAL(clicked()), SLOT(Search()));
|
||||||
connect(ui_->covers, SIGNAL(doubleClicked(QModelIndex)), SLOT(CoverDoubleClicked(QModelIndex)));
|
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) {
|
void AlbumCoverSearcher::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) {
|
||||||
|
|
||||||
Q_UNUSED(cover_url);
|
|
||||||
|
|
||||||
if (!cover_loading_tasks_.contains(id)) return;
|
if (!cover_loading_tasks_.contains(id)) return;
|
||||||
QStandardItem *item = cover_loading_tasks_.take(id);
|
QStandardItem *item = cover_loading_tasks_.take(id);
|
||||||
|
|
||||||
if (cover_loading_tasks_.isEmpty()) ui_->busy->hide();
|
if (cover_loading_tasks_.isEmpty()) ui_->busy->hide();
|
||||||
|
|
||||||
if (image.isNull()) {
|
if (result.image_original.isNull()) {
|
||||||
model_->removeRow(item->row());
|
model_->removeRow(item->row());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QIcon icon(QPixmap::fromImage(image));
|
QIcon icon;
|
||||||
|
icon.addPixmap(QPixmap::fromImage(result.image_original));
|
||||||
// Create a pixmap that's padded and exactly the right size for the icon.
|
icon.addPixmap(QPixmap::fromImage(result.image_thumbnail));
|
||||||
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));
|
|
||||||
|
|
||||||
item->setData(true, Role_ImageFetchFinished);
|
item->setData(true, Role_ImageFetchFinished);
|
||||||
item->setData(image.width() * image.height(), Role_ImageDimensions);
|
item->setData(result.image_original.width() * result.image_original.height(), Role_ImageDimensions);
|
||||||
item->setData(image.size(), Role_ImageSize);
|
item->setData(result.image_original.size(), Role_ImageSize);
|
||||||
item->setIcon(icon);
|
item->setIcon(icon);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
#include "albumcoverfetcher.h"
|
#include "albumcoverfetcher.h"
|
||||||
#include "albumcoverloaderoptions.h"
|
#include "albumcoverloaderoptions.h"
|
||||||
|
#include "albumcoverloaderresult.h"
|
||||||
|
|
||||||
class QWidget;
|
class QWidget;
|
||||||
class QStandardItem;
|
class QStandardItem;
|
||||||
@@ -88,7 +89,7 @@ class AlbumCoverSearcher : public QDialog {
|
|||||||
private slots:
|
private slots:
|
||||||
void Search();
|
void Search();
|
||||||
void SearchFinished(const quint64 id, const CoverSearchResults &results);
|
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);
|
void CoverDoubleClicked(const QModelIndex &index);
|
||||||
|
|
||||||
|
|||||||
@@ -26,5 +26,5 @@
|
|||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "coverprovider.h"
|
#include "coverprovider.h"
|
||||||
|
|
||||||
CoverProvider::CoverProvider(const QString &name, const float &quality, const bool &fetchall, Application *app, QObject *parent)
|
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) {}
|
: 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
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
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".
|
// A name (very short description) of this provider, like "last.fm".
|
||||||
QString name() const { return name_; }
|
QString name() const { return name_; }
|
||||||
bool quality() const { return quality_; }
|
bool quality() const { return quality_; }
|
||||||
bool fetchall() const { return fetchall_; }
|
bool fetchall() const { return fetchall_; }
|
||||||
|
bool allow_missing_album() const { return allow_missing_album_; }
|
||||||
|
|
||||||
// Starts searching for covers matching the given query text.
|
// Starts searching for covers matching the given query text.
|
||||||
// Returns true if the query has been started, or false if an error occurred.
|
// 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.
|
// 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); }
|
virtual void CancelSearch(int id) { Q_UNUSED(id); }
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ class CoverProvider : public QObject {
|
|||||||
QString name_;
|
QString name_;
|
||||||
float quality_;
|
float quality_;
|
||||||
bool fetchall_;
|
bool fetchall_;
|
||||||
|
bool allow_missing_album_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* 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
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
#include "playlist/playlistmanager.h"
|
#include "playlist/playlistmanager.h"
|
||||||
#include "albumcoverloader.h"
|
#include "albumcoverloader.h"
|
||||||
#include "albumcoverloaderoptions.h"
|
#include "albumcoverloaderoptions.h"
|
||||||
|
#include "albumcoverloaderresult.h"
|
||||||
#include "currentalbumcoverloader.h"
|
#include "currentalbumcoverloader.h"
|
||||||
|
|
||||||
CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *parent)
|
CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *parent)
|
||||||
@@ -43,56 +45,73 @@ CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *pare
|
|||||||
|
|
||||||
options_.scale_output_image_ = false;
|
options_.scale_output_image_ = false;
|
||||||
options_.pad_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_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)));
|
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(LoadAlbumCover(Song)));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentAlbumCoverLoader::~CurrentAlbumCoverLoader() {
|
CurrentAlbumCoverLoader::~CurrentAlbumCoverLoader() {
|
||||||
|
|
||||||
if (temp_cover_) temp_cover_->remove();
|
if (temp_cover_) temp_cover_->remove();
|
||||||
if (temp_cover_thumbnail_) temp_cover_thumbnail_->remove();
|
if (temp_cover_thumbnail_) temp_cover_thumbnail_->remove();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CurrentAlbumCoverLoader::LoadAlbumCover(const Song &song) {
|
void CurrentAlbumCoverLoader::LoadAlbumCover(const Song &song) {
|
||||||
|
|
||||||
last_song_ = song;
|
last_song_ = song;
|
||||||
id_ = app_->album_cover_loader()->LoadImageAsync(options_, last_song_);
|
id_ = app_->album_cover_loader()->LoadImageAsync(options_, last_song_);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, const QUrl &remote_url, const QImage &image) {
|
void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, AlbumCoverLoaderResult result) {
|
||||||
|
|
||||||
Q_UNUSED(remote_url);
|
|
||||||
|
|
||||||
if (id != id_) return;
|
if (id != id_) return;
|
||||||
id_ = 0;
|
id_ = 0;
|
||||||
|
|
||||||
QUrl cover_url;
|
if (!result.image_scaled.isNull()) {
|
||||||
QUrl thumbnail_url;
|
|
||||||
QImage thumbnail;
|
|
||||||
|
|
||||||
if (!image.isNull()) {
|
|
||||||
|
|
||||||
QString filename;
|
|
||||||
|
|
||||||
temp_cover_.reset(new QTemporaryFile(temp_file_pattern_));
|
temp_cover_.reset(new QTemporaryFile(temp_file_pattern_));
|
||||||
temp_cover_->setAutoRemove(true);
|
temp_cover_->setAutoRemove(true);
|
||||||
temp_cover_->open();
|
if (temp_cover_->open()) {
|
||||||
|
if (result.image_scaled.save(temp_cover_->fileName(), "JPEG")) {
|
||||||
image.save(temp_cover_->fileName(), "JPEG");
|
result.temp_cover_url = QUrl::fromLocalFile(temp_cover_->fileName());
|
||||||
|
}
|
||||||
// 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.
|
else {
|
||||||
temp_cover_thumbnail_.reset(new QTemporaryFile(temp_file_pattern_));
|
qLog(Error) << "Unable to save cover image to" << temp_cover_->fileName();
|
||||||
temp_cover_thumbnail_->open();
|
}
|
||||||
temp_cover_thumbnail_->setAutoRemove(true);
|
}
|
||||||
thumbnail = image.scaledToHeight(120, Qt::SmoothTransformation);
|
else {
|
||||||
thumbnail.save(temp_cover_thumbnail_->fileName(), "JPEG");
|
qLog(Error) << "Unable to open" << temp_cover_->fileName();
|
||||||
|
}
|
||||||
cover_url = QUrl::fromLocalFile(temp_cover_->fileName());
|
|
||||||
thumbnail_url = QUrl::fromLocalFile(temp_cover_thumbnail_->fileName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emit AlbumCoverLoaded(last_song_, cover_url, image);
|
QUrl thumbnail_url;
|
||||||
emit ThumbnailLoaded(last_song_, thumbnail_url, thumbnail);
|
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
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* 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
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
|
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "albumcoverloaderoptions.h"
|
#include "albumcoverloaderoptions.h"
|
||||||
|
#include "albumcoverloaderresult.h"
|
||||||
|
|
||||||
class Application;
|
class Application;
|
||||||
|
|
||||||
@@ -50,11 +52,11 @@ class CurrentAlbumCoverLoader : public QObject {
|
|||||||
void LoadAlbumCover(const Song &song);
|
void LoadAlbumCover(const Song &song);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image);
|
void AlbumCoverLoaded(Song song, AlbumCoverLoaderResult result);
|
||||||
void ThumbnailLoaded(const Song &song, const QUrl &thumbnail_uri, const QImage &image);
|
void ThumbnailLoaded(Song song, QUrl thumbnail_uri, QImage image);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void TempAlbumCoverLoaded(const quint64 id, const QUrl &remote_url, const QImage &image);
|
void TempAlbumCoverLoaded(const quint64 id, AlbumCoverLoaderResult result);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Application *app_;
|
Application *app_;
|
||||||
|
|||||||
@@ -50,16 +50,27 @@
|
|||||||
const char *DeezerCoverProvider::kApiUrl = "https://api.deezer.com";
|
const char *DeezerCoverProvider::kApiUrl = "https://api.deezer.com";
|
||||||
const int DeezerCoverProvider::kLimit = 10;
|
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 QPair<QString, QString> Param;
|
||||||
typedef QList<Param> Params;
|
typedef QList<Param> Params;
|
||||||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
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")
|
const Params params = Params() << Param("output", "json")
|
||||||
<< Param("q", QString(artist + " " + album))
|
<< Param("q", search)
|
||||||
<< Param("limit", QString::number(kLimit));
|
<< Param("limit", QString::number(kLimit));
|
||||||
|
|
||||||
QUrlQuery url_query;
|
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);
|
url_query.addQueryItem(encoded_param.first, encoded_param.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl url(kApiUrl + QString("/search/album"));
|
|
||||||
url.setQuery(url_query);
|
url.setQuery(url_query);
|
||||||
QNetworkRequest req(url);
|
QNetworkRequest req(url);
|
||||||
req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||||
@@ -220,19 +230,27 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
|
|||||||
for (const QJsonValue &value : json_data) {
|
for (const QJsonValue &value : json_data) {
|
||||||
|
|
||||||
if (!value.isObject()) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
QJsonObject json_obj = value.toObject();
|
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")) {
|
if (!json_obj.contains("id") || !json_album.contains("id")) {
|
||||||
Error("Invalid Json reply, item is missing ID or type.", json_obj);
|
Error("Invalid Json reply, object is missing ID.", json_obj);
|
||||||
continue;
|
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") {
|
if (type != "album") {
|
||||||
Error("Invalid Json reply, incorrect type returned", json_obj);
|
Error("Invalid Json reply, incorrect type returned", json_album);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +260,7 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id)
|
|||||||
}
|
}
|
||||||
QJsonValue json_value_artist = json_obj["artist"];
|
QJsonValue json_value_artist = json_obj["artist"];
|
||||||
if (!json_value_artist.isObject()) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
QJsonObject json_artist = json_value_artist.toObject();
|
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();
|
QString artist = json_artist["name"].toString();
|
||||||
|
|
||||||
if (!json_obj.contains("title")) {
|
if (!json_album.contains("title")) {
|
||||||
Error("Invalid Json reply, data missing title.", json_obj);
|
Error("Invalid Json reply, data missing title.", json_album);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
QString album = json_obj["title"].toString();
|
QString album = json_album["title"].toString();
|
||||||
|
|
||||||
QString cover;
|
QString cover;
|
||||||
if (json_obj.contains("cover_xl")) {
|
if (json_album.contains("cover_xl")) {
|
||||||
cover = json_obj["cover_xl"].toString();
|
cover = json_album["cover_xl"].toString();
|
||||||
}
|
}
|
||||||
else if (json_obj.contains("cover_big")) {
|
else if (json_album.contains("cover_big")) {
|
||||||
cover = json_obj["cover_big"].toString();
|
cover = json_album["cover_big"].toString();
|
||||||
}
|
}
|
||||||
else if (json_obj.contains("cover_medium")) {
|
else if (json_album.contains("cover_medium")) {
|
||||||
cover = json_obj["cover_medium"].toString();
|
cover = json_album["cover_medium"].toString();
|
||||||
}
|
}
|
||||||
else if (json_obj.contains("cover_small")) {
|
else if (json_album.contains("cover_small")) {
|
||||||
cover = json_obj["cover_small"].toString();
|
cover = json_album["cover_small"].toString();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Error("Invalid Json reply, data missing cover.", json_obj);
|
Error("Invalid Json reply, album missing cover.", json_album);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
QUrl url(cover);
|
QUrl url(cover);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class DeezerCoverProvider : public CoverProvider {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit DeezerCoverProvider(Application *app, QObject *parent = nullptr);
|
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);
|
void CancelSearch(const int id);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|||||||
@@ -59,9 +59,11 @@ const char *DiscogsCoverProvider::kUrlReleases = "https://api.discogs.com/releas
|
|||||||
const char *DiscogsCoverProvider::kAccessKeyB64 = "dGh6ZnljUGJlZ1NEeXBuSFFxSVk=";
|
const char *DiscogsCoverProvider::kAccessKeyB64 = "dGh6ZnljUGJlZ1NEeXBuSFFxSVk=";
|
||||||
const char *DiscogsCoverProvider::kSecretKeyB64 = "ZkFIcmlaSER4aHhRSlF2U3d0bm5ZVmdxeXFLWUl0UXI=";
|
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;
|
DiscogsCoverSearchContext *s_ctx = new DiscogsCoverSearchContext;
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class DiscogsCoverProvider : public CoverProvider {
|
|||||||
public:
|
public:
|
||||||
explicit DiscogsCoverProvider(Application *app, QObject *parent = nullptr);
|
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);
|
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::kApiKey = "211990b4c96782c05d1536e7219eb56e";
|
||||||
const char *LastFmCoverProvider::kSecret = "80fd738f49596e9709b1bf9319c444a8";
|
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<QString, QString> Param;
|
||||||
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
typedef QPair<QByteArray, QByteArray> EncodedParam;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class LastFmCoverProvider : public CoverProvider {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit LastFmCoverProvider(Application *app, QObject *parent = nullptr);
|
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:
|
private slots:
|
||||||
void QueryFinished(QNetworkReply *reply, const int id);
|
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 char *MusicbrainzCoverProvider::kAlbumCoverUrl = "https://coverartarchive.org/release/%1/front";
|
||||||
const int MusicbrainzCoverProvider::kLimit = 8;
|
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('"', "\\\""));
|
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:
|
public:
|
||||||
explicit MusicbrainzCoverProvider(Application *app, QObject *parent = nullptr);
|
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);
|
void CancelSearch(const int id);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|||||||
@@ -52,13 +52,15 @@ const char *TidalCoverProvider::kResourcesUrl = "https://resources.tidal.com";
|
|||||||
const int TidalCoverProvider::kLimit = 10;
|
const int TidalCoverProvider::kLimit = 10;
|
||||||
|
|
||||||
TidalCoverProvider::TidalCoverProvider(Application *app, QObject *parent) :
|
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>()),
|
service_(app->internet_services()->Service<TidalService>()),
|
||||||
network_(new NetworkAccessManager(this)) {
|
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;
|
if (!service_ || !service_->authenticated()) return false;
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class TidalCoverProvider : public CoverProvider {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TidalCoverProvider(Application *app, QObject *parent = nullptr);
|
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);
|
void CancelSearch(int id);
|
||||||
|
|
||||||
private slots:
|
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
|
#endif
|
||||||
#include "covermanager/albumcoverchoicecontroller.h"
|
#include "covermanager/albumcoverchoicecontroller.h"
|
||||||
#include "covermanager/albumcoverloader.h"
|
#include "covermanager/albumcoverloader.h"
|
||||||
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
#include "covermanager/coverproviders.h"
|
#include "covermanager/coverproviders.h"
|
||||||
#include "edittagdialog.h"
|
#include "edittagdialog.h"
|
||||||
#include "trackselectiondialog.h"
|
#include "trackselectiondialog.h"
|
||||||
@@ -106,9 +108,9 @@ EditTagDialog::EditTagDialog(Application *app, QWidget *parent)
|
|||||||
pending_(0)
|
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)
|
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
||||||
connect(tag_fetcher_, SIGNAL(ResultAvailable(Song, SongList)), results_dialog_, SLOT(FetchTagFinished(Song, SongList)), Qt::QueuedConnection);
|
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) {
|
void EditTagDialog::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) {
|
||||||
|
|
||||||
Q_UNUSED(cover_url);
|
|
||||||
|
|
||||||
if (id == cover_art_id_) {
|
if (id == cover_art_id_) {
|
||||||
ui_->art->setPixmap(QPixmap::fromImage(scaled));
|
ui_->art->setPixmap(QPixmap::fromImage(result.image_scaled));
|
||||||
original_ = original;
|
original_ = result.image_original;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
#include "core/tagreaderclient.h"
|
#include "core/tagreaderclient.h"
|
||||||
#include "playlist/playlistitem.h"
|
#include "playlist/playlistitem.h"
|
||||||
#include "covermanager/albumcoverloaderoptions.h"
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
|
|
||||||
class QWidget;
|
class QWidget;
|
||||||
class QMenu;
|
class QMenu;
|
||||||
@@ -110,7 +111,7 @@ class EditTagDialog : public QDialog {
|
|||||||
void FetchTag();
|
void FetchTag();
|
||||||
void FetchTagSongChosen(const Song &original_song, const Song &new_metadata);
|
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 LoadCoverFromFile();
|
||||||
void SaveCoverToFile();
|
void SaveCoverToFile();
|
||||||
|
|||||||
@@ -839,6 +839,16 @@ void GstEnginePipeline::TagMessageReceived(GstMessage *msg) {
|
|||||||
bundle.bitrate = ParseUIntTag(taglist, GST_TAG_BITRATE) / 1000;
|
bundle.bitrate = ParseUIntTag(taglist, GST_TAG_BITRATE) / 1000;
|
||||||
bundle.lyrics = ParseStrTag(taglist, GST_TAG_LYRICS);
|
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);
|
gst_tag_list_free(taglist);
|
||||||
|
|
||||||
emit MetadataFound(id(), bundle);
|
emit MetadataFound(id(), bundle);
|
||||||
|
|||||||
@@ -46,9 +46,11 @@ InternetPlaylistItem::InternetPlaylistItem(InternetService *service, const Song
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool InternetPlaylistItem::InitFromQuery(const SqlRow &query) {
|
bool InternetPlaylistItem::InitFromQuery(const SqlRow &query) {
|
||||||
|
|
||||||
metadata_.InitFromQuery(query, false, (Song::kColumns.count() + 1) * 1);
|
metadata_.InitFromQuery(query, false, (Song::kColumns.count() + 1) * 1);
|
||||||
InitMetadata();
|
InitMetadata();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant InternetPlaylistItem::DatabaseValue(DatabaseColumn column) const {
|
QVariant InternetPlaylistItem::DatabaseValue(DatabaseColumn column) const {
|
||||||
@@ -56,15 +58,26 @@ QVariant InternetPlaylistItem::DatabaseValue(DatabaseColumn column) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void InternetPlaylistItem::InitMetadata() {
|
void InternetPlaylistItem::InitMetadata() {
|
||||||
|
|
||||||
if (metadata_.title().isEmpty()) metadata_.set_title(metadata_.url().toString());
|
if (metadata_.title().isEmpty()) metadata_.set_title(metadata_.url().toString());
|
||||||
if (metadata_.source() == Song::Source_Unknown) metadata_.set_source(Song::Source_Stream);
|
if (metadata_.source() == Song::Source_Unknown) metadata_.set_source(Song::Source_Stream);
|
||||||
if (metadata_.filetype() == Song::FileType_Unknown) metadata_.set_filetype(Song::FileType_Stream);
|
if (metadata_.filetype() == Song::FileType_Unknown) metadata_.set_filetype(Song::FileType_Stream);
|
||||||
metadata_.set_valid(true);
|
metadata_.set_valid(true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Song InternetPlaylistItem::Metadata() const {
|
Song InternetPlaylistItem::Metadata() const {
|
||||||
|
|
||||||
if (HasTemporaryMetadata()) return temp_metadata_;
|
if (HasTemporaryMetadata()) return temp_metadata_;
|
||||||
return metadata_;
|
return metadata_;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl InternetPlaylistItem::Url() const { return metadata_.url(); }
|
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:
|
public:
|
||||||
explicit InternetPlaylistItem(const Song::Source &type);
|
explicit InternetPlaylistItem(const Song::Source &type);
|
||||||
InternetPlaylistItem(InternetService *service, const Song &metadata);
|
explicit InternetPlaylistItem(InternetService *service, const Song &metadata);
|
||||||
bool InitFromQuery(const SqlRow &query);
|
bool InitFromQuery(const SqlRow &query);
|
||||||
Song Metadata() const;
|
Song Metadata() const;
|
||||||
QUrl Url() const;
|
QUrl Url() const;
|
||||||
|
void SetArtManual(const QUrl &cover_url);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QVariant DatabaseValue(DatabaseColumn) const;
|
QVariant DatabaseValue(DatabaseColumn) const;
|
||||||
|
|||||||
@@ -27,11 +27,11 @@
|
|||||||
InternetSearchItemDelegate::InternetSearchItemDelegate(InternetSearchView *view)
|
InternetSearchItemDelegate::InternetSearchItemDelegate(InternetSearchView *view)
|
||||||
: CollectionItemDelegate(view), view_(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.
|
// 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
|
* Strawberry Music Player
|
||||||
* This code was part of Clementine (GlobalSearch)
|
* This code was part of Clementine (GlobalSearch)
|
||||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
* 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
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* 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) {
|
void InternetSearchModel::AddResults(const InternetSearchView::ResultList &results) {
|
||||||
|
|
||||||
int sort_index = 0;
|
|
||||||
|
|
||||||
for (const InternetSearchView::Result &result : results) {
|
for (const InternetSearchView::Result &result : results) {
|
||||||
QStandardItem *parent = invisibleRootItem();
|
QStandardItem *parent = invisibleRootItem();
|
||||||
|
|
||||||
// Find (or create) the container nodes for this result if we can.
|
// Find (or create) the container nodes for this result if we can.
|
||||||
ContainerKey key;
|
ContainerKey key;
|
||||||
key.provider_index_ = sort_index;
|
|
||||||
parent = BuildContainers(result.metadata_, parent, &key);
|
parent = BuildContainers(result.metadata_, parent, &key);
|
||||||
|
|
||||||
// Create the item
|
// Create the item
|
||||||
QStandardItem *item = new QStandardItem;
|
QStandardItem *item = new QStandardItem;
|
||||||
item->setText(result.metadata_.TitleWithCompilationArtist());
|
item->setText(result.metadata_.TitleWithCompilationArtist());
|
||||||
item->setData(QVariant::fromValue(result), Role_Result);
|
item->setData(QVariant::fromValue(result), Role_Result);
|
||||||
item->setData(sort_index, Role_ProviderIndex);
|
|
||||||
|
|
||||||
parent->appendRow(item);
|
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) {
|
if (level >= 3) {
|
||||||
return parent;
|
return parent;
|
||||||
@@ -249,7 +246,6 @@ QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem
|
|||||||
QStandardItem *container = containers_[*key];
|
QStandardItem *container = containers_[*key];
|
||||||
if (!container) {
|
if (!container) {
|
||||||
container = new QStandardItem(display_text);
|
container = new QStandardItem(display_text);
|
||||||
container->setData(key->provider_index_, Role_ProviderIndex);
|
|
||||||
container->setData(sort_text, CollectionModel::Role_SortText);
|
container->setData(sort_text, CollectionModel::Role_SortText);
|
||||||
container->setData(group_by_[level], CollectionModel::Role_ContainerType);
|
container->setData(group_by_[level], CollectionModel::Role_ContainerType);
|
||||||
|
|
||||||
@@ -275,8 +271,10 @@ QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem
|
|||||||
}
|
}
|
||||||
|
|
||||||
void InternetSearchModel::Clear() {
|
void InternetSearchModel::Clear() {
|
||||||
|
|
||||||
containers_.clear();
|
containers_.clear();
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InternetSearchView::ResultList InternetSearchModel::GetChildResults(const QModelIndexList &indexes) const {
|
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());
|
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.
|
// 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 proxy_index = parent_proxy_index.model()->index(i, 0, parent_proxy_index);
|
||||||
const QModelIndex index = proxy_->mapToSource(proxy_index);
|
const QModelIndex index = proxy_->mapToSource(proxy_index);
|
||||||
GetChildResults(itemFromIndex(index), results, visited);
|
GetChildResults(itemFromIndex(index), results, visited);
|
||||||
@@ -326,21 +324,6 @@ void InternetSearchModel::GetChildResults(const QStandardItem *item, InternetSea
|
|||||||
if (result.isValid()) {
|
if (result.isValid()) {
|
||||||
results->append(result.value<InternetSearchView::Result>());
|
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);
|
(*results).append(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < parent->rowCount(); ++i) {
|
for (int i = 0 ; i < parent->rowCount() ; ++i) {
|
||||||
GatherResults(parent->child(i), results);
|
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_;
|
const CollectionModel::Grouping old_group_by = group_by_;
|
||||||
group_by_ = grouping;
|
group_by_ = grouping;
|
||||||
@@ -389,24 +372,17 @@ MimeData *InternetSearchModel::LoadTracks(const InternetSearchView::ResultList &
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
InternetSearchView::ResultList results_copy;
|
|
||||||
for (const InternetSearchView::Result &result : results) {
|
|
||||||
results_copy << result;
|
|
||||||
}
|
|
||||||
|
|
||||||
SongList songs;
|
SongList songs;
|
||||||
|
QList<QUrl> urls;
|
||||||
for (const InternetSearchView::Result &result : results) {
|
for (const InternetSearchView::Result &result : results) {
|
||||||
songs << result.metadata_;
|
songs << result.metadata_;
|
||||||
|
urls << result.metadata_.url();
|
||||||
}
|
}
|
||||||
|
|
||||||
InternetSongMimeData *internet_song_mime_data = new InternetSongMimeData(service_);
|
InternetSongMimeData *internet_song_mime_data = new InternetSongMimeData(service_);
|
||||||
internet_song_mime_data->songs = songs;
|
internet_song_mime_data->songs = songs;
|
||||||
MimeData *mime_data = internet_song_mime_data;
|
MimeData *mime_data = internet_song_mime_data;
|
||||||
|
|
||||||
QList<QUrl> urls;
|
|
||||||
for (const InternetSearchView::Result &result : results) {
|
|
||||||
urls << result.metadata_.url();
|
|
||||||
}
|
|
||||||
mime_data->setUrls(urls);
|
mime_data->setUrls(urls);
|
||||||
|
|
||||||
return mime_data;
|
return mime_data;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This code was part of Clementine (GlobalSearch)
|
* This code was part of Clementine (GlobalSearch)
|
||||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
* 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
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -54,18 +55,16 @@ class InternetSearchModel : public QStandardItemModel {
|
|||||||
enum Role {
|
enum Role {
|
||||||
Role_Result = CollectionModel::LastRole,
|
Role_Result = CollectionModel::LastRole,
|
||||||
Role_LazyLoadingArt,
|
Role_LazyLoadingArt,
|
||||||
Role_ProviderIndex,
|
|
||||||
LastRole
|
LastRole
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ContainerKey {
|
struct ContainerKey {
|
||||||
int provider_index_;
|
|
||||||
QString group_[3];
|
QString group_[3];
|
||||||
};
|
};
|
||||||
|
|
||||||
void set_proxy(QSortFilterProxyModel *proxy) { proxy_ = proxy; }
|
void set_proxy(QSortFilterProxyModel *proxy) { proxy_ = proxy; }
|
||||||
void set_use_pretty_covers(const bool pretty) { use_pretty_covers_ = pretty; }
|
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();
|
void Clear();
|
||||||
|
|
||||||
@@ -82,7 +81,7 @@ class InternetSearchModel : public QStandardItemModel {
|
|||||||
void AddResults(const InternetSearchView::ResultList &results);
|
void AddResults(const InternetSearchView::ResultList &results);
|
||||||
|
|
||||||
private:
|
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;
|
void GetChildResults(const QStandardItem *item, InternetSearchView::ResultList *results, QSet<const QStandardItem*> *visited) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -98,7 +97,7 @@ class InternetSearchModel : public QStandardItemModel {
|
|||||||
};
|
};
|
||||||
|
|
||||||
inline uint qHash(const InternetSearchModel::ContainerKey &key) {
|
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) {
|
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 true; \
|
||||||
if (left.field > right.field) return false
|
if (left.field > right.field) return false
|
||||||
|
|
||||||
CMP(provider_index_);
|
|
||||||
CMP(group_[0]);
|
CMP(group_[0]);
|
||||||
CMP(group_[1]);
|
CMP(group_[1]);
|
||||||
CMP(group_[2]);
|
CMP(group_[2]);
|
||||||
|
|||||||
@@ -32,15 +32,9 @@
|
|||||||
#include "internetsearchsortmodel.h"
|
#include "internetsearchsortmodel.h"
|
||||||
#include "internetsearchview.h"
|
#include "internetsearchview.h"
|
||||||
|
|
||||||
InternetSearchSortModel::InternetSearchSortModel(QObject *parent)
|
InternetSearchSortModel::InternetSearchSortModel(QObject *parent) : QSortFilterProxyModel(parent) {}
|
||||||
: QSortFilterProxyModel(parent) {}
|
|
||||||
|
|
||||||
bool InternetSearchSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const {
|
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
|
// Dividers always go first
|
||||||
if (left.data(CollectionModel::Role_IsDivider).toBool()) return true;
|
if (left.data(CollectionModel::Role_IsDivider).toBool()) return true;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QPair>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
@@ -69,10 +71,13 @@
|
|||||||
#include "core/mimedata.h"
|
#include "core/mimedata.h"
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
#include "core/logging.h"
|
||||||
#include "collection/collectionfilterwidget.h"
|
#include "collection/collectionfilterwidget.h"
|
||||||
#include "collection/collectionmodel.h"
|
#include "collection/collectionmodel.h"
|
||||||
#include "collection/groupbydialog.h"
|
#include "collection/groupbydialog.h"
|
||||||
#include "covermanager/albumcoverloader.h"
|
#include "covermanager/albumcoverloader.h"
|
||||||
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
#include "internetsongmimedata.h"
|
#include "internetsongmimedata.h"
|
||||||
#include "internetservice.h"
|
#include "internetservice.h"
|
||||||
#include "internetsearchitemdelegate.h"
|
#include "internetsearchitemdelegate.h"
|
||||||
@@ -102,11 +107,11 @@ InternetSearchView::InternetSearchView(QWidget *parent)
|
|||||||
back_proxy_(new InternetSearchSortModel(this)),
|
back_proxy_(new InternetSearchSortModel(this)),
|
||||||
current_proxy_(front_proxy_),
|
current_proxy_(front_proxy_),
|
||||||
swap_models_timer_(new QTimer(this)),
|
swap_models_timer_(new QTimer(this)),
|
||||||
|
use_pretty_covers_(true),
|
||||||
search_type_(InternetSearchView::SearchType_Artists),
|
search_type_(InternetSearchView::SearchType_Artists),
|
||||||
search_error_(false),
|
search_error_(false),
|
||||||
last_search_id_(0),
|
last_search_id_(0),
|
||||||
searches_next_id_(1),
|
searches_next_id_(1) {
|
||||||
art_searches_next_id_(1) {
|
|
||||||
|
|
||||||
ui_->setupUi(this);
|
ui_->setupUi(this);
|
||||||
|
|
||||||
@@ -164,7 +169,7 @@ void InternetSearchView::Init(Application *app, InternetService *service) {
|
|||||||
|
|
||||||
current_model_ = front_model_;
|
current_model_ = front_model_;
|
||||||
current_proxy_ = front_proxy_;
|
current_proxy_ = front_proxy_;
|
||||||
|
|
||||||
// Set up the sorting proxy model
|
// Set up the sorting proxy model
|
||||||
front_proxy_->setSourceModel(front_model_);
|
front_proxy_->setSourceModel(front_model_);
|
||||||
front_proxy_->setDynamicSortFilter(true);
|
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_albums, SIGNAL(clicked(bool)), SLOT(SearchAlbumsClicked(bool)));
|
||||||
connect(ui_->radiobutton_search_songs, SIGNAL(clicked(bool)), SLOT(SearchSongsClicked(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(group_by_actions_, SIGNAL(triggered(QAction*)), SLOT(GroupByClicked(QAction*)));
|
||||||
|
|
||||||
connect(ui_->search, SIGNAL(textChanged(QString)), SLOT(TextEdited(QString)));
|
connect(ui_->search, SIGNAL(textChanged(QString)), SLOT(TextEdited(QString)));
|
||||||
connect(ui_->results, SIGNAL(AddToPlaylistSignal(QMimeData*)), SIGNAL(AddToPlaylist(QMimeData*)));
|
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(service_, SIGNAL(SearchResults(int, SongList, QString)), SLOT(SearchDone(int, SongList, QString)));
|
||||||
|
|
||||||
connect(app_, SIGNAL(SettingsChanged()), SLOT(ReloadSettings()));
|
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();
|
ReloadSettings();
|
||||||
|
|
||||||
@@ -214,9 +220,9 @@ void InternetSearchView::ReloadSettings() {
|
|||||||
// Collection settings
|
// Collection settings
|
||||||
|
|
||||||
s.beginGroup(service_->settings_group());
|
s.beginGroup(service_->settings_group());
|
||||||
const bool pretty = s.value("pretty_covers", true).toBool();
|
use_pretty_covers_ = s.value("pretty_covers", true).toBool();
|
||||||
front_model_->set_use_pretty_covers(pretty);
|
front_model_->set_use_pretty_covers(use_pretty_covers_);
|
||||||
back_model_->set_use_pretty_covers(pretty);
|
back_model_->set_use_pretty_covers(use_pretty_covers_);
|
||||||
|
|
||||||
// Internet search settings
|
// 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) {
|
bool InternetSearchView::eventFilter(QObject *object, QEvent *e) {
|
||||||
|
|
||||||
if (object == ui_->search && e->type() == QEvent::KeyRelease) {
|
if (object == ui_->search && e->type() == QEvent::KeyRelease) {
|
||||||
@@ -374,6 +374,7 @@ void InternetSearchView::TextEdited(const QString &text) {
|
|||||||
const QString trimmed(text.trimmed());
|
const QString trimmed(text.trimmed());
|
||||||
|
|
||||||
search_error_ = false;
|
search_error_ = false;
|
||||||
|
cover_loader_tasks_.clear();
|
||||||
|
|
||||||
// Add results to the back model, switch models after some delay.
|
// Add results to the back model, switch models after some delay.
|
||||||
back_model_->Clear();
|
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 text query is empty, don't start a new search
|
||||||
if (trimmed.isEmpty()) {
|
if (trimmed.isEmpty()) {
|
||||||
last_search_id_ = -1;
|
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_->label_status->clear();
|
||||||
ui_->progressbar->hide();
|
ui_->progressbar->hide();
|
||||||
ui_->progressbar->reset();
|
ui_->progressbar->reset();
|
||||||
@@ -401,7 +402,7 @@ void InternetSearchView::TextEdited(const QString &text) {
|
|||||||
|
|
||||||
void InternetSearchView::SwapModels() {
|
void InternetSearchView::SwapModels() {
|
||||||
|
|
||||||
art_requests_.clear();
|
cover_loader_tasks_.clear();
|
||||||
|
|
||||||
std::swap(front_model_, back_model_);
|
std::swap(front_model_, back_model_);
|
||||||
std::swap(front_proxy_, back_proxy_);
|
std::swap(front_proxy_, back_proxy_);
|
||||||
@@ -488,10 +489,8 @@ void InternetSearchView::SearchDone(const int service_id, const SongList &songs,
|
|||||||
results << result;
|
results << result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (results.isEmpty()) return;
|
|
||||||
|
|
||||||
// Load cached pixmaps into the results
|
// 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);
|
it->pixmap_cache_key_ = PixmapCacheKey(*it);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,7 +575,7 @@ MimeData *InternetSearchView::SelectedMimeData() {
|
|||||||
QModelIndexList indexes = ui_->results->selectionModel()->selectedRows();
|
QModelIndexList indexes = ui_->results->selectionModel()->selectedRows();
|
||||||
if (indexes.isEmpty()) {
|
if (indexes.isEmpty()) {
|
||||||
// There's nothing selected - take the first thing in the model that isn't a divider.
|
// 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);
|
QModelIndex index = front_proxy_->index(i, 0);
|
||||||
if (!index.data(CollectionModel::Role_IsDivider).toBool()) {
|
if (!index.data(CollectionModel::Role_IsDivider).toBool()) {
|
||||||
indexes << index;
|
indexes << index;
|
||||||
@@ -663,7 +662,7 @@ void InternetSearchView::GroupByClicked(QAction *action) {
|
|||||||
if (action->property("group_by").isNull()) {
|
if (action->property("group_by").isNull()) {
|
||||||
if (!group_by_dialog_) {
|
if (!group_by_dialog_) {
|
||||||
group_by_dialog_.reset(new GroupByDialog);
|
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();
|
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,
|
// 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
|
// 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)
|
// songs when they will be displayed again anyway (when InternetSearchItemDelegate::paint will call LazyLoadAlbumCover)
|
||||||
art_requests_.clear();
|
cover_loader_tasks_.clear();
|
||||||
|
|
||||||
// Update the models
|
// Update the models
|
||||||
front_model_->SetGroupBy(g, true);
|
front_model_->SetGroupBy(g, true);
|
||||||
back_model_->SetGroupBy(g, false);
|
back_model_->SetGroupBy(g, false);
|
||||||
@@ -722,10 +722,12 @@ void InternetSearchView::SearchSongsClicked(const bool) {
|
|||||||
void InternetSearchView::SetSearchType(const InternetSearchView::SearchType type) {
|
void InternetSearchView::SetSearchType(const InternetSearchView::SearchType type) {
|
||||||
|
|
||||||
search_type_ = type;
|
search_type_ = type;
|
||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(service_->settings_group());
|
s.beginGroup(service_->settings_group());
|
||||||
s.setValue("type", int(search_type_));
|
s.setValue("type", int(search_type_));
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
TextEdited(ui_->search->text());
|
TextEdited(ui_->search->text());
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -761,110 +763,101 @@ void InternetSearchView::AddSongs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString InternetSearchView::PixmapCacheKey(const InternetSearchView::Result &result) const {
|
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 {
|
bool InternetSearchView::FindCachedPixmap(const InternetSearchView::Result &result, QPixmap *pixmap) const {
|
||||||
return pixmap_cache_.find(result.pixmap_cache_key_, pixmap);
|
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) {
|
void InternetSearchView::LazyLoadAlbumCover(const QModelIndex &proxy_index) {
|
||||||
|
|
||||||
if (!proxy_index.isValid() || proxy_index.model() != front_proxy_) {
|
if (!proxy_index.isValid() || proxy_index.model() != front_proxy_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QModelIndex source_index = front_proxy_->mapToSource(proxy_index);
|
||||||
|
if (!source_index.isValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Already loading art for this item?
|
// Already loading art for this item?
|
||||||
if (proxy_index.data(InternetSearchModel::Role_LazyLoadingArt).isValid()) {
|
if (source_index.data(InternetSearchModel::Role_LazyLoadingArt).isValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should we even load art at all?
|
// Should we even load art at all?
|
||||||
if (!app_->collection_model()->use_pretty_covers()) {
|
if (!use_pretty_covers_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this an album?
|
// Is this an album?
|
||||||
const CollectionModel::GroupBy container_type = CollectionModel::GroupBy(proxy_index.data(CollectionModel::Role_ContainerType).toInt());
|
const CollectionModel::GroupBy container_type = CollectionModel::GroupBy(proxy_index.data(CollectionModel::Role_ContainerType).toInt());
|
||||||
if (container_type != CollectionModel::GroupBy_Album &&
|
if (container_type != CollectionModel::GroupBy_Album &&
|
||||||
|
container_type != CollectionModel::GroupBy_AlbumDisc &&
|
||||||
container_type != CollectionModel::GroupBy_YearAlbum &&
|
container_type != CollectionModel::GroupBy_YearAlbum &&
|
||||||
|
container_type != CollectionModel::GroupBy_YearAlbumDisc &&
|
||||||
container_type != CollectionModel::GroupBy_OriginalYearAlbum) {
|
container_type != CollectionModel::GroupBy_OriginalYearAlbum) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the item as loading art
|
// Mark the item as loading art
|
||||||
const QModelIndex source_index = front_proxy_->mapToSource(proxy_index);
|
|
||||||
QStandardItem *item = front_model_->itemFromIndex(source_index);
|
QStandardItem *item_album = front_model_->itemFromIndex(source_index);
|
||||||
item->setData(true, InternetSearchModel::Role_LazyLoadingArt);
|
if (!item_album) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
item_album->setData(true, InternetSearchModel::Role_LazyLoadingArt);
|
||||||
|
|
||||||
// Walk down the item's children until we find a track
|
// Walk down the item's children until we find a track
|
||||||
while (item->rowCount()) {
|
QStandardItem *item_song = item_album;
|
||||||
item = item->child(0);
|
while (item_song->rowCount()) {
|
||||||
|
item_song = item_song->child(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the track's Result
|
// 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.
|
QPixmap cached_pixmap;
|
||||||
int id = LoadAlbumCoverAsync(result);
|
if (pixmap_cache_.find(result.pixmap_cache_key_, &cached_pixmap)) {
|
||||||
art_requests_[id] = source_index;
|
item_album->setData(cached_pixmap, Qt::DecorationRole);
|
||||||
|
}
|
||||||
}
|
else {
|
||||||
|
quint64 loader_id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, result.metadata_);
|
||||||
void InternetSearchView::AlbumCoverLoaded(const quint64 id, const QUrl&, const QImage &image) {
|
cover_loader_tasks_[loader_id] = qMakePair(source_index, result.pixmap_cache_key_);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
if (idx.isValid()) {
|
||||||
|
QStandardItem *item = front_model_->itemFromIndex(idx);
|
||||||
// Scale the image down
|
if (item) {
|
||||||
QImage copy;
|
item->setData(albumcover_result.image_scaled, Qt::DecorationRole);
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,13 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
|
#include <QPair>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
@@ -36,12 +39,12 @@
|
|||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QPixmapCache>
|
#include <QPixmapCache>
|
||||||
#include <QScopedPointer>
|
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
|
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "collection/collectionmodel.h"
|
#include "collection/collectionmodel.h"
|
||||||
#include "covermanager/albumcoverloaderoptions.h"
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
#include "settings/settingsdialog.h"
|
#include "settings/settingsdialog.h"
|
||||||
|
|
||||||
class QSortFilterProxyModel;
|
class QSortFilterProxyModel;
|
||||||
@@ -53,14 +56,12 @@ class QActionGroup;
|
|||||||
class QEvent;
|
class QEvent;
|
||||||
class QKeyEvent;
|
class QKeyEvent;
|
||||||
class QShowEvent;
|
class QShowEvent;
|
||||||
class QHideEvent;
|
|
||||||
class QContextMenuEvent;
|
class QContextMenuEvent;
|
||||||
class QTimerEvent;
|
class QTimerEvent;
|
||||||
|
|
||||||
class Application;
|
class Application;
|
||||||
class MimeData;
|
class MimeData;
|
||||||
class GroupByDialog;
|
class GroupByDialog;
|
||||||
class AlbumCoverLoader;
|
|
||||||
class InternetService;
|
class InternetService;
|
||||||
class InternetSearchModel;
|
class InternetSearchModel;
|
||||||
class Ui_InternetSearchView;
|
class Ui_InternetSearchView;
|
||||||
@@ -104,7 +105,6 @@ class InternetSearchView : public QWidget {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void showEvent(QShowEvent *e);
|
void showEvent(QShowEvent *e);
|
||||||
void hideEvent(QHideEvent *e);
|
|
||||||
bool eventFilter(QObject *object, QEvent *e);
|
bool eventFilter(QObject *object, QEvent *e);
|
||||||
void timerEvent(QTimerEvent *e);
|
void timerEvent(QTimerEvent *e);
|
||||||
|
|
||||||
@@ -135,7 +135,6 @@ class InternetSearchView : public QWidget {
|
|||||||
|
|
||||||
QString PixmapCacheKey(const Result &result) const;
|
QString PixmapCacheKey(const Result &result) const;
|
||||||
bool FindCachedPixmap(const Result &result, QPixmap *pixmap) const;
|
bool FindCachedPixmap(const Result &result, QPixmap *pixmap) const;
|
||||||
static QImage ScaleAndPad(const QImage &image);
|
|
||||||
int LoadAlbumCoverAsync(const Result &result);
|
int LoadAlbumCoverAsync(const Result &result);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
@@ -173,7 +172,7 @@ class InternetSearchView : public QWidget {
|
|||||||
void GroupByClicked(QAction *action);
|
void GroupByClicked(QAction *action);
|
||||||
void SetGroupBy(const CollectionModel::Grouping &g);
|
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:
|
public slots:
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
@@ -187,7 +186,7 @@ class InternetSearchView : public QWidget {
|
|||||||
Application *app_;
|
Application *app_;
|
||||||
InternetService *service_;
|
InternetService *service_;
|
||||||
Ui_InternetSearchView *ui_;
|
Ui_InternetSearchView *ui_;
|
||||||
QScopedPointer<GroupByDialog> group_by_dialog_;
|
std::unique_ptr<GroupByDialog> group_by_dialog_;
|
||||||
|
|
||||||
QMenu *context_menu_;
|
QMenu *context_menu_;
|
||||||
QList<QAction*> context_actions_;
|
QList<QAction*> context_actions_;
|
||||||
@@ -206,18 +205,17 @@ class InternetSearchView : public QWidget {
|
|||||||
|
|
||||||
QTimer *swap_models_timer_;
|
QTimer *swap_models_timer_;
|
||||||
|
|
||||||
|
bool use_pretty_covers_;
|
||||||
SearchType search_type_;
|
SearchType search_type_;
|
||||||
bool search_error_;
|
bool search_error_;
|
||||||
int last_search_id_;
|
int last_search_id_;
|
||||||
int searches_next_id_;
|
int searches_next_id_;
|
||||||
int art_searches_next_id_;
|
|
||||||
|
|
||||||
QMap<int, DelayedSearch> delayed_searches_;
|
QMap<int, DelayedSearch> delayed_searches_;
|
||||||
QMap<int, PendingState> pending_searches_;
|
QMap<int, PendingState> pending_searches_;
|
||||||
QMap<int, QString> pending_art_searches_;
|
|
||||||
QMap<int, QModelIndex> art_requests_;
|
|
||||||
AlbumCoverLoaderOptions cover_loader_options_;
|
AlbumCoverLoaderOptions cover_loader_options_;
|
||||||
QMap<quint64, quint64> cover_loader_tasks_;
|
QMap<quint64, QPair<QModelIndex, QString>> cover_loader_tasks_;
|
||||||
QPixmapCache pixmap_cache_;
|
QPixmapCache pixmap_cache_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
#include "collection/collection.h"
|
#include "collection/collection.h"
|
||||||
#include "collection/collectionbackend.h"
|
#include "collection/collectionbackend.h"
|
||||||
#include "collection/collectionplaylistitem.h"
|
#include "collection/collectionplaylistitem.h"
|
||||||
|
#include "covermanager/albumcoverloader.h"
|
||||||
#include "queue/queue.h"
|
#include "queue/queue.h"
|
||||||
#include "playlist.h"
|
#include "playlist.h"
|
||||||
#include "playlistitem.h"
|
#include "playlistitem.h"
|
||||||
@@ -88,12 +89,6 @@
|
|||||||
|
|
||||||
using std::placeholders::_1;
|
using std::placeholders::_1;
|
||||||
using std::placeholders::_2;
|
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::kCddaMimeType = "x-content/audio-cdda";
|
||||||
const char *Playlist::kRowsMimetype = "application/x-strawberry-playlist-rows";
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -392,7 +388,9 @@ void Playlist::SongSaveComplete(TagReaderReply *reply, const QPersistentModelInd
|
|||||||
|
|
||||||
if (reply->is_successful() && index.isValid()) {
|
if (reply->is_successful() && index.isValid()) {
|
||||||
if (reply->message().save_file_response().success()) {
|
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);
|
NewClosure(future, this, SLOT(ItemReloadComplete(QPersistentModelIndex)), index);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -406,6 +404,20 @@ void Playlist::SongSaveComplete(TagReaderReply *reply, const QPersistentModelInd
|
|||||||
void Playlist::ItemReloadComplete(const QPersistentModelIndex &index) {
|
void Playlist::ItemReloadComplete(const QPersistentModelIndex &index) {
|
||||||
|
|
||||||
if (index.isValid()) {
|
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 dataChanged(index, index);
|
||||||
emit EditingFinished(index);
|
emit EditingFinished(index);
|
||||||
}
|
}
|
||||||
@@ -879,7 +891,7 @@ void Playlist::InsertItems(const PlaylistItemList &itemsIn, int pos, bool play_n
|
|||||||
// exercise vetoes
|
// exercise vetoes
|
||||||
SongList songs;
|
SongList songs;
|
||||||
|
|
||||||
for (const PlaylistItemPtr item : items) {
|
for (PlaylistItemPtr item : items) {
|
||||||
songs << item->Metadata();
|
songs << item->Metadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1004,7 +1016,7 @@ void Playlist::InsertInternetItems(InternetService *service, const SongList &son
|
|||||||
|
|
||||||
PlaylistItemList playlist_items;
|
PlaylistItemList playlist_items;
|
||||||
for (const Song &song : songs) {
|
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);
|
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) {
|
void Playlist::UpdateItems(const SongList &songs) {
|
||||||
|
|
||||||
qLog(Debug) << "Updating playlist with new tracks' info";
|
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.
|
// 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
|
// 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,
|
// 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);
|
QMutableLinkedListIterator<Song> it(songs_list);
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
const Song &song = it.next();
|
const Song &song = it.next();
|
||||||
PlaylistItemPtr &item = items_[i];
|
const PlaylistItemPtr &item = items_[i];
|
||||||
if (item->Metadata().url() == song.url() &&
|
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()
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
PlaylistItemPtr new_item;
|
PlaylistItemPtr new_item;
|
||||||
if (song.is_collection_song()) {
|
if (song.is_collection_song()) {
|
||||||
new_item = PlaylistItemPtr(new CollectionPlaylistItem(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);
|
collection_items_by_id_.insertMulti(song.id(), new_item);
|
||||||
}
|
}
|
||||||
else {
|
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;
|
std::shared_ptr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
|
||||||
shared_ptr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
|
std::shared_ptr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
|
||||||
|
|
||||||
#define cmp(field) return a->Metadata().field() < b->Metadata().field()
|
#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;
|
#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;
|
std::shared_ptr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
|
||||||
shared_ptr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
|
std::shared_ptr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
|
||||||
|
|
||||||
int a_dir_level = a->Url().path().count('/');
|
int a_dir_level = a->Url().path().count('/');
|
||||||
int b_dir_level = b->Url().path().count('/');
|
int b_dir_level = b->Url().path().count('/');
|
||||||
@@ -1293,6 +1298,7 @@ void Playlist::SetCurrentIsPaused(bool paused) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Playlist::Save() const {
|
void Playlist::Save() const {
|
||||||
|
|
||||||
if (!backend_ || is_loading_) return;
|
if (!backend_ || is_loading_) return;
|
||||||
|
|
||||||
backend_->SavePlaylistAsync(id_, items_, last_played_row());
|
backend_->SavePlaylistAsync(id_, items_, last_played_row());
|
||||||
@@ -1443,7 +1449,7 @@ PlaylistItemList Playlist::RemoveItemsWithoutUndo(int row, int count) {
|
|||||||
|
|
||||||
if (item->source() == Song::Source_Collection) {
|
if (item->source() == Song::Source_Collection) {
|
||||||
int id = item->Metadata().id();
|
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);
|
collection_items_by_id_.remove(id, item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1755,22 +1761,26 @@ void Playlist::set_sequence(PlaylistSequence *v) {
|
|||||||
QSortFilterProxyModel *Playlist::proxy() const { return proxy_; }
|
QSortFilterProxyModel *Playlist::proxy() const { return proxy_; }
|
||||||
|
|
||||||
SongList Playlist::GetAllSongs() const {
|
SongList Playlist::GetAllSongs() const {
|
||||||
|
|
||||||
SongList ret;
|
SongList ret;
|
||||||
for (const PlaylistItemPtr item : items_) {
|
for (PlaylistItemPtr item : items_) {
|
||||||
ret << item->Metadata();
|
ret << item->Metadata();
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistItemList Playlist::GetAllItems() const { return items_; }
|
PlaylistItemList Playlist::GetAllItems() const { return items_; }
|
||||||
|
|
||||||
quint64 Playlist::GetTotalLength() const {
|
quint64 Playlist::GetTotalLength() const {
|
||||||
|
|
||||||
quint64 ret = 0;
|
quint64 ret = 0;
|
||||||
for (const PlaylistItemPtr item : items_) {
|
for (PlaylistItemPtr item : items_) {
|
||||||
quint64 length = item->Metadata().length_nanosec();
|
quint64 length = item->Metadata().length_nanosec();
|
||||||
if (length > 0) ret += length;
|
if (length > 0) ret += length;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistItemList Playlist::collection_items_by_id(int id) const {
|
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) {
|
void Playlist::TracksAboutToBeDequeued(const QModelIndex&, int begin, int end) {
|
||||||
|
|
||||||
for (int i = begin; i <= end; ++i) {
|
for (int i = begin; i <= end; ++i) {
|
||||||
temp_dequeue_change_indexes_ << queue_->mapToSource(queue_->index(i, Column_Title));
|
temp_dequeue_change_indexes_ << queue_->mapToSource(queue_->index(i, Column_Title));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Playlist::TracksDequeued() {
|
void Playlist::TracksDequeued() {
|
||||||
|
|
||||||
for (const QModelIndex &index : temp_dequeue_change_indexes_) {
|
for (const QModelIndex &index : temp_dequeue_change_indexes_) {
|
||||||
emit dataChanged(index, index);
|
emit dataChanged(index, index);
|
||||||
}
|
}
|
||||||
temp_dequeue_change_indexes_.clear();
|
temp_dequeue_change_indexes_.clear();
|
||||||
emit QueueChanged();
|
emit QueueChanged();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Playlist::TracksEnqueued(const QModelIndex&, int begin, int end) {
|
void Playlist::TracksEnqueued(const QModelIndex&, int begin, int end) {
|
||||||
|
|
||||||
const QModelIndex &b = queue_->mapToSource(queue_->index(begin, Column_Title));
|
const QModelIndex &b = queue_->mapToSource(queue_->index(begin, Column_Title));
|
||||||
const QModelIndex &e = queue_->mapToSource(queue_->index(end, Column_Title));
|
const QModelIndex &e = queue_->mapToSource(queue_->index(end, Column_Title));
|
||||||
emit dataChanged(b, e);
|
emit dataChanged(b, e);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Playlist::QueueLayoutChanged() {
|
void Playlist::QueueLayoutChanged() {
|
||||||
|
|
||||||
for (int i = 0; i < queue_->rowCount(); ++i) {
|
for (int i = 0; i < queue_->rowCount(); ++i) {
|
||||||
const QModelIndex &index = queue_->mapToSource(queue_->index(i, Column_Title));
|
const QModelIndex &index = queue_->mapToSource(queue_->index(i, Column_Title));
|
||||||
emit dataChanged(index, index);
|
emit dataChanged(index, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Playlist::ItemChanged(PlaylistItemPtr item) {
|
void Playlist::ItemChanged(PlaylistItemPtr item) {
|
||||||
@@ -1858,6 +1876,7 @@ void Playlist::InvalidateDeletedSongs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Playlist::RemoveDeletedSongs() {
|
void Playlist::RemoveDeletedSongs() {
|
||||||
|
|
||||||
QList<int> rows_to_remove;
|
QList<int> rows_to_remove;
|
||||||
|
|
||||||
for (int row = 0; row < items_.count(); ++row) {
|
for (int row = 0; row < items_.count(); ++row) {
|
||||||
@@ -1892,7 +1911,7 @@ struct SongSimilarEqual {
|
|||||||
void Playlist::RemoveDuplicateSongs() {
|
void Playlist::RemoveDuplicateSongs() {
|
||||||
|
|
||||||
QList<int> rows_to_remove;
|
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) {
|
for (int row = 0; row < items_.count(); ++row) {
|
||||||
PlaylistItemPtr item = items_[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/song.h"
|
||||||
#include "core/tagreaderclient.h"
|
#include "core/tagreaderclient.h"
|
||||||
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
#include "playlistitem.h"
|
#include "playlistitem.h"
|
||||||
#include "playlistsequence.h"
|
#include "playlistsequence.h"
|
||||||
|
|
||||||
@@ -348,6 +349,7 @@ class Playlist : public QAbstractListModel {
|
|||||||
void ItemReloadComplete(const QPersistentModelIndex &index);
|
void ItemReloadComplete(const QPersistentModelIndex &index);
|
||||||
void ItemsLoaded(QFuture<PlaylistItemList> future);
|
void ItemsLoaded(QFuture<PlaylistItemList> future);
|
||||||
void SongInsertVetoListenerDestroyed();
|
void SongInsertVetoListenerDestroyed();
|
||||||
|
void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool is_loading_;
|
bool is_loading_;
|
||||||
@@ -364,8 +366,10 @@ class Playlist : public QAbstractListModel {
|
|||||||
bool favorite_;
|
bool favorite_;
|
||||||
|
|
||||||
PlaylistItemList items_;
|
PlaylistItemList items_;
|
||||||
|
|
||||||
// Contains the indices into items_ in the order that they will be played.
|
// Contains the indices into items_ in the order that they will be played.
|
||||||
QList<int> virtual_items_;
|
QList<int> virtual_items_;
|
||||||
|
|
||||||
// A map of collection ID to playlist item - for fast lookups when collection items change.
|
// A map of collection ID to playlist item - for fast lookups when collection items change.
|
||||||
QMultiMap<int, PlaylistItemPtr> collection_items_by_id_;
|
QMultiMap<int, PlaylistItemPtr> collection_items_by_id_;
|
||||||
|
|
||||||
|
|||||||
@@ -43,21 +43,21 @@ PlaylistItem::~PlaylistItem() {}
|
|||||||
PlaylistItem *PlaylistItem::NewFromSource(const Song::Source &source) {
|
PlaylistItem *PlaylistItem::NewFromSource(const Song::Source &source) {
|
||||||
|
|
||||||
switch (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_Tidal:
|
||||||
case Song::Source_Stream: return new InternetPlaylistItem(source);
|
case Song::Source_Qobuz:
|
||||||
default: return new SongPlaylistItem(source);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
return new SongPlaylistItem(source);
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ class PlaylistItem : public std::enable_shared_from_this<PlaylistItem> {
|
|||||||
virtual ~PlaylistItem();
|
virtual ~PlaylistItem();
|
||||||
|
|
||||||
static PlaylistItem *NewFromSource(const Song::Source &source);
|
static PlaylistItem *NewFromSource(const Song::Source &source);
|
||||||
static PlaylistItem *NewFromSongsTable(const QString &table, const Song &song);
|
|
||||||
|
|
||||||
enum Option {
|
enum Option {
|
||||||
Default = 0x00,
|
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_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(); }
|
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.
|
// Background colors.
|
||||||
void SetBackgroundColor(short priority, const QColor &color);
|
void SetBackgroundColor(short priority, const QColor &color);
|
||||||
bool HasBackgroundColor(short priority) const;
|
bool HasBackgroundColor(short priority) const;
|
||||||
|
|||||||
@@ -48,6 +48,8 @@
|
|||||||
#include "core/utilities.h"
|
#include "core/utilities.h"
|
||||||
#include "collection/collectionbackend.h"
|
#include "collection/collectionbackend.h"
|
||||||
#include "collection/collectionplaylistitem.h"
|
#include "collection/collectionplaylistitem.h"
|
||||||
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
|
#include "covermanager/currentalbumcoverloader.h"
|
||||||
#include "playlist.h"
|
#include "playlist.h"
|
||||||
#include "playlistbackend.h"
|
#include "playlistbackend.h"
|
||||||
#include "playlistcontainer.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(Error(QString)), SIGNAL(Error(QString)));
|
||||||
connect(ret, SIGNAL(PlayRequested(QModelIndex)), SIGNAL(PlayRequested(QModelIndex)));
|
connect(ret, SIGNAL(PlayRequested(QModelIndex)), SIGNAL(PlayRequested(QModelIndex)));
|
||||||
connect(playlist_container_->view(), SIGNAL(ColumnAlignmentChanged(ColumnAlignmentMap)), ret, SLOT(SetColumnAlignment(ColumnAlignmentMap)));
|
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);
|
playlists_[id] = Data(ret, name);
|
||||||
|
|
||||||
@@ -440,7 +443,8 @@ void PlaylistManager::UpdateSummaryText() {
|
|||||||
QString summary;
|
QString summary;
|
||||||
if (selected > 1) {
|
if (selected > 1) {
|
||||||
summary += tr("%1 selected of").arg(selected) + " ";
|
summary += tr("%1 selected of").arg(selected) + " ";
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
nanoseconds = current()->GetTotalLength();
|
nanoseconds = current()->GetTotalLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,7 +470,7 @@ void PlaylistManager::SongsDiscovered(const SongList &songs) {
|
|||||||
for (const Song &song : songs) {
|
for (const Song &song : songs) {
|
||||||
for (const Data &data : playlists_) {
|
for (const Data &data : playlists_) {
|
||||||
PlaylistItemList items = data.p->collection_items_by_id(song.id());
|
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;
|
if (item->Metadata().directory_id() != song.directory_id()) continue;
|
||||||
static_cast<CollectionPlaylistItem*>(item.get())->SetMetadata(song);
|
static_cast<CollectionPlaylistItem*>(item.get())->SetMetadata(song);
|
||||||
item->UpdateTemporaryMetadata(song);
|
item->UpdateTemporaryMetadata(song);
|
||||||
|
|||||||
@@ -75,6 +75,7 @@
|
|||||||
#include "playlistheader.h"
|
#include "playlistheader.h"
|
||||||
#include "playlistview.h"
|
#include "playlistview.h"
|
||||||
#include "covermanager/currentalbumcoverloader.h"
|
#include "covermanager/currentalbumcoverloader.h"
|
||||||
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
#include "settings/appearancesettingspage.h"
|
#include "settings/appearancesettingspage.h"
|
||||||
#include "settings/playlistsettingspage.h"
|
#include "settings/playlistsettingspage.h"
|
||||||
|
|
||||||
@@ -82,8 +83,6 @@
|
|||||||
# include "moodbar/moodbaritemdelegate.h"
|
# include "moodbar/moodbaritemdelegate.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
using std::sort;
|
|
||||||
|
|
||||||
const int PlaylistView::kGlowIntensitySteps = 24;
|
const int PlaylistView::kGlowIntensitySteps = 24;
|
||||||
const int PlaylistView::kAutoscrollGraceTimeout = 30; // seconds
|
const int PlaylistView::kAutoscrollGraceTimeout = 30; // seconds
|
||||||
const int PlaylistView::kDropIndicatorWidth = 2;
|
const int PlaylistView::kDropIndicatorWidth = 2;
|
||||||
@@ -221,7 +220,7 @@ void PlaylistView::SetApplication(Application *app) {
|
|||||||
Q_ASSERT(app);
|
Q_ASSERT(app);
|
||||||
app_ = app;
|
app_ = app;
|
||||||
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), this, SLOT(SongChanged(Song)));
|
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(Playing()), SLOT(StartGlowing()));
|
||||||
connect(app_->player(), SIGNAL(Paused()), SLOT(StopGlowing()));
|
connect(app_->player(), SIGNAL(Paused()), SLOT(StopGlowing()));
|
||||||
connect(app_->player(), SIGNAL(Stopped()), SLOT(Stopped()));
|
connect(app_->player(), SIGNAL(Stopped()), SLOT(Stopped()));
|
||||||
@@ -1261,17 +1260,15 @@ void PlaylistView::Stopped() {
|
|||||||
if (song_playing_ == Song()) return;
|
if (song_playing_ == Song()) return;
|
||||||
song_playing_ = Song();
|
song_playing_ = Song();
|
||||||
StopGlowing();
|
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_ = result.image_original;
|
||||||
|
|
||||||
current_song_cover_art_ = song_art;
|
|
||||||
if (background_image_type_ == AppearanceSettingsPage::BackgroundImageType_Album) {
|
if (background_image_type_ == AppearanceSettingsPage::BackgroundImageType_Album) {
|
||||||
if (song.art_automatic().isEmpty() && song.art_manual().isEmpty()) {
|
if (song.art_automatic().isEmpty() && song.art_manual().isEmpty()) {
|
||||||
set_background_image(QImage());
|
set_background_image(QImage());
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
#include <QCommonStyle>
|
#include <QCommonStyle>
|
||||||
|
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
#include "settings/appearancesettingspage.h"
|
#include "settings/appearancesettingspage.h"
|
||||||
#include "playlist.h"
|
#include "playlist.h"
|
||||||
|
|
||||||
@@ -173,7 +174,7 @@ class PlaylistView : public QTreeView {
|
|||||||
void Playing();
|
void Playing();
|
||||||
void Stopped();
|
void Stopped();
|
||||||
void SongChanged(const Song &song);
|
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:
|
private:
|
||||||
void LoadGeometry();
|
void LoadGeometry();
|
||||||
|
|||||||
@@ -48,3 +48,10 @@ Song SongPlaylistItem::Metadata() const {
|
|||||||
if (HasTemporaryMetadata()) return temp_metadata_;
|
if (HasTemporaryMetadata()) return temp_metadata_;
|
||||||
return song_;
|
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;
|
QUrl Url() const;
|
||||||
|
|
||||||
protected:
|
|
||||||
Song DatabaseSongMetadata() const { return song_; }
|
Song DatabaseSongMetadata() const { return song_; }
|
||||||
|
void SetArtManual(const QUrl &cover_url);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Song song_;
|
Song song_;
|
||||||
|
|||||||
@@ -48,8 +48,6 @@
|
|||||||
#include "covermanager/albumcoverloader.h"
|
#include "covermanager/albumcoverloader.h"
|
||||||
#include "playingwidget.h"
|
#include "playingwidget.h"
|
||||||
|
|
||||||
using std::unique_ptr;
|
|
||||||
|
|
||||||
const char *PlayingWidget::kSettingsGroup = "PlayingWidget";
|
const char *PlayingWidget::kSettingsGroup = "PlayingWidget";
|
||||||
|
|
||||||
// Space between the cover and the details in small mode
|
// Space between the cover and the details in small mode
|
||||||
@@ -269,9 +267,7 @@ void PlayingWidget::SongChanged(const Song &song) {
|
|||||||
song_ = song;
|
song_ = song;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayingWidget::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image) {
|
void PlayingWidget::AlbumCoverLoaded(const Song &song, const QImage &image) {
|
||||||
|
|
||||||
Q_UNUSED(cover_url);
|
|
||||||
|
|
||||||
if (!playing_ || song != song_playing_ || (timeline_fade_->state() == QTimeLine::Running && image == image_original_)) return;
|
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() {
|
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();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,8 +40,6 @@
|
|||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "covermanager/albumcoverloaderoptions.h"
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
|
|
||||||
using std::unique_ptr;
|
|
||||||
|
|
||||||
class QTimeLine;
|
class QTimeLine;
|
||||||
class QTextDocument;
|
class QTextDocument;
|
||||||
class QPainter;
|
class QPainter;
|
||||||
@@ -98,7 +96,7 @@ class PlayingWidget : public QWidget {
|
|||||||
|
|
||||||
void AutomaticCoverSearchDone();
|
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 SetHeight(int height);
|
||||||
void FadePreviousTrack(qreal value);
|
void FadePreviousTrack(qreal value);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user