1
/* This file is part of Clementine.
2
Copyright 2010, David Sansome <me@davidsansome.com>
4
Clementine is free software: you can redistribute it and/or modify
5
it under the terms of the GNU General Public License as published by
6
the Free Software Foundation, either version 3 of the License, or
7
(at your option) any later version.
9
Clementine is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
GNU General Public License for more details.
14
You should have received a copy of the GNU General Public License
15
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
18
#include "albumcoverloader.h"
20
#include "core/logging.h"
21
#include "core/network.h"
22
#include "core/utilities.h"
23
#include "internet/internetmodel.h"
27
#include <QCoreApplication>
29
#include <QNetworkReply>
32
# include "internet/spotifyservice.h"
36
AlbumCoverLoader::AlbumCoverLoader(QObject* parent)
38
stop_requested_(false),
43
network_(new NetworkAccessManager(this)),
44
connected_spotify_(false)
48
QString AlbumCoverLoader::ImageCacheDir() {
49
return Utilities::GetConfigPath(Utilities::Path_AlbumCovers);
52
void AlbumCoverLoader::Clear() {
53
QMutexLocker l(&mutex_);
57
quint64 AlbumCoverLoader::LoadImageAsync(const QString& art_automatic,
58
const QString& art_manual,
59
const QString& song_filename,
60
const QImage& embedded_image) {
62
task.art_automatic = art_automatic;
63
task.art_manual = art_manual;
64
task.song_filename = song_filename;
65
task.embedded_image = embedded_image;
66
task.state = State_TryingManual;
69
QMutexLocker l(&mutex_);
70
task.id = next_id_ ++;
74
metaObject()->invokeMethod(this, "ProcessTasks", Qt::QueuedConnection);
79
void AlbumCoverLoader::ProcessTasks() {
80
while (!stop_requested_) {
84
QMutexLocker l(&mutex_);
87
task = tasks_.dequeue();
94
void AlbumCoverLoader::ProcessTask(Task *task) {
95
TryLoadResult result = TryLoadImage(*task);
96
if (result.started_async) {
97
// The image is being loaded from a remote URL, we'll carry on later
102
if (result.loaded_success) {
103
QImage scaled = ScaleAndPad(result.image);
104
emit ImageLoaded(task->id, scaled);
105
emit ImageLoaded(task->id, scaled, result.image);
112
void AlbumCoverLoader::NextState(Task* task) {
113
if (task->state == State_TryingManual) {
114
// Try the automatic one next
115
task->state = State_TryingAuto;
119
emit ImageLoaded(task->id, default_);
120
emit ImageLoaded(task->id, default_, default_);
124
AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(
126
// An image embedded in the song itself takes priority
127
if (!task.embedded_image.isNull())
128
return TryLoadResult(false, true, ScaleAndPad(task.embedded_image));
131
switch (task.state) {
132
case State_TryingAuto: filename = task.art_automatic; break;
133
case State_TryingManual: filename = task.art_manual; break;
136
if (filename == Song::kManuallyUnsetCover)
137
return TryLoadResult(false, true, default_);
139
if (filename == Song::kEmbeddedCover && !task.song_filename.isEmpty()) {
140
QImage taglib_image = Song::LoadEmbeddedArt(task.song_filename);
141
if (!taglib_image.isNull())
142
return TryLoadResult(false, true, ScaleAndPad(taglib_image));
145
if (filename.toLower().startsWith("http://")) {
146
QNetworkReply* reply = network_->get(QNetworkRequest(filename));
147
connect(reply, SIGNAL(finished()), SLOT(RemoteFetchFinished()));
149
remote_tasks_.insert(reply, task);
150
return TryLoadResult(true, false, QImage());
151
} else if (filename.toLower().startsWith("spotify://image/")) {
152
// HACK: we should add generic image URL handlers
154
SpotifyService* spotify = InternetModel::Service<SpotifyService>();
156
if (!connected_spotify_) {
157
connect(spotify, SIGNAL(ImageLoaded(QString,QImage)),
158
SLOT(SpotifyImageLoaded(QString,QImage)));
159
connected_spotify_ = true;
162
QString id = QUrl(filename).path();
163
if (id.startsWith('/')) {
166
remote_spotify_tasks_.insert(id, task);
168
// Need to schedule this in the spotify service's thread
169
QMetaObject::invokeMethod(spotify, "LoadImage", Qt::QueuedConnection,
171
return TryLoadResult(true, false, QImage());
173
return TryLoadResult(false, false, QImage());
177
QImage image(filename);
178
return TryLoadResult(false, !image.isNull(), image.isNull() ? default_ : image);
181
void AlbumCoverLoader::SpotifyImageLoaded(const QString& id, const QImage& image) {
182
if (!remote_spotify_tasks_.contains(id))
185
Task task = remote_spotify_tasks_.take(id);
186
QImage scaled = ScaleAndPad(image);
187
emit ImageLoaded(task.id, scaled);
188
emit ImageLoaded(task.id, scaled, image);
191
void AlbumCoverLoader::RemoteFetchFinished() {
192
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
195
reply->deleteLater();
197
Task task = remote_tasks_.take(reply);
200
QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
201
if (redirect.isValid()) {
202
if (++task.redirects > kMaxRedirects) {
205
QNetworkRequest request = reply->request();
206
request.setUrl(redirect.toUrl());
207
QNetworkReply* redirected_reply = network_->get(request);
208
connect(redirected_reply, SIGNAL(finished()), SLOT(RemoteFetchFinished()));
210
remote_tasks_.insert(redirected_reply, task);
214
if (reply->error() == QNetworkReply::NoError) {
215
// Try to load the image
217
if (image.load(reply, 0)) {
218
QImage scaled = ScaleAndPad(image);
219
emit ImageLoaded(task.id, scaled);
220
emit ImageLoaded(task.id, scaled, image);
228
QImage AlbumCoverLoader::ScaleAndPad(const QImage& image) const {
232
// Scale the image down
235
copy = image.scaled(QSize(height_, height_),
236
Qt::KeepAspectRatio, Qt::SmoothTransformation);
244
// Pad the image to height_ x height_
245
QImage padded_image(height_, height_, QImage::Format_ARGB32);
246
padded_image.fill(0);
248
QPainter p(&padded_image);
249
p.drawImage((height_ - copy.width()) / 2, (height_ - copy.height()) / 2,
256
QPixmap AlbumCoverLoader::TryLoadPixmap(const QString& automatic,
257
const QString& manual,
258
const QString& filename) {
260
if (manual == Song::kManuallyUnsetCover)
262
if (!manual.isEmpty())
265
if (automatic == Song::kEmbeddedCover && !filename.isNull())
266
ret = QPixmap::fromImage(Song::LoadEmbeddedArt(filename));
267
else if (!automatic.isEmpty())
273
void AlbumCoverLoader::SetDefaultOutputImage(const QImage &image) {
274
default_ = ScaleAndPad(image);
277
quint64 AlbumCoverLoader::LoadImageAsync(const Song &song) {
278
return LoadImageAsync(song.art_automatic(), song.art_manual(),
279
song.url().toLocalFile(), song.image());