1 /*
2  * Strawberry Music Player
3  * This file was part of Clementine.
4  * Copyright 2010, David Sansome <me@davidsansome.com>
5  * Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
6  *
7  * Strawberry is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Strawberry is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Strawberry.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #include "config.h"
23 
24 #include <QFile>
25 #include <QSize>
26 #include <QString>
27 #include <QStringBuilder>
28 #include <QUrl>
29 #include <QImage>
30 
31 #include "core/song.h"
32 #include "core/tagreaderclient.h"
33 #include "albumcoverexport.h"
34 #include "coverexportrunnable.h"
35 
36 CoverExportRunnable::CoverExportRunnable(const AlbumCoverExport::DialogResult &dialog_result, const Song &song, QObject *parent)
37     : QObject(parent),
38       dialog_result_(dialog_result),
39       song_(song) {}
40 
41 void CoverExportRunnable::run() {
42 
43   QString cover_path = GetCoverPath();
44 
45   // Manually unset?
46   if (cover_path.isEmpty()) {
47     EmitCoverSkipped();
48   }
49   else {
50     if (dialog_result_.RequiresCoverProcessing()) {
51       ProcessAndExportCover();
52     }
53     else {
54       ExportCover();
55     }
56   }
57 
58 }
59 
60 QString CoverExportRunnable::GetCoverPath() {
61 
62   if (song_.has_manually_unset_cover()) {
63     return QString();
64     // Export downloaded covers?
65   }
66   else if (!song_.art_manual().isEmpty() && dialog_result_.export_downloaded_) {
67     return song_.art_manual().toLocalFile();
68     // Export embedded covers?
69   }
70   else if (!song_.art_automatic().isEmpty() && song_.art_automatic().path() == Song::kEmbeddedCover && dialog_result_.export_embedded_) {
71     return song_.art_automatic().toLocalFile();
72   }
SizeOverlayDelegate(QObject * parent)73   else {
74     return QString();
75   }
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & idx) const76 
77 }
78 
79 // Exports a single album cover using a "save QImage to file" approach.
80 // 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:
81 // - either the force size flag is being used
82 // - or the "overwrite smaller" mode is used
83 // In all other cases, the faster ExportCover() method will be used.
84 void CoverExportRunnable::ProcessAndExportCover() {
85 
86   QString cover_path = GetCoverPath();
87 
88   // either embedded or disk - the one we'll export for the current album
89   QImage cover;
90 
91   QImage embedded_cover;
92   QImage disk_cover;
93 
94   // load embedded cover if any
95   if (song_.has_embedded_cover()) {
96     embedded_cover = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile());
97 
98     if (embedded_cover.isNull()) {
99       EmitCoverSkipped();
100       return;
101     }
102     cover = embedded_cover;
103   }
104 
105   // load a file cover which iss mandatory if there's no embedded cover
106   disk_cover.load(cover_path);
107 
108   if (embedded_cover.isNull()) {
109     if (disk_cover.isNull()) {
110       EmitCoverSkipped();
111       return;
112     }
113     cover = disk_cover;
114   }
115 
116   // rescale if necessary
117   if (dialog_result_.IsSizeForced()) {
118     cover = cover.scaled(QSize(dialog_result_.width_, dialog_result_.height_), Qt::IgnoreAspectRatio);
119   }
120 
121   QString dir = song_.url().toLocalFile().section('/', 0, -2);
122   QString extension = cover_path.section('.', -1);
123 
124   QString new_file = dir + '/' + dialog_result_.filename_ + '.' + (cover_path == Song::kEmbeddedCover ? "jpg" : extension);
125 
126   // If the file exists, do not override!
127   if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode_None && QFile::exists(new_file)) {
128     EmitCoverSkipped();
129     return;
130   }
131 
132   // we're handling overwrite as remove + copy so we need to delete the old file first
133   if (QFile::exists(new_file) && dialog_result_.overwrite_ != AlbumCoverExport::OverwriteMode_None) {
134 
135     // if the mode is "overwrite smaller" then skip the cover if a bigger one is already available in the folder
136     if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode_Smaller) {
137       QImage existing;
138       existing.load(new_file);
139 
140       if (existing.isNull() || existing.size().height() >= cover.size().height() || existing.size().width() >= cover.size().width()) {
141 
142         EmitCoverSkipped();
143         return;
144       }
145     }
146 
147     if (!QFile::remove(new_file)) {
148       EmitCoverSkipped();
149       return;
150     }
151   }
152 
153   if (cover.save(new_file)) {
154     EmitCoverExported();
155   }
156   else {
157     EmitCoverSkipped();
158   }
159 
160 }
161 
162 // Exports a single album cover using a "copy file" approach.
163 void CoverExportRunnable::ExportCover() {
Exec(const QString & artist,const QString & album)164 
165   QString cover_path = GetCoverPath();
166 
167   QString dir = song_.url().toLocalFile().section('/', 0, -2);
168   QString extension = cover_path.section('.', -1);
169 
170   QString new_file = dir + '/' + dialog_result_.filename_ + '.' + (cover_path == Song::kEmbeddedCover ? "jpg" : extension);
171 
172   // If the file exists, do not override!
173   if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode_None && QFile::exists(new_file)) {
174     EmitCoverSkipped();
175     return;
176   }
177 
178   // We're handling overwrite as remove + copy so we need to delete the old file first
179   if (dialog_result_.overwrite_ != AlbumCoverExport::OverwriteMode_None && QFile::exists(new_file)) {
180     if (!QFile::remove(new_file)) {
181       EmitCoverSkipped();
182       return;
183     }
184   }
185 
186   if (cover_path == Song::kEmbeddedCover) {
187     // an embedded cover
188     QImage embedded = TagReaderClient::Instance()->LoadEmbeddedArtAsImageBlocking(song_.url().toLocalFile());
Search()189     if (!embedded.save(new_file)) {
190       EmitCoverSkipped();
191       return;
192     }
193   }
194   else {
195     // Automatic or manual cover, available in an image file
196     if (!QFile::copy(cover_path, new_file)) {
197       EmitCoverSkipped();
198       return;
199     }
200   }
201   EmitCoverExported();
202 
203 }
204 
205 void CoverExportRunnable::EmitCoverExported() { emit CoverExported(); }
206 
207 void CoverExportRunnable::EmitCoverSkipped() { emit CoverSkipped(); }
208