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:
@@ -69,6 +69,7 @@
|
||||
#include "collection/collection.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionplaylistitem.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "queue/queue.h"
|
||||
#include "playlist.h"
|
||||
#include "playlistitem.h"
|
||||
@@ -88,12 +89,6 @@
|
||||
|
||||
using std::placeholders::_1;
|
||||
using std::placeholders::_2;
|
||||
using std::shared_ptr;
|
||||
using std::unordered_map;
|
||||
using std::sort;
|
||||
using std::stable_sort;
|
||||
using std::greater;
|
||||
using std::swap;
|
||||
|
||||
const char *Playlist::kCddaMimeType = "x-content/audio-cdda";
|
||||
const char *Playlist::kRowsMimetype = "application/x-strawberry-playlist-rows";
|
||||
@@ -256,6 +251,7 @@ bool Playlist::set_column_value(Song &song, Playlist::Column column, const QVari
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
@@ -392,7 +388,9 @@ void Playlist::SongSaveComplete(TagReaderReply *reply, const QPersistentModelInd
|
||||
|
||||
if (reply->is_successful() && index.isValid()) {
|
||||
if (reply->message().save_file_response().success()) {
|
||||
QFuture<void> future = item_at(index.row())->BackgroundReload();
|
||||
PlaylistItemPtr item = item_at(index.row());
|
||||
if (!item) return;
|
||||
QFuture<void> future = item->BackgroundReload();
|
||||
NewClosure(future, this, SLOT(ItemReloadComplete(QPersistentModelIndex)), index);
|
||||
}
|
||||
else {
|
||||
@@ -406,6 +404,20 @@ void Playlist::SongSaveComplete(TagReaderReply *reply, const QPersistentModelInd
|
||||
void Playlist::ItemReloadComplete(const QPersistentModelIndex &index) {
|
||||
|
||||
if (index.isValid()) {
|
||||
|
||||
PlaylistItemPtr item = item_at(index.row());
|
||||
if (item) {
|
||||
|
||||
// Update temporary metadata for songs that are not in the collection.
|
||||
// Songs that are in the collection is updated through the collection watcher/backend in playlist manager.
|
||||
if (item->Metadata().source() != Song::Source_Collection) {
|
||||
SongPlaylistItem *song_item = static_cast<SongPlaylistItem*>(item.get());
|
||||
if (song_item) {
|
||||
song_item->UpdateTemporaryMetadata(song_item->DatabaseSongMetadata());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit dataChanged(index, index);
|
||||
emit EditingFinished(index);
|
||||
}
|
||||
@@ -879,7 +891,7 @@ void Playlist::InsertItems(const PlaylistItemList &itemsIn, int pos, bool play_n
|
||||
// exercise vetoes
|
||||
SongList songs;
|
||||
|
||||
for (const PlaylistItemPtr item : items) {
|
||||
for (PlaylistItemPtr item : items) {
|
||||
songs << item->Metadata();
|
||||
}
|
||||
|
||||
@@ -1004,7 +1016,7 @@ void Playlist::InsertInternetItems(InternetService *service, const SongList &son
|
||||
|
||||
PlaylistItemList playlist_items;
|
||||
for (const Song &song : songs) {
|
||||
playlist_items << shared_ptr<PlaylistItem>(new InternetPlaylistItem(service, song));
|
||||
playlist_items << std::shared_ptr<PlaylistItem>(new InternetPlaylistItem(service, song));
|
||||
}
|
||||
|
||||
InsertItems(playlist_items, pos, play_now, enqueue, enqueue_next);
|
||||
@@ -1014,6 +1026,7 @@ void Playlist::InsertInternetItems(InternetService *service, const SongList &son
|
||||
void Playlist::UpdateItems(const SongList &songs) {
|
||||
|
||||
qLog(Debug) << "Updating playlist with new tracks' info";
|
||||
|
||||
// We first convert our songs list into a linked list (a 'real' list), because removals are faster with QLinkedList.
|
||||
// Next, we walk through the list of playlist's items then the list of songs
|
||||
// we want to update: if an item corresponds to the song (we rely on URL for this), we update the item with the new metadata,
|
||||
@@ -1027,20 +1040,12 @@ void Playlist::UpdateItems(const SongList &songs) {
|
||||
QMutableLinkedListIterator<Song> it(songs_list);
|
||||
while (it.hasNext()) {
|
||||
const Song &song = it.next();
|
||||
PlaylistItemPtr &item = items_[i];
|
||||
if (item->Metadata().url() == song.url() &&
|
||||
(
|
||||
item->Metadata().source() == Song::Source_Unknown ||
|
||||
item->Metadata().filetype() == Song::FileType_Unknown ||
|
||||
// Stream may change and may need to be updated too
|
||||
item->Metadata().is_stream() ||
|
||||
// And CD tracks as well (tags are loaded in a second step)
|
||||
item->Metadata().is_cdda()
|
||||
)
|
||||
) {
|
||||
const PlaylistItemPtr &item = items_[i];
|
||||
if (item->Metadata().url() == song.url()) {
|
||||
PlaylistItemPtr new_item;
|
||||
if (song.is_collection_song()) {
|
||||
new_item = PlaylistItemPtr(new CollectionPlaylistItem(song));
|
||||
if (collection_items_by_id_.contains(song.id(), item)) collection_items_by_id_.remove(song.id(), item);
|
||||
collection_items_by_id_.insertMulti(song.id(), new_item);
|
||||
}
|
||||
else {
|
||||
@@ -1103,10 +1108,10 @@ QMimeData *Playlist::mimeData(const QModelIndexList &indexes) const {
|
||||
|
||||
}
|
||||
|
||||
bool Playlist::CompareItems(int column, Qt::SortOrder order, shared_ptr<PlaylistItem> _a, shared_ptr<PlaylistItem> _b) {
|
||||
bool Playlist::CompareItems(int column, Qt::SortOrder order, std::shared_ptr<PlaylistItem> _a, std::shared_ptr<PlaylistItem> _b) {
|
||||
|
||||
shared_ptr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
|
||||
shared_ptr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
|
||||
std::shared_ptr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
|
||||
std::shared_ptr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
|
||||
|
||||
#define cmp(field) return a->Metadata().field() < b->Metadata().field()
|
||||
#define strcmp(field) return QString::localeAwareCompare(a->Metadata().field().toLower(), b->Metadata().field().toLower()) < 0;
|
||||
@@ -1154,10 +1159,10 @@ bool Playlist::CompareItems(int column, Qt::SortOrder order, shared_ptr<Playlist
|
||||
|
||||
}
|
||||
|
||||
bool Playlist::ComparePathDepths(Qt::SortOrder order, shared_ptr<PlaylistItem> _a, shared_ptr<PlaylistItem> _b) {
|
||||
bool Playlist::ComparePathDepths(Qt::SortOrder order, std::shared_ptr<PlaylistItem> _a, std::shared_ptr<PlaylistItem> _b) {
|
||||
|
||||
shared_ptr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
|
||||
shared_ptr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
|
||||
std::shared_ptr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
|
||||
std::shared_ptr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
|
||||
|
||||
int a_dir_level = a->Url().path().count('/');
|
||||
int b_dir_level = b->Url().path().count('/');
|
||||
@@ -1293,6 +1298,7 @@ void Playlist::SetCurrentIsPaused(bool paused) {
|
||||
}
|
||||
|
||||
void Playlist::Save() const {
|
||||
|
||||
if (!backend_ || is_loading_) return;
|
||||
|
||||
backend_->SavePlaylistAsync(id_, items_, last_played_row());
|
||||
@@ -1443,7 +1449,7 @@ PlaylistItemList Playlist::RemoveItemsWithoutUndo(int row, int count) {
|
||||
|
||||
if (item->source() == Song::Source_Collection) {
|
||||
int id = item->Metadata().id();
|
||||
if (id != -1) {
|
||||
if (id != -1 && collection_items_by_id_.contains(id, item)) {
|
||||
collection_items_by_id_.remove(id, item);
|
||||
}
|
||||
}
|
||||
@@ -1755,22 +1761,26 @@ void Playlist::set_sequence(PlaylistSequence *v) {
|
||||
QSortFilterProxyModel *Playlist::proxy() const { return proxy_; }
|
||||
|
||||
SongList Playlist::GetAllSongs() const {
|
||||
|
||||
SongList ret;
|
||||
for (const PlaylistItemPtr item : items_) {
|
||||
for (PlaylistItemPtr item : items_) {
|
||||
ret << item->Metadata();
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
PlaylistItemList Playlist::GetAllItems() const { return items_; }
|
||||
|
||||
quint64 Playlist::GetTotalLength() const {
|
||||
|
||||
quint64 ret = 0;
|
||||
for (const PlaylistItemPtr item : items_) {
|
||||
for (PlaylistItemPtr item : items_) {
|
||||
quint64 length = item->Metadata().length_nanosec();
|
||||
if (length > 0) ret += length;
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
PlaylistItemList Playlist::collection_items_by_id(int id) const {
|
||||
@@ -1778,30 +1788,38 @@ PlaylistItemList Playlist::collection_items_by_id(int id) const {
|
||||
}
|
||||
|
||||
void Playlist::TracksAboutToBeDequeued(const QModelIndex&, int begin, int end) {
|
||||
|
||||
for (int i = begin; i <= end; ++i) {
|
||||
temp_dequeue_change_indexes_ << queue_->mapToSource(queue_->index(i, Column_Title));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Playlist::TracksDequeued() {
|
||||
|
||||
for (const QModelIndex &index : temp_dequeue_change_indexes_) {
|
||||
emit dataChanged(index, index);
|
||||
}
|
||||
temp_dequeue_change_indexes_.clear();
|
||||
emit QueueChanged();
|
||||
|
||||
}
|
||||
|
||||
void Playlist::TracksEnqueued(const QModelIndex&, int begin, int end) {
|
||||
|
||||
const QModelIndex &b = queue_->mapToSource(queue_->index(begin, Column_Title));
|
||||
const QModelIndex &e = queue_->mapToSource(queue_->index(end, Column_Title));
|
||||
emit dataChanged(b, e);
|
||||
|
||||
}
|
||||
|
||||
void Playlist::QueueLayoutChanged() {
|
||||
|
||||
for (int i = 0; i < queue_->rowCount(); ++i) {
|
||||
const QModelIndex &index = queue_->mapToSource(queue_->index(i, Column_Title));
|
||||
emit dataChanged(index, index);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Playlist::ItemChanged(PlaylistItemPtr item) {
|
||||
@@ -1858,6 +1876,7 @@ void Playlist::InvalidateDeletedSongs() {
|
||||
}
|
||||
|
||||
void Playlist::RemoveDeletedSongs() {
|
||||
|
||||
QList<int> rows_to_remove;
|
||||
|
||||
for (int row = 0; row < items_.count(); ++row) {
|
||||
@@ -1892,7 +1911,7 @@ struct SongSimilarEqual {
|
||||
void Playlist::RemoveDuplicateSongs() {
|
||||
|
||||
QList<int> rows_to_remove;
|
||||
unordered_map<Song, int, SongSimilarHash, SongSimilarEqual> unique_songs;
|
||||
std::unordered_map<Song, int, SongSimilarHash, SongSimilarEqual> unique_songs;
|
||||
|
||||
for (int row = 0; row < items_.count(); ++row) {
|
||||
PlaylistItemPtr item = items_[row];
|
||||
@@ -2007,3 +2026,16 @@ void Playlist::UpdateScrobblePoint(const qint64 seek_point_nanosec) {
|
||||
|
||||
}
|
||||
|
||||
void Playlist::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) {
|
||||
|
||||
// Update art_manual for local songs that are not in the collection.
|
||||
if (result.type == AlbumCoverLoaderResult::Type_Manual && result.cover_url.isLocalFile() && (song.source() == Song::Source_LocalFile || song.source() == Song::Source_CDDA || song.source() == Song::Source_Device)) {
|
||||
PlaylistItemPtr item = current_item();
|
||||
if (item && item->Metadata() == song && !item->Metadata().art_manual_is_valid()) {
|
||||
qLog(Debug) << "Updating art manual for local song" << song.title() << song.album() << song.title() << "to" << result.cover_url << "in playlist.";
|
||||
item->SetArtManual(result.cover_url);
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
#include "playlistitem.h"
|
||||
#include "playlistsequence.h"
|
||||
|
||||
@@ -348,6 +349,7 @@ class Playlist : public QAbstractListModel {
|
||||
void ItemReloadComplete(const QPersistentModelIndex &index);
|
||||
void ItemsLoaded(QFuture<PlaylistItemList> future);
|
||||
void SongInsertVetoListenerDestroyed();
|
||||
void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result);
|
||||
|
||||
private:
|
||||
bool is_loading_;
|
||||
@@ -364,8 +366,10 @@ class Playlist : public QAbstractListModel {
|
||||
bool favorite_;
|
||||
|
||||
PlaylistItemList items_;
|
||||
|
||||
// Contains the indices into items_ in the order that they will be played.
|
||||
QList<int> virtual_items_;
|
||||
|
||||
// A map of collection ID to playlist item - for fast lookups when collection items change.
|
||||
QMultiMap<int, PlaylistItemPtr> collection_items_by_id_;
|
||||
|
||||
|
||||
@@ -43,21 +43,21 @@ PlaylistItem::~PlaylistItem() {}
|
||||
PlaylistItem *PlaylistItem::NewFromSource(const Song::Source &source) {
|
||||
|
||||
switch (source) {
|
||||
case Song::Source_Collection: return new CollectionPlaylistItem();
|
||||
case Song::Source_Collection:
|
||||
return new CollectionPlaylistItem();
|
||||
case Song::Source_Subsonic:
|
||||
case Song::Source_Tidal:
|
||||
case Song::Source_Stream: return new InternetPlaylistItem(source);
|
||||
default: return new SongPlaylistItem(source);
|
||||
case Song::Source_Qobuz:
|
||||
case Song::Source_Stream:
|
||||
return new InternetPlaylistItem(source);
|
||||
case Song::Source_LocalFile:
|
||||
case Song::Source_CDDA:
|
||||
case Song::Source_Device:
|
||||
case Song::Source_Unknown:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PlaylistItem *PlaylistItem::NewFromSongsTable(const QString &table, const Song &song) {
|
||||
|
||||
if (table == SCollection::kSongsTable)
|
||||
return new CollectionPlaylistItem(song);
|
||||
|
||||
qLog(Warning) << "Invalid PlaylistItem songs table:" << table;
|
||||
return nullptr;
|
||||
return new SongPlaylistItem(source);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@ class PlaylistItem : public std::enable_shared_from_this<PlaylistItem> {
|
||||
virtual ~PlaylistItem();
|
||||
|
||||
static PlaylistItem *NewFromSource(const Song::Source &source);
|
||||
static PlaylistItem *NewFromSongsTable(const QString &table, const Song &song);
|
||||
|
||||
enum Option {
|
||||
Default = 0x00,
|
||||
@@ -87,6 +86,8 @@ class PlaylistItem : public std::enable_shared_from_this<PlaylistItem> {
|
||||
qint64 effective_beginning_nanosec() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() && temp_metadata_.beginning_nanosec() != -1 ? temp_metadata_.beginning_nanosec() : Metadata().beginning_nanosec(); }
|
||||
qint64 effective_end_nanosec() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() && temp_metadata_.end_nanosec() != -1 ? temp_metadata_.end_nanosec() : Metadata().end_nanosec(); }
|
||||
|
||||
virtual void SetArtManual(const QUrl &cover_url) = 0;
|
||||
|
||||
// Background colors.
|
||||
void SetBackgroundColor(short priority, const QColor &color);
|
||||
bool HasBackgroundColor(short priority) const;
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
#include "core/utilities.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionplaylistitem.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
#include "covermanager/currentalbumcoverloader.h"
|
||||
#include "playlist.h"
|
||||
#include "playlistbackend.h"
|
||||
#include "playlistcontainer.h"
|
||||
@@ -147,6 +149,7 @@ Playlist *PlaylistManager::AddPlaylist(int id, const QString &name, const QStrin
|
||||
connect(ret, SIGNAL(Error(QString)), SIGNAL(Error(QString)));
|
||||
connect(ret, SIGNAL(PlayRequested(QModelIndex)), SIGNAL(PlayRequested(QModelIndex)));
|
||||
connect(playlist_container_->view(), SIGNAL(ColumnAlignmentChanged(ColumnAlignmentMap)), ret, SLOT(SetColumnAlignment(ColumnAlignmentMap)));
|
||||
connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)), ret, SLOT(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)));
|
||||
|
||||
playlists_[id] = Data(ret, name);
|
||||
|
||||
@@ -440,7 +443,8 @@ void PlaylistManager::UpdateSummaryText() {
|
||||
QString summary;
|
||||
if (selected > 1) {
|
||||
summary += tr("%1 selected of").arg(selected) + " ";
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
nanoseconds = current()->GetTotalLength();
|
||||
}
|
||||
|
||||
@@ -466,7 +470,7 @@ void PlaylistManager::SongsDiscovered(const SongList &songs) {
|
||||
for (const Song &song : songs) {
|
||||
for (const Data &data : playlists_) {
|
||||
PlaylistItemList items = data.p->collection_items_by_id(song.id());
|
||||
for (const PlaylistItemPtr item : items) {
|
||||
for (PlaylistItemPtr item : items) {
|
||||
if (item->Metadata().directory_id() != song.directory_id()) continue;
|
||||
static_cast<CollectionPlaylistItem*>(item.get())->SetMetadata(song);
|
||||
item->UpdateTemporaryMetadata(song);
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
#include "playlistheader.h"
|
||||
#include "playlistview.h"
|
||||
#include "covermanager/currentalbumcoverloader.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
#include "settings/appearancesettingspage.h"
|
||||
#include "settings/playlistsettingspage.h"
|
||||
|
||||
@@ -82,8 +83,6 @@
|
||||
# include "moodbar/moodbaritemdelegate.h"
|
||||
#endif
|
||||
|
||||
using std::sort;
|
||||
|
||||
const int PlaylistView::kGlowIntensitySteps = 24;
|
||||
const int PlaylistView::kAutoscrollGraceTimeout = 30; // seconds
|
||||
const int PlaylistView::kDropIndicatorWidth = 2;
|
||||
@@ -221,7 +220,7 @@ void PlaylistView::SetApplication(Application *app) {
|
||||
Q_ASSERT(app);
|
||||
app_ = app;
|
||||
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), this, SLOT(SongChanged(Song)));
|
||||
connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, QUrl, QImage)), SLOT(AlbumCoverLoaded(Song, QUrl, QImage)));
|
||||
connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)));
|
||||
connect(app_->player(), SIGNAL(Playing()), SLOT(StartGlowing()));
|
||||
connect(app_->player(), SIGNAL(Paused()), SLOT(StopGlowing()));
|
||||
connect(app_->player(), SIGNAL(Stopped()), SLOT(Stopped()));
|
||||
@@ -1261,17 +1260,15 @@ void PlaylistView::Stopped() {
|
||||
if (song_playing_ == Song()) return;
|
||||
song_playing_ = Song();
|
||||
StopGlowing();
|
||||
AlbumCoverLoaded(Song(), QUrl(), QImage());
|
||||
AlbumCoverLoaded(Song());
|
||||
|
||||
}
|
||||
|
||||
void PlaylistView::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &song_art) {
|
||||
void PlaylistView::AlbumCoverLoaded(const Song &song, AlbumCoverLoaderResult result) {
|
||||
|
||||
Q_UNUSED(cover_url);
|
||||
if ((song != Song() && song_playing_ == Song()) || result.image_original == current_song_cover_art_) return;
|
||||
|
||||
if ((song != Song() && song_playing_ == Song()) || song_art == current_song_cover_art_) return;
|
||||
|
||||
current_song_cover_art_ = song_art;
|
||||
current_song_cover_art_ = result.image_original;
|
||||
if (background_image_type_ == AppearanceSettingsPage::BackgroundImageType_Album) {
|
||||
if (song.art_automatic().isEmpty() && song.art_manual().isEmpty()) {
|
||||
set_background_image(QImage());
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
#include <QCommonStyle>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
#include "settings/appearancesettingspage.h"
|
||||
#include "playlist.h"
|
||||
|
||||
@@ -173,7 +174,7 @@ class PlaylistView : public QTreeView {
|
||||
void Playing();
|
||||
void Stopped();
|
||||
void SongChanged(const Song &song);
|
||||
void AlbumCoverLoaded(const Song &new_song, const QUrl &cover_url, const QImage &song_art);
|
||||
void AlbumCoverLoaded(const Song &song, AlbumCoverLoaderResult result = AlbumCoverLoaderResult());
|
||||
|
||||
private:
|
||||
void LoadGeometry();
|
||||
|
||||
@@ -48,3 +48,10 @@ Song SongPlaylistItem::Metadata() const {
|
||||
if (HasTemporaryMetadata()) return temp_metadata_;
|
||||
return song_;
|
||||
}
|
||||
|
||||
void SongPlaylistItem::SetArtManual(const QUrl &cover_url) {
|
||||
|
||||
song_.set_art_manual(cover_url);
|
||||
temp_metadata_.set_art_manual(cover_url);
|
||||
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ class SongPlaylistItem : public PlaylistItem {
|
||||
|
||||
QUrl Url() const;
|
||||
|
||||
protected:
|
||||
Song DatabaseSongMetadata() const { return song_; }
|
||||
void SetArtManual(const QUrl &cover_url);
|
||||
|
||||
private:
|
||||
Song song_;
|
||||
|
||||
Reference in New Issue
Block a user