Improve album cover loader, lyrics search and streaming support

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

View File

@@ -69,6 +69,7 @@
#include "collection/collection.h"
#include "collection/collectionbackend.h"
#include "collection/collectionplaylistitem.h"
#include "covermanager/albumcoverloader.h"
#include "queue/queue.h"
#include "playlist.h"
#include "playlistitem.h"
@@ -88,12 +89,6 @@
using std::placeholders::_1;
using std::placeholders::_2;
using std::shared_ptr;
using std::unordered_map;
using std::sort;
using std::stable_sort;
using std::greater;
using std::swap;
const char *Playlist::kCddaMimeType = "x-content/audio-cdda";
const char *Playlist::kRowsMimetype = "application/x-strawberry-playlist-rows";
@@ -256,6 +251,7 @@ bool Playlist::set_column_value(Song &song, Playlist::Column column, const QVari
default:
break;
}
return true;
}
@@ -392,7 +388,9 @@ void Playlist::SongSaveComplete(TagReaderReply *reply, const QPersistentModelInd
if (reply->is_successful() && index.isValid()) {
if (reply->message().save_file_response().success()) {
QFuture<void> future = item_at(index.row())->BackgroundReload();
PlaylistItemPtr item = item_at(index.row());
if (!item) return;
QFuture<void> future = item->BackgroundReload();
NewClosure(future, this, SLOT(ItemReloadComplete(QPersistentModelIndex)), index);
}
else {
@@ -406,6 +404,20 @@ void Playlist::SongSaveComplete(TagReaderReply *reply, const QPersistentModelInd
void Playlist::ItemReloadComplete(const QPersistentModelIndex &index) {
if (index.isValid()) {
PlaylistItemPtr item = item_at(index.row());
if (item) {
// Update temporary metadata for songs that are not in the collection.
// Songs that are in the collection is updated through the collection watcher/backend in playlist manager.
if (item->Metadata().source() != Song::Source_Collection) {
SongPlaylistItem *song_item = static_cast<SongPlaylistItem*>(item.get());
if (song_item) {
song_item->UpdateTemporaryMetadata(song_item->DatabaseSongMetadata());
}
}
}
emit dataChanged(index, index);
emit EditingFinished(index);
}
@@ -879,7 +891,7 @@ void Playlist::InsertItems(const PlaylistItemList &itemsIn, int pos, bool play_n
// exercise vetoes
SongList songs;
for (const PlaylistItemPtr item : items) {
for (PlaylistItemPtr item : items) {
songs << item->Metadata();
}
@@ -1004,7 +1016,7 @@ void Playlist::InsertInternetItems(InternetService *service, const SongList &son
PlaylistItemList playlist_items;
for (const Song &song : songs) {
playlist_items << shared_ptr<PlaylistItem>(new InternetPlaylistItem(service, song));
playlist_items << std::shared_ptr<PlaylistItem>(new InternetPlaylistItem(service, song));
}
InsertItems(playlist_items, pos, play_now, enqueue, enqueue_next);
@@ -1014,6 +1026,7 @@ void Playlist::InsertInternetItems(InternetService *service, const SongList &son
void Playlist::UpdateItems(const SongList &songs) {
qLog(Debug) << "Updating playlist with new tracks' info";
// We first convert our songs list into a linked list (a 'real' list), because removals are faster with QLinkedList.
// Next, we walk through the list of playlist's items then the list of songs
// we want to update: if an item corresponds to the song (we rely on URL for this), we update the item with the new metadata,
@@ -1027,20 +1040,12 @@ void Playlist::UpdateItems(const SongList &songs) {
QMutableLinkedListIterator<Song> it(songs_list);
while (it.hasNext()) {
const Song &song = it.next();
PlaylistItemPtr &item = items_[i];
if (item->Metadata().url() == song.url() &&
(
item->Metadata().source() == Song::Source_Unknown ||
item->Metadata().filetype() == Song::FileType_Unknown ||
// Stream may change and may need to be updated too
item->Metadata().is_stream() ||
// And CD tracks as well (tags are loaded in a second step)
item->Metadata().is_cdda()
)
) {
const PlaylistItemPtr &item = items_[i];
if (item->Metadata().url() == song.url()) {
PlaylistItemPtr new_item;
if (song.is_collection_song()) {
new_item = PlaylistItemPtr(new CollectionPlaylistItem(song));
if (collection_items_by_id_.contains(song.id(), item)) collection_items_by_id_.remove(song.id(), item);
collection_items_by_id_.insertMulti(song.id(), new_item);
}
else {
@@ -1103,10 +1108,10 @@ QMimeData *Playlist::mimeData(const QModelIndexList &indexes) const {
}
bool Playlist::CompareItems(int column, Qt::SortOrder order, shared_ptr<PlaylistItem> _a, shared_ptr<PlaylistItem> _b) {
bool Playlist::CompareItems(int column, Qt::SortOrder order, std::shared_ptr<PlaylistItem> _a, std::shared_ptr<PlaylistItem> _b) {
shared_ptr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
shared_ptr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
std::shared_ptr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
std::shared_ptr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
#define cmp(field) return a->Metadata().field() < b->Metadata().field()
#define strcmp(field) return QString::localeAwareCompare(a->Metadata().field().toLower(), b->Metadata().field().toLower()) < 0;
@@ -1154,10 +1159,10 @@ bool Playlist::CompareItems(int column, Qt::SortOrder order, shared_ptr<Playlist
}
bool Playlist::ComparePathDepths(Qt::SortOrder order, shared_ptr<PlaylistItem> _a, shared_ptr<PlaylistItem> _b) {
bool Playlist::ComparePathDepths(Qt::SortOrder order, std::shared_ptr<PlaylistItem> _a, std::shared_ptr<PlaylistItem> _b) {
shared_ptr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
shared_ptr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
std::shared_ptr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
std::shared_ptr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
int a_dir_level = a->Url().path().count('/');
int b_dir_level = b->Url().path().count('/');
@@ -1293,6 +1298,7 @@ void Playlist::SetCurrentIsPaused(bool paused) {
}
void Playlist::Save() const {
if (!backend_ || is_loading_) return;
backend_->SavePlaylistAsync(id_, items_, last_played_row());
@@ -1443,7 +1449,7 @@ PlaylistItemList Playlist::RemoveItemsWithoutUndo(int row, int count) {
if (item->source() == Song::Source_Collection) {
int id = item->Metadata().id();
if (id != -1) {
if (id != -1 && collection_items_by_id_.contains(id, item)) {
collection_items_by_id_.remove(id, item);
}
}
@@ -1755,22 +1761,26 @@ void Playlist::set_sequence(PlaylistSequence *v) {
QSortFilterProxyModel *Playlist::proxy() const { return proxy_; }
SongList Playlist::GetAllSongs() const {
SongList ret;
for (const PlaylistItemPtr item : items_) {
for (PlaylistItemPtr item : items_) {
ret << item->Metadata();
}
return ret;
}
PlaylistItemList Playlist::GetAllItems() const { return items_; }
quint64 Playlist::GetTotalLength() const {
quint64 ret = 0;
for (const PlaylistItemPtr item : items_) {
for (PlaylistItemPtr item : items_) {
quint64 length = item->Metadata().length_nanosec();
if (length > 0) ret += length;
}
return ret;
}
PlaylistItemList Playlist::collection_items_by_id(int id) const {
@@ -1778,30 +1788,38 @@ PlaylistItemList Playlist::collection_items_by_id(int id) const {
}
void Playlist::TracksAboutToBeDequeued(const QModelIndex&, int begin, int end) {
for (int i = begin; i <= end; ++i) {
temp_dequeue_change_indexes_ << queue_->mapToSource(queue_->index(i, Column_Title));
}
}
void Playlist::TracksDequeued() {
for (const QModelIndex &index : temp_dequeue_change_indexes_) {
emit dataChanged(index, index);
}
temp_dequeue_change_indexes_.clear();
emit QueueChanged();
}
void Playlist::TracksEnqueued(const QModelIndex&, int begin, int end) {
const QModelIndex &b = queue_->mapToSource(queue_->index(begin, Column_Title));
const QModelIndex &e = queue_->mapToSource(queue_->index(end, Column_Title));
emit dataChanged(b, e);
}
void Playlist::QueueLayoutChanged() {
for (int i = 0; i < queue_->rowCount(); ++i) {
const QModelIndex &index = queue_->mapToSource(queue_->index(i, Column_Title));
emit dataChanged(index, index);
}
}
void Playlist::ItemChanged(PlaylistItemPtr item) {
@@ -1858,6 +1876,7 @@ void Playlist::InvalidateDeletedSongs() {
}
void Playlist::RemoveDeletedSongs() {
QList<int> rows_to_remove;
for (int row = 0; row < items_.count(); ++row) {
@@ -1892,7 +1911,7 @@ struct SongSimilarEqual {
void Playlist::RemoveDuplicateSongs() {
QList<int> rows_to_remove;
unordered_map<Song, int, SongSimilarHash, SongSimilarEqual> unique_songs;
std::unordered_map<Song, int, SongSimilarHash, SongSimilarEqual> unique_songs;
for (int row = 0; row < items_.count(); ++row) {
PlaylistItemPtr item = items_[row];
@@ -2007,3 +2026,16 @@ void Playlist::UpdateScrobblePoint(const qint64 seek_point_nanosec) {
}
void Playlist::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) {
// Update art_manual for local songs that are not in the collection.
if (result.type == AlbumCoverLoaderResult::Type_Manual && result.cover_url.isLocalFile() && (song.source() == Song::Source_LocalFile || song.source() == Song::Source_CDDA || song.source() == Song::Source_Device)) {
PlaylistItemPtr item = current_item();
if (item && item->Metadata() == song && !item->Metadata().art_manual_is_valid()) {
qLog(Debug) << "Updating art manual for local song" << song.title() << song.album() << song.title() << "to" << result.cover_url << "in playlist.";
item->SetArtManual(result.cover_url);
Save();
}
}
}

View File

@@ -42,6 +42,7 @@
#include "core/song.h"
#include "core/tagreaderclient.h"
#include "covermanager/albumcoverloaderresult.h"
#include "playlistitem.h"
#include "playlistsequence.h"
@@ -348,6 +349,7 @@ class Playlist : public QAbstractListModel {
void ItemReloadComplete(const QPersistentModelIndex &index);
void ItemsLoaded(QFuture<PlaylistItemList> future);
void SongInsertVetoListenerDestroyed();
void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result);
private:
bool is_loading_;
@@ -364,8 +366,10 @@ class Playlist : public QAbstractListModel {
bool favorite_;
PlaylistItemList items_;
// Contains the indices into items_ in the order that they will be played.
QList<int> virtual_items_;
// A map of collection ID to playlist item - for fast lookups when collection items change.
QMultiMap<int, PlaylistItemPtr> collection_items_by_id_;

View File

@@ -43,21 +43,21 @@ PlaylistItem::~PlaylistItem() {}
PlaylistItem *PlaylistItem::NewFromSource(const Song::Source &source) {
switch (source) {
case Song::Source_Collection: return new CollectionPlaylistItem();
case Song::Source_Collection:
return new CollectionPlaylistItem();
case Song::Source_Subsonic:
case Song::Source_Tidal:
case Song::Source_Stream: return new InternetPlaylistItem(source);
default: return new SongPlaylistItem(source);
case Song::Source_Qobuz:
case Song::Source_Stream:
return new InternetPlaylistItem(source);
case Song::Source_LocalFile:
case Song::Source_CDDA:
case Song::Source_Device:
case Song::Source_Unknown:
break;
}
}
PlaylistItem *PlaylistItem::NewFromSongsTable(const QString &table, const Song &song) {
if (table == SCollection::kSongsTable)
return new CollectionPlaylistItem(song);
qLog(Warning) << "Invalid PlaylistItem songs table:" << table;
return nullptr;
return new SongPlaylistItem(source);
}

View File

@@ -49,7 +49,6 @@ class PlaylistItem : public std::enable_shared_from_this<PlaylistItem> {
virtual ~PlaylistItem();
static PlaylistItem *NewFromSource(const Song::Source &source);
static PlaylistItem *NewFromSongsTable(const QString &table, const Song &song);
enum Option {
Default = 0x00,
@@ -87,6 +86,8 @@ class PlaylistItem : public std::enable_shared_from_this<PlaylistItem> {
qint64 effective_beginning_nanosec() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() && temp_metadata_.beginning_nanosec() != -1 ? temp_metadata_.beginning_nanosec() : Metadata().beginning_nanosec(); }
qint64 effective_end_nanosec() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() && temp_metadata_.end_nanosec() != -1 ? temp_metadata_.end_nanosec() : Metadata().end_nanosec(); }
virtual void SetArtManual(const QUrl &cover_url) = 0;
// Background colors.
void SetBackgroundColor(short priority, const QColor &color);
bool HasBackgroundColor(short priority) const;

View File

@@ -48,6 +48,8 @@
#include "core/utilities.h"
#include "collection/collectionbackend.h"
#include "collection/collectionplaylistitem.h"
#include "covermanager/albumcoverloaderresult.h"
#include "covermanager/currentalbumcoverloader.h"
#include "playlist.h"
#include "playlistbackend.h"
#include "playlistcontainer.h"
@@ -147,6 +149,7 @@ Playlist *PlaylistManager::AddPlaylist(int id, const QString &name, const QStrin
connect(ret, SIGNAL(Error(QString)), SIGNAL(Error(QString)));
connect(ret, SIGNAL(PlayRequested(QModelIndex)), SIGNAL(PlayRequested(QModelIndex)));
connect(playlist_container_->view(), SIGNAL(ColumnAlignmentChanged(ColumnAlignmentMap)), ret, SLOT(SetColumnAlignment(ColumnAlignmentMap)));
connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)), ret, SLOT(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)));
playlists_[id] = Data(ret, name);
@@ -440,7 +443,8 @@ void PlaylistManager::UpdateSummaryText() {
QString summary;
if (selected > 1) {
summary += tr("%1 selected of").arg(selected) + " ";
} else {
}
else {
nanoseconds = current()->GetTotalLength();
}
@@ -466,7 +470,7 @@ void PlaylistManager::SongsDiscovered(const SongList &songs) {
for (const Song &song : songs) {
for (const Data &data : playlists_) {
PlaylistItemList items = data.p->collection_items_by_id(song.id());
for (const PlaylistItemPtr item : items) {
for (PlaylistItemPtr item : items) {
if (item->Metadata().directory_id() != song.directory_id()) continue;
static_cast<CollectionPlaylistItem*>(item.get())->SetMetadata(song);
item->UpdateTemporaryMetadata(song);

View File

@@ -75,6 +75,7 @@
#include "playlistheader.h"
#include "playlistview.h"
#include "covermanager/currentalbumcoverloader.h"
#include "covermanager/albumcoverloaderresult.h"
#include "settings/appearancesettingspage.h"
#include "settings/playlistsettingspage.h"
@@ -82,8 +83,6 @@
# include "moodbar/moodbaritemdelegate.h"
#endif
using std::sort;
const int PlaylistView::kGlowIntensitySteps = 24;
const int PlaylistView::kAutoscrollGraceTimeout = 30; // seconds
const int PlaylistView::kDropIndicatorWidth = 2;
@@ -221,7 +220,7 @@ void PlaylistView::SetApplication(Application *app) {
Q_ASSERT(app);
app_ = app;
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), this, SLOT(SongChanged(Song)));
connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, QUrl, QImage)), SLOT(AlbumCoverLoaded(Song, QUrl, QImage)));
connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)));
connect(app_->player(), SIGNAL(Playing()), SLOT(StartGlowing()));
connect(app_->player(), SIGNAL(Paused()), SLOT(StopGlowing()));
connect(app_->player(), SIGNAL(Stopped()), SLOT(Stopped()));
@@ -1261,17 +1260,15 @@ void PlaylistView::Stopped() {
if (song_playing_ == Song()) return;
song_playing_ = Song();
StopGlowing();
AlbumCoverLoaded(Song(), QUrl(), QImage());
AlbumCoverLoaded(Song());
}
void PlaylistView::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &song_art) {
void PlaylistView::AlbumCoverLoaded(const Song &song, AlbumCoverLoaderResult result) {
Q_UNUSED(cover_url);
if ((song != Song() && song_playing_ == Song()) || result.image_original == current_song_cover_art_) return;
if ((song != Song() && song_playing_ == Song()) || song_art == current_song_cover_art_) return;
current_song_cover_art_ = song_art;
current_song_cover_art_ = result.image_original;
if (background_image_type_ == AppearanceSettingsPage::BackgroundImageType_Album) {
if (song.art_automatic().isEmpty() && song.art_manual().isEmpty()) {
set_background_image(QImage());

View File

@@ -47,6 +47,7 @@
#include <QCommonStyle>
#include "core/song.h"
#include "covermanager/albumcoverloaderresult.h"
#include "settings/appearancesettingspage.h"
#include "playlist.h"
@@ -173,7 +174,7 @@ class PlaylistView : public QTreeView {
void Playing();
void Stopped();
void SongChanged(const Song &song);
void AlbumCoverLoaded(const Song &new_song, const QUrl &cover_url, const QImage &song_art);
void AlbumCoverLoaded(const Song &song, AlbumCoverLoaderResult result = AlbumCoverLoaderResult());
private:
void LoadGeometry();

View File

@@ -48,3 +48,10 @@ Song SongPlaylistItem::Metadata() const {
if (HasTemporaryMetadata()) return temp_metadata_;
return song_;
}
void SongPlaylistItem::SetArtManual(const QUrl &cover_url) {
song_.set_art_manual(cover_url);
temp_metadata_.set_art_manual(cover_url);
}

View File

@@ -44,8 +44,8 @@ class SongPlaylistItem : public PlaylistItem {
QUrl Url() const;
protected:
Song DatabaseSongMetadata() const { return song_; }
void SetArtManual(const QUrl &cover_url);
private:
Song song_;