~ubuntu-branches/ubuntu/saucy/clementine/saucy

« back to all changes in this revision

Viewing changes to src/covers/albumcoverloader.cpp

  • Committer: Package Import Robot
  • Author(s): Thomas PIERSON
  • Date: 2012-01-01 20:43:39 UTC
  • mfrom: (1.1.1)
  • Revision ID: package-import@ubuntu.com-20120101204339-lsb6nndwhfy05sde
Tags: 1.0.1+dfsg-1
New upstream release. (Closes: #653926, #651611, #657391)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* This file is part of Clementine.
 
2
   Copyright 2010, David Sansome <me@davidsansome.com>
 
3
 
 
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.
 
8
 
 
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.
 
13
 
 
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/>.
 
16
*/
 
17
 
 
18
#include "albumcoverloader.h"
 
19
#include "config.h"
 
20
#include "core/logging.h"
 
21
#include "core/network.h"
 
22
#include "core/utilities.h"
 
23
#include "internet/internetmodel.h"
 
24
 
 
25
#include <QPainter>
 
26
#include <QDir>
 
27
#include <QCoreApplication>
 
28
#include <QUrl>
 
29
#include <QNetworkReply>
 
30
 
 
31
#ifdef HAVE_SPOTIFY
 
32
# include "internet/spotifyservice.h"
 
33
#endif
 
34
 
 
35
 
 
36
AlbumCoverLoader::AlbumCoverLoader(QObject* parent)
 
37
  : QObject(parent),
 
38
    stop_requested_(false),
 
39
    height_(120),
 
40
    scale_(true),
 
41
    padding_(true),
 
42
    next_id_(0),
 
43
    network_(new NetworkAccessManager(this)),
 
44
    connected_spotify_(false)
 
45
{
 
46
}
 
47
 
 
48
QString AlbumCoverLoader::ImageCacheDir() {
 
49
  return Utilities::GetConfigPath(Utilities::Path_AlbumCovers);
 
50
}
 
51
 
 
52
void AlbumCoverLoader::Clear() {
 
53
  QMutexLocker l(&mutex_);
 
54
  tasks_.clear();
 
55
}
 
56
 
 
57
quint64 AlbumCoverLoader::LoadImageAsync(const QString& art_automatic,
 
58
                                         const QString& art_manual,
 
59
                                         const QString& song_filename,
 
60
                                         const QImage& embedded_image) {
 
61
  Task task;
 
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;
 
67
 
 
68
  {
 
69
    QMutexLocker l(&mutex_);
 
70
    task.id = next_id_ ++;
 
71
    tasks_.enqueue(task);
 
72
  }
 
73
 
 
74
  metaObject()->invokeMethod(this, "ProcessTasks", Qt::QueuedConnection);
 
75
 
 
76
  return task.id;
 
77
}
 
78
 
 
79
void AlbumCoverLoader::ProcessTasks() {
 
80
  while (!stop_requested_) {
 
81
    // Get the next task
 
82
    Task task;
 
83
    {
 
84
      QMutexLocker l(&mutex_);
 
85
      if (tasks_.isEmpty())
 
86
        return;
 
87
      task = tasks_.dequeue();
 
88
    }
 
89
 
 
90
    ProcessTask(&task);
 
91
  }
 
92
}
 
93
 
 
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
 
98
    // when it's done
 
99
    return;
 
100
  }
 
101
 
 
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);
 
106
    return;
 
107
  }
 
108
 
 
109
  NextState(task);
 
110
}
 
111
 
 
112
void AlbumCoverLoader::NextState(Task* task) {
 
113
  if (task->state == State_TryingManual) {
 
114
    // Try the automatic one next
 
115
    task->state = State_TryingAuto;
 
116
    ProcessTask(task);
 
117
  } else {
 
118
    // Give up
 
119
    emit ImageLoaded(task->id, default_);
 
120
    emit ImageLoaded(task->id, default_, default_);
 
121
  }
 
122
}
 
123
 
 
124
AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(
 
125
    const Task& task) {
 
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));
 
129
 
 
130
  QString filename;
 
131
  switch (task.state) {
 
132
    case State_TryingAuto:   filename = task.art_automatic; break;
 
133
    case State_TryingManual: filename = task.art_manual;    break;
 
134
  }
 
135
 
 
136
  if (filename == Song::kManuallyUnsetCover)
 
137
    return TryLoadResult(false, true, default_);
 
138
 
 
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));
 
143
  }
 
144
 
 
145
  if (filename.toLower().startsWith("http://")) {
 
146
    QNetworkReply* reply = network_->get(QNetworkRequest(filename));
 
147
    connect(reply, SIGNAL(finished()), SLOT(RemoteFetchFinished()));
 
148
 
 
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
 
153
    #ifdef HAVE_SPOTIFY
 
154
      SpotifyService* spotify = InternetModel::Service<SpotifyService>();
 
155
 
 
156
      if (!connected_spotify_) {
 
157
        connect(spotify, SIGNAL(ImageLoaded(QString,QImage)),
 
158
                SLOT(SpotifyImageLoaded(QString,QImage)));
 
159
        connected_spotify_ = true;
 
160
      }
 
161
 
 
162
      QString id = QUrl(filename).path();
 
163
      if (id.startsWith('/')) {
 
164
        id.remove(0, 1);
 
165
      }
 
166
      remote_spotify_tasks_.insert(id, task);
 
167
 
 
168
      // Need to schedule this in the spotify service's thread
 
169
      QMetaObject::invokeMethod(spotify, "LoadImage", Qt::QueuedConnection,
 
170
                                Q_ARG(QString, id));
 
171
      return TryLoadResult(true, false, QImage());
 
172
    #else
 
173
      return TryLoadResult(false, false, QImage());
 
174
    #endif
 
175
  }
 
176
 
 
177
  QImage image(filename);
 
178
  return TryLoadResult(false, !image.isNull(), image.isNull() ? default_ : image);
 
179
}
 
180
 
 
181
void AlbumCoverLoader::SpotifyImageLoaded(const QString& id, const QImage& image) {
 
182
  if (!remote_spotify_tasks_.contains(id))
 
183
    return;
 
184
 
 
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);
 
189
}
 
190
 
 
191
void AlbumCoverLoader::RemoteFetchFinished() {
 
192
  QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
 
193
  if (!reply)
 
194
    return;
 
195
  reply->deleteLater();
 
196
 
 
197
  Task task = remote_tasks_.take(reply);
 
198
 
 
199
  // Handle redirects.
 
200
  QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
 
201
  if (redirect.isValid()) {
 
202
    if (++task.redirects > kMaxRedirects) {
 
203
      return;  // Give up.
 
204
    }
 
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()));
 
209
 
 
210
    remote_tasks_.insert(redirected_reply, task);
 
211
    return;
 
212
  }
 
213
 
 
214
  if (reply->error() == QNetworkReply::NoError) {
 
215
    // Try to load the image
 
216
    QImage 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);
 
221
      return;
 
222
    }
 
223
  }
 
224
 
 
225
  NextState(&task);
 
226
}
 
227
 
 
228
QImage AlbumCoverLoader::ScaleAndPad(const QImage& image) const {
 
229
  if (image.isNull())
 
230
    return image;
 
231
 
 
232
  // Scale the image down
 
233
  QImage copy;
 
234
  if (scale_) {
 
235
    copy = image.scaled(QSize(height_, height_),
 
236
                        Qt::KeepAspectRatio, Qt::SmoothTransformation);
 
237
  } else {
 
238
    copy = image;
 
239
  }
 
240
 
 
241
  if (!padding_)
 
242
    return copy;
 
243
 
 
244
  // Pad the image to height_ x height_
 
245
  QImage padded_image(height_, height_, QImage::Format_ARGB32);
 
246
  padded_image.fill(0);
 
247
 
 
248
  QPainter p(&padded_image);
 
249
  p.drawImage((height_ - copy.width()) / 2, (height_ - copy.height()) / 2,
 
250
              copy);
 
251
  p.end();
 
252
 
 
253
  return padded_image;
 
254
}
 
255
 
 
256
QPixmap AlbumCoverLoader::TryLoadPixmap(const QString& automatic,
 
257
                                        const QString& manual,
 
258
                                        const QString& filename) {
 
259
  QPixmap ret;
 
260
  if (manual == Song::kManuallyUnsetCover)
 
261
    return ret;
 
262
  if (!manual.isEmpty())
 
263
    ret.load(manual);
 
264
  if (ret.isNull()) {
 
265
    if (automatic == Song::kEmbeddedCover && !filename.isNull())
 
266
      ret = QPixmap::fromImage(Song::LoadEmbeddedArt(filename));
 
267
    else if (!automatic.isEmpty())
 
268
      ret.load(automatic);
 
269
  }
 
270
  return ret;
 
271
}
 
272
 
 
273
void AlbumCoverLoader::SetDefaultOutputImage(const QImage &image) {
 
274
  default_ = ScaleAndPad(image);
 
275
}
 
276
 
 
277
quint64 AlbumCoverLoader::LoadImageAsync(const Song &song) {
 
278
  return LoadImageAsync(song.art_automatic(), song.art_manual(),
 
279
                        song.url().toLocalFile(), song.image());
 
280
}