/* * Copyright 2013 Canonical Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 3. * * This program 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * Authors: Jussi Pakkanen * Pawel Stolowski */ #include "albumartprovider.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; const std::string AlbumArtProvider::DEFAULT_ALBUM_ART = "/usr/share/unity/icons/album_missing.png"; const std::string AlbumArtProvider::UNITY_LENS_SCHEMA = "com.canonical.Unity.Lenses"; AlbumArtProvider::AlbumArtProvider() : QQuickImageProvider(QQmlImageProviderBase::Image, QQmlImageProviderBase::ForceAsynchronousImageLoading), m_settings(nullptr) { auto schemas = g_settings_list_schemas(); if (schemas) { for (int i = 0; schemas[i]; i++) { if (g_strcmp0(schemas[i], UNITY_LENS_SCHEMA.c_str()) == 0) { m_settings = g_settings_new(UNITY_LENS_SCHEMA.c_str()); break; } } } if (m_settings == nullptr) { qWarning() << "Missing" << QString::fromStdString(UNITY_LENS_SCHEMA) << "schema"; } } AlbumArtProvider::~AlbumArtProvider() { if (m_settings) g_object_unref(m_settings); } std::string AlbumArtProvider::get_lastfm_url(const albuminfo &ai) { QString artist = QString::fromStdString(ai.artist); QString album = QString::fromStdString(ai.album); // audioscrobbler wants it without any / (as it would break the URL) artist.remove('/'); album.remove('/'); /// @todo: this is the old API which will probably get axed at some point in the future /// The new 2.0 API requires an API key, but supports JSON output, etc, so switching /// to it should be done ASAP. QString request = QString("http://ws.audioscrobbler.com/1.0/album/%1/%2/info.xml").arg(artist) .arg(album) .toHtmlEscaped(); QScopedPointer am(new QNetworkAccessManager); QNetworkReply *reply = am->get(QNetworkRequest(QUrl(request))); QEventLoop loop; QObject::connect(am.data(), &QNetworkAccessManager::finished, &loop, &QEventLoop::quit); loop.exec(); if (reply->error() != QNetworkReply::NoError) { qWarning() << "Error getting the XML:" << reply->errorString(); return ""; } QXmlQuery query; QBuffer tmp; tmp.setData(reply->readAll()); tmp.open(QIODevice::ReadOnly); query.bindVariable("reply", &tmp); query.setQuery("doc($reply)/album/coverart/large[1]/text()"); QString image; query.evaluateTo(&image); image = image.trimmed(); // return empty if last.fm tries to offer their default // fallback artwork if (image.contains("catalogue/noimage")) { return ""; } return image.toStdString(); } bool AlbumArtProvider::download_and_store(const std::string &image_url, const std::string &output_file) { QString url = QString::fromStdString(image_url); QString fileName = QString::fromStdString(output_file); QScopedPointer am(new QNetworkAccessManager); QNetworkReply *reply = am->get(QNetworkRequest(QUrl(url))); QEventLoop loop; QObject::connect(am.data(), &QNetworkAccessManager::finished, &loop, &QEventLoop::quit); loop.exec(); if (reply->error() != QNetworkReply::NoError) { qWarning() << "Error getting the image:" << reply->errorString(); return false; } QFile file(fileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { qWarning() << "Could not open file for writing:" << file.errorString(); return false; } if (file.write(reply->readAll()) == -1){ qWarning() << "Could not write the image:" << file.error(); return false; } return true; } void AlbumArtProvider::fix_format(const std::string &fname) { // MediaArtSpec requires jpg. Convert to it if necessary. FILE *f = fopen(fname.c_str(), "r"); if(!f) return; unsigned char buf[2]; fread(buf, 1, 2, f); fclose(f); if(buf[0] == 0xff && buf[1] == 0xd8) { return; } QImage im(fname.c_str()); im.save(fname.c_str(), "JPEG"); } std::string AlbumArtProvider::get_image(const std::string &artist, const std::string &album) { albuminfo info; info.artist = artist; info.album = album; if(info.album.empty() || info.artist.empty()) { return DEFAULT_ALBUM_ART; } if(cache.has_art(info.artist, info.album)) { // Image may have expired from cache between these two lines. // It might expire before we return. C'est la vie. return cache.get_art_file(info.artist, info.album); } if (m_settings != nullptr && g_strcmp0(g_settings_get_string(m_settings, "remote-content-search"), "all") !=0) { qDebug() << "Remote content disabled"; return DEFAULT_ALBUM_ART; } std::string image_url = get_lastfm_url(info); if(image_url.empty()) { return DEFAULT_ALBUM_ART; } QTemporaryFile tempFile; tempFile.open(); tempFile.setAutoRemove(true); QString fname = tempFile.fileName(); std::string tmpname = fname.toUtf8().data(); if(!download_and_store(image_url, tmpname)) { return DEFAULT_ALBUM_ART; } fix_format(tmpname.c_str()); QFile f(fname); if(!f.open(QIODevice::ReadWrite)) { return DEFAULT_ALBUM_ART; } QByteArray arr = f.readAll(); f.close(); cache.add_art(info.artist, info.album, arr.data(), arr.size()); try { std::string res = cache.get_art_file(info.artist, info.album); if(res.empty()) return DEFAULT_ALBUM_ART; return res; } catch(...) { } return DEFAULT_ALBUM_ART; } QImage AlbumArtProvider::requestImage(const QString &id, QSize *realSize, const QSize &requestedSize) { Q_UNUSED(requestedSize) QUrlQuery query(id); if (!query.hasQueryItem(QStringLiteral("artist")) || !query.hasQueryItem(QStringLiteral("album"))) { qWarning() << "Invalid albumart uri:" << id; return QImage(QString::fromStdString(DEFAULT_ALBUM_ART)); } const QString artist = query.queryItemValue(QStringLiteral("artist"), QUrl::FullyEncoded); const QString album = query.queryItemValue(QStringLiteral("album"), QUrl::FullyEncoded); std::string tgt_path; try { tgt_path = get_image(artist.toStdString(), album.toStdString()); if(!tgt_path.empty()) { QImage image; image.load(QString::fromStdString(tgt_path)); // FIXME: Rescale to requested size preserving aspect. *realSize = image.size(); return image; } } catch(std::exception &e) { qDebug() << "Album art loader failed: " << e.what(); } catch(...) { qDebug() << "Unknown error when generating image."; } QImage fallback(QString::fromStdString(DEFAULT_ALBUM_ART)); *realSize = fallback.size(); return fallback; }