on 5b03213 help sign in
unity8/plugins/Utils/albumartprovider.cpp
231 lines 7.8 kB history raw
1/*
2 * Copyright 2013 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors: Jussi Pakkanen <jussi.pakkanen@canonical.com>
17 * Pawel Stolowski <pawel.stolowski@canonical.com>
18*/
20#include "albumartprovider.h"
21#include <QString>
22#include <QNetworkAccessManager>
23#include <QEventLoop>
24#include <QNetworkRequest>
25#include <QNetworkReply>
26#include <QXmlQuery>
27#include <QTemporaryFile>
28#include <QBuffer>
29#include <QDebug>
30#include <QFile>
31#include <QStringList>
32#include <QByteArray>
33#include <QImage>
34#include <QUrl>
36using namespace std;
38const std::string AlbumArtProvider::DEFAULT_ALBUM_ART = "/usr/share/unity/icons/album_missing.png";
39const std::string AlbumArtProvider::UNITY_LENS_SCHEMA = "com.canonical.Unity.Lenses";
41AlbumArtProvider::AlbumArtProvider()
42 : QQuickImageProvider(QQmlImageProviderBase::Image, QQmlImageProviderBase::ForceAsynchronousImageLoading),
43 m_settings(nullptr)
45 auto schemas = g_settings_list_schemas();
46 if (schemas) {
47 for (int i = 0; schemas[i]; i++) {
48 if (g_strcmp0(schemas[i], UNITY_LENS_SCHEMA.c_str()) == 0) {
49 m_settings = g_settings_new(UNITY_LENS_SCHEMA.c_str());
50 break;
51 }
52 }
53 }
54 if (m_settings == nullptr) {
55 qWarning() << "Missing" << QString::fromStdString(UNITY_LENS_SCHEMA) << "schema";
56 }
59AlbumArtProvider::~AlbumArtProvider()
61 if (m_settings)
62 g_object_unref(m_settings);
65std::string AlbumArtProvider::get_lastfm_url(const albuminfo &ai) {
66 QString artist = QString::fromStdString(ai.artist);
67 QString album = QString::fromStdString(ai.album);
69 /// @todo: this is the old API which will probably get axed at some point in the future
70 /// The new 2.0 API requires an API key, but supports JSON output, etc, so switching
71 /// to it should be done ASAP.
72 QString request = QString("http://ws.audioscrobbler.com/1.0/album/%1/%2/info.xml").arg(artist)
73 .arg(album)
74 .toHtmlEscaped();
75 QScopedPointer<QNetworkAccessManager> am(new QNetworkAccessManager);
76 QNetworkReply *reply = am->get(QNetworkRequest(QUrl(request)));
77 QEventLoop loop;
78 QObject::connect(am.data(), &QNetworkAccessManager::finished, &loop, &QEventLoop::quit);
79 loop.exec();
81 if (reply->error() != QNetworkReply::NoError) {
82 qWarning() << "Error getting the XML:" << reply->errorString();
83 return "";
84 }
86 QXmlQuery query;
87 QBuffer tmp;
88 tmp.setData(reply->readAll());
89 tmp.open(QIODevice::ReadOnly);
90 query.bindVariable("reply", &tmp);
91 query.setQuery("doc($reply)/album/coverart/large[1]/text()");
92 QString image;
93 query.evaluateTo(&image);
94 image = image.trimmed();
96 // return empty if last.fm tries to offer their default
97 // fallback artwork
98 if (image.contains("catalogue/noimage")) {
99 return "";
100 }
102 return image.toStdString();
105bool AlbumArtProvider::download_and_store(const std::string &image_url, const std::string &output_file) {
106 QString url = QString::fromStdString(image_url);
107 QString fileName = QString::fromStdString(output_file);
109 QScopedPointer<QNetworkAccessManager> am(new QNetworkAccessManager);
110 QNetworkReply *reply = am->get(QNetworkRequest(QUrl(url)));
111 QEventLoop loop;
112 QObject::connect(am.data(), &QNetworkAccessManager::finished, &loop, &QEventLoop::quit);
113 loop.exec();
115 if (reply->error() != QNetworkReply::NoError) {
116 qWarning() << "Error getting the image:" << reply->errorString();
117 return false;
118 }
120 QFile file(fileName);
121 if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
122 qWarning() << "Could not open file for writing:" << file.errorString();
123 return false;
124 }
125 if (file.write(reply->readAll()) == -1){
126 qWarning() << "Could not write the image:" << file.error();
127 return false;
128 }
130 return true;
133void AlbumArtProvider::fix_format(const std::string &fname) {
134 // MediaArtSpec requires jpg. Convert to it if necessary.
135 FILE *f = fopen(fname.c_str(), "r");
136 if(!f)
137 return;
138 unsigned char buf[2];
139 fread(buf, 1, 2, f);
140 fclose(f);
141 if(buf[0] == 0xff && buf[1] == 0xd8) {
142 return;
143 }
144 QImage im(fname.c_str());
145 im.save(fname.c_str(), "JPEG");
148std::string AlbumArtProvider::get_image(const std::string &artist, const std::string &album) {
149 albuminfo info;
151 info.artist = artist;
152 info.album = album;
154 if(info.album.empty() || info.artist.empty()) {
155 return DEFAULT_ALBUM_ART;
156 }
157 if(cache.has_art(info.artist, info.album)) {
158 // Image may have expired from cache between these two lines.
159 // It might expire before we return. C'est la vie.
160 return cache.get_art_file(info.artist, info.album);
161 }
163 if (m_settings != nullptr && g_strcmp0(g_settings_get_string(m_settings, "remote-content-search"), "all") !=0) {
164 qDebug() << "Remote content disabled";
165 return DEFAULT_ALBUM_ART;
166 }
168 std::string image_url = get_lastfm_url(info);
169 if(image_url.empty()) {
170 return DEFAULT_ALBUM_ART;
171 }
172 QTemporaryFile tempFile;
173 tempFile.open();
174 tempFile.setAutoRemove(true);
175 QString fname = tempFile.fileName();
176 std::string tmpname = fname.toUtf8().data();
178 if(!download_and_store(image_url, tmpname)) {
179 return DEFAULT_ALBUM_ART;
180 }
181 fix_format(tmpname.c_str());
182 QFile f(fname);
183 if(!f.open(QIODevice::ReadWrite)) {
184 return DEFAULT_ALBUM_ART;
185 }
186 QByteArray arr = f.readAll();
187 f.close();
189 cache.add_art(info.artist, info.album, arr.data(), arr.size());
190 try {
191 std::string res = cache.get_art_file(info.artist, info.album);
192 if(res.empty())
193 return DEFAULT_ALBUM_ART;
194 return res;
195 } catch(...) {
196 }
197 return DEFAULT_ALBUM_ART;
200QImage AlbumArtProvider::requestImage(const QString &id, QSize *realSize, const QSize &requestedSize) {
201 Q_UNUSED(requestedSize)
203 const QStringList parts = id.split("/");
204 if (parts.size() != 2) {
205 qWarning() << "Invalid albumart uri:" << id;
206 return QImage(QString::fromStdString(DEFAULT_ALBUM_ART));
207 }
208 const std::string artist = QUrl::fromPercentEncoding(parts[0].toUtf8()).toStdString();
209 const std::string album = QUrl::fromPercentEncoding(parts[1].toUtf8()).toStdString();
211 std::string tgt_path;
212 try {
213 tgt_path = get_image(artist, album);
214 if(!tgt_path.empty()) {
215 QString tgt(tgt_path.c_str());
216 QImage image;
217 image.load(tgt);
218 // FIXME: Rescale to requested size preserving aspect.
219 *realSize = image.size();
220 return image;
221 }
222 } catch(std::exception &e) {
223 qDebug() << "Album art loader failed: " << e.what();
224 } catch(...) {
225 qDebug() << "Unknown error when generating image.";
226 }
228 QImage fallback(QString::fromStdString(DEFAULT_ALBUM_ART));
229 *realSize = fallback.size();
230 return fallback;