Rewrite album cover loader

This commit is contained in:
Jonas Kvinge
2023-05-14 11:34:55 +02:00
parent 3c160c2f13
commit 331aa382f9
68 changed files with 2948 additions and 2565 deletions

View File

@@ -8,6 +8,7 @@
<file>schema/schema-14.sql</file> <file>schema/schema-14.sql</file>
<file>schema/schema-15.sql</file> <file>schema/schema-15.sql</file>
<file>schema/schema-16.sql</file> <file>schema/schema-16.sql</file>
<file>schema/schema-17.sql</file>
<file>schema/device-schema.sql</file> <file>schema/device-schema.sql</file>
<file>style/strawberry.css</file> <file>style/strawberry.css</file>
<file>style/smartplaylistsearchterm.css</file> <file>style/smartplaylistsearchterm.css</file>

View File

@@ -59,8 +59,10 @@ CREATE TABLE device_%deviceid_songs (
compilation_off INTEGER NOT NULL DEFAULT 0, compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0, compilation_effective INTEGER NOT NULL DEFAULT 0,
art_embedded INTEGER DEFAULT 0,
art_automatic TEXT, art_automatic TEXT,
art_manual TEXT, art_manual TEXT,
art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT, effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0, effective_originalyear INTEGER NOT NULL DEFAULT 0,

37
data/schema/schema-17.sql Normal file
View File

@@ -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;

View File

@@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS schema_version (
DELETE FROM 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 ( CREATE TABLE IF NOT EXISTS directories (
path TEXT NOT NULL, path TEXT NOT NULL,
@@ -67,8 +67,10 @@ CREATE TABLE IF NOT EXISTS songs (
compilation_off INTEGER NOT NULL DEFAULT 0, compilation_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0, compilation_effective INTEGER NOT NULL DEFAULT 0,
art_embedded INTEGER DEFAULT 0,
art_automatic TEXT, art_automatic TEXT,
art_manual TEXT, art_manual TEXT,
art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT, effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0, 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_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0, compilation_effective INTEGER NOT NULL DEFAULT 0,
art_embedded INTEGER DEFAULT 0,
art_automatic TEXT, art_automatic TEXT,
art_manual TEXT, art_manual TEXT,
art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT, effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0, 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_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0, compilation_effective INTEGER NOT NULL DEFAULT 0,
art_embedded INTEGER DEFAULT 0,
art_automatic TEXT, art_automatic TEXT,
art_manual TEXT, art_manual TEXT,
art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT, effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0, 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_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0, compilation_effective INTEGER NOT NULL DEFAULT 0,
art_embedded INTEGER DEFAULT 0,
art_automatic TEXT, art_automatic TEXT,
art_manual TEXT, art_manual TEXT,
art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT, effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0, 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_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0, compilation_effective INTEGER NOT NULL DEFAULT 0,
art_embedded INTEGER DEFAULT 0,
art_automatic TEXT, art_automatic TEXT,
art_manual TEXT, art_manual TEXT,
art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT, effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0, 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_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0, compilation_effective INTEGER NOT NULL DEFAULT 0,
art_embedded INTEGER DEFAULT 0,
art_automatic TEXT, art_automatic TEXT,
art_manual TEXT, art_manual TEXT,
art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT, effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0, 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_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0, compilation_effective INTEGER NOT NULL DEFAULT 0,
art_embedded INTEGER DEFAULT 0,
art_automatic TEXT, art_automatic TEXT,
art_manual TEXT, art_manual TEXT,
art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT, effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0, 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_off INTEGER NOT NULL DEFAULT 0,
compilation_effective INTEGER NOT NULL DEFAULT 0, compilation_effective INTEGER NOT NULL DEFAULT 0,
art_embedded INTEGER DEFAULT 0,
art_automatic TEXT, art_automatic TEXT,
art_manual TEXT, art_manual TEXT,
art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT, effective_albumartist TEXT,
effective_originalyear INTEGER NOT NULL DEFAULT 0, effective_originalyear INTEGER NOT NULL DEFAULT 0,
@@ -695,8 +711,10 @@ CREATE TABLE IF NOT EXISTS playlist_items (
compilation_off INTEGER DEFAULT 0, compilation_off INTEGER DEFAULT 0,
compilation_effective INTEGER DEFAULT 0, compilation_effective INTEGER DEFAULT 0,
art_embedded INTEGER DEFAULT 0,
art_automatic TEXT, art_automatic TEXT,
art_manual TEXT, art_manual TEXT,
art_unset INTEGER DEFAULT 0,
effective_albumartist TEXT, effective_albumartist TEXT,
effective_originalyear INTEGER, effective_originalyear INTEGER,

View File

@@ -1,5 +1,5 @@
/* This file is part of Strawberry. /* This file is part of Strawberry.
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
Strawberry is free software: you can redistribute it and/or modify Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -30,8 +30,6 @@
#include "core/logging.h" #include "core/logging.h"
#include "tagreaderbase.h" #include "tagreaderbase.h"
const std::string TagReaderBase::kEmbeddedCover = "(embedded)";
TagReaderBase::TagReaderBase() = default; TagReaderBase::TagReaderBase() = default;
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()) { if (!request.has_save_cover() || !request.save_cover()) {
return QByteArray(); return Cover();
} }
const QString song_filename = QString::fromUtf8(request.filename().data(), request.filename().size()); 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()) { if (request.has_cover_data()) {
cover_data = QByteArray(request.cover_data().data(), request.cover_data().size()); cover_data = QByteArray(request.cover_data().data(), request.cover_data().size());
} }
bool cover_is_jpeg = false; QString cover_mime_type;
if (request.has_cover_is_jpeg()) { if (request.has_cover_mime_type()) {
cover_is_jpeg = request.cover_is_jpeg(); 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()); const QString song_filename = QString::fromUtf8(request.filename().data(), request.filename().size());
QString cover_filename; QString cover_filename;
@@ -94,37 +92,39 @@ QByteArray TagReaderBase::LoadCoverDataFromRequest(const spb::tagreader::SaveEmb
if (request.has_cover_data()) { if (request.has_cover_data()) {
cover_data = QByteArray(request.cover_data().data(), request.cover_data().size()); cover_data = QByteArray(request.cover_data().data(), request.cover_data().size());
} }
bool cover_is_jpeg = false; QString cover_mime_type;
if (request.has_cover_is_jpeg()) { if (request.has_cover_mime_type()) {
cover_is_jpeg = request.cover_is_jpeg(); 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) { TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, QString cover_mime_type) {
if (!cover_data.isEmpty() && cover_is_jpeg) {
qLog(Debug) << "Using cover from JPEG data for" << song_filename;
return cover_data;
}
if (cover_data.isEmpty() && !cover_filename.isEmpty()) { if (cover_data.isEmpty() && !cover_filename.isEmpty()) {
qLog(Debug) << "Loading cover from" << cover_filename << "for" << song_filename; qLog(Debug) << "Loading cover from" << cover_filename << "for" << song_filename;
QFile file(cover_filename); QFile file(cover_filename);
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
qLog(Error) << "Failed to open file" << cover_filename << "for reading:" << file.errorString(); qLog(Error) << "Failed to open file" << cover_filename << "for reading:" << file.errorString();
return QByteArray(); return Cover();
} }
cover_data = file.readAll(); cover_data = file.readAll();
file.close(); file.close();
} }
if (!cover_data.isEmpty()) { 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; 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. // Convert image to JPEG.
qLog(Debug) << "Converting cover to JPEG data for" << song_filename; 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"); cover_image.save(&buffer, "JPEG");
buffer.close(); buffer.close();
} }
return cover_data; return Cover(cover_data, "image/jpeg");
} }
return QByteArray(); return Cover();
} }

View File

@@ -1,5 +1,5 @@
/* This file is part of Strawberry. /* This file is part of Strawberry.
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
Strawberry is free software: you can redistribute it and/or modify Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -36,6 +36,14 @@ class TagReaderBase {
explicit TagReaderBase(); explicit TagReaderBase();
~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 IsMediaFile(const QString &filename) const = 0;
virtual bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) 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 float ConvertPOPMRating(const int POPM_rating);
static int ConvertToPOPMRating(const float rating); static int ConvertToPOPMRating(const float rating);
static QByteArray LoadCoverDataFromRequest(const spb::tagreader::SaveFileRequest &request); static Cover LoadCoverFromRequest(const spb::tagreader::SaveFileRequest &request);
static QByteArray LoadCoverDataFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request); static Cover LoadCoverFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request);
private: private:
static QByteArray LoadCoverDataFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, const bool cover_is_jpeg); static Cover LoadCoverFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, QString cover_mime_type);
protected:
static const std::string kEmbeddedCover;
Q_DISABLE_COPY(TagReaderBase) Q_DISABLE_COPY(TagReaderBase)
}; };

View File

@@ -69,7 +69,7 @@ message SongMetadata {
optional int64 lastplayed = 29; optional int64 lastplayed = 29;
optional int64 lastseen = 30; optional int64 lastseen = 30;
optional string art_automatic = 31; optional bool art_embedded = 31;
optional float rating = 32; optional float rating = 32;
@@ -97,6 +97,7 @@ message IsMediaFileRequest {
message IsMediaFileResponse { message IsMediaFileResponse {
optional bool success = 1; optional bool success = 1;
optional string error = 2;
} }
message ReadFileRequest { message ReadFileRequest {
@@ -105,6 +106,7 @@ message ReadFileRequest {
message ReadFileResponse { message ReadFileResponse {
optional SongMetadata metadata = 1; optional SongMetadata metadata = 1;
optional string error = 2;
} }
message SaveFileRequest { message SaveFileRequest {
@@ -116,11 +118,12 @@ message SaveFileRequest {
optional SongMetadata metadata = 6; optional SongMetadata metadata = 6;
optional string cover_filename = 7; optional string cover_filename = 7;
optional bytes cover_data = 8; optional bytes cover_data = 8;
optional bool cover_is_jpeg = 9; optional string cover_mime_type = 9;
} }
message SaveFileResponse { message SaveFileResponse {
optional bool success = 1; optional bool success = 1;
optional string error = 2;
} }
message LoadEmbeddedArtRequest { message LoadEmbeddedArtRequest {
@@ -129,17 +132,19 @@ message LoadEmbeddedArtRequest {
message LoadEmbeddedArtResponse { message LoadEmbeddedArtResponse {
optional bytes data = 1; optional bytes data = 1;
optional string error = 2;
} }
message SaveEmbeddedArtRequest { message SaveEmbeddedArtRequest {
optional string filename = 1; optional string filename = 1;
optional string cover_filename = 2; optional string cover_filename = 2;
optional bytes cover_data = 3; optional bytes cover_data = 3;
optional bool cover_is_jpeg = 4; optional string cover_mime_type = 4;
} }
message SaveEmbeddedArtResponse { message SaveEmbeddedArtResponse {
optional bool success = 1; optional bool success = 1;
optional string error = 2;
} }
message SaveSongPlaycountToFileRequest { message SaveSongPlaycountToFileRequest {
@@ -149,6 +154,7 @@ message SaveSongPlaycountToFileRequest {
message SaveSongPlaycountToFileResponse { message SaveSongPlaycountToFileResponse {
optional bool success = 1; optional bool success = 1;
optional string error = 2;
} }
message SaveSongRatingToFileRequest { message SaveSongRatingToFileRequest {
@@ -158,6 +164,7 @@ message SaveSongRatingToFileRequest {
message SaveSongRatingToFileResponse { message SaveSongRatingToFileResponse {
optional bool success = 1; optional bool success = 1;
optional string error = 2;
} }
message Message { message Message {

View File

@@ -1,6 +1,6 @@
/* This file is part of Strawberry. /* This file is part of Strawberry.
Copyright 2013, David Sansome <me@davidsansome.com> Copyright 2013, David Sansome <me@davidsansome.com>
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
Strawberry is free software: you can redistribute it and/or modify Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -287,7 +287,7 @@ bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
if (!pictures.isEmpty()) { if (!pictures.isEmpty()) {
for (TagLib::FLAC::Picture *picture : pictures) { for (TagLib::FLAC::Picture *picture : pictures) {
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) { if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
song->set_art_automatic(kEmbeddedCover); song->set_art_embedded(true);
break; break;
} }
} }
@@ -302,7 +302,7 @@ bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
if (!pictures.isEmpty()) { if (!pictures.isEmpty()) {
for (TagLib::FLAC::Picture *picture : pictures) { for (TagLib::FLAC::Picture *picture : pictures) {
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) { if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
song->set_art_automatic(kEmbeddedCover); song->set_art_embedded(true);
break; break;
} }
} }
@@ -362,7 +362,7 @@ bool TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
TStringToStdString(map["SYLT"].front()->toString(), song->mutable_lyrics()); 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. // Find a suitable comment tag. For now we ignore iTunNORM comments.
for (uint i = 0; i < map["COMM"].size(); ++i) { 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 // Find album cover art
if (mp4_tag->item("covr").isValid()) { if (mp4_tag->item("covr").isValid()) {
song->set_art_automatic(kEmbeddedCover); song->set_art_embedded(true);
} }
if (mp4_tag->item("disk").isValid()) { 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("DISCNUMBER")) *disc = TStringToQString(map["DISCNUMBER"].front()).trimmed();
if (map.contains("COMPILATION")) *compilation = TStringToQString(map["COMPILATION"].front()).trimmed(); if (map.contains("COMPILATION")) *compilation = TStringToQString(map["COMPILATION"].front()).trimmed();
if (map.contains("COVERART")) song->set_art_automatic(kEmbeddedCover); if (map.contains("COVERART")) song->set_art_embedded(true);
if (map.contains("METADATA_BLOCK_PICTURE")) song->set_art_automatic(kEmbeddedCover); if (map.contains("METADATA_BLOCK_PICTURE")) song->set_art_embedded(true);
if (map.contains("FMPS_PLAYCOUNT") && song->playcount() <= 0) { if (map.contains("FMPS_PLAYCOUNT") && song->playcount() <= 0) {
const int playcount = TStringToQString(map["FMPS_PLAYCOUNT"].front()).trimmed().toInt(); 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")) { if (map.contains("COMPILATION")) {
*compilation = TStringToQString(TagLib::String::number(map["COMPILATION"].toString().toInt())); *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; qLog(Debug) << "Saving" << save_tags_options.join(", ") << "to" << filename;
const QByteArray cover_data = LoadCoverDataFromRequest(request); const Cover cover = LoadCoverFromRequest(request);
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename)); std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
if (!fileref || fileref->isNull()) return false; if (!fileref || fileref->isNull()) return false;
@@ -883,7 +883,7 @@ bool TagReaderTagLib::SaveFile(const spb::tagreader::SaveFileRequest &request) c
SetRating(xiph_comment, song); SetRating(xiph_comment, song);
} }
if (save_cover) { 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); SetRating(tag, song);
} }
if (save_cover) { 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); SetRating(tag, song);
} }
if (save_cover) { 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); SetRating(xiph_comment, song);
} }
if (save_cover) { 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 #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) // 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); utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
} }
#endif // Q_OS_LINUX #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; (void)xiph_comment;
@@ -1247,28 +1247,28 @@ void TagReaderTagLib::SetEmbeddedArt(TagLib::FLAC::File *flac_file, TagLib::Ogg:
if (!data.isEmpty()) { if (!data.isEmpty()) {
TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture(); TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture();
picture->setType(TagLib::FLAC::Picture::FrontCover); picture->setType(TagLib::FLAC::Picture::FrontCover);
picture->setMimeType("image/jpeg"); picture->setMimeType(QStringToTString(mime_type));
picture->setData(TagLib::ByteVector(data.constData(), data.size())); picture->setData(TagLib::ByteVector(data.constData(), data.size()));
flac_file->addPicture(picture); 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(); xiph_comment->removeAllPictures();
if (!data.isEmpty()) { if (!data.isEmpty()) {
TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture(); TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture();
picture->setType(TagLib::FLAC::Picture::FrontCover); picture->setType(TagLib::FLAC::Picture::FrontCover);
picture->setMimeType("image/jpeg"); picture->setMimeType(QStringToTString(mime_type));
picture->setData(TagLib::ByteVector(data.constData(), data.size())); picture->setData(TagLib::ByteVector(data.constData(), data.size()));
xiph_comment->addPicture(picture); 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; (void)file_mp3;
@@ -1284,14 +1284,14 @@ void TagReaderTagLib::SetEmbeddedArt(TagLib::MPEG::File *file_mp3, TagLib::ID3v2
TagLib::ID3v2::AttachedPictureFrame *frontcover = nullptr; TagLib::ID3v2::AttachedPictureFrame *frontcover = nullptr;
frontcover = new TagLib::ID3v2::AttachedPictureFrame("APIC"); frontcover = new TagLib::ID3v2::AttachedPictureFrame("APIC");
frontcover->setType(TagLib::ID3v2::AttachedPictureFrame::FrontCover); frontcover->setType(TagLib::ID3v2::AttachedPictureFrame::FrontCover);
frontcover->setMimeType("image/jpeg"); frontcover->setMimeType(QStringToTString(mime_type));
frontcover->setPicture(TagLib::ByteVector(data.constData(), data.size())); frontcover->setPicture(TagLib::ByteVector(data.constData(), data.size()));
tag->addFrame(frontcover); 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; (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"); if (tag->contains("covr")) tag->removeItem("covr");
} }
else { 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); tag->setItem("covr", covers);
} }
@@ -1314,7 +1324,7 @@ bool TagReaderTagLib::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtReque
qLog(Debug) << "Saving art to" << filename; qLog(Debug) << "Saving art to" << filename;
const QByteArray cover_data = LoadCoverDataFromRequest(request); const Cover cover = LoadCoverFromRequest(request);
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
TagLib::FileRef fileref(filename.toStdWString().c_str()); 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<TagLib::FLAC::File*>(fileref.file())) { if (TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(fileref.file())) {
TagLib::Ogg::XiphComment *xiph_comment = flac_file->xiphComment(true); TagLib::Ogg::XiphComment *xiph_comment = flac_file->xiphComment(true);
if (!xiph_comment) return false; 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 // Ogg Vorbis / Opus / Speex
else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref.file()->tag())) { else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref.file()->tag())) {
SetEmbeddedArt(xiph_comment, cover_data); SetEmbeddedArt(xiph_comment, cover.data, cover.mime_type);
} }
// MP3 // MP3
else if (TagLib::MPEG::File *file_mp3 = dynamic_cast<TagLib::MPEG::File*>(fileref.file())) { else if (TagLib::MPEG::File *file_mp3 = dynamic_cast<TagLib::MPEG::File*>(fileref.file())) {
TagLib::ID3v2::Tag *tag = file_mp3->ID3v2Tag(); TagLib::ID3v2::Tag *tag = file_mp3->ID3v2Tag();
if (!tag) return false; if (!tag) return false;
SetEmbeddedArt(file_mp3, tag, cover_data); SetEmbeddedArt(file_mp3, tag, cover.data, cover.mime_type);
} }
// MP4/AAC // MP4/AAC
else if (TagLib::MP4::File *aac_file = dynamic_cast<TagLib::MP4::File*>(fileref.file())) { else if (TagLib::MP4::File *aac_file = dynamic_cast<TagLib::MP4::File*>(fileref.file())) {
TagLib::MP4::Tag *tag = aac_file->tag(); TagLib::MP4::Tag *tag = aac_file->tag();
if (!tag) return false; if (!tag) return false;
SetEmbeddedArt(aac_file, tag, cover_data); SetEmbeddedArt(aac_file, tag, cover.data, cover.mime_type);
} }
// Not supported. // Not supported.
else return false; else return false;
const bool result = fileref.file()->save(); const bool success = fileref.file()->save();
#ifdef Q_OS_LINUX #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) // 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); utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
} }
#endif // Q_OS_LINUX #endif // Q_OS_LINUX
return result; return success;
} }
@@ -1490,15 +1500,15 @@ bool TagReaderTagLib::SaveSongPlaycountToFile(const QString &filename, const spb
return true; return true;
} }
bool ret = fileref->save(); bool success = fileref->save();
#ifdef Q_OS_LINUX #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) // 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); utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
} }
#endif // Q_OS_LINUX #endif // Q_OS_LINUX
return ret; return success;
} }
@@ -1602,14 +1612,14 @@ bool TagReaderTagLib::SaveSongRatingToFile(const QString &filename, const spb::t
return true; return true;
} }
bool ret = fileref->save(); const bool success = fileref->save();
#ifdef Q_OS_LINUX #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) // 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); utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
} }
#endif // Q_OS_LINUX #endif // Q_OS_LINUX
return ret; return success;
} }

View File

@@ -1,6 +1,6 @@
/* This file is part of Strawberry. /* This file is part of Strawberry.
Copyright 2013, David Sansome <me@davidsansome.com> Copyright 2013, David Sansome <me@davidsansome.com>
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
Strawberry is free software: you can redistribute it and/or modify Strawberry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -96,10 +96,10 @@ class TagReaderTagLib : public TagReaderBase {
void SetRating(TagLib::MP4::Tag *tag, const spb::tagreader::SongMetadata &song) const; 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 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::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; 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; 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; void SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::Tag *tag, const QByteArray &data, const QString &mime_type) const;
private: private:
FileRefFactory *factory_; FileRefFactory *factory_;

View File

@@ -224,7 +224,7 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
song->set_track(tag->value(TagParser::KnownField::TrackPosition).toInteger()); song->set_track(tag->value(TagParser::KnownField::TrackPosition).toInteger());
song->set_disc(tag->value(TagParser::KnownField::DiskPosition).toInteger()); song->set_disc(tag->value(TagParser::KnownField::DiskPosition).toInteger());
if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) { 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)); const float rating = ConvertPOPMRating(tag->value(TagParser::KnownField::Rating));
if (song->rating() <= 0 && rating > 0.0 && rating <= 1.0) { if (song->rating() <= 0 && rating > 0.0 && rating <= 1.0) {

View File

@@ -147,6 +147,7 @@ set(SOURCES
covermanager/albumcovermanager.cpp covermanager/albumcovermanager.cpp
covermanager/albumcovermanagerlist.cpp covermanager/albumcovermanagerlist.cpp
covermanager/albumcoverloader.cpp covermanager/albumcoverloader.cpp
covermanager/albumcoverloaderoptions.cpp
covermanager/albumcoverfetcher.cpp covermanager/albumcoverfetcher.cpp
covermanager/albumcoverfetchersearch.cpp covermanager/albumcoverfetchersearch.cpp
covermanager/albumcoversearcher.cpp covermanager/albumcoversearcher.cpp

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -1435,7 +1435,7 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
QSqlDatabase db(db_->Connect()); QSqlDatabase db(db_->Connect());
CollectionQuery query(db, songs_table_, fts_table_, opt); 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"); query.SetOrderBy("effective_albumartist, album, url");
if (compilation_required) { if (compilation_required) {
@@ -1453,42 +1453,48 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
QMap<QString, Album> albums; QMap<QString, Album> albums;
while (query.Next()) { while (query.Next()) {
bool is_compilation = query.Value(3).toBool();
Album info; Album album_info;
QUrl url = QUrl::fromEncoded(query.Value(0).toByteArray()); QUrl url = QUrl::fromEncoded(query.Value(0).toByteArray());
album_info.filetype = static_cast<Song::FileType>(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) { 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("..+:.*"))) { if (art_automatic.contains(QRegularExpression("..+:.*"))) {
info.art_automatic = QUrl::fromEncoded(art_automatic.toUtf8()); album_info.art_automatic = QUrl::fromEncoded(art_automatic.toUtf8());
} }
else { 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("..+:.*"))) { if (art_manual.contains(QRegularExpression("..+:.*"))) {
info.art_manual = QUrl::fromEncoded(art_manual.toUtf8()); album_info.art_manual = QUrl::fromEncoded(art_manual.toUtf8());
} }
else { else {
info.art_manual = QUrl::fromLocalFile(art_manual); album_info.art_manual = QUrl::fromLocalFile(art_manual);
} }
info.filetype = static_cast<Song::FileType>(query.Value(6).toInt()); album_info.art_unset = query.Value(9).toBool();
QString filetype = Song::TextForFiletype(info.filetype);
info.cue_path = query.Value(7).toString();
QString key; QString key;
if (!info.album_artist.isEmpty()) { if (!album_info.album_artist.isEmpty()) {
key.append(info.album_artist); key.append(album_info.album_artist);
} }
if (!info.album.isEmpty()) { if (!album_info.album.isEmpty()) {
if (!key.isEmpty()) key.append("-"); if (!key.isEmpty()) key.append("-");
key.append(info.album); key.append(album_info.album);
} }
if (!filetype.isEmpty()) { if (!filetype.isEmpty()) {
key.append(filetype); key.append(filetype);
@@ -1500,8 +1506,8 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
albums[key].urls.append(url); albums[key].urls.append(url);
} }
else { else {
info.urls << url; album_info.urls << url;
albums.insert(key, info); albums.insert(key, album_info);
} }
} }
@@ -1520,7 +1526,7 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective
ret.album_artist = effective_albumartist; ret.album_artist = effective_albumartist;
CollectionQuery query(db, songs_table_, fts_table_); 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()) { if (!effective_albumartist.isEmpty()) {
query.AddWhere("effective_albumartist", effective_albumartist); query.AddWhere("effective_albumartist", effective_albumartist);
} }
@@ -1532,22 +1538,24 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective
} }
if (query.Next()) { if (query.Next()) {
ret.art_automatic = QUrl::fromEncoded(query.Value(0).toByteArray()); ret.urls << QUrl::fromEncoded(query.Value(0).toByteArray());
ret.art_manual = QUrl::fromEncoded(query.Value(1).toByteArray()); ret.art_embedded = query.Value(1).toInt() == 1;
ret.urls << QUrl::fromEncoded(query.Value(2).toByteArray()); 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; 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()); QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect()); QSqlDatabase db(db_->Connect());
@@ -1571,15 +1579,11 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
} }
// Update the songs // Update the songs
QString sql = QString("UPDATE %1 SET art_manual = :cover").arg(songs_table_); 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_);
if (clear_art_automatic) {
sql += ", art_automatic = ''";
}
sql += " WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0";
SqlQuery q(db); SqlQuery q(db);
q.prepare(sql); 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(":effective_albumartist", effective_albumartist);
q.BindValue(":album", album); 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()); QMutexLocker l(db_->Mutex());
QSqlDatabase db(db_->Connect()); QSqlDatabase db(db_->Connect());
// Get the songs before they're updated
CollectionQuery query(db, songs_table_, fts_table_); CollectionQuery query(db, songs_table_, fts_table_);
query.SetColumnSpec("ROWID, " + Song::kColumnSpec); query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
query.AddWhere("effective_albumartist", effective_albumartist); query.AddWhere("effective_albumartist", effective_albumartist);
@@ -1637,16 +1640,124 @@ void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumar
deleted_songs << song; 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); SqlQuery q(db);
q.prepare(sql); 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(":cover", cover_url.isValid() ? cover_url.toString(QUrl::FullyEncoded) : ""); 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(":effective_albumartist", effective_albumartist);
q.BindValue(":album", album); q.BindValue(":album", album);
@@ -1655,7 +1766,6 @@ void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumar
return; return;
} }
// Now get the updated songs
if (!query.Exec()) { if (!query.Exec()) {
ReportErrors(query); ReportErrors(query);
return; return;

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -54,12 +54,14 @@ class CollectionBackendInterface : public QObject {
explicit CollectionBackendInterface(QObject *parent = nullptr) : QObject(parent) {} explicit CollectionBackendInterface(QObject *parent = nullptr) : QObject(parent) {}
struct Album { struct Album {
Album() : filetype(Song::FileType::Unknown) {} Album() : art_embedded(false), art_unset(false), filetype(Song::FileType::Unknown) {}
Album(const QString &_album_artist, const QString &_album, const QUrl &_art_automatic, const QUrl &_art_manual, const QList<QUrl> &_urls, const Song::FileType _filetype, const QString &_cue_path) 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<QUrl> &_urls, const Song::FileType _filetype, const QString &_cue_path)
: album_artist(_album_artist), : album_artist(_album_artist),
album(_album), album(_album),
art_embedded(_art_embedded),
art_automatic(_art_automatic), art_automatic(_art_automatic),
art_manual(_art_manual), art_manual(_art_manual),
art_unset(_art_unset),
urls(_urls), urls(_urls),
filetype(_filetype), filetype(_filetype),
cue_path(_cue_path) {} cue_path(_cue_path) {}
@@ -67,8 +69,10 @@ class CollectionBackendInterface : public QObject {
QString album_artist; QString album_artist;
QString album; QString album;
bool art_embedded;
QUrl art_automatic; QUrl art_automatic;
QUrl art_manual; QUrl art_manual;
bool art_unset;
QList<QUrl> urls; QList<QUrl> urls;
Song::FileType filetype; Song::FileType filetype;
QString cue_path; QString cue_path;
@@ -109,8 +113,10 @@ class CollectionBackendInterface : public QObject {
virtual AlbumList GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0; virtual AlbumList GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
virtual AlbumList GetCompilationAlbums(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 UpdateEmbeddedAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_embedded) = 0;
virtual void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) = 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; 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 GetCompilationAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
AlbumList GetAlbumsByArtist(const QString &artist, 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 UpdateEmbeddedAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_embedded) override;
void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) 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; 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 MarkSongsUnavailable(const SongList &songs, const bool unavailable = true);
void AddOrUpdateSubdirs(const CollectionSubdirectoryList &subdirs); void AddOrUpdateSubdirs(const CollectionSubdirectoryList &subdirs);
void CompilationsNeedUpdating(); void CompilationsNeedUpdating();
void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false); void UpdateEmbeddedAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_embedded);
void UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false); 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<QString> &artists, const bool on); void ForceCompilation(const QString &album, const QList<QString> &artists, const bool on);
void IncrementPlayCount(const int id); void IncrementPlayCount(const int id);
void IncrementSkipCount(const int id, const float progress); void IncrementSkipCount(const int id, const float progress);
@@ -303,7 +313,6 @@ class CollectionBackend : public CollectionBackendInterface {
QString subdirs_table_; QString subdirs_table_;
QString fts_table_; QString fts_table_;
QThread *original_thread_; QThread *original_thread_;
}; };
#endif // COLLECTIONBACKEND_H #endif // COLLECTIONBACKEND_H

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -71,6 +71,7 @@
#include "covermanager/albumcoverloader.h" #include "covermanager/albumcoverloader.h"
#include "covermanager/albumcoverloaderresult.h" #include "covermanager/albumcoverloaderresult.h"
#include "settings/collectionsettingspage.h" #include "settings/collectionsettingspage.h"
#include "settings/coverssettingspage.h"
const int CollectionModel::kPrettyCoverSize = 32; const int CollectionModel::kPrettyCoverSize = 32;
const char *CollectionModel::kPixmapDiskCacheDir = "pixmapcache"; const char *CollectionModel::kPixmapDiskCacheDir = "pixmapcache";
@@ -101,12 +102,6 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q
group_by_[1] = GroupBy::AlbumDisc; group_by_[1] = GroupBy::AlbumDisc;
group_by_[2] = GroupBy::None; 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_) { if (app_) {
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &CollectionModel::AlbumCoverLoaded); 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; use_pretty_covers_ = use_pretty_covers;
Reset(); Reset();
} }
} }
void CollectionModel::set_show_dividers(const bool show_dividers) { void CollectionModel::set_show_dividers(const bool show_dividers) {
@@ -177,6 +173,8 @@ void CollectionModel::ReloadSettings() {
s.endGroup(); s.endGroup();
cover_types_ = AlbumCoverLoaderOptions::LoadTypes();
if (!use_disk_cache_) { if (!use_disk_cache_) {
ClearDiskCache(); 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. // No art is cached and we're not loading it already. Load art for the first song in the album.
SongList songs = GetChildSongs(idx); SongList songs = GetChildSongs(idx);
if (!songs.isEmpty()) { 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_art_[id] = ItemAndCacheKey(item, cache_key);
pending_cache_keys_.insert(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); pending_cache_keys_.remove(cache_key);
// Insert this image in the cache. // 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. // Set the no_cover image so we don't continually try to load art.
QPixmapCache::insert(cache_key, no_cover_icon_); QPixmapCache::insert(cache_key, no_cover_icon_);
} }

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -49,11 +49,11 @@
#include "core/song.h" #include "core/song.h"
#include "core/sqlrow.h" #include "core/sqlrow.h"
#include "covermanager/albumcoverloader.h" #include "covermanager/albumcoverloader.h"
#include "covermanager/albumcoverloaderoptions.h"
#include "collectionfilteroptions.h" #include "collectionfilteroptions.h"
#include "collectionquery.h" #include "collectionquery.h"
#include "collectionqueryoptions.h" #include "collectionqueryoptions.h"
#include "collectionitem.h" #include "collectionitem.h"
#include "covermanager/albumcoverloaderoptions.h"
class QSettings; class QSettings;
@@ -310,7 +310,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
bool use_disk_cache_; bool use_disk_cache_;
bool use_lazy_loading_; bool use_lazy_loading_;
AlbumCoverLoaderOptions cover_loader_options_; AlbumCoverLoaderOptions::Types cover_types_;
using ItemAndCacheKey = QPair<CollectionItem*, QString>; using ItemAndCacheKey = QPair<CollectionItem*, QString>;
QMap<quint64, ItemAndCacheKey> pending_art_; QMap<quint64, ItemAndCacheKey> pending_art_;

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -156,10 +156,10 @@ void CollectionWatcher::ReloadSettings() {
overwrite_rating_ = s.value("overwrite_rating", false).toBool(); overwrite_rating_ = s.value("overwrite_rating", false).toBool();
s.endGroup(); s.endGroup();
best_image_filters_.clear(); best_art_filters_.clear();
for (const QString &filter : filters) { for (const QString &filter : filters) {
QString str = filter.trimmed(); QString str = filter.trimmed();
if (!str.isEmpty()) best_image_filters_ << str; if (!str.isEmpty()) best_art_filters_ << str;
} }
if (!monitor_ && was_monitoring_before) { 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; 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 // Also want to look to see whether the album art has changed
QUrl image = ImageForSong(file, album_art); const QUrl art_automatic = ArtForSong(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()))) { if (matching_song.art_automatic() != art_automatic || (!matching_song.art_automatic().isEmpty() && !matching_song.art_automatic_is_valid())) {
changed = true; changed = true;
} }
@@ -573,16 +573,16 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
#endif #endif
if (new_cue.isEmpty() || new_cue_mtime == 0) { // If no CUE or it's about to lose it. 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. 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 // 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; t->readded_songs << matching_songs;
} }
@@ -633,13 +633,13 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
} }
// Get new album art // 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. 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. 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."; qLog(Debug) << file << "is new.";
// Choose an image for the song(s) // Choose art for the song(s)
QUrl image = ImageForSong(file, album_art); const QUrl art_automatic = ArtForSong(file, album_art);
for (Song song : songs) { for (Song song : songs) {
song.set_directory_id(t->dir()); 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; t->new_songs << song;
} }
} }
@@ -669,7 +669,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
// Look for deleted songs // Look for deleted songs
for (const Song &song : songs_in_db) { for (const Song &song : songs_in_db) {
QString file = song.url().toLocalFile(); 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; qLog(Debug) << "Song deleted from disk:" << file;
t->deleted_songs << song; t->deleted_songs << song;
} }
@@ -704,7 +704,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
const QString &path, const QString &path,
const QString &fingerprint, const QString &fingerprint,
const QString &matching_cue, const QString &matching_cue,
const QUrl &image, const QUrl &art_automatic,
const SongList &old_cue_songs, const SongList &old_cue_songs,
ScanTransaction *t) { ScanTransaction *t) {
@@ -733,7 +733,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
if (sections_map.contains(new_cue_song.beginning_nanosec())) { // Changed section if (sections_map.contains(new_cue_song.beginning_nanosec())) { // Changed section
const Song matching_cue_song = sections_map[new_cue_song.beginning_nanosec()]; const Song matching_cue_song = sections_map[new_cue_song.beginning_nanosec()];
new_cue_song.set_id(matching_cue_song.id()); 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); new_cue_song.MergeUserSetData(matching_cue_song, true, true);
AddChangedSong(file, matching_cue_song, new_cue_song, t); AddChangedSong(file, matching_cue_song, new_cue_song, t);
used_ids.insert(matching_cue_song.id()); used_ids.insert(matching_cue_song.id());
@@ -755,7 +755,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file, void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
const QString &fingerprint, const QString &fingerprint,
const SongList &matching_songs, const SongList &matching_songs,
const QUrl &image, const QUrl &art_automatic,
const bool cue_deleted, const bool cue_deleted,
ScanTransaction *t) { ScanTransaction *t) {
@@ -776,7 +776,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
song_on_disk.set_directory_id(t->dir()); song_on_disk.set_directory_id(t->dir());
song_on_disk.set_id(matching_song.id()); song_on_disk.set_id(matching_song.id());
song_on_disk.set_fingerprint(fingerprint); 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_); song_on_disk.MergeUserSetData(matching_song, !overwrite_playcount_, !overwrite_rating_);
AddChangedSong(file, matching_song, song_on_disk, t); 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; bool notify_new = false;
QStringList changes; QStringList changes;
if (matching_song.is_unavailable()) { if (matching_song.unavailable()) {
qLog(Debug) << "unavailable song" << file << "restored."; qLog(Debug) << "unavailable song" << file << "restored.";
notify_new = true; 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. // This is used when there is more than one image in a directory.
// Pick the biggest image that matches the most important filter // Pick the biggest image that matches the most important filter
QStringList filtered; 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 // 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) { for (const QString &art_automatic : art_automatic_list) {
QFileInfo fileinfo(image); QFileInfo fileinfo(art_automatic);
QString filename(fileinfo.fileName()); QString filename(fileinfo.fileName());
if (filename.contains(filter_text, Qt::CaseInsensitive)) 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. // 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()) { if (filtered.isEmpty()) {
// The filter was too restrictive, just use the original list // The filter was too restrictive, just use the original list
filtered = images; filtered = art_automatic_list;
} }
int biggest_size = 0; int biggest_size = 0;
@@ -1085,20 +1085,21 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) {
} }
QUrl CollectionWatcher::ImageForSong(const QString &path, QMap<QString, QStringList> &album_art) { QUrl CollectionWatcher::ArtForSong(const QString &path, QMap<QString, QStringList> &art_automatic_list) {
QString dir(DirectoryPart(path)); QString dir(DirectoryPart(path));
if (album_art.contains(dir)) { if (art_automatic_list.contains(dir)) {
if (album_art[dir].count() == 1) { if (art_automatic_list[dir].count() == 1) {
return QUrl::fromLocalFile(album_art[dir][0]); return QUrl::fromLocalFile(art_automatic_list[dir][0]);
} }
else { else {
QString best_image = PickBestImage(album_art[dir]); const QString best_art = PickBestArt(art_automatic_list[dir]);
album_art[dir] = QStringList() << best_image; art_automatic_list[dir] = QStringList() << best_art;
return QUrl::fromLocalFile(best_image); return QUrl::fromLocalFile(best_art);
} }
} }
return QUrl(); return QUrl();
} }

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -178,17 +178,17 @@ class CollectionWatcher : public QObject {
inline static QString NoExtensionPart(const QString &fileName); inline static QString NoExtensionPart(const QString &fileName);
inline static QString ExtensionPart(const QString &fileName); inline static QString ExtensionPart(const QString &fileName);
inline static QString DirectoryPart(const QString &fileName); inline static QString DirectoryPart(const QString &fileName);
QString PickBestImage(const QStringList &images); QString PickBestArt(const QStringList &art_automatic_list);
QUrl ImageForSong(const QString &path, QMap<QString, QStringList> &album_art); QUrl ArtForSong(const QString &path, QMap<QString, QStringList> &art_automatic_list);
void AddWatch(const CollectionDirectory &dir, const QString &path); void AddWatch(const CollectionDirectory &dir, const QString &path);
void RemoveWatch(const CollectionDirectory &dir, const CollectionSubdirectory &subdir); void RemoveWatch(const CollectionDirectory &dir, const CollectionSubdirectory &subdir);
static quint64 GetMtimeForCue(const QString &cue_path); static quint64 GetMtimeForCue(const QString &cue_path);
void PerformScan(const bool incremental, const bool ignore_mtimes); 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. // 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. // 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. // 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). // 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<QString> *cues_processed); SongList ScanNewFile(const QString &file, const QString &path, const QString &fingerprint, const QString &matching_cue, QSet<QString> *cues_processed);
@@ -210,9 +210,9 @@ class CollectionWatcher : public QObject {
QThread *original_thread_; QThread *original_thread_;
QHash<QString, CollectionDirectory> subdir_mapping_; QHash<QString, CollectionDirectory> 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. // 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 scan_on_startup_;
bool monitor_; bool monitor_;

View File

@@ -54,16 +54,14 @@ ContextAlbum::ContextAlbum(QWidget *parent)
timeline_fade_(new QTimeLine(kFadeTimeLineMs, this)), timeline_fade_(new QTimeLine(kFadeTimeLineMs, this)),
image_strawberry_(":/pictures/strawberry.png"), image_strawberry_(":/pictures/strawberry.png"),
image_original_(image_strawberry_), image_original_(image_strawberry_),
pixmap_current_opacity_(1.0) { pixmap_current_opacity_(1.0),
desired_height_(width()) {
setObjectName("context-widget-album"); setObjectName("context-widget-album");
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
cover_loader_options_.desired_height_ = width(); QImage image = ImageUtils::ScaleImage(image_strawberry_, QSize(desired_height_, desired_height_), devicePixelRatioF(), true);
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());
if (!image.isNull()) { if (!image.isNull()) {
pixmap_current_ = QPixmap::fromImage(image); pixmap_current_ = QPixmap::fromImage(image);
} }
@@ -128,8 +126,8 @@ void ContextAlbum::contextMenuEvent(QContextMenuEvent *e) {
void ContextAlbum::UpdateWidth(const int new_width) { void ContextAlbum::UpdateWidth(const int new_width) {
if (new_width != cover_loader_options_.desired_height_) { if (new_width != desired_height_) {
cover_loader_options_.desired_height_ = new_width; desired_height_ = new_width;
ScaleCover(); ScaleCover();
ScalePreviousCovers(); ScalePreviousCovers();
updateGeometry(); updateGeometry();
@@ -235,7 +233,7 @@ void ContextAlbum::FadePreviousCoverFinished(std::shared_ptr<PreviousCover> prev
void ContextAlbum::ScaleCover() { 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()) { if (image.isNull()) {
pixmap_current_ = QPixmap(); pixmap_current_ = QPixmap();
} }
@@ -248,7 +246,7 @@ void ContextAlbum::ScaleCover() {
void ContextAlbum::ScalePreviousCovers() { void ContextAlbum::ScalePreviousCovers() {
for (std::shared_ptr<PreviousCover> previous_cover : previous_covers_) { for (std::shared_ptr<PreviousCover> 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()) { if (image.isNull()) {
previous_cover->pixmap = QPixmap(); previous_cover->pixmap = QPixmap();
} }

View File

@@ -33,8 +33,6 @@
#include <QPixmap> #include <QPixmap>
#include <QMovie> #include <QMovie>
#include "covermanager/albumcoverloaderoptions.h"
class QMenu; class QMenu;
class QTimeLine; class QTimeLine;
class QPainter; class QPainter;
@@ -99,7 +97,6 @@ class ContextAlbum : public QWidget {
QMenu *menu_; QMenu *menu_;
ContextView *context_view_; ContextView *context_view_;
AlbumCoverChoiceController *album_cover_choice_controller_; AlbumCoverChoiceController *album_cover_choice_controller_;
AlbumCoverLoaderOptions cover_loader_options_;
bool downloading_covers_; bool downloading_covers_;
QTimeLine *timeline_fade_; QTimeLine *timeline_fade_;
QImage image_strawberry_; QImage image_strawberry_;
@@ -107,6 +104,7 @@ class ContextAlbum : public QWidget {
QPixmap pixmap_current_; QPixmap pixmap_current_;
qreal pixmap_current_opacity_; qreal pixmap_current_opacity_;
std::unique_ptr<QMovie> spinner_animation_; std::unique_ptr<QMovie> spinner_animation_;
int desired_height_;
}; };
#endif // CONTEXTALBUM_H #endif // CONTEXTALBUM_H

View File

@@ -48,7 +48,7 @@
#include "scopedtransaction.h" #include "scopedtransaction.h"
const char *Database::kDatabaseFilename = "strawberry.db"; const char *Database::kDatabaseFilename = "strawberry.db";
const int Database::kSchemaVersion = 16; const int Database::kSchemaVersion = 17;
const int Database::kMinSupportedSchemaVersion = 10; const int Database::kMinSupportedSchemaVersion = 10;
const char *Database::kMagicAllSongsTables = "%allsongstables"; const char *Database::kMagicAllSongsTables = "%allsongstables";

View File

@@ -1177,6 +1177,7 @@ void MainWindow::ReloadAllSettings() {
collection_view_->ReloadSettings(); collection_view_->ReloadSettings();
ui_->playlist->view()->ReloadSettings(); ui_->playlist->view()->ReloadSettings();
app_->playlist_manager()->playlist_container()->ReloadSettings(); app_->playlist_manager()->playlist_container()->ReloadSettings();
app_->current_albumcover_loader()->ReloadSettingsAsync();
album_cover_choice_controller_->ReloadSettings(); album_cover_choice_controller_->ReloadSettings();
context_view_->ReloadSettings(); context_view_->ReloadSettings();
file_view_->ReloadSettings(); file_view_->ReloadSettings();
@@ -1381,14 +1382,14 @@ void MainWindow::SongChanged(const Song &song) {
SendNowPlaying(); SendNowPlaying();
const bool enable_change_art = song.is_collection_song() && !song.effective_albumartist().isEmpty() && !song.album().isEmpty(); 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_->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.has_manually_unset_cover()); 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_file_action()->setEnabled(enable_change_art);
album_cover_choice_controller_->cover_from_url_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_->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_->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); emit AlbumCoverReady(song, result.album_cover.image);
const bool enable_change_art = song.is_collection_song() && !song.effective_albumartist().isEmpty() && !song.album().isEmpty(); 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_->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::ManuallyUnset); 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_file_action()->setEnabled(enable_change_art);
album_cover_choice_controller_->cover_from_url_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_->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_->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(); GetCoverAutomatically();
@@ -3089,7 +3090,8 @@ void MainWindow::GetCoverAutomatically() {
// Search for cover automatically? // Search for cover automatically?
const bool search = album_cover_choice_controller_->search_cover_auto_action()->isChecked() && 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_automatic_is_valid() &&
!song_.art_manual_is_valid() && !song_.art_manual_is_valid() &&
!song_.effective_albumartist().isEmpty() && !song_.effective_albumartist().isEmpty() &&

View File

@@ -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()) { else if (result.temp_cover_url.isValid() && result.temp_cover_url.isLocalFile()) {
cover_url = result.temp_cover_url; 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(); 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(); 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("year", song.year(), &last_metadata_);
AddMetadata("bitrate", song.bitrate(), &last_metadata_); AddMetadata("bitrate", song.bitrate(), &last_metadata_);

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -109,10 +109,6 @@ class Song {
Stream = 91 Stream = 91
}; };
Song(const Source source = Source::Unknown);
Song(const Song &other);
~Song();
static const QStringList kColumns; static const QStringList kColumns;
static const QString kColumnSpec; static const QString kColumnSpec;
static const QString kBindSpec; static const QString kBindSpec;
@@ -123,9 +119,6 @@ class Song {
static const QString kFtsBindSpec; static const QString kFtsBindSpec;
static const QString kFtsUpdateSpec; static const QString kFtsUpdateSpec;
static const QString kManuallyUnsetCover;
static const QString kEmbeddedCover;
static const QRegularExpression kAlbumRemoveDisc; static const QRegularExpression kAlbumRemoveDisc;
static const QRegularExpression kAlbumRemoveMisc; static const QRegularExpression kAlbumRemoveMisc;
static const QRegularExpression kTitleRemoveMisc; static const QRegularExpression kTitleRemoveMisc;
@@ -134,77 +127,22 @@ class Song {
static const QStringList kAcceptedExtensions; 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); bool operator==(const Song &other) const;
static QString TextForSource(const Source source); bool operator!=(const Song &other) const;
static QString DescriptionForSource(const Source source); Song &operator=(const Song &other);
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<Song> *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;
// Simple accessors // Simple accessors
bool is_valid() const;
bool is_unavailable() const;
int id() const; int id() const;
bool is_valid() const;
const QString &title() const; const QString &title() const;
const QString &title_sortable() const;
const QString &album() const; const QString &album() const;
const QString &album_sortable() const;
const QString &artist() const; const QString &artist() const;
const QString &artist_sortable() const;
const QString &albumartist() const; const QString &albumartist() const;
const QString &albumartist_sortable() const;
int track() const; int track() const;
int disc() const; int disc() const;
int year() const; int year() const;
@@ -237,6 +175,7 @@ class Song {
qint64 filesize() const; qint64 filesize() const;
qint64 mtime() const; qint64 mtime() const;
qint64 ctime() const; qint64 ctime() const;
bool unavailable() const;
QString fingerprint() const; QString fingerprint() const;
@@ -246,14 +185,15 @@ class Song {
qint64 lastseen() const; qint64 lastseen() const;
bool compilation_detected() const; bool compilation_detected() const;
bool compilation_off() const;
bool compilation_on() const; bool compilation_on() const;
bool compilation_off() const;
bool art_embedded() const;
const QUrl &art_automatic() const; const QUrl &art_automatic() const;
const QUrl &art_manual() const; const QUrl &art_manual() const;
bool art_unset() const;
const QString &cue_path() const; const QString &cue_path() const;
bool has_cue() const;
float rating() const; float rating() const;
@@ -271,74 +211,16 @@ class Song {
const QString &musicbrainz_release_group_id() const; const QString &musicbrainz_release_group_id() const;
const QString &musicbrainz_work_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; bool init_from_file() const;
// Pretty accessors const QString &title_sortable() const;
QString PrettyTitle() const; const QString &album_sortable() const;
QString PrettyTitleWithArtist() const; const QString &artist_sortable() const;
QString PrettyLength() const; const QString &albumartist_sortable() const;
QString PrettyYear() const;
QString PrettyOriginalYear() const;
QString TitleWithCompilationArtist() const; const QUrl &stream_url() const;
QString SampleRateBitDepthToText() const;
QString PrettyRating() const;
// Setters // Setters
bool IsEditable() const;
void set_id(const int id); void set_id(const int id);
void set_valid(const bool v); void set_valid(const bool v);
@@ -391,8 +273,10 @@ class Song {
void set_compilation_on(const bool v); void set_compilation_on(const bool v);
void set_compilation_off(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_automatic(const QUrl &v);
void set_art_manual(const QUrl &v); void set_art_manual(const QUrl &v);
void set_art_unset(const bool v);
void set_cue_path(const QString &v); void set_cue_path(const QString &v);
@@ -414,6 +298,61 @@ class Song {
void set_stream_url(const QUrl &v); 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 // Comparison functions
bool IsMetadataEqual(const Song &other) const; bool IsMetadataEqual(const Song &other) const;
bool IsPlayStatisticsEqual(const Song &other) const; bool IsPlayStatisticsEqual(const Song &other) const;
@@ -427,16 +366,69 @@ class Song {
bool IsOnSameAlbum(const Song &other) const; bool IsOnSameAlbum(const Song &other) const;
bool IsSimilar(const Song &other) const; bool IsSimilar(const Song &other) const;
bool operator==(const Song &other) const; static Source SourceFromURL(const QUrl &url);
bool operator!=(const Song &other) const; 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<Song> *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. // 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. // 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; QString AlbumKey() const;
Song &operator=(const Song &other);
private: private:
static const QString kManuallyUnsetCover;
static const QString kEmbeddedCover;
struct Private; struct Private;
static QString sortable(const QString &v); static QString sortable(const QString &v);

View File

@@ -30,6 +30,7 @@
#include <QIcon> #include <QIcon>
#include "covermanager/albumcoverloader.h" #include "covermanager/albumcoverloader.h"
#include "covermanager/albumcoverloaderoptions.h"
#include "standarditemiconloader.h" #include "standarditemiconloader.h"
StandardItemIconLoader::StandardItemIconLoader(AlbumCoverLoader *cover_loader, QObject *parent) StandardItemIconLoader::StandardItemIconLoader(AlbumCoverLoader *cover_loader, QObject *parent)
@@ -37,8 +38,6 @@ StandardItemIconLoader::StandardItemIconLoader(AlbumCoverLoader *cover_loader, Q
cover_loader_(cover_loader), cover_loader_(cover_loader),
model_(nullptr) { model_(nullptr) {
cover_options_.desired_height_ = 16;
QObject::connect(cover_loader_, &AlbumCoverLoader::AlbumCoverLoaded, this, &StandardItemIconLoader::AlbumCoverLoaded); 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) { 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); AlbumCoverLoaderOptions cover_options(AlbumCoverLoaderOptions::Option::ScaledImage);
pending_covers_[id] = for_item; 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) { 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) { 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) { if (item_parent && item_parent->index() == parent && item->index().row() >= begin && item->index().row() <= end) {
cover_loader_->CancelTask(it.key()); cover_loader_->CancelTask(it.key());
it = pending_covers_.erase(it); // clazy:exclude=strict-iterators it = pending_covers_.erase(it);
} }
else { else {
++it; ++it;
@@ -103,7 +108,7 @@ void StandardItemIconLoader::AlbumCoverLoaded(const quint64 id, const AlbumCover
QStandardItem *item = pending_covers_.take(id); QStandardItem *item = pending_covers_.take(id);
if (!item) return; 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))); item->setIcon(QIcon(QPixmap::fromImage(result.image_scaled)));
} }

View File

@@ -29,7 +29,6 @@
#include <QUrl> #include <QUrl>
#include <QImage> #include <QImage>
#include "covermanager/albumcoverloaderoptions.h"
#include "covermanager/albumcoverloaderresult.h" #include "covermanager/albumcoverloaderresult.h"
class QAbstractItemModel; class QAbstractItemModel;
@@ -38,8 +37,6 @@ class QStandardItem;
class Song; class Song;
class AlbumCoverLoader; class AlbumCoverLoader;
class QModelIndex;
// Uses an AlbumCoverLoader to asynchronously load and set an icon on a QStandardItem. // Uses an AlbumCoverLoader to asynchronously load and set an icon on a QStandardItem.
class StandardItemIconLoader : public QObject { class StandardItemIconLoader : public QObject {
Q_OBJECT Q_OBJECT
@@ -47,8 +44,6 @@ class StandardItemIconLoader : public QObject {
public: public:
explicit StandardItemIconLoader(AlbumCoverLoader *cover_loader, QObject *parent = nullptr); explicit StandardItemIconLoader(AlbumCoverLoader *cover_loader, QObject *parent = nullptr);
AlbumCoverLoaderOptions *options() { return &cover_options_; }
void SetModel(QAbstractItemModel *model); void SetModel(QAbstractItemModel *model);
void LoadIcon(const QUrl &art_automatic, const QUrl &art_manual, QStandardItem *for_item); void LoadIcon(const QUrl &art_automatic, const QUrl &art_manual, QStandardItem *for_item);
@@ -61,10 +56,7 @@ class StandardItemIconLoader : public QObject {
private: private:
AlbumCoverLoader *cover_loader_; AlbumCoverLoader *cover_loader_;
AlbumCoverLoaderOptions cover_options_;
QAbstractItemModel *model_; QAbstractItemModel *model_;
QMap<quint64, QStandardItem*> pending_covers_; QMap<quint64, QStandardItem*> pending_covers_;
}; };

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2019-2023, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -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::Message message;
spb::tagreader::SaveFileRequest *request = message.mutable_save_file_request(); spb::tagreader::SaveFileRequest *request = message.mutable_save_file_request();
const QByteArray filename_data = filename.toUtf8(); const QByteArray filename_data = filename.toUtf8();
request->set_filename(filename_data.constData(), filename_data.length()); 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_tags(save_types.testFlag(SaveType::Tags));
request->set_save_rating(save_rating == SaveRating::On); request->set_save_playcount(save_types.testFlag(SaveType::PlayCount));
request->set_save_cover(save_cover_options.enabled); request->set_save_rating(save_types.testFlag(SaveType::Rating));
request->set_cover_is_jpeg(save_cover_options.is_jpeg); request->set_save_cover(save_types.testFlag(SaveType::Cover));
if (save_cover_options.cover_filename.length() > 0) { 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()); request->set_cover_filename(cover_filename.constData(), cover_filename.length());
} }
if (save_cover_options.cover_data.length() > 0) { if (save_cover_options.cover_data.length() > 0) {
request->set_cover_data(save_cover_options.cover_data.constData(), save_cover_options.cover_data.length()); 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()); metadata.ToProtobuf(request->mutable_metadata());
ReplyType *reply = worker_pool_->SendMessageWithReply(&message); ReplyType *reply = worker_pool_->SendMessageWithReply(&message);
@@ -139,15 +144,17 @@ TagReaderReply *TagReaderClient::SaveEmbeddedArt(const QString &filename, const
const QByteArray filename_data = filename.toUtf8(); const QByteArray filename_data = filename.toUtf8();
request->set_filename(filename_data.constData(), filename_data.length()); 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) { 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()); request->set_cover_filename(cover_filename.constData(), cover_filename.length());
} }
if (save_cover_options.cover_data.length() > 0) { if (save_cover_options.cover_data.length() > 0) {
request->set_cover_data(save_cover_options.cover_data.constData(), save_cover_options.cover_data.length()); 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); 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()); Q_ASSERT(QThread::currentThread() != thread());
bool ret = false; 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()) { if (reply->WaitForFinished()) {
ret = reply->message().save_file_response().success(); ret = reply->message().save_file_response().success();
} }

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2011, David Sansome <me@davidsansome.com> * Copyright 2011, David Sansome <me@davidsansome.com>
* Copyright 2019-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2019-2023, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -53,35 +53,28 @@ class TagReaderClient : public QObject {
void Start(); void Start();
void ExitAsync(); void ExitAsync();
enum class SaveTags { enum class SaveType {
Off, NoType = 0,
On Tags = 1,
}; PlayCount = 2,
Rating = 4,
enum class SavePlaycount { Cover = 8
Off,
On
};
enum class SaveRating {
Off,
On
}; };
Q_DECLARE_FLAGS(SaveTypes, SaveType)
class SaveCoverOptions { class SaveCoverOptions {
public: 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 = 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) : enabled(true), is_jpeg(false), cover_filename(_cover_filename) {} 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) : enabled(true), is_jpeg(false), cover_data(_cover_data) {} explicit SaveCoverOptions(const QByteArray &_cover_data, const QString &_mime_type = QString()) : cover_data(_cover_data), mime_type(_mime_type) {}
bool enabled;
bool is_jpeg;
QString cover_filename; QString cover_filename;
QByteArray cover_data; QByteArray cover_data;
QString mime_type;
}; };
ReplyType *IsMediaFile(const QString &filename); ReplyType *IsMediaFile(const QString &filename);
ReplyType *ReadFile(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 *LoadEmbeddedArt(const QString &filename);
ReplyType *SaveEmbeddedArt(const QString &filename, const SaveCoverOptions &save_cover_options); ReplyType *SaveEmbeddedArt(const QString &filename, const SaveCoverOptions &save_cover_options);
ReplyType *UpdateSongPlaycount(const Song &metadata); 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. // 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. // 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); 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); bool IsMediaFileBlocking(const QString &filename);
QByteArray LoadEmbeddedArtBlocking(const QString &filename); QByteArray LoadEmbeddedArtBlocking(const QString &filename);
QImage LoadEmbeddedArtAsImageBlocking(const QString &filename); QImage LoadEmbeddedArtAsImageBlocking(const QString &filename);

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2019-2023, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -62,10 +62,11 @@
#include "core/application.h" #include "core/application.h"
#include "core/song.h" #include "core/song.h"
#include "core/iconloader.h" #include "core/iconloader.h"
#include "core/tagreaderclient.h"
#include "collection/collectionfilteroptions.h" #include "collection/collectionfilteroptions.h"
#include "collection/collectionbackend.h" #include "collection/collectionbackend.h"
#include "settings/collectionsettingspage.h" #include "settings/coverssettingspage.h"
#include "internet/internetservices.h" #include "internet/internetservices.h"
#include "internet/internetservice.h" #include "internet/internetservice.h"
#include "albumcoverchoicecontroller.h" #include "albumcoverchoicecontroller.h"
@@ -135,22 +136,23 @@ void AlbumCoverChoiceController::Init(Application *app) {
cover_searcher_->Init(cover_fetcher_); cover_searcher_->Init(cover_fetcher_);
QObject::connect(cover_fetcher_, &AlbumCoverFetcher::AlbumCoverFetched, this, &AlbumCoverChoiceController::AlbumCoverFetched); QObject::connect(cover_fetcher_, &AlbumCoverFetcher::AlbumCoverFetched, this, &AlbumCoverChoiceController::AlbumCoverFetched);
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::SaveEmbeddedCoverAsyncFinished, this, &AlbumCoverChoiceController::SaveEmbeddedCoverAsyncFinished);
} }
void AlbumCoverChoiceController::ReloadSettings() { void AlbumCoverChoiceController::ReloadSettings() {
QSettings s; QSettings s;
s.beginGroup(CollectionSettingsPage::kSettingsGroup); s.beginGroup(CoversSettingsPage::kSettingsGroup);
cover_options_.cover_type = static_cast<CoverOptions::CoverType>(s.value("save_cover_type", static_cast<int>(CoverOptions::CoverType::Cache)).toInt()); cover_options_.cover_type = static_cast<CoverOptions::CoverType>(s.value(CoversSettingsPage::kSaveType, static_cast<int>(CoverOptions::CoverType::Cache)).toInt());
cover_options_.cover_filename = static_cast<CoverOptions::CoverFilename>(s.value("save_cover_filename", static_cast<int>(CoverOptions::CoverFilename::Pattern)).toInt()); cover_options_.cover_filename = static_cast<CoverOptions::CoverFilename>(s.value(CoversSettingsPage::kSaveFilename, static_cast<int>(CoverOptions::CoverFilename::Pattern)).toInt());
cover_options_.cover_pattern = s.value("cover_pattern", "%albumartist-%album").toString(); cover_options_.cover_pattern = s.value(CoversSettingsPage::kSavePattern, "%albumartist-%album").toString();
cover_options_.cover_overwrite = s.value("cover_overwrite", false).toBool(); cover_options_.cover_overwrite = s.value(CoversSettingsPage::kSaveOverwrite, false).toBool();
cover_options_.cover_lowercase = s.value("cover_lowercase", false).toBool(); cover_options_.cover_lowercase = s.value(CoversSettingsPage::kSaveLowercase, false).toBool();
cover_options_.cover_replace_spaces = s.value("cover_replace_spaces", false).toBool(); cover_options_.cover_replace_spaces = s.value(CoversSettingsPage::kSaveReplaceSpaces, false).toBool();
s.endGroup(); s.endGroup();
cover_types_ = AlbumCoverLoaderOptions::LoadTypes();
} }
QList<QAction*> AlbumCoverChoiceController::GetAllActions() { QList<QAction*> AlbumCoverChoiceController::GetAllActions() {
@@ -170,30 +172,31 @@ QList<QAction*> AlbumCoverChoiceController::GetAllActions() {
AlbumCoverImageResult AlbumCoverChoiceController::LoadImageFromFile(Song *song) { 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)); QString cover_file = QFileDialog::getOpenFileName(this, tr("Load cover from disk"), GetInitialPathForFileDialog(*song, QString()), tr(kLoadImageFileFilter) + ";;" + tr(kAllFilesFilter));
if (cover_file.isEmpty()) return AlbumCoverImageResult(); if (cover_file.isEmpty()) return AlbumCoverImageResult();
AlbumCoverImageResult result; AlbumCoverImageResult result;
QFile file(cover_file); QFile file(cover_file);
if (file.open(QIODevice::ReadOnly)) { 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(); result.image_data = file.readAll();
file.close(); file.close();
if (result.image_data.isEmpty()) { if (result.image_data.isEmpty()) {
qLog(Error) << "Cover file" << cover_file << "is empty."; qLog(Error) << "Cover file" << cover_file << "is empty.";
emit Error(tr("Cover file %1 is empty.").arg(cover_file)); emit Error(tr("Cover file %1 is empty.").arg(cover_file));
return AlbumCoverImageResult();
} }
else {
result.mime_type = Utilities::MimeTypeFromData(result.image_data); if (result.image.loadFromData(result.image_data)) {
result.image.loadFromData(result.image_data);
result.cover_url = QUrl::fromLocalFile(cover_file); result.cover_url = QUrl::fromLocalFile(cover_file);
} result.mime_type = Utilities::MimeTypeFromData(result.image_data);
}
else {
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 result; return result;
@@ -202,24 +205,21 @@ AlbumCoverImageResult AlbumCoverChoiceController::LoadImageFromFile(Song *song)
QUrl AlbumCoverChoiceController::LoadCoverFromFile(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)); QString cover_file = QFileDialog::getOpenFileName(this, tr("Load cover from disk"), GetInitialPathForFileDialog(*song, QString()), tr(kLoadImageFileFilter) + ";;" + tr(kAllFilesFilter));
if (cover_file.isEmpty() || QImage(cover_file).isNull()) return QUrl();
if (cover_file.isEmpty()) return QUrl();
if (QImage(cover_file).isNull()) return QUrl();
switch (get_save_album_cover_type()) { switch (get_save_album_cover_type()) {
case CoverOptions::CoverType::Embedded: case CoverOptions::CoverType::Embedded:
if (song->save_embedded_cover_supported()) { if (song->save_embedded_cover_supported()) {
SaveCoverEmbeddedAutomatic(*song, cover_file); SaveCoverEmbeddedToCollectionSongs(*song, cover_file);
return QUrl::fromLocalFile(Song::kEmbeddedCover); return QUrl();
} }
[[fallthrough]]; [[fallthrough]];
case CoverOptions::CoverType::Cache: case CoverOptions::CoverType::Cache:
case CoverOptions::CoverType::Album:{ case CoverOptions::CoverType::Album:{
QUrl cover_url = QUrl::fromLocalFile(cover_file); const QUrl cover_url = QUrl::fromLocalFile(cover_file);
SaveArtManualToSong(song, cover_url); SaveArtManualToSong(song, cover_url);
return cover_url; return cover_url;
} }
@@ -258,18 +258,20 @@ void AlbumCoverChoiceController::SaveCoverToFileManual(const Song &song, const A
if (result.is_jpeg() && fileinfo.completeSuffix().compare("jpg", Qt::CaseInsensitive) == 0) { if (result.is_jpeg() && fileinfo.completeSuffix().compare("jpg", Qt::CaseInsensitive) == 0) {
QFile file(save_filename); QFile file(save_filename);
if (file.open(QIODevice::WriteOnly)) { 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) { if (file.write(result.image_data) <= 0) {
qLog(Error) << "Failed writing cover to file" << save_filename << file.errorString(); 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())); emit Error(tr("Failed writing cover to file %1: %2").arg(save_filename, file.errorString()));
file.close();
return;
} }
file.close(); file.close();
} }
else {
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()));
}
}
else { else {
if (!result.image.save(save_filename)) { if (!result.image.save(save_filename)) {
qLog(Error) << "Failed writing cover to file" << save_filename; qLog(Error) << "Failed writing cover to file" << 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; // 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 // 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_is_valid()) {
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(); return song.art_automatic().toLocalFile();
} }
// If no automatic art, start in the song's folder // If no automatic art, start in the song's folder
} if (!song.url().isEmpty() && song.url().isValid() && song.url().isLocalFile() && song.url().toLocalFile().contains('/')) {
else if (!song.url().isEmpty() && song.url().toLocalFile().contains('/')) {
return song.url().toLocalFile().section('/', 0, -2) + filename; return song.url().toLocalFile().section('/', 0, -2) + filename;
// Fallback - start in home
} }
return QDir::home().absolutePath() + filename; 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(); const AlbumCoverImageResult result = LoadImageFromURL();
if (!result.image.isNull()) {
if (result.image.isNull()) { SaveCoverAutomatic(song, result);
return QUrl();
}
else {
return 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 // Get something sensible to stick in the search box
AlbumCoverImageResult result = SearchForImage(song); AlbumCoverImageResult result = SearchForImage(song);
if (result.is_valid()) { if (result.is_valid()) {
return SaveCoverAutomatic(song, result); SaveCoverAutomatic(song, result);
}
else {
return QUrl();
} }
} }
AlbumCoverImageResult AlbumCoverChoiceController::SearchForImage(Song *song) { 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(); QString album = song->effective_album();
album = album.remove(Song::kAlbumRemoveDisc).remove(Song::kAlbumRemoveMisc); 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); UnsetAlbumCoverForSong(song);
SaveArtManualToSong(song, cover_url, clear_art_automatic);
return cover_url;
} }
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(); ClearAlbumCoverForSong(song);
if (clear_art_automatic) song->clear_art_automatic();
SaveArtManualToSong(song, QUrl(), clear_art_automatic);
} }
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()) { if (song->art_embedded() && song->save_embedded_cover_supported()) {
SaveCoverEmbeddedAutomatic(*song, AlbumCoverImageResult()); SaveCoverEmbeddedToCollectionSongs(*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();
} }
bool success = true; 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); QFile file(art_automatic);
if (file.exists()) { if (file.exists()) {
if (file.remove()) { if (file.remove()) {
song->clear_art_automatic(); song->clear_art_automatic();
if (art_automatic == art_manual) song->clear_art_manual();
} }
else { else {
success = false; success = false;
@@ -408,12 +384,12 @@ bool AlbumCoverChoiceController::DeleteCover(Song *song, const bool manually_uns
} }
else song->clear_art_automatic(); 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); QFile file(art_manual);
if (file.exists()) { if (file.exists()) {
if (file.remove()) { if (file.remove()) {
song->clear_art_manual(); song->clear_art_manual();
if (art_automatic == art_manual) song->clear_art_automatic();
} }
else { else {
success = false; success = false;
@@ -426,8 +402,8 @@ bool AlbumCoverChoiceController::DeleteCover(Song *song, const bool manually_uns
else song->clear_art_manual(); else song->clear_art_manual();
if (success) { if (success) {
if (manually_unset) UnsetCover(song, true); if (unset) UnsetCover(song);
else ClearCover(song, true); else ClearCover(song);
} }
return success; return success;
@@ -436,23 +412,55 @@ bool AlbumCoverChoiceController::DeleteCover(Song *song, const bool manually_uns
void AlbumCoverChoiceController::ShowCover(const Song &song, const QImage &image) { void AlbumCoverChoiceController::ShowCover(const Song &song, const QImage &image) {
if (image.isNull()) { 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 {
QPixmap pixmap = QPixmap::fromImage(image); QPixmap pixmap = QPixmap::fromImage(image);
if (!pixmap.isNull()) { if (!pixmap.isNull()) {
pixmap.setDevicePixelRatio(devicePixelRatioF()); pixmap.setDevicePixelRatio(devicePixelRatioF());
ShowCover(song, pixmap); 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); 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; 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; if (!song->is_valid()) return;
song->set_art_automatic(art_automatic); song->set_art_embedded(art_embedded);
if (song->has_embedded_cover()) { song->set_art_unset(false);
song->clear_art_manual();
}
if (song->source() == Song::Source::Collection) { 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()) { 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; if (!song->is_valid()) return;
song->set_art_manual(art_manual); song->set_art_manual(art_manual);
if (clear_art_automatic) song->clear_art_automatic(); song->set_art_unset(false);
// Update the backends. // Update the backends.
switch (song->source()) { switch (song->source()) {
case Song::Source::Collection: 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; break;
case Song::Source::LocalFile: case Song::Source::LocalFile:
case Song::Source::CDDA: case Song::Source::CDDA:
@@ -575,13 +581,13 @@ void AlbumCoverChoiceController::SaveArtManualToSong(Song *song, const QUrl &art
InternetService *service = app_->internet_services()->ServiceBySource(song->source()); InternetService *service = app_->internet_services()->ServiceBySource(song->source());
if (!service) break; if (!service) break;
if (service->artists_collection_backend()) { 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()) { 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()) { 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; 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) { QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song *song, const AlbumCoverImageResult &result, const bool force_overwrite) {
return SaveCoverToFileAutomatic(song->source(), return SaveCoverToFileAutomatic(song->source(),
@@ -625,11 +669,10 @@ QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song::Source sou
filepath = file.fileName(); filepath = file.fileName();
} }
QUrl cover_url; if (!result.image_data.isEmpty() && result.is_jpeg()) {
if (result.is_jpeg()) {
if (file.open(QIODevice::WriteOnly)) { if (file.open(QIODevice::WriteOnly)) {
if (file.write(result.image_data) > 0) { if (file.write(result.image_data) > 0) {
cover_url = QUrl::fromLocalFile(filepath); return QUrl::fromLocalFile(filepath);
} }
else { else {
qLog(Error) << "Failed to write cover to file" << file.fileName() << file.errorString(); qLog(Error) << "Failed to write cover to file" << file.fileName() << file.errorString();
@@ -643,88 +686,58 @@ QUrl AlbumCoverChoiceController::SaveCoverToFileAutomatic(const Song::Source sou
} }
} }
else { else {
if (result.image.save(filepath, "JPG")) cover_url = QUrl::fromLocalFile(filepath); if (result.image.save(filepath, "JPG")) {
return QUrl::fromLocalFile(filepath);
}
} }
return cover_url; return QUrl();
} }
void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const Song &song, const AlbumCoverImageResult &result) { 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 (song.source() == Song::Source::Collection) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) SaveCoverEmbeddedToCollectionSongs(song.effective_albumartist(), song.effective_album(), cover_filename, image_data, mime_type);
QFuture<SongList> future = QtConcurrent::run(&CollectionBackend::GetAlbumSongs, app_->collection_backend(), song.effective_albumartist(), song.effective_album(), CollectionFilterOptions());
#else
QFuture<SongList> future = QtConcurrent::run(app_->collection_backend(), &CollectionBackend::GetAlbumSongs, song.effective_albumartist(), song.effective_album(), CollectionFilterOptions());
#endif
QFutureWatcher<SongList> *watcher = new QFutureWatcher<SongList>();
QObject::connect(watcher, &QFutureWatcher<SongList>::finished, this, [this, watcher, song, result]() {
SongList songs = watcher->result();
watcher->deleteLater();
QList<QUrl> 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 { else {
quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image); SaveCoverEmbeddedToSong(song, cover_filename, image_data, mime_type);
QMutexLocker l(&mutex_cover_save_tasks_); }
cover_save_tasks_.insert(id, song);
}
void AlbumCoverChoiceController::SaveCoverEmbeddedToCollectionSongs(const QString &effective_albumartist, const QString &effective_album, const QString &cover_filename, const QByteArray &image_data, const QString &mime_type) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QFuture<SongList> future = QtConcurrent::run(&CollectionBackend::GetAlbumSongs, app_->collection_backend(), effective_albumartist, effective_album, CollectionFilterOptions());
#else
QFuture<SongList> future = QtConcurrent::run(app_->collection_backend(), &CollectionBackend::GetAlbumSongs, effective_albumartist, effective_album, CollectionFilterOptions());
#endif
QFutureWatcher<SongList> *watcher = new QFutureWatcher<SongList>();
QObject::connect(watcher, &QFutureWatcher<SongList>::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); 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);
}
}
} }
void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const Song &song, const QUrl &cover_url) { void AlbumCoverChoiceController::SaveCoverEmbeddedToSong(const Song &song, const QString &cover_filename, const QByteArray &image_data, const QString &mime_type) {
SaveCoverEmbeddedAutomatic(song, cover_url.toLocalFile());
}
void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const Song &song, const QString &cover_filename) {
if (song.source() == Song::Source::Collection) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QFuture<SongList> future = QtConcurrent::run(&CollectionBackend::GetAlbumSongs, app_->collection_backend(), song.effective_albumartist(), song.effective_album(), CollectionFilterOptions());
#else
QFuture<SongList> future = QtConcurrent::run(app_->collection_backend(), &CollectionBackend::GetAlbumSongs, song.effective_albumartist(), song.effective_album(), CollectionFilterOptions());
#endif
QFutureWatcher<SongList> *watcher = new QFutureWatcher<SongList>();
QObject::connect(watcher, &QFutureWatcher<SongList>::finished, this, [this, watcher, song, cover_filename]() {
SongList songs = watcher->result();
watcher->deleteLater();
QList<QUrl> 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_); QMutexLocker l(&mutex_cover_save_tasks_);
cover_save_tasks_.insert(id, song); cover_save_tasks_.append(song);
}); const bool art_embedded = !image_data.isNull();
watcher->setFuture(future); 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); });
else {
app_->album_cover_loader()->SaveEmbeddedCoverAsync(song.url().toLocalFile(), cover_filename);
}
}
void AlbumCoverChoiceController::SaveCoverEmbeddedAutomatic(const QList<QUrl> &urls, const QImage &image) {
app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, image);
} }
@@ -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()) { for (const QUrl &url : e->mimeData()->urls()) {
@@ -758,25 +771,22 @@ QUrl AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
if (IsKnownImageExtension(suffix)) { if (IsKnownImageExtension(suffix)) {
if (get_save_album_cover_type() == CoverOptions::CoverType::Embedded && song->save_embedded_cover_supported()) { if (get_save_album_cover_type() == CoverOptions::CoverType::Embedded && song->save_embedded_cover_supported()) {
SaveCoverEmbeddedAutomatic(*song, filename); SaveCoverEmbeddedToCollectionSongs(*song, filename);
return QUrl::fromLocalFile(Song::kEmbeddedCover);
} }
else { else {
SaveArtManualToSong(song, url); SaveArtManualToSong(song, url);
} }
return url; return;
} }
} }
if (e->mimeData()->hasImage()) { if (e->mimeData()->hasImage()) {
QImage image = qvariant_cast<QImage>(e->mimeData()->imageData()); QImage image = qvariant_cast<QImage>(e->mimeData()->imageData());
if (!image.isNull()) { if (!image.isNull()) {
return SaveCoverAutomatic(song, AlbumCoverImageResult(image)); SaveCoverAutomatic(song, AlbumCoverImageResult(image));
} }
} }
return QUrl();
} }
QUrl AlbumCoverChoiceController::SaveCoverAutomatic(Song *song, const AlbumCoverImageResult &result) { 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()) { switch(get_save_album_cover_type()) {
case CoverOptions::CoverType::Embedded:{ case CoverOptions::CoverType::Embedded:{
if (song->save_embedded_cover_supported()) { if (song->save_embedded_cover_supported()) {
SaveCoverEmbeddedAutomatic(*song, result); SaveCoverEmbeddedToCollectionSongs(*song, result);
cover_url = QUrl::fromLocalFile(Song::kEmbeddedCover);
break; 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 (reply->is_successful()) {
if (success) { SaveArtEmbeddedToSong(&song, art_embedded);
if (cleared) SaveArtAutomaticToSong(&song, QUrl()); }
else SaveArtAutomaticToSong(&song, QUrl::fromLocalFile(Song::kEmbeddedCover)); else {
emit Error(tr("Could not save cover to file %1.").arg(song.url().toLocalFile()));
} }
} }

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2019-2023, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -38,8 +38,10 @@
#include <QMutex> #include <QMutex>
#include "core/song.h" #include "core/song.h"
#include "core/tagreaderclient.h"
#include "utilities/coveroptions.h" #include "utilities/coveroptions.h"
#include "settings/collectionsettingspage.h" #include "settings/collectionsettingspage.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverimageresult.h" #include "albumcoverimageresult.h"
class QFileDialog; class QFileDialog;
@@ -108,22 +110,21 @@ class AlbumCoverChoiceController : public QWidget {
// Downloads the cover from an URL given by user. // 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. // 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(); AlbumCoverImageResult LoadImageFromURL();
// Lets the user choose a cover among all that have been found on last.fm. // 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. // 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); AlbumCoverImageResult SearchForImage(Song *song);
// Returns a path which indicates that the cover has been unset manually. void UnsetCover(Song *song);
QUrl UnsetCover(Song *song, const bool clear_art_automatic = false);
// Clears any album cover art associated with the 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. // 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. // Shows the cover of given song in it's original size.
void ShowCover(const Song &song, const QImage &image = QImage()); void ShowCover(const Song &song, const QImage &image = QImage());
@@ -133,20 +134,25 @@ class AlbumCoverChoiceController : public QWidget {
quint64 SearchCoverAutomatically(const Song &song); quint64 SearchCoverAutomatically(const Song &song);
// Saves the chosen cover as manual cover path of this song in collection. // Saves the chosen cover as manual cover path of this song in collection.
void SaveArtEmbeddedToSong(Song *song, const bool art_embedded);
void SaveArtAutomaticToSong(Song *song, const QUrl &art_automatic); 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. // 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. // 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 SaveCoverAutomatic(Song *song, const AlbumCoverImageResult &result);
QUrl SaveCoverToFileAutomatic(const Song *song, const AlbumCoverImageResult &result, const bool force_overwrite = false); 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); 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 SaveCoverEmbeddedToCollectionSongs(const Song &song, const AlbumCoverImageResult &result);
void SaveCoverEmbeddedAutomatic(const Song &song, const QString &cover_filename); void SaveCoverEmbeddedToCollectionSongs(const Song &song, const QString &cover_filename, const QByteArray &image_data = QByteArray(), const QString &mime_type = QString());
void SaveCoverEmbeddedAutomatic(const QList<QUrl> &urls, const QImage &image); 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); static bool CanAcceptDrag(const QDragEnterEvent *e);
@@ -155,7 +161,7 @@ class AlbumCoverChoiceController : public QWidget {
private slots: private slots:
void AlbumCoverFetched(const quint64 id, const AlbumCoverImageResult &result, const CoverSearchStatistics &statistics); 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: signals:
void Error(const QString &error); void Error(const QString &error);
@@ -187,12 +193,13 @@ class AlbumCoverChoiceController : public QWidget {
QAction *search_cover_auto_; QAction *search_cover_auto_;
QMap<quint64, Song> cover_fetching_tasks_; QMap<quint64, Song> cover_fetching_tasks_;
QMap<quint64, Song> cover_save_tasks_; QList<Song> cover_save_tasks_;
QMutex mutex_cover_save_tasks_; QMutex mutex_cover_save_tasks_;
CoverOptions cover_options_; CoverOptions cover_options_;
bool save_embedded_cover_override_; bool save_embedded_cover_override_;
AlbumCoverLoaderOptions::Types cover_types_;
}; };
#endif // ALBUMCOVERCHOICECONTROLLER_H #endif // ALBUMCOVERCHOICECONTROLLER_H

View File

@@ -24,6 +24,7 @@
#include <QThreadPool> #include <QThreadPool>
#include "core/song.h" #include "core/song.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverexport.h" #include "albumcoverexport.h"
#include "albumcoverexporter.h" #include "albumcoverexporter.h"
#include "coverexportrunnable.h" #include "coverexportrunnable.h"
@@ -43,9 +44,15 @@ void AlbumCoverExporter::SetDialogResult(const AlbumCoverExport::DialogResult &d
dialog_result_ = dialog_result; dialog_result_ = dialog_result;
} }
void AlbumCoverExporter::SetCoverTypes(const AlbumCoverLoaderOptions::Types cover_types) {
cover_types_ = cover_types;
}
void AlbumCoverExporter::AddExportRequest(const Song &song) { 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<int>(requests_.count()); all_ = static_cast<int>(requests_.count());
} }
void AlbumCoverExporter::Cancel() { requests_.clear(); } void AlbumCoverExporter::Cancel() { requests_.clear(); }

View File

@@ -27,6 +27,7 @@
#include <QQueue> #include <QQueue>
#include <QString> #include <QString>
#include "albumcoverloaderoptions.h"
#include "albumcoverexport.h" #include "albumcoverexport.h"
class QThreadPool; class QThreadPool;
@@ -42,6 +43,7 @@ class AlbumCoverExporter : public QObject {
static const int kMaxConcurrentRequests; static const int kMaxConcurrentRequests;
void SetDialogResult(const AlbumCoverExport::DialogResult &dialog_result); void SetDialogResult(const AlbumCoverExport::DialogResult &dialog_result);
void SetCoverTypes(const AlbumCoverLoaderOptions::Types cover_types);
void AddExportRequest(const Song &song); void AddExportRequest(const Song &song);
void StartExporting(); void StartExporting();
void Cancel(); void Cancel();
@@ -57,6 +59,8 @@ class AlbumCoverExporter : public QObject {
private: private:
void AddJobsToPool(); void AddJobsToPool();
AlbumCoverLoaderOptions::Types cover_types_;
AlbumCoverExport::DialogResult dialog_result_; AlbumCoverExport::DialogResult dialog_result_;
QQueue<CoverExportRunnable*> requests_; QQueue<CoverExportRunnable*> requests_;

View File

@@ -20,8 +20,6 @@
#ifndef ALBUMCOVERIMAGERESULT_H #ifndef ALBUMCOVERIMAGERESULT_H
#define ALBUMCOVERIMAGERESULT_H #define ALBUMCOVERIMAGERESULT_H
#include "config.h"
#include <QMetaType> #include <QMetaType>
#include <QByteArray> #include <QByteArray>
#include <QString> #include <QString>
@@ -30,10 +28,7 @@
class AlbumCoverImageResult { class AlbumCoverImageResult {
public: public:
explicit AlbumCoverImageResult(const QUrl &_cover_url = QUrl(), explicit AlbumCoverImageResult(const QUrl &_cover_url = QUrl(), const QString &_mime_type = QString(), const QByteArray &_image_data = QByteArray(), const QImage &_image = QImage())
const QString &_mime_type = QString(),
const QByteArray &_image_data = QByteArray(),
const QImage &_image = QImage())
: cover_url(_cover_url), : cover_url(_cover_url),
mime_type(_mime_type), mime_type(_mime_type),
image_data(_image_data), image_data(_image_data),
@@ -47,7 +42,6 @@ class AlbumCoverImageResult {
bool is_valid() const { return !image_data.isNull() || !image.isNull(); } bool is_valid() const { return !image_data.isNull() || !image.isNull(); }
bool is_jpeg() const { return mime_type == "image/jpeg" && !image_data.isEmpty(); } bool is_jpeg() const { return mime_type == "image/jpeg" && !image_data.isEmpty(); }
}; };
Q_DECLARE_METATYPE(AlbumCoverImageResult) Q_DECLARE_METATYPE(AlbumCoverImageResult)

View File

@@ -1,8 +1,6 @@
/* /*
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * Copyright 2019-2023, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019-2021, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -19,19 +17,13 @@
* *
*/ */
#include "config.h"
#include <memory> #include <memory>
#include <QtGlobal> #include <QtGlobal>
#include <QObject> #include <QObject>
#include <QStandardPaths>
#include <QDir>
#include <QThread> #include <QThread>
#include <QMutex> #include <QMutex>
#include <QBuffer>
#include <QSet> #include <QSet>
#include <QList>
#include <QQueue> #include <QQueue>
#include <QVariant> #include <QVariant>
#include <QByteArray> #include <QByteArray>
@@ -39,7 +31,6 @@
#include <QUrl> #include <QUrl>
#include <QFile> #include <QFile>
#include <QImage> #include <QImage>
#include <QPainter>
#include <QNetworkReply> #include <QNetworkReply>
#include <QNetworkRequest> #include <QNetworkRequest>
@@ -53,12 +44,13 @@
#include "albumcoverloaderresult.h" #include "albumcoverloaderresult.h"
#include "albumcoverimageresult.h" #include "albumcoverimageresult.h"
using std::make_shared;
AlbumCoverLoader::AlbumCoverLoader(QObject *parent) AlbumCoverLoader::AlbumCoverLoader(QObject *parent)
: QObject(parent), : QObject(parent),
network_(new NetworkAccessManager(this)), network_(new NetworkAccessManager(this)),
stop_requested_(false), stop_requested_(false),
load_image_async_id_(1), load_image_async_id_(1),
save_image_async_id_(1),
original_thread_(nullptr) { original_thread_(nullptr) {
original_thread_ = thread(); original_thread_ = thread();
@@ -86,7 +78,7 @@ void AlbumCoverLoader::CancelTask(const quint64 id) {
for (QQueue<TaskPtr>::iterator it = tasks_.begin(); it != tasks_.end(); ++it) { for (QQueue<TaskPtr>::iterator it = tasks_.begin(); it != tasks_.end(); ++it) {
TaskPtr task = *it; TaskPtr task = *it;
if (task->id == id) { if (task->id == id) {
tasks_.erase(it); // clazy:exclude=strict-iterators tasks_.erase(it);
break; break;
} }
} }
@@ -99,7 +91,7 @@ void AlbumCoverLoader::CancelTasks(const QSet<quint64> &ids) {
for (QQueue<TaskPtr>::iterator it = tasks_.begin(); it != tasks_.end();) { for (QQueue<TaskPtr>::iterator it = tasks_.begin(); it != tasks_.end();) {
TaskPtr task = *it; TaskPtr task = *it;
if (ids.contains(task->id)) { if (ids.contains(task->id)) {
it = tasks_.erase(it); // clazy:exclude=strict-iterators it = tasks_.erase(it);
} }
else { else {
++it; ++it;
@@ -112,24 +104,28 @@ quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options,
TaskPtr task = std::make_shared<Task>(); TaskPtr task = std::make_shared<Task>();
task->options = options; 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->song = song;
task->state = State::Manual;
return EnqueueTask(task); 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) { 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) {
Song song(song_source);
song.set_url(song_url);
song.set_art_automatic(art_automatic);
song.set_art_manual(art_manual);
TaskPtr task = std::make_shared<Task>(); TaskPtr task = std::make_shared<Task>();
task->options = options; task->options = options;
task->song = song; task->art_embedded = art_embedded;
task->state = State::Manual; 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); return EnqueueTask(task);
@@ -137,7 +133,7 @@ quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options,
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const AlbumCoverImageResult &album_cover) { quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const AlbumCoverImageResult &album_cover) {
TaskPtr task = std::make_shared<Task>(); TaskPtr task = make_shared<Task>();
task->options = options; task->options = options;
task->album_cover = album_cover; task->album_cover = album_cover;
@@ -147,7 +143,7 @@ quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options,
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QImage &image) { quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QImage &image) {
TaskPtr task = std::make_shared<Task>(); TaskPtr task = make_shared<Task>();
task->options = options; task->options = options;
task->album_cover.image = image; task->album_cover.image = image;
@@ -171,8 +167,6 @@ quint64 AlbumCoverLoader::EnqueueTask(TaskPtr task) {
void AlbumCoverLoader::ProcessTasks() { void AlbumCoverLoader::ProcessTasks() {
while (!stop_requested_) {
// Get the next task
TaskPtr task; TaskPtr task;
{ {
QMutexLocker l(&mutex_load_image_async_); QMutexLocker l(&mutex_load_image_async_);
@@ -181,380 +175,246 @@ void AlbumCoverLoader::ProcessTasks() {
} }
ProcessTask(task); ProcessTask(task);
}
} }
void AlbumCoverLoader::ProcessTask(TaskPtr task) { void AlbumCoverLoader::ProcessTask(TaskPtr task) {
TryLoadResult result = TryLoadImage(task); // If we have album cover already, only do scale and pad.
if (result.started_async) { if (task->album_cover.is_valid()) {
// The image is being loaded from a remote URL, we'll carry on later when it's done task->success = true;
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);
} }
else { else {
// Give up InitArt(task);
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));
} }
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. QImage image_scaled;
if (task->album_cover.is_valid()) { if (task->success) {
return TryLoadResult(false, true, AlbumCoverLoaderResult::Type::Embedded, task->album_cover); 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. // 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()) { 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()) {
switch (task->state) {
case State::None:
break;
case State::Manual:
task->song.InitArtManual(); task->song.InitArtManual();
if (task->song.art_manual_is_valid()) task->art_updated = true; if (task->song.art_manual_is_valid()) {
break; task->art_updated = true;
case State::Automatic: task->art_manual = task->song.art_manual();
}
if (task->song.url().isLocalFile()) { if (task->song.url().isLocalFile()) {
task->song.InitArtAutomatic(); task->song.InitArtAutomatic();
if (task->song.art_automatic_is_valid()) task->art_updated = true; if (task->song.art_automatic_is_valid()) {
task->art_updated = true;
task->art_automatic = task->song.art_automatic();
}
}
}
}
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; break;
} }
} }
AlbumCoverLoaderResult::Type type = AlbumCoverLoaderResult::Type::None; return LoadImageResult();
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_)); AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadEmbeddedImage(TaskPtr task) {
}
else if (cover_url.path() == Song::kEmbeddedCover && task->song.url().isLocalFile()) { if (task->art_embedded && task->song_url.isValid() && task->song_url.isLocalFile()) {
QByteArray image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song.url().toLocalFile()); task->album_cover.image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song_url.toLocalFile());
if (!image_data.isEmpty()) { if (!task->album_cover.image_data.isEmpty() && task->album_cover.image.loadFromData(task->album_cover.image_data)) {
QImage image; return LoadImageResult(AlbumCoverLoaderResult::Type::Embedded, LoadImageResult::Status::Success);
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));
}
} }
} }
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()) { if (cover_url.isLocalFile()) {
QFile file(cover_url.toLocalFile()); return LoadLocalUrlImage(task, result_type, cover_url);
if (file.exists()) { }
if (file.open(QIODevice::ReadOnly)) { else if (network_->supportedSchemes().contains(cover_url.scheme())) {
QByteArray image_data = file.readAll(); 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(); file.close();
QImage image;
if (!image_data.isEmpty() && task->options.get_image_ && image.loadFromData(image_data)) { if (task->album_cover.image_data.isEmpty()) {
return TryLoadResult(false, !image.isNull(), type, AlbumCoverImageResult(cover_url, QString(), image_data, image.isNull() ? task->options.default_output_image_ : image)); qLog(Error) << "Cover file" << cover_file << "is empty.";
return LoadImageResult(result_type, LoadImageResult::Status::Failure);
} }
else {
return TryLoadResult(false, !image_data.isEmpty(), type, AlbumCoverImageResult(cover_url, QString(), image_data, image.isNull() ? task->options.default_output_image_ : image)); 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);
} }
}
else { return LoadImageResult(result_type, LoadImageResult::Status::Success);
qLog(Error) << "Failed to open cover file" << cover_url << "for reading" << file.errorString();
} }
}
else { AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadRemoteUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url) {
qLog(Error) << "Cover file" << cover_url << "does not exist";
} qLog(Debug) << "Loading remote cover from URL" << cover_url;
}
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); QNetworkRequest request(cover_url);
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
QNetworkReply *reply = network_->get(request); QNetworkReply *reply = network_->get(request);
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, cover_url]() { RemoteFetchFinished(reply, cover_url); }); QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, task, result_type, cover_url]() { LoadRemoteImageFinished(reply, task, result_type, cover_url); });
remote_tasks_.insert(reply, task); return LoadImageResult(result_type, LoadImageResult::Status::Async);
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) { void AlbumCoverLoader::LoadRemoteImageFinished(QNetworkReply *reply, TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url) {
reply->deleteLater(); reply->deleteLater();
if (!remote_tasks_.contains(reply)) return;
TaskPtr task = remote_tasks_.take(reply);
// Handle redirects.
QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
if (redirect.isValid()) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
if (++task->redirects > kMaxRedirects) { if (redirect.isValid() && redirect.metaType().id() == QMetaType::QUrl) {
return; // Give up. #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(); QNetworkRequest request = reply->request();
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
request.setUrl(redirect.toUrl()); request.setUrl(redirect_url);
QNetworkReply *redirected_reply = network_->get(request); QNetworkReply *redirected_reply = network_->get(request);
QObject::connect(redirected_reply, &QNetworkReply::finished, this, [this, redirected_reply, redirect]() { RemoteFetchFinished(redirected_reply, redirect.toUrl()); }); QObject::connect(redirected_reply, &QNetworkReply::finished, this, [this, reply, task, result_type, redirect_url]() { LoadRemoteImageFinished(reply, task, result_type, redirect_url); });
remote_tasks_.insert(redirected_reply, task);
return; return;
} }
if (reply->error() == QNetworkReply::NoError) { if (reply->error() == QNetworkReply::NoError) {
// Try to load the image task->album_cover.image_data = reply->readAll();
QByteArray image_data = reply->readAll(); if (!task->album_cover.image_data.isEmpty() && task->album_cover.image.loadFromData(task->album_cover.image_data)) {
QString mime_type = Utilities::MimeTypeFromData(image_data); task->success = true;
QImage image; FinishTask(task, result_type);
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));
return; return;
} }
else { else {
qLog(Error) << "Unable to load album cover image" << cover_url; qLog(Error) << "Unable to load album cover image from URL" << cover_url;
} }
} }
else { 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); ProcessTask(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<QUrl> &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<QUrl>, urls), Q_ARG(QString, cover_filename));
return id;
}
quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList<QUrl> &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<QUrl>, urls), Q_ARG(QImage, image));
return id;
}
quint64 AlbumCoverLoader::SaveEmbeddedCoverAsync(const QList<QUrl> &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<QUrl>, 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<QUrl> &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<QUrl> &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<QUrl> &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();
} }

View File

@@ -1,8 +1,6 @@
/* /*
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * Copyright 2019-2023, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019-2021, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -24,21 +22,19 @@
#include "config.h" #include "config.h"
#include <memory>
#include <QtGlobal> #include <QtGlobal>
#include <QObject> #include <QObject>
#include <QMutex> #include <QMutex>
#include <QPair>
#include <QSet> #include <QSet>
#include <QHash> #include <QHash>
#include <QMultiMap>
#include <QQueue> #include <QQueue>
#include <QByteArray> #include <QByteArray>
#include <QString> #include <QString>
#include <QImage> #include <QImage>
#include <QPixmap>
#include "core/song.h" #include "core/song.h"
#include "core/tagreaderclient.h"
#include "albumcoverloaderoptions.h" #include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h" #include "albumcoverloaderresult.h"
#include "albumcoverimageresult.h" #include "albumcoverimageresult.h"
@@ -47,111 +43,96 @@ class QThread;
class QNetworkReply; class QNetworkReply;
class NetworkAccessManager; class NetworkAccessManager;
using std::shared_ptr;
class AlbumCoverLoader : public QObject { class AlbumCoverLoader : public QObject {
Q_OBJECT Q_OBJECT
public: public:
explicit AlbumCoverLoader(QObject *parent = nullptr); explicit AlbumCoverLoader(QObject *parent = nullptr);
enum class State {
None,
Manual,
Automatic
};
void ExitAsync(); void ExitAsync();
void Stop() { stop_requested_ = true; } void Stop() { stop_requested_ = true; }
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const Song &song); 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 AlbumCoverImageResult &album_cover);
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QImage &image); quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QImage &image);
void CancelTask(const quint64 id); void CancelTask(const quint64 id);
void CancelTasks(const QSet<quint64> &ids); void CancelTasks(const QSet<quint64> &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<QUrl> &urls, const QString &cover_filename);
quint64 SaveEmbeddedCoverAsync(const QList<QUrl> &urls, const QImage &image);
quint64 SaveEmbeddedCoverAsync(const QList<QUrl> &urls, const QByteArray &image_data);
signals: signals:
void ExitFinished(); void ExitFinished();
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result); void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
void SaveEmbeddedCoverAsyncFinished(const quint64 id, const bool success, const bool cleared);
protected slots: private:
void Exit(); class Task {
void ProcessTasks(); public:
void RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url); 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); quint64 id;
void SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QImage &image); bool success;
void SaveEmbeddedCover(const quint64 id, const QString &song_filename, const QByteArray &image_data);
void SaveEmbeddedCover(const quint64 id, const QList<QUrl> &urls, const QImage &image);
void SaveEmbeddedCover(const quint64 id, const QList<QUrl> &urls, const QString &cover_filename);
void SaveEmbeddedCover(const quint64 id, const QList<QUrl> &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) {}
AlbumCoverLoaderOptions options; 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; Song song;
AlbumCoverImageResult album_cover; AlbumCoverImageResult album_cover;
State state; AlbumCoverLoaderResult::Type result_type;
AlbumCoverLoaderResult::Type type;
bool art_updated; bool art_updated;
int redirects; int redirects;
}; };
using TaskPtr = std::shared_ptr<Task>; using TaskPtr = shared_ptr<Task>;
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;
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; AlbumCoverLoaderResult::Type type;
AlbumCoverImageResult album_cover; Status status;
}; };
private:
quint64 EnqueueTask(TaskPtr task); quint64 EnqueueTask(TaskPtr task);
void ProcessTask(TaskPtr task); void ProcessTask(TaskPtr task);
void NextState(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_; private slots:
void Exit();
bool stop_requested_; void ProcessTasks();
void LoadRemoteImageFinished(QNetworkReply *reply, TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url);
QMutex mutex_load_image_async_;
QMutex mutex_save_image_async_;
QQueue<TaskPtr> tasks_;
QHash<QNetworkReply*, TaskPtr> remote_tasks_;
quint64 load_image_async_id_;
quint64 save_image_async_id_;
private:
static const int kMaxRedirects = 3; static const int kMaxRedirects = 3;
NetworkAccessManager *network_;
bool stop_requested_;
QMutex mutex_load_image_async_;
QQueue<TaskPtr> tasks_;
quint64 load_image_async_id_;
QThread *original_thread_; QThread *original_thread_;
QMultiMap<quint64, TagReaderReply*> tagreader_save_embedded_art_requests_;
}; };
#endif // ALBUMCOVERLOADER_H #endif // ALBUMCOVERLOADER_H

View File

@@ -0,0 +1,59 @@
/*
* Strawberry Music Player
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "albumcoverloaderoptions.h"
#include <QSettings>
#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;
}

View File

@@ -1,8 +1,6 @@
/* /*
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2012, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -22,34 +20,40 @@
#ifndef ALBUMCOVERLOADEROPTIONS_H #ifndef ALBUMCOVERLOADEROPTIONS_H
#define ALBUMCOVERLOADEROPTIONS_H #define ALBUMCOVERLOADEROPTIONS_H
#include "config.h" #include <QList>
#include <QImage> #include <QImage>
#include <QSize> #include <QSize>
class AlbumCoverLoaderOptions { class AlbumCoverLoaderOptions {
public: public:
explicit AlbumCoverLoaderOptions() enum class Option {
: get_image_data_(true), NoOptions = 0x0,
get_image_(true), RawImageData = 0x2,
scale_output_image_(true), OriginalImage = 0x4,
pad_output_image_(true), ScaledImage = 0x8,
create_thumbnail_(false), PadScaledImage = 0x16
pad_thumbnail_image_(false), };
desired_height_(120), Q_DECLARE_FLAGS(Options, Option)
thumbnail_size_(120, 120) {}
bool get_image_data_; enum class Type {
bool get_image_; Embedded,
bool scale_output_image_; Automatic,
bool pad_output_image_; Manual,
bool create_thumbnail_; Unset
bool pad_thumbnail_image_; };
int desired_height_; using Types = QList<Type>;
QSize thumbnail_size_;
QImage default_output_image_; 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>() << AlbumCoverLoaderOptions::Type::Embedded << AlbumCoverLoaderOptions::Type::Automatic << AlbumCoverLoaderOptions::Type::Manual);
QImage default_scaled_image_;
QImage default_thumbnail_image_; 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 #endif // ALBUMCOVERLOADEROPTIONS_H

View File

@@ -32,35 +32,33 @@ class AlbumCoverLoaderResult {
enum class Type { enum class Type {
None, None,
ManuallyUnset, Unset,
Embedded, Embedded,
Automatic, Automatic,
Manual, Manual
Remote
}; };
explicit AlbumCoverLoaderResult(const bool _success = false, explicit AlbumCoverLoaderResult(const bool _success = false,
const Type _type = Type::None, const Type _type = Type::None,
AlbumCoverImageResult _album_cover = AlbumCoverImageResult(), AlbumCoverImageResult _album_cover = AlbumCoverImageResult(),
const QImage &_image_scaled = QImage(), const QImage &_image_scaled = QImage(),
const QImage &_image_thumbnail = QImage(), const bool _remote_cover = false,
const bool _updated = false) : const bool _updated = false) :
success(_success), success(_success),
type(_type), type(_type),
album_cover(_album_cover), album_cover(_album_cover),
image_scaled(_image_scaled), image_scaled(_image_scaled),
image_thumbnail(_image_thumbnail), remote_cover(_remote_cover),
updated(_updated) {} updated(_updated) {}
bool success; bool success;
Type type; Type type;
AlbumCoverImageResult album_cover; AlbumCoverImageResult album_cover;
QImage image_scaled; QImage image_scaled;
QImage image_thumbnail; bool remote_cover;
bool updated; bool updated;
QUrl temp_cover_url; QUrl temp_cover_url;
}; };
Q_DECLARE_METATYPE(AlbumCoverLoaderResult) Q_DECLARE_METATYPE(AlbumCoverLoaderResult)

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -95,6 +95,7 @@
#include "ui_albumcovermanager.h" #include "ui_albumcovermanager.h"
const char *AlbumCoverManager::kSettingsGroup = "CoverManager"; const char *AlbumCoverManager::kSettingsGroup = "CoverManager";
constexpr int AlbumCoverManager::kThumbnailSize = 120;
AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QMainWindow *mainwindow, QWidget *parent) AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QMainWindow *mainwindow, QWidget *parent)
: QMainWindow(parent), : QMainWindow(parent),
@@ -112,7 +113,7 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
cover_exporter_(new AlbumCoverExporter(this)), cover_exporter_(new AlbumCoverExporter(this)),
artist_icon_(IconLoader::Load("folder-sound")), artist_icon_(IconLoader::Load("folder-sound")),
all_artists_icon_(IconLoader::Load("library-music")), 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_)), icon_nocover_item_(QPixmap::fromImage(image_nocover_thumbnail_)),
context_menu_(new QMenu(this)), context_menu_(new QMenu(this)),
progress_bar_(new QProgressBar(this)), progress_bar_(new QProgressBar(this)),
@@ -150,11 +151,6 @@ AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collec
QShortcut *close = new QShortcut(QKeySequence::Close, this); QShortcut *close = new QShortcut(QKeySequence::Close, this);
QObject::connect(close, &QShortcut::activated, this, &AlbumCoverManager::close); 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(); EnableCoversButtons();
} }
@@ -236,7 +232,6 @@ void AlbumCoverManager::Init() {
s.endGroup(); s.endGroup();
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &AlbumCoverManager::AlbumCoverLoaded); 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_); cover_searcher_->Init(cover_fetcher_);
@@ -248,6 +243,7 @@ void AlbumCoverManager::showEvent(QShowEvent *e) {
if (!e->spontaneous()) { if (!e->spontaneous()) {
LoadGeometry(); LoadGeometry();
cover_types_ = AlbumCoverLoaderOptions::LoadTypes();
album_cover_choice_controller_->ReloadSettings(); album_cover_choice_controller_->ReloadSettings();
Reset(); Reset();
} }
@@ -321,7 +317,6 @@ void AlbumCoverManager::CancelRequests() {
#endif #endif
cover_loading_tasks_.clear(); cover_loading_tasks_.clear();
cover_save_tasks_.clear(); cover_save_tasks_.clear();
cover_save_tasks2_.clear();
cover_exporter_->Cancel(); 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. // 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); 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 // Don't show songs without an album, obviously
if (info.album.isEmpty()) continue; if (album_info.album.isEmpty()) continue;
QString display_text; QString display_text;
if (current->type() == Specific_Artist) { if (current->type() == Specific_Artist) {
display_text = info.album; display_text = album_info.album;
} }
else { 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); AlbumItem *album_item = new AlbumItem(icon_nocover_item_, display_text, ui_->albums);
item->setData(Role_AlbumArtist, info.album_artist); album_item->setData(Role_AlbumArtist, album_info.album_artist);
item->setData(Role_Album, info.album); album_item->setData(Role_Album, album_info.album);
item->setData(Role_Filetype, QVariant::fromValue(info.filetype)); album_item->setData(Role_Filetype, QVariant::fromValue(album_info.filetype));
item->setData(Role_CuePath, info.cue_path); album_item->setData(Role_CuePath, album_info.cue_path);
item->setData(Qt::TextAlignmentRole, QVariant(Qt::AlignTop | Qt::AlignHCenter)); album_item->setData(Qt::TextAlignmentRole, QVariant(Qt::AlignTop | Qt::AlignHCenter));
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); album_item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
item->urls = info.urls; album_item->urls = album_info.urls;
if (info.album_artist.isEmpty()) { if (album_info.album_artist.isEmpty()) {
item->setToolTip(info.album); album_item->setToolTip(album_info.album);
} }
else { 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()) { album_item->setData(Role_ArtEmbedded, album_info.art_embedded);
item->setData(Role_PathAutomatic, info.art_automatic); album_item->setData(Role_ArtAutomatic, album_info.art_automatic);
item->setData(Role_PathManual, info.art_manual); album_item->setData(Role_ArtManual, album_info.art_manual);
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, info.art_automatic, info.art_manual, info.urls.first()); album_item->setData(Role_ArtUnset, album_info.art_unset);
cover_loading_tasks_[id] = item;
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; 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) { if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type::Unset) {
item->setIcon(icon_nocover_item_); album_item->setIcon(icon_nocover_item_);
} }
else { 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(); UpdateFilter();
} }
@@ -471,13 +465,13 @@ void AlbumCoverManager::UpdateFilter() {
qint32 without_cover = 0; qint32 without_cover = 0;
for (int i = 0; i < ui_->albums->count(); ++i) { for (int i = 0; i < ui_->albums->count(); ++i) {
AlbumItem *item = static_cast<AlbumItem*>(ui_->albums->item(i)); AlbumItem *album_item = static_cast<AlbumItem*>(ui_->albums->item(i));
bool should_hide = ShouldHide(*item, filter, hide_covers); bool should_hide = ShouldHide(*album_item, filter, hide_covers);
item->setHidden(should_hide); album_item->setHidden(should_hide);
if (!should_hide) { if (!should_hide) {
++total_count; ++total_count;
if (!ItemHasCover(*item)) { if (!ItemHasCover(*album_item)) {
++without_cover; ++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) { if (hide_covers == HideCovers::WithCovers && has_cover) {
return true; return true;
} }
@@ -504,8 +498,8 @@ bool AlbumCoverManager::ShouldHide(const AlbumItem &item, const QString &filter,
QStringList query = filter.split(' '); QStringList query = filter.split(' ');
for (const QString &s : query) { for (const QString &s : query) {
bool in_text = item.text().contains(s, Qt::CaseInsensitive); bool in_text = album_item.text().contains(s, Qt::CaseInsensitive);
bool in_albumartist = item.data(Role_AlbumArtist).toString().contains(s, Qt::CaseInsensitive); bool in_albumartist = album_item.data(Role_AlbumArtist).toString().contains(s, Qt::CaseInsensitive);
if (!in_text && !in_albumartist) { if (!in_text && !in_albumartist) {
return true; return true;
} }
@@ -518,12 +512,12 @@ bool AlbumCoverManager::ShouldHide(const AlbumItem &item, const QString &filter,
void AlbumCoverManager::FetchAlbumCovers() { void AlbumCoverManager::FetchAlbumCovers() {
for (int i = 0; i < ui_->albums->count(); ++i) { for (int i = 0; i < ui_->albums->count(); ++i) {
AlbumItem *item = static_cast<AlbumItem*>(ui_->albums->item(i)); AlbumItem *album_item = static_cast<AlbumItem*>(ui_->albums->item(i));
if (item->isHidden()) continue; if (album_item->isHidden()) continue;
if (ItemHasCover(*item)) continue; if (ItemHasCover(*album_item)) continue;
quint64 id = cover_fetcher_->FetchAlbumCover(item->data(Role_AlbumArtist).toString(), item->data(Role_Album).toString(), QString(), true); quint64 id = cover_fetcher_->FetchAlbumCover(album_item->data(Role_AlbumArtist).toString(), album_item->data(Role_Album).toString(), QString(), true);
cover_fetching_tasks_[id] = item; cover_fetching_tasks_[id] = album_item;
jobs_++; jobs_++;
} }
@@ -541,9 +535,9 @@ void AlbumCoverManager::AlbumCoverFetched(const quint64 id, const AlbumCoverImag
if (!cover_fetching_tasks_.contains(id)) return; 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()) { if (!result.image.isNull()) {
SaveAndSetCover(item, result); SaveAndSetCover(album_item, result);
} }
if (cover_fetching_tasks_.isEmpty()) { if (cover_fetching_tasks_.isEmpty()) {
@@ -593,13 +587,13 @@ bool AlbumCoverManager::eventFilter(QObject *obj, QEvent *e) {
bool some_unset = false; bool some_unset = false;
bool some_clear = false; bool some_clear = false;
for (QListWidgetItem *item : context_menu_items_) { for (QListWidgetItem *list_widget_item : context_menu_items_) {
AlbumItem *album_item = static_cast<AlbumItem*>(item); AlbumItem *album_item = static_cast<AlbumItem*>(list_widget_item);
if (ItemHasCover(*album_item)) some_with_covers = true; 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; 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; some_clear = true;
} }
} }
@@ -623,19 +617,19 @@ bool AlbumCoverManager::eventFilter(QObject *obj, QEvent *e) {
} }
Song AlbumCoverManager::GetSingleSelectionAsSong() { 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() { 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); Song result(Song::Source::Collection);
QString title = item->data(Role_Album).toString(); QString title = album_item->data(Role_Album).toString();
QString artist_name = item->data(Role_AlbumArtist).toString(); QString artist_name = album_item->data(Role_AlbumArtist).toString();
if (!artist_name.isEmpty()) { if (!artist_name.isEmpty()) {
result.set_title(artist_name + " - " + title); result.set_title(artist_name + " - " + title);
} }
@@ -643,27 +637,30 @@ Song AlbumCoverManager::ItemAsSong(AlbumItem *item) {
result.set_title(title); result.set_title(title);
} }
result.set_artist(item->data(Role_AlbumArtist).toString()); result.set_artist(album_item->data(Role_AlbumArtist).toString());
result.set_albumartist(item->data(Role_AlbumArtist).toString()); result.set_albumartist(album_item->data(Role_AlbumArtist).toString());
result.set_album(item->data(Role_Album).toString()); result.set_album(album_item->data(Role_Album).toString());
result.set_filetype(static_cast<Song::FileType>(item->data(Role_Filetype).toInt())); result.set_filetype(static_cast<Song::FileType>(album_item->data(Role_Filetype).toInt()));
result.set_url(item->urls.first()); result.set_url(album_item->urls.first());
result.set_cue_path(item->data(Role_CuePath).toString()); result.set_cue_path(album_item->data(Role_CuePath).toString());
result.set_art_automatic(item->data(Role_PathAutomatic).toUrl()); result.set_art_embedded(album_item->data(Role_ArtEmbedded).toBool());
result.set_art_manual(item->data(Role_PathManual).toUrl()); 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 // force validity
result.set_valid(true); result.set_valid(true);
result.set_id(0); result.set_id(0);
return result; return result;
} }
void AlbumCoverManager::ShowCover() { void AlbumCoverManager::ShowCover() {
Song song = GetSingleSelectionAsSong(); const Song song = GetSingleSelectionAsSong();
if (!song.is_valid()) return; if (!song.is_valid()) return;
album_cover_choice_controller_->ShowCover(song); album_cover_choice_controller_->ShowCover(song);
@@ -672,8 +669,8 @@ void AlbumCoverManager::ShowCover() {
void AlbumCoverManager::FetchSingleCover() { void AlbumCoverManager::FetchSingleCover() {
for (QListWidgetItem *item : context_menu_items_) { for (QListWidgetItem *list_widget_item : context_menu_items_) {
AlbumItem *album_item = static_cast<AlbumItem*>(item); AlbumItem *album_item = static_cast<AlbumItem*>(list_widget_item);
quint64 id = cover_fetcher_->FetchAlbumCover(album_item->data(Role_AlbumArtist).toString(), album_item->data(Role_Album).toString(), QString(), false); 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; cover_fetching_tasks_[id] = album_item;
jobs_++; 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); album_item->setData(Role_ArtManual, cover_url);
item->setData(Role_PathManual, cover_url); album_item->setData(Role_ArtUnset, false);
cover_loading_tasks_[id] = item; LoadAlbumCoverAsync(album_item);
} }
@@ -709,26 +706,31 @@ void AlbumCoverManager::LoadCoverFromFile() {
void AlbumCoverManager::SaveCoverToFile() { void AlbumCoverManager::SaveCoverToFile() {
Song song = GetSingleSelectionAsSong(); Song song = GetSingleSelectionAsSong();
if (!song.is_valid() || song.has_manually_unset_cover()) return; if (!song.is_valid() || song.art_unset()) return;
AlbumCoverImageResult result;
// Load the image from disk // Load the image from disk
AlbumCoverImageResult result;
if (!song.art_manual().isEmpty() && !song.has_manually_unset_cover() && song.art_manual().isLocalFile() && QFile::exists(song.art_manual().toLocalFile())) { for (const AlbumCoverLoaderOptions::Type cover_type : cover_types_) {
result.image_data = Utilities::ReadDataFromFile(song.art_manual().toLocalFile()); switch (cover_type) {
} case AlbumCoverLoaderOptions::Type::Unset:
else if (!song.art_manual().isEmpty() && !song.art_manual().path().isEmpty() && song.art_manual().scheme().isEmpty() && QFile::exists(song.art_manual().path())) { return;
result.image_data = Utilities::ReadDataFromFile(song.art_manual().path()); case AlbumCoverLoaderOptions::Type::Embedded:
} if (song.art_embedded()) {
else if (song.has_embedded_cover()) {
result.image_data = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song.url().toLocalFile()); 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())) { break;
case AlbumCoverLoaderOptions::Type::Automatic:
if (song.art_automatic_is_valid()) {
result.image_data = Utilities::ReadDataFromFile(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())) { break;
result.image_data = Utilities::ReadDataFromFile(song.art_automatic().path()); 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; if (!result.is_valid()) return;
@@ -778,24 +780,32 @@ void AlbumCoverManager::SaveImageToAlbums(Song *song, const AlbumCoverImageResul
} }
break; break;
case CoverOptions::CoverType::Embedded: case CoverOptions::CoverType::Embedded:
cover_url = QUrl::fromLocalFile(Song::kEmbeddedCover); cover_url.clear();
break; break;
} }
// Force the found cover on all of the selected items // Force the found cover on all of the selected items
QList<QUrl> urls; QList<QUrl> urls;
QList<AlbumItem*> album_items; QList<AlbumItem*> album_items;
for (QListWidgetItem *item : context_menu_items_) { for (QListWidgetItem *list_widget_item : context_menu_items_) {
AlbumItem *album_item = static_cast<AlbumItem*>(item); AlbumItem *album_item = static_cast<AlbumItem*>(list_widget_item);
switch (album_cover_choice_controller_->get_save_album_cover_type()) { switch (album_cover_choice_controller_->get_save_album_cover_type()) {
case CoverOptions::CoverType::Cache: case CoverOptions::CoverType::Cache:
case CoverOptions::CoverType::Album:{ case CoverOptions::CoverType::Album:{
Song current_song = ItemAsSong(album_item); Song current_song = AlbumItemAsSong(album_item);
album_cover_choice_controller_->SaveArtManualToSong(&current_song, cover_url); album_cover_choice_controller_->SaveArtManualToSong(&current_song, cover_url);
UpdateCoverInList(album_item, cover_url); UpdateCoverInList(album_item, cover_url);
break; break;
} }
case CoverOptions::CoverType::Embedded:{ 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; urls << album_item->urls;
album_items << album_item; album_items << album_item;
break; 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() { void AlbumCoverManager::UnsetCover() {
Song song = GetFirstSelectedAsSong(); if (context_menu_items_.isEmpty()) return;
if (!song.is_valid()) return;
AlbumItem *first_album_item = static_cast<AlbumItem*>(context_menu_items_[0]);
QUrl cover_url = album_cover_choice_controller_->UnsetCover(&song);
// Force the 'none' cover on all of the selected items // Force the 'none' cover on all of the selected items
for (QListWidgetItem *item : context_menu_items_) { for (QListWidgetItem *list_widget_item : context_menu_items_) {
AlbumItem *album_item = static_cast<AlbumItem*>(item); AlbumItem *album_item = static_cast<AlbumItem*>(list_widget_item);
album_item->setIcon(icon_nocover_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 Song current_song = AlbumItemAsSong(album_item);
if (album_item != first_album_item) { album_cover_choice_controller_->UnsetAlbumCoverForSong(&current_song);
Song current_song = ItemAsSong(album_item);
album_cover_choice_controller_->SaveArtManualToSong(&current_song, cover_url);
}
} }
} }
void AlbumCoverManager::ClearCover() { void AlbumCoverManager::ClearCover() {
Song song = GetFirstSelectedAsSong(); if (context_menu_items_.isEmpty()) return;
if (!song.is_valid()) return;
AlbumItem *first_album_item = static_cast<AlbumItem*>(context_menu_items_[0]);
album_cover_choice_controller_->ClearCover(&song);
// Force the 'none' cover on all of the selected items // Force the 'none' cover on all of the selected items
for (QListWidgetItem *item : context_menu_items_) { for (QListWidgetItem *list_widget_item : context_menu_items_) {
AlbumItem *album_item = static_cast<AlbumItem*>(item); AlbumItem *album_item = static_cast<AlbumItem*>(list_widget_item);
album_item->setIcon(icon_nocover_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 Song current_song = AlbumItemAsSong(album_item);
if (album_item != first_album_item) { album_cover_choice_controller_->ClearAlbumCoverForSong(&current_song);
Song current_song = ItemAsSong(album_item);
album_cover_choice_controller_->SaveArtManualToSong(&current_song, QUrl(), false);
}
} }
} }
void AlbumCoverManager::DeleteCover() { void AlbumCoverManager::DeleteCover() {
for (QListWidgetItem *item : context_menu_items_) { for (QListWidgetItem *list_widget_item : context_menu_items_) {
AlbumItem *album_item = static_cast<AlbumItem*>(item); AlbumItem *album_item = static_cast<AlbumItem*>(list_widget_item);
Song song = ItemAsSong(album_item); Song song = AlbumItemAsSong(album_item);
album_cover_choice_controller_->DeleteCover(&song); album_cover_choice_controller_->DeleteCover(&song);
album_item->setIcon(icon_nocover_item_); album_item->setIcon(icon_nocover_item_);
album_item->setData(Role_PathManual, QUrl()); album_item->setData(Role_ArtEmbedded, QUrl());
album_item->setData(Role_PathAutomatic, 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) { void AlbumCoverManager::AlbumDoubleClicked(const QModelIndex &idx) {
AlbumItem *item = static_cast<AlbumItem*>(idx.internalPointer()); AlbumItem *album_item = static_cast<AlbumItem*>(idx.internalPointer());
if (!item) return; if (!album_item) return;
album_cover_choice_controller_->ShowCover(ItemAsSong(item)); 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 QList<QUrl> &urls = album_item->urls;
const QString album = item->data(Role_Album).toString(); const Song::FileType filetype = static_cast<Song::FileType>(album_item->data(Role_Filetype).toInt());
const QList<QUrl> &urls = item->urls; const bool has_cue = !album_item->data(Role_CuePath).toString().isEmpty();
const Song::FileType filetype = static_cast<Song::FileType>(item->data(Role_Filetype).toInt());
const bool has_cue = !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 (album_cover_choice_controller_->get_save_album_cover_type() == CoverOptions::CoverType::Embedded && Song::save_embedded_cover_supported(filetype) && !has_cue) {
if (result.is_jpeg()) { for (const QUrl &url : urls) {
quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image_data); const bool art_embedded = !result.image_data.isEmpty();
cover_save_tasks_.insert(id, item); 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]() {
else if (!result.image.isNull()) { SaveEmbeddedCoverFinished(reply, album_item, url, art_embedded);
quint64 id = app_->album_cover_loader()->SaveEmbeddedCoverAsync(urls, result.image); });
cover_save_tasks_.insert(id, item); cover_save_tasks_.insert(album_item, url);
}
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);
} }
} }
else { else {
const QString albumartist = album_item->data(Role_AlbumArtist).toString();
const QString album = album_item->data(Role_Album).toString();
QUrl cover_url; QUrl cover_url;
if (!result.cover_url.isEmpty() && result.cover_url.isValid() && result.cover_url.isLocalFile()) { if (!result.cover_url.isEmpty() && result.cover_url.isValid() && result.cover_url.isLocalFile()) {
cover_url = result.cover_url; cover_url = result.cover_url;
@@ -990,7 +971,7 @@ void AlbumCoverManager::SaveAndSetCover(AlbumItem *item, const AlbumCoverImageRe
collection_backend_->UpdateManualAlbumArtAsync(albumartist, album, cover_url); collection_backend_->UpdateManualAlbumArtAsync(albumartist, album, cover_url);
// Update the icon in our list // Update the icon in our list
UpdateCoverInList(item, cover_url); UpdateCoverInList(album_item, cover_url);
} }
} }
@@ -1006,16 +987,17 @@ void AlbumCoverManager::ExportCovers() {
DisableCoversButtons(); DisableCoversButtons();
cover_exporter_->SetDialogResult(result); cover_exporter_->SetDialogResult(result);
cover_exporter_->SetCoverTypes(cover_types_);
for (int i = 0; i < ui_->albums->count(); ++i) { for (int i = 0; i < ui_->albums->count(); ++i) {
AlbumItem *item = static_cast<AlbumItem*>(ui_->albums->item(i)); AlbumItem *album_item = static_cast<AlbumItem*>(ui_->albums->item(i));
// skip hidden and coverless albums // skip hidden and coverless albums
if (item->isHidden() || !ItemHasCover(*item)) { if (album_item->isHidden() || !ItemHasCover(*album_item)) {
continue; continue;
} }
cover_exporter_->AddExportRequest(ItemAsSong(item)); cover_exporter_->AddExportRequest(AlbumItemAsSong(album_item));
} }
if (cover_exporter_->request_count() > 0) { 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 { bool AlbumCoverManager::ItemHasCover(const AlbumItem &album_item) const {
return item.icon().cacheKey() != icon_nocover_item_.cacheKey(); 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)) { if (cover_save_tasks_.contains(album_item, url)) {
AlbumItem *album_item = cover_save_tasks_.take(id); cover_save_tasks_.remove(album_item, url);
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 (!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);
} }

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -37,6 +37,7 @@
#include <QIcon> #include <QIcon>
#include "core/song.h" #include "core/song.h"
#include "core/tagreaderclient.h"
#include "albumcoverloaderoptions.h" #include "albumcoverloaderoptions.h"
#include "albumcoverloaderresult.h" #include "albumcoverloaderresult.h"
#include "albumcoverchoicecontroller.h" #include "albumcoverchoicecontroller.h"
@@ -79,8 +80,6 @@ class AlbumCoverManager : public QMainWindow {
explicit AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QMainWindow *mainwindow, QWidget *parent = nullptr); explicit AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QMainWindow *mainwindow, QWidget *parent = nullptr);
~AlbumCoverManager() override; ~AlbumCoverManager() override;
static const char *kSettingsGroup;
void Reset(); void Reset();
void Init(); void Init();
@@ -108,8 +107,10 @@ class AlbumCoverManager : public QMainWindow {
enum Role { enum Role {
Role_AlbumArtist = Qt::UserRole + 1, Role_AlbumArtist = Qt::UserRole + 1,
Role_Album, Role_Album,
Role_PathAutomatic, Role_ArtEmbedded,
Role_PathManual, Role_ArtAutomatic,
Role_ArtManual,
Role_ArtUnset,
Role_Filetype, Role_Filetype,
Role_CuePath, Role_CuePath,
Role_ImageData, 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. // 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 GetFirstSelectedAsSong();
Song ItemAsSong(QListWidgetItem *item) { return ItemAsSong(static_cast<AlbumItem*>(item)); } Song AlbumItemAsSong(QListWidgetItem *list_widget_item) { return AlbumItemAsSong(static_cast<AlbumItem*>(list_widget_item)); }
static Song ItemAsSong(AlbumItem *item); static Song AlbumItemAsSong(AlbumItem *album_item);
void UpdateStatusText(); void UpdateStatusText();
bool ShouldHide(const AlbumItem &item, const QString &filter, const HideCovers hide_covers) const; bool ShouldHide(const AlbumItem &album_item, const QString &filter, const HideCovers hide_covers) const;
void SaveAndSetCover(AlbumItem *item, const AlbumCoverImageResult &result); void SaveAndSetCover(AlbumItem *album_item, const AlbumCoverImageResult &result);
void SaveImageToAlbums(Song *song, const AlbumCoverImageResult &result); void SaveImageToAlbums(Song *song, const AlbumCoverImageResult &result);
SongList GetSongsInAlbums(const QModelIndexList &indexes) const; SongList GetSongsInAlbums(const QModelIndexList &indexes) const;
SongMimeData *GetMimeDataForAlbums(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: signals:
void Error(const QString &error); void Error(const QString &error);
@@ -176,12 +179,15 @@ class AlbumCoverManager : public QMainWindow {
void AddSelectedToPlaylist(); void AddSelectedToPlaylist();
void LoadSelectedToPlaylist(); 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 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: private:
static const char *kSettingsGroup;
static const int kThumbnailSize;
Ui_CoverManager *ui_; Ui_CoverManager *ui_;
QMainWindow *mainwindow_; QMainWindow *mainwindow_;
Application *app_; Application *app_;
@@ -192,7 +198,6 @@ class AlbumCoverManager : public QMainWindow {
QAction *filter_with_covers_; QAction *filter_with_covers_;
QAction *filter_without_covers_; QAction *filter_without_covers_;
AlbumCoverLoaderOptions cover_loader_options_;
QMap<quint64, AlbumItem*> cover_loading_tasks_; QMap<quint64, AlbumItem*> cover_loading_tasks_;
AlbumCoverFetcher *cover_fetcher_; AlbumCoverFetcher *cover_fetcher_;
@@ -215,11 +220,11 @@ class AlbumCoverManager : public QMainWindow {
QPushButton *abort_progress_; QPushButton *abort_progress_;
int jobs_; int jobs_;
QMultiMap<quint64, AlbumItem*> cover_save_tasks_; QMultiMap<AlbumItem*, QUrl> cover_save_tasks_;
QList<AlbumItem*> cover_save_tasks2_;
QListWidgetItem *all_artists_; QListWidgetItem *all_artists_;
AlbumCoverLoaderOptions::Types cover_types_;
}; };
#endif // ALBUMCOVERMANAGER_H #endif // ALBUMCOVERMANAGER_H

View File

@@ -128,14 +128,6 @@ AlbumCoverSearcher::AlbumCoverSearcher(const QIcon &no_cover_icon, Application *
ui_->covers->setItemDelegate(new SizeOverlayDelegate(this)); ui_->covers->setItemDelegate(new SizeOverlayDelegate(this));
ui_->covers->setModel(model_); 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(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &AlbumCoverSearcher::AlbumCoverLoaded);
QObject::connect(ui_->search, &QPushButton::clicked, this, &AlbumCoverSearcher::Search); QObject::connect(ui_->search, &QPushButton::clicked, this, &AlbumCoverSearcher::Search);
QObject::connect(ui_->covers, &GroupedIconView::doubleClicked, this, &AlbumCoverSearcher::CoverDoubleClicked); 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; 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; QStandardItem *item = new QStandardItem;
item->setIcon(no_cover_icon_); 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 (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()); model_->removeRow(item->row());
return; return;
} }
const QPixmap pixmap = QPixmap::fromImage(result.image_thumbnail); const QPixmap pixmap = QPixmap::fromImage(result.image_scaled);
if (pixmap.isNull()) { if (pixmap.isNull()) {
model_->removeRow(item->row()); model_->removeRow(item->row());
return; return;

View File

@@ -107,7 +107,6 @@ class AlbumCoverSearcher : public QDialog {
QStandardItemModel *model_; QStandardItemModel *model_;
QIcon no_cover_icon_; QIcon no_cover_icon_;
AlbumCoverLoaderOptions options_;
AlbumCoverFetcher *fetcher_; AlbumCoverFetcher *fetcher_;
quint64 id_; quint64 id_;

View File

@@ -28,20 +28,19 @@
#include "core/song.h" #include "core/song.h"
#include "core/tagreaderclient.h" #include "core/tagreaderclient.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverexport.h" #include "albumcoverexport.h"
#include "coverexportrunnable.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), : QObject(parent),
dialog_result_(dialog_result), dialog_result_(dialog_result),
cover_types_(cover_types),
song_(song) {} song_(song) {}
void CoverExportRunnable::run() { void CoverExportRunnable::run() {
QString cover_path = GetCoverPath(); if (song_.art_unset() || (!song_.art_embedded() && !song_.art_automatic_is_valid() && !song_.art_manual_is_valid())) {
// Manually unset?
if (cover_path.isEmpty()) {
EmitCoverSkipped(); EmitCoverSkipped();
} }
else { 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. // 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: // 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 // - 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. // In all other cases, the faster ExportCover() method will be used.
void CoverExportRunnable::ProcessAndExportCover() { void CoverExportRunnable::ProcessAndExportCover() {
QString cover_path = GetCoverPath(); QImage image;
QString extension;
// either embedded or disk - the one we'll export for the current album for (const AlbumCoverLoaderOptions::Type cover_type : cover_types_) {
QImage cover; switch (cover_type) {
case AlbumCoverLoaderOptions::Type::Unset:
QImage embedded_cover; if (song_.art_unset()) {
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(); EmitCoverSkipped();
return; return;
} }
cover = embedded_cover; 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;
}
if (!image.isNull() && !extension.isEmpty()) break;
} }
// load a file cover which iss mandatory if there's no embedded cover if (image.isNull() || extension.isEmpty()) {
disk_cover.load(cover_path);
if (embedded_cover.isNull()) {
if (disk_cover.isNull()) {
EmitCoverSkipped(); EmitCoverSkipped();
return; return;
} }
cover = disk_cover;
}
// rescale if necessary // Rescale if necessary
if (dialog_result_.IsSizeForced()) { 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 cover_dir = song_.url().toLocalFile().section('/', 0, -2);
QString extension = cover_path.section('.', -1); QString new_file = cover_dir + '/' + dialog_result_.filename_ + '.' + (song_.art_embedded() ? "jpg" : extension);
QString new_file = dir + '/' + dialog_result_.filename_ + '.' + (cover_path == Song::kEmbeddedCover ? "jpg" : extension);
// If the file exists, do not override! // If the file exists, do not override!
if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode::None && QFile::exists(new_file)) { if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode::None && QFile::exists(new_file)) {
@@ -127,16 +119,15 @@ void CoverExportRunnable::ProcessAndExportCover() {
return; 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 (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 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) { if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode::Smaller) {
QImage existing; QImage image_existing;
existing.load(new_file); image_existing.load(new_file);
if (existing.isNull() || existing.size().height() >= cover.size().height() || existing.size().width() >= cover.size().width()) {
if (image_existing.isNull() || image_existing.size().height() >= image.size().height() || image_existing.size().width() >= image.size().width()) {
EmitCoverSkipped(); EmitCoverSkipped();
return; return;
} }
@@ -148,7 +139,7 @@ void CoverExportRunnable::ProcessAndExportCover() {
} }
} }
if (cover.save(new_file)) { if (image.save(new_file)) {
EmitCoverExported(); EmitCoverExported();
} }
else { else {
@@ -160,12 +151,55 @@ void CoverExportRunnable::ProcessAndExportCover() {
// Exports a single album cover using a "copy file" approach. // Exports a single album cover using a "copy file" approach.
void CoverExportRunnable::ExportCover() { 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); for (const AlbumCoverLoaderOptions::Type cover_type : cover_types_) {
QString extension = cover_path.section('.', -1); 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 the file exists, do not override!
if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode::None && QFile::exists(new_file)) { if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode::None && QFile::exists(new_file)) {
@@ -181,10 +215,8 @@ void CoverExportRunnable::ExportCover() {
} }
} }
if (cover_path == Song::kEmbeddedCover) { if (embedded_cover) {
// an embedded cover if (!image.save(new_file)) {
QImage embedded = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile());
if (!embedded.save(new_file)) {
EmitCoverSkipped(); EmitCoverSkipped();
return; return;
} }
@@ -196,6 +228,7 @@ void CoverExportRunnable::ExportCover() {
return; return;
} }
} }
EmitCoverExported(); EmitCoverExported();
} }

View File

@@ -29,13 +29,14 @@
#include <QString> #include <QString>
#include "core/song.h" #include "core/song.h"
#include "albumcoverloaderoptions.h"
#include "albumcoverexport.h" #include "albumcoverexport.h"
class CoverExportRunnable : public QObject, public QRunnable { class CoverExportRunnable : public QObject, public QRunnable {
Q_OBJECT Q_OBJECT
public: 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; void run() override;
@@ -49,11 +50,10 @@ class CoverExportRunnable : public QObject, public QRunnable {
void ProcessAndExportCover(); void ProcessAndExportCover();
void ExportCover(); void ExportCover();
QString GetCoverPath();
AlbumCoverExport::DialogResult dialog_result_; AlbumCoverExport::DialogResult dialog_result_;
AlbumCoverLoaderOptions::Types cover_types_;
Song song_; Song song_;
}; };
#endif // COVEREXPORTRUNNABLE_H #endif // COVEREXPORTRUNNABLE_H

View File

@@ -58,7 +58,7 @@ void CoverProviders::ReloadSettings() {
QSettings s; QSettings s;
s.beginGroup(CoversSettingsPage::kSettingsGroup); 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(); s.endGroup();
int i = 0; int i = 0;

View File

@@ -42,18 +42,15 @@ CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *pare
temp_file_pattern_(QDir::tempPath() + "/strawberry-cover-XXXXXX.jpg"), temp_file_pattern_(QDir::tempPath() + "/strawberry-cover-XXXXXX.jpg"),
id_(0) { id_(0) {
options_.get_image_data_ = true; options_.options = AlbumCoverLoaderOptions::Option::RawImageData | AlbumCoverLoaderOptions::Option::OriginalImage | AlbumCoverLoaderOptions::Option::ScaledImage;
options_.get_image_ = true; options_.desired_scaled_size = QSize(120, 120);
options_.scale_output_image_ = false; options_.default_cover = ":/pictures/cdcase.png";
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);
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &CurrentAlbumCoverLoader::TempAlbumCoverLoaded); QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &CurrentAlbumCoverLoader::TempAlbumCoverLoaded);
QObject::connect(app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, this, &CurrentAlbumCoverLoader::LoadAlbumCover); QObject::connect(app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, this, &CurrentAlbumCoverLoader::LoadAlbumCover);
ReloadSettingsAsync();
} }
CurrentAlbumCoverLoader::~CurrentAlbumCoverLoader() { 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) { void CurrentAlbumCoverLoader::LoadAlbumCover(const Song &song) {
last_song_ = song; last_song_ = song;
@@ -92,11 +101,11 @@ void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, AlbumCoverL
} }
QUrl thumbnail_url; QUrl thumbnail_url;
if (!result.image_thumbnail.isNull()) { if (!result.image_scaled.isNull()) {
temp_cover_thumbnail_ = std::make_unique<QTemporaryFile>(temp_file_pattern_); temp_cover_thumbnail_ = std::make_unique<QTemporaryFile>(temp_file_pattern_);
temp_cover_thumbnail_->setAutoRemove(true); temp_cover_thumbnail_->setAutoRemove(true);
if (temp_cover_thumbnail_->open()) { 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()); thumbnail_url = QUrl::fromLocalFile(temp_cover_thumbnail_->fileName());
} }
else { else {
@@ -113,6 +122,6 @@ void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, AlbumCoverL
} }
emit AlbumCoverLoaded(last_song_, result); emit AlbumCoverLoaded(last_song_, result);
emit ThumbnailLoaded(last_song_, thumbnail_url, result.image_thumbnail); emit ThumbnailLoaded(last_song_, thumbnail_url, result.image_scaled);
} }

View File

@@ -48,7 +48,10 @@ class CurrentAlbumCoverLoader : public QObject {
const AlbumCoverLoaderOptions &options() const { return options_; } const AlbumCoverLoaderOptions &options() const { return options_; }
const Song &last_song() const { return last_song_; } const Song &last_song() const { return last_song_; }
void ReloadSettingsAsync();
public slots: public slots:
void ReloadSettings();
void LoadAlbumCover(const Song &song); void LoadAlbumCover(const Song &song);
signals: signals:

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -98,9 +98,10 @@
#include "ui_edittagdialog.h" #include "ui_edittagdialog.h"
#include "tagreadermessages.pb.h" #include "tagreadermessages.pb.h"
const char *EditTagDialog::kTagsDifferentHintText = QT_TR_NOOP("(different across multiple songs)"); const char EditTagDialog::kTagsDifferentHintText[] = QT_TR_NOOP("(different across multiple songs)");
const char *EditTagDialog::kArtDifferentHintText = QT_TR_NOOP("Different art across multiple songs."); const char EditTagDialog::kArtDifferentHintText[] = QT_TR_NOOP("Different art across multiple songs.");
const char *EditTagDialog::kSettingsGroup = "EditTagDialog"; const char EditTagDialog::kSettingsGroup[] = "EditTagDialog";
const int EditTagDialog::kSmallImageSize = 128;
EditTagDialog::EditTagDialog(Application *app, QWidget *parent) EditTagDialog::EditTagDialog(Application *app, QWidget *parent)
: QDialog(parent), : QDialog(parent),
@@ -111,7 +112,7 @@ EditTagDialog::EditTagDialog(Application *app, QWidget *parent)
tag_fetcher_(new TagFetcher(app->network(), this)), tag_fetcher_(new TagFetcher(app->network(), this)),
results_dialog_(new TrackSelectionDialog(this)), results_dialog_(new TrackSelectionDialog(this)),
#endif #endif
image_no_cover_thumbnail_(ImageUtils::GenerateNoCoverImage(QSize(128, 128))), image_no_cover_thumbnail_(ImageUtils::GenerateNoCoverImage(QSize(128, 128), devicePixelRatioF())),
loading_(false), loading_(false),
ignore_edits_(false), ignore_edits_(false),
summary_cover_art_id_(-1), summary_cover_art_id_(-1),
@@ -251,11 +252,6 @@ EditTagDialog::EditTagDialog(Application *app, QWidget *parent)
new TagCompleter(app_->collection_backend(), Playlist::Column_Performer, ui_->performer); new TagCompleter(app_->collection_backend(), Playlist::Column_Performer, ui_->performer);
new TagCompleter(app_->collection_backend(), Playlist::Column_Grouping, ui_->grouping); new TagCompleter(app_->collection_backend(), Playlist::Column_Grouping, ui_->grouping);
cover_options_.get_image_data_ = true;
cover_options_.get_image_ = true;
cover_options_.scale_output_image_ = true;
cover_options_.desired_height_ = 128;
} }
EditTagDialog::~EditTagDialog() { EditTagDialog::~EditTagDialog() {
@@ -280,6 +276,8 @@ void EditTagDialog::showEvent(QShowEvent *e) {
album_cover_choice_controller_->ReloadSettings(); album_cover_choice_controller_->ReloadSettings();
cover_types_ = AlbumCoverLoaderOptions::LoadTypes();
} }
QDialog::showEvent(e); QDialog::showEvent(e);
@@ -328,7 +326,7 @@ bool EditTagDialog::eventFilter(QObject *o, QEvent *e) {
ShowCover(); ShowCover();
break; break;
case QEvent::DragEnter: { case QEvent::DragEnter:{
QDragEnterEvent *event = static_cast<QDragEnterEvent*>(e); QDragEnterEvent *event = static_cast<QDragEnterEvent*>(e);
if (AlbumCoverChoiceController::CanAcceptDrag(event)) { if (AlbumCoverChoiceController::CanAcceptDrag(event)) {
event->acceptProposedAction(); event->acceptProposedAction();
@@ -336,7 +334,7 @@ bool EditTagDialog::eventFilter(QObject *o, QEvent *e) {
break; break;
} }
case QEvent::Drop: { case QEvent::Drop:{
const QDropEvent *event = static_cast<QDropEvent*>(e); const QDropEvent *event = static_cast<QDropEvent*>(e);
if (event->mimeData()->hasImage()) { if (event->mimeData()->hasImage()) {
QImage image = qvariant_cast<QImage>(event->mimeData()->imageData()); QImage image = qvariant_cast<QImage>(event->mimeData()->imageData());
@@ -438,7 +436,7 @@ void EditTagDialog::SetSongsFinished() {
ui_->tab_widget->setEnabled(false); ui_->tab_widget->setEnabled(false);
// Show a summary with empty information // Show a summary with empty information
UpdateSummaryTab(Song(), UpdateCoverAction::None); UpdateSummaryTab(Song());
ui_->tab_widget->setCurrentWidget(ui_->tab_summary); ui_->tab_widget->setCurrentWidget(ui_->tab_summary);
SetSongListVisibility(false); SetSongListVisibility(false);
@@ -614,11 +612,11 @@ void EditTagDialog::SelectionChanged() {
ui_->tab_widget->setTabEnabled(ui_->tab_widget->indexOf(ui_->tab_lyrics), !multiple); ui_->tab_widget->setTabEnabled(ui_->tab_widget->indexOf(ui_->tab_lyrics), !multiple);
if (multiple) { if (multiple) {
UpdateSummaryTab(Song(), UpdateCoverAction::None); UpdateSummaryTab(Song());
UpdateStatisticsTab(Song()); UpdateStatisticsTab(Song());
} }
else { else {
UpdateSummaryTab(data_[indexes.first().row()].original_, data_[indexes.first().row()].cover_action_); UpdateSummaryTab(data_[indexes.first().row()].original_);
UpdateStatisticsTab(data_[indexes.first().row()].original_); UpdateStatisticsTab(data_[indexes.first().row()].original_);
} }
@@ -645,9 +643,9 @@ void EditTagDialog::SelectionChanged() {
} }
if (data_[idx.row()].cover_action_ != first_cover_action || if (data_[idx.row()].cover_action_ != first_cover_action ||
song.art_manual() != first_song.art_manual() || song.art_manual() != first_song.art_manual() ||
song.has_embedded_cover() != first_song.has_embedded_cover() || song.art_embedded() != first_song.art_embedded() ||
(song.art_manual().isEmpty() && song.art_automatic() != first_song.art_automatic()) || song.art_automatic() != first_song.art_automatic() ||
(song.has_embedded_cover() && first_song.has_embedded_cover() && (first_song.effective_albumartist() != song.effective_albumartist() || first_song.album() != song.album())) (song.art_embedded() && first_song.art_embedded() && (first_song.effective_albumartist() != song.effective_albumartist() || first_song.album() != song.album()))
) { ) {
art_different = true; art_different = true;
} }
@@ -681,7 +679,6 @@ void EditTagDialog::SelectionChanged() {
} }
QString summary; QString summary;
if (indexes.count() == 1) { if (indexes.count() == 1) {
summary += "<p><b>" + first_song.PrettyTitleWithArtist().toHtmlEscaped() + "</b></p>"; summary += "<p><b>" + first_song.PrettyTitleWithArtist().toHtmlEscaped() + "</b></p>";
} }
@@ -690,6 +687,7 @@ void EditTagDialog::SelectionChanged() {
summary += tr("%1 songs selected.").arg(indexes.count()); summary += tr("%1 songs selected.").arg(indexes.count());
summary += "</b></p>"; summary += "</b></p>";
} }
ui_->tags_summary->setText(summary);
const bool enable_change_art = first_song.is_collection_song(); const bool enable_change_art = first_song.is_collection_song();
ui_->tags_art_button->setEnabled(enable_change_art); ui_->tags_art_button->setEnabled(enable_change_art);
@@ -709,26 +707,27 @@ void EditTagDialog::SelectionChanged() {
} }
else { else {
ui_->tags_art->clear(); ui_->tags_art->clear();
album_cover_choice_controller_->show_cover_action()->setEnabled(first_song.has_valid_art() && !first_song.has_manually_unset_cover()); album_cover_choice_controller_->show_cover_action()->setEnabled(first_song.has_valid_art() && !first_song.art_unset());
album_cover_choice_controller_->cover_to_file_action()->setEnabled(first_song.has_valid_art() && !first_song.has_manually_unset_cover()); album_cover_choice_controller_->cover_to_file_action()->setEnabled(first_song.has_valid_art() && !first_song.art_unset());
album_cover_choice_controller_->cover_from_file_action()->setEnabled(enable_change_art); 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_->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_->search_for_cover_action()->setEnabled(app_->cover_providers()->HasAnyProviders() && enable_change_art);
album_cover_choice_controller_->unset_cover_action()->setEnabled(enable_change_art && !first_song.has_manually_unset_cover()); album_cover_choice_controller_->unset_cover_action()->setEnabled(enable_change_art && !first_song.art_unset());
album_cover_choice_controller_->clear_cover_action()->setEnabled(enable_change_art && !first_song.art_manual().isEmpty()); album_cover_choice_controller_->clear_cover_action()->setEnabled(enable_change_art && !first_song.art_manual().isEmpty());
album_cover_choice_controller_->delete_cover_action()->setEnabled(enable_change_art && first_song.has_valid_art() && !first_song.has_manually_unset_cover()); album_cover_choice_controller_->delete_cover_action()->setEnabled(enable_change_art && (first_song.art_embedded() || !first_song.art_automatic().isEmpty() || !first_song.art_manual().isEmpty()));
AlbumCoverLoaderOptions cover_options(AlbumCoverLoaderOptions::Option::RawImageData | AlbumCoverLoaderOptions::Option::OriginalImage | AlbumCoverLoaderOptions::Option::ScaledImage | AlbumCoverLoaderOptions::Option::PadScaledImage);
cover_options.types = cover_types_;
cover_options.desired_scaled_size = QSize(kSmallImageSize, kSmallImageSize);
cover_options.device_pixel_ratio = devicePixelRatioF();
if (data_[indexes.first().row()].cover_action_ == UpdateCoverAction::None) { if (data_[indexes.first().row()].cover_action_ == UpdateCoverAction::None) {
tags_cover_art_id_ = app_->album_cover_loader()->LoadImageAsync(cover_options_, first_song); tags_cover_art_id_ = app_->album_cover_loader()->LoadImageAsync(cover_options, first_song);
} }
else { else {
tags_cover_art_id_ = app_->album_cover_loader()->LoadImageAsync(cover_options_, data_[indexes.first().row()].cover_result_); tags_cover_art_id_ = app_->album_cover_loader()->LoadImageAsync(cover_options, data_[indexes.first().row()].cover_result_);
} }
summary += GetArtSummary(first_song, first_cover_action);
} }
ui_->tags_summary->setText(summary); const bool embedded_cover = (first_song.save_embedded_cover_supported() && (first_song.art_embedded() || album_cover_choice_controller_->get_collection_save_album_cover_type() == CoverOptions::CoverType::Embedded));
const bool embedded_cover = (first_song.save_embedded_cover_supported() && (first_song.has_embedded_cover() || album_cover_choice_controller_->get_collection_save_album_cover_type() == CoverOptions::CoverType::Embedded));
ui_->checkbox_embedded_cover->setChecked(embedded_cover); ui_->checkbox_embedded_cover->setChecked(embedded_cover);
album_cover_choice_controller_->set_save_embedded_cover_override(embedded_cover); album_cover_choice_controller_->set_save_embedded_cover_override(embedded_cover);
@@ -769,15 +768,15 @@ void EditTagDialog::SetDate(QLabel *label, const uint time) {
} }
void EditTagDialog::UpdateSummaryTab(const Song &song, const UpdateCoverAction cover_action) { void EditTagDialog::UpdateSummaryTab(const Song &song) {
summary_cover_art_id_ = app_->album_cover_loader()->LoadImageAsync(cover_options_, song); AlbumCoverLoaderOptions cover_options(AlbumCoverLoaderOptions::Option::ScaledImage | AlbumCoverLoaderOptions::Option::PadScaledImage);
cover_options.types = cover_types_;
cover_options.desired_scaled_size = QSize(kSmallImageSize, kSmallImageSize);
cover_options.device_pixel_ratio = devicePixelRatioF();
summary_cover_art_id_ = app_->album_cover_loader()->LoadImageAsync(cover_options, song);
QString summary = "<p><b>" + song.PrettyTitleWithArtist().toHtmlEscaped() + "</b><p/>"; ui_->summary->setText("<p><b>" + song.PrettyTitleWithArtist().toHtmlEscaped() + "</b></p>");
summary += GetArtSummary(song, cover_action);
ui_->summary->setText(summary);
ui_->length->setText(Utilities::PrettyTimeNanosec(song.length_nanosec())); ui_->length->setText(Utilities::PrettyTimeNanosec(song.length_nanosec()));
@@ -805,12 +804,11 @@ void EditTagDialog::UpdateSummaryTab(const Song &song, const UpdateCoverAction c
ui_->path->clear(); ui_->path->clear();
} }
ui_->art_embedded->setText(song.art_embedded() ? tr("Yes") : tr("No"));
if (song.art_manual().isEmpty()) { if (song.art_manual().isEmpty()) {
ui_->art_manual->setText(tr("None")); ui_->art_manual->setText(tr("None"));
} }
else if (song.has_manually_unset_cover()) {
ui_->art_manual->setText(tr("Unset"));
}
else { else {
ui_->art_manual->setText(song.art_manual().toString()); ui_->art_manual->setText(song.art_manual().toString());
} }
@@ -818,58 +816,38 @@ void EditTagDialog::UpdateSummaryTab(const Song &song, const UpdateCoverAction c
if (song.art_automatic().isEmpty()) { if (song.art_automatic().isEmpty()) {
ui_->art_automatic->setText(tr("None")); ui_->art_automatic->setText(tr("None"));
} }
else if (song.has_embedded_cover()) {
ui_->art_automatic->setText(tr("Embedded"));
}
else { else {
ui_->art_automatic->setText(song.art_automatic().toString()); ui_->art_automatic->setText(song.art_automatic().toString());
} }
ui_->art_unset->setText(song.art_unset() ? tr("Yes") : tr("No"));
} }
QString EditTagDialog::GetArtSummary(const Song &song, const UpdateCoverAction cover_action) { QString EditTagDialog::GetArtSummary(const Song &song, const AlbumCoverLoaderResult::Type cover_type) {
QString summary; QString summary;
if (cover_action != UpdateCoverAction::None) { switch (cover_type) {
switch (cover_action) { case AlbumCoverLoaderResult::Type::None:
case UpdateCoverAction::Clear:
summary = tr("Cover changed: Will be cleared when saved.").toHtmlEscaped();
break; break;
case UpdateCoverAction::Unset: case AlbumCoverLoaderResult::Type::Unset:
summary = tr("Cover changed: Will be unset when saved.").toHtmlEscaped(); summary = tr("Cover is unset.").toHtmlEscaped();
break; break;
case UpdateCoverAction::Delete: case AlbumCoverLoaderResult::Type::Embedded:
summary = tr("Cover changed: Will be deleted when saved.").toHtmlEscaped(); summary = tr("Cover from embedded image.");
break; break;
case UpdateCoverAction::New: case AlbumCoverLoaderResult::Type::Manual:
summary = tr("Cover changed: Will set new when saved.").toHtmlEscaped(); summary = tr("Cover from %1").arg(song.art_manual().toString()).toHtmlEscaped();
break; break;
case UpdateCoverAction::None: case AlbumCoverLoaderResult::Type::Automatic:
summary = tr("Cover from %1").arg(song.art_automatic().toString()).toHtmlEscaped();
break; break;
} }
}
else if (song.art_manual().isEmpty() && song.art_automatic().isEmpty()) { if (summary.isEmpty()) {
summary = tr("Cover art not set").toHtmlEscaped(); summary = tr("Cover art not set").toHtmlEscaped();
} }
else if (song.has_manually_unset_cover()) {
summary = tr("Cover art manually unset").toHtmlEscaped();
}
else if (song.art_manual_is_valid()) {
summary = tr("Manually set cover art from %1").arg(song.art_manual().toString()).toHtmlEscaped();
}
else if (song.has_embedded_cover()) {
summary = tr("Cover art from embedded image");
}
else if (song.art_automatic_is_valid()) {
summary = tr("Cover art automatically loaded from %1").arg(song.art_automatic().toString()).toHtmlEscaped();
}
else if (!song.art_manual().isEmpty()) {
summary = tr("Manually cover art from %1 is missing").arg(song.art_manual().toString()).toHtmlEscaped();
}
else if (!song.art_automatic().isEmpty()) {
summary = tr("Automatically cover art from %1 is missing").arg(song.art_automatic().toString()).toHtmlEscaped();
}
if (!song.is_collection_song()) { if (!song.is_collection_song()) {
if (!summary.isEmpty()) summary += "<br />"; if (!summary.isEmpty()) summary += "<br />";
@@ -880,6 +858,25 @@ QString EditTagDialog::GetArtSummary(const Song &song, const UpdateCoverAction c
} }
QString EditTagDialog::GetArtSummary(const UpdateCoverAction cover_action) {
switch (cover_action) {
case UpdateCoverAction::Clear:
return tr("Cover changed: Will be cleared when saved.").toHtmlEscaped();
case UpdateCoverAction::Unset:
return tr("Cover changed: Will be unset when saved.").toHtmlEscaped();
case UpdateCoverAction::Delete:
return tr("Cover changed: Will be deleted when saved.").toHtmlEscaped();
case UpdateCoverAction::New:
return tr("Cover changed: Will set new when saved.").toHtmlEscaped();
case UpdateCoverAction::None:
break;
}
return QString();
}
void EditTagDialog::UpdateStatisticsTab(const Song &song) { void EditTagDialog::UpdateStatisticsTab(const Song &song) {
ui_->playcount->setText(QString::number(song.playcount())); ui_->playcount->setText(QString::number(song.playcount()));
@@ -890,38 +887,58 @@ void EditTagDialog::UpdateStatisticsTab(const Song &song) {
void EditTagDialog::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) { void EditTagDialog::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) {
if (id == tags_cover_art_id_) { if (id == summary_cover_art_id_) {
ui_->tags_art->clear(); if (result.success && !result.image_scaled.isNull() && result.type != AlbumCoverLoaderResult::Type::Unset) {
bool enable_change_art = false;
if (result.success && !result.image_scaled.isNull() && result.type != AlbumCoverLoaderResult::Type::ManuallyUnset) {
ui_->tags_art->setPixmap(QPixmap::fromImage(result.image_scaled));
for (const QModelIndex &idx : ui_->song_list->selectionModel()->selectedIndexes()) {
data_[idx.row()].cover_result_ = result.album_cover;
enable_change_art = data_[idx.row()].original_.is_collection_song();
}
}
else {
ui_->tags_art->setPixmap(QPixmap::fromImage(image_no_cover_thumbnail_));
for (const QModelIndex &idx : ui_->song_list->selectionModel()->selectedIndexes()) {
data_[idx.row()].cover_result_ = AlbumCoverImageResult();
enable_change_art = data_[idx.row()].original_.is_collection_song();
}
}
tags_cover_art_id_ = -1;
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_->delete_cover_action()->setEnabled(enable_change_art && result.success && result.type != AlbumCoverLoaderResult::Type::ManuallyUnset);
}
else if (id == summary_cover_art_id_) {
if (result.success && !result.image_scaled.isNull() && result.type != AlbumCoverLoaderResult::Type::ManuallyUnset) {
ui_->summary_art->setPixmap(QPixmap::fromImage(result.image_scaled)); ui_->summary_art->setPixmap(QPixmap::fromImage(result.image_scaled));
} }
else { else {
ui_->summary_art->setPixmap(QPixmap::fromImage(image_no_cover_thumbnail_)); ui_->summary_art->setPixmap(QPixmap::fromImage(image_no_cover_thumbnail_));
} }
if (ui_->song_list->selectionModel()->selectedIndexes().count() > 0) {
const QModelIndex idx = ui_->song_list->selectionModel()->selectedIndexes().first();
QString summary = ui_->summary->toPlainText();
summary += "<br />";
summary += "<br />";
summary += GetArtSummary(data_[idx.row()].current_, result.type);
ui_->summary->setText(summary);
}
summary_cover_art_id_ = -1; summary_cover_art_id_ = -1;
} }
else if (id == tags_cover_art_id_) {
if (result.success && !result.image_scaled.isNull() && result.type != AlbumCoverLoaderResult::Type::Unset) {
ui_->tags_art->setPixmap(QPixmap::fromImage(result.image_scaled));
}
else {
ui_->tags_art->setPixmap(QPixmap::fromImage(image_no_cover_thumbnail_));
}
Song first_song;
UpdateCoverAction cover_action;
for (const QModelIndex &idx : ui_->song_list->selectionModel()->selectedIndexes()) {
data_[idx.row()].cover_result_ = result.album_cover;
if (!first_song.is_valid()) {
first_song = data_[idx.row()].current_;
cover_action = data_[idx.row()].cover_action_;
}
}
bool enable_change_art = false;
if (first_song.is_valid()) {
QString summary = ui_->tags_summary->toPlainText();
summary += "<br />";
summary += "<br />";
if (cover_action == UpdateCoverAction::None) {
summary += GetArtSummary(first_song, result.type);
}
else {
summary += GetArtSummary(cover_action);
}
ui_->tags_summary->setText(summary);
enable_change_art = first_song.is_collection_song() && !first_song.effective_albumartist().isEmpty() && !first_song.album().isEmpty();
}
tags_cover_art_id_ = -1;
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_->delete_cover_action()->setEnabled(enable_change_art && result.success && result.type != AlbumCoverLoaderResult::Type::Unset);
}
} }
@@ -1018,7 +1035,10 @@ void EditTagDialog::UnsetCover() {
Song *song = GetFirstSelected(); Song *song = GetFirstSelected();
if (!song) return; if (!song) return;
song->set_manually_unset_cover(); song->set_art_embedded(false);
song->clear_art_automatic();
song->clear_art_manual();
song->set_art_unset(true);
UpdateCover(UpdateCoverAction::Unset); UpdateCover(UpdateCoverAction::Unset);
@@ -1029,8 +1049,10 @@ void EditTagDialog::ClearCover() {
Song *song = GetFirstSelected(); Song *song = GetFirstSelected();
if (!song) return; if (!song) return;
song->set_art_embedded(false);
song->clear_art_automatic(); song->clear_art_automatic();
song->clear_art_manual(); song->clear_art_manual();
song->set_art_unset(false);
UpdateCover(UpdateCoverAction::Clear); UpdateCover(UpdateCoverAction::Clear);
@@ -1050,7 +1072,7 @@ void EditTagDialog::ShowCover() {
} }
void EditTagDialog::UpdateCover(const UpdateCoverAction action, const AlbumCoverImageResult &result) { void EditTagDialog::UpdateCover(const UpdateCoverAction cover_action, const AlbumCoverImageResult &cover_result) {
const QModelIndexList indexes = ui_->song_list->selectionModel()->selectedIndexes(); const QModelIndexList indexes = ui_->song_list->selectionModel()->selectedIndexes();
if (indexes.isEmpty()) return; if (indexes.isEmpty()) return;
@@ -1059,17 +1081,20 @@ void EditTagDialog::UpdateCover(const UpdateCoverAction action, const AlbumCover
QString album = data_[indexes.first().row()].current_.album(); QString album = data_[indexes.first().row()].current_.album();
for (const QModelIndex &idx : indexes) { for (const QModelIndex &idx : indexes) {
data_[idx.row()].cover_action_ = action; data_[idx.row()].cover_action_ = cover_action;
data_[idx.row()].cover_result_ = result; data_[idx.row()].cover_result_ = cover_result;
if (action == UpdateCoverAction::New) { if (cover_action == UpdateCoverAction::New) {
data_[idx.row()].current_.clear_art_manual(); data_[idx.row()].current_.clear_art_manual();
data_[idx.row()].current_.set_art_unset(false);
} }
else if (action == UpdateCoverAction::Unset) { else if (cover_action == UpdateCoverAction::Unset) {
data_[idx.row()].current_.set_manually_unset_cover(); data_[idx.row()].current_.set_art_unset(true);
} }
else if (action == UpdateCoverAction::Clear || action == UpdateCoverAction::Delete) { else if (cover_action == UpdateCoverAction::Clear || cover_action == UpdateCoverAction::Delete) {
data_[idx.row()].current_.set_art_embedded(false);
data_[idx.row()].current_.clear_art_manual(); data_[idx.row()].current_.clear_art_manual();
data_[idx.row()].current_.clear_art_automatic(); data_[idx.row()].current_.clear_art_automatic();
data_[idx.row()].current_.set_art_unset(false);
} }
if (artist != data_[idx.row()].current_.effective_albumartist() || album != data_[idx.row()].current_.effective_albumartist()) { if (artist != data_[idx.row()].current_.effective_albumartist() || album != data_[idx.row()].current_.effective_albumartist()) {
artist.clear(); artist.clear();
@@ -1081,23 +1106,26 @@ void EditTagDialog::UpdateCover(const UpdateCoverAction action, const AlbumCover
if (!artist.isEmpty() && !album.isEmpty()) { if (!artist.isEmpty() && !album.isEmpty()) {
for (int i = 0; i < data_.count(); ++i) { for (int i = 0; i < data_.count(); ++i) {
if (data_[i].current_.effective_albumartist() == artist && data_[i].current_.album() == album) { if (data_[i].current_.effective_albumartist() == artist && data_[i].current_.album() == album) {
data_[i].cover_action_ = action; data_[i].cover_action_ = cover_action;
data_[i].cover_result_ = result; data_[i].cover_result_ = cover_result;
if (action == UpdateCoverAction::New) { if (cover_action == UpdateCoverAction::New) {
data_[i].current_.clear_art_manual(); data_[i].current_.clear_art_manual();
data_[i].current_.set_art_unset(false);
} }
else if (action == UpdateCoverAction::Unset) { else if (cover_action == UpdateCoverAction::Unset) {
data_[i].current_.set_manually_unset_cover(); data_[i].current_.set_art_unset(true);
} }
else if (action == UpdateCoverAction::Clear || action == UpdateCoverAction::Delete) { else if (cover_action == UpdateCoverAction::Clear || cover_action == UpdateCoverAction::Delete) {
data_[i].current_.set_art_embedded(false);
data_[i].current_.clear_art_manual(); data_[i].current_.clear_art_manual();
data_[i].current_.clear_art_automatic(); data_[i].current_.clear_art_automatic();
data_[i].current_.set_art_unset(false);
} }
} }
} }
} }
UpdateSummaryTab(data_[indexes.first().row()].current_, data_[indexes.first().row()].cover_action_); UpdateSummaryTab(data_[indexes.first().row()].current_);
SelectionChanged(); SelectionChanged();
} }
@@ -1141,12 +1169,12 @@ void EditTagDialog::SaveData() {
QString embedded_cover_from_file; QString embedded_cover_from_file;
// If embedded album cover is selected, and it isn't saved to the tags, then save it even if no action was done. // If embedded album cover is selected, and it isn't saved to the tags, then save it even if no action was done.
if (ui_->checkbox_embedded_cover->isChecked() && ref.cover_action_ == UpdateCoverAction::None && !ref.original_.has_embedded_cover() && ref.original_.save_embedded_cover_supported()) { if (ui_->checkbox_embedded_cover->isChecked() && ref.cover_action_ == UpdateCoverAction::None && !ref.original_.art_embedded() && ref.original_.save_embedded_cover_supported()) {
if (ref.original_.art_manual().isValid() && ref.original_.art_manual().isLocalFile() && QFile::exists(ref.original_.art_manual().toLocalFile())) { if (ref.original_.art_manual_is_valid()) {
ref.cover_action_ = UpdateCoverAction::New; ref.cover_action_ = UpdateCoverAction::New;
embedded_cover_from_file = ref.original_.art_manual().toLocalFile(); embedded_cover_from_file = ref.original_.art_manual().toLocalFile();
} }
else if (ref.original_.art_automatic().isValid() && ref.original_.art_automatic().isLocalFile() && QFile::exists(ref.original_.art_automatic().toLocalFile())) { else if (ref.original_.art_automatic_is_valid()) {
ref.cover_action_ = UpdateCoverAction::New; ref.cover_action_ = UpdateCoverAction::New;
embedded_cover_from_file = ref.original_.art_automatic().toLocalFile(); embedded_cover_from_file = ref.original_.art_automatic().toLocalFile();
} }
@@ -1175,38 +1203,49 @@ void EditTagDialog::SaveData() {
} }
else { else {
cover_url = album_cover_choice_controller_->SaveCoverToFileAutomatic(&ref.current_, ref.cover_result_); cover_url = album_cover_choice_controller_->SaveCoverToFileAutomatic(&ref.current_, ref.cover_result_);
if (cover_url.isValid()) {
cover_urls.insert(cover_hash, cover_url); cover_urls.insert(cover_hash, cover_url);
} }
} }
}
ref.current_.set_art_manual(cover_url); ref.current_.set_art_manual(cover_url);
ref.current_.set_art_unset(false);
} }
break; break;
} }
case UpdateCoverAction::Unset: case UpdateCoverAction::Unset:
ref.current_.set_manually_unset_cover(); ref.current_.set_art_embedded(false);
ref.current_.clear_art_manual();
ref.current_.clear_art_automatic();
ref.current_.set_art_unset(true);
break; break;
case UpdateCoverAction::Clear: case UpdateCoverAction::Clear:
ref.current_.set_art_embedded(false);
ref.current_.clear_art_manual(); ref.current_.clear_art_manual();
ref.current_.clear_art_automatic();
ref.current_.set_art_unset(false);
break; break;
case UpdateCoverAction::Delete:{ case UpdateCoverAction::Delete:{
ref.current_.set_art_embedded(false);
if (!ref.original_.art_automatic().isEmpty()) { if (!ref.original_.art_automatic().isEmpty()) {
if (ref.original_.art_automatic().isValid() && !ref.original_.has_embedded_cover() && ref.original_.art_automatic().isLocalFile()) { if (ref.original_.art_automatic_is_valid()) {
QString art_automatic = ref.original_.art_automatic().toLocalFile(); const QString art_automatic = ref.original_.art_automatic().toLocalFile();
if (QFile::exists(art_automatic)) { if (QFile::exists(art_automatic)) {
QFile::remove(art_automatic); QFile::remove(art_automatic);
} }
} }
ref.current_.clear_art_automatic(); ref.current_.clear_art_automatic();
} }
if (!ref.original_.art_manual().isEmpty() && !ref.original_.has_manually_unset_cover()) { if (!ref.original_.art_manual().isEmpty()) {
if (ref.original_.art_manual().isValid() && ref.original_.art_manual().isLocalFile()) { if (ref.original_.art_manual_is_valid()) {
QString art_manual = ref.original_.art_manual().toLocalFile(); const QString art_manual = ref.original_.art_manual().toLocalFile();
if (QFile::exists(art_manual)) { if (QFile::exists(art_manual)) {
QFile::remove(art_manual); QFile::remove(art_manual);
} }
} }
ref.current_.clear_art_manual(); ref.current_.clear_art_manual();
} }
ref.current_.set_art_unset(false);
break; break;
} }
} }
@@ -1221,17 +1260,29 @@ void EditTagDialog::SaveData() {
if (ref.current_.lastplayed() <= 0) { ref.current_.set_lastplayed(-1); } if (ref.current_.lastplayed() <= 0) { ref.current_.set_lastplayed(-1); }
++save_tag_pending_; ++save_tag_pending_;
TagReaderClient::SaveCoverOptions savecover_options; TagReaderClient::SaveCoverOptions savecover_options;
savecover_options.enabled = save_embedded_cover;
if (save_embedded_cover && ref.cover_action_ == UpdateCoverAction::New) { if (save_embedded_cover && ref.cover_action_ == UpdateCoverAction::New) {
if (!ref.cover_result_.image.isNull()) { if (!ref.cover_result_.image.isNull()) {
savecover_options.is_jpeg = ref.cover_result_.is_jpeg(); savecover_options.mime_type = ref.cover_result_.mime_type;
savecover_options.cover_data = ref.cover_result_.image_data;
} }
else if (!embedded_cover_from_file.isEmpty()) { else if (!embedded_cover_from_file.isEmpty()) {
savecover_options.cover_filename = embedded_cover_from_file; savecover_options.cover_filename = embedded_cover_from_file;
} }
savecover_options.cover_data = ref.cover_result_.image_data;
} }
TagReaderReply *reply = TagReaderClient::Instance()->SaveFile(ref.current_.url().toLocalFile(), ref.current_, save_tags ? TagReaderClient::SaveTags::On : TagReaderClient::SaveTags::Off, save_playcount ? TagReaderClient::SavePlaycount::On : TagReaderClient::SavePlaycount::Off, save_rating ? TagReaderClient::SaveRating::On : TagReaderClient::SaveRating::Off, savecover_options); TagReaderClient::SaveTypes save_types;
if (save_tags) {
save_types |= TagReaderClient::SaveType::Tags;
}
if (save_playcount) {
save_types |= TagReaderClient::SaveType::PlayCount;
}
if (save_rating) {
save_types |= TagReaderClient::SaveType::Rating;
}
if (save_embedded_cover) {
save_types |= TagReaderClient::SaveType::Cover;
}
TagReaderReply *reply = TagReaderClient::Instance()->SaveFile(ref.current_.url().toLocalFile(), ref.current_, save_types, savecover_options);
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, ref]() { SongSaveTagsComplete(reply, ref.current_.url().toLocalFile(), ref.current_, ref.cover_action_); }, Qt::QueuedConnection); QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, ref]() { SongSaveTagsComplete(reply, ref.current_.url().toLocalFile(), ref.current_, ref.cover_action_); }, Qt::QueuedConnection);
} }
// If the cover was changed, but no tags written, make sure to update the collection. // If the cover was changed, but no tags written, make sure to update the collection.
@@ -1376,16 +1427,15 @@ void EditTagDialog::SongSaveTagsComplete(TagReaderReply *reply, const QString &f
break; break;
case UpdateCoverAction::New: case UpdateCoverAction::New:
song.clear_art_manual(); song.clear_art_manual();
song.set_embedded_cover(); song.set_art_embedded(true);
break; break;
case UpdateCoverAction::Clear: case UpdateCoverAction::Clear:
case UpdateCoverAction::Delete: case UpdateCoverAction::Delete:
song.clear_art_automatic(); song.set_art_embedded(false);
song.clear_art_manual();
break; break;
case UpdateCoverAction::Unset: case UpdateCoverAction::Unset:
song.clear_art_automatic(); song.set_art_embedded(false);
song.set_manually_unset_cover(); song.set_art_unset(true);
break; break;
} }
collection_songs_.insert(song.id(), song); collection_songs_.insert(song.id(), song);

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com> * Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net> * Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -66,10 +66,6 @@ class EditTagDialog : public QDialog {
explicit EditTagDialog(Application *app, QWidget *parent = nullptr); explicit EditTagDialog(Application *app, QWidget *parent = nullptr);
~EditTagDialog() override; ~EditTagDialog() override;
static const char *kSettingsGroup;
static const char *kTagsDifferentHintText;
static const char *kArtDifferentHintText;
void SetSongs(const SongList &songs, const PlaylistItemPtrList &items = PlaylistItemPtrList()); void SetSongs(const SongList &songs, const PlaylistItemPtrList &items = PlaylistItemPtrList());
PlaylistItemPtrList playlist_items() const { return playlist_items_; } PlaylistItemPtrList playlist_items() const { return playlist_items_; }
@@ -85,6 +81,11 @@ class EditTagDialog : public QDialog {
void hideEvent(QHideEvent *e) override; void hideEvent(QHideEvent *e) override;
private: private:
static const char kSettingsGroup[];
static const char kTagsDifferentHintText[];
static const char kArtDifferentHintText[];
static const int kSmallImageSize;
enum class UpdateCoverAction { enum class UpdateCoverAction {
None = 0, None = 0,
Clear, Clear,
@@ -120,7 +121,7 @@ class EditTagDialog : public QDialog {
void FetchTag(); void FetchTag();
void FetchTagSongChosen(const Song &original_song, const Song &new_metadata); void FetchTagSongChosen(const Song &original_song, const Song &new_metadata);
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result); void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &cover_result);
void LoadCoverFromFile(); void LoadCoverFromFile();
void SaveCoverToFile(); void SaveCoverToFile();
@@ -147,7 +148,7 @@ class EditTagDialog : public QDialog {
}; };
Song *GetFirstSelected(); Song *GetFirstSelected();
void UpdateCover(const UpdateCoverAction action, const AlbumCoverImageResult &result = AlbumCoverImageResult()); void UpdateCover(const UpdateCoverAction cover_action, const AlbumCoverImageResult &cover_result = AlbumCoverImageResult());
bool DoesValueVary(const QModelIndexList &sel, const QString &id) const; bool DoesValueVary(const QModelIndexList &sel, const QString &id) const;
bool IsValueModified(const QModelIndexList &sel, const QString &id) const; bool IsValueModified(const QModelIndexList &sel, const QString &id) const;
@@ -157,10 +158,11 @@ class EditTagDialog : public QDialog {
void UpdateModifiedField(const FieldData &field, const QModelIndexList &sel); void UpdateModifiedField(const FieldData &field, const QModelIndexList &sel);
void ResetFieldValue(const FieldData &field, const QModelIndexList &sel); void ResetFieldValue(const FieldData &field, const QModelIndexList &sel);
void UpdateSummaryTab(const Song &song, const UpdateCoverAction cover_action); void UpdateSummaryTab(const Song &song);
void UpdateStatisticsTab(const Song &song); void UpdateStatisticsTab(const Song &song);
static QString GetArtSummary(const Song &song, const UpdateCoverAction cover_action); QString GetArtSummary(const Song &song, const AlbumCoverLoaderResult::Type cover_type);
QString GetArtSummary(const UpdateCoverAction cover_action);
void UpdateUI(const QModelIndexList &indexes); void UpdateUI(const QModelIndexList &indexes);
@@ -194,7 +196,6 @@ class EditTagDialog : public QDialog {
bool ignore_edits_; bool ignore_edits_;
AlbumCoverLoaderOptions cover_options_;
quint64 summary_cover_art_id_; quint64 summary_cover_art_id_;
quint64 tags_cover_art_id_; quint64 tags_cover_art_id_;
bool cover_art_is_set_; bool cover_art_is_set_;
@@ -207,6 +208,8 @@ class EditTagDialog : public QDialog {
int save_tag_pending_; int save_tag_pending_;
QMap<int, Song> collection_songs_; QMap<int, Song> collection_songs_;
AlbumCoverLoaderOptions::Types cover_types_;
}; };
#endif // EDITTAGDIALOG_H #endif // EDITTAGDIALOG_H

View File

@@ -48,7 +48,7 @@
</widget> </widget>
<widget class="QTabWidget" name="tab_widget"> <widget class="QTabWidget" name="tab_widget">
<property name="currentIndex"> <property name="currentIndex">
<number>1</number> <number>0</number>
</property> </property>
<widget class="QWidget" name="tab_summary"> <widget class="QWidget" name="tab_summary">
<property name="sizePolicy"> <property name="sizePolicy">
@@ -162,8 +162,38 @@
<property name="sizeConstraint"> <property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum> <enum>QLayout::SetMinimumSize</enum>
</property> </property>
<item row="6" column="1"> <item row="15" column="0">
<widget class="QLabel" name="ctime"> <widget class="QLabel" name="label_samplerate">
<property name="text">
<string>Sample rate</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="22" column="0">
<widget class="QLabel" name="label_art_unset">
<property name="text">
<string>Art Unset</string>
</property>
</widget>
</item>
<item row="20" column="0">
<widget class="QLabel" name="label_art_manual">
<property name="text">
<string>Art Manual</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLabel" name="length">
<property name="baseSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>
@@ -172,22 +202,17 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="0"> <item row="0" column="1">
<widget class="QLabel" name="label_length"> <widget class="QLineEdit" name="filename">
<property name="text"> <property name="styleSheet">
<string>Length</string> <string notr="true">QLineEdit {
background: transparent;
}</string>
</property> </property>
<property name="field_label" stdset="0"> <property name="frame">
<bool>true</bool> <bool>false</bool>
</property> </property>
</widget> <property name="readOnly">
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_filetype">
<property name="text">
<string>File type</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
@@ -202,8 +227,28 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="17" column="1"> <item row="7" column="1">
<widget class="QLabel" name="playcount"> <widget class="QLabel" name="mtime">
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_filesize">
<property name="text">
<string>File size</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="16" column="1">
<widget class="QLabel" name="bitdepth">
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>
@@ -225,6 +270,26 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="12" column="0">
<widget class="QLabel" name="label_bitrate">
<property name="text">
<string>Bit rate</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLabel" name="lastplayed">
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="15" column="1"> <item row="15" column="1">
<widget class="QLabel" name="samplerate"> <widget class="QLabel" name="samplerate">
<property name="wordWrap"> <property name="wordWrap">
@@ -235,8 +300,18 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="16" column="1"> <item row="2" column="0">
<widget class="QLabel" name="bitdepth"> <widget class="QLabel" name="label_filetype">
<property name="text">
<string>File type</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="filesize">
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>
@@ -245,6 +320,16 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="17" column="0">
<widget class="QLabel" name="label_playcount">
<property name="text">
<string>Play count</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1"> <item row="2" column="1">
<widget class="QLabel" name="filetype"> <widget class="QLabel" name="filetype">
<property name="text"> <property name="text">
@@ -258,48 +343,8 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="8" column="1"> <item row="18" column="1">
<widget class="QLabel" name="lastplayed"> <widget class="QLabel" name="skipcount">
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="18" column="0">
<widget class="QLabel" name="label_skipcount">
<property name="text">
<string>Skip count</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_mtime">
<property name="text">
<string>Date modified</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="17" column="0">
<widget class="QLabel" name="label_playcount">
<property name="text">
<string>Play count</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLabel" name="mtime">
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>
@@ -325,8 +370,25 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1"> <item row="21" column="0">
<widget class="QLabel" name="filesize"> <widget class="QLabel" name="label_art_automatic">
<property name="text">
<string>Art Automatic</string>
</property>
</widget>
</item>
<item row="18" column="0">
<widget class="QLabel" name="label_skipcount">
<property name="text">
<string>Skip count</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="17" column="1">
<widget class="QLabel" name="playcount">
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
</property> </property>
@@ -335,18 +397,28 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="15" column="0"> <item row="6" column="1">
<widget class="QLabel" name="label_samplerate"> <widget class="QLabel" name="ctime">
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_mtime">
<property name="text"> <property name="text">
<string>Sample rate</string> <string>Date modified</string>
</property> </property>
<property name="field_label" stdset="0"> <property name="field_label" stdset="0">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="1" column="1">
<widget class="QLineEdit" name="filename"> <widget class="QLineEdit" name="path">
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">QLineEdit { <string notr="true">QLineEdit {
background: transparent; background: transparent;
@@ -360,16 +432,40 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="8" column="0"> <item row="22" column="1">
<widget class="QLabel" name="label_lastplayed"> <widget class="QLineEdit" name="art_unset">
<property name="styleSheet">
<string notr="true">QLineEdit {
background: transparent;
}</string>
</property>
<property name="frame">
<bool>false</bool>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_length">
<property name="text"> <property name="text">
<string comment="A playlist's tag.">Last played</string> <string>Length</string>
</property> </property>
<property name="field_label" stdset="0"> <property name="field_label" stdset="0">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="21" column="1">
<widget class="QLineEdit" name="art_automatic">
<property name="styleSheet">
<string notr="true">QLineEdit {
background: transparent;
}</string>
</property>
<property name="frame">
<bool>false</bool>
</property>
</widget>
</item>
<item row="12" column="1"> <item row="12" column="1">
<widget class="QLabel" name="bitrate"> <widget class="QLabel" name="bitrate">
<property name="baseSize"> <property name="baseSize">
@@ -386,82 +482,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="20" column="1">
<widget class="QLineEdit" name="path">
<property name="styleSheet">
<string notr="true">QLineEdit {
background: transparent;
}</string>
</property>
<property name="frame">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLabel" name="length">
<property name="baseSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="19" column="0">
<widget class="QLabel" name="label_art_manual">
<property name="text">
<string>Art Manual</string>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label_bitrate">
<property name="text">
<string>Bit rate</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_filesize">
<property name="text">
<string>File size</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="18" column="1">
<widget class="QLabel" name="skipcount">
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="20" column="0">
<widget class="QLabel" name="label_art_automatic">
<property name="text">
<string>Art Automatic</string>
</property>
</widget>
</item>
<item row="19" column="1">
<widget class="QLineEdit" name="art_manual"> <widget class="QLineEdit" name="art_manual">
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">QLineEdit { <string notr="true">QLineEdit {
@@ -473,8 +494,25 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="20" column="1"> <item row="8" column="0">
<widget class="QLineEdit" name="art_automatic"> <widget class="QLabel" name="label_lastplayed">
<property name="text">
<string comment="A playlist's tag.">Last played</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="19" column="0">
<widget class="QLabel" name="label_art_embedded">
<property name="text">
<string>Art Embedded</string>
</property>
</widget>
</item>
<item row="19" column="1">
<widget class="QLineEdit" name="art_embedded">
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">QLineEdit { <string notr="true">QLineEdit {
background: transparent; background: transparent;

View File

@@ -139,10 +139,6 @@ InternetSearchView::InternetSearchView(QWidget *parent)
ui_->progressbar->hide(); ui_->progressbar->hide();
ui_->progressbar->reset(); ui_->progressbar->reset();
cover_loader_options_.desired_height_ = kArtHeight;
cover_loader_options_.pad_output_image_ = true;
cover_loader_options_.scale_output_image_ = true;
} }
InternetSearchView::~InternetSearchView() { delete ui_; } InternetSearchView::~InternetSearchView() { delete ui_; }
@@ -851,7 +847,9 @@ void InternetSearchView::LazyLoadAlbumCover(const QModelIndex &proxy_index) {
item_album->setData(cached_pixmap, Qt::DecorationRole); item_album->setData(cached_pixmap, Qt::DecorationRole);
} }
else { else {
quint64 loader_id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, result.metadata_); AlbumCoverLoaderOptions cover_loader_options(AlbumCoverLoaderOptions::Option::ScaledImage | AlbumCoverLoaderOptions::Option::PadScaledImage);
cover_loader_options.desired_scaled_size = QSize(kArtHeight, kArtHeight);
quint64 loader_id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options, result.metadata_);
cover_loader_tasks_[loader_id] = qMakePair(source_index, result.pixmap_cache_key_); cover_loader_tasks_[loader_id] = qMakePair(source_index, result.pixmap_cache_key_);
} }

View File

@@ -42,7 +42,6 @@
#include "core/song.h" #include "core/song.h"
#include "collection/collectionmodel.h" #include "collection/collectionmodel.h"
#include "covermanager/albumcoverloaderoptions.h"
#include "covermanager/albumcoverloaderresult.h" #include "covermanager/albumcoverloaderresult.h"
#include "settings/settingsdialog.h" #include "settings/settingsdialog.h"
@@ -215,7 +214,6 @@ class InternetSearchView : public QWidget {
QMap<int, DelayedSearch> delayed_searches_; QMap<int, DelayedSearch> delayed_searches_;
QMap<int, PendingState> pending_searches_; QMap<int, PendingState> pending_searches_;
AlbumCoverLoaderOptions cover_loader_options_;
QMap<quint64, QPair<QModelIndex, QString>> cover_loader_tasks_; QMap<quint64, QPair<QModelIndex, QString>> cover_loader_tasks_;
}; };

View File

@@ -222,7 +222,7 @@ void Organize::ProcessSomeFiles() {
job.remove_original_ = !copy_; job.remove_original_ = !copy_;
job.playlist_ = playlist_; job.playlist_ = playlist_;
if (task.song_info_.song_.art_manual_is_valid() && !task.song_info_.song_.has_manually_unset_cover()) { if (task.song_info_.song_.art_manual_is_valid() && !task.song_info_.song_.art_unset()) {
if (task.song_info_.song_.art_manual().isLocalFile() && QFile::exists(task.song_info_.song_.art_manual().toLocalFile())) { if (task.song_info_.song_.art_manual().isLocalFile() && QFile::exists(task.song_info_.song_.art_manual().toLocalFile())) {
job.cover_source_ = task.song_info_.song_.art_manual().toLocalFile(); job.cover_source_ = task.song_info_.song_.art_manual().toLocalFile();
} }
@@ -230,7 +230,7 @@ void Organize::ProcessSomeFiles() {
job.cover_source_ = task.song_info_.song_.art_manual().path(); job.cover_source_ = task.song_info_.song_.art_manual().path();
} }
} }
else if (task.song_info_.song_.art_automatic_is_valid() && !task.song_info_.song_.has_embedded_cover()) { else if (task.song_info_.song_.art_automatic_is_valid()) {
if (task.song_info_.song_.art_automatic().isLocalFile() && QFile::exists(task.song_info_.song_.art_automatic().toLocalFile())) { if (task.song_info_.song_.art_automatic().isLocalFile() && QFile::exists(task.song_info_.song_.art_automatic().toLocalFile())) {
job.cover_source_ = task.song_info_.song_.art_automatic().toLocalFile(); job.cover_source_ = task.song_info_.song_.art_automatic().toLocalFile();
} }

View File

@@ -2319,9 +2319,9 @@ void Playlist::UpdateScrobblePoint(const qint64 seek_point_nanosec) {
void Playlist::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) { void Playlist::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) {
// Update art_manual for local songs that are not in the collection. // Update art_manual for local songs that are not in the collection.
if (((result.type == AlbumCoverLoaderResult::Type::Manual && result.album_cover.cover_url.isLocalFile()) || result.type == AlbumCoverLoaderResult::Type::ManuallyUnset) && (song.source() == Song::Source::LocalFile || song.source() == Song::Source::CDDA || song.source() == Song::Source::Device)) { if (((result.type == AlbumCoverLoaderResult::Type::Manual && result.album_cover.cover_url.isLocalFile()) || result.type == AlbumCoverLoaderResult::Type::Unset) && (song.source() == Song::Source::LocalFile || song.source() == Song::Source::CDDA || song.source() == Song::Source::Device)) {
PlaylistItemPtr item = current_item(); PlaylistItemPtr item = current_item();
if (item && item->Metadata() == song && (!item->Metadata().art_manual_is_valid() || (result.type == AlbumCoverLoaderResult::Type::ManuallyUnset && !item->Metadata().has_manually_unset_cover()))) { if (item && item->Metadata() == song && (!item->Metadata().art_manual_is_valid() || (result.type == AlbumCoverLoaderResult::Type::Unset && !item->Metadata().art_unset()))) {
qLog(Debug) << "Updating art manual for local song" << song.title() << song.album() << song.title() << "to" << result.album_cover.cover_url << "in playlist."; qLog(Debug) << "Updating art manual for local song" << song.title() << song.album() << song.title() << "to" << result.album_cover.cover_url << "in playlist.";
item->SetArtManual(result.album_cover.cover_url); item->SetArtManual(result.album_cover.cover_url);
ScheduleSaveAsync(); ScheduleSaveAsync();

View File

@@ -1468,12 +1468,7 @@ void PlaylistView::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResu
current_song_cover_art_ = result.album_cover.image; current_song_cover_art_ = result.album_cover.image;
if (background_image_type_ == AppearanceSettingsPage::BackgroundImageType::Album) { if (background_image_type_ == AppearanceSettingsPage::BackgroundImageType::Album) {
if (song.art_automatic().isEmpty() && song.art_manual().isEmpty()) { set_background_image(result.success && result.type != AlbumCoverLoaderResult::Type::None && result.type != AlbumCoverLoaderResult::Type::Unset ? current_song_cover_art_ : QImage());
set_background_image(QImage());
}
else {
set_background_image(current_song_cover_art_);
}
force_background_redraw_ = true; force_background_redraw_ = true;
update(); update();
} }

View File

@@ -172,13 +172,10 @@ void XSPFParser::Save(const SongList &songs, QIODevice *device, const QDir &dir,
writer.writeTextElement("trackNum", QString::number(song.track())); writer.writeTextElement("trackNum", QString::number(song.track()));
} }
QUrl cover_url = song.art_manual().isEmpty() || song.art_manual().path().isEmpty() ? song.art_automatic() : song.art_manual(); const QUrl cover_url = song.art_manual().isEmpty() || !song.art_manual().isValid() ? song.art_automatic() : song.art_manual();
// Ignore images that are in our resource bundle. // Ignore images that are in our resource bundle.
if (!cover_url.isEmpty() && !cover_url.path().isEmpty() && cover_url.path() != Song::kManuallyUnsetCover && cover_url.path() != Song::kEmbeddedCover) { if (!cover_url.isEmpty() && cover_url.isValid()) {
if (cover_url.scheme().isEmpty()) { const QString cover_filename = QUrl::toPercentEncoding(URLOrFilename(cover_url, dir, path_type), "/ ");
cover_url.setScheme("file");
}
QString cover_filename = QUrl::toPercentEncoding(URLOrFilename(cover_url, dir, path_type), "/ ");
writer.writeTextElement("image", cover_filename); writer.writeTextElement("image", cover_filename);
} }
} }

View File

@@ -47,12 +47,6 @@ RadioModel::RadioModel(Application *app, QObject *parent)
root_->lazy_loaded = true; root_->lazy_loaded = true;
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_ = kTreeIconSize;
if (app_) { if (app_) {
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &RadioModel::AlbumCoverLoaded); QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &RadioModel::AlbumCoverLoaded);
} }
@@ -298,7 +292,7 @@ QPixmap RadioModel::ChannelIcon(const QModelIndex &idx) {
if (!songs.isEmpty()) { if (!songs.isEmpty()) {
Song song = songs.first(); Song song = songs.first();
song.set_art_automatic(item->channel.thumbnail_url); song.set_art_automatic(item->channel.thumbnail_url);
const quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, song); const quint64 id = app_->album_cover_loader()->LoadImageAsync(AlbumCoverLoaderOptions(AlbumCoverLoaderOptions::Option::ScaledImage | AlbumCoverLoaderOptions::Option::PadScaledImage, QSize(kTreeIconSize, kTreeIconSize)), song);
pending_art_[id] = ItemAndCacheKey(item, cache_key); pending_art_[id] = ItemAndCacheKey(item, cache_key);
pending_cache_keys_.insert(cache_key); pending_cache_keys_.insert(cache_key);
} }
@@ -319,7 +313,7 @@ void RadioModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult
pending_cache_keys_.remove(cache_key); pending_cache_keys_.remove(cache_key);
if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type::ManuallyUnset) { if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type::Unset) {
QPixmapCache::insert(cache_key, ServiceIcon(item)); QPixmapCache::insert(cache_key, ServiceIcon(item));
} }
else { else {

View File

@@ -33,7 +33,6 @@
#include "core/song.h" #include "core/song.h"
#include "core/simpletreemodel.h" #include "core/simpletreemodel.h"
#include "covermanager/albumcoverloaderoptions.h"
#include "covermanager/albumcoverloaderresult.h" #include "covermanager/albumcoverloaderresult.h"
#include "radioitem.h" #include "radioitem.h"
#include "radiochannel.h" #include "radiochannel.h"
@@ -91,7 +90,6 @@ class RadioModel : public SimpleTreeModel<RadioItem> {
using ItemAndCacheKey = QPair<RadioItem*, QString>; using ItemAndCacheKey = QPair<RadioItem*, QString>;
Application *app_; Application *app_;
AlbumCoverLoaderOptions cover_loader_options_;
QMap<Song::Source, RadioItem*> container_nodes_; QMap<Song::Source, RadioItem*> container_nodes_;
QList<RadioItem*> items_; QList<RadioItem*> items_;
QMap<quint64, ItemAndCacheKey> pending_art_; QMap<quint64, ItemAndCacheKey> pending_art_;

View File

@@ -45,7 +45,6 @@
#include "core/iconloader.h" #include "core/iconloader.h"
#include "utilities/strutils.h" #include "utilities/strutils.h"
#include "utilities/timeutils.h" #include "utilities/timeutils.h"
#include "utilities/coveroptions.h"
#include "collection/collection.h" #include "collection/collection.h"
#include "collection/collectionmodel.h" #include "collection/collectionmodel.h"
#include "collection/collectiondirectorymodel.h" #include "collection/collectiondirectorymodel.h"
@@ -90,10 +89,6 @@ CollectionSettingsPage::CollectionSettingsPage(SettingsDialog *dialog, QWidget *
QObject::connect(ui_->song_tracking, &QCheckBox::toggled, this, &CollectionSettingsPage::SongTrackingToggled); QObject::connect(ui_->song_tracking, &QCheckBox::toggled, this, &CollectionSettingsPage::SongTrackingToggled);
#endif #endif
QObject::connect(ui_->radiobutton_save_albumcover_albumdir, &QRadioButton::toggled, this, &CollectionSettingsPage::CoverSaveInAlbumDirChanged);
QObject::connect(ui_->radiobutton_cover_hash, &QRadioButton::toggled, this, &CollectionSettingsPage::CoverSaveInAlbumDirChanged);
QObject::connect(ui_->radiobutton_cover_pattern, &QRadioButton::toggled, this, &CollectionSettingsPage::CoverSaveInAlbumDirChanged);
QObject::connect(ui_->checkbox_disk_cache, &QCheckBox::stateChanged, this, &CollectionSettingsPage::DiskCacheEnable); QObject::connect(ui_->checkbox_disk_cache, &QCheckBox::stateChanged, this, &CollectionSettingsPage::DiskCacheEnable);
QObject::connect(ui_->button_clear_disk_cache, &QPushButton::clicked, dialog->app(), &Application::ClearPixmapDiskCache); QObject::connect(ui_->button_clear_disk_cache, &QPushButton::clicked, dialog->app(), &Application::ClearPixmapDiskCache);
QObject::connect(ui_->button_clear_disk_cache, &QPushButton::clicked, this, &CollectionSettingsPage::ClearPixmapDiskCache); QObject::connect(ui_->button_clear_disk_cache, &QPushButton::clicked, this, &CollectionSettingsPage::ClearPixmapDiskCache);
@@ -189,34 +184,6 @@ void CollectionSettingsPage::Load() {
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList(); QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList();
ui_->cover_art_patterns->setText(filters.join(",")); ui_->cover_art_patterns->setText(filters.join(","));
const CoverOptions::CoverType save_cover_type = static_cast<CoverOptions::CoverType>(s.value("save_cover_type", static_cast<int>(CoverOptions::CoverType::Cache)).toInt());
switch (save_cover_type) {
case CoverOptions::CoverType::Cache:
ui_->radiobutton_save_albumcover_cache->setChecked(true);
break;
case CoverOptions::CoverType::Album:
ui_->radiobutton_save_albumcover_albumdir->setChecked(true);
break;
case CoverOptions::CoverType::Embedded:
ui_->radiobutton_save_albumcover_embedded->setChecked(true);
break;
}
const CoverOptions::CoverFilename save_cover_filename = static_cast<CoverOptions::CoverFilename>(s.value("save_cover_filename", static_cast<int>(CoverOptions::CoverFilename::Pattern)).toInt());
switch (save_cover_filename) {
case CoverOptions::CoverFilename::Hash:
ui_->radiobutton_cover_hash->setChecked(true);
break;
case CoverOptions::CoverFilename::Pattern:
ui_->radiobutton_cover_pattern->setChecked(true);
break;
}
QString cover_pattern = s.value("cover_pattern").toString();
if (!cover_pattern.isEmpty()) ui_->lineedit_cover_pattern->setText(cover_pattern);
ui_->checkbox_cover_overwrite->setChecked(s.value("cover_overwrite", false).toBool());
ui_->checkbox_cover_lowercase->setChecked(s.value("cover_lowercase", true).toBool());
ui_->checkbox_cover_replace_spaces->setChecked(s.value("cover_replace_spaces", true).toBool());
ui_->spinbox_cache_size->setValue(s.value(kSettingsCacheSize, kSettingsCacheSizeDefault).toInt()); ui_->spinbox_cache_size->setValue(s.value(kSettingsCacheSize, kSettingsCacheSizeDefault).toInt());
ui_->combobox_cache_size->setCurrentIndex(ui_->combobox_cache_size->findData(s.value(kSettingsCacheSizeUnit, static_cast<int>(CacheSizeUnit::MB)).toInt())); ui_->combobox_cache_size->setCurrentIndex(ui_->combobox_cache_size->findData(s.value(kSettingsCacheSizeUnit, static_cast<int>(CacheSizeUnit::MB)).toInt()));
ui_->checkbox_disk_cache->setChecked(s.value(kSettingsDiskCacheEnable, false).toBool()); ui_->checkbox_disk_cache->setChecked(s.value(kSettingsDiskCacheEnable, false).toBool());
@@ -270,22 +237,6 @@ void CollectionSettingsPage::Save() {
s.setValue("cover_art_patterns", filters); s.setValue("cover_art_patterns", filters);
CoverOptions::CoverType save_cover_type = CoverOptions::CoverType::Cache;
if (ui_->radiobutton_save_albumcover_cache->isChecked()) save_cover_type = CoverOptions::CoverType::Cache;
else if (ui_->radiobutton_save_albumcover_albumdir->isChecked()) save_cover_type = CoverOptions::CoverType::Album;
else if (ui_->radiobutton_save_albumcover_embedded->isChecked()) save_cover_type = CoverOptions::CoverType::Embedded;
s.setValue("save_cover_type", static_cast<int>(save_cover_type));
CoverOptions::CoverFilename save_cover_filename = CoverOptions::CoverFilename::Hash;
if (ui_->radiobutton_cover_hash->isChecked()) save_cover_filename = CoverOptions::CoverFilename::Hash;
else if (ui_->radiobutton_cover_pattern->isChecked()) save_cover_filename = CoverOptions::CoverFilename::Pattern;
s.setValue("save_cover_filename", static_cast<int>(save_cover_filename));
s.setValue("cover_pattern", ui_->lineedit_cover_pattern->text());
s.setValue("cover_overwrite", ui_->checkbox_cover_overwrite->isChecked());
s.setValue("cover_lowercase", ui_->checkbox_cover_lowercase->isChecked());
s.setValue("cover_replace_spaces", ui_->checkbox_cover_replace_spaces->isChecked());
s.setValue(kSettingsCacheSize, ui_->spinbox_cache_size->value()); s.setValue(kSettingsCacheSize, ui_->spinbox_cache_size->value());
s.setValue(kSettingsCacheSizeUnit, ui_->combobox_cache_size->currentData().toInt()); s.setValue(kSettingsCacheSizeUnit, ui_->combobox_cache_size->currentData().toInt());
s.setValue(kSettingsDiskCacheEnable, ui_->checkbox_disk_cache->isChecked()); s.setValue(kSettingsDiskCacheEnable, ui_->checkbox_disk_cache->isChecked());
@@ -303,33 +254,6 @@ void CollectionSettingsPage::Save() {
} }
void CollectionSettingsPage::CoverSaveInAlbumDirChanged() {
if (ui_->radiobutton_save_albumcover_albumdir->isChecked()) {
if (!ui_->groupbox_cover_filename->isEnabled()) {
ui_->groupbox_cover_filename->setEnabled(true);
}
if (ui_->radiobutton_cover_pattern->isChecked()) {
if (!ui_->lineedit_cover_pattern->isEnabled()) ui_->lineedit_cover_pattern->setEnabled(true);
if (!ui_->checkbox_cover_overwrite->isEnabled()) ui_->checkbox_cover_overwrite->setEnabled(true);
if (!ui_->checkbox_cover_lowercase->isEnabled()) ui_->checkbox_cover_lowercase->setEnabled(true);
if (!ui_->checkbox_cover_replace_spaces->isEnabled()) ui_->checkbox_cover_replace_spaces->setEnabled(true);
}
else {
if (ui_->lineedit_cover_pattern->isEnabled()) ui_->lineedit_cover_pattern->setEnabled(false);
if (ui_->checkbox_cover_overwrite->isEnabled()) ui_->checkbox_cover_overwrite->setEnabled(false);
if (ui_->checkbox_cover_lowercase->isEnabled()) ui_->checkbox_cover_lowercase->setEnabled(false);
if (ui_->checkbox_cover_replace_spaces->isEnabled()) ui_->checkbox_cover_replace_spaces->setEnabled(false);
}
}
else {
if (ui_->groupbox_cover_filename->isEnabled()) {
ui_->groupbox_cover_filename->setEnabled(false);
}
}
}
void CollectionSettingsPage::ClearPixmapDiskCache() { void CollectionSettingsPage::ClearPixmapDiskCache() {
ui_->disk_cache_in_use->setText("empty"); ui_->disk_cache_in_use->setText("empty");

View File

@@ -67,7 +67,6 @@ class CollectionSettingsPage : public SettingsPage {
void CurrentRowChanged(const QModelIndex &idx); void CurrentRowChanged(const QModelIndex &idx);
void SongTrackingToggled(); void SongTrackingToggled();
void DiskCacheEnable(const int state); void DiskCacheEnable(const int state);
void CoverSaveInAlbumDirChanged();
void ClearPixmapDiskCache(); void ClearPixmapDiskCache();
void CacheSizeUnitChanged(int index); void CacheSizeUnitChanged(int index);
void DiskCacheSizeUnitChanged(int index); void DiskCacheSizeUnitChanged(int index);

View File

@@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>519</width> <width>519</width>
<height>1546</height> <height>920</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@@ -221,130 +221,6 @@ If there are no matches then it will use the largest image in the directory.</st
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupbox_albumcovers">
<property name="title">
<string>Saving album covers</string>
</property>
<layout class="QVBoxLayout" name="layout_albumcovers">
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupbox_save_options">
<property name="title">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="radiobutton_save_albumcover_albumdir">
<property name="text">
<string>Save album covers in album directory</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radiobutton_save_albumcover_cache">
<property name="text">
<string>Save album covers in cache directory</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radiobutton_save_albumcover_embedded">
<property name="text">
<string>Save album covers as embedded cover</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupbox_cover_filename">
<property name="title">
<string>Filename:</string>
</property>
<layout class="QVBoxLayout" name="layout_cover_filename">
<item>
<widget class="QWidget" name="groupbox_cover_type" native="true">
<layout class="QHBoxLayout" name="layout_cover_type">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="radiobutton_cover_pattern">
<property name="text">
<string>Pattern</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radiobutton_cover_hash">
<property name="text">
<string>Random</string>
</property>
</widget>
</item>
<item>
<spacer name="spacer_cover_filename_bttons">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineedit_cover_pattern">
<property name="text">
<string>%albumartist-%album</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_cover_overwrite">
<property name="text">
<string>Overwrite existing file</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_cover_lowercase">
<property name="text">
<string>Lowercase filename</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_cover_replace_spaces">
<property name="text">
<string>Replace spaces with dashes</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="groupbox_albumcovercache"> <widget class="QGroupBox" name="groupbox_albumcovercache">
<property name="title"> <property name="title">
@@ -623,15 +499,6 @@ If there are no matches then it will use the largest image in the directory.</st
<tabstop>auto_open</tabstop> <tabstop>auto_open</tabstop>
<tabstop>pretty_covers</tabstop> <tabstop>pretty_covers</tabstop>
<tabstop>show_dividers</tabstop> <tabstop>show_dividers</tabstop>
<tabstop>radiobutton_save_albumcover_albumdir</tabstop>
<tabstop>radiobutton_save_albumcover_cache</tabstop>
<tabstop>radiobutton_save_albumcover_embedded</tabstop>
<tabstop>radiobutton_cover_pattern</tabstop>
<tabstop>radiobutton_cover_hash</tabstop>
<tabstop>lineedit_cover_pattern</tabstop>
<tabstop>checkbox_cover_overwrite</tabstop>
<tabstop>checkbox_cover_lowercase</tabstop>
<tabstop>checkbox_cover_replace_spaces</tabstop>
<tabstop>spinbox_cache_size</tabstop> <tabstop>spinbox_cache_size</tabstop>
<tabstop>combobox_cache_size</tabstop> <tabstop>combobox_cache_size</tabstop>
<tabstop>checkbox_disk_cache</tabstop> <tabstop>checkbox_disk_cache</tabstop>

View File

@@ -39,29 +39,49 @@
#include "ui_coverssettingspage.h" #include "ui_coverssettingspage.h"
#include "core/application.h" #include "core/application.h"
#include "core/iconloader.h" #include "core/iconloader.h"
#include "utilities/coveroptions.h"
#include "covermanager/coverproviders.h" #include "covermanager/coverproviders.h"
#include "covermanager/coverprovider.h" #include "covermanager/coverprovider.h"
#include "widgets/loginstatewidget.h" #include "widgets/loginstatewidget.h"
const char *CoversSettingsPage::kSettingsGroup = "Covers"; const char *CoversSettingsPage::kSettingsGroup = "Covers";
const char *CoversSettingsPage::kProviders = "providers";
const char *CoversSettingsPage::kTypes = "types";
const char *CoversSettingsPage::kSaveType = "save_type";
const char *CoversSettingsPage::kSaveFilename = "save_filename";
const char *CoversSettingsPage::kSavePattern = "save_pattern";
const char *CoversSettingsPage::kSaveOverwrite = "save_overwrite";
const char *CoversSettingsPage::kSaveLowercase = "save_lowercase";
const char *CoversSettingsPage::kSaveReplaceSpaces = "save_replace_spaces";
CoversSettingsPage::CoversSettingsPage(SettingsDialog *dialog, QWidget *parent) CoversSettingsPage::CoversSettingsPage(SettingsDialog *dialog, QWidget *parent)
: SettingsPage(dialog, parent), : SettingsPage(dialog, parent),
ui_(new Ui::CoversSettingsPage), ui_(new Ui::CoversSettingsPage),
provider_selected_(false) { provider_selected_(false),
types_selected_(false) {
ui_->setupUi(this); ui_->setupUi(this);
setWindowIcon(IconLoader::Load("cdcase", true, 0, 32)); setWindowIcon(IconLoader::Load("cdcase", true, 0, 32));
QObject::connect(ui_->providers_up, &QPushButton::clicked, this, &CoversSettingsPage::ProvidersMoveUp); QObject::connect(ui_->providers_up, &QPushButton::clicked, this, &CoversSettingsPage::ProvidersMoveUp);
QObject::connect(ui_->providers_down, &QPushButton::clicked, this, &CoversSettingsPage::ProvidersMoveDown); QObject::connect(ui_->providers_down, &QPushButton::clicked, this, &CoversSettingsPage::ProvidersMoveDown);
QObject::connect(ui_->providers, &QListWidget::currentItemChanged, this, &CoversSettingsPage::CurrentItemChanged); QObject::connect(ui_->providers, &QListWidget::currentItemChanged, this, &CoversSettingsPage::ProvidersCurrentItemChanged);
QObject::connect(ui_->providers, &QListWidget::itemSelectionChanged, this, &CoversSettingsPage::ItemSelectionChanged); QObject::connect(ui_->providers, &QListWidget::itemSelectionChanged, this, &CoversSettingsPage::ProvidersItemSelectionChanged);
QObject::connect(ui_->providers, &QListWidget::itemChanged, this, &CoversSettingsPage::ItemChanged); QObject::connect(ui_->providers, &QListWidget::itemChanged, this, &CoversSettingsPage::ProvidersItemChanged);
QObject::connect(ui_->button_authenticate, &QPushButton::clicked, this, &CoversSettingsPage::AuthenticateClicked); QObject::connect(ui_->button_authenticate, &QPushButton::clicked, this, &CoversSettingsPage::AuthenticateClicked);
QObject::connect(ui_->login_state, &LoginStateWidget::LogoutClicked, this, &CoversSettingsPage::LogoutClicked); QObject::connect(ui_->login_state, &LoginStateWidget::LogoutClicked, this, &CoversSettingsPage::LogoutClicked);
QObject::connect(ui_->types_up, &QPushButton::clicked, this, &CoversSettingsPage::TypesMoveUp);
QObject::connect(ui_->types_down, &QPushButton::clicked, this, &CoversSettingsPage::TypesMoveDown);
QObject::connect(ui_->types, &QListWidget::currentItemChanged, this, &CoversSettingsPage::TypesCurrentItemChanged);
QObject::connect(ui_->types, &QListWidget::itemSelectionChanged, this, &CoversSettingsPage::TypesItemSelectionChanged);
QObject::connect(ui_->types, &QListWidget::itemChanged, this, &CoversSettingsPage::TypesItemChanged);
QObject::connect(ui_->radiobutton_save_albumcover_albumdir, &QRadioButton::toggled, this, &CoversSettingsPage::CoverSaveInAlbumDirChanged);
QObject::connect(ui_->radiobutton_cover_hash, &QRadioButton::toggled, this, &CoversSettingsPage::CoverSaveInAlbumDirChanged);
QObject::connect(ui_->radiobutton_cover_pattern, &QRadioButton::toggled, this, &CoversSettingsPage::CoverSaveInAlbumDirChanged);
ui_->login_state->AddCredentialGroup(ui_->widget_authenticate); ui_->login_state->AddCredentialGroup(ui_->widget_authenticate);
NoProviderSelected(); NoProviderSelected();
@@ -87,6 +107,57 @@ void CoversSettingsPage::Load() {
item->setForeground(provider->is_enabled() ? palette().color(QPalette::Active, QPalette::Text) : palette().color(QPalette::Disabled, QPalette::Text)); item->setForeground(provider->is_enabled() ? palette().color(QPalette::Active, QPalette::Text) : palette().color(QPalette::Disabled, QPalette::Text));
} }
QSettings s;
s.beginGroup(kSettingsGroup);
const QStringList all_types = QStringList() << "art_unset"
<< "art_manual"
<< "art_automatic"
<< "art_embedded";
const QStringList types = s.value(kTypes, all_types).toStringList();
ui_->types->clear();
for (const QString &type : types) {
AddAlbumCoverArtType(type, AlbumCoverArtTypeDescription(type), true);
}
for (const QString &type : all_types) {
if (!types.contains(type)) {
AddAlbumCoverArtType(type, AlbumCoverArtTypeDescription(type), false);
}
}
const CoverOptions::CoverType save_cover_type = static_cast<CoverOptions::CoverType>(s.value(kSaveType, static_cast<int>(CoverOptions::CoverType::Cache)).toInt());
switch (save_cover_type) {
case CoverOptions::CoverType::Cache:
ui_->radiobutton_save_albumcover_cache->setChecked(true);
break;
case CoverOptions::CoverType::Album:
ui_->radiobutton_save_albumcover_albumdir->setChecked(true);
break;
case CoverOptions::CoverType::Embedded:
ui_->radiobutton_save_albumcover_embedded->setChecked(true);
break;
}
const CoverOptions::CoverFilename save_cover_filename = static_cast<CoverOptions::CoverFilename>(s.value(kSaveFilename, static_cast<int>(CoverOptions::CoverFilename::Pattern)).toInt());
switch (save_cover_filename) {
case CoverOptions::CoverFilename::Hash:
ui_->radiobutton_cover_hash->setChecked(true);
break;
case CoverOptions::CoverFilename::Pattern:
ui_->radiobutton_cover_pattern->setChecked(true);
break;
}
QString cover_pattern = s.value(kSavePattern).toString();
if (!cover_pattern.isEmpty()) ui_->lineedit_cover_pattern->setText(cover_pattern);
ui_->checkbox_cover_overwrite->setChecked(s.value(kSaveOverwrite, false).toBool());
ui_->checkbox_cover_lowercase->setChecked(s.value(kSaveLowercase, true).toBool());
ui_->checkbox_cover_replace_spaces->setChecked(s.value(kSaveReplaceSpaces, true).toBool());
s.endGroup();
Init(ui_->layout_coverssettingspage->parentWidget()); Init(ui_->layout_coverssettingspage->parentWidget());
if (!QSettings().childGroups().contains(kSettingsGroup)) set_changed(); if (!QSettings().childGroups().contains(kSettingsGroup)) set_changed();
@@ -95,20 +166,47 @@ void CoversSettingsPage::Load() {
void CoversSettingsPage::Save() { void CoversSettingsPage::Save() {
QSettings s;
s.beginGroup(kSettingsGroup);
QStringList providers; QStringList providers;
for (int i = 0; i < ui_->providers->count(); ++i) { for (int i = 0; i < ui_->providers->count(); ++i) {
const QListWidgetItem *item = ui_->providers->item(i); const QListWidgetItem *item = ui_->providers->item(i);
if (item->checkState() == Qt::Checked) providers << item->text(); // clazy:exclude=reserve-candidates if (item->checkState() == Qt::Checked) providers << item->text();
}
s.setValue(kProviders, providers);
QStringList types;
for (int i = 0; i < ui_->types->count(); ++i) {
const QListWidgetItem *item = ui_->types->item(i);
if (item->checkState() == Qt::Checked) {
types << item->data(Type_Role_Name).toString();
}
} }
QSettings s; s.setValue(kTypes, types);
s.beginGroup(kSettingsGroup);
s.setValue("providers", providers); CoverOptions::CoverType save_cover_type = CoverOptions::CoverType::Cache;
if (ui_->radiobutton_save_albumcover_cache->isChecked()) save_cover_type = CoverOptions::CoverType::Cache;
else if (ui_->radiobutton_save_albumcover_albumdir->isChecked()) save_cover_type = CoverOptions::CoverType::Album;
else if (ui_->radiobutton_save_albumcover_embedded->isChecked()) save_cover_type = CoverOptions::CoverType::Embedded;
s.setValue(kSaveType, static_cast<int>(save_cover_type));
CoverOptions::CoverFilename save_cover_filename = CoverOptions::CoverFilename::Hash;
if (ui_->radiobutton_cover_hash->isChecked()) save_cover_filename = CoverOptions::CoverFilename::Hash;
else if (ui_->radiobutton_cover_pattern->isChecked()) save_cover_filename = CoverOptions::CoverFilename::Pattern;
s.setValue(kSaveFilename, static_cast<int>(save_cover_filename));
s.setValue(kSavePattern, ui_->lineedit_cover_pattern->text());
s.setValue(kSaveOverwrite, ui_->checkbox_cover_overwrite->isChecked());
s.setValue(kSaveLowercase, ui_->checkbox_cover_lowercase->isChecked());
s.setValue(kSaveReplaceSpaces, ui_->checkbox_cover_replace_spaces->isChecked());
s.endGroup(); s.endGroup();
} }
void CoversSettingsPage::CurrentItemChanged(QListWidgetItem *item_current, QListWidgetItem *item_previous) { void CoversSettingsPage::ProvidersCurrentItemChanged(QListWidgetItem *item_current, QListWidgetItem *item_previous) {
if (item_previous) { if (item_previous) {
CoverProvider *provider = dialog()->app()->cover_providers()->ProviderByName(item_previous->text()); CoverProvider *provider = dialog()->app()->cover_providers()->ProviderByName(item_previous->text());
@@ -155,7 +253,7 @@ void CoversSettingsPage::CurrentItemChanged(QListWidgetItem *item_current, QList
} }
void CoversSettingsPage::ItemSelectionChanged() { void CoversSettingsPage::ProvidersItemSelectionChanged() {
if (ui_->providers->selectedItems().count() == 0) { if (ui_->providers->selectedItems().count() == 0) {
DisableAuthentication(); DisableAuthentication();
@@ -166,7 +264,7 @@ void CoversSettingsPage::ItemSelectionChanged() {
} }
else { else {
if (ui_->providers->currentItem() && !provider_selected_) { if (ui_->providers->currentItem() && !provider_selected_) {
CurrentItemChanged(ui_->providers->currentItem(), nullptr); ProvidersCurrentItemChanged(ui_->providers->currentItem(), nullptr);
} }
} }
@@ -187,7 +285,7 @@ void CoversSettingsPage::ProvidersMove(const int d) {
} }
void CoversSettingsPage::ItemChanged(QListWidgetItem *item) { void CoversSettingsPage::ProvidersItemChanged(QListWidgetItem *item) {
item->setForeground((item->checkState() == Qt::Checked) ? palette().color(QPalette::Active, QPalette::Text) : palette().color(QPalette::Disabled, QPalette::Text)); item->setForeground((item->checkState() == Qt::Checked) ? palette().color(QPalette::Active, QPalette::Text) : palette().color(QPalette::Disabled, QPalette::Text));
@@ -281,3 +379,114 @@ void CoversSettingsPage::AuthenticationFailure(const QStringList &errors) {
bool CoversSettingsPage::ProviderCompareOrder(CoverProvider *a, CoverProvider *b) { bool CoversSettingsPage::ProviderCompareOrder(CoverProvider *a, CoverProvider *b) {
return a->order() < b->order(); return a->order() < b->order();
} }
void CoversSettingsPage::CoverSaveInAlbumDirChanged() {
if (ui_->radiobutton_save_albumcover_albumdir->isChecked()) {
if (!ui_->groupbox_cover_filename->isEnabled()) {
ui_->groupbox_cover_filename->setEnabled(true);
}
if (ui_->radiobutton_cover_pattern->isChecked()) {
if (!ui_->lineedit_cover_pattern->isEnabled()) ui_->lineedit_cover_pattern->setEnabled(true);
if (!ui_->checkbox_cover_overwrite->isEnabled()) ui_->checkbox_cover_overwrite->setEnabled(true);
if (!ui_->checkbox_cover_lowercase->isEnabled()) ui_->checkbox_cover_lowercase->setEnabled(true);
if (!ui_->checkbox_cover_replace_spaces->isEnabled()) ui_->checkbox_cover_replace_spaces->setEnabled(true);
}
else {
if (ui_->lineedit_cover_pattern->isEnabled()) ui_->lineedit_cover_pattern->setEnabled(false);
if (ui_->checkbox_cover_overwrite->isEnabled()) ui_->checkbox_cover_overwrite->setEnabled(false);
if (ui_->checkbox_cover_lowercase->isEnabled()) ui_->checkbox_cover_lowercase->setEnabled(false);
if (ui_->checkbox_cover_replace_spaces->isEnabled()) ui_->checkbox_cover_replace_spaces->setEnabled(false);
}
}
else {
if (ui_->groupbox_cover_filename->isEnabled()) {
ui_->groupbox_cover_filename->setEnabled(false);
}
}
}
void CoversSettingsPage::AddAlbumCoverArtType(const QString &name, const QString &description, const bool enabled) {
QListWidgetItem *item = new QListWidgetItem;
item->setData(Type_Role_Name, name);
item->setText(description);
item->setCheckState(enabled ? Qt::Checked : Qt::Unchecked);
ui_->types->addItem(item);
}
QString CoversSettingsPage::AlbumCoverArtTypeDescription(const QString &type) const {
if (type == "art_unset") {
return tr("Manually unset (%1)").arg(type);
}
if (type == "art_manual") {
return tr("Set through album cover search (%1)").arg(type);
}
if (type == "art_automatic") {
return tr("Automatically picked up from album directory (%1)").arg(type);
}
if (type == "art_embedded") {
return tr("Embedded album cover art (%1)").arg(type);
}
return QString();
}
void CoversSettingsPage::TypesMoveUp() { TypesMove(-1); }
void CoversSettingsPage::TypesMoveDown() { TypesMove(+1); }
void CoversSettingsPage::TypesMove(const int d) {
const int row = ui_->types->currentRow();
QListWidgetItem *item = ui_->types->takeItem(row);
ui_->types->insertItem(row + d, item);
ui_->types->setCurrentRow(row + d);
set_changed();
}
void CoversSettingsPage::TypesItemChanged(QListWidgetItem *item) {
item->setForeground((item->checkState() == Qt::Checked) ? palette().color(QPalette::Active, QPalette::Text) : palette().color(QPalette::Disabled, QPalette::Text));
set_changed();
}
void CoversSettingsPage::TypesCurrentItemChanged(QListWidgetItem *item_current, QListWidgetItem *item_previous) {
Q_UNUSED(item_previous)
if (item_current) {
const int row = ui_->types->row(item_current);
ui_->types_up->setEnabled(row != 0);
ui_->types_down->setEnabled(row != ui_->types->count() - 1);
types_selected_ = true;
}
else {
ui_->types_up->setEnabled(false);
ui_->types_down->setEnabled(false);
types_selected_ = false;
}
}
void CoversSettingsPage::TypesItemSelectionChanged() {
if (ui_->types->selectedItems().count() == 0) {
ui_->types_up->setEnabled(false);
ui_->types_down->setEnabled(false);
}
else {
if (ui_->providers->currentItem() && !types_selected_) {
TypesCurrentItemChanged(ui_->types->currentItem(), nullptr);
}
}
}

View File

@@ -42,6 +42,14 @@ class CoversSettingsPage : public SettingsPage {
~CoversSettingsPage() override; ~CoversSettingsPage() override;
static const char *kSettingsGroup; static const char *kSettingsGroup;
static const char *kProviders;
static const char *kTypes;
static const char *kSaveType;
static const char *kSaveFilename;
static const char *kSavePattern;
static const char *kSaveOverwrite;
static const char *kSaveLowercase;
static const char *kSaveReplaceSpaces;
void Load() override; void Load() override;
void Save() override; void Save() override;
@@ -52,21 +60,35 @@ class CoversSettingsPage : public SettingsPage {
void DisableAuthentication(); void DisableAuthentication();
void DisconnectAuthentication(CoverProvider *provider) const; void DisconnectAuthentication(CoverProvider *provider) const;
static bool ProviderCompareOrder(CoverProvider *a, CoverProvider *b); static bool ProviderCompareOrder(CoverProvider *a, CoverProvider *b);
void AddAlbumCoverArtType(const QString &name, const QString &description, const bool enabled);
QString AlbumCoverArtTypeDescription(const QString &type) const;
void TypesMove(const int d);
private slots: private slots:
void CurrentItemChanged(QListWidgetItem *item_current, QListWidgetItem *item_previous); void ProvidersCurrentItemChanged(QListWidgetItem *item_current, QListWidgetItem *item_previous);
void ItemSelectionChanged(); void ProvidersItemSelectionChanged();
void ItemChanged(QListWidgetItem *item); void ProvidersItemChanged(QListWidgetItem *item);
void ProvidersMoveUp(); void ProvidersMoveUp();
void ProvidersMoveDown(); void ProvidersMoveDown();
void AuthenticateClicked(); void AuthenticateClicked();
void LogoutClicked(); void LogoutClicked();
void AuthenticationSuccess(); void AuthenticationSuccess();
void AuthenticationFailure(const QStringList &errors); void AuthenticationFailure(const QStringList &errors);
void CoverSaveInAlbumDirChanged();
void TypesCurrentItemChanged(QListWidgetItem *item_current, QListWidgetItem *item_previous);
void TypesItemSelectionChanged();
void TypesItemChanged(QListWidgetItem *item);
void TypesMoveUp();
void TypesMoveDown();
private: private:
enum Type_Role {
Type_Role_Name = Qt::UserRole + 1
};
Ui_CoversSettingsPage *ui_; Ui_CoversSettingsPage *ui_;
bool provider_selected_; bool provider_selected_;
bool types_selected_;
}; };
#endif // COVERSSETTINGSPAGE_H #endif // COVERSSETTINGSPAGE_H

View File

@@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>460</width> <width>460</width>
<height>600</height> <height>897</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@@ -141,6 +141,180 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupbox_album_cover_art_types">
<property name="title">
<string>Album cover types</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QListWidget" name="types">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Ignored">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="layout_move_up_down">
<item>
<widget class="QPushButton" name="types_up">
<property name="text">
<string>Move up</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="types_down">
<property name="text">
<string>Move down</string>
</property>
</widget>
</item>
<item>
<spacer name="spacer_types_updown">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupbox_albumcovers">
<property name="title">
<string>Saving album covers</string>
</property>
<layout class="QVBoxLayout" name="layout_albumcovers">
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupbox_save_options">
<property name="title">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="radiobutton_save_albumcover_albumdir">
<property name="text">
<string>Save album covers in album directory</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radiobutton_save_albumcover_cache">
<property name="text">
<string>Save album covers in cache directory</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radiobutton_save_albumcover_embedded">
<property name="text">
<string>Save album covers as embedded cover</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupbox_cover_filename">
<property name="title">
<string>Filename:</string>
</property>
<layout class="QVBoxLayout" name="layout_cover_filename">
<item>
<widget class="QWidget" name="groupbox_cover_type" native="true">
<layout class="QHBoxLayout" name="layout_cover_type">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="radiobutton_cover_pattern">
<property name="text">
<string>Pattern</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radiobutton_cover_hash">
<property name="text">
<string>Random</string>
</property>
</widget>
</item>
<item>
<spacer name="spacer_cover_filename_bttons">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineedit_cover_pattern">
<property name="text">
<string>%albumartist-%album</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_cover_overwrite">
<property name="text">
<string>Overwrite existing file</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_cover_lowercase">
<property name="text">
<string>Lowercase filename</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkbox_cover_replace_spaces">
<property name="text">
<string>Replace spaces with dashes</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<spacer name="spacer_bottom"> <spacer name="spacer_bottom">
<property name="orientation"> <property name="orientation">

View File

@@ -18,14 +18,11 @@
*/ */
#include <QByteArray> #include <QByteArray>
#include <QByteArrayList>
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
#include <QUrl>
#include <QBuffer> #include <QBuffer>
#include <QImage> #include <QImage>
#include <QImageReader> #include <QImageReader>
#include <QPixmap>
#include <QPainter> #include <QPainter>
#include <QSize> #include <QSize>
@@ -61,35 +58,6 @@ QStringList ImageUtils::SupportedImageFormats() {
} }
QPixmap ImageUtils::TryLoadPixmap(const QUrl &art_automatic, const QUrl &art_manual, const QUrl &url) {
QPixmap ret;
if (!art_manual.path().isEmpty()) {
if (art_manual.path() == Song::kManuallyUnsetCover) return ret;
else if (art_manual.isLocalFile()) {
ret.load(art_manual.toLocalFile());
}
else if (art_manual.scheme().isEmpty()) {
ret.load(art_manual.path());
}
}
if (ret.isNull() && !art_automatic.path().isEmpty()) {
if (art_automatic.path() == Song::kEmbeddedCover && !url.isEmpty() && url.isLocalFile()) {
ret = QPixmap::fromImage(TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(url.toLocalFile()));
}
else if (art_automatic.isLocalFile()) {
ret.load(art_automatic.toLocalFile());
}
else if (art_automatic.scheme().isEmpty()) {
ret.load(art_automatic.path());
}
}
return ret;
}
QByteArray ImageUtils::SaveImageToJpegData(const QImage &image) { QByteArray ImageUtils::SaveImageToJpegData(const QImage &image) {
if (image.isNull()) return QByteArray(); if (image.isNull()) return QByteArray();
@@ -124,28 +92,24 @@ QByteArray ImageUtils::FileToJpegData(const QString &filename) {
} }
QImage ImageUtils::ScaleAndPad(const QImage &image, const bool scale, const bool pad, const int desired_height, const qreal device_pixel_ratio) { QImage ImageUtils::ScaleImage(const QImage &image, const QSize desired_size, const qreal device_pixel_ratio, const bool pad) {
if (image.isNull()) return image; if (image.isNull() || (image.width() == desired_size.width() && image.height() == desired_size.height())) {
return image;
const int scaled_height = desired_height * device_pixel_ratio;
// Scale the image down
QImage image_scaled;
if (scale) {
image_scaled = image.scaled(QSize(scaled_height, scaled_height), Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
else {
image_scaled = image;
} }
// Pad the image to height x height QSize scale_size(desired_size.width() * device_pixel_ratio, desired_size.height() * device_pixel_ratio);
if (pad) {
QImage image_padded(scaled_height, scaled_height, QImage::Format_ARGB32); // Scale the image
QImage image_scaled = image.scaled(scale_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
// Pad the image
if (pad && image_scaled.width() != image_scaled.height()) {
QImage image_padded(scale_size, QImage::Format_ARGB32);
image_padded.fill(0); image_padded.fill(0);
QPainter p(&image_padded); QPainter p(&image_padded);
p.drawImage((scaled_height - image_scaled.width()) / 2, (scaled_height - image_scaled.height()) / 2, image_scaled); p.drawImage((image_padded.width() - image_scaled.width()) / 2, (image_padded.height() - image_scaled.height()) / 2, image_scaled);
p.end(); p.end();
image_scaled = image_padded; image_scaled = image_padded;
@@ -157,44 +121,23 @@ QImage ImageUtils::ScaleAndPad(const QImage &image, const bool scale, const bool
} }
QImage ImageUtils::CreateThumbnail(const QImage &image, const bool pad, const QSize size) { QImage ImageUtils::GenerateNoCoverImage(const QSize size, const qreal device_pixel_ratio) {
if (image.isNull()) return image;
QImage image_thumbnail;
if (pad) {
image_thumbnail = image.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
QImage image_padded(size, QImage::Format_ARGB32_Premultiplied);
image_padded.fill(0);
QPainter p(&image_padded);
p.drawImage((image_padded.width() - image_thumbnail.width()) / 2, (image_padded.height() - image_thumbnail.height()) / 2, image_thumbnail);
p.end();
image_thumbnail = image_padded;
}
else {
image_thumbnail = image.scaledToHeight(size.height(), Qt::SmoothTransformation);
}
return image_thumbnail;
}
QImage ImageUtils::GenerateNoCoverImage(const QSize size) {
QImage image(":/pictures/cdcase.png"); QImage image(":/pictures/cdcase.png");
QSize scale_size(size.width() * device_pixel_ratio, size.height() * device_pixel_ratio);
// Get a square version of the nocover image with some transparency: // Get a square version of the nocover image with some transparency:
QImage image_scaled = image.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); QImage image_scaled = image.scaled(scale_size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
QImage image_square(size, QImage::Format_ARGB32); QImage image_square(scale_size, QImage::Format_ARGB32);
image_square.fill(0); image_square.fill(0);
QPainter p(&image_square); QPainter p(&image_square);
p.setOpacity(0.4); p.setOpacity(0.4);
p.drawImage((size.width() - image_scaled.width()) / 2, (size.height() - image_scaled.height()) / 2, image_scaled); p.drawImage((image_square.width() - image_scaled.width()) / 2, (image_square.height() - image_scaled.height()) / 2, image_scaled);
p.end(); p.end();
image_square.setDevicePixelRatio(device_pixel_ratio);
return image_square; return image_square;
} }

View File

@@ -39,11 +39,8 @@ class ImageUtils {
static QStringList SupportedImageFormats(); static QStringList SupportedImageFormats();
static QByteArray SaveImageToJpegData(const QImage &image = QImage()); static QByteArray SaveImageToJpegData(const QImage &image = QImage());
static QByteArray FileToJpegData(const QString &filename); static QByteArray FileToJpegData(const QString &filename);
static QPixmap TryLoadPixmap(const QUrl &automatic, const QUrl &manual, const QUrl &url = QUrl()); static QImage ScaleImage(const QImage &image, const QSize desired_size, const qreal device_pixel_ratio = 1.0F, const bool pad = true);
static QImage ScaleAndPad(const QImage &image, const bool scale, const bool pad, const int desired_height, const qreal device_pixel_ratio = 1.0F); static QImage GenerateNoCoverImage(const QSize size, const qreal device_pixel_ratio);
static QImage CreateThumbnail(const QImage &image, const bool pad, const QSize size);
static QImage GenerateNoCoverImage(const QSize size = QSize());
}; };
#endif // IMAGEUTILS_H #endif // IMAGEUTILS_H

View File

@@ -74,6 +74,7 @@ PlayingWidget::PlayingWidget(QWidget *parent)
active_(false), active_(false),
small_ideal_height_(0), small_ideal_height_(0),
total_height_(0), total_height_(0),
desired_height_(0),
fit_width_(false), fit_width_(false),
timeline_show_hide_(new QTimeLine(500, this)), timeline_show_hide_(new QTimeLine(500, this)),
timeline_fade_(new QTimeLine(1000, this)), timeline_fade_(new QTimeLine(1000, this)),
@@ -203,7 +204,7 @@ void PlayingWidget::set_ideal_height(const int height) {
} }
QSize PlayingWidget::sizeHint() const { QSize PlayingWidget::sizeHint() const {
return QSize(cover_loader_options_.desired_height_, total_height_); return QSize(desired_height_, total_height_);
} }
void PlayingWidget::CreateModeAction(const Mode mode, const QString &text, QActionGroup *group) { void PlayingWidget::CreateModeAction(const Mode mode, const QString &text, QActionGroup *group) {
@@ -340,7 +341,7 @@ void PlayingWidget::SetImage(const QImage &image) {
void PlayingWidget::ScaleCover() { void PlayingWidget::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()); QImage image = ImageUtils::ScaleImage(image_original_, QSize(desired_height_, desired_height_), devicePixelRatioF(), true);
if (image.isNull()) pixmap_cover_ = QPixmap(); if (image.isNull()) pixmap_cover_ = QPixmap();
else pixmap_cover_ = QPixmap::fromImage(image); else pixmap_cover_ = QPixmap::fromImage(image);
update(); update();
@@ -370,13 +371,13 @@ void PlayingWidget::UpdateHeight() {
switch (mode_) { switch (mode_) {
case Mode::SmallSongDetails: case Mode::SmallSongDetails:
cover_loader_options_.desired_height_ = small_ideal_height_; desired_height_ = small_ideal_height_;
total_height_ = small_ideal_height_; total_height_ = small_ideal_height_;
break; break;
case Mode::LargeSongDetails: case Mode::LargeSongDetails:
if (fit_width_) cover_loader_options_.desired_height_ = width(); if (fit_width_) desired_height_ = width();
else cover_loader_options_.desired_height_ = qMin(kMaxCoverSize, width()); else desired_height_ = qMin(kMaxCoverSize, width());
total_height_ = kTopBorder + cover_loader_options_.desired_height_ + kBottomOffset + static_cast<int>(details_->size().height()); total_height_ = kTopBorder + desired_height_ + kBottomOffset + static_cast<int>(details_->size().height());
break; break;
} }
@@ -406,7 +407,7 @@ void PlayingWidget::UpdateDetailsText() {
html += "<p>"; html += "<p>";
break; break;
case Mode::LargeSongDetails: case Mode::LargeSongDetails:
details_->setTextWidth(cover_loader_options_.desired_height_); details_->setTextWidth(desired_height_);
html += "<p align=center>"; html += "<p align=center>";
break; break;
} }
@@ -461,7 +462,7 @@ void PlayingWidget::DrawContents(QPainter *p) {
// Work out how high the text is going to be // Work out how high the text is going to be
const int text_height = static_cast<int>(details_->size().height()); const int text_height = static_cast<int>(details_->size().height());
const int cover_size = fit_width_ ? width() : qMin(kMaxCoverSize, width()); const int cover_size = fit_width_ ? width() : qMin(kMaxCoverSize, width());
const int x_offset = (width() - cover_loader_options_.desired_height_) / 2; const int x_offset = (width() - desired_height_) / 2;
// Draw the cover // Draw the cover
p->drawPixmap(x_offset, kTopBorder, cover_size, cover_size, pixmap_cover_); p->drawPixmap(x_offset, kTopBorder, cover_size, cover_size, pixmap_cover_);

View File

@@ -35,7 +35,6 @@
#include <QMovie> #include <QMovie>
#include "core/song.h" #include "core/song.h"
#include "covermanager/albumcoverloaderoptions.h"
class QTimeLine; class QTimeLine;
class QTextDocument; class QTextDocument;
@@ -123,8 +122,8 @@ class PlayingWidget : public QWidget {
bool playing_; bool playing_;
bool active_; bool active_;
int small_ideal_height_; int small_ideal_height_;
AlbumCoverLoaderOptions cover_loader_options_;
int total_height_; int total_height_;
int desired_height_;
bool fit_width_; bool fit_width_;
QTimeLine *timeline_show_hide_; QTimeLine *timeline_show_hide_;
QTimeLine *timeline_fade_; QTimeLine *timeline_fade_;