diff --git a/data/data.qrc b/data/data.qrc
index 2ac1879a5..e95311c61 100644
--- a/data/data.qrc
+++ b/data/data.qrc
@@ -8,6 +8,7 @@
schema/schema-14.sql
schema/schema-15.sql
schema/schema-16.sql
+ schema/schema-17.sql
schema/device-schema.sql
style/strawberry.css
style/smartplaylistsearchterm.css
diff --git a/data/schema/device-schema.sql b/data/schema/device-schema.sql
index a998b1bd7..76a20b3f1 100644
--- a/data/schema/device-schema.sql
+++ b/data/schema/device-schema.sql
@@ -59,8 +59,10 @@ CREATE TABLE device_%deviceid_songs (
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
+ art_embedded INTEGER DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
+ art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
diff --git a/data/schema/schema-17.sql b/data/schema/schema-17.sql
new file mode 100644
index 000000000..8e7bbf6c9
--- /dev/null
+++ b/data/schema/schema-17.sql
@@ -0,0 +1,37 @@
+ALTER TABLE songs ADD COLUMN art_embedded INTEGER DEFAULT 0;
+
+ALTER TABLE songs ADD COLUMN art_unset INTEGER DEFAULT 0;
+
+ALTER TABLE subsonic_songs ADD COLUMN art_embedded INTEGER DEFAULT 0;
+
+ALTER TABLE subsonic_songs ADD COLUMN art_unset INTEGER DEFAULT 0;
+
+ALTER TABLE tidal_artists_songs ADD COLUMN art_embedded INTEGER DEFAULT 0;
+
+ALTER TABLE tidal_artists_songs ADD COLUMN art_unset INTEGER DEFAULT 0;
+
+ALTER TABLE tidal_albums_songs ADD COLUMN art_embedded INTEGER DEFAULT 0;
+
+ALTER TABLE tidal_albums_songs ADD COLUMN art_unset INTEGER DEFAULT 0;
+
+ALTER TABLE tidal_songs ADD COLUMN art_embedded INTEGER DEFAULT 0;
+
+ALTER TABLE tidal_songs ADD COLUMN art_unset INTEGER DEFAULT 0;
+
+ALTER TABLE qobuz_artists_songs ADD COLUMN art_embedded INTEGER DEFAULT 0;
+
+ALTER TABLE qobuz_artists_songs ADD COLUMN art_unset INTEGER DEFAULT 0;
+
+ALTER TABLE qobuz_albums_songs ADD COLUMN art_embedded INTEGER DEFAULT 0;
+
+ALTER TABLE qobuz_albums_songs ADD COLUMN art_unset INTEGER DEFAULT 0;
+
+ALTER TABLE qobuz_songs ADD COLUMN art_embedded INTEGER DEFAULT 0;
+
+ALTER TABLE qobuz_songs ADD COLUMN art_unset INTEGER DEFAULT 0;
+
+ALTER TABLE playlist_items ADD COLUMN art_embedded INTEGER DEFAULT 0;
+
+ALTER TABLE playlist_items ADD COLUMN art_unset INTEGER DEFAULT 0;
+
+UPDATE schema_version SET version=17;
diff --git a/data/schema/schema.sql b/data/schema/schema.sql
index 4e0b445e5..793e21234 100644
--- a/data/schema/schema.sql
+++ b/data/schema/schema.sql
@@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS schema_version (
DELETE FROM schema_version;
-INSERT INTO schema_version (version) VALUES (16);
+INSERT INTO schema_version (version) VALUES (17);
CREATE TABLE IF NOT EXISTS directories (
path TEXT NOT NULL,
@@ -67,8 +67,10 @@ CREATE TABLE IF NOT EXISTS songs (
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
+ art_embedded INTEGER DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
+ art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
@@ -143,8 +145,10 @@ CREATE TABLE IF NOT EXISTS subsonic_songs (
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
+ art_embedded INTEGER DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
+ art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
@@ -219,8 +223,10 @@ CREATE TABLE IF NOT EXISTS tidal_artists_songs (
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
+ art_embedded INTEGER DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
+ art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
@@ -295,8 +301,10 @@ CREATE TABLE IF NOT EXISTS tidal_albums_songs (
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
+ art_embedded INTEGER DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
+ art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
@@ -371,8 +379,10 @@ CREATE TABLE IF NOT EXISTS tidal_songs (
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
+ art_embedded INTEGER DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
+ art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
@@ -447,8 +457,10 @@ CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
+ art_embedded INTEGER DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
+ art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
@@ -523,8 +535,10 @@ CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
+ art_embedded INTEGER DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
+ art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
@@ -599,8 +613,10 @@ CREATE TABLE IF NOT EXISTS qobuz_songs (
compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0,
+ art_embedded INTEGER DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
+ art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0,
@@ -695,8 +711,10 @@ CREATE TABLE IF NOT EXISTS playlist_items (
compilation_off INTEGER DEFAULT 0,
compilation_effective INTEGER DEFAULT 0,
+ art_embedded INTEGER DEFAULT 0,
art_automatic TEXT,
art_manual TEXT,
+ art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT,
effective_originalyear INTEGER,
diff --git a/ext/libstrawberry-tagreader/tagreaderbase.cpp b/ext/libstrawberry-tagreader/tagreaderbase.cpp
index e98382bec..dcbd44b1d 100644
--- a/ext/libstrawberry-tagreader/tagreaderbase.cpp
+++ b/ext/libstrawberry-tagreader/tagreaderbase.cpp
@@ -1,5 +1,5 @@
/* This file is part of Strawberry.
- Copyright 2018-2021, Jonas Kvinge
+ Copyright 2018-2023, Jonas Kvinge
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -30,8 +30,6 @@
#include "core/logging.h"
#include "tagreaderbase.h"
-const std::string TagReaderBase::kEmbeddedCover = "(embedded)";
-
TagReaderBase::TagReaderBase() = default;
TagReaderBase::~TagReaderBase() = default;
@@ -59,10 +57,10 @@ int TagReaderBase::ConvertToPOPMRating(const float rating) {
}
-QByteArray TagReaderBase::LoadCoverDataFromRequest(const spb::tagreader::SaveFileRequest &request) {
+TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const spb::tagreader::SaveFileRequest &request) {
if (!request.has_save_cover() || !request.save_cover()) {
- return QByteArray();
+ return Cover();
}
const QString song_filename = QString::fromUtf8(request.filename().data(), request.filename().size());
@@ -74,16 +72,16 @@ QByteArray TagReaderBase::LoadCoverDataFromRequest(const spb::tagreader::SaveFil
if (request.has_cover_data()) {
cover_data = QByteArray(request.cover_data().data(), request.cover_data().size());
}
- bool cover_is_jpeg = false;
- if (request.has_cover_is_jpeg()) {
- cover_is_jpeg = request.cover_is_jpeg();
+ QString cover_mime_type;
+ if (request.has_cover_mime_type()) {
+ cover_mime_type = QByteArray(request.cover_mime_type().data(), request.cover_mime_type().size());
}
- return LoadCoverDataFromRequest(song_filename, cover_filename, cover_data, cover_is_jpeg);
+ return LoadCoverFromRequest(song_filename, cover_filename, cover_data, cover_mime_type);
}
-QByteArray TagReaderBase::LoadCoverDataFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request) {
+TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request) {
const QString song_filename = QString::fromUtf8(request.filename().data(), request.filename().size());
QString cover_filename;
@@ -94,37 +92,39 @@ QByteArray TagReaderBase::LoadCoverDataFromRequest(const spb::tagreader::SaveEmb
if (request.has_cover_data()) {
cover_data = QByteArray(request.cover_data().data(), request.cover_data().size());
}
- bool cover_is_jpeg = false;
- if (request.has_cover_is_jpeg()) {
- cover_is_jpeg = request.cover_is_jpeg();
+ QString cover_mime_type;
+ if (request.has_cover_mime_type()) {
+ cover_mime_type = QByteArray(request.cover_mime_type().data(), request.cover_mime_type().size());
}
- return LoadCoverDataFromRequest(song_filename, cover_filename, cover_data, cover_is_jpeg);
+ return LoadCoverFromRequest(song_filename, cover_filename, cover_data, cover_mime_type);
}
-QByteArray TagReaderBase::LoadCoverDataFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, const bool cover_is_jpeg) {
-
- if (!cover_data.isEmpty() && cover_is_jpeg) {
- qLog(Debug) << "Using cover from JPEG data for" << song_filename;
- return cover_data;
- }
+TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, QString cover_mime_type) {
if (cover_data.isEmpty() && !cover_filename.isEmpty()) {
qLog(Debug) << "Loading cover from" << cover_filename << "for" << song_filename;
QFile file(cover_filename);
if (!file.open(QIODevice::ReadOnly)) {
qLog(Error) << "Failed to open file" << cover_filename << "for reading:" << file.errorString();
- return QByteArray();
+ return Cover();
}
cover_data = file.readAll();
file.close();
}
if (!cover_data.isEmpty()) {
- if (QMimeDatabase().mimeTypeForData(cover_data).name() == "image/jpeg") {
+ if (cover_mime_type.isEmpty()) {
+ cover_mime_type = QMimeDatabase().mimeTypeForData(cover_data).name();
+ }
+ if (cover_mime_type == "image/jpeg") {
qLog(Debug) << "Using cover from JPEG data for" << song_filename;
- return cover_data;
+ return Cover(cover_data, cover_mime_type);
+ }
+ if (cover_mime_type == "image/png") {
+ qLog(Debug) << "Using cover from PNG data for" << song_filename;
+ return Cover(cover_data, cover_mime_type);
}
// Convert image to JPEG.
qLog(Debug) << "Converting cover to JPEG data for" << song_filename;
@@ -135,9 +135,9 @@ QByteArray TagReaderBase::LoadCoverDataFromRequest(const QString &song_filename,
cover_image.save(&buffer, "JPEG");
buffer.close();
}
- return cover_data;
+ return Cover(cover_data, "image/jpeg");
}
- return QByteArray();
+ return Cover();
}
diff --git a/ext/libstrawberry-tagreader/tagreaderbase.h b/ext/libstrawberry-tagreader/tagreaderbase.h
index 49aa73484..52fc1cbed 100644
--- a/ext/libstrawberry-tagreader/tagreaderbase.h
+++ b/ext/libstrawberry-tagreader/tagreaderbase.h
@@ -1,5 +1,5 @@
/* This file is part of Strawberry.
- Copyright 2018-2021, Jonas Kvinge
+ Copyright 2018-2023, Jonas Kvinge
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -36,6 +36,14 @@ class TagReaderBase {
explicit TagReaderBase();
~TagReaderBase();
+ class Cover {
+ public:
+ explicit Cover(const QByteArray &_data = QByteArray(), const QString &_mime_type = QString()) : data(_data), mime_type(_mime_type) {}
+ QByteArray data;
+ QString mime_type;
+ QString error;
+ };
+
virtual bool IsMediaFile(const QString &filename) const = 0;
virtual bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const = 0;
@@ -50,14 +58,11 @@ class TagReaderBase {
static float ConvertPOPMRating(const int POPM_rating);
static int ConvertToPOPMRating(const float rating);
- static QByteArray LoadCoverDataFromRequest(const spb::tagreader::SaveFileRequest &request);
- static QByteArray LoadCoverDataFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request);
+ static Cover LoadCoverFromRequest(const spb::tagreader::SaveFileRequest &request);
+ static Cover LoadCoverFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request);
private:
- static QByteArray LoadCoverDataFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, const bool cover_is_jpeg);
-
- protected:
- static const std::string kEmbeddedCover;
+ static Cover LoadCoverFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, QString cover_mime_type);
Q_DISABLE_COPY(TagReaderBase)
};
diff --git a/ext/libstrawberry-tagreader/tagreadermessages.proto b/ext/libstrawberry-tagreader/tagreadermessages.proto
index 7b5cf8199..80c03835e 100644
--- a/ext/libstrawberry-tagreader/tagreadermessages.proto
+++ b/ext/libstrawberry-tagreader/tagreadermessages.proto
@@ -69,7 +69,7 @@ message SongMetadata {
optional int64 lastplayed = 29;
optional int64 lastseen = 30;
- optional string art_automatic = 31;
+ optional bool art_embedded = 31;
optional float rating = 32;
@@ -97,6 +97,7 @@ message IsMediaFileRequest {
message IsMediaFileResponse {
optional bool success = 1;
+ optional string error = 2;
}
message ReadFileRequest {
@@ -105,6 +106,7 @@ message ReadFileRequest {
message ReadFileResponse {
optional SongMetadata metadata = 1;
+ optional string error = 2;
}
message SaveFileRequest {
@@ -116,11 +118,12 @@ message SaveFileRequest {
optional SongMetadata metadata = 6;
optional string cover_filename = 7;
optional bytes cover_data = 8;
- optional bool cover_is_jpeg = 9;
+ optional string cover_mime_type = 9;
}
message SaveFileResponse {
optional bool success = 1;
+ optional string error = 2;
}
message LoadEmbeddedArtRequest {
@@ -129,17 +132,19 @@ message LoadEmbeddedArtRequest {
message LoadEmbeddedArtResponse {
optional bytes data = 1;
+ optional string error = 2;
}
message SaveEmbeddedArtRequest {
optional string filename = 1;
optional string cover_filename = 2;
optional bytes cover_data = 3;
- optional bool cover_is_jpeg = 4;
+ optional string cover_mime_type = 4;
}
message SaveEmbeddedArtResponse {
optional bool success = 1;
+ optional string error = 2;
}
message SaveSongPlaycountToFileRequest {
@@ -149,6 +154,7 @@ message SaveSongPlaycountToFileRequest {
message SaveSongPlaycountToFileResponse {
optional bool success = 1;
+ optional string error = 2;
}
message SaveSongRatingToFileRequest {
@@ -158,6 +164,7 @@ message SaveSongRatingToFileRequest {
message SaveSongRatingToFileResponse {
optional bool success = 1;
+ optional string error = 2;
}
message Message {
diff --git a/ext/libstrawberry-tagreader/tagreadertaglib.cpp b/ext/libstrawberry-tagreader/tagreadertaglib.cpp
index 54ede44ab..4647e1fe2 100644
--- a/ext/libstrawberry-tagreader/tagreadertaglib.cpp
+++ b/ext/libstrawberry-tagreader/tagreadertaglib.cpp
@@ -1,6 +1,6 @@
/* This file is part of Strawberry.
Copyright 2013, David Sansome
- Copyright 2018-2021, Jonas Kvinge
+ Copyright 2018-2023, Jonas Kvinge
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -287,7 +287,7 @@ bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
if (!pictures.isEmpty()) {
for (TagLib::FLAC::Picture *picture : pictures) {
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
- song->set_art_automatic(kEmbeddedCover);
+ song->set_art_embedded(true);
break;
}
}
@@ -302,7 +302,7 @@ bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
if (!pictures.isEmpty()) {
for (TagLib::FLAC::Picture *picture : pictures) {
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
- song->set_art_automatic(kEmbeddedCover);
+ song->set_art_embedded(true);
break;
}
}
@@ -362,7 +362,7 @@ bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
TStringToStdString(map["SYLT"].front()->toString(), song->mutable_lyrics());
}
- if (map.contains("APIC")) song->set_art_automatic(kEmbeddedCover);
+ if (map.contains("APIC")) song->set_art_embedded(true);
// Find a suitable comment tag. For now we ignore iTunNORM comments.
for (uint i = 0; i < map["COMM"].size(); ++i) {
@@ -469,7 +469,7 @@ bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
// Find album cover art
if (mp4_tag->item("covr").isValid()) {
- song->set_art_automatic(kEmbeddedCover);
+ song->set_art_embedded(true);
}
if (mp4_tag->item("disk").isValid()) {
@@ -715,8 +715,8 @@ void TagReaderTagLib::ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString
if (map.contains("DISCNUMBER")) *disc = TStringToQString(map["DISCNUMBER"].front()).trimmed();
if (map.contains("COMPILATION")) *compilation = TStringToQString(map["COMPILATION"].front()).trimmed();
- if (map.contains("COVERART")) song->set_art_automatic(kEmbeddedCover);
- if (map.contains("METADATA_BLOCK_PICTURE")) song->set_art_automatic(kEmbeddedCover);
+ if (map.contains("COVERART")) song->set_art_embedded(true);
+ if (map.contains("METADATA_BLOCK_PICTURE")) song->set_art_embedded(true);
if (map.contains("FMPS_PLAYCOUNT") && song->playcount() <= 0) {
const int playcount = TStringToQString(map["FMPS_PLAYCOUNT"].front()).trimmed().toInt();
@@ -753,7 +753,7 @@ void TagReaderTagLib::ParseAPETag(const TagLib::APE::ItemListMap &map, QString *
}
}
- if (map.find("COVER ART (FRONT)") != map.end()) song->set_art_automatic(kEmbeddedCover);
+ if (map.find("COVER ART (FRONT)") != map.end()) song->set_art_embedded(true);
if (map.contains("COMPILATION")) {
*compilation = TStringToQString(TagLib::String::number(map["COMPILATION"].toString().toInt()));
}
@@ -853,7 +853,7 @@ bool TagReaderTagLib::SaveFile(const spb::tagreader::SaveFileRequest &request) c
qLog(Debug) << "Saving" << save_tags_options.join(", ") << "to" << filename;
- const QByteArray cover_data = LoadCoverDataFromRequest(request);
+ const Cover cover = LoadCoverFromRequest(request);
std::unique_ptr fileref(factory_->GetFileRef(filename));
if (!fileref || fileref->isNull()) return false;
@@ -883,7 +883,7 @@ bool TagReaderTagLib::SaveFile(const spb::tagreader::SaveFileRequest &request) c
SetRating(xiph_comment, song);
}
if (save_cover) {
- SetEmbeddedArt(file_flac, xiph_comment, cover_data);
+ SetEmbeddedArt(file_flac, xiph_comment, cover.data, cover.mime_type);
}
}
@@ -949,7 +949,7 @@ bool TagReaderTagLib::SaveFile(const spb::tagreader::SaveFileRequest &request) c
SetRating(tag, song);
}
if (save_cover) {
- SetEmbeddedArt(file_mpeg, tag, cover_data);
+ SetEmbeddedArt(file_mpeg, tag, cover.data, cover.mime_type);
}
}
@@ -971,7 +971,7 @@ bool TagReaderTagLib::SaveFile(const spb::tagreader::SaveFileRequest &request) c
SetRating(tag, song);
}
if (save_cover) {
- SetEmbeddedArt(file_mp4, tag, cover_data);
+ SetEmbeddedArt(file_mp4, tag, cover.data, cover.mime_type);
}
}
@@ -989,20 +989,20 @@ bool TagReaderTagLib::SaveFile(const spb::tagreader::SaveFileRequest &request) c
SetRating(xiph_comment, song);
}
if (save_cover) {
- SetEmbeddedArt(xiph_comment, cover_data);
+ SetEmbeddedArt(xiph_comment, cover.data, cover.mime_type);
}
}
}
- const bool result = fileref->save();
+ const bool success = fileref->save();
#ifdef Q_OS_LINUX
- if (result) {
+ if (success) {
// Linux: inotify doesn't seem to notice the change to the file unless we change the timestamps as well. (this is what touch does)
utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
}
#endif // Q_OS_LINUX
- return result;
+ return success;
}
@@ -1238,7 +1238,7 @@ QByteArray TagReaderTagLib::LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &m
}
-void TagReaderTagLib::SetEmbeddedArt(TagLib::FLAC::File *flac_file, TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data) const {
+void TagReaderTagLib::SetEmbeddedArt(TagLib::FLAC::File *flac_file, TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data, const QString &mime_type) const {
(void)xiph_comment;
@@ -1247,28 +1247,28 @@ void TagReaderTagLib::SetEmbeddedArt(TagLib::FLAC::File *flac_file, TagLib::Ogg:
if (!data.isEmpty()) {
TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture();
picture->setType(TagLib::FLAC::Picture::FrontCover);
- picture->setMimeType("image/jpeg");
+ picture->setMimeType(QStringToTString(mime_type));
picture->setData(TagLib::ByteVector(data.constData(), data.size()));
flac_file->addPicture(picture);
}
}
-void TagReaderTagLib::SetEmbeddedArt(TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data) const {
+void TagReaderTagLib::SetEmbeddedArt(TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data, const QString &mime_type) const {
xiph_comment->removeAllPictures();
if (!data.isEmpty()) {
TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture();
picture->setType(TagLib::FLAC::Picture::FrontCover);
- picture->setMimeType("image/jpeg");
+ picture->setMimeType(QStringToTString(mime_type));
picture->setData(TagLib::ByteVector(data.constData(), data.size()));
xiph_comment->addPicture(picture);
}
}
-void TagReaderTagLib::SetEmbeddedArt(TagLib::MPEG::File *file_mp3, TagLib::ID3v2::Tag *tag, const QByteArray &data) const {
+void TagReaderTagLib::SetEmbeddedArt(TagLib::MPEG::File *file_mp3, TagLib::ID3v2::Tag *tag, const QByteArray &data, const QString &mime_type) const {
(void)file_mp3;
@@ -1284,14 +1284,14 @@ void TagReaderTagLib::SetEmbeddedArt(TagLib::MPEG::File *file_mp3, TagLib::ID3v2
TagLib::ID3v2::AttachedPictureFrame *frontcover = nullptr;
frontcover = new TagLib::ID3v2::AttachedPictureFrame("APIC");
frontcover->setType(TagLib::ID3v2::AttachedPictureFrame::FrontCover);
- frontcover->setMimeType("image/jpeg");
+ frontcover->setMimeType(QStringToTString(mime_type));
frontcover->setPicture(TagLib::ByteVector(data.constData(), data.size()));
tag->addFrame(frontcover);
}
}
-void TagReaderTagLib::SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::Tag *tag, const QByteArray &data) const {
+void TagReaderTagLib::SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::Tag *tag, const QByteArray &data, const QString &mime_type) const {
(void)aac_file;
@@ -1300,7 +1300,17 @@ void TagReaderTagLib::SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::T
if (tag->contains("covr")) tag->removeItem("covr");
}
else {
- covers.append(TagLib::MP4::CoverArt(TagLib::MP4::CoverArt::JPEG, TagLib::ByteVector(data.constData(), data.size())));
+ TagLib::MP4::CoverArt::Format cover_format;
+ if (mime_type == "image/jpeg") {
+ cover_format = TagLib::MP4::CoverArt::Format::JPEG;
+ }
+ else if (mime_type == "image/png") {
+ cover_format = TagLib::MP4::CoverArt::Format::PNG;
+ }
+ else {
+ return;
+ }
+ covers.append(TagLib::MP4::CoverArt(cover_format, TagLib::ByteVector(data.constData(), data.size())));
tag->setItem("covr", covers);
}
@@ -1314,7 +1324,7 @@ bool TagReaderTagLib::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtReque
qLog(Debug) << "Saving art to" << filename;
- const QByteArray cover_data = LoadCoverDataFromRequest(request);
+ const Cover cover = LoadCoverFromRequest(request);
#ifdef Q_OS_WIN32
TagLib::FileRef fileref(filename.toStdWString().c_str());
@@ -1328,40 +1338,40 @@ bool TagReaderTagLib::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtReque
if (TagLib::FLAC::File *flac_file = dynamic_cast(fileref.file())) {
TagLib::Ogg::XiphComment *xiph_comment = flac_file->xiphComment(true);
if (!xiph_comment) return false;
- SetEmbeddedArt(flac_file, xiph_comment, cover_data);
+ SetEmbeddedArt(flac_file, xiph_comment, cover.data, cover.mime_type);
}
// Ogg Vorbis / Opus / Speex
else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast(fileref.file()->tag())) {
- SetEmbeddedArt(xiph_comment, cover_data);
+ SetEmbeddedArt(xiph_comment, cover.data, cover.mime_type);
}
// MP3
else if (TagLib::MPEG::File *file_mp3 = dynamic_cast(fileref.file())) {
TagLib::ID3v2::Tag *tag = file_mp3->ID3v2Tag();
if (!tag) return false;
- SetEmbeddedArt(file_mp3, tag, cover_data);
+ SetEmbeddedArt(file_mp3, tag, cover.data, cover.mime_type);
}
// MP4/AAC
else if (TagLib::MP4::File *aac_file = dynamic_cast(fileref.file())) {
TagLib::MP4::Tag *tag = aac_file->tag();
if (!tag) return false;
- SetEmbeddedArt(aac_file, tag, cover_data);
+ SetEmbeddedArt(aac_file, tag, cover.data, cover.mime_type);
}
// Not supported.
else return false;
- const bool result = fileref.file()->save();
+ const bool success = fileref.file()->save();
#ifdef Q_OS_LINUX
- if (result) {
+ if (success) {
// Linux: inotify doesn't seem to notice the change to the file unless we change the timestamps as well. (this is what touch does)
utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
}
#endif // Q_OS_LINUX
- return result;
+ return success;
}
@@ -1490,15 +1500,15 @@ bool TagReaderTagLib::SaveSongPlaycountToFile(const QString &filename, const spb
return true;
}
- bool ret = fileref->save();
+ bool success = fileref->save();
#ifdef Q_OS_LINUX
- if (ret) {
+ if (success) {
// Linux: inotify doesn't seem to notice the change to the file unless we change the timestamps as well. (this is what touch does)
utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
}
#endif // Q_OS_LINUX
- return ret;
+ return success;
}
@@ -1602,14 +1612,14 @@ bool TagReaderTagLib::SaveSongRatingToFile(const QString &filename, const spb::t
return true;
}
- bool ret = fileref->save();
+ const bool success = fileref->save();
#ifdef Q_OS_LINUX
- if (ret) {
+ if (success) {
// Linux: inotify doesn't seem to notice the change to the file unless we change the timestamps as well. (this is what touch does)
utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
}
#endif // Q_OS_LINUX
- return ret;
+ return success;
}
diff --git a/ext/libstrawberry-tagreader/tagreadertaglib.h b/ext/libstrawberry-tagreader/tagreadertaglib.h
index 8522cc408..bde696460 100644
--- a/ext/libstrawberry-tagreader/tagreadertaglib.h
+++ b/ext/libstrawberry-tagreader/tagreadertaglib.h
@@ -1,6 +1,6 @@
/* This file is part of Strawberry.
Copyright 2013, David Sansome
- Copyright 2018-2021, Jonas Kvinge
+ Copyright 2018-2023, Jonas Kvinge
Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -96,10 +96,10 @@ class TagReaderTagLib : public TagReaderBase {
void SetRating(TagLib::MP4::Tag *tag, const spb::tagreader::SongMetadata &song) const;
void SetRating(TagLib::ASF::Tag *tag, const spb::tagreader::SongMetadata &song) const;
- void SetEmbeddedArt(TagLib::FLAC::File *flac_file, TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data) const;
- void SetEmbeddedArt(TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data) const;
- void SetEmbeddedArt(TagLib::MPEG::File *file_mp3, TagLib::ID3v2::Tag *tag, const QByteArray &data) const;
- void SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::Tag *tag, const QByteArray &data) const;
+ void SetEmbeddedArt(TagLib::FLAC::File *flac_file, TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data, const QString &mime_type) const;
+ void SetEmbeddedArt(TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data, const QString &mime_type) const;
+ void SetEmbeddedArt(TagLib::MPEG::File *file_mp3, TagLib::ID3v2::Tag *tag, const QByteArray &data, const QString &mime_type) const;
+ void SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::Tag *tag, const QByteArray &data, const QString &mime_type) const;
private:
FileRefFactory *factory_;
diff --git a/ext/libstrawberry-tagreader/tagreadertagparser.cpp b/ext/libstrawberry-tagreader/tagreadertagparser.cpp
index 9cd8ae9dc..51a783f0e 100644
--- a/ext/libstrawberry-tagreader/tagreadertagparser.cpp
+++ b/ext/libstrawberry-tagreader/tagreadertagparser.cpp
@@ -224,7 +224,7 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
song->set_track(tag->value(TagParser::KnownField::TrackPosition).toInteger());
song->set_disc(tag->value(TagParser::KnownField::DiskPosition).toInteger());
if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) {
- song->set_art_automatic(kEmbeddedCover);
+ song->set_art_embedded(true);
}
const float rating = ConvertPOPMRating(tag->value(TagParser::KnownField::Rating));
if (song->rating() <= 0 && rating > 0.0 && rating <= 1.0) {
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2d4a28da4..e5ac69e66 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -147,6 +147,7 @@ set(SOURCES
covermanager/albumcovermanager.cpp
covermanager/albumcovermanagerlist.cpp
covermanager/albumcoverloader.cpp
+ covermanager/albumcoverloaderoptions.cpp
covermanager/albumcoverfetcher.cpp
covermanager/albumcoverfetchersearch.cpp
covermanager/albumcoversearcher.cpp
diff --git a/src/collection/collectionbackend.cpp b/src/collection/collectionbackend.cpp
index 4cd1b0688..95dbcf058 100644
--- a/src/collection/collectionbackend.cpp
+++ b/src/collection/collectionbackend.cpp
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
- * Copyright 2018-2021, Jonas Kvinge
+ * Copyright 2018-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1435,7 +1435,7 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
QSqlDatabase db(db_->Connect());
CollectionQuery query(db, songs_table_, fts_table_, opt);
- query.SetColumnSpec("url, effective_albumartist, album, compilation_effective, art_automatic, art_manual, filetype, cue_path");
+ query.SetColumnSpec("url, filetype, cue_path, effective_albumartist, album, compilation_effective, art_embedded, art_automatic, art_manual, art_unset");
query.SetOrderBy("effective_albumartist, album, url");
if (compilation_required) {
@@ -1453,42 +1453,48 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
QMap albums;
while (query.Next()) {
- bool is_compilation = query.Value(3).toBool();
- Album info;
+ Album album_info;
QUrl url = QUrl::fromEncoded(query.Value(0).toByteArray());
+
+ album_info.filetype = static_cast(query.Value(1).toInt());
+ const QString filetype = Song::TextForFiletype(album_info.filetype);
+ album_info.cue_path = query.Value(2).toString();
+
+ const bool is_compilation = query.Value(5).toBool();
if (!is_compilation) {
- info.album_artist = query.Value(1).toString();
+ album_info.album_artist = query.Value(3).toString();
}
- info.album = query.Value(2).toString();
- QString art_automatic = query.Value(4).toString();
+ album_info.album = query.Value(4).toString();
+
+ album_info.art_embedded = query.Value(6).toBool();
+
+ const QString art_automatic = query.Value(7).toString();
if (art_automatic.contains(QRegularExpression("..+:.*"))) {
- info.art_automatic = QUrl::fromEncoded(art_automatic.toUtf8());
+ album_info.art_automatic = QUrl::fromEncoded(art_automatic.toUtf8());
}
else {
- info.art_automatic = QUrl::fromLocalFile(art_automatic);
+ album_info.art_automatic = QUrl::fromLocalFile(art_automatic);
}
- QString art_manual = query.Value(5).toString();
+ const QString art_manual = query.Value(8).toString();
if (art_manual.contains(QRegularExpression("..+:.*"))) {
- info.art_manual = QUrl::fromEncoded(art_manual.toUtf8());
+ album_info.art_manual = QUrl::fromEncoded(art_manual.toUtf8());
}
else {
- info.art_manual = QUrl::fromLocalFile(art_manual);
+ album_info.art_manual = QUrl::fromLocalFile(art_manual);
}
- info.filetype = static_cast(query.Value(6).toInt());
- QString filetype = Song::TextForFiletype(info.filetype);
- info.cue_path = query.Value(7).toString();
+ album_info.art_unset = query.Value(9).toBool();
QString key;
- if (!info.album_artist.isEmpty()) {
- key.append(info.album_artist);
+ if (!album_info.album_artist.isEmpty()) {
+ key.append(album_info.album_artist);
}
- if (!info.album.isEmpty()) {
+ if (!album_info.album.isEmpty()) {
if (!key.isEmpty()) key.append("-");
- key.append(info.album);
+ key.append(album_info.album);
}
if (!filetype.isEmpty()) {
key.append(filetype);
@@ -1500,8 +1506,8 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
albums[key].urls.append(url);
}
else {
- info.urls << url;
- albums.insert(key, info);
+ album_info.urls << url;
+ albums.insert(key, album_info);
}
}
@@ -1520,7 +1526,7 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective
ret.album_artist = effective_albumartist;
CollectionQuery query(db, songs_table_, fts_table_);
- query.SetColumnSpec("art_automatic, art_manual, url");
+ query.SetColumnSpec("url, art_embedded, art_automatic, art_manual, art_unset");
if (!effective_albumartist.isEmpty()) {
query.AddWhere("effective_albumartist", effective_albumartist);
}
@@ -1532,22 +1538,24 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective
}
if (query.Next()) {
- ret.art_automatic = QUrl::fromEncoded(query.Value(0).toByteArray());
- ret.art_manual = QUrl::fromEncoded(query.Value(1).toByteArray());
- ret.urls << QUrl::fromEncoded(query.Value(2).toByteArray());
+ ret.urls << QUrl::fromEncoded(query.Value(0).toByteArray());
+ ret.art_embedded = query.Value(1).toInt() == 1;
+ ret.art_automatic = QUrl::fromEncoded(query.Value(2).toByteArray());
+ ret.art_manual = QUrl::fromEncoded(query.Value(3).toByteArray());
+ ret.art_unset = query.Value(4).toInt() == 1;
}
return ret;
}
-void CollectionBackend::UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic) {
+void CollectionBackend::UpdateEmbeddedAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_embedded) {
- QMetaObject::invokeMethod(this, "UpdateManualAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(QUrl, cover_url), Q_ARG(bool, clear_art_automatic));
+ QMetaObject::invokeMethod(this, "UpdateEmbeddedAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(bool, art_embedded));
}
-void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic) {
+void CollectionBackend::UpdateEmbeddedAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_embedded) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
@@ -1571,15 +1579,11 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
}
// Update the songs
- QString sql = QString("UPDATE %1 SET art_manual = :cover").arg(songs_table_);
- if (clear_art_automatic) {
- sql += ", art_automatic = ''";
- }
- sql += " WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0";
+ QString sql = QString("UPDATE %1 SET art_embedded = :art_embedded, art_unset = 0 WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_);
SqlQuery q(db);
q.prepare(sql);
- q.BindValue(":cover", cover_url.isValid() ? cover_url.toString(QUrl::FullyEncoded) : "");
+ q.BindValue(":art_embedded", art_embedded ? 1 : 0);
q.BindValue(":effective_albumartist", effective_albumartist);
q.BindValue(":album", album);
@@ -1608,18 +1612,17 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
}
-void CollectionBackend::UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual) {
+void CollectionBackend::UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &art_manual) {
- QMetaObject::invokeMethod(this, "UpdateAutomaticAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(QUrl, cover_url), Q_ARG(bool, clear_art_manual));
+ QMetaObject::invokeMethod(this, "UpdateManualAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(QUrl, art_manual));
}
-void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual) {
+void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &art_manual) {
QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect());
- // Get the songs before they're updated
CollectionQuery query(db, songs_table_, fts_table_);
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
query.AddWhere("effective_albumartist", effective_albumartist);
@@ -1637,16 +1640,124 @@ void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumar
deleted_songs << song;
}
- // Update the songs
- QString sql = QString("UPDATE %1 SET art_automatic = :cover").arg(songs_table_);
- if (clear_art_manual) {
- sql += ", art_manual = ''";
- }
- sql += " WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0";
-
SqlQuery q(db);
- q.prepare(sql);
- q.BindValue(":cover", cover_url.isValid() ? cover_url.toString(QUrl::FullyEncoded) : "");
+ q.prepare(QString("UPDATE %1 SET art_manual = :art_manual, art_unset = 0 WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
+ q.BindValue(":art_manual", art_manual.isValid() ? art_manual.toString(QUrl::FullyEncoded) : "");
+ q.BindValue(":effective_albumartist", effective_albumartist);
+ q.BindValue(":album", album);
+
+ if (!q.Exec()) {
+ db_->ReportErrors(q);
+ return;
+ }
+
+ if (!query.Exec()) {
+ ReportErrors(query);
+ return;
+ }
+
+ SongList added_songs;
+ while (query.Next()) {
+ Song song(source_);
+ song.InitFromQuery(query, true);
+ added_songs << song;
+ }
+
+ if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
+ emit SongsDeleted(deleted_songs);
+ emit SongsDiscovered(added_songs);
+ }
+
+}
+
+void CollectionBackend::UnsetAlbumArtAsync(const QString &effective_albumartist, const QString &album) {
+
+ QMetaObject::invokeMethod(this, "UnsetAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album));
+
+}
+
+void CollectionBackend::UnsetAlbumArt(const QString &effective_albumartist, const QString &album) {
+
+ QMutexLocker l(db_->Mutex());
+ QSqlDatabase db(db_->Connect());
+
+ CollectionQuery query(db, songs_table_, fts_table_);
+ query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
+ query.AddWhere("effective_albumartist", effective_albumartist);
+ query.AddWhere("album", album);
+
+ if (!query.Exec()) {
+ ReportErrors(query);
+ return;
+ }
+
+ SongList deleted_songs;
+ while (query.Next()) {
+ Song song(source_);
+ song.InitFromQuery(query, true);
+ deleted_songs << song;
+ }
+
+ SqlQuery q(db);
+ q.prepare(QString("UPDATE %1 SET art_unset = 1, art_manual = '', art_automatic = '', art_embedded = '' WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
+ q.BindValue(":effective_albumartist", effective_albumartist);
+ q.BindValue(":album", album);
+
+ if (!q.Exec()) {
+ db_->ReportErrors(q);
+ return;
+ }
+
+ if (!query.Exec()) {
+ ReportErrors(query);
+ return;
+ }
+
+ SongList added_songs;
+ while (query.Next()) {
+ Song song(source_);
+ song.InitFromQuery(query, true);
+ added_songs << song;
+ }
+
+ if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
+ emit SongsDeleted(deleted_songs);
+ emit SongsDiscovered(added_songs);
+ }
+
+}
+
+void CollectionBackend::ClearAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool unset) {
+
+ QMetaObject::invokeMethod(this, "ClearAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(bool, unset));
+
+}
+
+void CollectionBackend::ClearAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_unset) {
+
+ QMutexLocker l(db_->Mutex());
+ QSqlDatabase db(db_->Connect());
+
+ CollectionQuery query(db, songs_table_, fts_table_);
+ query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
+ query.AddWhere("effective_albumartist", effective_albumartist);
+ query.AddWhere("album", album);
+
+ if (!query.Exec()) {
+ ReportErrors(query);
+ return;
+ }
+
+ SongList deleted_songs;
+ while (query.Next()) {
+ Song song(source_);
+ song.InitFromQuery(query, true);
+ deleted_songs << song;
+ }
+
+ SqlQuery q(db);
+ q.prepare(QString("UPDATE %1 SET art_embedded = 0, art_automatic = '', art_manual = '', art_unset = :art_unset WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
+ q.BindValue(":art_unset", art_unset ? 1 : 0);
q.BindValue(":effective_albumartist", effective_albumartist);
q.BindValue(":album", album);
@@ -1655,7 +1766,6 @@ void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumar
return;
}
- // Now get the updated songs
if (!query.Exec()) {
ReportErrors(query);
return;
diff --git a/src/collection/collectionbackend.h b/src/collection/collectionbackend.h
index fe6316ffd..bbb9388c6 100644
--- a/src/collection/collectionbackend.h
+++ b/src/collection/collectionbackend.h
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
- * Copyright 2018-2021, Jonas Kvinge
+ * Copyright 2018-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -54,12 +54,14 @@ class CollectionBackendInterface : public QObject {
explicit CollectionBackendInterface(QObject *parent = nullptr) : QObject(parent) {}
struct Album {
- Album() : filetype(Song::FileType::Unknown) {}
- Album(const QString &_album_artist, const QString &_album, const QUrl &_art_automatic, const QUrl &_art_manual, const QList &_urls, const Song::FileType _filetype, const QString &_cue_path)
+ Album() : art_embedded(false), art_unset(false), filetype(Song::FileType::Unknown) {}
+ Album(const QString &_album_artist, const QString &_album, const bool _art_embedded, const QUrl &_art_automatic, const QUrl &_art_manual, const bool _art_unset, const QList &_urls, const Song::FileType _filetype, const QString &_cue_path)
: album_artist(_album_artist),
album(_album),
+ art_embedded(_art_embedded),
art_automatic(_art_automatic),
art_manual(_art_manual),
+ art_unset(_art_unset),
urls(_urls),
filetype(_filetype),
cue_path(_cue_path) {}
@@ -67,8 +69,10 @@ class CollectionBackendInterface : public QObject {
QString album_artist;
QString album;
+ bool art_embedded;
QUrl art_automatic;
QUrl art_manual;
+ bool art_unset;
QList urls;
Song::FileType filetype;
QString cue_path;
@@ -109,8 +113,10 @@ class CollectionBackendInterface : public QObject {
virtual AlbumList GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
virtual AlbumList GetCompilationAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
- virtual void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) = 0;
- virtual void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) = 0;
+ virtual void UpdateEmbeddedAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_embedded) = 0;
+ virtual void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &art_manual) = 0;
+ virtual void UnsetAlbumArtAsync(const QString &effective_albumartist, const QString &album) = 0;
+ virtual void ClearAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_unset) = 0;
virtual Album GetAlbumArt(const QString &effective_albumartist, const QString &album) = 0;
@@ -179,8 +185,10 @@ class CollectionBackend : public CollectionBackendInterface {
AlbumList GetCompilationAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
AlbumList GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
- void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) override;
- void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) override;
+ void UpdateEmbeddedAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_embedded) override;
+ void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &art_manual) override;
+ void UnsetAlbumArtAsync(const QString &effective_albumartist, const QString &album) override;
+ void ClearAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_unset) override;
Album GetAlbumArt(const QString &effective_albumartist, const QString &album) override;
@@ -232,8 +240,10 @@ class CollectionBackend : public CollectionBackendInterface {
void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true);
void AddOrUpdateSubdirs(const CollectionSubdirectoryList &subdirs);
void CompilationsNeedUpdating();
- void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false);
- void UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false);
+ void UpdateEmbeddedAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_embedded);
+ void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &art_manual);
+ void UnsetAlbumArt(const QString &effective_albumartist, const QString &album);
+ void ClearAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_unset);
void ForceCompilation(const QString &album, const QList &artists, const bool on);
void IncrementPlayCount(const int id);
void IncrementSkipCount(const int id, const float progress);
@@ -303,7 +313,6 @@ class CollectionBackend : public CollectionBackendInterface {
QString subdirs_table_;
QString fts_table_;
QThread *original_thread_;
-
};
#endif // COLLECTIONBACKEND_H
diff --git a/src/collection/collectionmodel.cpp b/src/collection/collectionmodel.cpp
index 6858f6709..961067ac8 100644
--- a/src/collection/collectionmodel.cpp
+++ b/src/collection/collectionmodel.cpp
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
- * Copyright 2018-2021, Jonas Kvinge
+ * Copyright 2018-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -71,6 +71,7 @@
#include "covermanager/albumcoverloader.h"
#include "covermanager/albumcoverloaderresult.h"
#include "settings/collectionsettingspage.h"
+#include "settings/coverssettingspage.h"
const int CollectionModel::kPrettyCoverSize = 32;
const char *CollectionModel::kPixmapDiskCacheDir = "pixmapcache";
@@ -101,12 +102,6 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q
group_by_[1] = GroupBy::AlbumDisc;
group_by_[2] = GroupBy::None;
- cover_loader_options_.get_image_data_ = false;
- cover_loader_options_.get_image_ = true;
- cover_loader_options_.scale_output_image_ = true;
- cover_loader_options_.pad_output_image_ = true;
- cover_loader_options_.desired_height_ = kPrettyCoverSize;
-
if (app_) {
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &CollectionModel::AlbumCoverLoaded);
}
@@ -150,6 +145,7 @@ void CollectionModel::set_pretty_covers(const bool use_pretty_covers) {
use_pretty_covers_ = use_pretty_covers;
Reset();
}
+
}
void CollectionModel::set_show_dividers(const bool show_dividers) {
@@ -177,6 +173,8 @@ void CollectionModel::ReloadSettings() {
s.endGroup();
+ cover_types_ = AlbumCoverLoaderOptions::LoadTypes();
+
if (!use_disk_cache_) {
ClearDiskCache();
}
@@ -652,7 +650,10 @@ QVariant CollectionModel::AlbumIcon(const QModelIndex &idx) {
// No art is cached and we're not loading it already. Load art for the first song in the album.
SongList songs = GetChildSongs(idx);
if (!songs.isEmpty()) {
- const quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, songs.first());
+ AlbumCoverLoaderOptions cover_loader_options(AlbumCoverLoaderOptions::Option::ScaledImage | AlbumCoverLoaderOptions::Option::PadScaledImage);
+ cover_loader_options.desired_scaled_size = QSize(kPrettyCoverSize, kPrettyCoverSize);
+ cover_loader_options.types = cover_types_;
+ const quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options, songs.first());
pending_art_[id] = ItemAndCacheKey(item, cache_key);
pending_cache_keys_.insert(cache_key);
}
@@ -674,7 +675,7 @@ void CollectionModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderR
pending_cache_keys_.remove(cache_key);
// Insert this image in the cache.
- if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type::ManuallyUnset) {
+ if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type::Unset) {
// Set the no_cover image so we don't continually try to load art.
QPixmapCache::insert(cache_key, no_cover_icon_);
}
diff --git a/src/collection/collectionmodel.h b/src/collection/collectionmodel.h
index 9f0ffb8b3..c60f6b604 100644
--- a/src/collection/collectionmodel.h
+++ b/src/collection/collectionmodel.h
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
- * Copyright 2018-2021, Jonas Kvinge
+ * Copyright 2018-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -49,11 +49,11 @@
#include "core/song.h"
#include "core/sqlrow.h"
#include "covermanager/albumcoverloader.h"
+#include "covermanager/albumcoverloaderoptions.h"
#include "collectionfilteroptions.h"
#include "collectionquery.h"
#include "collectionqueryoptions.h"
#include "collectionitem.h"
-#include "covermanager/albumcoverloaderoptions.h"
class QSettings;
@@ -310,7 +310,7 @@ class CollectionModel : public SimpleTreeModel {
bool use_disk_cache_;
bool use_lazy_loading_;
- AlbumCoverLoaderOptions cover_loader_options_;
+ AlbumCoverLoaderOptions::Types cover_types_;
using ItemAndCacheKey = QPair;
QMap pending_art_;
diff --git a/src/collection/collectionwatcher.cpp b/src/collection/collectionwatcher.cpp
index 06318557c..2d95802f6 100644
--- a/src/collection/collectionwatcher.cpp
+++ b/src/collection/collectionwatcher.cpp
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
- * Copyright 2018-2021, Jonas Kvinge
+ * Copyright 2018-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -156,10 +156,10 @@ void CollectionWatcher::ReloadSettings() {
overwrite_rating_ = s.value("overwrite_rating", false).toBool();
s.endGroup();
- best_image_filters_.clear();
+ best_art_filters_.clear();
for (const QString &filter : filters) {
QString str = filter.trimmed();
- if (!str.isEmpty()) best_image_filters_ << str;
+ if (!str.isEmpty()) best_art_filters_ << str;
}
if (!monitor_ && was_monitoring_before) {
@@ -539,8 +539,8 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
bool changed = (matching_song.mtime() != qMax(fileinfo.lastModified().toSecsSinceEpoch(), matching_song_cue_mtime)) || cue_deleted || cue_added || cue_changed;
// Also want to look to see whether the album art has changed
- QUrl image = ImageForSong(file, album_art);
- if ((matching_song.art_automatic().isEmpty() && !image.isEmpty()) || (!matching_song.art_automatic().isEmpty() && !matching_song.has_embedded_cover() && !QFile::exists(matching_song.art_automatic().toLocalFile()))) {
+ const QUrl art_automatic = ArtForSong(file, album_art);
+ if (matching_song.art_automatic() != art_automatic || (!matching_song.art_automatic().isEmpty() && !matching_song.art_automatic_is_valid())) {
changed = true;
}
@@ -573,16 +573,16 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
#endif
if (new_cue.isEmpty() || new_cue_mtime == 0) { // If no CUE or it's about to lose it.
- UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, image, cue_deleted, t);
+ UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, art_automatic, cue_deleted, t);
}
else { // If CUE associated.
- UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, image, matching_songs, t);
+ UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, art_automatic, matching_songs, t);
}
}
// Nothing has changed - mark the song available without re-scanning
- else if (matching_song.is_unavailable()) {
+ else if (matching_song.unavailable()) {
t->readded_songs << matching_songs;
}
@@ -633,13 +633,13 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
}
// Get new album art
- QUrl image = ImageForSong(file, album_art);
+ const QUrl art_automatic = ArtForSong(file, album_art);
if (new_cue.isEmpty() || new_cue_mtime == 0) { // If no CUE or it's about to lose it.
- UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, image, matching_songs_has_cue && new_cue_mtime == 0, t);
+ UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, art_automatic, matching_songs_has_cue && new_cue_mtime == 0, t);
}
else { // If CUE associated.
- UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, image, matching_songs, t);
+ UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, art_automatic, matching_songs, t);
}
}
@@ -653,12 +653,12 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
qLog(Debug) << file << "is new.";
- // Choose an image for the song(s)
- QUrl image = ImageForSong(file, album_art);
+ // Choose art for the song(s)
+ const QUrl art_automatic = ArtForSong(file, album_art);
for (Song song : songs) {
song.set_directory_id(t->dir());
- if (song.art_automatic().isEmpty()) song.set_art_automatic(image);
+ if (song.art_automatic().isEmpty()) song.set_art_automatic(art_automatic);
t->new_songs << song;
}
}
@@ -669,7 +669,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
// Look for deleted songs
for (const Song &song : songs_in_db) {
QString file = song.url().toLocalFile();
- if (!song.is_unavailable() && !files_on_disk.contains(file) && !t->files_changed_path_.contains(file)) {
+ if (!song.unavailable() && !files_on_disk.contains(file) && !t->files_changed_path_.contains(file)) {
qLog(Debug) << "Song deleted from disk:" << file;
t->deleted_songs << song;
}
@@ -704,7 +704,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
const QString &path,
const QString &fingerprint,
const QString &matching_cue,
- const QUrl &image,
+ const QUrl &art_automatic,
const SongList &old_cue_songs,
ScanTransaction *t) {
@@ -733,7 +733,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
if (sections_map.contains(new_cue_song.beginning_nanosec())) { // Changed section
const Song matching_cue_song = sections_map[new_cue_song.beginning_nanosec()];
new_cue_song.set_id(matching_cue_song.id());
- if (!new_cue_song.has_embedded_cover()) new_cue_song.set_art_automatic(image);
+ new_cue_song.set_art_automatic(art_automatic);
new_cue_song.MergeUserSetData(matching_cue_song, true, true);
AddChangedSong(file, matching_cue_song, new_cue_song, t);
used_ids.insert(matching_cue_song.id());
@@ -755,7 +755,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
const QString &fingerprint,
const SongList &matching_songs,
- const QUrl &image,
+ const QUrl &art_automatic,
const bool cue_deleted,
ScanTransaction *t) {
@@ -776,7 +776,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
song_on_disk.set_directory_id(t->dir());
song_on_disk.set_id(matching_song.id());
song_on_disk.set_fingerprint(fingerprint);
- if (!song_on_disk.has_embedded_cover()) song_on_disk.set_art_automatic(image);
+ song_on_disk.set_art_automatic(art_automatic);
song_on_disk.MergeUserSetData(matching_song, !overwrite_playcount_, !overwrite_rating_);
AddChangedSong(file, matching_song, song_on_disk, t);
}
@@ -838,7 +838,7 @@ void CollectionWatcher::AddChangedSong(const QString &file, const Song &matching
bool notify_new = false;
QStringList changes;
- if (matching_song.is_unavailable()) {
+ if (matching_song.unavailable()) {
qLog(Debug) << "unavailable song" << file << "restored.";
notify_new = true;
}
@@ -1039,20 +1039,20 @@ void CollectionWatcher::RescanPathsNow() {
}
-QString CollectionWatcher::PickBestImage(const QStringList &images) {
+QString CollectionWatcher::PickBestArt(const QStringList &art_automatic_list) {
// This is used when there is more than one image in a directory.
// Pick the biggest image that matches the most important filter
QStringList filtered;
- for (const QString &filter_text : best_image_filters_) {
+ for (const QString &filter_text : best_art_filters_) {
// The images in the images list are represented by a full path, so we need to isolate just the filename
- for (const QString &image : images) {
- QFileInfo fileinfo(image);
+ for (const QString &art_automatic : art_automatic_list) {
+ QFileInfo fileinfo(art_automatic);
QString filename(fileinfo.fileName());
if (filename.contains(filter_text, Qt::CaseInsensitive))
- filtered << image;
+ filtered << art_automatic;
}
// We assume the filters are give in the order best to worst, so if we've got a result, we go with it.
@@ -1062,7 +1062,7 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) {
if (filtered.isEmpty()) {
// The filter was too restrictive, just use the original list
- filtered = images;
+ filtered = art_automatic_list;
}
int biggest_size = 0;
@@ -1085,20 +1085,21 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) {
}
-QUrl CollectionWatcher::ImageForSong(const QString &path, QMap &album_art) {
+QUrl CollectionWatcher::ArtForSong(const QString &path, QMap &art_automatic_list) {
QString dir(DirectoryPart(path));
- if (album_art.contains(dir)) {
- if (album_art[dir].count() == 1) {
- return QUrl::fromLocalFile(album_art[dir][0]);
+ if (art_automatic_list.contains(dir)) {
+ if (art_automatic_list[dir].count() == 1) {
+ return QUrl::fromLocalFile(art_automatic_list[dir][0]);
}
else {
- QString best_image = PickBestImage(album_art[dir]);
- album_art[dir] = QStringList() << best_image;
- return QUrl::fromLocalFile(best_image);
+ const QString best_art = PickBestArt(art_automatic_list[dir]);
+ art_automatic_list[dir] = QStringList() << best_art;
+ return QUrl::fromLocalFile(best_art);
}
}
+
return QUrl();
}
diff --git a/src/collection/collectionwatcher.h b/src/collection/collectionwatcher.h
index 61004d39f..505d878db 100644
--- a/src/collection/collectionwatcher.h
+++ b/src/collection/collectionwatcher.h
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
- * Copyright 2018-2021, Jonas Kvinge
+ * Copyright 2018-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -178,17 +178,17 @@ class CollectionWatcher : public QObject {
inline static QString NoExtensionPart(const QString &fileName);
inline static QString ExtensionPart(const QString &fileName);
inline static QString DirectoryPart(const QString &fileName);
- QString PickBestImage(const QStringList &images);
- QUrl ImageForSong(const QString &path, QMap &album_art);
+ QString PickBestArt(const QStringList &art_automatic_list);
+ QUrl ArtForSong(const QString &path, QMap &art_automatic_list);
void AddWatch(const CollectionDirectory &dir, const QString &path);
void RemoveWatch(const CollectionDirectory &dir, const CollectionSubdirectory &subdir);
static quint64 GetMtimeForCue(const QString &cue_path);
void PerformScan(const bool incremental, const bool ignore_mtimes);
// Updates the sections of a cue associated and altered (according to mtime) media file during a scan.
- void UpdateCueAssociatedSongs(const QString &file, const QString &path, const QString &fingerprint, const QString &matching_cue, const QUrl &image, const SongList &old_cue_songs, ScanTransaction *t);
+ void UpdateCueAssociatedSongs(const QString &file, const QString &path, const QString &fingerprint, const QString &matching_cue, const QUrl &art_automatic, const SongList &old_cue_songs, ScanTransaction *t);
// Updates a single non-cue associated and altered (according to mtime) song during a scan.
- void UpdateNonCueAssociatedSong(const QString &file, const QString &fingerprint, const SongList &matching_songs, const QUrl &image, const bool cue_deleted, ScanTransaction *t);
+ void UpdateNonCueAssociatedSong(const QString &file, const QString &fingerprint, const SongList &matching_songs, const QUrl &art_automatic, const bool cue_deleted, ScanTransaction *t);
// Scans a single media file that's present on the disk but not yet in the collection.
// It may result in a multiple files added to the collection when the media file has many sections (like a CUE related media file).
SongList ScanNewFile(const QString &file, const QString &path, const QString &fingerprint, const QString &matching_cue, QSet *cues_processed);
@@ -210,9 +210,9 @@ class CollectionWatcher : public QObject {
QThread *original_thread_;
QHash subdir_mapping_;
- // A list of words use to try to identify the (likely) best image found in an directory to use as cover artwork.
+ // A list of words use to try to identify the (likely) best album cover art found in an directory to use as cover artwork.
// e.g. using ["front", "cover"] would identify front.jpg and exclude back.jpg.
- QStringList best_image_filters_;
+ QStringList best_art_filters_;
bool scan_on_startup_;
bool monitor_;
diff --git a/src/context/contextalbum.cpp b/src/context/contextalbum.cpp
index 271e13f83..c4ed2adb4 100644
--- a/src/context/contextalbum.cpp
+++ b/src/context/contextalbum.cpp
@@ -54,16 +54,14 @@ ContextAlbum::ContextAlbum(QWidget *parent)
timeline_fade_(new QTimeLine(kFadeTimeLineMs, this)),
image_strawberry_(":/pictures/strawberry.png"),
image_original_(image_strawberry_),
- pixmap_current_opacity_(1.0) {
+ pixmap_current_opacity_(1.0),
+ desired_height_(width()) {
setObjectName("context-widget-album");
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
- cover_loader_options_.desired_height_ = width();
- cover_loader_options_.pad_output_image_ = true;
- cover_loader_options_.scale_output_image_ = true;
- QImage image = ImageUtils::ScaleAndPad(image_strawberry_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_, devicePixelRatioF());
+ QImage image = ImageUtils::ScaleImage(image_strawberry_, QSize(desired_height_, desired_height_), devicePixelRatioF(), true);
if (!image.isNull()) {
pixmap_current_ = QPixmap::fromImage(image);
}
@@ -128,8 +126,8 @@ void ContextAlbum::contextMenuEvent(QContextMenuEvent *e) {
void ContextAlbum::UpdateWidth(const int new_width) {
- if (new_width != cover_loader_options_.desired_height_) {
- cover_loader_options_.desired_height_ = new_width;
+ if (new_width != desired_height_) {
+ desired_height_ = new_width;
ScaleCover();
ScalePreviousCovers();
updateGeometry();
@@ -235,7 +233,7 @@ void ContextAlbum::FadePreviousCoverFinished(std::shared_ptr prev
void ContextAlbum::ScaleCover() {
- QImage image = ImageUtils::ScaleAndPad(image_original_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_, devicePixelRatioF());
+ const QImage image = ImageUtils::ScaleImage(image_original_, QSize(desired_height_, desired_height_), devicePixelRatioF(), true);
if (image.isNull()) {
pixmap_current_ = QPixmap();
}
@@ -248,7 +246,7 @@ void ContextAlbum::ScaleCover() {
void ContextAlbum::ScalePreviousCovers() {
for (std::shared_ptr previous_cover : previous_covers_) {
- QImage image = ImageUtils::ScaleAndPad(previous_cover->image, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_, devicePixelRatioF());
+ QImage image = ImageUtils::ScaleImage(previous_cover->image, QSize(desired_height_, desired_height_), devicePixelRatioF(), true);
if (image.isNull()) {
previous_cover->pixmap = QPixmap();
}
diff --git a/src/context/contextalbum.h b/src/context/contextalbum.h
index fb68f440d..6bebcb651 100644
--- a/src/context/contextalbum.h
+++ b/src/context/contextalbum.h
@@ -33,8 +33,6 @@
#include
#include
-#include "covermanager/albumcoverloaderoptions.h"
-
class QMenu;
class QTimeLine;
class QPainter;
@@ -99,7 +97,6 @@ class ContextAlbum : public QWidget {
QMenu *menu_;
ContextView *context_view_;
AlbumCoverChoiceController *album_cover_choice_controller_;
- AlbumCoverLoaderOptions cover_loader_options_;
bool downloading_covers_;
QTimeLine *timeline_fade_;
QImage image_strawberry_;
@@ -107,6 +104,7 @@ class ContextAlbum : public QWidget {
QPixmap pixmap_current_;
qreal pixmap_current_opacity_;
std::unique_ptr spinner_animation_;
+ int desired_height_;
};
#endif // CONTEXTALBUM_H
diff --git a/src/core/database.cpp b/src/core/database.cpp
index bbf8fd1d3..6a039bebc 100644
--- a/src/core/database.cpp
+++ b/src/core/database.cpp
@@ -48,7 +48,7 @@
#include "scopedtransaction.h"
const char *Database::kDatabaseFilename = "strawberry.db";
-const int Database::kSchemaVersion = 16;
+const int Database::kSchemaVersion = 17;
const int Database::kMinSupportedSchemaVersion = 10;
const char *Database::kMagicAllSongsTables = "%allsongstables";
diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp
index 32b91d476..39f4ab19c 100644
--- a/src/core/mainwindow.cpp
+++ b/src/core/mainwindow.cpp
@@ -1177,6 +1177,7 @@ void MainWindow::ReloadAllSettings() {
collection_view_->ReloadSettings();
ui_->playlist->view()->ReloadSettings();
app_->playlist_manager()->playlist_container()->ReloadSettings();
+ app_->current_albumcover_loader()->ReloadSettingsAsync();
album_cover_choice_controller_->ReloadSettings();
context_view_->ReloadSettings();
file_view_->ReloadSettings();
@@ -1381,14 +1382,14 @@ void MainWindow::SongChanged(const Song &song) {
SendNowPlaying();
const bool enable_change_art = song.is_collection_song() && !song.effective_albumartist().isEmpty() && !song.album().isEmpty();
- album_cover_choice_controller_->show_cover_action()->setEnabled(song.has_valid_art() && !song.has_manually_unset_cover());
- album_cover_choice_controller_->cover_to_file_action()->setEnabled(song.has_valid_art() && !song.has_manually_unset_cover());
+ album_cover_choice_controller_->show_cover_action()->setEnabled(song.has_valid_art() && !song.art_unset());
+ album_cover_choice_controller_->cover_to_file_action()->setEnabled(song.has_valid_art() && !song.art_unset());
album_cover_choice_controller_->cover_from_file_action()->setEnabled(enable_change_art);
album_cover_choice_controller_->cover_from_url_action()->setEnabled(enable_change_art);
album_cover_choice_controller_->search_for_cover_action()->setEnabled(app_->cover_providers()->HasAnyProviders() && enable_change_art);
- album_cover_choice_controller_->unset_cover_action()->setEnabled(enable_change_art && !song.has_manually_unset_cover());
+ album_cover_choice_controller_->unset_cover_action()->setEnabled(enable_change_art && !song.art_unset());
album_cover_choice_controller_->clear_cover_action()->setEnabled(enable_change_art && !song.art_manual().isEmpty());
- album_cover_choice_controller_->delete_cover_action()->setEnabled(enable_change_art && song.has_valid_art() && !song.has_manually_unset_cover());
+ album_cover_choice_controller_->delete_cover_action()->setEnabled(enable_change_art && (song.art_embedded() || !song.art_automatic().isEmpty() || !song.art_manual().isEmpty()));
}
@@ -3072,14 +3073,14 @@ void MainWindow::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult
emit AlbumCoverReady(song, result.album_cover.image);
const bool enable_change_art = song.is_collection_song() && !song.effective_albumartist().isEmpty() && !song.album().isEmpty();
- album_cover_choice_controller_->show_cover_action()->setEnabled(result.success && result.type != AlbumCoverLoaderResult::Type::ManuallyUnset);
- album_cover_choice_controller_->cover_to_file_action()->setEnabled(result.success && result.type != AlbumCoverLoaderResult::Type::ManuallyUnset);
+ album_cover_choice_controller_->show_cover_action()->setEnabled(result.success && result.type != AlbumCoverLoaderResult::Type::Unset);
+ album_cover_choice_controller_->cover_to_file_action()->setEnabled(result.success && result.type != AlbumCoverLoaderResult::Type::Unset);
album_cover_choice_controller_->cover_from_file_action()->setEnabled(enable_change_art);
album_cover_choice_controller_->cover_from_url_action()->setEnabled(enable_change_art);
album_cover_choice_controller_->search_for_cover_action()->setEnabled(app_->cover_providers()->HasAnyProviders() && enable_change_art);
- album_cover_choice_controller_->unset_cover_action()->setEnabled(enable_change_art && !song.has_manually_unset_cover());
+ album_cover_choice_controller_->unset_cover_action()->setEnabled(enable_change_art && !song.art_unset());
album_cover_choice_controller_->clear_cover_action()->setEnabled(enable_change_art && !song.art_manual().isEmpty());
- album_cover_choice_controller_->delete_cover_action()->setEnabled(enable_change_art && result.success && result.type != AlbumCoverLoaderResult::Type::ManuallyUnset);
+ album_cover_choice_controller_->delete_cover_action()->setEnabled(enable_change_art && result.success && result.type != AlbumCoverLoaderResult::Type::Unset);
GetCoverAutomatically();
@@ -3089,7 +3090,8 @@ void MainWindow::GetCoverAutomatically() {
// Search for cover automatically?
const bool search = album_cover_choice_controller_->search_cover_auto_action()->isChecked() &&
- !song_.has_manually_unset_cover() &&
+ !song_.art_unset() &&
+ !song_.art_embedded() &&
!song_.art_automatic_is_valid() &&
!song_.art_manual_is_valid() &&
!song_.effective_albumartist().isEmpty() &&
diff --git a/src/core/mpris2.cpp b/src/core/mpris2.cpp
index 791b7e3fd..04119d83e 100644
--- a/src/core/mpris2.cpp
+++ b/src/core/mpris2.cpp
@@ -401,14 +401,16 @@ void Mpris2::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &re
else if (result.temp_cover_url.isValid() && result.temp_cover_url.isLocalFile()) {
cover_url = result.temp_cover_url;
}
- else if (song.art_manual().isValid() && song.art_manual().isLocalFile() && song.art_manual().path() != Song::kManuallyUnsetCover && song.art_manual().path() != Song::kEmbeddedCover) {
+ else if (song.art_manual().isValid() && song.art_manual().isLocalFile()) {
cover_url = song.art_manual();
}
- else if (song.art_automatic().isValid() && song.art_automatic().isLocalFile() && song.art_automatic().path() != Song::kManuallyUnsetCover && song.art_automatic().path() != Song::kEmbeddedCover) {
+ else if (song.art_automatic().isValid() && song.art_automatic().isLocalFile()) {
cover_url = song.art_automatic();
}
- if (cover_url.isValid()) AddMetadata("mpris:artUrl", cover_url.toString(), &last_metadata_);
+ if (cover_url.isValid()) {
+ AddMetadata("mpris:artUrl", cover_url.toString(), &last_metadata_);
+ }
AddMetadata("year", song.year(), &last_metadata_);
AddMetadata("bitrate", song.bitrate(), &last_metadata_);
diff --git a/src/core/song.cpp b/src/core/song.cpp
index 133afa8f8..2c99122a2 100644
--- a/src/core/song.cpp
+++ b/src/core/song.cpp
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
- * Copyright 2018-2021, Jonas Kvinge
+ * Copyright 2018-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -108,8 +108,10 @@ const QStringList Song::kColumns = QStringList() << "title"
<< "compilation_off"
<< "compilation_effective"
+ << "art_embedded"
<< "art_automatic"
<< "art_manual"
+ << "art_unset"
<< "effective_albumartist"
<< "effective_originalyear"
@@ -170,17 +172,14 @@ struct Song::Private : public QSharedData {
explicit Private(Source source = Source::Unknown);
- bool valid_;
int id_;
+ bool valid_;
+
QString title_;
- QString title_sortable_;
QString album_;
- QString album_sortable_;
QString artist_;
- QString artist_sortable_;
QString albumartist_;
- QString albumartist_sortable_;
int track_;
int disc_;
int year_;
@@ -225,9 +224,10 @@ struct Song::Private : public QSharedData {
bool compilation_on_; // Set by the user
bool compilation_off_; // Set by the user
- // Filenames to album art for this song.
- QUrl art_automatic_; // Guessed by CollectionWatcher
- QUrl art_manual_; // Set by the user - should take priority
+ bool art_embedded_; // if the song has embedded album cover art.
+ QUrl art_automatic_; // Guessed by CollectionWatcher.
+ QUrl art_manual_; // Set by the user - should take priority.
+ bool art_unset_; // If the art was unset by the user.
QString cue_path_; // If the song has a CUE, this contains it's path.
@@ -247,15 +247,21 @@ struct Song::Private : public QSharedData {
QString musicbrainz_release_group_id_;
QString musicbrainz_work_id_;
- QUrl stream_url_; // Temporary stream url set by url handler.
bool init_from_file_; // Whether this song was loaded from a file using taglib.
bool suspicious_tags_; // Whether our encoding guesser thinks these tags might be incorrectly encoded.
+ QString title_sortable_;
+ QString album_sortable_;
+ QString artist_sortable_;
+ QString albumartist_sortable_;
+
+ QUrl stream_url_; // Temporary stream url set by the URL handler.
+
};
Song::Private::Private(const Source source)
- : valid_(false),
- id_(-1),
+ : id_(-1),
+ valid_(false),
track_(-1),
disc_(-1),
@@ -287,6 +293,9 @@ Song::Private::Private(const Source source)
compilation_on_(false),
compilation_off_(false),
+ art_embedded_(false),
+ art_unset_(false),
+
rating_(-1),
init_from_file_(false),
@@ -298,38 +307,30 @@ Song::Song(const Source source) : d(new Private(source)) {}
Song::Song(const Song &other) = default;
Song::~Song() = default;
+bool Song::operator==(const Song &other) const {
+ return source() == other.source() && url() == other.url() && beginning_nanosec() == other.beginning_nanosec();
+}
+
+bool Song::operator!=(const Song &other) const {
+ return source() != other.source() || url() != other.url() || beginning_nanosec() != other.beginning_nanosec();
+}
+
Song &Song::operator=(const Song &other) {
d = other.d;
return *this;
}
-bool Song::is_valid() const { return d->valid_; }
-bool Song::is_unavailable() const { return d->unavailable_; }
int Song::id() const { return d->id_; }
-
-QString Song::artist_id() const { return d->artist_id_.isNull() ? "" : d->artist_id_; }
-QString Song::album_id() const { return d->album_id_.isNull() ? "" : d->album_id_; }
-QString Song::song_id() const { return d->song_id_.isNull() ? "" : d->song_id_; }
+bool Song::is_valid() const { return d->valid_; }
const QString &Song::title() const { return d->title_; }
-const QString &Song::title_sortable() const { return d->title_sortable_; }
const QString &Song::album() const { return d->album_; }
-const QString &Song::album_sortable() const { return d->album_sortable_; }
-// This value is useful for singles, which are one-track albums on their own.
-const QString &Song::effective_album() const { return d->album_.isEmpty() ? d->title_ : d->album_; }
const QString &Song::artist() const { return d->artist_; }
-const QString &Song::artist_sortable() const { return d->artist_sortable_; }
const QString &Song::albumartist() const { return d->albumartist_; }
-const QString &Song::albumartist_sortable() const { return d->albumartist_sortable_; }
-const QString &Song::effective_albumartist() const { return d->albumartist_.isEmpty() ? d->artist_ : d->albumartist_; }
-const QString &Song::effective_albumartist_sortable() const { return d->albumartist_.isEmpty() ? d->artist_sortable_ : d->albumartist_sortable_; }
-const QString &Song::playlist_albumartist() const { return is_compilation() ? d->albumartist_ : effective_albumartist(); }
-const QString &Song::playlist_albumartist_sortable() const { return is_compilation() ? d->albumartist_sortable_ : effective_albumartist_sortable(); }
int Song::track() const { return d->track_; }
int Song::disc() const { return d->disc_; }
int Song::year() const { return d->year_; }
int Song::originalyear() const { return d->originalyear_; }
-int Song::effective_originalyear() const { return d->originalyear_ < 0 ? d->year_ : d->originalyear_; }
const QString &Song::genre() const { return d->genre_; }
bool Song::compilation() const { return d->compilation_; }
const QString &Song::composer() const { return d->composer_; }
@@ -338,6 +339,10 @@ const QString &Song::grouping() const { return d->grouping_; }
const QString &Song::comment() const { return d->comment_; }
const QString &Song::lyrics() const { return d->lyrics_; }
+QString Song::artist_id() const { return d->artist_id_.isNull() ? "" : d->artist_id_; }
+QString Song::album_id() const { return d->album_id_.isNull() ? "" : d->album_id_; }
+QString Song::song_id() const { return d->song_id_.isNull() ? "" : d->song_id_; }
+
qint64 Song::beginning_nanosec() const { return d->beginning_; }
qint64 Song::end_nanosec() const { return d->end_; }
qint64 Song::length_nanosec() const { return d->end_ - d->beginning_; }
@@ -354,6 +359,7 @@ Song::FileType Song::filetype() const { return d->filetype_; }
qint64 Song::filesize() const { return d->filesize_; }
qint64 Song::mtime() const { return d->mtime_; }
qint64 Song::ctime() const { return d->ctime_; }
+bool Song::unavailable() const { return d->unavailable_; }
QString Song::fingerprint() const { return d->fingerprint_; }
@@ -366,99 +372,12 @@ bool Song::compilation_detected() const { return d->compilation_detected_; }
bool Song::compilation_off() const { return d->compilation_off_; }
bool Song::compilation_on() const { return d->compilation_on_; }
+bool Song::art_embedded() const { return d->art_embedded_; }
const QUrl &Song::art_automatic() const { return d->art_automatic_; }
const QUrl &Song::art_manual() const { return d->art_manual_; }
-bool Song::has_manually_unset_cover() const { return d->art_manual_.path() == kManuallyUnsetCover; }
-void Song::set_manually_unset_cover() { d->art_manual_ = QUrl::fromLocalFile(kManuallyUnsetCover); }
-bool Song::has_embedded_cover() const { return d->art_automatic_.path() == kEmbeddedCover; }
-void Song::set_embedded_cover() { d->art_automatic_ = QUrl::fromLocalFile(kEmbeddedCover); }
-
-void Song::clear_art_automatic() { d->art_automatic_.clear(); }
-void Song::clear_art_manual() { d->art_manual_.clear(); }
-
-bool Song::additional_tags_supported() const {
- return d->filetype_ == FileType::FLAC ||
- d->filetype_ == FileType::WavPack ||
- d->filetype_ == FileType::OggFlac ||
- d->filetype_ == FileType::OggVorbis ||
- d->filetype_ == FileType::OggOpus ||
- d->filetype_ == FileType::OggSpeex ||
- d->filetype_ == FileType::MPEG ||
- d->filetype_ == FileType::MP4 ||
- d->filetype_ == FileType::MPC ||
- d->filetype_ == FileType::APE;
-}
-
-bool Song::albumartist_supported() const {
- return additional_tags_supported();
-}
-
-bool Song::composer_supported() const {
- return additional_tags_supported();
-}
-
-bool Song::performer_supported() const {
- return d->filetype_ == FileType::FLAC ||
- d->filetype_ == FileType::WavPack ||
- d->filetype_ == FileType::OggFlac ||
- d->filetype_ == FileType::OggVorbis ||
- d->filetype_ == FileType::OggOpus ||
- d->filetype_ == FileType::OggSpeex ||
- d->filetype_ == FileType::MPEG ||
- d->filetype_ == FileType::MPC ||
- d->filetype_ == FileType::APE;
-}
-
-bool Song::grouping_supported() const {
- return additional_tags_supported();
-}
-
-bool Song::genre_supported() const {
- return additional_tags_supported();
-}
-
-bool Song::compilation_supported() const {
- return additional_tags_supported();
-}
-
-bool Song::rating_supported() const {
- return d->filetype_ == FileType::FLAC ||
- d->filetype_ == FileType::WavPack ||
- d->filetype_ == FileType::OggFlac ||
- d->filetype_ == FileType::OggVorbis ||
- d->filetype_ == FileType::OggOpus ||
- d->filetype_ == FileType::OggSpeex ||
- d->filetype_ == FileType::MPEG ||
- d->filetype_ == FileType::MP4 ||
- d->filetype_ == FileType::ASF ||
- d->filetype_ == FileType::MPC ||
- d->filetype_ == FileType::APE;
-}
-
-bool Song::comment_supported() const {
- return additional_tags_supported();
-}
-
-bool Song::lyrics_supported() const {
- return additional_tags_supported();
-}
-
-bool Song::save_embedded_cover_supported(const FileType filetype) {
-
- return filetype == FileType::FLAC ||
- filetype == FileType::OggVorbis ||
- filetype == FileType::OggOpus ||
- filetype == FileType::MPEG ||
- filetype == FileType::MP4;
-
-}
-
-const QUrl &Song::stream_url() const { return d->stream_url_; }
-const QUrl &Song::effective_stream_url() const { return !d->stream_url_.isEmpty() && d->stream_url_.isValid() ? d->stream_url_ : d->url_; }
-bool Song::init_from_file() const { return d->init_from_file_; }
+bool Song::art_unset() const { return d->art_unset_; }
const QString &Song::cue_path() const { return d->cue_path_; }
-bool Song::has_cue() const { return !d->cue_path_.isEmpty(); }
float Song::rating() const { return d->rating_; }
@@ -476,60 +395,18 @@ const QString &Song::musicbrainz_disc_id() const { return d->musicbrainz_disc_id
const QString &Song::musicbrainz_release_group_id() const { return d->musicbrainz_release_group_id_; }
const QString &Song::musicbrainz_work_id() const { return d->musicbrainz_work_id_; }
-bool Song::is_collection_song() const { return d->source_ == Source::Collection; }
-bool Song::is_metadata_good() const { return !d->url_.isEmpty() && !d->artist_.isEmpty() && !d->title_.isEmpty(); }
-bool Song::is_stream() const { return is_radio() || d->source_ == Source::Tidal || d->source_ == Source::Subsonic || d->source_ == Source::Qobuz; }
-bool Song::is_radio() const { return d->source_ == Source::Stream || d->source_ == Source::SomaFM || d->source_ == Source::RadioParadise; }
-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::stream_url_can_expire() const { return d->source_ == Source::Tidal || d->source_ == Source::Qobuz; }
-bool Song::is_module_music() const { return d->filetype_ == FileType::MOD || d->filetype_ == FileType::S3M || d->filetype_ == FileType::XM || d->filetype_ == FileType::IT; }
+bool Song::init_from_file() const { return d->init_from_file_; }
-bool Song::art_automatic_is_valid() const {
- return !d->art_automatic_.isEmpty() &&
- (
- (d->art_automatic_.path() == kManuallyUnsetCover) ||
- (d->art_automatic_.path() == kEmbeddedCover) ||
- (d->art_automatic_.isValid() && !d->art_automatic_.isLocalFile()) ||
- (d->art_automatic_.isLocalFile() && QFile::exists(d->art_automatic_.toLocalFile())) ||
- (d->art_automatic_.scheme().isEmpty() && !d->art_automatic_.path().isEmpty() && QFile::exists(d->art_automatic_.path()))
- );
-}
+const QString &Song::title_sortable() const { return d->title_sortable_; }
+const QString &Song::album_sortable() const { return d->album_sortable_; }
+const QString &Song::artist_sortable() const { return d->artist_sortable_; }
+const QString &Song::albumartist_sortable() const { return d->albumartist_sortable_; }
-bool Song::art_manual_is_valid() const {
- return !d->art_manual_.isEmpty() &&
- (
- (d->art_manual_.path() == kManuallyUnsetCover) ||
- (d->art_manual_.path() == kEmbeddedCover) ||
- (d->art_manual_.isValid() && !d->art_manual_.isLocalFile()) ||
- (d->art_manual_.isLocalFile() && QFile::exists(d->art_manual_.toLocalFile())) ||
- (d->art_manual_.scheme().isEmpty() && !d->art_manual_.path().isEmpty() && QFile::exists(d->art_manual_.path()))
- );
-}
-
-bool Song::has_valid_art() const { return art_automatic_is_valid() || art_manual_is_valid(); }
+const QUrl &Song::stream_url() const { return d->stream_url_; }
void Song::set_id(const int id) { d->id_ = id; }
void Song::set_valid(const bool v) { d->valid_ = v; }
-void Song::set_artist_id(const QString &v) { d->artist_id_ = v; }
-void Song::set_album_id(const QString &v) { d->album_id_ = v; }
-void Song::set_song_id(const QString &v) { d->song_id_ = v; }
-
-QString Song::sortable(const QString &v) {
-
- QString copy = v.toLower();
-
- for (const auto &i : kArticles) {
- if (copy.startsWith(i)) {
- qint64 ilen = i.length();
- return copy.right(copy.length() - ilen) + ", " + copy.left(ilen - 1);
- }
- }
-
- return copy;
-}
-
void Song::set_title(const QString &v) { d->title_sortable_ = sortable(v); d->title_ = v; }
void Song::set_album(const QString &v) { d->album_sortable_ = sortable(v); d->album_ = v; }
void Song::set_artist(const QString &v) { d->artist_sortable_ = sortable(v); d->artist_ = v; }
@@ -546,6 +423,10 @@ void Song::set_grouping(const QString &v) { d->grouping_ = v; }
void Song::set_comment(const QString &v) { d->comment_ = v; }
void Song::set_lyrics(const QString &v) { d->lyrics_ = v; }
+void Song::set_artist_id(const QString &v) { d->artist_id_ = v; }
+void Song::set_album_id(const QString &v) { d->album_id_ = v; }
+void Song::set_song_id(const QString &v) { d->song_id_ = v; }
+
void Song::set_beginning_nanosec(const qint64 v) { d->beginning_ = qMax(0LL, v); }
void Song::set_end_nanosec(const qint64 v) { d->end_ = v; }
void Song::set_length_nanosec(const qint64 v) { d->end_ = d->beginning_ + v; }
@@ -575,8 +456,11 @@ void Song::set_compilation_detected(const bool v) { d->compilation_detected_ = v
void Song::set_compilation_on(const bool v) { d->compilation_on_ = v; }
void Song::set_compilation_off(const bool v) { d->compilation_off_ = v; }
+void Song::set_art_embedded(const bool v) { d->art_embedded_ = v; }
void Song::set_art_automatic(const QUrl &v) { d->art_automatic_ = v; }
void Song::set_art_manual(const QUrl &v) { d->art_manual_ = v; }
+void Song::set_art_unset(const bool v) { d->art_unset_ = v; }
+
void Song::set_cue_path(const QString &v) { d->cue_path_ = v; }
void Song::set_rating(const float v) { d->rating_ = v; }
@@ -597,10 +481,325 @@ void Song::set_musicbrainz_work_id(const QString &v) { d->musicbrainz_work_id_ =
void Song::set_stream_url(const QUrl &v) { d->stream_url_ = v; }
+const QUrl &Song::effective_stream_url() const { return !d->stream_url_.isEmpty() && d->stream_url_.isValid() ? d->stream_url_ : d->url_; }
+const QString &Song::effective_albumartist() const { return d->albumartist_.isEmpty() ? d->artist_ : d->albumartist_; }
+const QString &Song::effective_albumartist_sortable() const { return d->albumartist_.isEmpty() ? d->artist_sortable_ : d->albumartist_sortable_; }
+const QString &Song::effective_album() const { return d->album_.isEmpty() ? d->title_ : d->album_; }
+int Song::effective_originalyear() const { return d->originalyear_ < 0 ? d->year_ : d->originalyear_; }
+const QString &Song::playlist_albumartist() const { return is_compilation() ? d->albumartist_ : effective_albumartist(); }
+const QString &Song::playlist_albumartist_sortable() const { return is_compilation() ? d->albumartist_sortable_ : effective_albumartist_sortable(); }
+
+bool Song::is_metadata_good() const { return !d->url_.isEmpty() && !d->artist_.isEmpty() && !d->title_.isEmpty(); }
+bool Song::is_collection_song() const { return d->source_ == Source::Collection; }
+bool Song::is_stream() const { return is_radio() || d->source_ == Source::Tidal || d->source_ == Source::Subsonic || d->source_ == Source::Qobuz; }
+bool Song::is_radio() const { return d->source_ == Source::Stream || d->source_ == Source::SomaFM || d->source_ == Source::RadioParadise; }
+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::stream_url_can_expire() const { return d->source_ == Source::Tidal || d->source_ == Source::Qobuz; }
+bool Song::is_module_music() const { return d->filetype_ == FileType::MOD || d->filetype_ == FileType::S3M || d->filetype_ == FileType::XM || d->filetype_ == FileType::IT; }
+bool Song::has_cue() const { return !d->cue_path_.isEmpty(); }
+
+bool Song::art_automatic_is_valid() const {
+ return !d->art_automatic_.isEmpty() && d->art_automatic_.isValid() && (!d->art_automatic_.isLocalFile() || (d->art_automatic_.isLocalFile() && QFile::exists(d->art_automatic_.toLocalFile())));
+}
+bool Song::art_manual_is_valid() const {
+ return !d->art_manual_.isEmpty() && d->art_manual_.isValid() && (!d->art_manual_.isLocalFile() || (d->art_manual_.isLocalFile() && QFile::exists(d->art_manual_.toLocalFile())));
+}
+bool Song::has_valid_art() const { return art_embedded() || art_automatic_is_valid() || art_manual_is_valid(); }
+void Song::clear_art_automatic() { d->art_automatic_.clear(); }
+void Song::clear_art_manual() { d->art_manual_.clear(); }
+
+bool Song::additional_tags_supported() const {
+
+ return d->filetype_ == FileType::FLAC ||
+ d->filetype_ == FileType::WavPack ||
+ d->filetype_ == FileType::OggFlac ||
+ d->filetype_ == FileType::OggVorbis ||
+ d->filetype_ == FileType::OggOpus ||
+ d->filetype_ == FileType::OggSpeex ||
+ d->filetype_ == FileType::MPEG ||
+ d->filetype_ == FileType::MP4 ||
+ d->filetype_ == FileType::MPC ||
+ d->filetype_ == FileType::APE;
+
+}
+
+bool Song::albumartist_supported() const {
+ return additional_tags_supported();
+}
+
+bool Song::composer_supported() const {
+ return additional_tags_supported();
+}
+
+bool Song::performer_supported() const {
+
+ return d->filetype_ == FileType::FLAC ||
+ d->filetype_ == FileType::WavPack ||
+ d->filetype_ == FileType::OggFlac ||
+ d->filetype_ == FileType::OggVorbis ||
+ d->filetype_ == FileType::OggOpus ||
+ d->filetype_ == FileType::OggSpeex ||
+ d->filetype_ == FileType::MPEG ||
+ d->filetype_ == FileType::MPC ||
+ d->filetype_ == FileType::APE;
+
+}
+
+bool Song::grouping_supported() const {
+ return additional_tags_supported();
+}
+
+bool Song::genre_supported() const {
+ return additional_tags_supported();
+}
+
+bool Song::compilation_supported() const {
+ return additional_tags_supported();
+}
+
+bool Song::rating_supported() const {
+
+ return d->filetype_ == FileType::FLAC ||
+ d->filetype_ == FileType::WavPack ||
+ d->filetype_ == FileType::OggFlac ||
+ d->filetype_ == FileType::OggVorbis ||
+ d->filetype_ == FileType::OggOpus ||
+ d->filetype_ == FileType::OggSpeex ||
+ d->filetype_ == FileType::MPEG ||
+ d->filetype_ == FileType::MP4 ||
+ d->filetype_ == FileType::ASF ||
+ d->filetype_ == FileType::MPC ||
+ d->filetype_ == FileType::APE;
+
+}
+
+bool Song::comment_supported() const {
+ return additional_tags_supported();
+}
+
+bool Song::lyrics_supported() const {
+ return additional_tags_supported();
+}
+
+bool Song::save_embedded_cover_supported(const FileType filetype) {
+
+ return filetype == FileType::FLAC ||
+ filetype == FileType::OggVorbis ||
+ filetype == FileType::OggOpus ||
+ filetype == FileType::MPEG ||
+ filetype == FileType::MP4;
+
+}
+
+QString Song::sortable(const QString &v) {
+
+ QString copy = v.toLower();
+
+ for (const auto &i : kArticles) {
+ if (copy.startsWith(i)) {
+ qint64 ilen = i.length();
+ return copy.right(copy.length() - ilen) + ", " + copy.left(ilen - 1);
+ }
+ }
+
+ return copy;
+
+}
+
QString Song::JoinSpec(const QString &table) {
return Utilities::Prepend(table + ".", kColumns).join(", ");
}
+QString Song::PrettyTitle() const {
+
+ QString title(d->title_);
+
+ if (title.isEmpty()) title = d->basefilename_;
+ if (title.isEmpty()) title = d->url_.toString();
+
+ return title;
+
+}
+
+QString Song::PrettyTitleWithArtist() const {
+
+ QString title(PrettyTitle());
+
+ if (!d->artist_.isEmpty()) title = d->artist_ + " - " + title;
+
+ return title;
+
+}
+
+QString Song::PrettyLength() const {
+
+ if (length_nanosec() == -1) return QString();
+
+ return Utilities::PrettyTimeNanosec(length_nanosec());
+
+}
+
+QString Song::PrettyYear() const {
+
+ if (d->year_ == -1) return QString();
+
+ return QString::number(d->year_);
+
+}
+
+QString Song::PrettyOriginalYear() const {
+
+ if (effective_originalyear() == -1) return QString();
+
+ return QString::number(effective_originalyear());
+
+}
+
+QString Song::TitleWithCompilationArtist() const {
+
+ QString title(d->title_);
+
+ if (title.isEmpty()) title = d->basefilename_;
+
+ if (is_compilation() && !d->artist_.isEmpty() && !d->artist_.contains("various", Qt::CaseInsensitive)) title = d->artist_ + " - " + title;
+
+ return title;
+
+}
+
+QString Song::SampleRateBitDepthToText() const {
+
+ if (d->samplerate_ == -1) return QString("");
+ if (d->bitdepth_ == -1) return QString("%1 hz").arg(d->samplerate_);
+
+ return QString("%1 hz / %2 bit").arg(d->samplerate_).arg(d->bitdepth_);
+
+}
+
+QString Song::PrettyRating() const {
+
+ float rating = d->rating_;
+
+ if (rating == -1.0F) return "0";
+
+ return QString::number(static_cast(rating * 100));
+
+}
+
+bool Song::IsEditable() const {
+ return d->valid_ && d->url_.isValid() && (d->url_.isLocalFile() || d->source_ == Source::Stream) && !has_cue();
+}
+
+bool Song::IsMetadataEqual(const Song &other) const {
+
+ return d->title_ == other.d->title_ &&
+ d->album_ == other.d->album_ &&
+ d->artist_ == other.d->artist_ &&
+ d->albumartist_ == other.d->albumartist_ &&
+ d->track_ == other.d->track_ &&
+ d->disc_ == other.d->disc_ &&
+ d->year_ == other.d->year_ &&
+ d->originalyear_ == other.d->originalyear_ &&
+ d->genre_ == other.d->genre_ &&
+ d->compilation_ == other.d->compilation_ &&
+ d->composer_ == other.d->composer_ &&
+ d->performer_ == other.d->performer_ &&
+ d->grouping_ == other.d->grouping_ &&
+ d->comment_ == other.d->comment_ &&
+ d->lyrics_ == other.d->lyrics_ &&
+ d->artist_id_ == other.d->artist_id_ &&
+ d->album_id_ == other.d->album_id_ &&
+ d->song_id_ == other.d->song_id_ &&
+ d->beginning_ == other.d->beginning_ &&
+ length_nanosec() == other.length_nanosec() &&
+ d->bitrate_ == other.d->bitrate_ &&
+ d->samplerate_ == other.d->samplerate_ &&
+ d->bitdepth_ == other.d->bitdepth_ &&
+ d->cue_path_ == other.d->cue_path_;
+}
+
+bool Song::IsPlayStatisticsEqual(const Song &other) const {
+
+ return d->playcount_ == other.d->playcount_ &&
+ d->skipcount_ == other.d->skipcount_ &&
+ d->lastplayed_ == other.d->lastplayed_;
+
+}
+
+bool Song::IsRatingEqual(const Song &other) const {
+
+ return d->rating_ == other.d->rating_;
+
+}
+
+bool Song::IsFingerprintEqual(const Song &other) const {
+
+ return d->fingerprint_ == other.d->fingerprint_;
+
+}
+
+bool Song::IsAcoustIdEqual(const Song &other) const {
+
+ return d->acoustid_id_ == other.d->acoustid_id_ && d->acoustid_fingerprint_ == other.d->acoustid_fingerprint_;
+
+}
+
+bool Song::IsMusicBrainzEqual(const Song &other) const {
+
+ return d->musicbrainz_album_artist_id_ == other.d->musicbrainz_album_artist_id_ &&
+ d->musicbrainz_artist_id_ == other.d->musicbrainz_artist_id_ &&
+ d->musicbrainz_original_artist_id_ == other.d->musicbrainz_original_artist_id_ &&
+ d->musicbrainz_album_id_ == other.d->musicbrainz_album_id_ &&
+ d->musicbrainz_original_album_id_ == other.d->musicbrainz_original_album_id_ &&
+ d->musicbrainz_recording_id_ == other.d->musicbrainz_recording_id_ &&
+ d->musicbrainz_track_id_ == other.d->musicbrainz_track_id_ &&
+ d->musicbrainz_disc_id_ == other.d->musicbrainz_disc_id_ &&
+ d->musicbrainz_release_group_id_ == other.d->musicbrainz_release_group_id_ &&
+ d->musicbrainz_work_id_ == other.d->musicbrainz_work_id_;
+
+}
+
+bool Song::IsArtEqual(const Song &other) const {
+
+ return d->art_embedded_ == other.d->art_embedded_ &&
+ d->art_automatic_ == other.d->art_automatic_ &&
+ d->art_manual_ == other.d->art_manual_ &&
+ d->art_unset_ == other.d->art_unset_;
+
+}
+
+bool Song::IsAllMetadataEqual(const Song &other) const {
+
+ return IsMetadataEqual(other) &&
+ IsPlayStatisticsEqual(other) &&
+ IsRatingEqual(other) &&
+ IsAcoustIdEqual(other) &&
+ IsMusicBrainzEqual(other) &&
+ IsArtEqual(other);
+
+}
+
+bool Song::IsOnSameAlbum(const Song &other) const {
+
+ if (is_compilation() != other.is_compilation()) return false;
+
+ if (has_cue() && other.has_cue() && cue_path() == other.cue_path()) {
+ return true;
+ }
+
+ if (is_compilation() && album() == other.album()) return true;
+
+ return effective_album() == other.effective_album() && effective_albumartist() == other.effective_albumartist();
+
+}
+
+bool Song::IsSimilar(const Song &other) const {
+ return title().compare(other.title(), Qt::CaseInsensitive) == 0 &&
+ artist().compare(other.artist(), Qt::CaseInsensitive) == 0 &&
+ album().compare(other.album(), Qt::CaseInsensitive) == 0;
+}
+
Song::Source Song::SourceFromURL(const QUrl &url) {
if (url.isLocalFile()) return Source::LocalFile;
@@ -791,6 +990,7 @@ QIcon Song::IconForFiletype(const FileType filetype) {
}
bool Song::IsFileLossless() const {
+
switch (filetype()) {
case FileType::WAV:
case FileType::FLAC:
@@ -807,6 +1007,7 @@ bool Song::IsFileLossless() const {
default:
return false;
}
+
}
Song::FileType Song::FiletypeByMimetype(const QString &mimetype) {
@@ -991,10 +1192,7 @@ void Song::InitFromProtobuf(const spb::tagreader::SongMetadata &pb) {
d->rating_ = pb.rating();
}
- if (pb.has_art_automatic()) {
- QByteArray art_automatic(pb.art_automatic().data(), static_cast(pb.art_automatic().size()));
- if (!art_automatic.isEmpty()) set_art_automatic(QUrl::fromLocalFile(art_automatic));
- }
+ d->art_embedded_ = pb.has_art_embedded();
d->acoustid_id_ = QString::fromUtf8(pb.acoustid_id().data(), pb.acoustid_id().size());
d->acoustid_fingerprint_ = QString::fromUtf8(pb.acoustid_fingerprint().data(), pb.acoustid_fingerprint().size());
@@ -1019,7 +1217,6 @@ void Song::InitFromProtobuf(const spb::tagreader::SongMetadata &pb) {
void Song::ToProtobuf(spb::tagreader::SongMetadata *pb) const {
const QByteArray url(d->url_.toEncoded());
- const QByteArray art_automatic(d->art_automatic_.toEncoded());
pb->set_valid(d->valid_);
pb->set_title(d->title_.toStdString());
@@ -1051,7 +1248,7 @@ void Song::ToProtobuf(spb::tagreader::SongMetadata *pb) const {
pb->set_skipcount(d->skipcount_);
pb->set_lastplayed(d->lastplayed_);
pb->set_lastseen(d->lastseen_);
- pb->set_art_automatic(art_automatic.constData(), art_automatic.size());
+ pb->set_art_embedded(d->art_embedded_);
pb->set_rating(d->rating_);
pb->set_acoustid_id(d->acoustid_id_.toStdString());
@@ -1116,22 +1313,32 @@ void Song::InitFromQuery(const SqlRow &q, const bool reliable_metadata) {
d->compilation_detected_ = q.ValueToBool("compilation_detected");
d->compilation_on_ = q.ValueToBool("compilation_on");
d->compilation_off_ = q.ValueToBool("compilation_off");
- QString art_automatic = q.ValueToString("art_automatic");
+
+ d->art_embedded_ = q.ValueToBool("art_embedded");
+ d->art_unset_ = q.ValueToBool("art_unset");
+
+ const QString art_automatic = q.ValueToString("art_automatic");
if (!art_automatic.isEmpty()) {
- if (art_automatic.contains(QRegularExpression("..+:.*"))) {
- set_art_automatic(QUrl::fromEncoded(art_automatic.toUtf8()));
- }
- else {
- set_art_automatic(QUrl::fromLocalFile(art_automatic));
+ QUrl url_art_automatic = QUrl::fromEncoded(art_automatic.toUtf8());
+ if (url_art_automatic.isValid()) {
+ if (url_art_automatic.path() == kEmbeddedCover) {
+ d->art_embedded_ = true;
+ }
+ else {
+ d->art_automatic_ = url_art_automatic;
+ }
}
}
- QString art_manual = q.ValueToString("art_manual");
+ const QString art_manual = q.ValueToString("art_manual");
if (!art_manual.isEmpty()) {
- if (art_manual.contains(QRegularExpression("..+:.*"))) {
- set_art_manual(QUrl::fromEncoded(art_manual.toUtf8()));
- }
- else {
- set_art_manual(QUrl::fromLocalFile(art_manual));
+ const QUrl url_art_manual = QUrl::fromEncoded(art_manual.toUtf8());
+ if (url_art_manual.isValid()) {
+ if (url_art_manual.path() == kManuallyUnsetCover) {
+ d->art_unset_ = true;
+ }
+ else {
+ d->art_manual_ = url_art_manual;
+ }
}
}
@@ -1173,8 +1380,8 @@ void Song::InitFromFilePartial(const QString &filename, const QFileInfo &fileinf
void Song::InitArtManual() {
- // If we don't have an art, check if we have one in the cache
- if (d->art_manual_.isEmpty() && d->art_automatic_.isEmpty() && !effective_albumartist().isEmpty() && !effective_album().isEmpty()) {
+ // If we don't have cover art, check if we have one in the cache
+ if (d->art_manual_.isEmpty() && !effective_albumartist().isEmpty() && !effective_album().isEmpty()) {
QString filename(CoverUtils::Sha1CoverHash(effective_albumartist(), effective_album()).toHex() + ".jpg");
QString path(ImageCacheDir(d->source_) + "/" + filename);
if (QFile::exists(path)) {
@@ -1186,7 +1393,7 @@ void Song::InitArtManual() {
void Song::InitArtAutomatic() {
- if (d->source_ == Source::LocalFile && d->url_.isLocalFile() && d->art_automatic_.isEmpty()) {
+ if (d->art_automatic_.isEmpty() && d->source_ == Source::LocalFile && d->url_.isLocalFile()) {
// Pick the first image file in the album directory.
QFileInfo file(d->url_.toLocalFile());
QDir dir(file.path());
@@ -1385,6 +1592,127 @@ void Song::ToMTP(LIBMTP_track_t *track) const {
}
#endif
+void Song::BindToQuery(SqlQuery *query) const {
+
+ // Remember to bind these in the same order as kBindSpec
+
+ query->BindStringValue(":title", d->title_);
+ query->BindStringValue(":album", d->album_);
+ query->BindStringValue(":artist", d->artist_);
+ query->BindStringValue(":albumartist", d->albumartist_);
+ query->BindIntValue(":track", d->track_);
+ query->BindIntValue(":disc", d->disc_);
+ query->BindIntValue(":year", d->year_);
+ query->BindIntValue(":originalyear", d->originalyear_);
+ query->BindStringValue(":genre", d->genre_);
+ query->BindBoolValue(":compilation", d->compilation_);
+ query->BindStringValue(":composer", d->composer_);
+ query->BindStringValue(":performer", d->performer_);
+ query->BindStringValue(":grouping", d->grouping_);
+ query->BindStringValue(":comment", d->comment_);
+ query->BindStringValue(":lyrics", d->lyrics_);
+
+ query->BindStringValue(":artist_id", d->artist_id_);
+ query->BindStringValue(":album_id", d->album_id_);
+ query->BindStringValue(":song_id", d->song_id_);
+
+ query->BindValue(":beginning", d->beginning_);
+ query->BindLongLongValue(":length", length_nanosec());
+
+ query->BindIntValue(":bitrate", d->bitrate_);
+ query->BindIntValue(":samplerate", d->samplerate_);
+ query->BindIntValue(":bitdepth", d->bitdepth_);
+
+ query->BindValue(":source", static_cast(d->source_));
+ query->BindNotNullIntValue(":directory_id", d->directory_id_);
+ query->BindUrlValue(":url", d->url_);
+ query->BindValue(":filetype", static_cast(d->filetype_));
+ query->BindLongLongValueOrZero(":filesize", d->filesize_);
+ query->BindLongLongValueOrZero(":mtime", d->mtime_);
+ query->BindLongLongValueOrZero(":ctime", d->ctime_);
+ query->BindBoolValue(":unavailable", d->unavailable_);
+
+ query->BindStringValue(":fingerprint", d->fingerprint_);
+
+ query->BindValue(":playcount", d->playcount_);
+ query->BindValue(":skipcount", d->skipcount_);
+ query->BindLongLongValue(":lastplayed", d->lastplayed_);
+ query->BindLongLongValue(":lastseen", d->lastseen_);
+
+ query->BindBoolValue(":compilation_detected", d->compilation_detected_);
+ query->BindBoolValue(":compilation_on", d->compilation_on_);
+ query->BindBoolValue(":compilation_off", d->compilation_off_);
+ query->BindBoolValue(":compilation_effective", is_compilation());
+
+ query->BindBoolValue(":art_embedded", d->art_embedded_);
+ query->BindUrlValue(":art_automatic", d->art_automatic_);
+ query->BindUrlValue(":art_manual", d->art_manual_);
+ query->BindBoolValue(":art_unset", d->art_unset_);
+
+ query->BindStringValue(":effective_albumartist", effective_albumartist());
+ query->BindIntValue(":effective_originalyear", effective_originalyear());
+
+ query->BindValue(":cue_path", d->cue_path_);
+
+ query->BindFloatValue(":rating", d->rating_);
+
+ query->BindStringValue(":acoustid_id", d->acoustid_id_);
+ query->BindStringValue(":acoustid_fingerprint", d->acoustid_fingerprint_);
+
+ query->BindStringValue(":musicbrainz_album_artist_id", d->musicbrainz_album_artist_id_);
+ query->BindStringValue(":musicbrainz_artist_id", d->musicbrainz_artist_id_);
+ query->BindStringValue(":musicbrainz_original_artist_id", d->musicbrainz_original_artist_id_);
+ query->BindStringValue(":musicbrainz_album_id", d->musicbrainz_album_id_);
+ query->BindStringValue(":musicbrainz_original_album_id", d->musicbrainz_original_album_id_);
+ query->BindStringValue(":musicbrainz_recording_id", d->musicbrainz_recording_id_);
+ query->BindStringValue(":musicbrainz_track_id", d->musicbrainz_track_id_);
+ query->BindStringValue(":musicbrainz_disc_id", d->musicbrainz_disc_id_);
+ query->BindStringValue(":musicbrainz_release_group_id", d->musicbrainz_release_group_id_);
+ query->BindStringValue(":musicbrainz_work_id", d->musicbrainz_work_id_);
+
+}
+
+void Song::BindToFtsQuery(SqlQuery *query) const {
+
+ query->BindValue(":ftstitle", d->title_);
+ query->BindValue(":ftsalbum", d->album_);
+ query->BindValue(":ftsartist", d->artist_);
+ query->BindValue(":ftsalbumartist", d->albumartist_);
+ query->BindValue(":ftscomposer", d->composer_);
+ query->BindValue(":ftsperformer", d->performer_);
+ query->BindValue(":ftsgrouping", d->grouping_);
+ query->BindValue(":ftsgenre", d->genre_);
+ query->BindValue(":ftscomment", d->comment_);
+
+}
+
+void Song::ToXesam(QVariantMap *map) const {
+
+ using mpris::AddMetadata;
+ using mpris::AddMetadataAsList;
+ using mpris::AsMPRISDateTimeType;
+
+ AddMetadata("xesam:url", effective_stream_url().toString(), map);
+ AddMetadata("xesam:title", PrettyTitle(), map);
+ AddMetadataAsList("xesam:artist", artist(), map);
+ AddMetadata("xesam:album", album(), map);
+ AddMetadataAsList("xesam:albumArtist", albumartist(), map);
+ AddMetadata("mpris:length", (length_nanosec() / kNsecPerUsec), map);
+ AddMetadata("xesam:trackNumber", track(), map);
+ AddMetadataAsList("xesam:genre", genre(), map);
+ AddMetadata("xesam:discNumber", disc(), map);
+ AddMetadataAsList("xesam:comment", comment(), map);
+ AddMetadata("xesam:contentCreated", AsMPRISDateTimeType(ctime()), map);
+ AddMetadata("xesam:lastUsed", AsMPRISDateTimeType(lastplayed()), map);
+ AddMetadataAsList("xesam:composer", composer(), map);
+ AddMetadata("xesam:useCount", static_cast(playcount()), map);
+
+ if (rating() != -1.0) {
+ AddMetadata("xesam:userRating", rating(), map);
+ }
+
+}
+
bool Song::MergeFromEngineMetadata(const EngineMetadata &engine_metadata) {
d->valid_ = true;
@@ -1439,337 +1767,6 @@ bool Song::MergeFromEngineMetadata(const EngineMetadata &engine_metadata) {
}
-void Song::BindToQuery(SqlQuery *query) const {
-
- // Remember to bind these in the same order as kBindSpec
-
- query->BindStringValue(":title", d->title_);
- query->BindStringValue(":album", d->album_);
- query->BindStringValue(":artist", d->artist_);
- query->BindStringValue(":albumartist", d->albumartist_);
- query->BindIntValue(":track", d->track_);
- query->BindIntValue(":disc", d->disc_);
- query->BindIntValue(":year", d->year_);
- query->BindIntValue(":originalyear", d->originalyear_);
- query->BindStringValue(":genre", d->genre_);
- query->BindBoolValue(":compilation", d->compilation_);
- query->BindStringValue(":composer", d->composer_);
- query->BindStringValue(":performer", d->performer_);
- query->BindStringValue(":grouping", d->grouping_);
- query->BindStringValue(":comment", d->comment_);
- query->BindStringValue(":lyrics", d->lyrics_);
-
- query->BindStringValue(":artist_id", d->artist_id_);
- query->BindStringValue(":album_id", d->album_id_);
- query->BindStringValue(":song_id", d->song_id_);
-
- query->BindValue(":beginning", d->beginning_);
- query->BindLongLongValue(":length", length_nanosec());
-
- query->BindIntValue(":bitrate", d->bitrate_);
- query->BindIntValue(":samplerate", d->samplerate_);
- query->BindIntValue(":bitdepth", d->bitdepth_);
-
- query->BindValue(":source", static_cast(d->source_));
- query->BindNotNullIntValue(":directory_id", d->directory_id_);
- query->BindUrlValue(":url", d->url_);
- query->BindValue(":filetype", static_cast(d->filetype_));
- query->BindLongLongValueOrZero(":filesize", d->filesize_);
- query->BindLongLongValueOrZero(":mtime", d->mtime_);
- query->BindLongLongValueOrZero(":ctime", d->ctime_);
- query->BindBoolValue(":unavailable", d->unavailable_);
-
- query->BindStringValue(":fingerprint", d->fingerprint_);
-
- query->BindValue(":playcount", d->playcount_);
- query->BindValue(":skipcount", d->skipcount_);
- query->BindLongLongValue(":lastplayed", d->lastplayed_);
- query->BindLongLongValue(":lastseen", d->lastseen_);
-
- query->BindBoolValue(":compilation_detected", d->compilation_detected_);
- query->BindBoolValue(":compilation_on", d->compilation_on_);
- query->BindBoolValue(":compilation_off", d->compilation_off_);
- query->BindBoolValue(":compilation_effective", is_compilation());
-
- query->BindUrlValue(":art_automatic", d->art_automatic_);
- query->BindUrlValue(":art_manual", d->art_manual_);
-
- query->BindStringValue(":effective_albumartist", effective_albumartist());
- query->BindIntValue(":effective_originalyear", effective_originalyear());
-
- query->BindValue(":cue_path", d->cue_path_);
-
- query->BindFloatValue(":rating", d->rating_);
-
- query->BindStringValue(":acoustid_id", d->acoustid_id_);
- query->BindStringValue(":acoustid_fingerprint", d->acoustid_fingerprint_);
-
- query->BindStringValue(":musicbrainz_album_artist_id", d->musicbrainz_album_artist_id_);
- query->BindStringValue(":musicbrainz_artist_id", d->musicbrainz_artist_id_);
- query->BindStringValue(":musicbrainz_original_artist_id", d->musicbrainz_original_artist_id_);
- query->BindStringValue(":musicbrainz_album_id", d->musicbrainz_album_id_);
- query->BindStringValue(":musicbrainz_original_album_id", d->musicbrainz_original_album_id_);
- query->BindStringValue(":musicbrainz_recording_id", d->musicbrainz_recording_id_);
- query->BindStringValue(":musicbrainz_track_id", d->musicbrainz_track_id_);
- query->BindStringValue(":musicbrainz_disc_id", d->musicbrainz_disc_id_);
- query->BindStringValue(":musicbrainz_release_group_id", d->musicbrainz_release_group_id_);
- query->BindStringValue(":musicbrainz_work_id", d->musicbrainz_work_id_);
-
-}
-
-void Song::BindToFtsQuery(SqlQuery *query) const {
-
- query->BindValue(":ftstitle", d->title_);
- query->BindValue(":ftsalbum", d->album_);
- query->BindValue(":ftsartist", d->artist_);
- query->BindValue(":ftsalbumartist", d->albumartist_);
- query->BindValue(":ftscomposer", d->composer_);
- query->BindValue(":ftsperformer", d->performer_);
- query->BindValue(":ftsgrouping", d->grouping_);
- query->BindValue(":ftsgenre", d->genre_);
- query->BindValue(":ftscomment", d->comment_);
-
-}
-
-QString Song::PrettyTitle() const {
-
- QString title(d->title_);
-
- if (title.isEmpty()) title = d->basefilename_;
- if (title.isEmpty()) title = d->url_.toString();
-
- return title;
-
-}
-
-QString Song::PrettyTitleWithArtist() const {
-
- QString title(PrettyTitle());
-
- if (!d->artist_.isEmpty()) title = d->artist_ + " - " + title;
-
- return title;
-
-}
-
-QString Song::PrettyLength() const {
-
- if (length_nanosec() == -1) return QString();
-
- return Utilities::PrettyTimeNanosec(length_nanosec());
-
-}
-
-QString Song::PrettyYear() const {
-
- if (d->year_ == -1) return QString();
-
- return QString::number(d->year_);
-
-}
-
-QString Song::PrettyOriginalYear() const {
-
- if (effective_originalyear() == -1) return QString();
-
- return QString::number(effective_originalyear());
-
-}
-
-QString Song::TitleWithCompilationArtist() const {
-
- QString title(d->title_);
-
- if (title.isEmpty()) title = d->basefilename_;
-
- if (is_compilation() && !d->artist_.isEmpty() && !d->artist_.contains("various", Qt::CaseInsensitive)) title = d->artist_ + " - " + title;
-
- return title;
-
-}
-
-QString Song::SampleRateBitDepthToText() const {
-
- if (d->samplerate_ == -1) return QString("");
- if (d->bitdepth_ == -1) return QString("%1 hz").arg(d->samplerate_);
-
- return QString("%1 hz / %2 bit").arg(d->samplerate_).arg(d->bitdepth_);
-
-}
-
-QString Song::PrettyRating() const {
-
- float rating = d->rating_;
-
- if (rating == -1.0F) return "0";
-
- return QString::number(static_cast(rating * 100));
-
-}
-
-bool Song::IsMetadataEqual(const Song &other) const {
-
- return d->title_ == other.d->title_ &&
- d->album_ == other.d->album_ &&
- d->artist_ == other.d->artist_ &&
- d->albumartist_ == other.d->albumartist_ &&
- d->track_ == other.d->track_ &&
- d->disc_ == other.d->disc_ &&
- d->year_ == other.d->year_ &&
- d->originalyear_ == other.d->originalyear_ &&
- d->genre_ == other.d->genre_ &&
- d->compilation_ == other.d->compilation_ &&
- d->composer_ == other.d->composer_ &&
- d->performer_ == other.d->performer_ &&
- d->grouping_ == other.d->grouping_ &&
- d->comment_ == other.d->comment_ &&
- d->lyrics_ == other.d->lyrics_ &&
- d->artist_id_ == other.d->artist_id_ &&
- d->album_id_ == other.d->album_id_ &&
- d->song_id_ == other.d->song_id_ &&
- d->beginning_ == other.d->beginning_ &&
- length_nanosec() == other.length_nanosec() &&
- d->bitrate_ == other.d->bitrate_ &&
- d->samplerate_ == other.d->samplerate_ &&
- d->bitdepth_ == other.d->bitdepth_ &&
- d->cue_path_ == other.d->cue_path_;
-}
-
-bool Song::IsPlayStatisticsEqual(const Song &other) const {
-
- return d->playcount_ == other.d->playcount_ &&
- d->skipcount_ == other.d->skipcount_ &&
- d->lastplayed_ == other.d->lastplayed_;
-
-}
-
-bool Song::IsRatingEqual(const Song &other) const {
-
- return d->rating_ == other.d->rating_;
-
-}
-
-bool Song::IsFingerprintEqual(const Song &other) const {
-
- return d->fingerprint_ == other.d->fingerprint_;
-
-}
-
-bool Song::IsAcoustIdEqual(const Song &other) const {
-
- return d->acoustid_id_ == other.d->acoustid_id_ && d->acoustid_fingerprint_ == other.d->acoustid_fingerprint_;
-
-}
-
-bool Song::IsMusicBrainzEqual(const Song &other) const {
-
- return d->musicbrainz_album_artist_id_ == other.d->musicbrainz_album_artist_id_ &&
- d->musicbrainz_artist_id_ == other.d->musicbrainz_artist_id_ &&
- d->musicbrainz_original_artist_id_ == other.d->musicbrainz_original_artist_id_ &&
- d->musicbrainz_album_id_ == other.d->musicbrainz_album_id_ &&
- d->musicbrainz_original_album_id_ == other.d->musicbrainz_original_album_id_ &&
- d->musicbrainz_recording_id_ == other.d->musicbrainz_recording_id_ &&
- d->musicbrainz_track_id_ == other.d->musicbrainz_track_id_ &&
- d->musicbrainz_disc_id_ == other.d->musicbrainz_disc_id_ &&
- d->musicbrainz_release_group_id_ == other.d->musicbrainz_release_group_id_ &&
- d->musicbrainz_work_id_ == other.d->musicbrainz_work_id_;
-
-}
-
-bool Song::IsArtEqual(const Song &other) const {
-
- return d->art_automatic_ == other.d->art_automatic_ && d->art_manual_ == other.d->art_manual_;
-
-}
-
-bool Song::IsAllMetadataEqual(const Song &other) const {
-
- return IsMetadataEqual(other) &&
- IsPlayStatisticsEqual(other) &&
- IsRatingEqual(other) &&
- IsAcoustIdEqual(other) &&
- IsMusicBrainzEqual(other) &&
- IsArtEqual(other);
-
-}
-
-bool Song::IsEditable() const {
- return d->valid_ && d->url_.isValid() && (d->url_.isLocalFile() || d->source_ == Source::Stream) && !has_cue();
-}
-
-bool Song::operator==(const Song &other) const {
- return source() == other.source() && url() == other.url() && beginning_nanosec() == other.beginning_nanosec();
-}
-
-bool Song::operator!=(const Song &other) const {
- return source() != other.source() || url() != other.url() || beginning_nanosec() != other.beginning_nanosec();
-}
-
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
-size_t qHash(const Song &song) {
-#else
-uint qHash(const Song &song) {
-#endif
- // Should compare the same fields as operator==
- return qHash(song.url().toString()) ^ qHash(song.beginning_nanosec());
-}
-
-bool Song::IsSimilar(const Song &other) const {
- return title().compare(other.title(), Qt::CaseInsensitive) == 0 &&
- artist().compare(other.artist(), Qt::CaseInsensitive) == 0 &&
- album().compare(other.album(), Qt::CaseInsensitive) == 0;
-}
-
-size_t HashSimilar(const Song &song) {
- // Should compare the same fields as function IsSimilar
- return qHash(song.title().toLower()) ^ qHash(song.artist().toLower()) ^ qHash(song.album().toLower());
-}
-
-bool Song::IsOnSameAlbum(const Song &other) const {
-
- if (is_compilation() != other.is_compilation()) return false;
-
- if (has_cue() && other.has_cue() && cue_path() == other.cue_path()) {
- return true;
- }
-
- if (is_compilation() && album() == other.album()) return true;
-
- return effective_album() == other.effective_album() && effective_albumartist() == other.effective_albumartist();
-
-}
-
-QString Song::AlbumKey() const {
- return QString("%1|%2|%3").arg(is_compilation() ? "_compilation" : effective_albumartist(), has_cue() ? cue_path() : "", effective_album());
-}
-
-void Song::ToXesam(QVariantMap *map) const {
-
- using mpris::AddMetadata;
- using mpris::AddMetadataAsList;
- using mpris::AsMPRISDateTimeType;
-
- AddMetadata("xesam:url", effective_stream_url().toString(), map);
- AddMetadata("xesam:title", PrettyTitle(), map);
- AddMetadataAsList("xesam:artist", artist(), map);
- AddMetadata("xesam:album", album(), map);
- AddMetadataAsList("xesam:albumArtist", albumartist(), map);
- AddMetadata("mpris:length", (length_nanosec() / kNsecPerUsec), map);
- AddMetadata("xesam:trackNumber", track(), map);
- AddMetadataAsList("xesam:genre", genre(), map);
- AddMetadata("xesam:discNumber", disc(), map);
- AddMetadataAsList("xesam:comment", comment(), map);
- AddMetadata("xesam:contentCreated", AsMPRISDateTimeType(ctime()), map);
- AddMetadata("xesam:lastUsed", AsMPRISDateTimeType(lastplayed()), map);
- AddMetadataAsList("xesam:composer", composer(), map);
- AddMetadata("xesam:useCount", static_cast(playcount()), map);
-
- if (rating() != -1.0) {
- AddMetadata("xesam:userRating", rating(), map);
- }
-
-}
-
void Song::MergeUserSetData(const Song &other, const bool merge_playcount, const bool merge_rating) {
if (merge_playcount && other.playcount() > 0) {
@@ -1783,7 +1780,26 @@ void Song::MergeUserSetData(const Song &other, const bool merge_playcount, const
set_skipcount(other.skipcount());
set_lastplayed(other.lastplayed());
set_art_manual(other.art_manual());
+ set_art_unset(other.art_unset());
set_compilation_on(other.compilation_on());
set_compilation_off(other.compilation_off());
}
+
+QString Song::AlbumKey() const {
+ return QString("%1|%2|%3").arg(is_compilation() ? "_compilation" : effective_albumartist(), has_cue() ? cue_path() : "", effective_album());
+}
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+size_t qHash(const Song &song) {
+#else
+uint qHash(const Song &song) {
+#endif
+ // Should compare the same fields as operator==
+ return qHash(song.url().toString()) ^ qHash(song.beginning_nanosec());
+}
+
+size_t HashSimilar(const Song &song) {
+ // Should compare the same fields as function IsSimilar
+ return qHash(song.title().toLower()) ^ qHash(song.artist().toLower()) ^ qHash(song.album().toLower());
+}
diff --git a/src/core/song.h b/src/core/song.h
index 293a50555..93b24d60a 100644
--- a/src/core/song.h
+++ b/src/core/song.h
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
- * Copyright 2018-2021, Jonas Kvinge
+ * Copyright 2018-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -109,10 +109,6 @@ class Song {
Stream = 91
};
- Song(const Source source = Source::Unknown);
- Song(const Song &other);
- ~Song();
-
static const QStringList kColumns;
static const QString kColumnSpec;
static const QString kBindSpec;
@@ -123,9 +119,6 @@ class Song {
static const QString kFtsBindSpec;
static const QString kFtsUpdateSpec;
- static const QString kManuallyUnsetCover;
- static const QString kEmbeddedCover;
-
static const QRegularExpression kAlbumRemoveDisc;
static const QRegularExpression kAlbumRemoveMisc;
static const QRegularExpression kTitleRemoveMisc;
@@ -134,77 +127,22 @@ class Song {
static const QStringList kAcceptedExtensions;
- static QString JoinSpec(const QString &table);
+ Song(const Source source = Source::Unknown);
+ Song(const Song &other);
+ ~Song();
- static Source SourceFromURL(const QUrl &url);
- static QString TextForSource(const Source source);
- static QString DescriptionForSource(const Source source);
- static Source SourceFromText(const QString &source);
- static QIcon IconForSource(const Source source);
- static QString TextForFiletype(const FileType filetype);
- static QString ExtensionForFiletype(const FileType filetype);
- static QIcon IconForFiletype(const FileType filetype);
-
- QString TextForSource() const { return TextForSource(source()); }
- QString DescriptionForSource() const { return DescriptionForSource(source()); }
- QIcon IconForSource() const { return IconForSource(source()); }
- QString TextForFiletype() const { return TextForFiletype(filetype()); }
- QIcon IconForFiletype() const { return IconForFiletype(filetype()); }
-
- bool IsFileLossless() const;
- static FileType FiletypeByMimetype(const QString &mimetype);
- static FileType FiletypeByDescription(const QString &text);
- static FileType FiletypeByExtension(const QString &ext);
- static QString ImageCacheDir(const Source source);
-
- // Sort songs alphabetically using their pretty title
- static int CompareSongsName(const Song &song1, const Song &song2);
- static void SortSongsListAlphabetically(QList *songs);
-
- // Constructors
- void Init(const QString &title, const QString &artist, const QString &album, const qint64 length_nanosec);
- void Init(const QString &title, const QString &artist, const QString &album, const qint64 beginning, const qint64 end);
- void InitFromProtobuf(const spb::tagreader::SongMetadata &pb);
- void InitFromQuery(const SqlRow &query, const bool reliable_metadata);
- void InitFromFilePartial(const QString &filename, const QFileInfo &fileinfo);
- void InitArtManual();
- void InitArtAutomatic();
-
- bool MergeFromEngineMetadata(const EngineMetadata &engine_metadata);
-
-#ifdef HAVE_LIBGPOD
- void InitFromItdb(_Itdb_Track *track, const QString &prefix);
- void ToItdb(_Itdb_Track *track) const;
-#endif
-
-#ifdef HAVE_LIBMTP
- void InitFromMTP(const LIBMTP_track_struct *track, const QString &host);
- void ToMTP(LIBMTP_track_struct *track) const;
-#endif
-
- // Copies important statistics from the other song to this one, overwriting any data that already exists.
- // Useful when you want updated tags from disk but you want to keep user stats.
- void MergeUserSetData(const Song &other, const bool merge_playcount, const bool merge_rating);
-
- // Save
- void BindToQuery(SqlQuery *query) const;
- void BindToFtsQuery(SqlQuery *query) const;
- void ToXesam(QVariantMap *map) const;
- void ToProtobuf(spb::tagreader::SongMetadata *pb) const;
+ bool operator==(const Song &other) const;
+ bool operator!=(const Song &other) const;
+ Song &operator=(const Song &other);
// Simple accessors
- bool is_valid() const;
- bool is_unavailable() const;
int id() const;
+ bool is_valid() const;
const QString &title() const;
- const QString &title_sortable() const;
const QString &album() const;
- const QString &album_sortable() const;
const QString &artist() const;
- const QString &artist_sortable() const;
const QString &albumartist() const;
- const QString &albumartist_sortable() const;
int track() const;
int disc() const;
int year() const;
@@ -237,6 +175,7 @@ class Song {
qint64 filesize() const;
qint64 mtime() const;
qint64 ctime() const;
+ bool unavailable() const;
QString fingerprint() const;
@@ -246,14 +185,15 @@ class Song {
qint64 lastseen() const;
bool compilation_detected() const;
- bool compilation_off() const;
bool compilation_on() const;
+ bool compilation_off() const;
+ bool art_embedded() const;
const QUrl &art_automatic() const;
const QUrl &art_manual() const;
+ bool art_unset() const;
const QString &cue_path() const;
- bool has_cue() const;
float rating() const;
@@ -271,74 +211,16 @@ class Song {
const QString &musicbrainz_release_group_id() const;
const QString &musicbrainz_work_id() const;
- const QString &effective_album() const;
- int effective_originalyear() const;
- const QString &effective_albumartist() const;
- const QString &effective_albumartist_sortable() const;
-
- bool is_collection_song() const;
- bool is_stream() const;
- bool is_radio() const;
- bool is_cdda() const;
- bool is_metadata_good() const;
- bool art_automatic_is_valid() const;
- bool art_manual_is_valid() const;
- bool has_valid_art() const;
- bool is_compilation() const;
- bool stream_url_can_expire() const;
- bool is_module_music() const;
-
- // Playlist views are special because you don't want to fill in album artists automatically for compilations, but you do for normal albums:
- const QString &playlist_albumartist() const;
- const QString &playlist_albumartist_sortable() const;
-
- // Returns true if this Song had it's cover manually unset by user.
- bool has_manually_unset_cover() const;
- // This method represents an explicit request to unset this song's cover.
- void set_manually_unset_cover();
-
- // Returns true if this song (it's media file) has an embedded cover.
- bool has_embedded_cover() const;
- // Sets a flag saying that this song (it's media file) has an embedded cover.
- void set_embedded_cover();
-
- void clear_art_automatic();
- void clear_art_manual();
-
- static bool save_embedded_cover_supported(const FileType filetype);
- bool save_embedded_cover_supported() const { return url().isLocalFile() && save_embedded_cover_supported(filetype()) && !has_cue(); };
-
- bool additional_tags_supported() const;
- bool albumartist_supported() const;
- bool composer_supported() const;
- bool performer_supported() const;
- bool grouping_supported() const;
- bool genre_supported() const;
- bool compilation_supported() const;
- bool rating_supported() const;
- bool comment_supported() const;
- bool lyrics_supported() const;
-
- const QUrl &stream_url() const;
- const QUrl &effective_stream_url() const;
bool init_from_file() const;
- // Pretty accessors
- QString PrettyTitle() const;
- QString PrettyTitleWithArtist() const;
- QString PrettyLength() const;
- QString PrettyYear() const;
- QString PrettyOriginalYear() const;
+ const QString &title_sortable() const;
+ const QString &album_sortable() const;
+ const QString &artist_sortable() const;
+ const QString &albumartist_sortable() const;
- QString TitleWithCompilationArtist() const;
-
- QString SampleRateBitDepthToText() const;
-
- QString PrettyRating() const;
+ const QUrl &stream_url() const;
// Setters
- bool IsEditable() const;
-
void set_id(const int id);
void set_valid(const bool v);
@@ -391,8 +273,10 @@ class Song {
void set_compilation_on(const bool v);
void set_compilation_off(const bool v);
+ void set_art_embedded(const bool v);
void set_art_automatic(const QUrl &v);
void set_art_manual(const QUrl &v);
+ void set_art_unset(const bool v);
void set_cue_path(const QString &v);
@@ -414,6 +298,61 @@ class Song {
void set_stream_url(const QUrl &v);
+ const QUrl &effective_stream_url() const;
+ const QString &effective_albumartist() const;
+ const QString &effective_albumartist_sortable() const;
+ const QString &effective_album() const;
+ int effective_originalyear() const;
+ const QString &playlist_albumartist() const;
+ const QString &playlist_albumartist_sortable() const;
+
+ bool is_metadata_good() const;
+ bool is_collection_song() const;
+ bool is_stream() const;
+ bool is_radio() const;
+ bool is_cdda() const;
+ bool is_compilation() const;
+ bool stream_url_can_expire() const;
+ bool is_module_music() const;
+ bool has_cue() const;
+
+ bool art_automatic_is_valid() const;
+ bool art_manual_is_valid() const;
+ bool has_valid_art() const;
+ void clear_art_automatic();
+ void clear_art_manual();
+
+ bool additional_tags_supported() const;
+ bool albumartist_supported() const;
+ bool composer_supported() const;
+ bool performer_supported() const;
+ bool grouping_supported() const;
+ bool genre_supported() const;
+ bool compilation_supported() const;
+ bool rating_supported() const;
+ bool comment_supported() const;
+ bool lyrics_supported() const;
+
+ static bool save_embedded_cover_supported(const FileType filetype);
+ bool save_embedded_cover_supported() const { return url().isLocalFile() && save_embedded_cover_supported(filetype()) && !has_cue(); };
+
+ static QString JoinSpec(const QString &table);
+
+ // Pretty accessors
+ QString PrettyTitle() const;
+ QString PrettyTitleWithArtist() const;
+ QString PrettyLength() const;
+ QString PrettyYear() const;
+ QString PrettyOriginalYear() const;
+
+ QString TitleWithCompilationArtist() const;
+
+ QString SampleRateBitDepthToText() const;
+
+ QString PrettyRating() const;
+
+ bool IsEditable() const;
+
// Comparison functions
bool IsMetadataEqual(const Song &other) const;
bool IsPlayStatisticsEqual(const Song &other) const;
@@ -427,16 +366,69 @@ class Song {
bool IsOnSameAlbum(const Song &other) const;
bool IsSimilar(const Song &other) const;
- bool operator==(const Song &other) const;
- bool operator!=(const Song &other) const;
+ static Source SourceFromURL(const QUrl &url);
+ static QString TextForSource(const Source source);
+ static QString DescriptionForSource(const Source source);
+ static Source SourceFromText(const QString &source);
+ static QIcon IconForSource(const Source source);
+ static QString TextForFiletype(const FileType filetype);
+ static QString ExtensionForFiletype(const FileType filetype);
+ static QIcon IconForFiletype(const FileType filetype);
+
+ QString TextForSource() const { return TextForSource(source()); }
+ QString DescriptionForSource() const { return DescriptionForSource(source()); }
+ QIcon IconForSource() const { return IconForSource(source()); }
+ QString TextForFiletype() const { return TextForFiletype(filetype()); }
+ QIcon IconForFiletype() const { return IconForFiletype(filetype()); }
+
+ bool IsFileLossless() const;
+ static FileType FiletypeByMimetype(const QString &mimetype);
+ static FileType FiletypeByDescription(const QString &text);
+ static FileType FiletypeByExtension(const QString &ext);
+ static QString ImageCacheDir(const Source source);
+
+ // Sort songs alphabetically using their pretty title
+ static int CompareSongsName(const Song &song1, const Song &song2);
+ static void SortSongsListAlphabetically(QList *songs);
+
+ // Constructors
+ void Init(const QString &title, const QString &artist, const QString &album, const qint64 length_nanosec);
+ void Init(const QString &title, const QString &artist, const QString &album, const qint64 beginning, const qint64 end);
+ void InitFromProtobuf(const spb::tagreader::SongMetadata &pb);
+ void InitFromQuery(const SqlRow &query, const bool reliable_metadata);
+ void InitFromFilePartial(const QString &filename, const QFileInfo &fileinfo);
+ void InitArtManual();
+ void InitArtAutomatic();
+
+#ifdef HAVE_LIBGPOD
+ void InitFromItdb(_Itdb_Track *track, const QString &prefix);
+ void ToItdb(_Itdb_Track *track) const;
+#endif
+
+#ifdef HAVE_LIBMTP
+ void InitFromMTP(const LIBMTP_track_struct *track, const QString &host);
+ void ToMTP(LIBMTP_track_struct *track) const;
+#endif
+
+ // Save
+ void BindToQuery(SqlQuery *query) const;
+ void BindToFtsQuery(SqlQuery *query) const;
+ void ToXesam(QVariantMap *map) const;
+ void ToProtobuf(spb::tagreader::SongMetadata *pb) const;
+
+ bool MergeFromEngineMetadata(const EngineMetadata &engine_metadata);
+
+ // Copies important statistics from the other song to this one, overwriting any data that already exists.
+ // Useful when you want updated tags from disk but you want to keep user stats.
+ void MergeUserSetData(const Song &other, const bool merge_playcount, const bool merge_rating);
// Two songs that are on the same album will have the same AlbumKey.
// It is more efficient to use IsOnSameAlbum, but this function can be used when you need to hash the key to do fast lookups.
QString AlbumKey() const;
- Song &operator=(const Song &other);
-
private:
+ static const QString kManuallyUnsetCover;
+ static const QString kEmbeddedCover;
struct Private;
static QString sortable(const QString &v);
diff --git a/src/core/standarditemiconloader.cpp b/src/core/standarditemiconloader.cpp
index 654421672..cc48638a8 100644
--- a/src/core/standarditemiconloader.cpp
+++ b/src/core/standarditemiconloader.cpp
@@ -30,6 +30,7 @@
#include
#include "covermanager/albumcoverloader.h"
+#include "covermanager/albumcoverloaderoptions.h"
#include "standarditemiconloader.h"
StandardItemIconLoader::StandardItemIconLoader(AlbumCoverLoader *cover_loader, QObject *parent)
@@ -37,8 +38,6 @@ StandardItemIconLoader::StandardItemIconLoader(AlbumCoverLoader *cover_loader, Q
cover_loader_(cover_loader),
model_(nullptr) {
- cover_options_.desired_height_ = 16;
-
QObject::connect(cover_loader_, &AlbumCoverLoader::AlbumCoverLoaded, this, &StandardItemIconLoader::AlbumCoverLoaded);
}
@@ -58,14 +57,20 @@ void StandardItemIconLoader::SetModel(QAbstractItemModel *model) {
void StandardItemIconLoader::LoadIcon(const QUrl &art_automatic, const QUrl &art_manual, QStandardItem *for_item) {
- const quint64 id = cover_loader_->LoadImageAsync(cover_options_, art_automatic, art_manual);
- pending_covers_[id] = for_item;
+ AlbumCoverLoaderOptions cover_options(AlbumCoverLoaderOptions::Option::ScaledImage);
+ cover_options.desired_scaled_size = QSize(16, 16);
+ const quint64 id = cover_loader_->LoadImageAsync(cover_options, false, art_automatic, art_manual, false);
+ pending_covers_.insert(id, for_item);
}
void StandardItemIconLoader::LoadIcon(const Song &song, QStandardItem *for_item) {
- const quint64 id = cover_loader_->LoadImageAsync(cover_options_, song);
- pending_covers_[id] = for_item;
+
+ AlbumCoverLoaderOptions cover_options(AlbumCoverLoaderOptions::Option::ScaledImage);
+ cover_options.desired_scaled_size = QSize(16, 16);
+ const quint64 id = cover_loader_->LoadImageAsync(cover_options, song);
+ pending_covers_.insert(id, for_item);
+
}
void StandardItemIconLoader::RowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end) {
@@ -76,7 +81,7 @@ void StandardItemIconLoader::RowsAboutToBeRemoved(const QModelIndex &parent, int
if (item_parent && item_parent->index() == parent && item->index().row() >= begin && item->index().row() <= end) {
cover_loader_->CancelTask(it.key());
- it = pending_covers_.erase(it); // clazy:exclude=strict-iterators
+ it = pending_covers_.erase(it);
}
else {
++it;
@@ -103,7 +108,7 @@ void StandardItemIconLoader::AlbumCoverLoaded(const quint64 id, const AlbumCover
QStandardItem *item = pending_covers_.take(id);
if (!item) return;
- if (result.success && !result.image_scaled.isNull() && result.type != AlbumCoverLoaderResult::Type::ManuallyUnset) {
+ if (result.success && !result.image_scaled.isNull() && result.type != AlbumCoverLoaderResult::Type::Unset) {
item->setIcon(QIcon(QPixmap::fromImage(result.image_scaled)));
}
diff --git a/src/core/standarditemiconloader.h b/src/core/standarditemiconloader.h
index 0d135f71f..e9cffa6a9 100644
--- a/src/core/standarditemiconloader.h
+++ b/src/core/standarditemiconloader.h
@@ -29,7 +29,6 @@
#include
#include
-#include "covermanager/albumcoverloaderoptions.h"
#include "covermanager/albumcoverloaderresult.h"
class QAbstractItemModel;
@@ -38,8 +37,6 @@ class QStandardItem;
class Song;
class AlbumCoverLoader;
-class QModelIndex;
-
// Uses an AlbumCoverLoader to asynchronously load and set an icon on a QStandardItem.
class StandardItemIconLoader : public QObject {
Q_OBJECT
@@ -47,8 +44,6 @@ class StandardItemIconLoader : public QObject {
public:
explicit StandardItemIconLoader(AlbumCoverLoader *cover_loader, QObject *parent = nullptr);
- AlbumCoverLoaderOptions *options() { return &cover_options_; }
-
void SetModel(QAbstractItemModel *model);
void LoadIcon(const QUrl &art_automatic, const QUrl &art_manual, QStandardItem *for_item);
@@ -61,10 +56,7 @@ class StandardItemIconLoader : public QObject {
private:
AlbumCoverLoader *cover_loader_;
- AlbumCoverLoaderOptions cover_options_;
-
QAbstractItemModel *model_;
-
QMap pending_covers_;
};
diff --git a/src/core/tagreaderclient.cpp b/src/core/tagreaderclient.cpp
index 2ea932256..f261cca2e 100644
--- a/src/core/tagreaderclient.cpp
+++ b/src/core/tagreaderclient.cpp
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
- * Copyright 2019-2021, Jonas Kvinge
+ * Copyright 2019-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -93,25 +93,30 @@ TagReaderReply *TagReaderClient::ReadFile(const QString &filename) {
}
-TagReaderReply *TagReaderClient::SaveFile(const QString &filename, const Song &metadata, const SaveTags save_tags, const SavePlaycount save_playcount, const SaveRating save_rating, const SaveCoverOptions &save_cover_options) {
+TagReaderReply *TagReaderClient::SaveFile(const QString &filename, const Song &metadata, const SaveTypes save_types, const SaveCoverOptions &save_cover_options) {
spb::tagreader::Message message;
spb::tagreader::SaveFileRequest *request = message.mutable_save_file_request();
const QByteArray filename_data = filename.toUtf8();
request->set_filename(filename_data.constData(), filename_data.length());
- request->set_save_tags(save_tags == SaveTags::On);
- request->set_save_playcount(save_playcount == SavePlaycount::On);
- request->set_save_rating(save_rating == SaveRating::On);
- request->set_save_cover(save_cover_options.enabled);
- request->set_cover_is_jpeg(save_cover_options.is_jpeg);
+
+ request->set_save_tags(save_types.testFlag(SaveType::Tags));
+ request->set_save_playcount(save_types.testFlag(SaveType::PlayCount));
+ request->set_save_rating(save_types.testFlag(SaveType::Rating));
+ request->set_save_cover(save_types.testFlag(SaveType::Cover));
+
if (save_cover_options.cover_filename.length() > 0) {
- const QByteArray cover_filename = filename.toUtf8();
+ const QByteArray cover_filename = save_cover_options.cover_filename.toUtf8();
request->set_cover_filename(cover_filename.constData(), cover_filename.length());
}
if (save_cover_options.cover_data.length() > 0) {
request->set_cover_data(save_cover_options.cover_data.constData(), save_cover_options.cover_data.length());
}
+ if (save_cover_options.mime_type.length() > 0) {
+ const QByteArray cover_mime_type = save_cover_options.mime_type.toUtf8();
+ request->set_cover_mime_type(cover_mime_type.constData(), cover_mime_type.length());
+ }
metadata.ToProtobuf(request->mutable_metadata());
ReplyType *reply = worker_pool_->SendMessageWithReply(&message);
@@ -139,15 +144,17 @@ TagReaderReply *TagReaderClient::SaveEmbeddedArt(const QString &filename, const
const QByteArray filename_data = filename.toUtf8();
request->set_filename(filename_data.constData(), filename_data.length());
- request->set_cover_is_jpeg(save_cover_options.is_jpeg);
if (save_cover_options.cover_filename.length() > 0) {
- const QByteArray cover_filename = filename.toUtf8();
+ const QByteArray cover_filename = save_cover_options.cover_filename.toUtf8();
request->set_cover_filename(cover_filename.constData(), cover_filename.length());
}
if (save_cover_options.cover_data.length() > 0) {
request->set_cover_data(save_cover_options.cover_data.constData(), save_cover_options.cover_data.length());
}
-
+ if (save_cover_options.mime_type.length() > 0) {
+ const QByteArray cover_mime_type = save_cover_options.mime_type.toUtf8();
+ request->set_cover_mime_type(cover_mime_type.constData(), cover_mime_type.length());
+ }
return worker_pool_->SendMessageWithReply(&message);
@@ -225,13 +232,13 @@ void TagReaderClient::ReadFileBlocking(const QString &filename, Song *song) {
}
-bool TagReaderClient::SaveFileBlocking(const QString &filename, const Song &metadata, const SaveTags save_tags, const SavePlaycount save_playcount, const SaveRating save_rating, const SaveCoverOptions &save_cover_options) {
+bool TagReaderClient::SaveFileBlocking(const QString &filename, const Song &metadata, const SaveTypes save_types, const SaveCoverOptions &save_cover_options) {
Q_ASSERT(QThread::currentThread() != thread());
bool ret = false;
- TagReaderReply *reply = SaveFile(filename, metadata, save_tags, save_playcount, save_rating, save_cover_options);
+ TagReaderReply *reply = SaveFile(filename, metadata, save_types, save_cover_options);
if (reply->WaitForFinished()) {
ret = reply->message().save_file_response().success();
}
diff --git a/src/core/tagreaderclient.h b/src/core/tagreaderclient.h
index abb92bf1e..1ad2dd71c 100644
--- a/src/core/tagreaderclient.h
+++ b/src/core/tagreaderclient.h
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2011, David Sansome
- * Copyright 2019-2021, Jonas Kvinge
+ * Copyright 2019-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -53,35 +53,28 @@ class TagReaderClient : public QObject {
void Start();
void ExitAsync();
- enum class SaveTags {
- Off,
- On
- };
-
- enum class SavePlaycount {
- Off,
- On
- };
-
- enum class SaveRating {
- Off,
- On
+ enum class SaveType {
+ NoType = 0,
+ Tags = 1,
+ PlayCount = 2,
+ Rating = 4,
+ Cover = 8
};
+ Q_DECLARE_FLAGS(SaveTypes, SaveType)
class SaveCoverOptions {
public:
- explicit SaveCoverOptions(const bool _enabled = false, const bool _is_jpeg = false, const QString &_cover_filename = QString(), const QByteArray &_cover_data = QByteArray()) : enabled(_enabled), is_jpeg(_is_jpeg), cover_filename(_cover_filename), cover_data(_cover_data) {}
- explicit SaveCoverOptions(const QString &_cover_filename) : enabled(true), is_jpeg(false), cover_filename(_cover_filename) {}
- explicit SaveCoverOptions(const QByteArray &_cover_data) : enabled(true), is_jpeg(false), cover_data(_cover_data) {}
- bool enabled;
- bool is_jpeg;
+ explicit SaveCoverOptions(const QString &_cover_filename = QString(), const QByteArray &_cover_data = QByteArray(), const QString &_mime_type = QString()) : cover_filename(_cover_filename), cover_data(_cover_data), mime_type(_mime_type) {}
+ explicit SaveCoverOptions(const QString &_cover_filename, const QString &_mime_type = QString()) : cover_filename(_cover_filename), mime_type(_mime_type) {}
+ explicit SaveCoverOptions(const QByteArray &_cover_data, const QString &_mime_type = QString()) : cover_data(_cover_data), mime_type(_mime_type) {}
QString cover_filename;
QByteArray cover_data;
+ QString mime_type;
};
ReplyType *IsMediaFile(const QString &filename);
ReplyType *ReadFile(const QString &filename);
- ReplyType *SaveFile(const QString &filename, const Song &metadata, const SaveTags save_tags = SaveTags::On, const SavePlaycount save_playcount = SavePlaycount::Off, const SaveRating save_rating = SaveRating::Off, const SaveCoverOptions &save_cover_options = SaveCoverOptions());
+ ReplyType *SaveFile(const QString &filename, const Song &metadata, const SaveTypes types = SaveType::Tags, const SaveCoverOptions &save_cover_options = SaveCoverOptions());
ReplyType *LoadEmbeddedArt(const QString &filename);
ReplyType *SaveEmbeddedArt(const QString &filename, const SaveCoverOptions &save_cover_options);
ReplyType *UpdateSongPlaycount(const Song &metadata);
@@ -90,7 +83,7 @@ class TagReaderClient : public QObject {
// Convenience functions that call the above functions and wait for a response.
// These block the calling thread with a semaphore, and must NOT be called from the TagReaderClient's thread.
void ReadFileBlocking(const QString &filename, Song *song);
- bool SaveFileBlocking(const QString &filename, const Song &metadata, const SaveTags save_tags = SaveTags::On, const SavePlaycount save_playcount = SavePlaycount::Off, const SaveRating save_rating = SaveRating::Off, const SaveCoverOptions &save_cover_options = SaveCoverOptions());
+ bool SaveFileBlocking(const QString &filename, const Song &metadata, const SaveTypes types = SaveType::Tags, const SaveCoverOptions &save_cover_options = SaveCoverOptions());
bool IsMediaFileBlocking(const QString &filename);
QByteArray LoadEmbeddedArtBlocking(const QString &filename);
QImage LoadEmbeddedArtAsImageBlocking(const QString &filename);
diff --git a/src/covermanager/albumcoverchoicecontroller.cpp b/src/covermanager/albumcoverchoicecontroller.cpp
index db398715d..a1546ee72 100644
--- a/src/covermanager/albumcoverchoicecontroller.cpp
+++ b/src/covermanager/albumcoverchoicecontroller.cpp
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
- * Copyright 2019-2021, Jonas Kvinge
+ * Copyright 2019-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -62,10 +62,11 @@
#include "core/application.h"
#include "core/song.h"
#include "core/iconloader.h"
+#include "core/tagreaderclient.h"
#include "collection/collectionfilteroptions.h"
#include "collection/collectionbackend.h"
-#include "settings/collectionsettingspage.h"
+#include "settings/coverssettingspage.h"
#include "internet/internetservices.h"
#include "internet/internetservice.h"
#include "albumcoverchoicecontroller.h"
@@ -135,22 +136,23 @@ void AlbumCoverChoiceController::Init(Application *app) {
cover_searcher_->Init(cover_fetcher_);
QObject::connect(cover_fetcher_, &AlbumCoverFetcher::AlbumCoverFetched, this, &AlbumCoverChoiceController::AlbumCoverFetched);
- QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::SaveEmbeddedCoverAsyncFinished, this, &AlbumCoverChoiceController::SaveEmbeddedCoverAsyncFinished);
}
void AlbumCoverChoiceController::ReloadSettings() {
QSettings s;
- s.beginGroup(CollectionSettingsPage::kSettingsGroup);
- cover_options_.cover_type = static_cast(s.value("save_cover_type", static_cast(CoverOptions::CoverType::Cache)).toInt());
- cover_options_.cover_filename = static_cast(s.value("save_cover_filename", static_cast(CoverOptions::CoverFilename::Pattern)).toInt());
- cover_options_.cover_pattern = s.value("cover_pattern", "%albumartist-%album").toString();
- cover_options_.cover_overwrite = s.value("cover_overwrite", false).toBool();
- cover_options_.cover_lowercase = s.value("cover_lowercase", false).toBool();
- cover_options_.cover_replace_spaces = s.value("cover_replace_spaces", false).toBool();
+ s.beginGroup(CoversSettingsPage::kSettingsGroup);
+ cover_options_.cover_type = static_cast(s.value(CoversSettingsPage::kSaveType, static_cast(CoverOptions::CoverType::Cache)).toInt());
+ cover_options_.cover_filename = static_cast(s.value(CoversSettingsPage::kSaveFilename, static_cast(CoverOptions::CoverFilename::Pattern)).toInt());
+ cover_options_.cover_pattern = s.value(CoversSettingsPage::kSavePattern, "%albumartist-%album").toString();
+ cover_options_.cover_overwrite = s.value(CoversSettingsPage::kSaveOverwrite, false).toBool();
+ cover_options_.cover_lowercase = s.value(CoversSettingsPage::kSaveLowercase, false).toBool();
+ cover_options_.cover_replace_spaces = s.value(CoversSettingsPage::kSaveReplaceSpaces, false).toBool();
s.endGroup();
+ cover_types_ = AlbumCoverLoaderOptions::LoadTypes();
+
}
QList AlbumCoverChoiceController::GetAllActions() {
@@ -170,30 +172,31 @@ QList AlbumCoverChoiceController::GetAllActions() {
AlbumCoverImageResult AlbumCoverChoiceController::LoadImageFromFile(Song *song) {
- if (!song->url().isLocalFile()) return AlbumCoverImageResult();
+ if (!song->url().isValid() || !song->url().isLocalFile()) {
+ return AlbumCoverImageResult();
+ }
QString cover_file = QFileDialog::getOpenFileName(this, tr("Load cover from disk"), GetInitialPathForFileDialog(*song, QString()), tr(kLoadImageFileFilter) + ";;" + tr(kAllFilesFilter));
-
if (cover_file.isEmpty()) return AlbumCoverImageResult();
AlbumCoverImageResult result;
QFile file(cover_file);
- if (file.open(QIODevice::ReadOnly)) {
- result.image_data = file.readAll();
- file.close();
- if (result.image_data.isEmpty()) {
- qLog(Error) << "Cover file" << cover_file << "is empty.";
- emit Error(tr("Cover file %1 is empty.").arg(cover_file));
- }
- else {
- result.mime_type = Utilities::MimeTypeFromData(result.image_data);
- result.image.loadFromData(result.image_data);
- result.cover_url = QUrl::fromLocalFile(cover_file);
- }
- }
- else {
+ if (!file.open(QIODevice::ReadOnly)) {
qLog(Error) << "Failed to open cover file" << cover_file << "for reading:" << file.errorString();
emit Error(tr("Failed to open cover file %1 for reading: %2").arg(cover_file, file.errorString()));
+ return AlbumCoverImageResult();
+ }
+ result.image_data = file.readAll();
+ file.close();
+ if (result.image_data.isEmpty()) {
+ qLog(Error) << "Cover file" << cover_file << "is empty.";
+ emit Error(tr("Cover file %1 is empty.").arg(cover_file));
+ return AlbumCoverImageResult();
+ }
+
+ if (result.image.loadFromData(result.image_data)) {
+ result.cover_url = QUrl::fromLocalFile(cover_file);
+ result.mime_type = Utilities::MimeTypeFromData(result.image_data);
}
return result;
@@ -202,24 +205,21 @@ AlbumCoverImageResult AlbumCoverChoiceController::LoadImageFromFile(Song *song)
QUrl AlbumCoverChoiceController::LoadCoverFromFile(Song *song) {
- if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
+ if (!song->url().isValid() || !song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
QString cover_file = QFileDialog::getOpenFileName(this, tr("Load cover from disk"), GetInitialPathForFileDialog(*song, QString()), tr(kLoadImageFileFilter) + ";;" + tr(kAllFilesFilter));
-
- if (cover_file.isEmpty()) return QUrl();
-
- if (QImage(cover_file).isNull()) return QUrl();
+ if (cover_file.isEmpty() || QImage(cover_file).isNull()) return QUrl();
switch (get_save_album_cover_type()) {
case CoverOptions::CoverType::Embedded:
if (song->save_embedded_cover_supported()) {
- SaveCoverEmbeddedAutomatic(*song, cover_file);
- return QUrl::fromLocalFile(Song::kEmbeddedCover);
+ SaveCoverEmbeddedToCollectionSongs(*song, cover_file);
+ return QUrl();
}
[[fallthrough]];
case CoverOptions::CoverType::Cache:
case CoverOptions::CoverType::Album:{
- QUrl cover_url = QUrl::fromLocalFile(cover_file);
+ const QUrl cover_url = QUrl::fromLocalFile(cover_file);
SaveArtManualToSong(song, cover_url);
return cover_url;
}
@@ -258,17 +258,19 @@ void AlbumCoverChoiceController::SaveCoverToFileManual(const Song &song, const A
if (result.is_jpeg() && fileinfo.completeSuffix().compare("jpg", Qt::CaseInsensitive) == 0) {
QFile file(save_filename);
- if (file.open(QIODevice::WriteOnly)) {
- if (file.write(result.image_data) <= 0) {
- qLog(Error) << "Failed writing cover to file" << save_filename << file.errorString();
- emit Error(tr("Failed writing cover to file %1: %2").arg(save_filename, file.errorString()));
- }
- file.close();
- }
- else {
+ if (!file.open(QIODevice::WriteOnly)) {
qLog(Error) << "Failed to open cover file" << save_filename << "for writing:" << file.errorString();
emit Error(tr("Failed to open cover file %1 for writing: %2").arg(save_filename, file.errorString()));
+ file.close();
+ return;
}
+ if (file.write(result.image_data) <= 0) {
+ qLog(Error) << "Failed writing cover to file" << save_filename << file.errorString();
+ emit Error(tr("Failed writing cover to file %1: %2").arg(save_filename, file.errorString()));
+ file.close();
+ return;
+ }
+ file.close();
}
else {
if (!result.image.save(save_filename)) {
@@ -283,35 +285,26 @@ QString AlbumCoverChoiceController::GetInitialPathForFileDialog(const Song &song
// Art automatic is first to show user which cover the album may be using now;
// The song is using it if there's no manual path but we cannot use manual path here because it can contain cached paths
- if (!song.art_automatic().isEmpty() && !song.art_automatic().path().isEmpty() && !song.has_embedded_cover()) {
- if (song.art_automatic().scheme().isEmpty() && QFile::exists(QFileInfo(song.art_automatic().path()).path())) {
- return song.art_automatic().path();
- }
- else if (song.art_automatic().isLocalFile() && QFile::exists(QFileInfo(song.art_automatic().toLocalFile()).path())) {
- return song.art_automatic().toLocalFile();
- }
- // If no automatic art, start in the song's folder
+ if (song.art_automatic_is_valid()) {
+ return song.art_automatic().toLocalFile();
}
- else if (!song.url().isEmpty() && song.url().toLocalFile().contains('/')) {
+
+ // If no automatic art, start in the song's folder
+ if (!song.url().isEmpty() && song.url().isValid() && song.url().isLocalFile() && song.url().toLocalFile().contains('/')) {
return song.url().toLocalFile().section('/', 0, -2) + filename;
- // Fallback - start in home
}
return QDir::home().absolutePath() + filename;
}
-QUrl AlbumCoverChoiceController::LoadCoverFromURL(Song *song) {
+void AlbumCoverChoiceController::LoadCoverFromURL(Song *song) {
- if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
+ if (!song->url().isValid() || !song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return;
const AlbumCoverImageResult result = LoadImageFromURL();
-
- if (result.image.isNull()) {
- return QUrl();
- }
- else {
- return SaveCoverAutomatic(song, result);
+ if (!result.image.isNull()) {
+ SaveCoverAutomatic(song, result);
}
}
@@ -324,24 +317,21 @@ AlbumCoverImageResult AlbumCoverChoiceController::LoadImageFromURL() {
}
-QUrl AlbumCoverChoiceController::SearchForCover(Song *song) {
+void AlbumCoverChoiceController::SearchForCover(Song *song) {
- if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
+ if (!song->url().isValid() || !song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return;
// Get something sensible to stick in the search box
AlbumCoverImageResult result = SearchForImage(song);
if (result.is_valid()) {
- return SaveCoverAutomatic(song, result);
- }
- else {
- return QUrl();
+ SaveCoverAutomatic(song, result);
}
}
AlbumCoverImageResult AlbumCoverChoiceController::SearchForImage(Song *song) {
- if (!song->url().isLocalFile()) return AlbumCoverImageResult();
+ if (!song->url().isValid() || !song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return AlbumCoverImageResult();
QString album = song->effective_album();
album = album.remove(Song::kAlbumRemoveDisc).remove(Song::kAlbumRemoveMisc);
@@ -351,52 +341,38 @@ AlbumCoverImageResult AlbumCoverChoiceController::SearchForImage(Song *song) {
}
-QUrl AlbumCoverChoiceController::UnsetCover(Song *song, const bool clear_art_automatic) {
+void AlbumCoverChoiceController::UnsetCover(Song *song) {
- if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return QUrl();
+ if (!song->url().isValid() || !song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return;
- QUrl cover_url = QUrl::fromLocalFile(Song::kManuallyUnsetCover);
- SaveArtManualToSong(song, cover_url, clear_art_automatic);
-
- return cover_url;
+ UnsetAlbumCoverForSong(song);
}
-void AlbumCoverChoiceController::ClearCover(Song *song, const bool clear_art_automatic) {
+void AlbumCoverChoiceController::ClearCover(Song *song) {
- if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return;
+ if (!song->url().isValid() || !song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return;
- song->clear_art_manual();
- if (clear_art_automatic) song->clear_art_automatic();
- SaveArtManualToSong(song, QUrl(), clear_art_automatic);
+ ClearAlbumCoverForSong(song);
}
-bool AlbumCoverChoiceController::DeleteCover(Song *song, const bool manually_unset) {
+bool AlbumCoverChoiceController::DeleteCover(Song *song, const bool unset) {
- if (!song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return false;
+ if (!song->url().isValid() || !song->url().isLocalFile() || song->effective_albumartist().isEmpty() || song->album().isEmpty()) return false;
- if (song->has_embedded_cover() && song->save_embedded_cover_supported()) {
- SaveCoverEmbeddedAutomatic(*song, AlbumCoverImageResult());
- }
-
- QString art_automatic;
- QString art_manual;
- if (song->art_automatic().isValid() && song->art_automatic().isLocalFile()) {
- art_automatic = song->art_automatic().toLocalFile();
- }
- if (song->art_manual().isValid() && song->art_manual().isLocalFile()) {
- art_manual = song->art_manual().toLocalFile();
+ if (song->art_embedded() && song->save_embedded_cover_supported()) {
+ SaveCoverEmbeddedToCollectionSongs(*song, AlbumCoverImageResult());
}
bool success = true;
- if (!art_automatic.isEmpty()) {
+ if (song->art_automatic().isValid() && song->art_automatic().isLocalFile()) {
+ const QString art_automatic = song->art_automatic().toLocalFile();
QFile file(art_automatic);
if (file.exists()) {
if (file.remove()) {
song->clear_art_automatic();
- if (art_automatic == art_manual) song->clear_art_manual();
}
else {
success = false;
@@ -408,12 +384,12 @@ bool AlbumCoverChoiceController::DeleteCover(Song *song, const bool manually_uns
}
else song->clear_art_automatic();
- if (!art_manual.isEmpty()) {
+ if (song->art_manual().isValid() && song->art_manual().isLocalFile()) {
+ const QString art_manual = song->art_manual().toLocalFile();
QFile file(art_manual);
if (file.exists()) {
if (file.remove()) {
song->clear_art_manual();
- if (art_automatic == art_manual) song->clear_art_automatic();
}
else {
success = false;
@@ -426,8 +402,8 @@ bool AlbumCoverChoiceController::DeleteCover(Song *song, const bool manually_uns
else song->clear_art_manual();
if (success) {
- if (manually_unset) UnsetCover(song, true);
- else ClearCover(song, true);
+ if (unset) UnsetCover(song);
+ else ClearCover(song);
}
return success;
@@ -436,23 +412,55 @@ bool AlbumCoverChoiceController::DeleteCover(Song *song, const bool manually_uns
void AlbumCoverChoiceController::ShowCover(const Song &song, const QImage &image) {
- if (image.isNull()) {
- if ((song.art_manual().isValid() && song.art_manual().isLocalFile() && QFile::exists(song.art_manual().toLocalFile())) ||
- (song.art_automatic().isValid() && song.art_automatic().isLocalFile() && QFile::exists(song.art_automatic().toLocalFile())) ||
- song.has_embedded_cover()
- ) {
- QPixmap pixmap = ImageUtils::TryLoadPixmap(song.art_automatic(), song.art_manual(), song.url());
- if (!pixmap.isNull()) {
- pixmap.setDevicePixelRatio(devicePixelRatioF());
- ShowCover(song, pixmap);
- }
- }
- }
- else {
+ if (!image.isNull()) {
QPixmap pixmap = QPixmap::fromImage(image);
if (!pixmap.isNull()) {
- pixmap.setDevicePixelRatio(devicePixelRatioF());
- ShowCover(song, pixmap);
+ pixmap.setDevicePixelRatio(devicePixelRatioF());
+ ShowCover(song, pixmap);
+ return;
+ }
+ }
+
+ for (const AlbumCoverLoaderOptions::Type type : cover_types_) {
+ switch (type) {
+ case AlbumCoverLoaderOptions::Type::Unset: {
+ if (song.art_unset()) {
+ return;
+ }
+ break;
+ }
+ case AlbumCoverLoaderOptions::Type::Manual:{
+ QPixmap pixmap;
+ if (song.art_manual_is_valid() && song.art_manual().isLocalFile() && pixmap.load(song.art_manual().toLocalFile())) {
+ pixmap.setDevicePixelRatio(devicePixelRatioF());
+ ShowCover(song, pixmap);
+ return;
+ }
+ break;
+ }
+ case AlbumCoverLoaderOptions::Type::Embedded:{
+ if (song.art_embedded() && !song.url().isEmpty() && song.url().isValid() && song.url().isLocalFile()) {
+ const QImage image_embedded_cover = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song.url().toLocalFile());
+ if (!image_embedded_cover.isNull()) {
+ QPixmap pixmap = QPixmap::fromImage(image_embedded_cover);
+ if (!pixmap.isNull()) {
+ pixmap.setDevicePixelRatio(devicePixelRatioF());
+ ShowCover(song, pixmap);
+ return;
+ }
+ }
+ }
+ break;
+ }
+ case AlbumCoverLoaderOptions::Type::Automatic:{
+ QPixmap pixmap;
+ if (song.art_automatic_is_valid() && song.art_automatic().isLocalFile() && pixmap.load(song.art_automatic().toLocalFile())) {
+ pixmap.setDevicePixelRatio(devicePixelRatioF());
+ ShowCover(song, pixmap);
+ return;
+ }
+ break;
+ }
}
}
@@ -507,7 +515,7 @@ quint64 AlbumCoverChoiceController::SearchCoverAutomatically(const Song &song) {
quint64 id = cover_fetcher_->FetchAlbumCover(song.effective_albumartist(), song.album(), song.title(), true);
- cover_fetching_tasks_[id] = song;
+ cover_fetching_tasks_.insert(id, song);
return id;
@@ -530,17 +538,15 @@ void AlbumCoverChoiceController::AlbumCoverFetched(const quint64 id, const Album
}
-void AlbumCoverChoiceController::SaveArtAutomaticToSong(Song *song, const QUrl &art_automatic) {
+void AlbumCoverChoiceController::SaveArtEmbeddedToSong(Song *song, const bool art_embedded) {
if (!song->is_valid()) return;
- song->set_art_automatic(art_automatic);
- if (song->has_embedded_cover()) {
- song->clear_art_manual();
- }
+ song->set_art_embedded(art_embedded);
+ song->set_art_unset(false);
if (song->source() == Song::Source::Collection) {
- app_->collection_backend()->UpdateAutomaticAlbumArtAsync(song->effective_albumartist(), song->album(), art_automatic, song->has_embedded_cover());
+ app_->collection_backend()->UpdateEmbeddedAlbumArtAsync(song->effective_albumartist(), song->album(), art_embedded);
}
if (*song == app_->current_albumcover_loader()->last_song()) {
@@ -549,17 +555,17 @@ void AlbumCoverChoiceController::SaveArtAutomaticToSong(Song *song, const QUrl &
}
-void AlbumCoverChoiceController::SaveArtManualToSong(Song *song, const QUrl &art_manual, const bool clear_art_automatic) {
+void AlbumCoverChoiceController::SaveArtManualToSong(Song *song, const QUrl &art_manual) {
if (!song->is_valid()) return;
song->set_art_manual(art_manual);
- if (clear_art_automatic) song->clear_art_automatic();
+ song->set_art_unset(false);
// Update the backends.
switch (song->source()) {
case Song::Source::Collection:
- app_->collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual, clear_art_automatic);
+ app_->collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual);
break;
case Song::Source::LocalFile:
case Song::Source::CDDA:
@@ -575,13 +581,13 @@ void AlbumCoverChoiceController::SaveArtManualToSong(Song *song, const QUrl &art
InternetService *service = app_->internet_services()->ServiceBySource(song->source());
if (!service) break;
if (service->artists_collection_backend()) {
- service->artists_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual, clear_art_automatic);
+ service->artists_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual);
}
if (service->albums_collection_backend()) {
- service->albums_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual, clear_art_automatic);
+ service->albums_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual);
}
if (service->songs_collection_backend()) {
- service->songs_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual, clear_art_automatic);
+ service->songs_collection_backend()->UpdateManualAlbumArtAsync(song->effective_albumartist(), song->album(), art_manual);
}
break;
}
@@ -592,6 +598,44 @@ void AlbumCoverChoiceController::SaveArtManualToSong(Song *song, const QUrl &art
}
+void AlbumCoverChoiceController::ClearAlbumCoverForSong(Song *song) {
+
+ if (!song->is_valid()) return;
+
+ song->set_art_unset(false);
+ song->set_art_embedded(false);
+ song->clear_art_automatic();
+ song->clear_art_manual();
+
+ if (song->source() == Song::Source::Collection) {
+ app_->collection_backend()->ClearAlbumArtAsync(song->effective_albumartist(), song->album(), false);
+ }
+
+ if (*song == app_->current_albumcover_loader()->last_song()) {
+ app_->current_albumcover_loader()->LoadAlbumCover(*song);
+ }
+
+}
+
+void AlbumCoverChoiceController::UnsetAlbumCoverForSong(Song *song) {
+
+ if (!song->is_valid()) return;
+
+ song->set_art_unset(true);
+ song->set_art_embedded(false);
+ song->clear_art_manual();
+ song->clear_art_automatic();
+
+ if (song->source() == Song::Source::Collection) {
+ app_->collection_backend()->UnsetAlbumArtAsync(song->effective_albumartist(), song->album());
+ }
+
+ if (*song == app_->current_albumcover_loader()->last_song()) {
+ app_->current_albumcover_loader()->LoadAlbumCover(*song);
+ }
+
+}
+
QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song *song, const AlbumCoverImageResult &result, const bool force_overwrite) {
return SaveCoverToFileAutomatic(song->source(),
@@ -625,11 +669,10 @@ QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song::Source sou
filepath = file.fileName();
}
- QUrl cover_url;
- if (result.is_jpeg()) {
+ if (!result.image_data.isEmpty() && result.is_jpeg()) {
if (file.open(QIODevice::WriteOnly)) {
if (file.write(result.image_data) > 0) {
- cover_url = QUrl::fromLocalFile(filepath);
+ return QUrl::fromLocalFile(filepath);
}
else {
qLog(Error) << "Failed to write cover to file" << file.fileName() << file.errorString();
@@ -643,88 +686,58 @@ QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song::Source sou
}
}
else {
- if (result.image.save(filepath, "JPG")) cover_url = QUrl::fromLocalFile(filepath);
- }
-
- return cover_url;
-
-}
-
-void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const Song &song, const AlbumCoverImageResult &result) {
-
- if (song.source() == Song::Source::Collection) {
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- QFuture future = QtConcurrent::run(&CollectionBackend::GetAlbumSongs, app_->collection_backend(), song.effective_albumartist(), song.effective_album(), CollectionFilterOptions());
-#else
- QFuture future = QtConcurrent::run(app_->collection_backend(), &CollectionBackend::GetAlbumSongs, song.effective_albumartist(), song.effective_album(), CollectionFilterOptions());
-#endif
- QFutureWatcher *watcher = new QFutureWatcher();
- QObject::connect(watcher, &QFutureWatcher::finished, this, [this, watcher, song, result]() {
- SongList songs = watcher->result();
- watcher->deleteLater();
- QList urls;
- urls.reserve(songs.count());
- for (const Song &s : songs) urls << s.url();
- if (result.is_jpeg()) {
- quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image_data);
- QMutexLocker l(&mutex_cover_save_tasks_);
- cover_save_tasks_.insert(id, song);
- }
- else {
- quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image);
- QMutexLocker l(&mutex_cover_save_tasks_);
- cover_save_tasks_.insert(id, song);
- }
- });
- watcher->setFuture(future);
- }
- else {
- if (result.is_jpeg()) {
- app_->album_cover_loader()->SaveEmbeddedCoverAsync(song.url().toLocalFile(), result.image_data);
- }
- else {
- app_->album_cover_loader()->SaveEmbeddedCoverAsync(song.url().toLocalFile(), result.image);
+ if (result.image.save(filepath, "JPG")) {
+ return QUrl::fromLocalFile(filepath);
}
}
-}
-
-void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const Song &song, const QUrl &cover_url) {
-
- SaveCoverEmbeddedAutomatic(song, cover_url.toLocalFile());
+ return QUrl();
}
-void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const Song &song, const QString &cover_filename) {
+void AlbumCoverChoiceController::SaveCoverEmbeddedToCollectionSongs(const Song &song, const AlbumCoverImageResult &result) {
+
+ SaveCoverEmbeddedToCollectionSongs(song, QString(), result.image_data, result.mime_type);
+
+}
+
+void AlbumCoverChoiceController::SaveCoverEmbeddedToCollectionSongs(const Song &song, const QString &cover_filename, const QByteArray &image_data, const QString &mime_type) {
if (song.source() == Song::Source::Collection) {
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- QFuture future = QtConcurrent::run(&CollectionBackend::GetAlbumSongs, app_->collection_backend(), song.effective_albumartist(), song.effective_album(), CollectionFilterOptions());
-#else
- QFuture future = QtConcurrent::run(app_->collection_backend(), &CollectionBackend::GetAlbumSongs, song.effective_albumartist(), song.effective_album(), CollectionFilterOptions());
-#endif
- QFutureWatcher *watcher = new QFutureWatcher();
- QObject::connect(watcher, &QFutureWatcher::finished, this, [this, watcher, song, cover_filename]() {
- SongList songs = watcher->result();
- watcher->deleteLater();
- QList urls;
- urls.reserve(songs.count());
- for (const Song &s : songs) urls << s.url();
- quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, cover_filename);
- QMutexLocker l(&mutex_cover_save_tasks_);
- cover_save_tasks_.insert(id, song);
- });
- watcher->setFuture(future);
+ SaveCoverEmbeddedToCollectionSongs(song.effective_albumartist(), song.effective_album(), cover_filename, image_data, mime_type);
}
else {
- app_->album_cover_loader()->SaveEmbeddedCoverAsync(song.url().toLocalFile(), cover_filename);
+ SaveCoverEmbeddedToSong(song, cover_filename, image_data, mime_type);
}
}
-void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const QList &urls, const QImage &image) {
+void AlbumCoverChoiceController::SaveCoverEmbeddedToCollectionSongs(const QString &effective_albumartist, const QString &effective_album, const QString &cover_filename, const QByteArray &image_data, const QString &mime_type) {
- app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, image);
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ QFuture future = QtConcurrent::run(&CollectionBackend::GetAlbumSongs, app_->collection_backend(), effective_albumartist, effective_album, CollectionFilterOptions());
+#else
+ QFuture future = QtConcurrent::run(app_->collection_backend(), &CollectionBackend::GetAlbumSongs, effective_albumartist, effective_album, CollectionFilterOptions());
+#endif
+ QFutureWatcher *watcher = new QFutureWatcher();
+ QObject::connect(watcher, &QFutureWatcher::finished, this, [this, watcher, cover_filename, image_data, mime_type]() {
+ const SongList collection_songs = watcher->result();
+ watcher->deleteLater();
+ for (const Song &collection_song : collection_songs) {
+ SaveCoverEmbeddedToSong(collection_song, cover_filename, image_data, mime_type);
+ }
+ });
+ watcher->setFuture(future);
+
+}
+
+void AlbumCoverChoiceController::SaveCoverEmbeddedToSong(const Song &song, const QString &cover_filename, const QByteArray &image_data, const QString &mime_type) {
+
+ QMutexLocker l(&mutex_cover_save_tasks_);
+ cover_save_tasks_.append(song);
+ const bool art_embedded = !image_data.isNull();
+ TagReaderReply *reply = app_->tag_reader_client()->SaveEmbeddedArt(song.url().toLocalFile(), TagReaderClient::SaveCoverOptions(cover_filename, image_data, mime_type));
+ QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, song, art_embedded]() { SaveEmbeddedCoverFinished(reply, song, art_embedded); });
}
@@ -749,7 +762,7 @@ bool AlbumCoverChoiceController::CanAcceptDrag(const QDragEnterEvent *e) {
}
-QUrl AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
+void AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
for (const QUrl &url : e->mimeData()->urls()) {
@@ -758,25 +771,22 @@ QUrl AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
if (IsKnownImageExtension(suffix)) {
if (get_save_album_cover_type() == CoverOptions::CoverType::Embedded && song->save_embedded_cover_supported()) {
- SaveCoverEmbeddedAutomatic(*song, filename);
- return QUrl::fromLocalFile(Song::kEmbeddedCover);
+ SaveCoverEmbeddedToCollectionSongs(*song, filename);
}
else {
SaveArtManualToSong(song, url);
}
- return url;
+ return;
}
}
if (e->mimeData()->hasImage()) {
QImage image = qvariant_cast(e->mimeData()->imageData());
if (!image.isNull()) {
- return SaveCoverAutomatic(song, AlbumCoverImageResult(image));
+ SaveCoverAutomatic(song, AlbumCoverImageResult(image));
}
}
- return QUrl();
-
}
QUrl AlbumCoverChoiceController::SaveCoverAutomatic(Song *song, const AlbumCoverImageResult &result) {
@@ -785,8 +795,7 @@ QUrl AlbumCoverChoiceController::SaveCoverAutomatic(Song *song, const AlbumCover
switch(get_save_album_cover_type()) {
case CoverOptions::CoverType::Embedded:{
if (song->save_embedded_cover_supported()) {
- SaveCoverEmbeddedAutomatic(*song, result);
- cover_url = QUrl::fromLocalFile(Song::kEmbeddedCover);
+ SaveCoverEmbeddedToCollectionSongs(*song, result);
break;
}
}
@@ -803,14 +812,16 @@ QUrl AlbumCoverChoiceController::SaveCoverAutomatic(Song *song, const AlbumCover
}
-void AlbumCoverChoiceController::SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success, const bool cleared) {
+void AlbumCoverChoiceController::SaveEmbeddedCoverFinished(TagReaderReply *reply, Song song, const bool art_embedded) {
- if (!cover_save_tasks_.contains(id)) return;
+ if (!cover_save_tasks_.contains(song)) return;
+ cover_save_tasks_.removeAll(song);
- Song song = cover_save_tasks_.take(id);
- if (success) {
- if (cleared) SaveArtAutomaticToSong(&song, QUrl());
- else SaveArtAutomaticToSong(&song, QUrl::fromLocalFile(Song::kEmbeddedCover));
+ if (reply->is_successful()) {
+ SaveArtEmbeddedToSong(&song, art_embedded);
+ }
+ else {
+ emit Error(tr("Could not save cover to file %1.").arg(song.url().toLocalFile()));
}
}
diff --git a/src/covermanager/albumcoverchoicecontroller.h b/src/covermanager/albumcoverchoicecontroller.h
index 5ba4f99f9..ddfdbaa9b 100644
--- a/src/covermanager/albumcoverchoicecontroller.h
+++ b/src/covermanager/albumcoverchoicecontroller.h
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
- * Copyright 2019-2021, Jonas Kvinge
+ * Copyright 2019-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -38,8 +38,10 @@
#include
#include "core/song.h"
+#include "core/tagreaderclient.h"
#include "utilities/coveroptions.h"
#include "settings/collectionsettingspage.h"
+#include "albumcoverloaderoptions.h"
#include "albumcoverimageresult.h"
class QFileDialog;
@@ -108,22 +110,21 @@ class AlbumCoverChoiceController : public QWidget {
// Downloads the cover from an URL given by user.
// This returns the downloaded image or null image if something went wrong for example when user cancelled the dialog.
- QUrl LoadCoverFromURL(Song *song);
+ void LoadCoverFromURL(Song *song);
AlbumCoverImageResult LoadImageFromURL();
// Lets the user choose a cover among all that have been found on last.fm.
// Returns the chosen cover or null cover if user didn't choose anything.
- QUrl SearchForCover(Song *song);
+ void SearchForCover(Song *song);
AlbumCoverImageResult SearchForImage(Song *song);
- // Returns a path which indicates that the cover has been unset manually.
- QUrl UnsetCover(Song *song, const bool clear_art_automatic = false);
+ void UnsetCover(Song *song);
// Clears any album cover art associated with the song.
- void ClearCover(Song *song, const bool clear_art_automatic = false);
+ void ClearCover(Song *song);
// Physically deletes associated album covers from disk.
- bool DeleteCover(Song *song, const bool manually_unset = false);
+ bool DeleteCover(Song *song, const bool unset = false);
// Shows the cover of given song in it's original size.
void ShowCover(const Song &song, const QImage &image = QImage());
@@ -133,20 +134,25 @@ class AlbumCoverChoiceController : public QWidget {
quint64 SearchCoverAutomatically(const Song &song);
// Saves the chosen cover as manual cover path of this song in collection.
+ void SaveArtEmbeddedToSong(Song *song, const bool art_embedded);
void SaveArtAutomaticToSong(Song *song, const QUrl &art_automatic);
- void SaveArtManualToSong(Song *song, const QUrl &art_manual, const bool clear_art_automatic = false);
+ void SaveArtManualToSong(Song *song, const QUrl &art_manual);
+ void ClearAlbumCoverForSong(Song *song);
+ void UnsetAlbumCoverForSong(Song *song);
// Saves the cover that the user picked through a drag and drop operation.
- QUrl SaveCover(Song *song, const QDropEvent *e);
+ void SaveCover(Song *song, const QDropEvent *e);
// Saves the given image in album directory or cache as a cover for 'album artist' - 'album'. The method returns path of the image.
QUrl SaveCoverAutomatic(Song *song, const AlbumCoverImageResult &result);
+
QUrl SaveCoverToFileAutomatic(const Song *song, const AlbumCoverImageResult &result, const bool force_overwrite = false);
QUrl SaveCoverToFileAutomatic(const Song::Source source, const QString &artist, const QString &album, const QString &album_id, const QString &album_dir, const AlbumCoverImageResult &result, const bool force_overwrite = false);
- void SaveCoverEmbeddedAutomatic(const Song &song, const AlbumCoverImageResult &result);
- void SaveCoverEmbeddedAutomatic(const Song &song, const QUrl &cover_url);
- void SaveCoverEmbeddedAutomatic(const Song &song, const QString &cover_filename);
- void SaveCoverEmbeddedAutomatic(const QList &urls, const QImage &image);
+
+ void SaveCoverEmbeddedToCollectionSongs(const Song &song, const AlbumCoverImageResult &result);
+ void SaveCoverEmbeddedToCollectionSongs(const Song &song, const QString &cover_filename, const QByteArray &image_data = QByteArray(), const QString &mime_type = QString());
+ void SaveCoverEmbeddedToCollectionSongs(const QString &effective_albumartist, const QString &effective_album, const QString &cover_filename, const QByteArray &image_data = QByteArray(), const QString &mime_type = QString());
+ void SaveCoverEmbeddedToSong(const Song &song, const QString &cover_filename, const QByteArray &image_data, const QString &mime_type = QString());
static bool CanAcceptDrag(const QDragEnterEvent *e);
@@ -155,7 +161,7 @@ class AlbumCoverChoiceController : public QWidget {
private slots:
void AlbumCoverFetched(const quint64 id, const AlbumCoverImageResult &result, const CoverSearchStatistics &statistics);
- void SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success, const bool cleared);
+ void SaveEmbeddedCoverFinished(TagReaderReply *reply, Song song, const bool art_embedded);
signals:
void Error(const QString &error);
@@ -187,12 +193,13 @@ class AlbumCoverChoiceController : public QWidget {
QAction *search_cover_auto_;
QMap cover_fetching_tasks_;
- QMap cover_save_tasks_;
+ QList cover_save_tasks_;
QMutex mutex_cover_save_tasks_;
CoverOptions cover_options_;
bool save_embedded_cover_override_;
+ AlbumCoverLoaderOptions::Types cover_types_;
};
#endif // ALBUMCOVERCHOICECONTROLLER_H
diff --git a/src/covermanager/albumcoverexporter.cpp b/src/covermanager/albumcoverexporter.cpp
index 8f3c9abb2..dbf3457a7 100644
--- a/src/covermanager/albumcoverexporter.cpp
+++ b/src/covermanager/albumcoverexporter.cpp
@@ -24,6 +24,7 @@
#include
#include "core/song.h"
+#include "albumcoverloaderoptions.h"
#include "albumcoverexport.h"
#include "albumcoverexporter.h"
#include "coverexportrunnable.h"
@@ -43,9 +44,15 @@ void AlbumCoverExporter::SetDialogResult(const AlbumCoverExport::DialogResult &d
dialog_result_ = dialog_result;
}
+void AlbumCoverExporter::SetCoverTypes(const AlbumCoverLoaderOptions::Types cover_types) {
+ cover_types_ = cover_types;
+}
+
void AlbumCoverExporter::AddExportRequest(const Song &song) {
- requests_.append(new CoverExportRunnable(dialog_result_, song));
+
+ requests_.append(new CoverExportRunnable(dialog_result_, cover_types_, song));
all_ = static_cast(requests_.count());
+
}
void AlbumCoverExporter::Cancel() { requests_.clear(); }
diff --git a/src/covermanager/albumcoverexporter.h b/src/covermanager/albumcoverexporter.h
index 30aadab3a..705dd47ed 100644
--- a/src/covermanager/albumcoverexporter.h
+++ b/src/covermanager/albumcoverexporter.h
@@ -27,6 +27,7 @@
#include
#include
+#include "albumcoverloaderoptions.h"
#include "albumcoverexport.h"
class QThreadPool;
@@ -42,6 +43,7 @@ class AlbumCoverExporter : public QObject {
static const int kMaxConcurrentRequests;
void SetDialogResult(const AlbumCoverExport::DialogResult &dialog_result);
+ void SetCoverTypes(const AlbumCoverLoaderOptions::Types cover_types);
void AddExportRequest(const Song &song);
void StartExporting();
void Cancel();
@@ -57,6 +59,8 @@ class AlbumCoverExporter : public QObject {
private:
void AddJobsToPool();
+
+ AlbumCoverLoaderOptions::Types cover_types_;
AlbumCoverExport::DialogResult dialog_result_;
QQueue requests_;
diff --git a/src/covermanager/albumcoverimageresult.h b/src/covermanager/albumcoverimageresult.h
index 83858d43f..2ec4e87e6 100644
--- a/src/covermanager/albumcoverimageresult.h
+++ b/src/covermanager/albumcoverimageresult.h
@@ -20,8 +20,6 @@
#ifndef ALBUMCOVERIMAGERESULT_H
#define ALBUMCOVERIMAGERESULT_H
-#include "config.h"
-
#include
#include
#include
@@ -30,10 +28,7 @@
class AlbumCoverImageResult {
public:
- explicit AlbumCoverImageResult(const QUrl &_cover_url = QUrl(),
- const QString &_mime_type = QString(),
- const QByteArray &_image_data = QByteArray(),
- const QImage &_image = QImage())
+ explicit AlbumCoverImageResult(const QUrl &_cover_url = QUrl(), const QString &_mime_type = QString(), const QByteArray &_image_data = QByteArray(), const QImage &_image = QImage())
: cover_url(_cover_url),
mime_type(_mime_type),
image_data(_image_data),
@@ -47,7 +42,6 @@ class AlbumCoverImageResult {
bool is_valid() const { return !image_data.isNull() || !image.isNull(); }
bool is_jpeg() const { return mime_type == "image/jpeg" && !image_data.isEmpty(); }
-
};
Q_DECLARE_METATYPE(AlbumCoverImageResult)
diff --git a/src/covermanager/albumcoverloader.cpp b/src/covermanager/albumcoverloader.cpp
index 2556c2f36..324e9957a 100644
--- a/src/covermanager/albumcoverloader.cpp
+++ b/src/covermanager/albumcoverloader.cpp
@@ -1,8 +1,6 @@
/*
* Strawberry Music Player
- * This file was part of Clementine.
- * Copyright 2010, David Sansome
- * Copyright 2019-2021, Jonas Kvinge
+ * Copyright 2019-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,19 +17,13 @@
*
*/
-#include "config.h"
-
#include
#include
#include
-#include
-#include
#include
#include
-#include
#include
-#include
#include
#include
#include
@@ -39,7 +31,6 @@
#include
#include
#include
-#include
#include
#include
@@ -53,12 +44,13 @@
#include "albumcoverloaderresult.h"
#include "albumcoverimageresult.h"
+using std::make_shared;
+
AlbumCoverLoader::AlbumCoverLoader(QObject *parent)
: QObject(parent),
network_(new NetworkAccessManager(this)),
stop_requested_(false),
load_image_async_id_(1),
- save_image_async_id_(1),
original_thread_(nullptr) {
original_thread_ = thread();
@@ -86,7 +78,7 @@ void AlbumCoverLoader::CancelTask(const quint64 id) {
for (QQueue::iterator it = tasks_.begin(); it != tasks_.end(); ++it) {
TaskPtr task = *it;
if (task->id == id) {
- tasks_.erase(it); // clazy:exclude=strict-iterators
+ tasks_.erase(it);
break;
}
}
@@ -99,7 +91,7 @@ void AlbumCoverLoader::CancelTasks(const QSet &ids) {
for (QQueue::iterator it = tasks_.begin(); it != tasks_.end();) {
TaskPtr task = *it;
if (ids.contains(task->id)) {
- it = tasks_.erase(it); // clazy:exclude=strict-iterators
+ it = tasks_.erase(it);
}
else {
++it;
@@ -112,24 +104,28 @@ quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options,
TaskPtr task = std::make_shared();
task->options = options;
+ task->art_embedded = song.art_embedded();
+ task->art_automatic = song.art_automatic();
+ task->art_manual = song.art_manual();
+ task->art_unset = song.art_unset();
+ task->song_source = song.source();
+ task->song_url = song.url();
task->song = song;
- task->state = State::Manual;
return EnqueueTask(task);
}
-quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QUrl &song_url, const Song::Source song_source) {
-
- Song song(song_source);
- song.set_url(song_url);
- song.set_art_automatic(art_automatic);
- song.set_art_manual(art_manual);
+quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const bool art_embedded, const QUrl &art_automatic, const QUrl &art_manual, const bool art_unset, const QUrl &song_url, const Song::Source song_source) {
TaskPtr task = std::make_shared();
task->options = options;
- task->song = song;
- task->state = State::Manual;
+ task->art_embedded = art_embedded;
+ task->art_automatic = art_automatic;
+ task->art_manual = art_manual;
+ task->art_unset = art_unset;
+ task->song_source = song_source;
+ task->song_url = song_url;
return EnqueueTask(task);
@@ -137,7 +133,7 @@ quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options,
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const AlbumCoverImageResult &album_cover) {
- TaskPtr task = std::make_shared();
+ TaskPtr task = make_shared();
task->options = options;
task->album_cover = album_cover;
@@ -147,7 +143,7 @@ quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options,
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QImage &image) {
- TaskPtr task = std::make_shared();
+ TaskPtr task = make_shared();
task->options = options;
task->album_cover.image = image;
@@ -171,390 +167,254 @@ quint64 AlbumCoverLoader::EnqueueTask(TaskPtr task) {
void AlbumCoverLoader::ProcessTasks() {
- while (!stop_requested_) {
- // Get the next task
- TaskPtr task;
- {
- QMutexLocker l(&mutex_load_image_async_);
- if (tasks_.isEmpty()) return;
- task = tasks_.dequeue();
- }
-
- ProcessTask(task);
+ TaskPtr task;
+ {
+ QMutexLocker l(&mutex_load_image_async_);
+ if (tasks_.isEmpty()) return;
+ task = tasks_.dequeue();
}
+ ProcessTask(task);
+
}
void AlbumCoverLoader::ProcessTask(TaskPtr task) {
- TryLoadResult result = TryLoadImage(task);
- if (result.started_async) {
- // The image is being loaded from a remote URL, we'll carry on later when it's done
- return;
- }
-
- if (result.loaded_success) {
- result.album_cover.mime_type = Utilities::MimeTypeFromData(result.album_cover.image_data);
- QImage image_scaled;
- QImage image_thumbnail;
- if (task->options.get_image_ && task->options.scale_output_image_) {
- image_scaled = ImageUtils::ScaleAndPad(result.album_cover.image, task->options.scale_output_image_, task->options.pad_output_image_, task->options.desired_height_);
- }
- if (task->options.get_image_ && task->options.create_thumbnail_) {
- image_thumbnail = ImageUtils::CreateThumbnail(result.album_cover.image, task->options.pad_thumbnail_image_, task->options.thumbnail_size_);
- }
- emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(result.loaded_success, result.type, result.album_cover, image_scaled, image_thumbnail, task->art_updated));
- return;
- }
-
- NextState(task);
-
-}
-
-void AlbumCoverLoader::NextState(TaskPtr task) {
-
- if (task->state == State::Manual) {
- // Try the automatic one next
- task->state = State::Automatic;
- ProcessTask(task);
+ // If we have album cover already, only do scale and pad.
+ if (task->album_cover.is_valid()) {
+ task->success = true;
}
else {
- // Give up
- emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(false, AlbumCoverLoaderResult::Type::None, AlbumCoverImageResult(task->options.default_output_image_), task->options.default_scaled_image_, task->options.default_thumbnail_image_, task->art_updated));
+ InitArt(task);
}
+ while (!task->success && !task->options.types.isEmpty()) {
+ const AlbumCoverLoaderOptions::Type type = task->options.types.takeFirst();
+ const LoadImageResult result = LoadImage(task, type);
+ if (result.status == LoadImageResult::Status::Async) {
+ // The image is being loaded from a remote URL, we'll carry on later when it's done.
+ return;
+ }
+ if (result.status == LoadImageResult::Status::Success) {
+ task->success = true;
+ task->result_type = result.type;
+ break;
+ }
+ }
+
+ if (!task->success && !task->options.default_cover.isEmpty()) {
+ LoadLocalFileImage(task, AlbumCoverLoaderResult::Type::None, task->options.default_cover);
+ }
+
+ FinishTask(task, task->result_type);
+
}
-AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(TaskPtr task) {
+void AlbumCoverLoader::FinishTask(TaskPtr task, const AlbumCoverLoaderResult::Type result_type) {
- // Only scale and pad.
- if (task->album_cover.is_valid()) {
- return TryLoadResult(false, true, AlbumCoverLoaderResult::Type::Embedded, task->album_cover);
+ QImage image_scaled;
+ if (task->success) {
+ task->result_type = result_type;
+ task->album_cover.mime_type = Utilities::MimeTypeFromData(task->album_cover.image_data);
+ if (task->scaled_image()) {
+ image_scaled = ImageUtils::ScaleImage(task->album_cover.image, task->options.desired_scaled_size, task->options.device_pixel_ratio, task->pad_scaled_image());
+ }
+ if (!task->raw_image_data() && !task->album_cover.image_data.isNull()) {
+ task->album_cover.image_data = QByteArray();
+ }
+ if (!task->original_image() && !task->album_cover.image.isNull()) {
+ task->album_cover.image = QImage();
+ }
}
+ emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(task->success, task->result_type, task->album_cover, image_scaled, task->art_updated));
+
+}
+
+void AlbumCoverLoader::InitArt(TaskPtr task) {
+
// For local files and streams initialize art if found.
- if ((task->song.source() == Song::Source::LocalFile || task->song.is_radio()) && !task->song.art_manual_is_valid() && !task->song.art_automatic_is_valid()) {
- switch (task->state) {
- case State::None:
- break;
- case State::Manual:
- task->song.InitArtManual();
- if (task->song.art_manual_is_valid()) task->art_updated = true;
- break;
- case State::Automatic:
- if (task->song.url().isLocalFile()) {
- task->song.InitArtAutomatic();
- if (task->song.art_automatic_is_valid()) task->art_updated = true;
- }
- break;
+ if (task->song.is_valid() && (task->song.source() == Song::Source::LocalFile || task->song.is_radio()) && !task->song.art_manual_is_valid() && !task->song.art_automatic_is_valid()) {
+ task->song.InitArtManual();
+ if (task->song.art_manual_is_valid()) {
+ task->art_updated = true;
+ task->art_manual = task->song.art_manual();
+ }
+ if (task->song.url().isLocalFile()) {
+ task->song.InitArtAutomatic();
+ if (task->song.art_automatic_is_valid()) {
+ task->art_updated = true;
+ task->art_automatic = task->song.art_automatic();
+ }
}
}
- AlbumCoverLoaderResult::Type type = AlbumCoverLoaderResult::Type::None;
- QUrl cover_url;
- switch (task->state) {
- case State::None:
- case State::Automatic:
- type = AlbumCoverLoaderResult::Type::Automatic;
- cover_url = task->song.art_automatic();
- break;
- case State::Manual:
- type = AlbumCoverLoaderResult::Type::Manual;
- cover_url = task->song.art_manual();
- break;
- }
- task->type = type;
-
- if (!cover_url.isEmpty() && !cover_url.path().isEmpty()) {
- if (cover_url.path() == Song::kManuallyUnsetCover) {
- return TryLoadResult(false, true, AlbumCoverLoaderResult::Type::ManuallyUnset, AlbumCoverImageResult(cover_url, QString(), QByteArray(), task->options.default_output_image_));
- }
- else if (cover_url.path() == Song::kEmbeddedCover && task->song.url().isLocalFile()) {
- QByteArray image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song.url().toLocalFile());
- if (!image_data.isEmpty()) {
- QImage image;
- if (!image_data.isEmpty() && task->options.get_image_ && image.loadFromData(image_data)) {
- return TryLoadResult(false, !image.isNull(), AlbumCoverLoaderResult::Type::Embedded, AlbumCoverImageResult(cover_url, QString(), image_data, image));
- }
- else {
- return TryLoadResult(false, !image_data.isEmpty(), AlbumCoverLoaderResult::Type::Embedded, AlbumCoverImageResult(cover_url, QString(), image_data, image));
- }
- }
- }
-
- if (cover_url.isLocalFile()) {
- QFile file(cover_url.toLocalFile());
- if (file.exists()) {
- if (file.open(QIODevice::ReadOnly)) {
- QByteArray image_data = file.readAll();
- file.close();
- QImage image;
- if (!image_data.isEmpty() && task->options.get_image_ && image.loadFromData(image_data)) {
- return TryLoadResult(false, !image.isNull(), type, AlbumCoverImageResult(cover_url, QString(), image_data, image.isNull() ? task->options.default_output_image_ : image));
- }
- else {
- return TryLoadResult(false, !image_data.isEmpty(), type, AlbumCoverImageResult(cover_url, QString(), image_data, image.isNull() ? task->options.default_output_image_ : image));
- }
- }
- else {
- qLog(Error) << "Failed to open cover file" << cover_url << "for reading" << file.errorString();
- }
- }
- else {
- qLog(Error) << "Cover file" << cover_url << "does not exist";
- }
- }
- else if (cover_url.scheme().isEmpty()) { // Assume a local file with no scheme.
- QFile file(cover_url.path());
- if (file.exists()) {
- if (file.open(QIODevice::ReadOnly)) {
- QByteArray image_data = file.readAll();
- file.close();
- QImage image;
- if (!image_data.isEmpty() && task->options.get_image_ && image.loadFromData(image_data)) {
- return TryLoadResult(false, !image.isNull(), type, AlbumCoverImageResult(cover_url, QString(), image_data, image.isNull() ? task->options.default_output_image_ : image));
- }
- else {
- return TryLoadResult(false, !image_data.isEmpty(), type, AlbumCoverImageResult(cover_url, QString(), image_data, image.isNull() ? task->options.default_output_image_ : image));
- }
- }
- else {
- qLog(Error) << "Failed to open cover file" << cover_url << "for reading" << file.errorString();
- }
- }
- else {
- qLog(Error) << "Cover file" << cover_url << "does not exist";
- }
- }
- else if (network_->supportedSchemes().contains(cover_url.scheme())) { // Remote URL
- qLog(Debug) << "Loading remote cover from" << cover_url;
- QNetworkRequest request(cover_url);
- request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
- QNetworkReply *reply = network_->get(request);
- QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, cover_url]() { RemoteFetchFinished(reply, cover_url); });
-
- remote_tasks_.insert(reply, task);
- return TryLoadResult(true, false, type, AlbumCoverImageResult(cover_url));
- }
- }
-
- return TryLoadResult(false, false, AlbumCoverLoaderResult::Type::None, AlbumCoverImageResult(cover_url, QString(), QByteArray(), task->options.default_output_image_));
-
}
-void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url) {
+AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadImage(TaskPtr task, const AlbumCoverLoaderOptions::Type &type) {
+
+ switch (type) {
+ case AlbumCoverLoaderOptions::Type::Unset:{
+ if (task->art_unset) {
+ if (!task->options.default_cover.isEmpty()) {
+ return LoadLocalFileImage(task, AlbumCoverLoaderResult::Type::Unset, task->options.default_cover);
+ }
+ return LoadImageResult(AlbumCoverLoaderResult::Type::Unset, LoadImageResult::Status::Success);
+ }
+ break;
+ }
+ case AlbumCoverLoaderOptions::Type::Embedded:{
+ if (task->art_embedded && task->song_url.isValid() && task->song_url.isLocalFile()) {
+ return LoadEmbeddedImage(task);
+ }
+ break;
+ }
+ case AlbumCoverLoaderOptions::Type::Automatic:{
+ if (task->art_automatic.isValid()) {
+ return LoadUrlImage(task, AlbumCoverLoaderResult::Type::Automatic, task->art_automatic);
+ }
+ break;
+ }
+ case AlbumCoverLoaderOptions::Type::Manual:{
+ if (task->art_manual.isValid()) {
+ return LoadUrlImage(task, AlbumCoverLoaderResult::Type::Manual, task->art_manual);
+ }
+ break;
+ }
+ }
+
+ return LoadImageResult();
+
+}
+
+AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadEmbeddedImage(TaskPtr task) {
+
+ if (task->art_embedded && task->song_url.isValid() && task->song_url.isLocalFile()) {
+ task->album_cover.image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song_url.toLocalFile());
+ if (!task->album_cover.image_data.isEmpty() && task->album_cover.image.loadFromData(task->album_cover.image_data)) {
+ return LoadImageResult(AlbumCoverLoaderResult::Type::Embedded, LoadImageResult::Status::Success);
+ }
+ }
+
+ return LoadImageResult(AlbumCoverLoaderResult::Type::Embedded, LoadImageResult::Status::Failure);
+
+}
+
+AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url) {
+
+ if (cover_url.isValid()) {
+ if (cover_url.isLocalFile()) {
+ return LoadLocalUrlImage(task, result_type, cover_url);
+ }
+ else if (network_->supportedSchemes().contains(cover_url.scheme())) {
+ return LoadRemoteUrlImage(task, result_type, cover_url);
+ }
+ }
+
+ return LoadImageResult(result_type, LoadImageResult::Status::Failure);
+
+}
+
+AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadLocalUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url) {
+
+ if (cover_url.isEmpty()) {
+ return LoadImageResult(result_type, LoadImageResult::Status::Failure);
+ }
+
+ if (!cover_url.isValid()) {
+ return LoadImageResult(result_type, LoadImageResult::Status::Failure);
+ }
+
+ if (!cover_url.isLocalFile()) {
+ return LoadImageResult(result_type, LoadImageResult::Status::Failure);
+ }
+
+ return LoadLocalFileImage(task, result_type, cover_url.toLocalFile());
+
+}
+
+AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadLocalFileImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QString &cover_file) {
+
+ if (!QFileInfo::exists(cover_file)) {
+ qLog(Error) << "Cover file" << cover_file << "does not exist.";
+ return LoadImageResult(result_type, LoadImageResult::Status::Failure);
+ }
+
+ QFile file(cover_file);
+ if (!file.open(QIODevice::ReadOnly)) {
+ qLog(Error) << "Unable to open cover file" << cover_file << "for reading:" << file.errorString();
+ return LoadImageResult(result_type, LoadImageResult::Status::Failure);
+ }
+
+ task->album_cover.image_data = file.readAll();
+ file.close();
+
+ if (task->album_cover.image_data.isEmpty()) {
+ qLog(Error) << "Cover file" << cover_file << "is empty.";
+ return LoadImageResult(result_type, LoadImageResult::Status::Failure);
+ }
+
+ if (!task->album_cover.image.loadFromData(task->album_cover.image_data)) {
+ qLog(Error) << "Failed to load image from cover file" << cover_file << ":" << file.errorString();
+ return LoadImageResult(result_type, LoadImageResult::Status::Failure);
+ }
+
+ return LoadImageResult(result_type, LoadImageResult::Status::Success);
+
+}
+
+AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadRemoteUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url) {
+
+ qLog(Debug) << "Loading remote cover from URL" << cover_url;
+
+ QNetworkRequest request(cover_url);
+ request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
+ QNetworkReply *reply = network_->get(request);
+ QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, task, result_type, cover_url]() { LoadRemoteImageFinished(reply, task, result_type, cover_url); });
+
+ return LoadImageResult(result_type, LoadImageResult::Status::Async);
+
+}
+
+void AlbumCoverLoader::LoadRemoteImageFinished(QNetworkReply *reply, TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url) {
reply->deleteLater();
- if (!remote_tasks_.contains(reply)) return;
- TaskPtr task = remote_tasks_.take(reply);
-
- // Handle redirects.
QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
- if (redirect.isValid()) {
- if (++task->redirects > kMaxRedirects) {
- return; // Give up.
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ if (redirect.isValid() && redirect.metaType().id() == QMetaType::QUrl) {
+#else
+ if (redirect.isValid() && redirect.type() == QVariant::Url) {
+#endif
+ if (task->redirects++ >= kMaxRedirects) {
+ ProcessTask(task);
+ return;
}
+ const QUrl redirect_url = redirect.toUrl();
+ qLog(Debug) << "Loading remote cover from redirected URL" << redirect_url;
QNetworkRequest request = reply->request();
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
- request.setUrl(redirect.toUrl());
+ request.setUrl(redirect_url);
QNetworkReply *redirected_reply = network_->get(request);
- QObject::connect(redirected_reply, &QNetworkReply::finished, this, [this, redirected_reply, redirect]() { RemoteFetchFinished(redirected_reply, redirect.toUrl()); });
-
- remote_tasks_.insert(redirected_reply, task);
+ QObject::connect(redirected_reply, &QNetworkReply::finished, this, [this, reply, task, result_type, redirect_url]() { LoadRemoteImageFinished(reply, task, result_type, redirect_url); });
return;
}
if (reply->error() == QNetworkReply::NoError) {
- // Try to load the image
- QByteArray image_data = reply->readAll();
- QString mime_type = Utilities::MimeTypeFromData(image_data);
- QImage image;
- if (image.loadFromData(image_data)) {
- QImage image_scaled;
- QImage image_thumbnail;
- if (task->options.scale_output_image_) image_scaled = ImageUtils::ScaleAndPad(image, task->options.scale_output_image_, task->options.pad_output_image_, task->options.desired_height_);
- if (task->options.create_thumbnail_) image_thumbnail = ImageUtils::CreateThumbnail(image, task->options.pad_thumbnail_image_, task->options.thumbnail_size_);
- emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(true, task->type, AlbumCoverImageResult(cover_url, mime_type, (task->options.get_image_data_ ? image_data : QByteArray()), image), image_scaled, image_thumbnail, task->art_updated));
+ task->album_cover.image_data = reply->readAll();
+ if (!task->album_cover.image_data.isEmpty() && task->album_cover.image.loadFromData(task->album_cover.image_data)) {
+ task->success = true;
+ FinishTask(task, result_type);
return;
}
else {
- qLog(Error) << "Unable to load album cover image" << cover_url;
+ qLog(Error) << "Unable to load album cover image from URL" << cover_url;
}
}
else {
- qLog(Error) << "Unable to get album cover" << cover_url << reply->error() << reply->errorString();
+ qLog(Error) << "Unable to get album cover from URL" << cover_url << reply->error() << reply->errorString();
}
- NextState(task);
-
-}
-
-quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QString &song_filename, const QString &cover_filename) {
-
- QMutexLocker l(&mutex_save_image_async_);
- quint64 id = ++save_image_async_id_;
- QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QString, song_filename), Q_ARG(QString, cover_filename));
- return id;
-
-}
-
-quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QString &song_filename, const QImage &image) {
-
- QMutexLocker l(&mutex_save_image_async_);
- quint64 id = ++save_image_async_id_;
- QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QString, song_filename), Q_ARG(QImage, image));
- return id;
-
-}
-
-quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QString &song_filename, const QByteArray &image_data) {
-
- QMutexLocker l(&mutex_save_image_async_);
- quint64 id = ++save_image_async_id_;
- QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QString, song_filename), Q_ARG(QByteArray, image_data));
- return id;
-
-}
-
-quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList &urls, const QString &cover_filename) {
-
- QMutexLocker l(&mutex_save_image_async_);
- quint64 id = ++save_image_async_id_;
- QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QList, urls), Q_ARG(QString, cover_filename));
- return id;
-
-}
-
-quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList &urls, const QImage &image) {
-
- QMutexLocker l(&mutex_save_image_async_);
- quint64 id = ++save_image_async_id_;
- QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QList, urls), Q_ARG(QImage, image));
- return id;
-
-}
-
-quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList &urls, const QByteArray &image_data) {
-
- QMutexLocker l(&mutex_save_image_async_);
- quint64 id = ++save_image_async_id_;
- QMetaObject::invokeMethod(this, "SaveEmbeddedCover", Qt::QueuedConnection, Q_ARG(quint64, id), Q_ARG(QList, urls), Q_ARG(QByteArray, image_data));
- return id;
-
-}
-
-void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QByteArray &image_data) {
-
- TagReaderReply *reply = TagReaderClient::Instance()->SaveEmbeddedArt(song_filename, TagReaderClient::SaveCoverOptions(image_data));
- tagreader_save_embedded_art_requests_.insert(id, reply);
- const bool clear = image_data.isEmpty();
- QObject::connect(reply, &TagReaderReply::Finished, this, [this, id, reply, clear]() { SaveEmbeddedArtFinished(id, reply, clear); }, Qt::QueuedConnection);
-
-}
-
-void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QImage &image) {
-
- QByteArray image_data;
-
- if (!image.isNull()) {
- QBuffer buffer(&image_data);
- if (buffer.open(QIODevice::WriteOnly)) {
- image.save(&buffer, "JPEG");
- buffer.close();
- }
- }
-
- SaveEmbeddedCover(id, song_filename, image_data);
-
-}
-
-void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QString &cover_filename) {
-
- QFile file(cover_filename);
-
- if (file.size() >= 209715200) { // Max 200 MB.
- emit SaveEmbeddedCoverAsyncFinished(id, false, false);
- return;
- }
-
- if (!file.open(QIODevice::ReadOnly)) {
- qLog(Error) << "Failed to open cover file" << cover_filename << "for reading:" << file.errorString();
- emit SaveEmbeddedCoverAsyncFinished(id, false, false);
- return;
- }
-
- QByteArray image_data = file.readAll();
- file.close();
-
- SaveEmbeddedCover(id, song_filename, image_data);
-
-}
-
-void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QList &urls, const QImage &image) {
-
- if (image.isNull()) {
- for (const QUrl &url : urls) {
- SaveEmbeddedCover(id, url.toLocalFile(), QByteArray());
- }
- return;
- }
- else {
- QByteArray image_data;
- QBuffer buffer(&image_data);
- if (buffer.open(QIODevice::WriteOnly)) {
- if (image.save(&buffer, "JPEG")) {
- SaveEmbeddedCover(id, urls, image_data);
- buffer.close();
- return;
- }
- buffer.close();
- }
- }
-
- emit SaveEmbeddedCoverAsyncFinished(id, false, image.isNull());
-
-}
-
-void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QList &urls, const QString &cover_filename) {
-
- QFile file(cover_filename);
-
- if (file.size() >= 209715200) { // Max 200 MB.
- emit SaveEmbeddedCoverAsyncFinished(id, false, false);
- return;
- }
-
- if (!file.open(QIODevice::ReadOnly)) {
- qLog(Error) << "Failed to open cover file" << cover_filename << "for reading:" << file.errorString();
- emit SaveEmbeddedCoverAsyncFinished(id, false, false);
- return;
- }
-
- QByteArray image_data = file.readAll();
- file.close();
- SaveEmbeddedCover(id, urls, image_data);
-
-}
-
-void AlbumCoverLoader::SaveEmbeddedCover(const quint64 id, const QList &urls, const QByteArray &image_data) {
-
- for (const QUrl &url : urls) {
- SaveEmbeddedCover(id, url.toLocalFile(), image_data);
- }
-
-}
-
-void AlbumCoverLoader::SaveEmbeddedArtFinished(const quint64 id, TagReaderReply *reply, const bool cleared) {
-
- if (tagreader_save_embedded_art_requests_.contains(id)) {
- tagreader_save_embedded_art_requests_.remove(id, reply);
- }
-
- if (!tagreader_save_embedded_art_requests_.contains(id)) {
- emit SaveEmbeddedCoverAsyncFinished(id, reply->is_successful(), cleared);
- }
-
- reply->deleteLater();
+ ProcessTask(task);
}
diff --git a/src/covermanager/albumcoverloader.h b/src/covermanager/albumcoverloader.h
index 6b69dff48..9493fae21 100644
--- a/src/covermanager/albumcoverloader.h
+++ b/src/covermanager/albumcoverloader.h
@@ -1,8 +1,6 @@
/*
* Strawberry Music Player
- * This file was part of Clementine.
- * Copyright 2010, David Sansome
- * Copyright 2019-2021, Jonas Kvinge
+ * Copyright 2019-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -24,21 +22,19 @@
#include "config.h"
+#include
+
#include
#include
#include
-#include
#include
#include
-#include
#include
#include
#include
#include
-#include
#include "core/song.h"
-#include "core/tagreaderclient.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
#include "albumcoverimageresult.h"
@@ -47,111 +43,96 @@ class QThread;
class QNetworkReply;
class NetworkAccessManager;
+using std::shared_ptr;
+
class AlbumCoverLoader : public QObject {
Q_OBJECT
public:
explicit AlbumCoverLoader(QObject *parent = nullptr);
- enum class State {
- None,
- Manual,
- Automatic
- };
-
void ExitAsync();
void Stop() { stop_requested_ = true; }
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const Song &song);
- quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QUrl &song_url = QUrl(), const Song::Source song_source = Song::Source::Unknown);
+ quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const bool art_embedded, const QUrl &art_automatic, const QUrl &art_manual, const bool art_unset, const QUrl &song_url = QUrl(), const Song::Source song_source = Song::Source::Unknown);
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const AlbumCoverImageResult &album_cover);
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QImage &image);
void CancelTask(const quint64 id);
void CancelTasks(const QSet &ids);
- quint64 SaveEmbeddedCoverAsync(const QString &song_filename, const QString &cover_filename);
- quint64 SaveEmbeddedCoverAsync(const QString &song_filename, const QImage &image);
- quint64 SaveEmbeddedCoverAsync(const QString &song_filename, const QByteArray &image_data);
- quint64 SaveEmbeddedCoverAsync(const QList &urls, const QString &cover_filename);
- quint64 SaveEmbeddedCoverAsync(const QList &urls, const QImage &image);
- quint64 SaveEmbeddedCoverAsync(const QList &urls, const QByteArray &image_data);
-
signals:
void ExitFinished();
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
- void SaveEmbeddedCoverAsyncFinished(const quint64 id, const bool success, const bool cleared);
- protected slots:
- void Exit();
- void ProcessTasks();
- void RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url);
+ private:
+ class Task {
+ public:
+ explicit Task() : id(0), success(false), art_embedded(false), art_unset(false), result_type(AlbumCoverLoaderResult::Type::None), art_updated(false), redirects(0) {}
- void SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QString &cover_filename);
- void SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QImage &image);
- void SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QByteArray &image_data);
- void SaveEmbeddedCover(const quint64 id, const QList &urls, const QImage &image);
- void SaveEmbeddedCover(const quint64 id, const QList &urls, const QString &cover_filename);
- void SaveEmbeddedCover(const quint64 id, const QList &urls, const QByteArray &image_data);
-
- void SaveEmbeddedArtFinished(const quint64 id, TagReaderReply *reply, const bool cleared);
-
- protected:
-
- struct Task {
- explicit Task() : id(0), state(State::None), type(AlbumCoverLoaderResult::Type::None), art_updated(false), redirects(0) {}
+ quint64 id;
+ bool success;
AlbumCoverLoaderOptions options;
- quint64 id;
+ bool raw_image_data() const { return options.options & AlbumCoverLoaderOptions::Option::RawImageData; }
+ bool original_image() const { return options.options & AlbumCoverLoaderOptions::Option::OriginalImage; }
+ bool scaled_image() const { return options.options & AlbumCoverLoaderOptions::Option::ScaledImage; }
+ bool pad_scaled_image() const { return options.options & AlbumCoverLoaderOptions::Option::PadScaledImage; }
+
+ bool art_embedded;
+ QUrl art_automatic;
+ QUrl art_manual;
+ bool art_unset;
+ QUrl song_url;
+ Song::Source song_source;
Song song;
AlbumCoverImageResult album_cover;
- State state;
- AlbumCoverLoaderResult::Type type;
+ AlbumCoverLoaderResult::Type result_type;
bool art_updated;
int redirects;
};
- using TaskPtr = std::shared_ptr;
-
- struct TryLoadResult {
- explicit TryLoadResult(const bool _started_async = false,
- const bool _loaded_success = false,
- const AlbumCoverLoaderResult::Type _type = AlbumCoverLoaderResult::Type::None,
- const AlbumCoverImageResult &_album_cover = AlbumCoverImageResult()) :
- started_async(_started_async),
- loaded_success(_loaded_success),
- type(_type),
- album_cover(_album_cover) {}
-
- bool started_async;
- bool loaded_success;
+ using TaskPtr = shared_ptr;
+ class LoadImageResult {
+ public:
+ enum class Status {
+ Failure,
+ Async,
+ Success
+ };
+ explicit LoadImageResult(AlbumCoverLoaderResult::Type _type = AlbumCoverLoaderResult::Type::None, Status _status = Status::Failure) : type(_type), status(_status) {}
AlbumCoverLoaderResult::Type type;
- AlbumCoverImageResult album_cover;
+ Status status;
};
+ private:
quint64 EnqueueTask(TaskPtr task);
void ProcessTask(TaskPtr task);
void NextState(TaskPtr task);
- TryLoadResult TryLoadImage(TaskPtr task);
+ void InitArt(TaskPtr task);
+ LoadImageResult LoadImage(TaskPtr task, const AlbumCoverLoaderOptions::Type &type);
+ LoadImageResult LoadEmbeddedImage(TaskPtr task);
+ LoadImageResult LoadUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url);
+ LoadImageResult LoadLocalUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url);
+ LoadImageResult LoadLocalFileImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QString &cover_file);
+ LoadImageResult LoadRemoteUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url);
+ void FinishTask(TaskPtr task, const AlbumCoverLoaderResult::Type result_type);
- NetworkAccessManager *network_;
-
- bool stop_requested_;
-
- QMutex mutex_load_image_async_;
- QMutex mutex_save_image_async_;
- QQueue tasks_;
- QHash remote_tasks_;
- quint64 load_image_async_id_;
- quint64 save_image_async_id_;
+ private slots:
+ void Exit();
+ void ProcessTasks();
+ void LoadRemoteImageFinished(QNetworkReply *reply, TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url);
+ private:
static const int kMaxRedirects = 3;
-
+ NetworkAccessManager *network_;
+ bool stop_requested_;
+ QMutex mutex_load_image_async_;
+ QQueue tasks_;
+ quint64 load_image_async_id_;
QThread *original_thread_;
-
- QMultiMap tagreader_save_embedded_art_requests_;
-
};
#endif // ALBUMCOVERLOADER_H
diff --git a/src/covermanager/albumcoverloaderoptions.cpp b/src/covermanager/albumcoverloaderoptions.cpp
new file mode 100644
index 000000000..d9a653435
--- /dev/null
+++ b/src/covermanager/albumcoverloaderoptions.cpp
@@ -0,0 +1,59 @@
+/*
+* Strawberry Music Player
+* Copyright 2018-2023, Jonas Kvinge
+*
+* 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 .
+*
+*/
+
+#include "albumcoverloaderoptions.h"
+
+#include
+
+#include "settings/coverssettingspage.h"
+
+AlbumCoverLoaderOptions::AlbumCoverLoaderOptions(const Options _options, const QSize _desired_scaled_size, const qreal _device_pixel_ratio, const Types _types)
+ : options(_options),
+ desired_scaled_size(_desired_scaled_size),
+ device_pixel_ratio(_device_pixel_ratio),
+ types(_types) {}
+
+AlbumCoverLoaderOptions::Types AlbumCoverLoaderOptions::LoadTypes() {
+
+ Types cover_types;
+
+ QSettings s;
+ s.beginGroup(CoversSettingsPage::kSettingsGroup);
+ const QStringList all_cover_types = QStringList() << "art_unset" << "art_embedded" << "art_manual" << "art_automatic";
+ const QStringList cover_types_strlist = s.value(CoversSettingsPage::kTypes, all_cover_types).toStringList();
+ for (const QString &cover_type_str : cover_types_strlist) {
+ if (cover_type_str == "art_unset") {
+ cover_types << AlbumCoverLoaderOptions::Type::Unset;
+ }
+ else if (cover_type_str == "art_embedded") {
+ cover_types << AlbumCoverLoaderOptions::Type::Embedded;
+ }
+ else if (cover_type_str == "art_manual") {
+ cover_types << AlbumCoverLoaderOptions::Type::Manual;
+ }
+ else if (cover_type_str == "art_automatic") {
+ cover_types << AlbumCoverLoaderOptions::Type::Automatic;
+ }
+ }
+
+ s.endGroup();
+
+ return cover_types;
+
+}
\ No newline at end of file
diff --git a/src/covermanager/albumcoverloaderoptions.h b/src/covermanager/albumcoverloaderoptions.h
index fae098f73..4cf131f53 100644
--- a/src/covermanager/albumcoverloaderoptions.h
+++ b/src/covermanager/albumcoverloaderoptions.h
@@ -1,8 +1,6 @@
/*
* Strawberry Music Player
- * This file was part of Clementine.
- * Copyright 2012, David Sansome
- * Copyright 2018-2021, Jonas Kvinge
+ * Copyright 2018-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,34 +20,40 @@
#ifndef ALBUMCOVERLOADEROPTIONS_H
#define ALBUMCOVERLOADEROPTIONS_H
-#include "config.h"
-
+#include
#include
#include
class AlbumCoverLoaderOptions {
public:
- explicit AlbumCoverLoaderOptions()
- : get_image_data_(true),
- get_image_(true),
- scale_output_image_(true),
- pad_output_image_(true),
- create_thumbnail_(false),
- pad_thumbnail_image_(false),
- desired_height_(120),
- thumbnail_size_(120, 120) {}
+ enum class Option {
+ NoOptions = 0x0,
+ RawImageData = 0x2,
+ OriginalImage = 0x4,
+ ScaledImage = 0x8,
+ PadScaledImage = 0x16
+ };
+ Q_DECLARE_FLAGS(Options, Option)
- bool get_image_data_;
- bool get_image_;
- bool scale_output_image_;
- bool pad_output_image_;
- bool create_thumbnail_;
- bool pad_thumbnail_image_;
- int desired_height_;
- QSize thumbnail_size_;
- QImage default_output_image_;
- QImage default_scaled_image_;
- QImage default_thumbnail_image_;
+ enum class Type {
+ Embedded,
+ Automatic,
+ Manual,
+ Unset
+ };
+ using Types = QList;
+
+ explicit AlbumCoverLoaderOptions(const Options _options = AlbumCoverLoaderOptions::Option::ScaledImage, const QSize _desired_scaled_size = QSize(32, 32), const qreal device_pixel_ratio = 1.0F, const Types _types = QList() << AlbumCoverLoaderOptions::Type::Embedded << AlbumCoverLoaderOptions::Type::Automatic << AlbumCoverLoaderOptions::Type::Manual);
+
+ Options options;
+ QSize desired_scaled_size;
+ qreal device_pixel_ratio;
+ Types types;
+ QString default_cover;
+
+ static Types LoadTypes();
};
+Q_DECLARE_OPERATORS_FOR_FLAGS(AlbumCoverLoaderOptions::Options)
+
#endif // ALBUMCOVERLOADEROPTIONS_H
diff --git a/src/covermanager/albumcoverloaderresult.h b/src/covermanager/albumcoverloaderresult.h
index 0e09afec6..ffd623f20 100644
--- a/src/covermanager/albumcoverloaderresult.h
+++ b/src/covermanager/albumcoverloaderresult.h
@@ -32,35 +32,33 @@ class AlbumCoverLoaderResult {
enum class Type {
None,
- ManuallyUnset,
+ Unset,
Embedded,
Automatic,
- Manual,
- Remote
+ Manual
};
explicit AlbumCoverLoaderResult(const bool _success = false,
const Type _type = Type::None,
AlbumCoverImageResult _album_cover = AlbumCoverImageResult(),
const QImage &_image_scaled = QImage(),
- const QImage &_image_thumbnail = QImage(),
+ const bool _remote_cover = false,
const bool _updated = false) :
success(_success),
type(_type),
album_cover(_album_cover),
image_scaled(_image_scaled),
- image_thumbnail(_image_thumbnail),
+ remote_cover(_remote_cover),
updated(_updated) {}
bool success;
Type type;
AlbumCoverImageResult album_cover;
QImage image_scaled;
- QImage image_thumbnail;
+ bool remote_cover;
bool updated;
QUrl temp_cover_url;
-
};
Q_DECLARE_METATYPE(AlbumCoverLoaderResult)
diff --git a/src/covermanager/albumcovermanager.cpp b/src/covermanager/albumcovermanager.cpp
index 24f8f2435..864242f23 100644
--- a/src/covermanager/albumcovermanager.cpp
+++ b/src/covermanager/albumcovermanager.cpp
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
- * Copyright 2018-2021, Jonas Kvinge
+ * Copyright 2018-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -95,6 +95,7 @@
#include "ui_albumcovermanager.h"
const char *AlbumCoverManager::kSettingsGroup = "CoverManager";
+constexpr int AlbumCoverManager::kThumbnailSize = 120;
AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QMainWindow *mainwindow, QWidget *parent)
: QMainWindow(parent),
@@ -112,7 +113,7 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
cover_exporter_(new AlbumCoverExporter(this)),
artist_icon_(IconLoader::Load("folder-sound")),
all_artists_icon_(IconLoader::Load("library-music")),
- image_nocover_thumbnail_(ImageUtils::GenerateNoCoverImage(QSize(120 * devicePixelRatio(), 120 * devicePixelRatio()))),
+ image_nocover_thumbnail_(ImageUtils::GenerateNoCoverImage(QSize(120, 120), devicePixelRatio())),
icon_nocover_item_(QPixmap::fromImage(image_nocover_thumbnail_)),
context_menu_(new QMenu(this)),
progress_bar_(new QProgressBar(this)),
@@ -150,11 +151,6 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
QShortcut *close = new QShortcut(QKeySequence::Close, this);
QObject::connect(close, &QShortcut::activated, this, &AlbumCoverManager::close);
- cover_loader_options_.scale_output_image_ = true;
- cover_loader_options_.pad_output_image_ = true;
- cover_loader_options_.desired_height_ = 120 * devicePixelRatio();
- cover_loader_options_.create_thumbnail_ = false;
-
EnableCoversButtons();
}
@@ -236,7 +232,6 @@ void AlbumCoverManager::Init() {
s.endGroup();
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &AlbumCoverManager::AlbumCoverLoaded);
- QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::SaveEmbeddedCoverAsyncFinished, this, &AlbumCoverManager::SaveEmbeddedCoverAsyncFinished);
cover_searcher_->Init(cover_fetcher_);
@@ -248,6 +243,7 @@ void AlbumCoverManager::showEvent(QShowEvent *e) {
if (!e->spontaneous()) {
LoadGeometry();
+ cover_types_ = AlbumCoverLoaderOptions::LoadTypes();
album_cover_choice_controller_->ReloadSettings();
Reset();
}
@@ -321,7 +317,6 @@ void AlbumCoverManager::CancelRequests() {
#endif
cover_loading_tasks_.clear();
cover_save_tasks_.clear();
- cover_save_tasks2_.clear();
cover_exporter_->Cancel();
@@ -390,41 +385,43 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
// Sort by album name. The list is already sorted by sqlite but it was done case sensitively.
std::stable_sort(albums.begin(), albums.end(), CompareAlbumNameNocase);
- for (const CollectionBackend::Album &info : albums) {
+ for (const CollectionBackend::Album &album_info : albums) {
// Don't show songs without an album, obviously
- if (info.album.isEmpty()) continue;
+ if (album_info.album.isEmpty()) continue;
QString display_text;
if (current->type() == Specific_Artist) {
- display_text = info.album;
+ display_text = album_info.album;
}
else {
- display_text = info.album_artist + " - " + info.album;
+ display_text = album_info.album_artist + " - " + album_info.album;
}
- AlbumItem *item = new AlbumItem(icon_nocover_item_, display_text, ui_->albums);
- item->setData(Role_AlbumArtist, info.album_artist);
- item->setData(Role_Album, info.album);
- item->setData(Role_Filetype, QVariant::fromValue(info.filetype));
- item->setData(Role_CuePath, info.cue_path);
- item->setData(Qt::TextAlignmentRole, QVariant(Qt::AlignTop | Qt::AlignHCenter));
- item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
- item->urls = info.urls;
+ AlbumItem *album_item = new AlbumItem(icon_nocover_item_, display_text, ui_->albums);
+ album_item->setData(Role_AlbumArtist, album_info.album_artist);
+ album_item->setData(Role_Album, album_info.album);
+ album_item->setData(Role_Filetype, QVariant::fromValue(album_info.filetype));
+ album_item->setData(Role_CuePath, album_info.cue_path);
+ album_item->setData(Qt::TextAlignmentRole, QVariant(Qt::AlignTop | Qt::AlignHCenter));
+ album_item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
+ album_item->urls = album_info.urls;
- if (info.album_artist.isEmpty()) {
- item->setToolTip(info.album);
+ if (album_info.album_artist.isEmpty()) {
+ album_item->setToolTip(album_info.album);
}
else {
- item->setToolTip(info.album_artist + " - " + info.album);
+ album_item->setToolTip(album_info.album_artist + " - " + album_info.album);
}
- if (!info.art_automatic.isEmpty() || !info.art_manual.isEmpty()) {
- item->setData(Role_PathAutomatic, info.art_automatic);
- item->setData(Role_PathManual, info.art_manual);
- quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, info.art_automatic, info.art_manual, info.urls.first());
- cover_loading_tasks_[id] = item;
+ album_item->setData(Role_ArtEmbedded, album_info.art_embedded);
+ album_item->setData(Role_ArtAutomatic, album_info.art_automatic);
+ album_item->setData(Role_ArtManual, album_info.art_manual);
+ album_item->setData(Role_ArtUnset, album_info.art_unset);
+
+ if (album_info.art_embedded || !album_info.art_automatic.isEmpty() || !album_info.art_manual.isEmpty()) {
+ LoadAlbumCoverAsync(album_item);
}
}
@@ -437,18 +434,15 @@ void AlbumCoverManager::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoade
if (!cover_loading_tasks_.contains(id)) return;
- AlbumItem *item = cover_loading_tasks_.take(id);
+ AlbumItem *album_item = cover_loading_tasks_.take(id);
- if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type::ManuallyUnset) {
- item->setIcon(icon_nocover_item_);
+ if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type::Unset) {
+ album_item->setIcon(icon_nocover_item_);
}
else {
- item->setIcon(QPixmap::fromImage(result.image_scaled));
+ album_item->setIcon(QPixmap::fromImage(result.image_scaled));
}
- //item->setData(Role_Image, result.image_original);
- //item->setData(Role_ImageData, result.image_data);
-
UpdateFilter();
}
@@ -471,13 +465,13 @@ void AlbumCoverManager::UpdateFilter() {
qint32 without_cover = 0;
for (int i = 0; i < ui_->albums->count(); ++i) {
- AlbumItem *item = static_cast(ui_->albums->item(i));
- bool should_hide = ShouldHide(*item, filter, hide_covers);
- item->setHidden(should_hide);
+ AlbumItem *album_item = static_cast(ui_->albums->item(i));
+ bool should_hide = ShouldHide(*album_item, filter, hide_covers);
+ album_item->setHidden(should_hide);
if (!should_hide) {
++total_count;
- if (!ItemHasCover(*item)) {
+ if (!ItemHasCover(*album_item)) {
++without_cover;
}
}
@@ -488,9 +482,9 @@ void AlbumCoverManager::UpdateFilter() {
}
-bool AlbumCoverManager::ShouldHide(const AlbumItem &item, const QString &filter, const HideCovers hide_covers) const {
+bool AlbumCoverManager::ShouldHide(const AlbumItem &album_item, const QString &filter, const HideCovers hide_covers) const {
- bool has_cover = ItemHasCover(item);
+ bool has_cover = ItemHasCover(album_item);
if (hide_covers == HideCovers::WithCovers && has_cover) {
return true;
}
@@ -504,8 +498,8 @@ bool AlbumCoverManager::ShouldHide(const AlbumItem &item, const QString &filter,
QStringList query = filter.split(' ');
for (const QString &s : query) {
- bool in_text = item.text().contains(s, Qt::CaseInsensitive);
- bool in_albumartist = item.data(Role_AlbumArtist).toString().contains(s, Qt::CaseInsensitive);
+ bool in_text = album_item.text().contains(s, Qt::CaseInsensitive);
+ bool in_albumartist = album_item.data(Role_AlbumArtist).toString().contains(s, Qt::CaseInsensitive);
if (!in_text && !in_albumartist) {
return true;
}
@@ -518,12 +512,12 @@ bool AlbumCoverManager::ShouldHide(const AlbumItem &item, const QString &filter,
void AlbumCoverManager::FetchAlbumCovers() {
for (int i = 0; i < ui_->albums->count(); ++i) {
- AlbumItem *item = static_cast(ui_->albums->item(i));
- if (item->isHidden()) continue;
- if (ItemHasCover(*item)) continue;
+ AlbumItem *album_item = static_cast(ui_->albums->item(i));
+ if (album_item->isHidden()) continue;
+ if (ItemHasCover(*album_item)) continue;
- quint64 id = cover_fetcher_->FetchAlbumCover(item->data(Role_AlbumArtist).toString(), item->data(Role_Album).toString(), QString(), true);
- cover_fetching_tasks_[id] = item;
+ quint64 id = cover_fetcher_->FetchAlbumCover(album_item->data(Role_AlbumArtist).toString(), album_item->data(Role_Album).toString(), QString(), true);
+ cover_fetching_tasks_[id] = album_item;
jobs_++;
}
@@ -541,9 +535,9 @@ void AlbumCoverManager::AlbumCoverFetched(const quint64 id, const AlbumCoverImag
if (!cover_fetching_tasks_.contains(id)) return;
- AlbumItem *item = cover_fetching_tasks_.take(id);
+ AlbumItem *album_item = cover_fetching_tasks_.take(id);
if (!result.image.isNull()) {
- SaveAndSetCover(item, result);
+ SaveAndSetCover(album_item, result);
}
if (cover_fetching_tasks_.isEmpty()) {
@@ -593,13 +587,13 @@ bool AlbumCoverManager::eventFilter(QObject *obj, QEvent *e) {
bool some_unset = false;
bool some_clear = false;
- for (QListWidgetItem *item : context_menu_items_) {
- AlbumItem *album_item = static_cast(item);
+ for (QListWidgetItem *list_widget_item : context_menu_items_) {
+ AlbumItem *album_item = static_cast(list_widget_item);
if (ItemHasCover(*album_item)) some_with_covers = true;
- if (album_item->data(Role_PathManual).toUrl().path() == Song::kManuallyUnsetCover) {
+ if (album_item->data(Role_ArtUnset).toBool()) {
some_unset = true;
}
- else if (album_item->data(Role_PathAutomatic).toUrl().isEmpty() && album_item->data(Role_PathManual).toUrl().isEmpty()) {
+ else if (!album_item->data(Role_ArtEmbedded).toBool() && album_item->data(Role_ArtAutomatic).toUrl().isEmpty() && album_item->data(Role_ArtManual).toUrl().isEmpty()) {
some_clear = true;
}
}
@@ -623,19 +617,19 @@ bool AlbumCoverManager::eventFilter(QObject *obj, QEvent *e) {
}
Song AlbumCoverManager::GetSingleSelectionAsSong() {
- return context_menu_items_.size() != 1 ? Song() : ItemAsSong(context_menu_items_[0]);
+ return context_menu_items_.size() != 1 ? Song() : AlbumItemAsSong(context_menu_items_[0]);
}
Song AlbumCoverManager::GetFirstSelectedAsSong() {
- return context_menu_items_.isEmpty() ? Song() : ItemAsSong(context_menu_items_[0]);
+ return context_menu_items_.isEmpty() ? Song() : AlbumItemAsSong(context_menu_items_[0]);
}
-Song AlbumCoverManager::ItemAsSong(AlbumItem *item) {
+Song AlbumCoverManager::AlbumItemAsSong(AlbumItem *album_item) {
Song result(Song::Source::Collection);
- QString title = item->data(Role_Album).toString();
- QString artist_name = item->data(Role_AlbumArtist).toString();
+ QString title = album_item->data(Role_Album).toString();
+ QString artist_name = album_item->data(Role_AlbumArtist).toString();
if (!artist_name.isEmpty()) {
result.set_title(artist_name + " - " + title);
}
@@ -643,27 +637,30 @@ Song AlbumCoverManager::ItemAsSong(AlbumItem *item) {
result.set_title(title);
}
- result.set_artist(item->data(Role_AlbumArtist).toString());
- result.set_albumartist(item->data(Role_AlbumArtist).toString());
- result.set_album(item->data(Role_Album).toString());
+ result.set_artist(album_item->data(Role_AlbumArtist).toString());
+ result.set_albumartist(album_item->data(Role_AlbumArtist).toString());
+ result.set_album(album_item->data(Role_Album).toString());
- result.set_filetype(static_cast(item->data(Role_Filetype).toInt()));
- result.set_url(item->urls.first());
- result.set_cue_path(item->data(Role_CuePath).toString());
+ result.set_filetype(static_cast(album_item->data(Role_Filetype).toInt()));
+ result.set_url(album_item->urls.first());
+ result.set_cue_path(album_item->data(Role_CuePath).toString());
- result.set_art_automatic(item->data(Role_PathAutomatic).toUrl());
- result.set_art_manual(item->data(Role_PathManual).toUrl());
+ result.set_art_embedded(album_item->data(Role_ArtEmbedded).toBool());
+ result.set_art_automatic(album_item->data(Role_ArtAutomatic).toUrl());
+ result.set_art_manual(album_item->data(Role_ArtManual).toUrl());
+ result.set_art_unset(album_item->data(Role_ArtUnset).toBool());
// force validity
result.set_valid(true);
result.set_id(0);
return result;
+
}
void AlbumCoverManager::ShowCover() {
- Song song = GetSingleSelectionAsSong();
+ const Song song = GetSingleSelectionAsSong();
if (!song.is_valid()) return;
album_cover_choice_controller_->ShowCover(song);
@@ -672,8 +669,8 @@ void AlbumCoverManager::ShowCover() {
void AlbumCoverManager::FetchSingleCover() {
- for (QListWidgetItem *item : context_menu_items_) {
- AlbumItem *album_item = static_cast(item);
+ for (QListWidgetItem *list_widget_item : context_menu_items_) {
+ AlbumItem *album_item = static_cast(list_widget_item);
quint64 id = cover_fetcher_->FetchAlbumCover(album_item->data(Role_AlbumArtist).toString(), album_item->data(Role_Album).toString(), QString(), false);
cover_fetching_tasks_[id] = album_item;
jobs_++;
@@ -686,11 +683,11 @@ void AlbumCoverManager::FetchSingleCover() {
}
-void AlbumCoverManager::UpdateCoverInList(AlbumItem *item, const QUrl &cover_url) {
+void AlbumCoverManager::UpdateCoverInList(AlbumItem *album_item, const QUrl &cover_url) {
- quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QUrl(), cover_url);
- item->setData(Role_PathManual, cover_url);
- cover_loading_tasks_[id] = item;
+ album_item->setData(Role_ArtManual, cover_url);
+ album_item->setData(Role_ArtUnset, false);
+ LoadAlbumCoverAsync(album_item);
}
@@ -709,26 +706,31 @@ void AlbumCoverManager::LoadCoverFromFile() {
void AlbumCoverManager::SaveCoverToFile() {
Song song = GetSingleSelectionAsSong();
- if (!song.is_valid() || song.has_manually_unset_cover()) return;
-
- AlbumCoverImageResult result;
+ if (!song.is_valid() || song.art_unset()) return;
// Load the image from disk
-
- if (!song.art_manual().isEmpty() && !song.has_manually_unset_cover() && song.art_manual().isLocalFile() && QFile::exists(song.art_manual().toLocalFile())) {
- result.image_data = Utilities::ReadDataFromFile(song.art_manual().toLocalFile());
- }
- else if (!song.art_manual().isEmpty() && !song.art_manual().path().isEmpty() && song.art_manual().scheme().isEmpty() && QFile::exists(song.art_manual().path())) {
- result.image_data = Utilities::ReadDataFromFile(song.art_manual().path());
- }
- else if (song.has_embedded_cover()) {
- result.image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song.url().toLocalFile());
- }
- else if (!song.art_automatic().isEmpty() && song.art_automatic().isLocalFile() && QFile::exists(song.art_automatic().toLocalFile())) {
- result.image_data = Utilities::ReadDataFromFile(song.art_automatic().toLocalFile());
- }
- else if (!song.art_automatic().isEmpty() && !song.art_automatic().path().isEmpty() && song.art_automatic().scheme().isEmpty() && QFile::exists(song.art_automatic().path())) {
- result.image_data = Utilities::ReadDataFromFile(song.art_automatic().path());
+ AlbumCoverImageResult result;
+ for (const AlbumCoverLoaderOptions::Type cover_type : cover_types_) {
+ switch (cover_type) {
+ case AlbumCoverLoaderOptions::Type::Unset:
+ return;
+ case AlbumCoverLoaderOptions::Type::Embedded:
+ if (song.art_embedded()) {
+ result.image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song.url().toLocalFile());
+ }
+ break;
+ case AlbumCoverLoaderOptions::Type::Automatic:
+ if (song.art_automatic_is_valid()) {
+ result.image_data = Utilities::ReadDataFromFile(song.art_automatic().toLocalFile());
+ }
+ break;
+ case AlbumCoverLoaderOptions::Type::Manual:
+ if (song.art_manual_is_valid()) {
+ result.image_data = Utilities::ReadDataFromFile(song.art_manual().toLocalFile());
+ }
+ break;
+ }
+ if (result.is_valid()) break;
}
if (!result.is_valid()) return;
@@ -778,24 +780,32 @@ void AlbumCoverManager::SaveImageToAlbums(Song *song, const AlbumCoverImageResul
}
break;
case CoverOptions::CoverType::Embedded:
- cover_url = QUrl::fromLocalFile(Song::kEmbeddedCover);
+ cover_url.clear();
break;
}
// Force the found cover on all of the selected items
QList urls;
QList album_items;
- for (QListWidgetItem *item : context_menu_items_) {
- AlbumItem *album_item = static_cast(item);
+ for (QListWidgetItem *list_widget_item : context_menu_items_) {
+ AlbumItem *album_item = static_cast(list_widget_item);
switch (album_cover_choice_controller_->get_save_album_cover_type()) {
case CoverOptions::CoverType::Cache:
case CoverOptions::CoverType::Album:{
- Song current_song = ItemAsSong(album_item);
+ Song current_song = AlbumItemAsSong(album_item);
album_cover_choice_controller_->SaveArtManualToSong(¤t_song, cover_url);
UpdateCoverInList(album_item, cover_url);
break;
}
case CoverOptions::CoverType::Embedded:{
+ for (const QUrl &url : album_item->urls) {
+ const bool art_embedded = !result.image_data.isEmpty();
+ TagReaderReply *reply = app_->tag_reader_client()->SaveEmbeddedArt(url.toLocalFile(), TagReaderClient::SaveCoverOptions(result.image_data, result.mime_type));
+ QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, album_item, url, art_embedded]() {
+ SaveEmbeddedCoverFinished(reply, album_item, url, art_embedded);
+ });
+ cover_save_tasks_.insert(album_item, url);
+ }
urls << album_item->urls;
album_items << album_item;
break;
@@ -803,78 +813,53 @@ void AlbumCoverManager::SaveImageToAlbums(Song *song, const AlbumCoverImageResul
}
}
- if (album_cover_choice_controller_->get_save_album_cover_type() == CoverOptions::CoverType::Embedded && !urls.isEmpty()) {
- quint64 id = -1;
- if (result.is_jpeg()) {
- id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image_data);
- }
- else {
- id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image);
- }
- for (AlbumItem *album_item : album_items) {
- cover_save_tasks_.insert(id, album_item);
- }
- }
-
}
void AlbumCoverManager::UnsetCover() {
- Song song = GetFirstSelectedAsSong();
- if (!song.is_valid()) return;
-
- AlbumItem *first_album_item = static_cast(context_menu_items_[0]);
-
- QUrl cover_url = album_cover_choice_controller_->UnsetCover(&song);
+ if (context_menu_items_.isEmpty()) return;
// Force the 'none' cover on all of the selected items
- for (QListWidgetItem *item : context_menu_items_) {
- AlbumItem *album_item = static_cast(item);
+ for (QListWidgetItem *list_widget_item : context_menu_items_) {
+ AlbumItem *album_item = static_cast(list_widget_item);
album_item->setIcon(icon_nocover_item_);
- album_item->setData(Role_PathManual, cover_url);
+ album_item->setData(Role_ArtManual, QUrl());
+ album_item->setData(Role_ArtUnset, true);
- // Don't save the first one twice
- if (album_item != first_album_item) {
- Song current_song = ItemAsSong(album_item);
- album_cover_choice_controller_->SaveArtManualToSong(¤t_song, cover_url);
- }
+ Song current_song = AlbumItemAsSong(album_item);
+ album_cover_choice_controller_->UnsetAlbumCoverForSong(¤t_song);
}
}
void AlbumCoverManager::ClearCover() {
- Song song = GetFirstSelectedAsSong();
- if (!song.is_valid()) return;
-
- AlbumItem *first_album_item = static_cast(context_menu_items_[0]);
-
- album_cover_choice_controller_->ClearCover(&song);
+ if (context_menu_items_.isEmpty()) return;
// Force the 'none' cover on all of the selected items
- for (QListWidgetItem *item : context_menu_items_) {
- AlbumItem *album_item = static_cast(item);
+ for (QListWidgetItem *list_widget_item : context_menu_items_) {
+ AlbumItem *album_item = static_cast(list_widget_item);
album_item->setIcon(icon_nocover_item_);
- album_item->setData(Role_PathManual, QUrl());
+ album_item->setData(Role_ArtEmbedded, false);
+ album_item->setData(Role_ArtAutomatic, QUrl());
+ album_item->setData(Role_ArtManual, QUrl());
- // Don't save the first one twice
- if (album_item != first_album_item) {
- Song current_song = ItemAsSong(album_item);
- album_cover_choice_controller_->SaveArtManualToSong(¤t_song, QUrl(), false);
- }
+ Song current_song = AlbumItemAsSong(album_item);
+ album_cover_choice_controller_->ClearAlbumCoverForSong(¤t_song);
}
}
void AlbumCoverManager::DeleteCover() {
- for (QListWidgetItem *item : context_menu_items_) {
- AlbumItem *album_item = static_cast(item);
- Song song = ItemAsSong(album_item);
+ for (QListWidgetItem *list_widget_item : context_menu_items_) {
+ AlbumItem *album_item = static_cast(list_widget_item);
+ Song song = AlbumItemAsSong(album_item);
album_cover_choice_controller_->DeleteCover(&song);
album_item->setIcon(icon_nocover_item_);
- album_item->setData(Role_PathManual, QUrl());
- album_item->setData(Role_PathAutomatic, QUrl());
+ album_item->setData(Role_ArtEmbedded, QUrl());
+ album_item->setData(Role_ArtManual, QUrl());
+ album_item->setData(Role_ArtAutomatic, QUrl());
}
}
@@ -933,9 +918,9 @@ SongMimeData *AlbumCoverManager::GetMimeDataForAlbums(const QModelIndexList &ind
void AlbumCoverManager::AlbumDoubleClicked(const QModelIndex &idx) {
- AlbumItem *item = static_cast(idx.internalPointer());
- if (!item) return;
- album_cover_choice_controller_->ShowCover(ItemAsSong(item));
+ AlbumItem *album_item = static_cast(idx.internalPointer());
+ if (!album_item) return;
+ album_cover_choice_controller_->ShowCover(AlbumItemAsSong(album_item));
}
@@ -953,29 +938,25 @@ void AlbumCoverManager::LoadSelectedToPlaylist() {
}
-void AlbumCoverManager::SaveAndSetCover(AlbumItem *item, const AlbumCoverImageResult &result) {
+void AlbumCoverManager::SaveAndSetCover(AlbumItem *album_item, const AlbumCoverImageResult &result) {
- const QString albumartist = item->data(Role_AlbumArtist).toString();
- const QString album = item->data(Role_Album).toString();
- const QList &urls = item->urls;
- const Song::FileType filetype = static_cast(item->data(Role_Filetype).toInt());
- const bool has_cue = !item->data(Role_CuePath).toString().isEmpty();
+ const QList &urls = album_item->urls;
+ const Song::FileType filetype = static_cast(album_item->data(Role_Filetype).toInt());
+ const bool has_cue = !album_item->data(Role_CuePath).toString().isEmpty();
if (album_cover_choice_controller_->get_save_album_cover_type() == CoverOptions::CoverType::Embedded && Song::save_embedded_cover_supported(filetype) && !has_cue) {
- if (result.is_jpeg()) {
- quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image_data);
- cover_save_tasks_.insert(id, item);
- }
- else if (!result.image.isNull()) {
- quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image);
- cover_save_tasks_.insert(id, item);
- }
- else if (!result.cover_url.isEmpty() && result.cover_url.isLocalFile()) {
- quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.cover_url.toLocalFile());
- cover_save_tasks_.insert(id, item);
+ for (const QUrl &url : urls) {
+ const bool art_embedded = !result.image_data.isEmpty();
+ TagReaderReply *reply = app_->tag_reader_client()->SaveEmbeddedArt(url.toLocalFile(), TagReaderClient::SaveCoverOptions(result.cover_url.isValid() ? result.cover_url.toLocalFile() : QString(), result.image_data, result.mime_type));
+ QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, album_item, url, art_embedded]() {
+ SaveEmbeddedCoverFinished(reply, album_item, url, art_embedded);
+ });
+ cover_save_tasks_.insert(album_item, url);
}
}
else {
+ const QString albumartist = album_item->data(Role_AlbumArtist).toString();
+ const QString album = album_item->data(Role_Album).toString();
QUrl cover_url;
if (!result.cover_url.isEmpty() && result.cover_url.isValid() && result.cover_url.isLocalFile()) {
cover_url = result.cover_url;
@@ -990,7 +971,7 @@ void AlbumCoverManager::SaveAndSetCover(AlbumItem *item, const AlbumCoverImageRe
collection_backend_->UpdateManualAlbumArtAsync(albumartist, album, cover_url);
// Update the icon in our list
- UpdateCoverInList(item, cover_url);
+ UpdateCoverInList(album_item, cover_url);
}
}
@@ -1006,16 +987,17 @@ void AlbumCoverManager::ExportCovers() {
DisableCoversButtons();
cover_exporter_->SetDialogResult(result);
+ cover_exporter_->SetCoverTypes(cover_types_);
for (int i = 0; i < ui_->albums->count(); ++i) {
- AlbumItem *item = static_cast(ui_->albums->item(i));
+ AlbumItem *album_item = static_cast(ui_->albums->item(i));
// skip hidden and coverless albums
- if (item->isHidden() || !ItemHasCover(*item)) {
+ if (album_item->isHidden() || !ItemHasCover(*album_item)) {
continue;
}
- cover_exporter_->AddExportRequest(ItemAsSong(item));
+ cover_exporter_->AddExportRequest(AlbumItemAsSong(album_item));
}
if (cover_exporter_->request_count() > 0) {
@@ -1060,20 +1042,40 @@ void AlbumCoverManager::UpdateExportStatus(const int exported, const int skipped
}
-bool AlbumCoverManager::ItemHasCover(const AlbumItem &item) const {
- return item.icon().cacheKey() != icon_nocover_item_.cacheKey();
+bool AlbumCoverManager::ItemHasCover(const AlbumItem &album_item) const {
+ return album_item.icon().cacheKey() != icon_nocover_item_.cacheKey();
}
-void AlbumCoverManager::SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success) {
+void AlbumCoverManager::SaveEmbeddedCoverFinished(TagReaderReply *reply, AlbumItem *album_item, const QUrl &url, const bool art_embedded) {
- while (cover_save_tasks_.contains(id)) {
- AlbumItem *album_item = cover_save_tasks_.take(id);
- if (!success) continue;
- album_item->setData(Role_PathAutomatic, QUrl::fromLocalFile(Song::kEmbeddedCover));
- Song song = ItemAsSong(album_item);
- album_cover_choice_controller_->SaveArtAutomaticToSong(&song, QUrl::fromLocalFile(Song::kEmbeddedCover));
- quint64 cover_load_id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, album_item->data(Role_PathAutomatic).toUrl(), album_item->data(Role_PathManual).toUrl(), album_item->urls.first());
- cover_loading_tasks_[cover_load_id] = album_item;
+ if (cover_save_tasks_.contains(album_item, url)) {
+ cover_save_tasks_.remove(album_item, url);
}
+ if (!reply->is_successful()) {
+ emit Error(tr("Could not save cover to file %1.").arg(url.toLocalFile()));
+ return;
+ }
+
+ if (cover_save_tasks_.contains(album_item)) {
+ return;
+ }
+
+ album_item->setData(Role_ArtEmbedded, true);
+ album_item->setData(Role_ArtUnset, false);
+ Song song = AlbumItemAsSong(album_item);
+ album_cover_choice_controller_->SaveArtEmbeddedToSong(&song, art_embedded);
+ LoadAlbumCoverAsync(album_item);
+
+}
+
+void AlbumCoverManager::LoadAlbumCoverAsync(AlbumItem *album_item) {
+
+ AlbumCoverLoaderOptions cover_options(AlbumCoverLoaderOptions::Option::ScaledImage | AlbumCoverLoaderOptions::Option::PadScaledImage);
+ cover_options.types = cover_types_;
+ cover_options.desired_scaled_size = QSize(kThumbnailSize, kThumbnailSize);
+ cover_options.device_pixel_ratio = devicePixelRatioF();
+ quint64 cover_load_id = app_->album_cover_loader()->LoadImageAsync(cover_options, album_item->data(Role_ArtEmbedded).toBool(), album_item->data(Role_ArtAutomatic).toUrl(), album_item->data(Role_ArtManual).toUrl(), album_item->data(Role_ArtUnset).toBool(), album_item->urls.first());
+ cover_loading_tasks_.insert(cover_load_id, album_item);
+
}
diff --git a/src/covermanager/albumcovermanager.h b/src/covermanager/albumcovermanager.h
index 267137c18..205cce469 100644
--- a/src/covermanager/albumcovermanager.h
+++ b/src/covermanager/albumcovermanager.h
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
- * Copyright 2018-2021, Jonas Kvinge
+ * Copyright 2018-2023, Jonas Kvinge
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -37,6 +37,7 @@
#include
#include "core/song.h"
+#include "core/tagreaderclient.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h"
#include "albumcoverchoicecontroller.h"
@@ -79,8 +80,6 @@ class AlbumCoverManager : public QMainWindow {
explicit AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QMainWindow *mainwindow, QWidget *parent = nullptr);
~AlbumCoverManager() override;
- static const char *kSettingsGroup;
-
void Reset();
void Init();
@@ -108,8 +107,10 @@ class AlbumCoverManager : public QMainWindow {
enum Role {
Role_AlbumArtist = Qt::UserRole + 1,
Role_Album,
- Role_PathAutomatic,
- Role_PathManual,
+ Role_ArtEmbedded,
+ Role_ArtAutomatic,
+ Role_ArtManual,
+ Role_ArtUnset,
Role_Filetype,
Role_CuePath,
Role_ImageData,
@@ -132,19 +133,21 @@ class AlbumCoverManager : public QMainWindow {
// Returns the first of the selected elements in form of a Song ready to be used by AlbumCoverChoiceController or invalid song if there's nothing selected.
Song GetFirstSelectedAsSong();
- Song ItemAsSong(QListWidgetItem *item) { return ItemAsSong(static_cast(item)); }
- static Song ItemAsSong(AlbumItem *item);
+ Song AlbumItemAsSong(QListWidgetItem *list_widget_item) { return AlbumItemAsSong(static_cast(list_widget_item)); }
+ static Song AlbumItemAsSong(AlbumItem *album_item);
void UpdateStatusText();
- bool ShouldHide(const AlbumItem &item, const QString &filter, const HideCovers hide_covers) const;
- void SaveAndSetCover(AlbumItem *item, const AlbumCoverImageResult &result);
+ bool ShouldHide(const AlbumItem &album_item, const QString &filter, const HideCovers hide_covers) const;
+ void SaveAndSetCover(AlbumItem *album_item, const AlbumCoverImageResult &result);
void SaveImageToAlbums(Song *song, const AlbumCoverImageResult &result);
SongList GetSongsInAlbums(const QModelIndexList &indexes) const;
SongMimeData *GetMimeDataForAlbums(const QModelIndexList &indexes) const;
- bool ItemHasCover(const AlbumItem &item) const;
+ bool ItemHasCover(const AlbumItem &album_item) const;
+
+ void LoadAlbumCoverAsync(AlbumItem *album_item);
signals:
void Error(const QString &error);
@@ -176,12 +179,15 @@ class AlbumCoverManager : public QMainWindow {
void AddSelectedToPlaylist();
void LoadSelectedToPlaylist();
- void UpdateCoverInList(AlbumItem *item, const QUrl &cover);
+ void UpdateCoverInList(AlbumItem *album_item, const QUrl &cover);
void UpdateExportStatus(const int exported, const int skipped, const int max);
- void SaveEmbeddedCoverAsyncFinished(quint64 id, const bool success);
+ void SaveEmbeddedCoverFinished(TagReaderReply *reply, AlbumItem *album_item, const QUrl &url, const bool art_embedded);
private:
+ static const char *kSettingsGroup;
+ static const int kThumbnailSize;
+
Ui_CoverManager *ui_;
QMainWindow *mainwindow_;
Application *app_;
@@ -192,7 +198,6 @@ class AlbumCoverManager : public QMainWindow {
QAction *filter_with_covers_;
QAction *filter_without_covers_;
- AlbumCoverLoaderOptions cover_loader_options_;
QMap cover_loading_tasks_;
AlbumCoverFetcher *cover_fetcher_;
@@ -215,11 +220,11 @@ class AlbumCoverManager : public QMainWindow {
QPushButton *abort_progress_;
int jobs_;
- QMultiMap cover_save_tasks_;
- QList cover_save_tasks2_;
+ QMultiMap cover_save_tasks_;
QListWidgetItem *all_artists_;
+ AlbumCoverLoaderOptions::Types cover_types_;
};
#endif // ALBUMCOVERMANAGER_H
diff --git a/src/covermanager/albumcoversearcher.cpp b/src/covermanager/albumcoversearcher.cpp
index 347d04a18..d064cd6bb 100644
--- a/src/covermanager/albumcoversearcher.cpp
+++ b/src/covermanager/albumcoversearcher.cpp
@@ -128,14 +128,6 @@ AlbumCoverSearcher::AlbumCoverSearcher(const QIcon &no_cover_icon, Application *
ui_->covers->setItemDelegate(new SizeOverlayDelegate(this));
ui_->covers->setModel(model_);
- options_.get_image_data_ = true;
- options_.get_image_ = true;
- options_.scale_output_image_ = false;
- options_.pad_output_image_ = false;
- options_.create_thumbnail_ = true;
- options_.pad_thumbnail_image_ = true;
- options_.thumbnail_size_ = ui_->covers->iconSize();
-
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &AlbumCoverSearcher::AlbumCoverLoaded);
QObject::connect(ui_->search, &QPushButton::clicked, this, &AlbumCoverSearcher::Search);
QObject::connect(ui_->covers, &GroupedIconView::doubleClicked, this, &AlbumCoverSearcher::CoverDoubleClicked);
@@ -222,7 +214,9 @@ void AlbumCoverSearcher::SearchFinished(const quint64 id, const CoverProviderSea
if (result.image_url.isEmpty()) continue;
- quint64 new_id = app_->album_cover_loader()->LoadImageAsync(options_, result.image_url, QUrl());
+ AlbumCoverLoaderOptions cover_options(AlbumCoverLoaderOptions::Option::RawImageData | AlbumCoverLoaderOptions::Option::OriginalImage | AlbumCoverLoaderOptions::Option::ScaledImage | AlbumCoverLoaderOptions::Option::PadScaledImage);
+ cover_options.desired_scaled_size = ui_->covers->iconSize(), ui_->covers->iconSize();
+ quint64 new_id = app_->album_cover_loader()->LoadImageAsync(cover_options, false, result.image_url, QUrl(), false);
QStandardItem *item = new QStandardItem;
item->setIcon(no_cover_icon_);
@@ -249,12 +243,12 @@ void AlbumCoverSearcher::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoad
if (cover_loading_tasks_.isEmpty()) ui_->busy->hide();
- if (!result.success || result.album_cover.image_data.isNull() || result.album_cover.image.isNull() || result.image_thumbnail.isNull()) {
+ if (!result.success || result.album_cover.image_data.isNull() || result.album_cover.image.isNull() || result.image_scaled.isNull()) {
model_->removeRow(item->row());
return;
}
- const QPixmap pixmap = QPixmap::fromImage(result.image_thumbnail);
+ const QPixmap pixmap = QPixmap::fromImage(result.image_scaled);
if (pixmap.isNull()) {
model_->removeRow(item->row());
return;
diff --git a/src/covermanager/albumcoversearcher.h b/src/covermanager/albumcoversearcher.h
index 972a16366..73505d833 100644
--- a/src/covermanager/albumcoversearcher.h
+++ b/src/covermanager/albumcoversearcher.h
@@ -107,7 +107,6 @@ class AlbumCoverSearcher : public QDialog {
QStandardItemModel *model_;
QIcon no_cover_icon_;
- AlbumCoverLoaderOptions options_;
AlbumCoverFetcher *fetcher_;
quint64 id_;
diff --git a/src/covermanager/coverexportrunnable.cpp b/src/covermanager/coverexportrunnable.cpp
index 6ef908d3b..499f73b79 100644
--- a/src/covermanager/coverexportrunnable.cpp
+++ b/src/covermanager/coverexportrunnable.cpp
@@ -28,20 +28,19 @@
#include "core/song.h"
#include "core/tagreaderclient.h"
+#include "albumcoverloaderoptions.h"
#include "albumcoverexport.h"
#include "coverexportrunnable.h"
-CoverExportRunnable::CoverExportRunnable(const AlbumCoverExport::DialogResult &dialog_result, const Song &song, QObject *parent)
+CoverExportRunnable::CoverExportRunnable(const AlbumCoverExport::DialogResult &dialog_result, const AlbumCoverLoaderOptions::Types cover_types, const Song &song, QObject *parent)
: QObject(parent),
dialog_result_(dialog_result),
+ cover_types_(cover_types),
song_(song) {}
void CoverExportRunnable::run() {
- QString cover_path = GetCoverPath();
-
- // Manually unset?
- if (cover_path.isEmpty()) {
+ if (song_.art_unset() || (!song_.art_embedded() && !song_.art_automatic_is_valid() && !song_.art_manual_is_valid())) {
EmitCoverSkipped();
}
else {
@@ -55,25 +54,6 @@ void CoverExportRunnable::run() {
}
-QString CoverExportRunnable::GetCoverPath() {
-
- if (song_.has_manually_unset_cover()) {
- return QString();
- // Export downloaded covers?
- }
- else if (!song_.art_manual().isEmpty() && dialog_result_.export_downloaded_) {
- return song_.art_manual().toLocalFile();
- // Export embedded covers?
- }
- else if (!song_.art_automatic().isEmpty() && song_.art_automatic().path() == Song::kEmbeddedCover && dialog_result_.export_embedded_) {
- return song_.art_automatic().toLocalFile();
- }
- else {
- return QString();
- }
-
-}
-
// Exports a single album cover using a "save QImage to file" approach.
// For performance reasons this method will be invoked only if loading and in memory processing of images is necessary for current settings which means that:
// - either the force size flag is being used
@@ -81,45 +61,57 @@ QString CoverExportRunnable::GetCoverPath() {
// In all other cases, the faster ExportCover() method will be used.
void CoverExportRunnable::ProcessAndExportCover() {
- QString cover_path = GetCoverPath();
+ QImage image;
+ QString extension;
- // either embedded or disk - the one we'll export for the current album
- QImage cover;
-
- QImage embedded_cover;
- QImage disk_cover;
-
- // load embedded cover if any
- if (song_.has_embedded_cover()) {
- embedded_cover = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile());
-
- if (embedded_cover.isNull()) {
- EmitCoverSkipped();
- return;
+ for (const AlbumCoverLoaderOptions::Type cover_type : cover_types_) {
+ switch (cover_type) {
+ case AlbumCoverLoaderOptions::Type::Unset:
+ if (song_.art_unset()) {
+ EmitCoverSkipped();
+ return;
+ }
+ break;
+ case AlbumCoverLoaderOptions::Type::Embedded:
+ if (song_.art_embedded() && dialog_result_.export_embedded_) {
+ image = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile());
+ if (!image.isNull()) {
+ extension = "jpg";
+ }
+ }
+ break;
+ case AlbumCoverLoaderOptions::Type::Manual:
+ if (dialog_result_.export_downloaded_ && song_.art_manual_is_valid()) {
+ const QString cover_path = song_.art_manual().toLocalFile();
+ if (image.load(cover_path)) {
+ extension = cover_path.section('.', -1);
+ }
+ }
+ break;
+ case AlbumCoverLoaderOptions::Type::Automatic:
+ if (dialog_result_.export_downloaded_ && song_.art_automatic_is_valid()) {
+ const QString cover_path = song_.art_automatic().toLocalFile();
+ if (image.load(cover_path)) {
+ extension = cover_path.section('.', -1);
+ }
+ }
+ break;
}
- cover = embedded_cover;
+ if (!image.isNull() && !extension.isEmpty()) break;
}
- // load a file cover which iss mandatory if there's no embedded cover
- disk_cover.load(cover_path);
-
- if (embedded_cover.isNull()) {
- if (disk_cover.isNull()) {
- EmitCoverSkipped();
- return;
- }
- cover = disk_cover;
+ if (image.isNull() || extension.isEmpty()) {
+ EmitCoverSkipped();
+ return;
}
- // rescale if necessary
+ // Rescale if necessary
if (dialog_result_.IsSizeForced()) {
- cover = cover.scaled(QSize(dialog_result_.width_, dialog_result_.height_), Qt::IgnoreAspectRatio);
+ image = image.scaled(QSize(dialog_result_.width_, dialog_result_.height_), Qt::IgnoreAspectRatio);
}
- QString dir = song_.url().toLocalFile().section('/', 0, -2);
- QString extension = cover_path.section('.', -1);
-
- QString new_file = dir + '/' + dialog_result_.filename_ + '.' + (cover_path == Song::kEmbeddedCover ? "jpg" : extension);
+ QString cover_dir = song_.url().toLocalFile().section('/', 0, -2);
+ QString new_file = cover_dir + '/' + dialog_result_.filename_ + '.' + (song_.art_embedded() ? "jpg" : extension);
// If the file exists, do not override!
if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode::None && QFile::exists(new_file)) {
@@ -127,16 +119,15 @@ void CoverExportRunnable::ProcessAndExportCover() {
return;
}
- // we're handling overwrite as remove + copy so we need to delete the old file first
+ // We're handling overwrite as remove + copy so we need to delete the old file first
if (QFile::exists(new_file) && dialog_result_.overwrite_ != AlbumCoverExport::OverwriteMode::None) {
// if the mode is "overwrite smaller" then skip the cover if a bigger one is already available in the folder
if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode::Smaller) {
- QImage existing;
- existing.load(new_file);
-
- if (existing.isNull() || existing.size().height() >= cover.size().height() || existing.size().width() >= cover.size().width()) {
+ QImage image_existing;
+ image_existing.load(new_file);
+ if (image_existing.isNull() || image_existing.size().height() >= image.size().height() || image_existing.size().width() >= image.size().width()) {
EmitCoverSkipped();
return;
}
@@ -148,7 +139,7 @@ void CoverExportRunnable::ProcessAndExportCover() {
}
}
- if (cover.save(new_file)) {
+ if (image.save(new_file)) {
EmitCoverExported();
}
else {
@@ -160,12 +151,55 @@ void CoverExportRunnable::ProcessAndExportCover() {
// Exports a single album cover using a "copy file" approach.
void CoverExportRunnable::ExportCover() {
- QString cover_path = GetCoverPath();
+ QImage image;
+ QString extension;
+ QString cover_path;
+ bool embedded_cover = false;
- QString dir = song_.url().toLocalFile().section('/', 0, -2);
- QString extension = cover_path.section('.', -1);
+ for (const AlbumCoverLoaderOptions::Type cover_type : cover_types_) {
+ switch (cover_type) {
+ case AlbumCoverLoaderOptions::Type::Unset:
+ if (song_.art_unset()) {
+ EmitCoverSkipped();
+ return;
+ }
+ break;
+ case AlbumCoverLoaderOptions::Type::Embedded:
+ if (song_.art_embedded() && dialog_result_.export_embedded_) {
+ image = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile());
+ if (!image.isNull()) {
+ embedded_cover = true;
+ extension = "jpg";
+ }
+ }
+ break;
+ case AlbumCoverLoaderOptions::Type::Manual:
+ if (dialog_result_.export_downloaded_ && song_.art_manual_is_valid()) {
+ cover_path = song_.art_manual().toLocalFile();
+ if (image.load(cover_path)) {
+ extension = cover_path.section('.', -1);
+ }
+ }
+ break;
+ case AlbumCoverLoaderOptions::Type::Automatic:
+ if (dialog_result_.export_downloaded_ && song_.art_automatic_is_valid()) {
+ cover_path = song_.art_automatic().toLocalFile();
+ if (image.load(cover_path)) {
+ extension = cover_path.section('.', -1);
+ }
+ }
+ break;
+ }
+ if (!image.isNull() && !extension.isEmpty() && (embedded_cover || !cover_path.isEmpty())) break;
+ }
- QString new_file = dir + '/' + dialog_result_.filename_ + '.' + (cover_path == Song::kEmbeddedCover ? "jpg" : extension);
+ if (image.isNull() || extension.isEmpty()) {
+ EmitCoverSkipped();
+ return;
+ }
+
+ QString cover_dir = song_.url().toLocalFile().section('/', 0, -2);
+ QString new_file = cover_dir + '/' + dialog_result_.filename_ + '.' + extension;
// If the file exists, do not override!
if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode::None && QFile::exists(new_file)) {
@@ -181,10 +215,8 @@ void CoverExportRunnable::ExportCover() {
}
}
- if (cover_path == Song::kEmbeddedCover) {
- // an embedded cover
- QImage embedded = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile());
- if (!embedded.save(new_file)) {
+ if (embedded_cover) {
+ if (!image.save(new_file)) {
EmitCoverSkipped();
return;
}
@@ -196,6 +228,7 @@ void CoverExportRunnable::ExportCover() {
return;
}
}
+
EmitCoverExported();
}
diff --git a/src/covermanager/coverexportrunnable.h b/src/covermanager/coverexportrunnable.h
index ff06cf95e..cfb3e6a38 100644
--- a/src/covermanager/coverexportrunnable.h
+++ b/src/covermanager/coverexportrunnable.h
@@ -29,13 +29,14 @@
#include
#include "core/song.h"
+#include "albumcoverloaderoptions.h"
#include "albumcoverexport.h"
class CoverExportRunnable : public QObject, public QRunnable {
Q_OBJECT
public:
- explicit CoverExportRunnable(const AlbumCoverExport::DialogResult &dialog_result, const Song &song, QObject *parent = nullptr);
+ explicit CoverExportRunnable(const AlbumCoverExport::DialogResult &dialog_result, const AlbumCoverLoaderOptions::Types cover_types, const Song &song, QObject *parent = nullptr);
void run() override;
@@ -49,11 +50,10 @@ class CoverExportRunnable : public QObject, public QRunnable {
void ProcessAndExportCover();
void ExportCover();
- QString GetCoverPath();
AlbumCoverExport::DialogResult dialog_result_;
+ AlbumCoverLoaderOptions::Types cover_types_;
Song song_;
-
};
#endif // COVEREXPORTRUNNABLE_H
diff --git a/src/covermanager/coverproviders.cpp b/src/covermanager/coverproviders.cpp
index b958cff60..4042ec1a0 100644
--- a/src/covermanager/coverproviders.cpp
+++ b/src/covermanager/coverproviders.cpp
@@ -58,7 +58,7 @@ void CoverProviders::ReloadSettings() {
QSettings s;
s.beginGroup(CoversSettingsPage::kSettingsGroup);
- QStringList providers_enabled = s.value("providers", QStringList() << all_providers.values()).toStringList();
+ QStringList providers_enabled = s.value(CoversSettingsPage::kProviders, QStringList() << all_providers.values()).toStringList();
s.endGroup();
int i = 0;
diff --git a/src/covermanager/currentalbumcoverloader.cpp b/src/covermanager/currentalbumcoverloader.cpp
index f48799b5f..529fdefae 100644
--- a/src/covermanager/currentalbumcoverloader.cpp
+++ b/src/covermanager/currentalbumcoverloader.cpp
@@ -42,18 +42,15 @@ CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *pare
temp_file_pattern_(QDir::tempPath() + "/strawberry-cover-XXXXXX.jpg"),
id_(0) {
- options_.get_image_data_ = true;
- options_.get_image_ = true;
- options_.scale_output_image_ = false;
- options_.pad_output_image_ = false;
- options_.create_thumbnail_ = true;
- options_.thumbnail_size_ = QSize(120, 120);
- options_.default_output_image_ = QImage(":/pictures/cdcase.png");
- options_.default_thumbnail_image_ = options_.default_output_image_.scaledToHeight(120, Qt::SmoothTransformation);
+ options_.options = AlbumCoverLoaderOptions::Option::RawImageData | AlbumCoverLoaderOptions::Option::OriginalImage | AlbumCoverLoaderOptions::Option::ScaledImage;
+ options_.desired_scaled_size = QSize(120, 120);
+ options_.default_cover = ":/pictures/cdcase.png";
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &CurrentAlbumCoverLoader::TempAlbumCoverLoaded);
QObject::connect(app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, this, &CurrentAlbumCoverLoader::LoadAlbumCover);
+ ReloadSettingsAsync();
+
}
CurrentAlbumCoverLoader::~CurrentAlbumCoverLoader() {
@@ -63,6 +60,18 @@ CurrentAlbumCoverLoader::~CurrentAlbumCoverLoader() {
}
+void CurrentAlbumCoverLoader::ReloadSettingsAsync() {
+
+ QMetaObject::invokeMethod(this, &CurrentAlbumCoverLoader::ReloadSettings);
+
+}
+
+void CurrentAlbumCoverLoader::ReloadSettings() {
+
+ options_.types = AlbumCoverLoaderOptions::LoadTypes();
+
+}
+
void CurrentAlbumCoverLoader::LoadAlbumCover(const Song &song) {
last_song_ = song;
@@ -92,11 +101,11 @@ void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, AlbumCoverL
}
QUrl thumbnail_url;
- if (!result.image_thumbnail.isNull()) {
+ if (!result.image_scaled.isNull()) {
temp_cover_thumbnail_ = std::make_unique(temp_file_pattern_);
temp_cover_thumbnail_->setAutoRemove(true);
if (temp_cover_thumbnail_->open()) {
- if (result.image_thumbnail.save(temp_cover_thumbnail_->fileName(), "JPEG")) {
+ if (result.image_scaled.save(temp_cover_thumbnail_->fileName(), "JPEG")) {
thumbnail_url = QUrl::fromLocalFile(temp_cover_thumbnail_->fileName());
}
else {
@@ -113,6 +122,6 @@ void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, AlbumCoverL
}
emit AlbumCoverLoaded(last_song_, result);
- emit ThumbnailLoaded(last_song_, thumbnail_url, result.image_thumbnail);
+ emit ThumbnailLoaded(last_song_, thumbnail_url, result.image_scaled);
}
diff --git a/src/covermanager/currentalbumcoverloader.h b/src/covermanager/currentalbumcoverloader.h
index 22f3b00c3..b9ef6201d 100644
--- a/src/covermanager/currentalbumcoverloader.h
+++ b/src/covermanager/currentalbumcoverloader.h
@@ -48,7 +48,10 @@ class CurrentAlbumCoverLoader : public QObject {
const AlbumCoverLoaderOptions &options() const { return options_; }
const Song &last_song() const { return last_song_; }
+ void ReloadSettingsAsync();
+
public slots:
+ void ReloadSettings();
void LoadAlbumCover(const Song &song);
signals:
diff --git a/src/dialogs/edittagdialog.cpp b/src/dialogs/edittagdialog.cpp
index b66423e8b..851866a34 100644
--- a/src/dialogs/edittagdialog.cpp
+++ b/src/dialogs/edittagdialog.cpp
@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome
- * Copyright 2018-2021, Jonas Kvinge