Initial commit.
This commit is contained in:
348
src/covermanager/albumcoverchoicecontroller.cpp
Normal file
348
src/covermanager/albumcoverchoicecontroller.cpp
Normal file
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QDesktopWidget>
|
||||
#include <QDialog>
|
||||
#include <QDragEnterEvent>
|
||||
#include <QFileDialog>
|
||||
#include <QImageWriter>
|
||||
#include <QLabel>
|
||||
#include <QList>
|
||||
#include <QMenu>
|
||||
#include <QUrl>
|
||||
#include <QMimeData>
|
||||
|
||||
#include "covermanager/albumcoverchoicecontroller.h"
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/utilities.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
|
||||
#include "covermanager/albumcoverfetcher.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "covermanager/currentartloader.h"
|
||||
#include "covermanager/albumcovermanager.h"
|
||||
#include "covermanager/albumcoversearcher.h"
|
||||
#include "covermanager/coverfromurldialog.h"
|
||||
|
||||
const char *AlbumCoverChoiceController::kLoadImageFileFilter = QT_TR_NOOP("Images (*.png *.jpg *.jpeg *.bmp *.gif *.xpm *.pbm *.pgm *.ppm *.xbm)");
|
||||
const char *AlbumCoverChoiceController::kSaveImageFileFilter = QT_TR_NOOP("Images (*.png *.jpg *.jpeg *.bmp *.xpm *.pbm *.ppm *.xbm)");
|
||||
const char *AlbumCoverChoiceController::kAllFilesFilter = QT_TR_NOOP("All files (*)");
|
||||
|
||||
QSet<QString> *AlbumCoverChoiceController::sImageExtensions = nullptr;
|
||||
|
||||
AlbumCoverChoiceController::AlbumCoverChoiceController(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
app_(nullptr),
|
||||
cover_searcher_(nullptr),
|
||||
cover_fetcher_(nullptr),
|
||||
save_file_dialog_(nullptr),
|
||||
cover_from_url_dialog_(nullptr) {
|
||||
|
||||
cover_from_file_ = new QAction(IconLoader::Load("document-open"), tr("Load cover from disk..."), this);
|
||||
cover_to_file_ = new QAction(IconLoader::Load("document-save"), tr("Save cover to disk..."), this);
|
||||
cover_from_url_ = new QAction(IconLoader::Load("download"), tr("Load cover from URL..."), this);
|
||||
search_for_cover_ = new QAction(IconLoader::Load("search"), tr("Search for album covers..."), this);
|
||||
unset_cover_ = new QAction(IconLoader::Load("list-remove"), tr("Unset cover"), this);
|
||||
show_cover_ = new QAction(IconLoader::Load("zoom-in"), tr("Show fullsize..."), this);
|
||||
|
||||
search_cover_auto_ = new QAction(IconLoader::Load("search"), tr("Search automatically"), this);
|
||||
search_cover_auto_->setCheckable(true);
|
||||
search_cover_auto_->setChecked(false);
|
||||
|
||||
separator_ = new QAction(this);
|
||||
separator_->setSeparator(true);
|
||||
|
||||
}
|
||||
|
||||
AlbumCoverChoiceController::~AlbumCoverChoiceController() {}
|
||||
|
||||
void AlbumCoverChoiceController::SetApplication(Application *app) {
|
||||
|
||||
app_ = app;
|
||||
|
||||
cover_fetcher_ = new AlbumCoverFetcher(app_->cover_providers(), this);
|
||||
cover_searcher_ = new AlbumCoverSearcher(QIcon(":/pictures/noalbumart.png"), app, this);
|
||||
cover_searcher_->Init(cover_fetcher_);
|
||||
|
||||
connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(quint64, QImage, CoverSearchStatistics)), this, SLOT(AlbumCoverFetched(quint64, QImage, CoverSearchStatistics)));
|
||||
|
||||
}
|
||||
|
||||
QList<QAction*> AlbumCoverChoiceController::GetAllActions() {
|
||||
return QList<QAction*>() << cover_from_file_ << cover_to_file_ << separator_ << cover_from_url_ << search_for_cover_ << unset_cover_ << show_cover_;
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::LoadCoverFromFile(Song *song) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
QString cover = QFileDialog::getOpenFileName(this, tr("Load cover from disk"), GetInitialPathForFileDialog(*song, QString()), tr(kLoadImageFileFilter) + ";;" + tr(kAllFilesFilter));
|
||||
|
||||
if (cover.isNull()) return QString();
|
||||
|
||||
// Can we load the image?
|
||||
QImage image(cover);
|
||||
|
||||
if (!image.isNull()) {
|
||||
SaveCover(song, cover);
|
||||
return cover;
|
||||
}
|
||||
else {
|
||||
return QString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::SaveCoverToFile(const Song &song, const QImage &image) {
|
||||
|
||||
QString initial_file_name = "/" + (song.effective_album().isEmpty() ? tr("Unknown") : song.effective_album()) + ".jpg";
|
||||
|
||||
QString save_filename = QFileDialog::getSaveFileName(this, tr("Save album cover"), GetInitialPathForFileDialog(song, initial_file_name), tr(kSaveImageFileFilter) + ";;" + tr(kAllFilesFilter));
|
||||
|
||||
if (save_filename.isNull()) return;
|
||||
|
||||
QString extension = save_filename.right(4);
|
||||
if (!extension.startsWith('.') || !QImageWriter::supportedImageFormats().contains(extension.right(3).toUtf8())) {
|
||||
save_filename.append(".jpg");
|
||||
}
|
||||
|
||||
image.save(save_filename);
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::GetInitialPathForFileDialog(const Song &song, const QString &filename) {
|
||||
|
||||
// art automatic is first to show user which cover the album may be
|
||||
// using now; the song is using it if there's no manual path but we
|
||||
// cannot use manual path here because it can contain cached paths
|
||||
if (!song.art_automatic().isEmpty() && !song.has_embedded_cover()) {
|
||||
return song.art_automatic();
|
||||
|
||||
// if no automatic art, start in the song's folder
|
||||
}
|
||||
else if (!song.url().isEmpty() && song.url().toLocalFile().contains('/')) {
|
||||
return song.url().toLocalFile().section('/', 0, -2) + filename;
|
||||
|
||||
// fallback - start in home
|
||||
}
|
||||
else {
|
||||
return QDir::home().absolutePath() + filename;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::LoadCoverFromURL(Song *song) {
|
||||
|
||||
if (!cover_from_url_dialog_) { cover_from_url_dialog_ = new CoverFromURLDialog(this); }
|
||||
|
||||
QImage image = cover_from_url_dialog_->Exec();
|
||||
|
||||
if (!image.isNull()) {
|
||||
QString cover = SaveCoverInCache(song->artist(), song->album(), image);
|
||||
SaveCover(song, cover);
|
||||
|
||||
return cover;
|
||||
}
|
||||
else { return QString(); }
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::SearchForCover(Song *song) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
QString album = song->effective_album();
|
||||
album = album.remove(QRegExp(" ?-? ?(\\(|\\[)(Disc|CD)? ?[0-9](\\)|\\])$"));
|
||||
|
||||
// Get something sensible to stick in the search box
|
||||
QImage image = cover_searcher_->Exec(song->effective_albumartist(), album);
|
||||
|
||||
if (!image.isNull()) {
|
||||
QString cover = SaveCoverInCache(song->artist(), song->album(), image);
|
||||
SaveCover(song, cover);
|
||||
|
||||
return cover;
|
||||
}
|
||||
else { return QString(); }
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::UnsetCover(Song *song) {
|
||||
|
||||
QString cover = Song::kManuallyUnsetCover;
|
||||
SaveCover(song, cover);
|
||||
|
||||
return cover;
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::ShowCover(const Song &song) {
|
||||
|
||||
QDialog *dialog = new QDialog(this);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
|
||||
// Use Artist - Album as the window title
|
||||
QString title_text(song.effective_albumartist());
|
||||
if (!song.effective_album().isEmpty()) title_text += " - " + song.effective_album();
|
||||
|
||||
QLabel *label = new QLabel(dialog);
|
||||
label->setPixmap(AlbumCoverLoader::TryLoadPixmap(song.art_automatic(), song.art_manual(), song.url().toLocalFile()));
|
||||
|
||||
// add (WxHpx) to the title before possibly resizing
|
||||
title_text += " (" + QString::number(label->pixmap()->width()) + "x" + QString::number(label->pixmap()->height()) + "px)";
|
||||
|
||||
// if the cover is larger than the screen, resize the window
|
||||
// 85% seems to be enough to account for title bar and taskbar etc.
|
||||
QDesktopWidget desktop;
|
||||
int current_screen = desktop.screenNumber(this);
|
||||
int desktop_height = desktop.screenGeometry(current_screen).height();
|
||||
int desktop_width = desktop.screenGeometry(current_screen).width();
|
||||
|
||||
// resize differently if monitor is in portrait mode
|
||||
if (desktop_width < desktop_height) {
|
||||
const int new_width = (double)desktop_width * 0.95;
|
||||
if (new_width < label->pixmap()->width()) {
|
||||
label->setPixmap(
|
||||
label->pixmap()->scaledToWidth(new_width, Qt::SmoothTransformation));
|
||||
}
|
||||
}
|
||||
else {
|
||||
const int new_height = (double)desktop_height * 0.85;
|
||||
if (new_height < label->pixmap()->height()) {
|
||||
label->setPixmap(label->pixmap()->scaledToHeight(
|
||||
new_height, Qt::SmoothTransformation));
|
||||
}
|
||||
}
|
||||
|
||||
dialog->setWindowTitle(title_text);
|
||||
dialog->setFixedSize(label->pixmap()->size());
|
||||
dialog->show();
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::SearchCoverAutomatically(const Song &song) {
|
||||
|
||||
qint64 id = cover_fetcher_->FetchAlbumCover(song.effective_albumartist(), song.effective_album());
|
||||
|
||||
cover_fetching_tasks_[id] = song;
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::AlbumCoverFetched(quint64 id, const QImage &image, const CoverSearchStatistics &statistics) {
|
||||
|
||||
Song song;
|
||||
if (cover_fetching_tasks_.contains(id)) {
|
||||
song = cover_fetching_tasks_.take(id);
|
||||
}
|
||||
|
||||
if (!image.isNull()) {
|
||||
QString cover = SaveCoverInCache(song.artist(), song.album(), image);
|
||||
SaveCover(&song, cover);
|
||||
}
|
||||
|
||||
emit AutomaticCoverSearchDone();
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverChoiceController::SaveCover(Song *song, const QString &cover) {
|
||||
|
||||
if (song->is_valid() && song->id() != -1) {
|
||||
song->set_art_manual(cover);
|
||||
app_->collection_backend()->UpdateManualAlbumArtAsync(song->artist(), song->albumartist(), song->album(), cover);
|
||||
|
||||
if (song->url() == app_->current_art_loader()->last_song().url()) {
|
||||
app_->current_art_loader()->LoadArt(*song);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::SaveCoverInCache(const QString &artist, const QString &album, const QImage &image) {
|
||||
|
||||
// Hash the artist and album into a filename for the image
|
||||
QString filename(Utilities::Sha1CoverHash(artist, album).toHex() + ".jpg");
|
||||
QString path(AlbumCoverLoader::ImageCacheDir() + "/" + filename);
|
||||
|
||||
// Make sure this directory exists first
|
||||
QDir dir;
|
||||
dir.mkdir(AlbumCoverLoader::ImageCacheDir());
|
||||
|
||||
// Save the image to disk
|
||||
image.save(path, "JPG");
|
||||
|
||||
return path;
|
||||
|
||||
}
|
||||
|
||||
bool AlbumCoverChoiceController::IsKnownImageExtension(const QString &suffix) {
|
||||
|
||||
if (!sImageExtensions) {
|
||||
sImageExtensions = new QSet<QString>();
|
||||
(*sImageExtensions) << "png" << "jpg" << "jpeg" << "bmp" << "gif" << "xpm" << "pbm" << "pgm" << "ppm" << "xbm";
|
||||
}
|
||||
|
||||
return sImageExtensions->contains(suffix);
|
||||
|
||||
}
|
||||
|
||||
bool AlbumCoverChoiceController::CanAcceptDrag(const QDragEnterEvent *e) {
|
||||
|
||||
for (const QUrl &url : e->mimeData()->urls()) {
|
||||
const QString suffix = QFileInfo(url.toLocalFile()).suffix().toLower();
|
||||
if (IsKnownImageExtension(suffix)) return true;
|
||||
}
|
||||
if (e->mimeData()->hasImage()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverChoiceController::SaveCover(Song *song, const QDropEvent *e) {
|
||||
|
||||
for (const QUrl &url : e->mimeData()->urls()) {
|
||||
const QString filename = url.toLocalFile();
|
||||
const QString suffix = QFileInfo(filename).suffix().toLower();
|
||||
|
||||
if (IsKnownImageExtension(suffix)) {
|
||||
SaveCover(song, filename);
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
|
||||
if (e->mimeData()->hasImage()) {
|
||||
QImage image = qvariant_cast<QImage>(e->mimeData()->imageData());
|
||||
if (!image.isNull()) {
|
||||
QString cover_path = SaveCoverInCache(song->artist(), song->album(), image);
|
||||
SaveCover(song, cover_path);
|
||||
return cover_path;
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
|
||||
}
|
||||
|
||||
148
src/covermanager/albumcoverchoicecontroller.h
Normal file
148
src/covermanager/albumcoverchoicecontroller.h
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ALBUMCOVERCHOICECONTROLLER_H
|
||||
#define ALBUMCOVERCHOICECONTROLLER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QList>
|
||||
#include <QMenu>
|
||||
#include <QWidget>
|
||||
|
||||
class AlbumCoverFetcher;
|
||||
class AlbumCoverSearcher;
|
||||
class Application;
|
||||
class CoverFromURLDialog;
|
||||
class QFileDialog;
|
||||
class Song;
|
||||
|
||||
struct CoverSearchStatistics;
|
||||
|
||||
// Controller for the common album cover related menu options.
|
||||
class AlbumCoverChoiceController : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static const char *kLoadImageFileFilter;
|
||||
static const char *kSaveImageFileFilter;
|
||||
static const char *kAllFilesFilter;
|
||||
|
||||
AlbumCoverChoiceController(QWidget *parent = nullptr);
|
||||
~AlbumCoverChoiceController();
|
||||
|
||||
void SetApplication(Application *app);
|
||||
|
||||
// Getters for all QActions implemented by this controller.
|
||||
|
||||
QAction *cover_from_file_action() const { return cover_from_file_; }
|
||||
QAction *cover_to_file_action() const { return cover_to_file_; }
|
||||
QAction *cover_from_url_action() const { return cover_from_url_; }
|
||||
QAction *search_for_cover_action() const { return search_for_cover_; }
|
||||
QAction *unset_cover_action() const { return unset_cover_; }
|
||||
QAction *show_cover_action() const { return show_cover_; }
|
||||
QAction *search_cover_auto_action() const { return search_cover_auto_; }
|
||||
|
||||
// Returns QAction* for every operation implemented by this controller.
|
||||
// The list contains QAction* for:
|
||||
// 1. loading cover from file
|
||||
// 2. loading cover from URL
|
||||
// 3. searching for cover using last.fm
|
||||
// 4. unsetting the cover manually
|
||||
// 5. showing the cover in original size
|
||||
QList<QAction*> GetAllActions();
|
||||
|
||||
// All of the methods below require a currently selected song as an
|
||||
// input parameter. Also - LoadCoverFromFile, LoadCoverFromURL,
|
||||
// SearchForCover, UnsetCover and SaveCover all update manual path
|
||||
// of the given song in collection to the new cover.
|
||||
|
||||
// Lets the user choose a cover from disk. If no cover will be chosen or the chosen
|
||||
// cover will not be a proper image, this returns an empty string. Otherwise, the
|
||||
// path to the chosen cover will be returned.
|
||||
QString LoadCoverFromFile(Song *song);
|
||||
|
||||
// Shows a dialog that allows user to save the given image on disk. The image
|
||||
// is supposed to be the cover of the given song's album.
|
||||
void SaveCoverToFile(const Song &song, const QImage &image);
|
||||
|
||||
// Downloads the cover from an URL given by user. This returns the downloaded image
|
||||
// or null image if something went wrong for example when user cancelled the
|
||||
// dialog.
|
||||
QString LoadCoverFromURL(Song *song);
|
||||
|
||||
// Lets the user choose a cover among all that have been found on last.fm.
|
||||
// Returns the chosen cover or null cover if user didn't choose anything.
|
||||
QString SearchForCover(Song *song);
|
||||
|
||||
// Returns a path which indicates that the cover has been unset manually.
|
||||
QString UnsetCover(Song *song);
|
||||
|
||||
// Shows the cover of given song in it's original size.
|
||||
void ShowCover(const Song &song);
|
||||
|
||||
// Search for covers automatically
|
||||
void SearchCoverAutomatically(const Song &song);
|
||||
|
||||
// Saves the chosen cover as manual cover path of this song in collection.
|
||||
void SaveCover(Song *song, const QString &cover);
|
||||
|
||||
// Saves the cover that the user picked through a drag and drop operation.
|
||||
QString SaveCover(Song *song, const QDropEvent *e);
|
||||
|
||||
// Saves the given image in cache as a cover for 'artist' - 'album'.
|
||||
// The method returns path of the cached image.
|
||||
QString SaveCoverInCache(const QString &artist, const QString &album, const QImage &image);
|
||||
|
||||
static bool CanAcceptDrag(const QDragEnterEvent *e);
|
||||
|
||||
signals:
|
||||
void AutomaticCoverSearchDone();
|
||||
|
||||
private slots:
|
||||
void AlbumCoverFetched(quint64 id, const QImage &image, const CoverSearchStatistics &statistics);
|
||||
|
||||
private:
|
||||
QString GetInitialPathForFileDialog(const Song &song, const QString &filename);
|
||||
|
||||
static bool IsKnownImageExtension(const QString &suffix);
|
||||
static QSet<QString> *sImageExtensions;
|
||||
|
||||
Application *app_;
|
||||
AlbumCoverSearcher *cover_searcher_;
|
||||
AlbumCoverFetcher *cover_fetcher_;
|
||||
|
||||
QFileDialog *save_file_dialog_;
|
||||
CoverFromURLDialog *cover_from_url_dialog_;
|
||||
|
||||
QAction *cover_from_file_;
|
||||
QAction *cover_to_file_;
|
||||
QAction *separator_;
|
||||
QAction *cover_from_url_;
|
||||
QAction *search_for_cover_;
|
||||
QAction *unset_cover_;
|
||||
QAction *show_cover_;
|
||||
QAction *search_cover_auto_;
|
||||
|
||||
QMap<quint64, Song> cover_fetching_tasks_;
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERCHOICECONTROLLER_H
|
||||
96
src/covermanager/albumcoverexport.cpp
Normal file
96
src/covermanager/albumcoverexport.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "albumcoverexport.h"
|
||||
#include "ui_albumcoverexport.h"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
const char *AlbumCoverExport::kSettingsGroup = "AlbumCoverExport";
|
||||
|
||||
AlbumCoverExport::AlbumCoverExport(QWidget *parent) : QDialog(parent), ui_(new Ui_AlbumCoverExport) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
|
||||
connect(ui_->forceSize, SIGNAL(stateChanged(int)), SLOT(ForceSizeToggled(int)));
|
||||
|
||||
}
|
||||
|
||||
AlbumCoverExport::~AlbumCoverExport() { delete ui_; }
|
||||
|
||||
AlbumCoverExport::DialogResult AlbumCoverExport::Exec() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
|
||||
// restore last accepted settings
|
||||
ui_->fileName->setText(s.value("fileName", "cover").toString());
|
||||
ui_->doNotOverwrite->setChecked(s.value("overwrite", OverwriteMode_None).toInt() == OverwriteMode_None);
|
||||
ui_->overwriteAll->setChecked(s.value("overwrite", OverwriteMode_All).toInt() == OverwriteMode_All);
|
||||
ui_->overwriteSmaller->setChecked(s.value("overwrite", OverwriteMode_Smaller).toInt() == OverwriteMode_Smaller);
|
||||
ui_->forceSize->setChecked(s.value("forceSize", false).toBool());
|
||||
ui_->width->setText(s.value("width", "").toString());
|
||||
ui_->height->setText(s.value("height", "").toString());
|
||||
ui_->export_downloaded->setChecked(s.value("export_downloaded", true).toBool());
|
||||
ui_->export_embedded->setChecked(s.value("export_embedded", false).toBool());
|
||||
|
||||
ForceSizeToggled(ui_->forceSize->checkState());
|
||||
|
||||
DialogResult result = DialogResult();
|
||||
result.cancelled_ = (exec() == QDialog::Rejected);
|
||||
|
||||
if (!result.cancelled_) {
|
||||
QString fileName = ui_->fileName->text();
|
||||
if (fileName.isEmpty()) {
|
||||
fileName = "cover";
|
||||
}
|
||||
OverwriteMode overwrite = ui_->doNotOverwrite->isChecked() ? OverwriteMode_None : (ui_->overwriteAll->isChecked() ? OverwriteMode_All : OverwriteMode_Smaller);
|
||||
bool forceSize = ui_->forceSize->isChecked();
|
||||
QString width = ui_->width->text();
|
||||
QString height = ui_->height->text();
|
||||
|
||||
s.setValue("fileName", fileName);
|
||||
s.setValue("overwrite", overwrite);
|
||||
s.setValue("forceSize", forceSize);
|
||||
s.setValue("width", width);
|
||||
s.setValue("height", height);
|
||||
s.setValue("export_downloaded", ui_->export_downloaded->isChecked());
|
||||
s.setValue("export_embedded", ui_->export_embedded->isChecked());
|
||||
|
||||
result.fileName_ = fileName;
|
||||
result.overwrite_ = overwrite;
|
||||
result.forceSize_ = forceSize;
|
||||
result.width_ = width.toInt();
|
||||
result.height_ = height.toInt();
|
||||
result.export_downloaded_ = ui_->export_downloaded->isChecked();
|
||||
result.export_embedded_ = ui_->export_embedded->isChecked();
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverExport::ForceSizeToggled(int state) {
|
||||
ui_->width->setEnabled(state == Qt::Checked);
|
||||
ui_->height->setEnabled(state == Qt::Checked);
|
||||
}
|
||||
|
||||
77
src/covermanager/albumcoverexport.h
Normal file
77
src/covermanager/albumcoverexport.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ALBUMCOVEREXPORT_H
|
||||
#define ALBUMCOVEREXPORT_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class Ui_AlbumCoverExport;
|
||||
|
||||
// Controller for the "Export covers" dialog.
|
||||
class AlbumCoverExport : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AlbumCoverExport(QWidget *parent = nullptr);
|
||||
~AlbumCoverExport();
|
||||
|
||||
enum OverwriteMode {
|
||||
OverwriteMode_None = 0,
|
||||
OverwriteMode_All = 1,
|
||||
OverwriteMode_Smaller = 2
|
||||
};
|
||||
|
||||
struct DialogResult {
|
||||
bool cancelled_;
|
||||
|
||||
bool export_downloaded_;
|
||||
bool export_embedded_;
|
||||
|
||||
QString fileName_;
|
||||
OverwriteMode overwrite_;
|
||||
bool forceSize_;
|
||||
int width_;
|
||||
int height_;
|
||||
|
||||
bool IsSizeForced() const {
|
||||
return forceSize_ && width_ > 0 && height_ > 0;
|
||||
}
|
||||
|
||||
bool RequiresCoverProcessing() const {
|
||||
return IsSizeForced() || overwrite_ == OverwriteMode_Smaller;
|
||||
}
|
||||
};
|
||||
|
||||
DialogResult Exec();
|
||||
|
||||
private slots:
|
||||
void ForceSizeToggled(int state);
|
||||
|
||||
private:
|
||||
Ui_AlbumCoverExport *ui_;
|
||||
|
||||
static const char *kSettingsGroup;
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVEREXPORT_H
|
||||
|
||||
240
src/covermanager/albumcoverexport.ui
Normal file
240
src/covermanager/albumcoverexport.ui
Normal file
@@ -0,0 +1,240 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AlbumCoverExport</class>
|
||||
<widget class="QDialog" name="AlbumCoverExport">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>608</width>
|
||||
<height>412</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Export covers</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Output</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Enter a filename for exported covers (no extension):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="fileName"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="export_downloaded">
|
||||
<property name="text">
|
||||
<string>Export downloaded covers</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="export_embedded">
|
||||
<property name="text">
|
||||
<string>Export embedded covers</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Existing covers</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="doNotOverwrite">
|
||||
<property name="text">
|
||||
<string>Do not overwrite</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="overwriteAll">
|
||||
<property name="text">
|
||||
<string>Overwrite all</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="overwriteSmaller">
|
||||
<property name="text">
|
||||
<string>Overwrite smaller ones only</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Size</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="forceSize">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Scale size</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="width">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="inputMethodHints">
|
||||
<set>Qt::ImhDigitsOnly</set>
|
||||
</property>
|
||||
<property name="inputMask">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>4</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string notr="true">×</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="height">
|
||||
<property name="inputMethodHints">
|
||||
<set>Qt::ImhDigitsOnly</set>
|
||||
</property>
|
||||
<property name="inputMask">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>4</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Pixel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Preferred</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>AlbumCoverExport</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>AlbumCoverExport</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
82
src/covermanager/albumcoverexporter.cpp
Normal file
82
src/covermanager/albumcoverexporter.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QThreadPool>
|
||||
|
||||
#include "albumcoverexporter.h"
|
||||
#include "coverexportrunnable.h"
|
||||
#include "core/song.h"
|
||||
|
||||
const int AlbumCoverExporter::kMaxConcurrentRequests = 3;
|
||||
|
||||
AlbumCoverExporter::AlbumCoverExporter(QObject *parent)
|
||||
: QObject(parent),
|
||||
thread_pool_(new QThreadPool(this)),
|
||||
exported_(0),
|
||||
skipped_(0),
|
||||
all_(0) {
|
||||
thread_pool_->setMaxThreadCount(kMaxConcurrentRequests);
|
||||
}
|
||||
|
||||
void AlbumCoverExporter::SetDialogResult(const AlbumCoverExport::DialogResult& dialog_result) {
|
||||
dialog_result_ = dialog_result;
|
||||
}
|
||||
|
||||
void AlbumCoverExporter::AddExportRequest(Song song) {
|
||||
requests_.append(new CoverExportRunnable(dialog_result_, song));
|
||||
all_ = requests_.count();
|
||||
}
|
||||
|
||||
void AlbumCoverExporter::Cancel() { requests_.clear(); }
|
||||
|
||||
void AlbumCoverExporter::StartExporting() {
|
||||
exported_ = 0;
|
||||
skipped_ = 0;
|
||||
AddJobsToPool();
|
||||
}
|
||||
|
||||
void AlbumCoverExporter::AddJobsToPool() {
|
||||
|
||||
while (!requests_.isEmpty() && thread_pool_->activeThreadCount() < thread_pool_->maxThreadCount()) {
|
||||
CoverExportRunnable *runnable = requests_.dequeue();
|
||||
|
||||
connect(runnable, SIGNAL(CoverExported()), SLOT(CoverExported()));
|
||||
connect(runnable, SIGNAL(CoverSkipped()), SLOT(CoverSkipped()));
|
||||
|
||||
thread_pool_->start(runnable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverExporter::CoverExported() {
|
||||
exported_++;
|
||||
emit AlbumCoversExportUpdate(exported_, skipped_, all_);
|
||||
AddJobsToPool();
|
||||
}
|
||||
|
||||
void AlbumCoverExporter::CoverSkipped() {
|
||||
skipped_++;
|
||||
emit AlbumCoversExportUpdate(exported_, skipped_, all_);
|
||||
AddJobsToPool();
|
||||
}
|
||||
|
||||
72
src/covermanager/albumcoverexporter.h
Normal file
72
src/covermanager/albumcoverexporter.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ALBUMCOVEREXPORTER_H
|
||||
#define ALBUMCOVEREXPORTER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "coverexportrunnable.h"
|
||||
#include "core/song.h"
|
||||
#include "covermanager/albumcoverexport.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QQueue>
|
||||
#include <QTimer>
|
||||
|
||||
class QThreadPool;
|
||||
|
||||
class AlbumCoverExporter : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AlbumCoverExporter(QObject *parent = nullptr);
|
||||
virtual ~AlbumCoverExporter() {}
|
||||
|
||||
static const int kMaxConcurrentRequests;
|
||||
|
||||
void SetDialogResult(const AlbumCoverExport::DialogResult &dialog_result);
|
||||
void AddExportRequest(Song song);
|
||||
void StartExporting();
|
||||
void Cancel();
|
||||
|
||||
int request_count() { return requests_.size(); }
|
||||
|
||||
signals:
|
||||
void AlbumCoversExportUpdate(int exported, int skipped, int all);
|
||||
|
||||
private slots:
|
||||
void CoverExported();
|
||||
void CoverSkipped();
|
||||
|
||||
private:
|
||||
void AddJobsToPool();
|
||||
AlbumCoverExport::DialogResult dialog_result_;
|
||||
|
||||
QQueue<CoverExportRunnable*> requests_;
|
||||
QThreadPool *thread_pool_;
|
||||
|
||||
int exported_;
|
||||
int skipped_;
|
||||
int all_;
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVEREXPORTER_H
|
||||
|
||||
132
src/covermanager/albumcoverfetcher.cpp
Normal file
132
src/covermanager/albumcoverfetcher.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#include "albumcoverfetcher.h"
|
||||
#include "albumcoverfetchersearch.h"
|
||||
#include "core/network.h"
|
||||
|
||||
const int AlbumCoverFetcher::kMaxConcurrentRequests = 5;
|
||||
|
||||
AlbumCoverFetcher::AlbumCoverFetcher(CoverProviders *cover_providers, QObject *parent, QNetworkAccessManager *network)
|
||||
: QObject(parent),
|
||||
cover_providers_(cover_providers),
|
||||
network_(network ? network : new NetworkAccessManager(this)),
|
||||
next_id_(0),
|
||||
request_starter_(new QTimer(this)) {
|
||||
request_starter_->setInterval(1000);
|
||||
connect(request_starter_, SIGNAL(timeout()), SLOT(StartRequests()));
|
||||
}
|
||||
|
||||
quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString &album) {
|
||||
|
||||
CoverSearchRequest request;
|
||||
request.artist = artist;
|
||||
request.album = album;
|
||||
request.search = false;
|
||||
request.id = next_id_++;
|
||||
|
||||
AddRequest(request);
|
||||
return request.id;
|
||||
|
||||
}
|
||||
|
||||
quint64 AlbumCoverFetcher::SearchForCovers(const QString &artist, const QString &album) {
|
||||
|
||||
CoverSearchRequest request;
|
||||
request.artist = artist;
|
||||
request.album = album;
|
||||
request.search = true;
|
||||
request.id = next_id_++;
|
||||
|
||||
AddRequest(request);
|
||||
return request.id;
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverFetcher::AddRequest(const CoverSearchRequest &req) {
|
||||
|
||||
queued_requests_.enqueue(req);
|
||||
|
||||
if (!request_starter_->isActive()) request_starter_->start();
|
||||
|
||||
if (active_requests_.size() < kMaxConcurrentRequests) StartRequests();
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverFetcher::Clear() {
|
||||
|
||||
queued_requests_.clear();
|
||||
|
||||
for (AlbumCoverFetcherSearch *search : active_requests_.values()) {
|
||||
search->Cancel();
|
||||
search->deleteLater();
|
||||
}
|
||||
active_requests_.clear();
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverFetcher::StartRequests() {
|
||||
|
||||
if (queued_requests_.isEmpty()) {
|
||||
request_starter_->stop();
|
||||
return;
|
||||
}
|
||||
|
||||
while (!queued_requests_.isEmpty() && active_requests_.size() < kMaxConcurrentRequests) {
|
||||
|
||||
CoverSearchRequest request = queued_requests_.dequeue();
|
||||
|
||||
// search objects are this fetcher's children so worst case scenario - they get
|
||||
// deleted with it
|
||||
AlbumCoverFetcherSearch *search = new AlbumCoverFetcherSearch(request, network_, this);
|
||||
active_requests_.insert(request.id, search);
|
||||
|
||||
connect(search, SIGNAL(SearchFinished(quint64, CoverSearchResults)), SLOT(SingleSearchFinished(quint64, CoverSearchResults)));
|
||||
connect(search, SIGNAL(AlbumCoverFetched(quint64, const QImage&)), SLOT(SingleCoverFetched(quint64, const QImage&)));
|
||||
|
||||
search->Start(cover_providers_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverFetcher::SingleSearchFinished(quint64 request_id, CoverSearchResults results) {
|
||||
|
||||
AlbumCoverFetcherSearch *search = active_requests_.take(request_id);
|
||||
if (!search) return;
|
||||
|
||||
search->deleteLater();
|
||||
emit SearchFinished(request_id, results, search->statistics());
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverFetcher::SingleCoverFetched(quint64 request_id, const QImage &image) {
|
||||
|
||||
AlbumCoverFetcherSearch *search = active_requests_.take(request_id);
|
||||
if (!search) return;
|
||||
|
||||
search->deleteLater();
|
||||
emit AlbumCoverFetched(request_id, image, search->statistics());
|
||||
|
||||
}
|
||||
|
||||
117
src/covermanager/albumcoverfetcher.h
Normal file
117
src/covermanager/albumcoverfetcher.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ALBUMCOVERFETCHER_H
|
||||
#define ALBUMCOVERFETCHER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "coversearchstatistics.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QImage>
|
||||
#include <QList>
|
||||
#include <QMetaType>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
#include <QQueue>
|
||||
#include <QUrl>
|
||||
|
||||
class QNetworkReply;
|
||||
class QString;
|
||||
|
||||
class AlbumCoverFetcherSearch;
|
||||
class CoverProviders;
|
||||
|
||||
// This class represents a single search-for-cover request. It identifies
|
||||
// and describes the request.
|
||||
struct CoverSearchRequest {
|
||||
// an unique (for one AlbumCoverFetcher) request identifier
|
||||
quint64 id;
|
||||
|
||||
// a search query
|
||||
QString artist;
|
||||
QString album;
|
||||
|
||||
// is this only a search request or should we also fetch the first
|
||||
// cover that's found?
|
||||
bool search;
|
||||
};
|
||||
|
||||
// This structure represents a single result of some album's cover search request.
|
||||
// It contains an URL that leads to a found cover plus its description (usually
|
||||
// the "artist - album" string).
|
||||
struct CoverSearchResult {
|
||||
// used for grouping in the user interface. This is set automatically - don't
|
||||
// set it manually in your cover provider.
|
||||
QString provider;
|
||||
|
||||
// description of this result (we suggest using the "artist - album" format)
|
||||
QString description;
|
||||
|
||||
// an URL of a cover image described by this CoverSearchResult
|
||||
QUrl image_url;
|
||||
};
|
||||
Q_DECLARE_METATYPE(CoverSearchResult);
|
||||
|
||||
// This is a complete result of a single search request (a list of results, each
|
||||
// describing one image, actually).
|
||||
typedef QList<CoverSearchResult> CoverSearchResults;
|
||||
Q_DECLARE_METATYPE(QList<CoverSearchResult>);
|
||||
|
||||
// This class searches for album covers for a given query or artist/album and
|
||||
// returns URLs. It's NOT thread-safe.
|
||||
class AlbumCoverFetcher : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AlbumCoverFetcher(CoverProviders *cover_providers, QObject *parent = nullptr, QNetworkAccessManager *network = 0);
|
||||
virtual ~AlbumCoverFetcher() {}
|
||||
|
||||
static const int kMaxConcurrentRequests;
|
||||
|
||||
quint64 SearchForCovers(const QString &artist, const QString &album);
|
||||
quint64 FetchAlbumCover(const QString &artist, const QString &album);
|
||||
|
||||
void Clear();
|
||||
|
||||
signals:
|
||||
void AlbumCoverFetched(quint64, const QImage &cover, const CoverSearchStatistics &statistics);
|
||||
void SearchFinished(quint64, const CoverSearchResults &results, const CoverSearchStatistics &statistics);
|
||||
|
||||
private slots:
|
||||
void SingleSearchFinished(quint64, CoverSearchResults results);
|
||||
void SingleCoverFetched(quint64, const QImage &cover);
|
||||
void StartRequests();
|
||||
|
||||
private:
|
||||
void AddRequest(const CoverSearchRequest &req);
|
||||
|
||||
CoverProviders *cover_providers_;
|
||||
QNetworkAccessManager *network_;
|
||||
quint64 next_id_;
|
||||
|
||||
QQueue<CoverSearchRequest> queued_requests_;
|
||||
QHash<quint64, AlbumCoverFetcherSearch*> active_requests_;
|
||||
|
||||
QTimer *request_starter_;
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERFETCHER_H
|
||||
271
src/covermanager/albumcoverfetchersearch.cpp
Normal file
271
src/covermanager/albumcoverfetchersearch.cpp
Normal file
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "albumcoverfetchersearch.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QMutexLocker>
|
||||
#include <QNetworkReply>
|
||||
#include <QTimer>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "albumcoverfetcher.h"
|
||||
#include "coverprovider.h"
|
||||
#include "coverproviders.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
|
||||
const int AlbumCoverFetcherSearch::kSearchTimeoutMs = 10000;
|
||||
const int AlbumCoverFetcherSearch::kImageLoadTimeoutMs = 2500;
|
||||
const int AlbumCoverFetcherSearch::kTargetSize = 500;
|
||||
const float AlbumCoverFetcherSearch::kGoodScore = 1.85;
|
||||
|
||||
AlbumCoverFetcherSearch::AlbumCoverFetcherSearch(
|
||||
const CoverSearchRequest &request, QNetworkAccessManager *network, QObject *parent)
|
||||
: QObject(parent),
|
||||
request_(request),
|
||||
image_load_timeout_(new NetworkTimeouts(kImageLoadTimeoutMs, this)),
|
||||
network_(network),
|
||||
cancel_requested_(false) {
|
||||
// we will terminate the search after kSearchTimeoutMs miliseconds if we are
|
||||
// not
|
||||
// able to find all of the results before that point in time
|
||||
QTimer::singleShot(kSearchTimeoutMs, this, SLOT(TerminateSearch()));
|
||||
}
|
||||
|
||||
void AlbumCoverFetcherSearch::TerminateSearch() {
|
||||
for (int id : pending_requests_.keys()) {
|
||||
pending_requests_.take(id)->CancelSearch(id);
|
||||
}
|
||||
|
||||
AllProvidersFinished();
|
||||
}
|
||||
|
||||
void AlbumCoverFetcherSearch::Start(CoverProviders *cover_providers) {
|
||||
|
||||
for (CoverProvider *provider : cover_providers->List()) {
|
||||
connect(provider, SIGNAL(SearchFinished(int, QList<CoverSearchResult>)), SLOT(ProviderSearchFinished(int, QList<CoverSearchResult>)));
|
||||
const int id = cover_providers->NextId();
|
||||
const bool success = provider->StartSearch(request_.artist, request_.album, id);
|
||||
|
||||
if (success) {
|
||||
pending_requests_[id] = provider;
|
||||
statistics_.network_requests_made_++;
|
||||
}
|
||||
}
|
||||
|
||||
// end this search before it even began if there are no providers...
|
||||
if (pending_requests_.isEmpty()) {
|
||||
TerminateSearch();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static bool CompareProviders(const CoverSearchResult &a, const CoverSearchResult &b) {
|
||||
return a.provider < b.provider;
|
||||
}
|
||||
|
||||
void AlbumCoverFetcherSearch::ProviderSearchFinished(int id, const QList<CoverSearchResult> &results) {
|
||||
|
||||
if (!pending_requests_.contains(id)) return;
|
||||
|
||||
CoverProvider *provider = pending_requests_.take(id);
|
||||
|
||||
CoverSearchResults results_copy(results);
|
||||
// Set categories on the results
|
||||
for (int i = 0; i < results_copy.count(); ++i) {
|
||||
results_copy[i].provider = provider->name();
|
||||
}
|
||||
|
||||
// Add results from the current provider to our pool
|
||||
results_.append(results_copy);
|
||||
statistics_.total_images_by_provider_[provider->name()]++;
|
||||
|
||||
// do we have more providers left?
|
||||
if (!pending_requests_.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AllProvidersFinished();
|
||||
}
|
||||
|
||||
void AlbumCoverFetcherSearch::AllProvidersFinished() {
|
||||
|
||||
if (cancel_requested_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if we only wanted to do the search then we're done
|
||||
if (request_.search) {
|
||||
emit SearchFinished(request_.id, results_);
|
||||
return;
|
||||
}
|
||||
|
||||
// no results?
|
||||
if (results_.isEmpty()) {
|
||||
statistics_.missing_images_++;
|
||||
emit AlbumCoverFetched(request_.id, QImage());
|
||||
return;
|
||||
}
|
||||
|
||||
// Now we have to load some images and figure out which one is the best.
|
||||
// We'll sort the list of results by category, then load the first few images
|
||||
// from each category and use some heuristics to score them. If no images
|
||||
// are good enough we'll keep loading more images until we find one that is
|
||||
// or we run out of results.
|
||||
qStableSort(results_.begin(), results_.end(), CompareProviders);
|
||||
FetchMoreImages();
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverFetcherSearch::FetchMoreImages() {
|
||||
|
||||
// Try the first one in each category.
|
||||
QString last_provider;
|
||||
for (int i = 0; i < results_.count(); ++i) {
|
||||
if (results_[i].provider == last_provider) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CoverSearchResult result = results_.takeAt(i--);
|
||||
last_provider = result.provider;
|
||||
|
||||
qLog(Debug) << "Loading" << result.image_url << "from" << result.provider;
|
||||
|
||||
RedirectFollower *image_reply = new RedirectFollower(network_->get(QNetworkRequest(result.image_url)));
|
||||
NewClosure(image_reply, SIGNAL(finished()), this, SLOT(ProviderCoverFetchFinished(RedirectFollower*)), image_reply);
|
||||
pending_image_loads_[image_reply] = result.provider;
|
||||
image_load_timeout_->AddReply(image_reply);
|
||||
|
||||
statistics_.network_requests_made_++;
|
||||
}
|
||||
|
||||
if (pending_image_loads_.isEmpty()) {
|
||||
// There were no more results? Time to give up.
|
||||
SendBestImage();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverFetcherSearch::ProviderCoverFetchFinished(RedirectFollower *reply) {
|
||||
|
||||
reply->deleteLater();
|
||||
const QString provider = pending_image_loads_.take(reply);
|
||||
|
||||
statistics_.bytes_transferred_ += reply->bytesAvailable();
|
||||
|
||||
if (cancel_requested_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qLog(Info) << "Error requesting" << reply->url() << reply->errorString();
|
||||
}
|
||||
else {
|
||||
QImage image;
|
||||
if (!image.loadFromData(reply->readAll())) {
|
||||
qLog(Info) << "Error decoding image data from" << reply->url();
|
||||
}
|
||||
else {
|
||||
const float score = ScoreImage(image);
|
||||
candidate_images_.insertMulti(score, CandidateImage(provider, image));
|
||||
|
||||
qLog(Debug) << reply->url() << "scored" << score;
|
||||
}
|
||||
}
|
||||
|
||||
if (pending_image_loads_.isEmpty()) {
|
||||
// We've fetched everything we wanted to fetch for now, check if we have an
|
||||
// image that's good enough.
|
||||
float best_score = 0.0;
|
||||
|
||||
if (!candidate_images_.isEmpty()) {
|
||||
best_score = candidate_images_.keys().last();
|
||||
}
|
||||
|
||||
qLog(Debug) << "Best image so far has a score of" << best_score;
|
||||
if (best_score >= kGoodScore) {
|
||||
SendBestImage();
|
||||
}
|
||||
else {
|
||||
FetchMoreImages();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
float AlbumCoverFetcherSearch::ScoreImage(const QImage &image) const {
|
||||
|
||||
// Invalid images score nothing
|
||||
if (image.isNull()) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// A 500x500px image scores 1.0, bigger scores higher
|
||||
const float size_score = std::sqrt(float(image.width() * image.height())) / kTargetSize;
|
||||
|
||||
// A 1:1 image scores 1.0, anything else scores less
|
||||
const float aspect_score = 1.0 - float(image.height() - image.width()) / std::max(image.height(), image.width());
|
||||
|
||||
return size_score + aspect_score;
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverFetcherSearch::SendBestImage() {
|
||||
|
||||
QImage image;
|
||||
|
||||
if (!candidate_images_.isEmpty()) {
|
||||
const CandidateImage best_image = candidate_images_.values().back();
|
||||
image = best_image.second;
|
||||
|
||||
statistics_.chosen_images_by_provider_[best_image.first]++;
|
||||
statistics_.chosen_images_++;
|
||||
statistics_.chosen_width_ += image.width();
|
||||
statistics_.chosen_height_ += image.height();
|
||||
}
|
||||
else {
|
||||
statistics_.missing_images_++;
|
||||
}
|
||||
|
||||
emit AlbumCoverFetched(request_.id, image);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverFetcherSearch::Cancel() {
|
||||
|
||||
cancel_requested_ = true;
|
||||
|
||||
if (!pending_requests_.isEmpty()) {
|
||||
TerminateSearch();
|
||||
}
|
||||
else if (!pending_image_loads_.isEmpty()) {
|
||||
for (RedirectFollower *reply : pending_image_loads_.keys()) {
|
||||
reply->abort();
|
||||
}
|
||||
pending_image_loads_.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
102
src/covermanager/albumcoverfetchersearch.h
Normal file
102
src/covermanager/albumcoverfetchersearch.h
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ALBUMCOVERFETCHERSEARCH_H
|
||||
#define ALBUMCOVERFETCHERSEARCH_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "albumcoverfetcher.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
|
||||
class CoverProvider;
|
||||
class CoverProviders;
|
||||
class NetworkTimeouts;
|
||||
class NetworkAccessManager;
|
||||
class RedirectFollower;
|
||||
|
||||
// This class encapsulates a single search for covers initiated by an
|
||||
// AlbumCoverFetcher. The search engages all of the known cover providers.
|
||||
// AlbumCoverFetcherSearch signals search results to an interested
|
||||
// AlbumCoverFetcher when all of the providers have done their part.
|
||||
class AlbumCoverFetcherSearch : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AlbumCoverFetcherSearch(const CoverSearchRequest &request, QNetworkAccessManager *network, QObject *parent);
|
||||
|
||||
void Start(CoverProviders* cover_providers);
|
||||
|
||||
// Cancels all pending requests. No Finished signals will be emitted, and it
|
||||
// is the caller's responsibility to delete the AlbumCoverFetcherSearch.
|
||||
void Cancel();
|
||||
|
||||
CoverSearchStatistics statistics() const { return statistics_; }
|
||||
|
||||
signals:
|
||||
// It's the end of search (when there was no fetch-me-a-cover request).
|
||||
void SearchFinished(quint64, const CoverSearchResults& results);
|
||||
|
||||
// It's the end of search and we've fetched a cover.
|
||||
void AlbumCoverFetched(quint64, const QImage &cover);
|
||||
|
||||
private slots:
|
||||
void ProviderSearchFinished(int id, const QList<CoverSearchResult> &results);
|
||||
void ProviderCoverFetchFinished(RedirectFollower *reply);
|
||||
void TerminateSearch();
|
||||
|
||||
private:
|
||||
void AllProvidersFinished();
|
||||
|
||||
void FetchMoreImages();
|
||||
float ScoreImage(const QImage &image) const;
|
||||
void SendBestImage();
|
||||
|
||||
private:
|
||||
static const int kSearchTimeoutMs;
|
||||
static const int kImageLoadTimeoutMs;
|
||||
static const int kTargetSize;
|
||||
static const float kGoodScore;
|
||||
|
||||
CoverSearchStatistics statistics_;
|
||||
|
||||
// Search request encapsulated by this AlbumCoverFetcherSearch.
|
||||
CoverSearchRequest request_;
|
||||
|
||||
// Complete results (from all of the available providers).
|
||||
CoverSearchResults results_;
|
||||
|
||||
QMap<int, CoverProvider*> pending_requests_;
|
||||
QMap<RedirectFollower*, QString> pending_image_loads_;
|
||||
NetworkTimeouts* image_load_timeout_;
|
||||
|
||||
// QMap is sorted by key (score). Values are (provider_name, image)
|
||||
typedef QPair<QString, QImage> CandidateImage;
|
||||
QMap<float, CandidateImage> candidate_images_;
|
||||
|
||||
QNetworkAccessManager *network_;
|
||||
|
||||
bool cancel_requested_;
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERFETCHERSEARCH_H
|
||||
|
||||
279
src/covermanager/albumcoverloader.cpp
Normal file
279
src/covermanager/albumcoverloader.cpp
Normal file
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "albumcoverloader.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QDir>
|
||||
#include <QCoreApplication>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "config.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/utilities.h"
|
||||
|
||||
AlbumCoverLoader::AlbumCoverLoader(QObject *parent)
|
||||
: QObject(parent),
|
||||
stop_requested_(false),
|
||||
next_id_(1),
|
||||
network_(new NetworkAccessManager(this)){}
|
||||
|
||||
QString AlbumCoverLoader::ImageCacheDir() {
|
||||
return Utilities::GetConfigPath(Utilities::Path_AlbumCovers);
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::CancelTask(quint64 id) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
QMutexLocker l(&mutex_);
|
||||
for (QQueue<Task>::iterator it = tasks_.begin(); it != tasks_.end(); ++it) {
|
||||
if (it->id == id) {
|
||||
tasks_.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::CancelTasks(const QSet<quint64> &ids) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
QMutexLocker l(&mutex_);
|
||||
for (QQueue<Task>::iterator it = tasks_.begin(); it != tasks_.end();) {
|
||||
if (ids.contains(it->id)) {
|
||||
it = tasks_.erase(it);
|
||||
}
|
||||
else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions& options, const Song &song) {
|
||||
return LoadImageAsync(options, song.art_automatic(), song.art_manual(), song.url().toLocalFile(), song.image());
|
||||
}
|
||||
|
||||
quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QString &art_automatic, const QString &art_manual, const QString &song_filename, const QImage &embedded_image) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__ << art_automatic << art_manual << song_filename;
|
||||
|
||||
Task task;
|
||||
task.options = options;
|
||||
task.art_automatic = art_automatic;
|
||||
task.art_manual = art_manual;
|
||||
task.song_filename = song_filename;
|
||||
task.embedded_image = embedded_image;
|
||||
task.state = State_TryingManual;
|
||||
|
||||
{
|
||||
QMutexLocker l(&mutex_);
|
||||
task.id = next_id_++;
|
||||
tasks_.enqueue(task);
|
||||
}
|
||||
|
||||
metaObject()->invokeMethod(this, "ProcessTasks", Qt::QueuedConnection);
|
||||
|
||||
return task.id;
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::ProcessTasks() {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
while (!stop_requested_) {
|
||||
// Get the next task
|
||||
Task task;
|
||||
{
|
||||
QMutexLocker l(&mutex_);
|
||||
if (tasks_.isEmpty()) return;
|
||||
task = tasks_.dequeue();
|
||||
}
|
||||
|
||||
ProcessTask(&task);
|
||||
}
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::ProcessTask(Task *task) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
TryLoadResult result = TryLoadImage(*task);
|
||||
if (result.started_async) {
|
||||
// The image is being loaded from a remote URL, we'll carry on later
|
||||
// when it's done
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.loaded_success) {
|
||||
QImage scaled = ScaleAndPad(task->options, result.image);
|
||||
emit ImageLoaded(task->id, scaled);
|
||||
emit ImageLoaded(task->id, scaled, result.image);
|
||||
return;
|
||||
}
|
||||
|
||||
NextState(task);
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::NextState(Task *task) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
if (task->state == State_TryingManual) {
|
||||
// Try the automatic one next
|
||||
task->state = State_TryingAuto;
|
||||
ProcessTask(task);
|
||||
}
|
||||
else {
|
||||
// Give up
|
||||
emit ImageLoaded(task->id, task->options.default_output_image_);
|
||||
emit ImageLoaded(task->id, task->options.default_output_image_, task->options.default_output_image_);
|
||||
}
|
||||
}
|
||||
|
||||
AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(const Task &task) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
// An image embedded in the song itself takes priority
|
||||
if (!task.embedded_image.isNull())
|
||||
return TryLoadResult(false, true, ScaleAndPad(task.options, task.embedded_image));
|
||||
|
||||
QString filename;
|
||||
switch (task.state) {
|
||||
case State_TryingAuto: filename = task.art_automatic; break;
|
||||
case State_TryingManual: filename = task.art_manual; break;
|
||||
}
|
||||
|
||||
if (filename == Song::kManuallyUnsetCover)
|
||||
return TryLoadResult(false, true, task.options.default_output_image_);
|
||||
|
||||
if (filename == Song::kEmbeddedCover && !task.song_filename.isEmpty()) {
|
||||
const QImage taglib_image = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task.song_filename);
|
||||
|
||||
if (!taglib_image.isNull())
|
||||
return TryLoadResult(false, true, ScaleAndPad(task.options, taglib_image));
|
||||
}
|
||||
|
||||
if (filename.toLower().startsWith("http://") || filename.toLower().startsWith("https://")) {
|
||||
|
||||
QUrl url(filename);
|
||||
QNetworkReply* reply = network_->get(QNetworkRequest(url));
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*)), reply);
|
||||
|
||||
remote_tasks_.insert(reply, task);
|
||||
return TryLoadResult(true, false, QImage());
|
||||
}
|
||||
else if (filename.isEmpty()) {
|
||||
// Avoid "QFSFileEngine::open: No file name specified" messages if we know that the filename is empty
|
||||
return TryLoadResult(false, false, task.options.default_output_image_);
|
||||
}
|
||||
|
||||
QImage image(filename);
|
||||
return TryLoadResult(false, !image.isNull(), image.isNull() ? task.options.default_output_image_ : image);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
Task task = remote_tasks_.take(reply);
|
||||
|
||||
// Handle redirects.
|
||||
QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
|
||||
if (redirect.isValid()) {
|
||||
if (++task.redirects > kMaxRedirects) {
|
||||
return; // Give up.
|
||||
}
|
||||
QNetworkRequest request = reply->request();
|
||||
request.setUrl(redirect.toUrl());
|
||||
QNetworkReply* redirected_reply = network_->get(request);
|
||||
NewClosure(redirected_reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*)), redirected_reply);
|
||||
|
||||
remote_tasks_.insert(redirected_reply, task);
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
// Try to load the image
|
||||
QImage image;
|
||||
if (image.load(reply, 0)) {
|
||||
QImage scaled = ScaleAndPad(task.options, image);
|
||||
emit ImageLoaded(task.id, scaled);
|
||||
emit ImageLoaded(task.id, scaled, image);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NextState(&task);
|
||||
}
|
||||
|
||||
QImage AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
if (image.isNull()) return image;
|
||||
|
||||
// Scale the image down
|
||||
QImage copy;
|
||||
if (options.scale_output_image_) {
|
||||
copy = image.scaled(QSize(options.desired_height_, options.desired_height_), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
}
|
||||
else {
|
||||
copy = image;
|
||||
}
|
||||
|
||||
if (!options.pad_output_image_) return copy;
|
||||
|
||||
// Pad the image to height_ x height_
|
||||
QImage padded_image(options.desired_height_, options.desired_height_, QImage::Format_ARGB32);
|
||||
padded_image.fill(0);
|
||||
|
||||
QPainter p(&padded_image);
|
||||
p.drawImage((options.desired_height_ - copy.width()) / 2, (options.desired_height_ - copy.height()) / 2, copy);
|
||||
p.end();
|
||||
|
||||
return padded_image;
|
||||
}
|
||||
|
||||
QPixmap AlbumCoverLoader::TryLoadPixmap(const QString &automatic, const QString &manual, const QString &filename) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__ << automatic << manual << filename;
|
||||
|
||||
QPixmap ret;
|
||||
if (manual == Song::kManuallyUnsetCover) return ret;
|
||||
if (!manual.isEmpty()) ret.load(manual);
|
||||
if (ret.isNull()) {
|
||||
if (automatic == Song::kEmbeddedCover && !filename.isNull())
|
||||
ret = QPixmap::fromImage(TagReaderClient::Instance()->LoadEmbeddedArtBlocking(filename));
|
||||
else if (!automatic.isEmpty())
|
||||
ret.load(automatic);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
110
src/covermanager/albumcoverloader.h
Normal file
110
src/covermanager/albumcoverloader.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ALBUMCOVERLOADER_H
|
||||
#define ALBUMCOVERLOADER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "albumcoverloaderoptions.h"
|
||||
#include "core/song.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QQueue>
|
||||
#include <QUrl>
|
||||
|
||||
class NetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
class AlbumCoverLoader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AlbumCoverLoader(QObject *parent = nullptr);
|
||||
|
||||
void Stop() { stop_requested_ = true; }
|
||||
|
||||
static QString ImageCacheDir();
|
||||
|
||||
quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const Song &song);
|
||||
virtual quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QString &art_automatic, const QString &art_manual, const QString &song_filename = QString(), const QImage &embedded_image = QImage());
|
||||
|
||||
void CancelTask(quint64 id);
|
||||
void CancelTasks(const QSet<quint64> &ids);
|
||||
|
||||
static QPixmap TryLoadPixmap(const QString &automatic, const QString &manual, const QString &filename = QString());
|
||||
static QImage ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image);
|
||||
|
||||
signals:
|
||||
void ImageLoaded(quint64 id, const QImage &image);
|
||||
void ImageLoaded(quint64 id, const QImage &scaled, const QImage &original);
|
||||
|
||||
protected slots:
|
||||
void ProcessTasks();
|
||||
void RemoteFetchFinished(QNetworkReply* reply);
|
||||
|
||||
protected:
|
||||
enum State {
|
||||
State_TryingManual,
|
||||
State_TryingAuto,
|
||||
};
|
||||
|
||||
struct Task {
|
||||
Task() : redirects(0) {}
|
||||
|
||||
AlbumCoverLoaderOptions options;
|
||||
|
||||
quint64 id;
|
||||
QString art_automatic;
|
||||
QString art_manual;
|
||||
QString song_filename;
|
||||
QImage embedded_image;
|
||||
State state;
|
||||
int redirects;
|
||||
};
|
||||
|
||||
struct TryLoadResult {
|
||||
TryLoadResult(bool async, bool success, const QImage &i) : started_async(async), loaded_success(success), image(i) {}
|
||||
|
||||
bool started_async;
|
||||
bool loaded_success;
|
||||
QImage image;
|
||||
};
|
||||
|
||||
void ProcessTask(Task* task);
|
||||
void NextState(Task* task);
|
||||
TryLoadResult TryLoadImage(const Task &task);
|
||||
|
||||
bool stop_requested_;
|
||||
|
||||
QMutex mutex_;
|
||||
QQueue<Task> tasks_;
|
||||
QMap<QNetworkReply *, Task> remote_tasks_;
|
||||
quint64 next_id_;
|
||||
|
||||
NetworkAccessManager *network_;
|
||||
|
||||
static const int kMaxRedirects = 3;
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERLOADER_H
|
||||
|
||||
2
src/covermanager/albumcoverloaderoptions.cpp
Normal file
2
src/covermanager/albumcoverloaderoptions.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#include "config.h"
|
||||
#include "albumcoverloaderoptions.h"
|
||||
41
src/covermanager/albumcoverloaderoptions.h
Normal file
41
src/covermanager/albumcoverloaderoptions.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ALBUMCOVERLOADEROPTIONS_H
|
||||
#define ALBUMCOVERLOADEROPTIONS_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QImage>
|
||||
|
||||
struct AlbumCoverLoaderOptions {
|
||||
AlbumCoverLoaderOptions()
|
||||
: desired_height_(120),
|
||||
scale_output_image_(true),
|
||||
pad_output_image_(true) {}
|
||||
|
||||
int desired_height_;
|
||||
bool scale_output_image_;
|
||||
bool pad_output_image_;
|
||||
QImage default_output_image_;
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERLOADEROPTIONS_H
|
||||
|
||||
870
src/covermanager/albumcovermanager.cpp
Normal file
870
src/covermanager/albumcovermanager.cpp
Normal file
@@ -0,0 +1,870 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QActionGroup>
|
||||
#include <QPushButton>
|
||||
#include <QContextMenuEvent>
|
||||
#include <QEvent>
|
||||
#include <QFileDialog>
|
||||
#include <QKeySequence>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QPainter>
|
||||
#include <QProgressBar>
|
||||
#include <QSettings>
|
||||
#include <QShortcut>
|
||||
#include <QTimer>
|
||||
|
||||
#include "albumcovermanager.h"
|
||||
#include "albumcoversearcher.h"
|
||||
#include "ui_albumcovermanager.h"
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/utilities.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "covermanager/albumcoverexporter.h"
|
||||
#include "covermanager/albumcoverfetcher.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "covermanager/coverproviders.h"
|
||||
#include "covermanager/coversearchstatisticsdialog.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionquery.h"
|
||||
#include "collection/sqlrow.h"
|
||||
#include "playlist/songmimedata.h"
|
||||
#include "widgets/forcescrollperpixel.h"
|
||||
#include "covermanager/albumcoverchoicecontroller.h"
|
||||
#include "covermanager/albumcoverexport.h"
|
||||
|
||||
const char *AlbumCoverManager::kSettingsGroup = "CoverManager";
|
||||
|
||||
AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QWidget *parent, QNetworkAccessManager *network)
|
||||
: QMainWindow(parent),
|
||||
ui_(new Ui_CoverManager),
|
||||
app_(app),
|
||||
album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
|
||||
cover_fetcher_(new AlbumCoverFetcher(app_->cover_providers(), this, network)),
|
||||
cover_searcher_(nullptr),
|
||||
cover_export_(nullptr),
|
||||
cover_exporter_(new AlbumCoverExporter(this)),
|
||||
artist_icon_(IconLoader::Load("guitar" )),
|
||||
all_artists_icon_(IconLoader::Load("cd" )),
|
||||
//no_cover_icon_(IconLoader::Load("nocover")),
|
||||
no_cover_icon_(":/pictures/noalbumart.png"),
|
||||
no_cover_image_(GenerateNoCoverImage(no_cover_icon_)),
|
||||
no_cover_item_icon_(QPixmap::fromImage(no_cover_image_)),
|
||||
context_menu_(new QMenu(this)),
|
||||
progress_bar_(new QProgressBar(this)),
|
||||
abort_progress_(new QPushButton(this)),
|
||||
jobs_(0),
|
||||
collection_backend_(collection_backend) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
ui_->albums->set_cover_manager(this);
|
||||
|
||||
// Icons
|
||||
ui_->action_fetch->setIcon(IconLoader::Load("download" ));
|
||||
ui_->export_covers->setIcon(IconLoader::Load("document-save" ));
|
||||
ui_->view->setIcon(IconLoader::Load("view-choose" ));
|
||||
ui_->fetch->setIcon(IconLoader::Load("download" ));
|
||||
ui_->action_add_to_playlist->setIcon(IconLoader::Load("media-play" ));
|
||||
ui_->action_load->setIcon(IconLoader::Load("media-play" ));
|
||||
|
||||
album_cover_choice_controller_->SetApplication(app_);
|
||||
|
||||
// Get a square version of noalbumart.png
|
||||
QImage nocover(":/pictures/noalbumart.png");
|
||||
nocover = nocover.scaled(120, 120, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
QImage square_nocover(120, 120, QImage::Format_ARGB32);
|
||||
square_nocover.fill(0);
|
||||
QPainter p(&square_nocover);
|
||||
p.setOpacity(0.4);
|
||||
p.drawImage((120 - nocover.width()) / 2, (120 - nocover.height()) / 2, nocover);
|
||||
p.end();
|
||||
// no_cover_item_icon_ = QPixmap::fromImage(square_nocover);
|
||||
|
||||
cover_searcher_ = new AlbumCoverSearcher(no_cover_item_icon_, app_, this);
|
||||
cover_export_ = new AlbumCoverExport(this);
|
||||
|
||||
// Set up the status bar
|
||||
statusBar()->addPermanentWidget(progress_bar_);
|
||||
statusBar()->addPermanentWidget(abort_progress_);
|
||||
progress_bar_->hide();
|
||||
abort_progress_->hide();
|
||||
abort_progress_->setText(tr("Abort"));
|
||||
connect(abort_progress_, SIGNAL(clicked()), this, SLOT(CancelRequests()));
|
||||
|
||||
ui_->albums->setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
ui_->artists->setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
|
||||
QShortcut *close = new QShortcut(QKeySequence::Close, this);
|
||||
connect(close, SIGNAL(activated()), SLOT(close()));
|
||||
|
||||
EnableCoversButtons();
|
||||
|
||||
}
|
||||
|
||||
AlbumCoverManager::~AlbumCoverManager() {
|
||||
CancelRequests();
|
||||
|
||||
delete ui_;
|
||||
}
|
||||
|
||||
CollectionBackend *AlbumCoverManager::backend() const {
|
||||
return collection_backend_;
|
||||
}
|
||||
|
||||
void AlbumCoverManager::Init() {
|
||||
|
||||
// View menu
|
||||
QActionGroup *filter_group = new QActionGroup(this);
|
||||
filter_all_ = filter_group->addAction(tr("All albums"));
|
||||
filter_with_covers_ = filter_group->addAction(tr("Albums with covers"));
|
||||
filter_without_covers_ = filter_group->addAction(tr("Albums without covers"));
|
||||
filter_all_->setCheckable(true);
|
||||
filter_with_covers_->setCheckable(true);
|
||||
filter_without_covers_->setCheckable(true);
|
||||
filter_group->setExclusive(true);
|
||||
filter_all_->setChecked(true);
|
||||
|
||||
QMenu *view_menu = new QMenu(this);
|
||||
view_menu->addActions(filter_group->actions());
|
||||
|
||||
ui_->view->setMenu(view_menu);
|
||||
|
||||
// Context menu
|
||||
|
||||
QList<QAction*> actions = album_cover_choice_controller_->GetAllActions();
|
||||
|
||||
connect(album_cover_choice_controller_->cover_from_file_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromFile()));
|
||||
connect(album_cover_choice_controller_->cover_to_file_action(), SIGNAL(triggered()), this, SLOT(SaveCoverToFile()));
|
||||
connect(album_cover_choice_controller_->cover_from_url_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromURL()));
|
||||
connect(album_cover_choice_controller_->search_for_cover_action(), SIGNAL(triggered()), this, SLOT(SearchForCover()));
|
||||
connect(album_cover_choice_controller_->unset_cover_action(), SIGNAL(triggered()), this, SLOT(UnsetCover()));
|
||||
connect(album_cover_choice_controller_->show_cover_action(), SIGNAL(triggered()), this, SLOT(ShowCover()));
|
||||
|
||||
connect(cover_exporter_, SIGNAL(AlbumCoversExportUpdate(int, int, int)), SLOT(UpdateExportStatus(int, int, int)));
|
||||
|
||||
context_menu_->addActions(actions);
|
||||
context_menu_->addSeparator();
|
||||
context_menu_->addAction(ui_->action_load);
|
||||
context_menu_->addAction(ui_->action_add_to_playlist);
|
||||
|
||||
ui_->albums->installEventFilter(this);
|
||||
|
||||
// Connections
|
||||
connect(ui_->artists, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), SLOT(ArtistChanged(QListWidgetItem*)));
|
||||
connect(ui_->filter, SIGNAL(textChanged(QString)), SLOT(UpdateFilter()));
|
||||
connect(filter_group, SIGNAL(triggered(QAction*)), SLOT(UpdateFilter()));
|
||||
connect(ui_->view, SIGNAL(clicked()), ui_->view, SLOT(showMenu()));
|
||||
connect(ui_->fetch, SIGNAL(clicked()), SLOT(FetchAlbumCovers()));
|
||||
connect(ui_->export_covers, SIGNAL(clicked()), SLOT(ExportCovers()));
|
||||
connect(cover_fetcher_, SIGNAL(AlbumCoverFetched(quint64, QImage, CoverSearchStatistics)), SLOT(AlbumCoverFetched(quint64, QImage, CoverSearchStatistics)));
|
||||
connect(ui_->action_fetch, SIGNAL(triggered()), SLOT(FetchSingleCover()));
|
||||
connect(ui_->albums, SIGNAL(doubleClicked(QModelIndex)), SLOT(AlbumDoubleClicked(QModelIndex)));
|
||||
connect(ui_->action_add_to_playlist, SIGNAL(triggered()), SLOT(AddSelectedToPlaylist()));
|
||||
connect(ui_->action_load, SIGNAL(triggered()), SLOT(LoadSelectedToPlaylist()));
|
||||
|
||||
// Restore settings
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
|
||||
restoreGeometry(s.value("geometry").toByteArray());
|
||||
if (!ui_->splitter->restoreState(s.value("splitter_state").toByteArray())) {
|
||||
// Sensible default size for the artists view
|
||||
ui_->splitter->setSizes(QList<int>() << 200 << width() - 200);
|
||||
}
|
||||
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(CoverImageLoaded(quint64, QImage)));
|
||||
|
||||
cover_searcher_->Init(cover_fetcher_);
|
||||
|
||||
new ForceScrollPerPixel(ui_->albums, this);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::showEvent(QShowEvent *) {
|
||||
Reset();
|
||||
}
|
||||
|
||||
void AlbumCoverManager::closeEvent(QCloseEvent *e) {
|
||||
|
||||
if (!cover_fetching_tasks_.isEmpty()) {
|
||||
std::unique_ptr<QMessageBox> message_box(new QMessageBox(QMessageBox::Question, tr("Really cancel?"), tr("Closing this window will stop searching for album covers."), QMessageBox::Abort, this));
|
||||
message_box->addButton(tr("Don't stop!"), QMessageBox::AcceptRole);
|
||||
|
||||
if (message_box->exec() != QMessageBox::Abort) {
|
||||
e->ignore();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Save geometry
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
|
||||
s.setValue("geometry", saveGeometry());
|
||||
s.setValue("splitter_state", ui_->splitter->saveState());
|
||||
|
||||
// Cancel any outstanding requests
|
||||
CancelRequests();
|
||||
}
|
||||
|
||||
void AlbumCoverManager::CancelRequests() {
|
||||
|
||||
app_->album_cover_loader()->CancelTasks(QSet<quint64>::fromList(cover_loading_tasks_.keys()));
|
||||
cover_loading_tasks_.clear();
|
||||
|
||||
cover_exporter_->Cancel();
|
||||
|
||||
cover_fetching_tasks_.clear();
|
||||
cover_fetcher_->Clear();
|
||||
progress_bar_->hide();
|
||||
abort_progress_->hide();
|
||||
statusBar()->clearMessage();
|
||||
EnableCoversButtons();
|
||||
|
||||
}
|
||||
|
||||
static bool CompareNocase(const QString &left, const QString &right) {
|
||||
return QString::localeAwareCompare(left, right) < 0;
|
||||
}
|
||||
|
||||
static bool CompareAlbumNameNocase(const CollectionBackend::Album &left, const CollectionBackend::Album &right) {
|
||||
return CompareNocase(left.album_name, right.album_name);
|
||||
}
|
||||
|
||||
void AlbumCoverManager::Reset() {
|
||||
|
||||
EnableCoversButtons();
|
||||
|
||||
ui_->artists->clear();
|
||||
new QListWidgetItem(all_artists_icon_, tr("All artists"), ui_->artists, All_Artists);
|
||||
new QListWidgetItem(artist_icon_, tr("Various artists"), ui_->artists, Various_Artists);
|
||||
|
||||
QStringList artists(collection_backend_->GetAllArtistsWithAlbums());
|
||||
qStableSort(artists.begin(), artists.end(), CompareNocase);
|
||||
|
||||
for (const QString &artist : artists) {
|
||||
if (artist.isEmpty()) continue;
|
||||
new QListWidgetItem(artist_icon_, artist, ui_->artists, Specific_Artist);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::EnableCoversButtons() {
|
||||
ui_->fetch->setEnabled(app_->cover_providers()->HasAnyProviders());
|
||||
ui_->export_covers->setEnabled(true);
|
||||
}
|
||||
|
||||
void AlbumCoverManager::DisableCoversButtons() {
|
||||
ui_->fetch->setEnabled(false);
|
||||
ui_->export_covers->setEnabled(false);
|
||||
}
|
||||
|
||||
void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
|
||||
|
||||
if (!current) return;
|
||||
|
||||
QString artist;
|
||||
if (current->type() == Specific_Artist) artist = current->text();
|
||||
|
||||
ui_->albums->clear();
|
||||
context_menu_items_.clear();
|
||||
CancelRequests();
|
||||
|
||||
// Get the list of albums. How we do it depends on what thing we have selected in the artist list.
|
||||
CollectionBackend::AlbumList albums;
|
||||
switch (current->type()) {
|
||||
case Various_Artists: albums = collection_backend_->GetCompilationAlbums(); break;
|
||||
case Specific_Artist: albums = collection_backend_->GetAlbumsByArtist(current->text()); break;
|
||||
case All_Artists:
|
||||
default: albums = collection_backend_->GetAllAlbums(); break;
|
||||
}
|
||||
|
||||
// Sort by album name. The list is already sorted by sqlite but it was done case sensitively.
|
||||
qStableSort(albums.begin(), albums.end(), CompareAlbumNameNocase);
|
||||
|
||||
for (const CollectionBackend::Album &info : albums) {
|
||||
// Don't show songs without an album, obviously
|
||||
if (info.album_name.isEmpty()) continue;
|
||||
|
||||
QListWidgetItem *item = new QListWidgetItem(no_cover_item_icon_, info.album_name, ui_->albums);
|
||||
item->setData(Role_ArtistName, info.artist);
|
||||
item->setData(Role_AlbumArtistName, info.album_artist);
|
||||
item->setData(Role_AlbumName, info.album_name);
|
||||
item->setData(Role_FirstUrl, info.first_url);
|
||||
item->setData(Qt::TextAlignmentRole, QVariant(Qt::AlignTop | Qt::AlignHCenter));
|
||||
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
|
||||
item->setToolTip(info.artist + " - " + info.album_name);
|
||||
|
||||
QString effective_artist = EffectiveAlbumArtistName(*item);
|
||||
if (!artist.isEmpty()) {
|
||||
item->setToolTip(effective_artist + " - " + info.album_name);
|
||||
}
|
||||
else {
|
||||
item->setToolTip(info.album_name);
|
||||
}
|
||||
|
||||
if (!info.art_automatic.isEmpty() || !info.art_manual.isEmpty()) {
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, info.art_automatic, info.art_manual, info.first_url.toLocalFile());
|
||||
item->setData(Role_PathAutomatic, info.art_automatic);
|
||||
item->setData(Role_PathManual, info.art_manual);
|
||||
cover_loading_tasks_[id] = item;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateFilter();
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::CoverImageLoaded(quint64 id, const QImage &image) {
|
||||
|
||||
if (!cover_loading_tasks_.contains(id)) return;
|
||||
|
||||
QListWidgetItem *item = cover_loading_tasks_.take(id);
|
||||
|
||||
if (image.isNull()) return;
|
||||
|
||||
item->setIcon(QPixmap::fromImage(image));
|
||||
UpdateFilter();
|
||||
}
|
||||
|
||||
void AlbumCoverManager::UpdateFilter() {
|
||||
|
||||
const QString filter = ui_->filter->text().toLower();
|
||||
const bool hide_with_covers = filter_without_covers_->isChecked();
|
||||
const bool hide_without_covers = filter_with_covers_->isChecked();
|
||||
|
||||
HideCovers hide = Hide_None;
|
||||
if (hide_with_covers) {
|
||||
hide = Hide_WithCovers;
|
||||
}
|
||||
else if (hide_without_covers) {
|
||||
hide = Hide_WithoutCovers;
|
||||
}
|
||||
|
||||
qint32 total_count = 0;
|
||||
qint32 without_cover = 0;
|
||||
|
||||
for (int i = 0; i < ui_->albums->count(); ++i) {
|
||||
QListWidgetItem *item = ui_->albums->item(i);
|
||||
bool should_hide = ShouldHide(*item, filter, hide);
|
||||
item->setHidden(should_hide);
|
||||
|
||||
if (!should_hide) {
|
||||
total_count++;
|
||||
if (!ItemHasCover(*item)) {
|
||||
without_cover++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui_->total_albums->setText(QString::number(total_count));
|
||||
ui_->without_cover->setText(QString::number(without_cover));
|
||||
|
||||
}
|
||||
|
||||
bool AlbumCoverManager::ShouldHide(const QListWidgetItem &item, const QString &filter, HideCovers hide) const {
|
||||
|
||||
bool has_cover = ItemHasCover(item);
|
||||
if (hide == Hide_WithCovers && has_cover) {
|
||||
return true;
|
||||
}
|
||||
else if (hide == Hide_WithoutCovers && !has_cover) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (filter.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList query = filter.split(' ');
|
||||
for (const QString &s : query) {
|
||||
bool in_text = item.text().contains(s, Qt::CaseInsensitive);
|
||||
bool in_artist = item.data(Role_ArtistName).toString().contains(s, Qt::CaseInsensitive);
|
||||
bool in_albumartist = item.data(Role_AlbumArtistName).toString().contains(s, Qt::CaseInsensitive);
|
||||
if (!in_text && !in_artist && !in_albumartist) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::FetchAlbumCovers() {
|
||||
|
||||
for (int i = 0; i < ui_->albums->count(); ++i) {
|
||||
QListWidgetItem *item = ui_->albums->item(i);
|
||||
if (item->isHidden()) continue;
|
||||
if (ItemHasCover(*item)) continue;
|
||||
|
||||
quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString());
|
||||
cover_fetching_tasks_[id] = item;
|
||||
jobs_++;
|
||||
}
|
||||
|
||||
if (!cover_fetching_tasks_.isEmpty()) ui_->fetch->setEnabled(false);
|
||||
|
||||
progress_bar_->setMaximum(jobs_);
|
||||
progress_bar_->show();
|
||||
abort_progress_->show();
|
||||
fetch_statistics_ = CoverSearchStatistics();
|
||||
UpdateStatusText();
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::AlbumCoverFetched(quint64 id, const QImage &image, const CoverSearchStatistics &statistics) {
|
||||
|
||||
if (!cover_fetching_tasks_.contains(id))
|
||||
return;
|
||||
|
||||
QListWidgetItem *item = cover_fetching_tasks_.take(id);
|
||||
if (!image.isNull()) {
|
||||
SaveAndSetCover(item, image);
|
||||
}
|
||||
|
||||
if (cover_fetching_tasks_.isEmpty()) {
|
||||
EnableCoversButtons();
|
||||
}
|
||||
|
||||
fetch_statistics_ += statistics;
|
||||
UpdateStatusText();
|
||||
}
|
||||
|
||||
void AlbumCoverManager::UpdateStatusText() {
|
||||
|
||||
QString message = tr("Got %1 covers out of %2 (%3 failed)")
|
||||
.arg(fetch_statistics_.chosen_images_)
|
||||
.arg(jobs_)
|
||||
.arg(fetch_statistics_.missing_images_);
|
||||
|
||||
if (fetch_statistics_.bytes_transferred_) {
|
||||
message += ", " + tr("%1 transferred").arg(Utilities::PrettySize(fetch_statistics_.bytes_transferred_));
|
||||
}
|
||||
|
||||
statusBar()->showMessage(message);
|
||||
progress_bar_->setValue(fetch_statistics_.chosen_images_ + fetch_statistics_.missing_images_);
|
||||
|
||||
if (cover_fetching_tasks_.isEmpty()) {
|
||||
QTimer::singleShot(2000, statusBar(), SLOT(clearMessage()));
|
||||
progress_bar_->hide();
|
||||
abort_progress_->hide();
|
||||
|
||||
CoverSearchStatisticsDialog *dialog = new CoverSearchStatisticsDialog(this);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
dialog->Show(fetch_statistics_);
|
||||
|
||||
jobs_ = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool AlbumCoverManager::eventFilter(QObject *obj, QEvent *event) {
|
||||
|
||||
if (obj == ui_->albums && event->type() == QEvent::ContextMenu) {
|
||||
context_menu_items_ = ui_->albums->selectedItems();
|
||||
if (context_menu_items_.isEmpty()) return false;
|
||||
|
||||
bool some_with_covers = false;
|
||||
|
||||
for (QListWidgetItem *item : context_menu_items_) {
|
||||
if (ItemHasCover(*item)) some_with_covers = true;
|
||||
}
|
||||
|
||||
album_cover_choice_controller_->cover_from_file_action()->setEnabled(context_menu_items_.size() == 1);
|
||||
album_cover_choice_controller_->cover_from_url_action()->setEnabled(context_menu_items_.size() == 1);
|
||||
album_cover_choice_controller_->show_cover_action()->setEnabled(some_with_covers && context_menu_items_.size() == 1);
|
||||
album_cover_choice_controller_->unset_cover_action()->setEnabled(some_with_covers);
|
||||
album_cover_choice_controller_->search_for_cover_action()->setEnabled(app_->cover_providers()->HasAnyProviders());
|
||||
|
||||
QContextMenuEvent *e = static_cast<QContextMenuEvent*>(event);
|
||||
context_menu_->popup(e->globalPos());
|
||||
return true;
|
||||
}
|
||||
return QMainWindow::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
Song AlbumCoverManager::GetSingleSelectionAsSong() {
|
||||
return context_menu_items_.size() != 1 ? Song() : ItemAsSong(context_menu_items_[0]);
|
||||
}
|
||||
|
||||
Song AlbumCoverManager::GetFirstSelectedAsSong() {
|
||||
return context_menu_items_.isEmpty() ? Song() : ItemAsSong(context_menu_items_[0]);
|
||||
}
|
||||
|
||||
Song AlbumCoverManager::ItemAsSong(QListWidgetItem *item) {
|
||||
|
||||
Song result;
|
||||
|
||||
QString title = item->data(Role_AlbumName).toString();
|
||||
QString artist_name = EffectiveAlbumArtistName(*item);
|
||||
if (!artist_name.isEmpty()) {
|
||||
result.set_title(artist_name + " - " + title);
|
||||
}
|
||||
else {
|
||||
result.set_title(title);
|
||||
}
|
||||
|
||||
result.set_artist(item->data(Role_ArtistName).toString());
|
||||
result.set_albumartist(item->data(Role_AlbumArtistName).toString());
|
||||
result.set_album(item->data(Role_AlbumName).toString());
|
||||
|
||||
result.set_url(item->data(Role_FirstUrl).toUrl());
|
||||
|
||||
result.set_art_automatic(item->data(Role_PathAutomatic).toString());
|
||||
result.set_art_manual(item->data(Role_PathManual).toString());
|
||||
|
||||
// force validity
|
||||
result.set_valid(true);
|
||||
result.set_id(0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void AlbumCoverManager::ShowCover() {
|
||||
|
||||
Song song = GetSingleSelectionAsSong();
|
||||
if (!song.is_valid()) return;
|
||||
|
||||
album_cover_choice_controller_->ShowCover(song);
|
||||
}
|
||||
|
||||
void AlbumCoverManager::FetchSingleCover() {
|
||||
|
||||
for (QListWidgetItem *item : context_menu_items_) {
|
||||
quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString());
|
||||
cover_fetching_tasks_[id] = item;
|
||||
jobs_++;
|
||||
}
|
||||
|
||||
progress_bar_->setMaximum(jobs_);
|
||||
progress_bar_->show();
|
||||
abort_progress_->show();
|
||||
UpdateStatusText();
|
||||
|
||||
}
|
||||
|
||||
|
||||
void AlbumCoverManager::UpdateCoverInList(QListWidgetItem *item, const QString &cover) {
|
||||
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QString(), cover);
|
||||
item->setData(Role_PathManual, cover);
|
||||
cover_loading_tasks_[id] = item;
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::LoadCoverFromFile() {
|
||||
|
||||
Song song = GetSingleSelectionAsSong();
|
||||
if (!song.is_valid()) return;
|
||||
|
||||
QListWidgetItem *item = context_menu_items_[0];
|
||||
|
||||
QString cover = album_cover_choice_controller_->LoadCoverFromFile(&song);
|
||||
|
||||
if (!cover.isEmpty()) {
|
||||
UpdateCoverInList(item, cover);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::SaveCoverToFile() {
|
||||
|
||||
Song song = GetSingleSelectionAsSong();
|
||||
if (!song.is_valid()) return;
|
||||
|
||||
QImage image;
|
||||
|
||||
// load the image from disk
|
||||
if (song.has_manually_unset_cover()) {
|
||||
image = no_cover_image_;
|
||||
}
|
||||
else {
|
||||
if (!song.art_manual().isEmpty() && QFile::exists(song.art_manual())) {
|
||||
image = QImage(song.art_manual());
|
||||
}
|
||||
else if(!song.art_automatic().isEmpty() && QFile::exists(song.art_automatic())) {
|
||||
image = QImage(song.art_automatic());
|
||||
}
|
||||
else {
|
||||
image = no_cover_image_;
|
||||
}
|
||||
}
|
||||
|
||||
album_cover_choice_controller_->SaveCoverToFile(song, image);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::LoadCoverFromURL() {
|
||||
|
||||
Song song = GetSingleSelectionAsSong();
|
||||
if (!song.is_valid()) return;
|
||||
|
||||
QListWidgetItem *item = context_menu_items_[0];
|
||||
|
||||
QString cover = album_cover_choice_controller_->LoadCoverFromURL(&song);
|
||||
|
||||
if (!cover.isEmpty()) {
|
||||
UpdateCoverInList(item, cover);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::SearchForCover() {
|
||||
|
||||
Song song = GetFirstSelectedAsSong();
|
||||
if (!song.is_valid()) return;
|
||||
|
||||
QListWidgetItem *item = context_menu_items_[0];
|
||||
|
||||
QString cover = album_cover_choice_controller_->SearchForCover(&song);
|
||||
if (cover.isEmpty()) return;
|
||||
|
||||
// force the found cover on all of the selected items
|
||||
for (QListWidgetItem *current : context_menu_items_) {
|
||||
// don't save the first one twice
|
||||
if (current != item) {
|
||||
Song current_song = ItemAsSong(current);
|
||||
album_cover_choice_controller_->SaveCover(¤t_song, cover);
|
||||
}
|
||||
|
||||
UpdateCoverInList(current, cover);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::UnsetCover() {
|
||||
|
||||
Song song = GetFirstSelectedAsSong();
|
||||
if (!song.is_valid()) return;
|
||||
|
||||
QListWidgetItem *item = context_menu_items_[0];
|
||||
|
||||
QString cover = album_cover_choice_controller_->UnsetCover(&song);
|
||||
|
||||
// force the 'none' cover on all of the selected items
|
||||
for (QListWidgetItem *current : context_menu_items_) {
|
||||
current->setIcon(no_cover_item_icon_);
|
||||
current->setData(Role_PathManual, cover);
|
||||
|
||||
// don't save the first one twice
|
||||
if (current != item) {
|
||||
Song current_song = ItemAsSong(current);
|
||||
album_cover_choice_controller_->SaveCover(¤t_song, cover);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SongList AlbumCoverManager::GetSongsInAlbum(const QModelIndex &index) const {
|
||||
|
||||
SongList ret;
|
||||
|
||||
CollectionQuery q;
|
||||
q.SetColumnSpec("ROWID," + Song::kColumnSpec);
|
||||
q.AddWhere("album", index.data(Role_AlbumName).toString());
|
||||
q.SetOrderBy("disc, track, title");
|
||||
|
||||
QString artist = index.data(Role_ArtistName).toString();
|
||||
QString albumartist = index.data(Role_AlbumArtistName).toString();
|
||||
|
||||
if (!albumartist.isEmpty()) {
|
||||
q.AddWhere("albumartist", albumartist);
|
||||
}
|
||||
else if (!artist.isEmpty()) {
|
||||
q.AddWhere("artist", artist);
|
||||
}
|
||||
|
||||
q.AddCompilationRequirement(artist.isEmpty() && albumartist.isEmpty());
|
||||
|
||||
if (!collection_backend_->ExecQuery(&q)) return ret;
|
||||
|
||||
while (q.Next()) {
|
||||
Song song;
|
||||
song.InitFromQuery(q, true);
|
||||
ret << song;
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
SongList AlbumCoverManager::GetSongsInAlbums(const QModelIndexList &indexes) const {
|
||||
|
||||
SongList ret;
|
||||
for (const QModelIndex &index : indexes) {
|
||||
ret << GetSongsInAlbum(index);
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
SongMimeData *AlbumCoverManager::GetMimeDataForAlbums(const QModelIndexList &indexes) const {
|
||||
|
||||
SongList songs = GetSongsInAlbums(indexes);
|
||||
if (songs.isEmpty()) return nullptr;
|
||||
|
||||
SongMimeData *data = new SongMimeData;
|
||||
data->backend = collection_backend_;
|
||||
data->songs = songs;
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::AlbumDoubleClicked(const QModelIndex &index) {
|
||||
|
||||
SongMimeData *data = GetMimeDataForAlbums(QModelIndexList() << index);
|
||||
if (data) {
|
||||
data->from_doubleclick_ = true;
|
||||
emit AddToPlaylist(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::AddSelectedToPlaylist() {
|
||||
emit AddToPlaylist(GetMimeDataForAlbums(ui_->albums->selectionModel()->selectedIndexes()));
|
||||
}
|
||||
|
||||
void AlbumCoverManager::LoadSelectedToPlaylist() {
|
||||
|
||||
SongMimeData *data = GetMimeDataForAlbums(ui_->albums->selectionModel()->selectedIndexes());
|
||||
if (data) {
|
||||
data->clear_first_ = true;
|
||||
emit AddToPlaylist(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::SaveAndSetCover(QListWidgetItem *item, const QImage &image) {
|
||||
|
||||
const QString artist = item->data(Role_ArtistName).toString();
|
||||
const QString albumartist = item->data(Role_ArtistName).toString();
|
||||
const QString album = item->data(Role_AlbumName).toString();
|
||||
|
||||
QString path = album_cover_choice_controller_->SaveCoverInCache(artist, album, image);
|
||||
|
||||
// Save the image in the database
|
||||
collection_backend_->UpdateManualAlbumArtAsync(artist, albumartist, album, path);
|
||||
|
||||
// Update the icon in our list
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, QString(), path);
|
||||
item->setData(Role_PathManual, path);
|
||||
cover_loading_tasks_[id] = item;
|
||||
}
|
||||
|
||||
void AlbumCoverManager::ExportCovers() {
|
||||
|
||||
AlbumCoverExport::DialogResult result = cover_export_->Exec();
|
||||
|
||||
if (result.cancelled_) {
|
||||
return;
|
||||
}
|
||||
|
||||
DisableCoversButtons();
|
||||
|
||||
cover_exporter_->SetDialogResult(result);
|
||||
|
||||
for (int i = 0; i < ui_->albums->count(); ++i) {
|
||||
QListWidgetItem *item = ui_->albums->item(i);
|
||||
|
||||
// skip hidden and coverless albums
|
||||
if (item->isHidden() || !ItemHasCover(*item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cover_exporter_->AddExportRequest(ItemAsSong(item));
|
||||
}
|
||||
|
||||
if (cover_exporter_->request_count() > 0) {
|
||||
progress_bar_->setMaximum(cover_exporter_->request_count());
|
||||
progress_bar_->show();
|
||||
abort_progress_->show();
|
||||
|
||||
cover_exporter_->StartExporting();
|
||||
}
|
||||
else {
|
||||
QMessageBox msg;
|
||||
msg.setWindowTitle(tr("Export finished"));
|
||||
msg.setText(tr("No covers to export."));
|
||||
msg.exec();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverManager::UpdateExportStatus(int exported, int skipped, int max) {
|
||||
|
||||
progress_bar_->setValue(exported);
|
||||
|
||||
QString message = tr("Exported %1 covers out of %2 (%3 skipped)")
|
||||
.arg(exported)
|
||||
.arg(max)
|
||||
.arg(skipped);
|
||||
statusBar()->showMessage(message);
|
||||
|
||||
// end of the current process
|
||||
if (exported + skipped >= max) {
|
||||
QTimer::singleShot(2000, statusBar(), SLOT(clearMessage()));
|
||||
|
||||
progress_bar_->hide();
|
||||
abort_progress_->hide();
|
||||
EnableCoversButtons();
|
||||
|
||||
QMessageBox msg;
|
||||
msg.setWindowTitle(tr("Export finished"));
|
||||
msg.setText(message);
|
||||
msg.exec();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString AlbumCoverManager::EffectiveAlbumArtistName(const QListWidgetItem &item) const {
|
||||
QString albumartist = item.data(Role_AlbumArtistName).toString();
|
||||
if (!albumartist.isEmpty()) {
|
||||
return albumartist;
|
||||
}
|
||||
return item.data(Role_ArtistName).toString();
|
||||
}
|
||||
|
||||
QImage AlbumCoverManager::GenerateNoCoverImage(const QIcon &no_cover_icon) const {
|
||||
|
||||
// Get a square version of noalbumart.png with some transparency:
|
||||
QImage nocover = no_cover_icon.pixmap(no_cover_icon.availableSizes().last()).toImage();
|
||||
nocover = nocover.scaled(120, 120, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
QImage square_nocover(120, 120, QImage::Format_ARGB32);
|
||||
square_nocover.fill(0);
|
||||
QPainter p(&square_nocover);
|
||||
p.setOpacity(0.4);
|
||||
p.drawImage((120 - nocover.width()) / 2, (120 - nocover.height()) / 2, nocover);
|
||||
p.end();
|
||||
|
||||
return square_nocover;
|
||||
|
||||
}
|
||||
|
||||
bool AlbumCoverManager::ItemHasCover(const QListWidgetItem& item) const {
|
||||
return item.icon().cacheKey() != no_cover_item_icon_.cacheKey();
|
||||
}
|
||||
|
||||
194
src/covermanager/albumcovermanager.h
Normal file
194
src/covermanager/albumcovermanager.h
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ALBUMCOVERMANAGER_H
|
||||
#define ALBUMCOVERMANAGER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QIcon>
|
||||
#include <QModelIndex>
|
||||
|
||||
#include "gtest/gtest_prod.h"
|
||||
|
||||
#include "core/song.h"
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
#include "covermanager/coversearchstatistics.h"
|
||||
|
||||
class AlbumCoverChoiceController;
|
||||
class AlbumCoverExport;
|
||||
class AlbumCoverExporter;
|
||||
class AlbumCoverFetcher;
|
||||
class AlbumCoverSearcher;
|
||||
class Application;
|
||||
class CollectionBackend;
|
||||
class SongMimeData;
|
||||
class Ui_CoverManager;
|
||||
|
||||
class QListWidgetItem;
|
||||
class QMenu;
|
||||
class QNetworkAccessManager;
|
||||
class QPushButton;
|
||||
class QProgressBar;
|
||||
|
||||
class AlbumCoverManager : public QMainWindow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QWidget *parent = nullptr, QNetworkAccessManager *network = 0);
|
||||
~AlbumCoverManager();
|
||||
|
||||
static const char *kSettingsGroup;
|
||||
|
||||
CollectionBackend *backend() const;
|
||||
QIcon no_cover_icon() const { return no_cover_icon_; }
|
||||
|
||||
void Reset();
|
||||
void Init();
|
||||
|
||||
void EnableCoversButtons();
|
||||
void DisableCoversButtons();
|
||||
|
||||
SongList GetSongsInAlbum(const QModelIndex &index) const;
|
||||
SongList GetSongsInAlbums(const QModelIndexList &indexes) const;
|
||||
SongMimeData *GetMimeDataForAlbums(const QModelIndexList &indexes) const;
|
||||
|
||||
signals:
|
||||
void AddToPlaylist(QMimeData *data);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *);
|
||||
void closeEvent(QCloseEvent *);
|
||||
|
||||
// For the album view context menu events
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
|
||||
private slots:
|
||||
void ArtistChanged(QListWidgetItem *current);
|
||||
void CoverImageLoaded(quint64 id, const QImage &image);
|
||||
void UpdateFilter();
|
||||
void FetchAlbumCovers();
|
||||
void ExportCovers();
|
||||
void AlbumCoverFetched(quint64 id, const QImage &image, const CoverSearchStatistics &statistics);
|
||||
void CancelRequests();
|
||||
|
||||
// On the context menu
|
||||
void FetchSingleCover();
|
||||
|
||||
void LoadCoverFromFile();
|
||||
void SaveCoverToFile();
|
||||
void LoadCoverFromURL();
|
||||
void SearchForCover();
|
||||
void UnsetCover();
|
||||
void ShowCover();
|
||||
|
||||
// For adding albums to the playlist
|
||||
void AlbumDoubleClicked(const QModelIndex &index);
|
||||
void AddSelectedToPlaylist();
|
||||
void LoadSelectedToPlaylist();
|
||||
|
||||
void UpdateCoverInList(QListWidgetItem *item, const QString &cover);
|
||||
void UpdateExportStatus(int exported, int bad, int count);
|
||||
|
||||
private:
|
||||
enum ArtistItemType {
|
||||
All_Artists,
|
||||
Various_Artists,
|
||||
Specific_Artist
|
||||
};
|
||||
|
||||
enum Role {
|
||||
Role_ArtistName = Qt::UserRole + 1,
|
||||
Role_AlbumArtistName,
|
||||
Role_AlbumName,
|
||||
Role_PathAutomatic,
|
||||
Role_PathManual,
|
||||
Role_FirstUrl
|
||||
};
|
||||
|
||||
enum HideCovers {
|
||||
Hide_None,
|
||||
Hide_WithCovers,
|
||||
Hide_WithoutCovers
|
||||
};
|
||||
|
||||
QString InitialPathForOpenCoverDialog(const QString &path_automatic, const QString &first_file_name) const;
|
||||
QString EffectiveAlbumArtistName(const QListWidgetItem &item) const;
|
||||
|
||||
// Returns the selected element in form of a Song ready to be used
|
||||
// by AlbumCoverChoiceController or invalid song if there's nothing
|
||||
// or multiple elements selected.
|
||||
Song GetSingleSelectionAsSong();
|
||||
// Returns the first of the selected elements in form of a Song ready
|
||||
// to be used by AlbumCoverChoiceController or invalid song if there's nothing
|
||||
// selected.
|
||||
Song GetFirstSelectedAsSong();
|
||||
|
||||
Song ItemAsSong(QListWidgetItem *item);
|
||||
|
||||
void UpdateStatusText();
|
||||
bool ShouldHide(const QListWidgetItem &item, const QString &filter, HideCovers hide) const;
|
||||
void SaveAndSetCover(QListWidgetItem *item, const QImage &image);
|
||||
|
||||
private:
|
||||
Ui_CoverManager *ui_;
|
||||
Application *app_;
|
||||
|
||||
AlbumCoverChoiceController *album_cover_choice_controller_;
|
||||
|
||||
QAction *filter_all_;
|
||||
QAction *filter_with_covers_;
|
||||
QAction *filter_without_covers_;
|
||||
|
||||
AlbumCoverLoaderOptions cover_loader_options_;
|
||||
QMap<quint64, QListWidgetItem*> cover_loading_tasks_;
|
||||
|
||||
AlbumCoverFetcher *cover_fetcher_;
|
||||
QMap<quint64, QListWidgetItem*> cover_fetching_tasks_;
|
||||
CoverSearchStatistics fetch_statistics_;
|
||||
|
||||
AlbumCoverSearcher *cover_searcher_;
|
||||
AlbumCoverExport *cover_export_;
|
||||
AlbumCoverExporter *cover_exporter_;
|
||||
|
||||
QImage GenerateNoCoverImage(const QIcon &no_cover_icon) const;
|
||||
bool ItemHasCover(const QListWidgetItem &item) const;
|
||||
|
||||
QIcon artist_icon_;
|
||||
QIcon all_artists_icon_;
|
||||
const QIcon no_cover_icon_;
|
||||
const QImage no_cover_image_;
|
||||
const QIcon no_cover_item_icon_;
|
||||
|
||||
QMenu *context_menu_;
|
||||
QList<QListWidgetItem*> context_menu_items_;
|
||||
|
||||
QProgressBar *progress_bar_;
|
||||
QPushButton *abort_progress_;
|
||||
int jobs_;
|
||||
|
||||
CollectionBackend *collection_backend_;
|
||||
|
||||
FRIEND_TEST(AlbumCoverManagerTest, HidesItemsWithCover);
|
||||
FRIEND_TEST(AlbumCoverManagerTest, HidesItemsWithoutCover);
|
||||
FRIEND_TEST(AlbumCoverManagerTest, HidesItemsWithFilter);
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERMANAGER_H
|
||||
284
src/covermanager/albumcovermanager.ui
Normal file
284
src/covermanager/albumcovermanager.ui
Normal file
@@ -0,0 +1,284 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CoverManager</class>
|
||||
<widget class="QMainWindow" name="CoverManager">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>903</width>
|
||||
<height>662</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Cover Manager</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QListWidget" name="artists">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="uniformItemSizes">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSearchField" name="filter" native="true">
|
||||
<property name="placeholderText" stdset="0">
|
||||
<string>Enter search terms here</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="view">
|
||||
<property name="text">
|
||||
<string>View</string>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::MenuButtonPopup</enum>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextBesideIcon</enum>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4" stretch="0,0">
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Total albums:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Without cover:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="total_albums">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="without_cover">
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="fetch">
|
||||
<property name="text">
|
||||
<string>Fetch Missing Covers</string>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="export_covers">
|
||||
<property name="text">
|
||||
<string>Export Covers</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="AlbumCoverManagerList" name="albums">
|
||||
<property name="dragEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="dragDropMode">
|
||||
<enum>QAbstractItemView::DragDrop</enum>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>120</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flow">
|
||||
<enum>QListView::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="isWrapping" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="resizeMode">
|
||||
<enum>QListView::Adjust</enum>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="viewMode">
|
||||
<enum>QListView::IconMode</enum>
|
||||
</property>
|
||||
<property name="uniformItemSizes">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusBar"/>
|
||||
<action name="action_fetch">
|
||||
<property name="text">
|
||||
<string>Fetch automatically</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_load">
|
||||
<property name="text">
|
||||
<string>Load</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_add_to_playlist">
|
||||
<property name="text">
|
||||
<string>Add to playlist</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QSearchField</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>3rdparty/qocoa/qsearchfield.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>AlbumCoverManagerList</class>
|
||||
<extends>QListWidget</extends>
|
||||
<header>covermanager/albumcovermanagerlist.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>artists</tabstop>
|
||||
<tabstop>albums</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
73
src/covermanager/albumcovermanagerlist.cpp
Normal file
73
src/covermanager/albumcovermanagerlist.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "albumcovermanagerlist.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QDropEvent>
|
||||
#include <QUrl>
|
||||
|
||||
#include "albumcovermanager.h"
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "playlist/songmimedata.h"
|
||||
|
||||
AlbumCoverManagerList::AlbumCoverManagerList(QWidget *parent) : QListWidget(parent), manager_(nullptr) {}
|
||||
|
||||
QMimeData *AlbumCoverManagerList::mimeData(const QList<QListWidgetItem*> items) const {
|
||||
|
||||
// Get songs
|
||||
SongList songs;
|
||||
for (QListWidgetItem *item : items) {
|
||||
songs << manager_->GetSongsInAlbum(indexFromItem(item));
|
||||
}
|
||||
|
||||
if (songs.isEmpty()) return nullptr;
|
||||
|
||||
// Get URLs from the songs
|
||||
QList<QUrl> urls;
|
||||
for (const Song &song : songs) {
|
||||
urls << song.url();
|
||||
}
|
||||
|
||||
// Get the QAbstractItemModel data so the picture works
|
||||
std::unique_ptr<QMimeData> orig_data(QListWidget::mimeData(items));
|
||||
|
||||
SongMimeData *mime_data = new SongMimeData;
|
||||
mime_data->backend = manager_->backend();
|
||||
mime_data->songs = songs;
|
||||
mime_data->setUrls(urls);
|
||||
mime_data->setData(orig_data->formats()[0], orig_data->data(orig_data->formats()[0]));
|
||||
return mime_data;
|
||||
}
|
||||
|
||||
void AlbumCoverManagerList::dropEvent(QDropEvent *e) {
|
||||
|
||||
// Set movement to Static just for this dropEvent so the user can't move the
|
||||
// album covers. If it's set to Static all the time then the user can't even
|
||||
// drag to the playlist
|
||||
QListWidget::Movement old_movement = movement();
|
||||
setMovement(QListWidget::Static);
|
||||
QListWidget::dropEvent(e);
|
||||
setMovement(old_movement);
|
||||
|
||||
}
|
||||
46
src/covermanager/albumcovermanagerlist.h
Normal file
46
src/covermanager/albumcovermanagerlist.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ALBUMCOVERMANAGERLIST_H
|
||||
#define ALBUMCOVERMANAGERLIST_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QListWidget>
|
||||
|
||||
class AlbumCoverManager;
|
||||
|
||||
class AlbumCoverManagerList : public QListWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AlbumCoverManagerList(QWidget *parent = nullptr);
|
||||
|
||||
void set_cover_manager(AlbumCoverManager* manager) { manager_ = manager; }
|
||||
|
||||
protected:
|
||||
QMimeData *mimeData(const QList<QListWidgetItem*> items) const;
|
||||
void dropEvent(QDropEvent *event);
|
||||
|
||||
private:
|
||||
AlbumCoverManager* manager_;
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERMANAGERLIST_H
|
||||
|
||||
259
src/covermanager/albumcoversearcher.cpp
Normal file
259
src/covermanager/albumcoversearcher.cpp
Normal file
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "albumcoversearcher.h"
|
||||
#include "ui_albumcoversearcher.h"
|
||||
#include "core/application.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/utilities.h"
|
||||
#include "covermanager/albumcoverfetcher.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "widgets/forcescrollperpixel.h"
|
||||
#include "widgets/groupediconview.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QListWidgetItem>
|
||||
#include <QPainter>
|
||||
#include <QStandardItemModel>
|
||||
|
||||
const int SizeOverlayDelegate::kMargin = 4;
|
||||
const int SizeOverlayDelegate::kPaddingX = 3;
|
||||
const int SizeOverlayDelegate::kPaddingY = 1;
|
||||
const qreal SizeOverlayDelegate::kBorder = 5.0;
|
||||
const qreal SizeOverlayDelegate::kFontPointSize = 7.5;
|
||||
const int SizeOverlayDelegate::kBorderAlpha = 200;
|
||||
const int SizeOverlayDelegate::kBackgroundAlpha = 175;
|
||||
|
||||
SizeOverlayDelegate::SizeOverlayDelegate(QObject *parent)
|
||||
: QStyledItemDelegate(parent) {}
|
||||
|
||||
void SizeOverlayDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
||||
|
||||
QStyledItemDelegate::paint(painter, option, index);
|
||||
|
||||
if (!index.data(AlbumCoverSearcher::Role_ImageFetchFinished).toBool()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QSize size = index.data(AlbumCoverSearcher::Role_ImageSize).toSize();
|
||||
const QString text = Utilities::PrettySize(size);
|
||||
|
||||
QFont font(option.font);
|
||||
font.setPointSizeF(kFontPointSize);
|
||||
font.setBold(true);
|
||||
|
||||
const QFontMetrics metrics(font);
|
||||
|
||||
const int text_width = metrics.width(text);
|
||||
|
||||
const QRect icon_rect(option.rect.left(), option.rect.top(), option.rect.width(), option.rect.width());
|
||||
|
||||
const QRect background_rect(icon_rect.right() - kMargin - text_width - kPaddingX * 2, icon_rect.bottom() - kMargin - metrics.height() - kPaddingY * 2, text_width + kPaddingX * 2, metrics.height() + kPaddingY * 2);
|
||||
const QRect text_rect(background_rect.left() + kPaddingX, background_rect.top() + kPaddingY, text_width, metrics.height());
|
||||
|
||||
painter->save();
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
painter->setPen(QColor(0, 0, 0, kBorderAlpha));
|
||||
painter->setBrush(QColor(0, 0, 0, kBackgroundAlpha));
|
||||
painter->drawRoundedRect(background_rect, kBorder, kBorder);
|
||||
|
||||
painter->setPen(Qt::white);
|
||||
painter->setFont(font);
|
||||
painter->drawText(text_rect, text);
|
||||
painter->restore();
|
||||
|
||||
}
|
||||
|
||||
AlbumCoverSearcher::AlbumCoverSearcher(const QIcon &no_cover_icon, Application *app, QWidget *parent)
|
||||
: QDialog(parent),
|
||||
ui_(new Ui_AlbumCoverSearcher),
|
||||
app_(app),
|
||||
model_(new QStandardItemModel(this)),
|
||||
no_cover_icon_(no_cover_icon),
|
||||
fetcher_(nullptr),
|
||||
id_(0) {
|
||||
|
||||
setWindowModality(Qt::WindowModal);
|
||||
ui_->setupUi(this);
|
||||
ui_->busy->hide();
|
||||
|
||||
ui_->covers->set_header_text(tr("Covers from %1"));
|
||||
ui_->covers->AddSortSpec(Role_ImageDimensions, Qt::DescendingOrder);
|
||||
ui_->covers->setItemDelegate(new SizeOverlayDelegate(this));
|
||||
ui_->covers->setModel(model_);
|
||||
|
||||
options_.scale_output_image_ = false;
|
||||
options_.pad_output_image_ = false;
|
||||
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(ImageLoaded(quint64, QImage)));
|
||||
|
||||
connect(ui_->search, SIGNAL(clicked()), SLOT(Search()));
|
||||
connect(ui_->covers, SIGNAL(doubleClicked(QModelIndex)), SLOT(CoverDoubleClicked(QModelIndex)));
|
||||
|
||||
new ForceScrollPerPixel(ui_->covers, this);
|
||||
|
||||
ui_->buttonBox->button(QDialogButtonBox::Cancel)->setShortcut(QKeySequence::Close);
|
||||
|
||||
}
|
||||
|
||||
AlbumCoverSearcher::~AlbumCoverSearcher() {
|
||||
delete ui_;
|
||||
}
|
||||
|
||||
void AlbumCoverSearcher::Init(AlbumCoverFetcher *fetcher) {
|
||||
|
||||
fetcher_ = fetcher;
|
||||
connect(fetcher_, SIGNAL(SearchFinished(quint64,CoverSearchResults,CoverSearchStatistics)), SLOT(SearchFinished(quint64,CoverSearchResults)));
|
||||
|
||||
}
|
||||
|
||||
QImage AlbumCoverSearcher::Exec(const QString &artist, const QString &album) {
|
||||
|
||||
ui_->artist->setText(artist);
|
||||
ui_->album->setText(album);
|
||||
ui_->artist->setFocus();
|
||||
|
||||
if (!artist.isEmpty() || !album.isEmpty()) {
|
||||
Search();
|
||||
}
|
||||
|
||||
if (exec() == QDialog::Rejected) return QImage();
|
||||
|
||||
QModelIndex selected = ui_->covers->currentIndex();
|
||||
if (!selected.isValid() || !selected.data(Role_ImageFetchFinished).toBool())
|
||||
return QImage();
|
||||
|
||||
QIcon icon = selected.data(Qt::DecorationRole).value<QIcon>();
|
||||
if (icon.cacheKey() == no_cover_icon_.cacheKey()) return QImage();
|
||||
|
||||
return icon.pixmap(icon.availableSizes()[0]).toImage();
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverSearcher::Search() {
|
||||
|
||||
model_->clear();
|
||||
cover_loading_tasks_.clear();
|
||||
|
||||
if (ui_->album->isEnabled()) {
|
||||
id_ = fetcher_->SearchForCovers(ui_->artist->text(), ui_->album->text());
|
||||
ui_->search->setText(tr("Abort"));
|
||||
ui_->busy->show();
|
||||
ui_->artist->setEnabled(false);
|
||||
ui_->album->setEnabled(false);
|
||||
ui_->covers->setEnabled(false);
|
||||
}
|
||||
else {
|
||||
fetcher_->Clear();
|
||||
ui_->search->setText(tr("Search"));
|
||||
ui_->busy->hide();
|
||||
ui_->search->setEnabled(true);
|
||||
ui_->artist->setEnabled(true);
|
||||
ui_->album->setEnabled(true);
|
||||
ui_->covers->setEnabled(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverSearcher::SearchFinished(quint64 id, const CoverSearchResults &results) {
|
||||
|
||||
if (id != id_)
|
||||
return;
|
||||
|
||||
ui_->search->setEnabled(true);
|
||||
ui_->artist->setEnabled(true);
|
||||
ui_->album->setEnabled(true);
|
||||
ui_->covers->setEnabled(true);
|
||||
ui_->search->setText(tr("Search"));
|
||||
id_ = 0;
|
||||
|
||||
for (const CoverSearchResult &result : results) {
|
||||
if (result.image_url.isEmpty()) continue;
|
||||
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(options_, result.image_url.toString(), QString());
|
||||
|
||||
QStandardItem *item = new QStandardItem;
|
||||
item->setIcon(no_cover_icon_);
|
||||
item->setText(result.description);
|
||||
item->setData(result.image_url, Role_ImageURL);
|
||||
item->setData(id, Role_ImageRequestId);
|
||||
item->setData(false, Role_ImageFetchFinished);
|
||||
item->setData(QVariant(Qt::AlignTop | Qt::AlignHCenter), Qt::TextAlignmentRole);
|
||||
item->setData(result.provider, GroupedIconView::Role_Group);
|
||||
|
||||
model_->appendRow(item);
|
||||
|
||||
cover_loading_tasks_[id] = item;
|
||||
}
|
||||
|
||||
if (cover_loading_tasks_.isEmpty()) ui_->busy->hide();
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverSearcher::ImageLoaded(quint64 id, const QImage &image) {
|
||||
|
||||
if (!cover_loading_tasks_.contains(id)) return;
|
||||
QStandardItem *item = cover_loading_tasks_.take(id);
|
||||
|
||||
if (cover_loading_tasks_.isEmpty()) ui_->busy->hide();
|
||||
|
||||
if (image.isNull()) {
|
||||
model_->removeRow(item->row());
|
||||
return;
|
||||
}
|
||||
|
||||
QIcon icon(QPixmap::fromImage(image));
|
||||
|
||||
// Create a pixmap that's padded and exactly the right size for the icon.
|
||||
QImage scaled_image(image.scaled(ui_->covers->iconSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
|
||||
QImage padded_image(ui_->covers->iconSize(), QImage::Format_ARGB32_Premultiplied);
|
||||
padded_image.fill(0);
|
||||
|
||||
QPainter p(&padded_image);
|
||||
p.drawImage((padded_image.width() - scaled_image.width()) / 2, (padded_image.height() - scaled_image.height()) / 2, scaled_image);
|
||||
p.end();
|
||||
|
||||
icon.addPixmap(QPixmap::fromImage(padded_image));
|
||||
|
||||
item->setData(true, Role_ImageFetchFinished);
|
||||
item->setData(image.width() * image.height(), Role_ImageDimensions);
|
||||
item->setData(image.size(), Role_ImageSize);
|
||||
item->setIcon(icon);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverSearcher::keyPressEvent(QKeyEvent *e) {
|
||||
|
||||
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
|
||||
e->ignore();
|
||||
return;
|
||||
}
|
||||
|
||||
QDialog::keyPressEvent(e);
|
||||
|
||||
}
|
||||
|
||||
void AlbumCoverSearcher::CoverDoubleClicked(const QModelIndex &index) {
|
||||
if (index.isValid()) accept();
|
||||
}
|
||||
|
||||
100
src/covermanager/albumcoversearcher.h
Normal file
100
src/covermanager/albumcoversearcher.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ALBUMCOVERSEARCHER_H
|
||||
#define ALBUMCOVERSEARCHER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QIcon>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
#include "covermanager/albumcoverfetcher.h"
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
|
||||
class AlbumCoverLoader;
|
||||
class Application;
|
||||
class Ui_AlbumCoverSearcher;
|
||||
|
||||
class QModelIndex;
|
||||
class QStandardItem;
|
||||
class QStandardItemModel;
|
||||
|
||||
class SizeOverlayDelegate : public QStyledItemDelegate {
|
||||
public:
|
||||
static const int kMargin;
|
||||
static const int kPaddingX;
|
||||
static const int kPaddingY;
|
||||
static const qreal kBorder;
|
||||
static const qreal kFontPointSize;
|
||||
static const int kBorderAlpha;
|
||||
static const int kBackgroundAlpha;
|
||||
|
||||
SizeOverlayDelegate(QObject *parent = nullptr);
|
||||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
|
||||
};
|
||||
|
||||
// This is a dialog that lets the user search for album covers
|
||||
class AlbumCoverSearcher : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AlbumCoverSearcher(const QIcon &no_cover_icon, Application *app, QWidget *parent);
|
||||
~AlbumCoverSearcher();
|
||||
|
||||
enum Role {
|
||||
Role_ImageURL = Qt::UserRole + 1,
|
||||
Role_ImageRequestId,
|
||||
Role_ImageFetchFinished,
|
||||
Role_ImageDimensions, // width * height
|
||||
Role_ImageSize,
|
||||
};
|
||||
|
||||
void Init(AlbumCoverFetcher *fetcher);
|
||||
|
||||
QImage Exec(const QString &artist, const QString &album);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *);
|
||||
|
||||
private slots:
|
||||
void Search();
|
||||
void SearchFinished(quint64 id, const CoverSearchResults &results);
|
||||
void ImageLoaded(quint64 id, const QImage &image);
|
||||
|
||||
void CoverDoubleClicked(const QModelIndex &index);
|
||||
|
||||
private:
|
||||
Ui_AlbumCoverSearcher *ui_;
|
||||
|
||||
Application *app_;
|
||||
QStandardItemModel *model_;
|
||||
|
||||
QIcon no_cover_icon_;
|
||||
AlbumCoverLoaderOptions options_;
|
||||
AlbumCoverFetcher *fetcher_;
|
||||
|
||||
quint64 id_;
|
||||
QMap<quint64, QStandardItem*> cover_loading_tasks_;
|
||||
};
|
||||
|
||||
#endif // ALBUMCOVERSEARCHER_H
|
||||
173
src/covermanager/albumcoversearcher.ui
Normal file
173
src/covermanager/albumcoversearcher.ui
Normal file
@@ -0,0 +1,173 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AlbumCoverSearcher</class>
|
||||
<widget class="QDialog" name="AlbumCoverSearcher">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>829</width>
|
||||
<height>518</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Cover Manager</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSearchField" name="artist">
|
||||
<property name="toolTip">
|
||||
<string>Artist</string>
|
||||
</property>
|
||||
<property name="placeholderText" stdset="0">
|
||||
<string>Artist</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSearchField" name="album">
|
||||
<property name="toolTip">
|
||||
<string>Album</string>
|
||||
</property>
|
||||
<property name="placeholderText" stdset="0">
|
||||
<string>Album</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="search">
|
||||
<property name="text">
|
||||
<string>Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="BusyIndicator" name="busy" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="GroupedIconView" name="covers">
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>120</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QSearchField</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>3rdparty/qocoa/qsearchfield.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>BusyIndicator</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/busyindicator.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>GroupedIconView</class>
|
||||
<extends>QListView</extends>
|
||||
<header>widgets/groupediconview.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>artist</tabstop>
|
||||
<tabstop>album</tabstop>
|
||||
<tabstop>search</tabstop>
|
||||
<tabstop>covers</tabstop>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>AlbumCoverSearcher</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>257</x>
|
||||
<y>508</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>AlbumCoverSearcher</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>325</x>
|
||||
<y>508</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>artist</sender>
|
||||
<signal>returnPressed()</signal>
|
||||
<receiver>search</receiver>
|
||||
<slot>click()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>357</x>
|
||||
<y>35</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>812</x>
|
||||
<y>36</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>album</sender>
|
||||
<signal>returnPressed()</signal>
|
||||
<receiver>search</receiver>
|
||||
<slot>click()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>580</x>
|
||||
<y>22</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>779</x>
|
||||
<y>21</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
181
src/covermanager/amazoncoverprovider.cpp
Normal file
181
src/covermanager/amazoncoverprovider.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2013, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QDateTime>
|
||||
#include <QNetworkReply>
|
||||
#include <QStringList>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include "amazoncoverprovider.h"
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/utilities.h"
|
||||
|
||||
const char *AmazonCoverProvider::kUrl = "http://ecs.amazonaws.com/onca/xml";
|
||||
const char *AmazonCoverProvider::kAssociateTag = "jonas052-20";
|
||||
|
||||
const char *AmazonCoverProvider::kAccessKeyB64 = "QUtJQUozQ1dIQ0RWSVlYN1JMTFE=";
|
||||
const char *AmazonCoverProvider::kSecretAccessKeyB64 = "TjFZU3F2c2hJZDVtUGxKVW1Ka0kvc2E1WkZESG9TYy9ZWkgxYWdJdQ==";
|
||||
|
||||
AmazonCoverProvider::AmazonCoverProvider(QObject *parent) : CoverProvider("Amazon", parent), network_(new NetworkAccessManager(this)) {}
|
||||
|
||||
bool AmazonCoverProvider::StartSearch(const QString &artist, const QString &album, int id) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
typedef QPair<QString, QString> Arg;
|
||||
typedef QList<Arg> ArgList;
|
||||
|
||||
typedef QPair<QByteArray, QByteArray> EncodedArg;
|
||||
typedef QList<EncodedArg> EncodedArgList;
|
||||
|
||||
// Must be sorted by parameter name
|
||||
ArgList args = ArgList()
|
||||
<< Arg("AWSAccessKeyId", QByteArray::fromBase64(kAccessKeyB64))
|
||||
//<< Arg("AWSAccessKeyId", kAccessKey)
|
||||
<< Arg("AssociateTag", kAssociateTag)
|
||||
<< Arg("Keywords", artist + " " + album)
|
||||
<< Arg("Operation", "ItemSearch")
|
||||
<< Arg("ResponseGroup", "Images")
|
||||
<< Arg("SearchIndex", "All")
|
||||
<< Arg("Service", "AWSECommerceService")
|
||||
<< Arg("Timestamp", QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm:ss.zzzZ"))
|
||||
<< Arg("Version", "2009-11-01");
|
||||
|
||||
QUrlQuery url_query;
|
||||
QUrl url(kUrl);
|
||||
QStringList query_items;
|
||||
|
||||
// Encode the arguments
|
||||
for (const Arg &arg : args) {
|
||||
EncodedArg encoded_arg(QUrl::toPercentEncoding(arg.first), QUrl::toPercentEncoding(arg.second));
|
||||
query_items << QString(encoded_arg.first + "=" + encoded_arg.second);
|
||||
url_query.addQueryItem(encoded_arg.first, encoded_arg.second);
|
||||
}
|
||||
|
||||
// Sign the request
|
||||
|
||||
const QByteArray data_to_sign = QString("GET\n%1\n%2\n%3").arg(url.host(), url.path(), query_items.join("&")).toLatin1();
|
||||
//const QByteArray signature(Utilities::HmacSha256(kSecretAccessKey, data_to_sign));
|
||||
const QByteArray signature(Utilities::HmacSha256(QByteArray::fromBase64(kSecretAccessKeyB64), data_to_sign));
|
||||
|
||||
// Add the signature to the request
|
||||
|
||||
url_query.addQueryItem("Signature", QUrl::toPercentEncoding(signature.toBase64()));
|
||||
|
||||
url.setQuery(url_query);
|
||||
QNetworkReply *reply = network_->get(QNetworkRequest(url));
|
||||
|
||||
NewClosure(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(QueryError(QNetworkReply::NetworkError, QNetworkReply*, int)), reply, id);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(QueryFinished(QNetworkReply*, int)), reply, id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AmazonCoverProvider::QueryError(QNetworkReply::NetworkError error, QNetworkReply *reply, int id) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
|
||||
}
|
||||
|
||||
void AmazonCoverProvider::QueryFinished(QNetworkReply *reply, int id) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
QString data=(QString)reply->readAll();
|
||||
|
||||
//qDebug() << data;
|
||||
|
||||
CoverSearchResults results;
|
||||
|
||||
QXmlStreamReader reader(data);
|
||||
while (!reader.atEnd()) {
|
||||
if (reader.readNext() == QXmlStreamReader::EndDocument) break;
|
||||
if (reader.tokenType() == QXmlStreamReader::Invalid ) { qLog(Debug) << reader.error() << reader.errorString(); break; }
|
||||
//qLog(Debug) << reader.tokenType() << reader.name();
|
||||
if (reader.tokenType() == QXmlStreamReader::StartElement && reader.name() == "Item") {
|
||||
ReadItem(&reader, &results);
|
||||
}
|
||||
}
|
||||
|
||||
emit SearchFinished(id, results);
|
||||
|
||||
}
|
||||
|
||||
void AmazonCoverProvider::ReadItem(QXmlStreamReader *reader, CoverSearchResults *results) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__ << "name: " << reader->name() << " text: " << reader->text();
|
||||
|
||||
while (!reader->atEnd()) {
|
||||
switch (reader->readNext()) {
|
||||
case QXmlStreamReader::StartElement:
|
||||
if (reader->name() == "LargeImage") {
|
||||
ReadLargeImage(reader, results);
|
||||
}
|
||||
else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
break;
|
||||
|
||||
case QXmlStreamReader::EndElement:
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AmazonCoverProvider::ReadLargeImage(QXmlStreamReader *reader, CoverSearchResults *results) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
while (!reader->atEnd()) {
|
||||
switch (reader->readNext()) {
|
||||
case QXmlStreamReader::StartElement:
|
||||
if (reader->name() == "URL") {
|
||||
CoverSearchResult result;
|
||||
result.image_url = QUrl(reader->readElementText());
|
||||
results->append(result);
|
||||
}
|
||||
else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
break;
|
||||
|
||||
case QXmlStreamReader::EndElement:
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
60
src/covermanager/amazoncoverprovider.h
Normal file
60
src/covermanager/amazoncoverprovider.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2013, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AMAZONCOVERPROVIDER_H
|
||||
#define AMAZONCOVERPROVIDER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QXmlStreamReader>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "coverprovider.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
|
||||
class AmazonCoverProvider : public CoverProvider {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AmazonCoverProvider(QObject *parent = nullptr);
|
||||
|
||||
static const char *kUrl;
|
||||
static const char *kAssociateTag;
|
||||
|
||||
static const char *kAccessKeyB64;
|
||||
static const char *kSecretAccessKeyB64;
|
||||
|
||||
bool StartSearch(const QString &artist, const QString &album, int id);
|
||||
|
||||
private slots:
|
||||
void QueryError(QNetworkReply::NetworkError error, QNetworkReply *reply, int id);
|
||||
void QueryFinished(QNetworkReply *reply, int id);
|
||||
|
||||
private:
|
||||
void ReadItem(QXmlStreamReader *reader, CoverSearchResults *results);
|
||||
void ReadLargeImage(QXmlStreamReader *reader, CoverSearchResults *results);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager *network_;
|
||||
};
|
||||
|
||||
#endif // AMAZONCOVERPROVIDER_H
|
||||
203
src/covermanager/coverexportrunnable.cpp
Normal file
203
src/covermanager/coverexportrunnable.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QUrl>
|
||||
|
||||
#include "albumcoverexporter.h"
|
||||
#include "core/song.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
|
||||
#include "coverexportrunnable.h"
|
||||
|
||||
CoverExportRunnable::CoverExportRunnable(const AlbumCoverExport::DialogResult &dialog_result, const Song &song)
|
||||
: dialog_result_(dialog_result), song_(song) {}
|
||||
|
||||
void CoverExportRunnable::run() {
|
||||
|
||||
QString cover_path = GetCoverPath();
|
||||
|
||||
// manually unset?
|
||||
if (cover_path.isEmpty()) {
|
||||
EmitCoverSkipped();
|
||||
}
|
||||
else {
|
||||
if (dialog_result_.RequiresCoverProcessing())
|
||||
ProcessAndExportCover();
|
||||
else
|
||||
ExportCover();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString CoverExportRunnable::GetCoverPath() {
|
||||
|
||||
if (song_.has_manually_unset_cover()) {
|
||||
return QString();
|
||||
// Export downloaded covers?
|
||||
}
|
||||
else if (!song_.art_manual().isEmpty() && dialog_result_.export_downloaded_) {
|
||||
return song_.art_manual();
|
||||
// Export embedded covers?
|
||||
}
|
||||
else if (!song_.art_automatic().isEmpty() && song_.art_automatic() == Song::kEmbeddedCover && dialog_result_.export_embedded_) {
|
||||
return song_.art_automatic();
|
||||
}
|
||||
else {
|
||||
return QString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Exports a single album cover using a "save QImage to file" approach.
|
||||
// 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:
|
||||
// - either the force size flag is being used
|
||||
// - or the "overwrite smaller" mode is used
|
||||
// In all other cases, the faster ExportCover() method will be used.
|
||||
void CoverExportRunnable::ProcessAndExportCover() {
|
||||
|
||||
QString cover_path = GetCoverPath();
|
||||
|
||||
// either embedded or disk - the one we'll export for the current album
|
||||
QImage cover;
|
||||
|
||||
QImage embedded_cover;
|
||||
QImage disk_cover;
|
||||
|
||||
// load embedded cover if any
|
||||
if (song_.has_embedded_cover()) {
|
||||
embedded_cover = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song_.url().toLocalFile());
|
||||
|
||||
if (embedded_cover.isNull()) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
}
|
||||
cover = embedded_cover;
|
||||
}
|
||||
|
||||
// load a file cover which iss mandatory if there's no embedded cover
|
||||
disk_cover.load(cover_path);
|
||||
|
||||
if (embedded_cover.isNull()) {
|
||||
if (disk_cover.isNull()) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
}
|
||||
cover = disk_cover;
|
||||
}
|
||||
|
||||
// rescale if necessary
|
||||
if (dialog_result_.IsSizeForced()) {
|
||||
cover = cover.scaled(QSize(dialog_result_.width_, dialog_result_.height_), Qt::IgnoreAspectRatio);
|
||||
}
|
||||
|
||||
QString dir = song_.url().toLocalFile().section('/', 0, -2);
|
||||
QString extension = cover_path.section('.', -1);
|
||||
|
||||
QString new_file = dir + '/' + dialog_result_.fileName_ + '.' + (cover_path == Song::kEmbeddedCover ? "jpg" : extension);
|
||||
|
||||
// If the file exists, do not override!
|
||||
if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode_None && QFile::exists(new_file)) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
}
|
||||
|
||||
// we're handling overwrite as remove + copy so we need to delete the old file
|
||||
// first
|
||||
if (QFile::exists(new_file) && dialog_result_.overwrite_ != AlbumCoverExport::OverwriteMode_None) {
|
||||
|
||||
// if the mode is "overwrite smaller" then skip the cover if a bigger one
|
||||
// is already available in the folder
|
||||
if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode_Smaller) {
|
||||
QImage existing;
|
||||
existing.load(new_file);
|
||||
|
||||
if (existing.isNull() || existing.size().height() >= cover.size().height() || existing.size().width() >= cover.size().width()) {
|
||||
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!QFile::remove(new_file)) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cover.save(new_file))
|
||||
EmitCoverExported();
|
||||
else
|
||||
EmitCoverSkipped();
|
||||
|
||||
}
|
||||
|
||||
// Exports a single album cover using a "copy file" approach.
|
||||
void CoverExportRunnable::ExportCover() {
|
||||
|
||||
QString cover_path = GetCoverPath();
|
||||
|
||||
QString dir = song_.url().toLocalFile().section('/', 0, -2);
|
||||
QString extension = cover_path.section('.', -1);
|
||||
|
||||
QString new_file = dir + '/' + dialog_result_.fileName_ + '.' + (cover_path == Song::kEmbeddedCover ? "jpg" : extension);
|
||||
|
||||
// If the file exists, do not override!
|
||||
if (dialog_result_.overwrite_ == AlbumCoverExport::OverwriteMode_None && QFile::exists(new_file)) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
}
|
||||
|
||||
// we're handling overwrite as remove + copy so we need to delete the old file
|
||||
// first
|
||||
if (dialog_result_.overwrite_ != AlbumCoverExport::OverwriteMode_None && QFile::exists(new_file)) {
|
||||
if (!QFile::remove(new_file)) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cover_path == Song::kEmbeddedCover) {
|
||||
// an embedded cover
|
||||
QImage embedded = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(song_.url().toLocalFile());
|
||||
if (!embedded.save(new_file)) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// automatic or manual cover, available in an image file
|
||||
if (!QFile::copy(cover_path, new_file)) {
|
||||
EmitCoverSkipped();
|
||||
return;
|
||||
}
|
||||
}
|
||||
EmitCoverExported();
|
||||
|
||||
}
|
||||
|
||||
void CoverExportRunnable::EmitCoverExported() { emit CoverExported(); }
|
||||
|
||||
void CoverExportRunnable::EmitCoverSkipped() { emit CoverSkipped(); }
|
||||
|
||||
61
src/covermanager/coverexportrunnable.h
Normal file
61
src/covermanager/coverexportrunnable.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef COVEREXPORTRUNNABLE_H
|
||||
#define COVEREXPORTRUNNABLE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QRunnable>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "covermanager/albumcoverexport.h"
|
||||
|
||||
class AlbumCoverExporter;
|
||||
|
||||
class CoverExportRunnable : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CoverExportRunnable(const AlbumCoverExport::DialogResult &dialog_result, const Song &song);
|
||||
virtual ~CoverExportRunnable() {}
|
||||
|
||||
void run();
|
||||
|
||||
signals:
|
||||
void CoverExported();
|
||||
void CoverSkipped();
|
||||
|
||||
private:
|
||||
void EmitCoverExported();
|
||||
void EmitCoverSkipped();
|
||||
|
||||
void ProcessAndExportCover();
|
||||
void ExportCover();
|
||||
QString GetCoverPath();
|
||||
|
||||
AlbumCoverExport::DialogResult dialog_result_;
|
||||
Song song_;
|
||||
AlbumCoverExporter* album_cover_exporter_;
|
||||
};
|
||||
|
||||
#endif // COVEREXPORTRUNNABLE_H
|
||||
|
||||
91
src/covermanager/coverfromurldialog.cpp
Normal file
91
src/covermanager/coverfromurldialog.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QImage>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
#include "coverfromurldialog.h"
|
||||
#include "ui_coverfromurldialog.h"
|
||||
|
||||
#include "core/network.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
|
||||
CoverFromURLDialog::CoverFromURLDialog(QWidget *parent) : QDialog(parent), ui_(new Ui_CoverFromURLDialog), network_(new NetworkAccessManager(this)) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
ui_->busy->hide();
|
||||
}
|
||||
|
||||
CoverFromURLDialog::~CoverFromURLDialog() {
|
||||
delete ui_;
|
||||
}
|
||||
|
||||
QImage CoverFromURLDialog::Exec() {
|
||||
// reset state
|
||||
ui_->url->setText("");;
|
||||
last_image_ = QImage();
|
||||
|
||||
QClipboard *clipboard = QApplication::clipboard();
|
||||
ui_->url->setText(clipboard->text());
|
||||
|
||||
exec();
|
||||
return last_image_;
|
||||
}
|
||||
|
||||
void CoverFromURLDialog::accept() {
|
||||
|
||||
ui_->busy->show();
|
||||
|
||||
QNetworkRequest network_request = QNetworkRequest(QUrl::fromUserInput(ui_->url->text()));
|
||||
|
||||
QNetworkReply *reply = network_->get(network_request);
|
||||
connect(reply, SIGNAL(finished()), SLOT(LoadCoverFromURLFinished()));
|
||||
}
|
||||
|
||||
void CoverFromURLDialog::LoadCoverFromURLFinished() {
|
||||
|
||||
ui_->busy->hide();
|
||||
|
||||
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
QMessageBox::information(this, tr("Fetching cover error"), tr("The site you requested does not exist!"));
|
||||
return;
|
||||
}
|
||||
|
||||
QImage image;
|
||||
image.loadFromData(reply->readAll());
|
||||
|
||||
if (!image.isNull()) {
|
||||
last_image_ = image;
|
||||
QDialog::accept();
|
||||
}
|
||||
else {
|
||||
QMessageBox::information(this, tr("Fetching cover error"), tr("The site you requested is not an image!"));
|
||||
}
|
||||
}
|
||||
|
||||
57
src/covermanager/coverfromurldialog.h
Normal file
57
src/covermanager/coverfromurldialog.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef COVERFROMURLDIALOG_H
|
||||
#define COVERFROMURLDIALOG_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QImage>
|
||||
|
||||
class NetworkAccessManager;
|
||||
class Song;
|
||||
class Ui_CoverFromURLDialog;
|
||||
|
||||
// Controller for a dialog which fetches covers from the given URL.
|
||||
class CoverFromURLDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CoverFromURLDialog(QWidget *parent = nullptr);
|
||||
~CoverFromURLDialog();
|
||||
|
||||
// Opens the dialog. This returns an image found at the URL chosen by user
|
||||
// or null image if the dialog got rejected.
|
||||
QImage Exec();
|
||||
|
||||
private slots:
|
||||
void accept();
|
||||
void LoadCoverFromURLFinished();
|
||||
|
||||
private:
|
||||
Ui_CoverFromURLDialog *ui_;
|
||||
|
||||
NetworkAccessManager *network_;
|
||||
QImage last_image_;
|
||||
};
|
||||
|
||||
#endif // COVERFROMURLDIALOG_H
|
||||
|
||||
117
src/covermanager/coverfromurldialog.ui
Normal file
117
src/covermanager/coverfromurldialog.ui
Normal file
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CoverFromURLDialog</class>
|
||||
<widget class="QDialog" name="CoverFromURLDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>407</width>
|
||||
<height>126</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Load cover from URL</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="urlLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enter a URL to download a cover from the Internet:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="BusyIndicator" name="busy"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="url"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>3</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>BusyIndicator</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/busyindicator.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>CoverFromURLDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>CoverFromURLDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
26
src/covermanager/coverprovider.cpp
Normal file
26
src/covermanager/coverprovider.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "coverprovider.h"
|
||||
|
||||
CoverProvider::CoverProvider(const QString &name, QObject *parent)
|
||||
: QObject(parent), name_(name) {}
|
||||
61
src/covermanager/coverprovider.h
Normal file
61
src/covermanager/coverprovider.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef COVERPROVIDER_H
|
||||
#define COVERPROVIDER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "albumcoverfetcher.h"
|
||||
#include "coverproviders.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class QNetworkReply;
|
||||
|
||||
// Each implementation of this interface downloads covers from one online
|
||||
// service. There are no limitations on what this service might be - last.fm,
|
||||
// Amazon, Google Images - you name it.
|
||||
class CoverProvider : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CoverProvider(const QString& name, QObject* parent);
|
||||
|
||||
// A name (very short description) of this provider, like "last.fm".
|
||||
QString name() const { return name_; }
|
||||
|
||||
// Starts searching for covers matching the given query text. Returns true
|
||||
// if the query has been started, or false if an error occurred. The provider
|
||||
// should remember the ID and emit it along with the result when it finishes.
|
||||
virtual bool StartSearch(const QString &artist, const QString &album, int id) = 0;
|
||||
|
||||
virtual void CancelSearch(int id) {}
|
||||
|
||||
signals:
|
||||
void SearchFinished(int id, const QList<CoverSearchResult>& results);
|
||||
|
||||
private:
|
||||
QString name_;
|
||||
};
|
||||
|
||||
#endif // COVERPROVIDER_H
|
||||
72
src/covermanager/coverproviders.cpp
Normal file
72
src/covermanager/coverproviders.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "coverprovider.h"
|
||||
#include "coverproviders.h"
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
CoverProviders::CoverProviders(QObject *parent) : QObject(parent) {}
|
||||
|
||||
void CoverProviders::AddProvider(CoverProvider *provider) {
|
||||
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
cover_providers_.insert(provider, provider->name());
|
||||
connect(provider, SIGNAL(destroyed()), SLOT(ProviderDestroyed()));
|
||||
}
|
||||
|
||||
qLog(Debug) << "Registered cover provider" << provider->name();
|
||||
|
||||
}
|
||||
|
||||
void CoverProviders::RemoveProvider(CoverProvider *provider) {
|
||||
|
||||
if (!provider) return;
|
||||
|
||||
// It's not safe to dereference provider at this pointbecause it might have
|
||||
// already been destroyed.
|
||||
|
||||
QString name;
|
||||
|
||||
{
|
||||
QMutexLocker locker(&mutex_);
|
||||
name = cover_providers_.take(provider);
|
||||
}
|
||||
|
||||
if (name.isNull()) {
|
||||
qLog(Debug) << "Tried to remove a cover provider that was not registered";
|
||||
}
|
||||
else {
|
||||
qLog(Debug) << "Unregistered cover provider" << name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CoverProviders::ProviderDestroyed() {
|
||||
|
||||
CoverProvider *provider = static_cast<CoverProvider*>(sender());
|
||||
RemoveProvider(provider);
|
||||
|
||||
}
|
||||
|
||||
int CoverProviders::NextId() { return next_id_.fetchAndAddRelaxed(1); }
|
||||
67
src/covermanager/coverproviders.h
Normal file
67
src/covermanager/coverproviders.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef COVERPROVIDERS_H
|
||||
#define COVERPROVIDERS_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
|
||||
class AlbumCoverFetcherSearch;
|
||||
class CoverProvider;
|
||||
|
||||
// This is a repository for cover providers.
|
||||
// Providers are automatically unregistered from the repository when they are
|
||||
// deleted. The class is thread safe.
|
||||
class CoverProviders : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CoverProviders(QObject *parent = nullptr);
|
||||
|
||||
// Lets a cover provider register itself in the repository.
|
||||
void AddProvider(CoverProvider *provider);
|
||||
void RemoveProvider(CoverProvider *provider);
|
||||
|
||||
// Returns a list of cover providers
|
||||
QList<CoverProvider*> List() const { return cover_providers_.keys(); }
|
||||
|
||||
// Returns true if this repository has at least one registered provider.
|
||||
bool HasAnyProviders() const { return !cover_providers_.isEmpty(); }
|
||||
|
||||
int NextId();
|
||||
|
||||
private slots:
|
||||
void ProviderDestroyed();
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(CoverProviders);
|
||||
|
||||
QMap<CoverProvider *, QString> cover_providers_;
|
||||
QMutex mutex_;
|
||||
|
||||
QAtomicInt next_id_;
|
||||
};
|
||||
|
||||
#endif // COVERPROVIDERS_H
|
||||
|
||||
64
src/covermanager/coversearchstatistics.cpp
Normal file
64
src/covermanager/coversearchstatistics.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "coversearchstatistics.h"
|
||||
|
||||
CoverSearchStatistics::CoverSearchStatistics()
|
||||
: network_requests_made_(0),
|
||||
bytes_transferred_(0),
|
||||
chosen_images_(0),
|
||||
missing_images_(0),
|
||||
chosen_width_(0),
|
||||
chosen_height_(0) {}
|
||||
|
||||
CoverSearchStatistics &CoverSearchStatistics::operator +=(const CoverSearchStatistics &other) {
|
||||
|
||||
network_requests_made_ += other.network_requests_made_;
|
||||
bytes_transferred_ += other.bytes_transferred_;
|
||||
|
||||
for (const QString& key : other.chosen_images_by_provider_.keys()) {
|
||||
chosen_images_by_provider_[key] += other.chosen_images_by_provider_[key];
|
||||
}
|
||||
for (const QString& key : other.total_images_by_provider_.keys()) {
|
||||
total_images_by_provider_[key] += other.total_images_by_provider_[key];
|
||||
}
|
||||
|
||||
chosen_images_ += other.chosen_images_;
|
||||
missing_images_ += other.missing_images_;
|
||||
|
||||
chosen_width_ += other.chosen_width_;
|
||||
chosen_height_ += other.chosen_height_;
|
||||
|
||||
return *this;
|
||||
|
||||
}
|
||||
|
||||
QString CoverSearchStatistics::AverageDimensions() const {
|
||||
|
||||
if (chosen_images_ == 0) {
|
||||
return "0x0";
|
||||
}
|
||||
|
||||
return QString::number(chosen_width_ / chosen_images_) + "x" + QString::number(chosen_height_ / chosen_images_);
|
||||
|
||||
}
|
||||
|
||||
50
src/covermanager/coversearchstatistics.h
Normal file
50
src/covermanager/coversearchstatistics.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef COVERSEARCHSTATISTICS_H
|
||||
#define COVERSEARCHSTATISTICS_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
struct CoverSearchStatistics {
|
||||
|
||||
CoverSearchStatistics();
|
||||
|
||||
CoverSearchStatistics& operator +=(const CoverSearchStatistics &other);
|
||||
|
||||
quint64 network_requests_made_;
|
||||
quint64 bytes_transferred_;
|
||||
QMap<QString, quint64> total_images_by_provider_;
|
||||
QMap<QString, quint64> chosen_images_by_provider_;
|
||||
|
||||
quint64 chosen_images_;
|
||||
quint64 missing_images_;
|
||||
|
||||
quint64 chosen_width_;
|
||||
quint64 chosen_height_;
|
||||
|
||||
QString AverageDimensions() const;
|
||||
|
||||
};
|
||||
|
||||
#endif // COVERSEARCHSTATISTICS_H
|
||||
97
src/covermanager/coversearchstatisticsdialog.cpp
Normal file
97
src/covermanager/coversearchstatisticsdialog.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "coversearchstatisticsdialog.h"
|
||||
#include "core/utilities.h"
|
||||
#include "ui_coversearchstatisticsdialog.h"
|
||||
|
||||
CoverSearchStatisticsDialog::CoverSearchStatisticsDialog(QWidget *parent)
|
||||
: QDialog(parent), ui_(new Ui_CoverSearchStatisticsDialog) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
details_layout_ = new QVBoxLayout(ui_->details);
|
||||
details_layout_->setSpacing(0);
|
||||
|
||||
setStyleSheet(
|
||||
"#details {"
|
||||
" background-color: palette(base);"
|
||||
"}"
|
||||
"#details QLabel[type=\"label\"] {"
|
||||
" border: 2px solid transparent;"
|
||||
" border-right: 2px solid palette(midlight);"
|
||||
" margin-right: 10px;"
|
||||
"}"
|
||||
"#details QLabel[type=\"value\"] {"
|
||||
" font-weight: bold;"
|
||||
" max-width: 100px;"
|
||||
"}");
|
||||
}
|
||||
|
||||
CoverSearchStatisticsDialog::~CoverSearchStatisticsDialog() { delete ui_; }
|
||||
|
||||
void CoverSearchStatisticsDialog::Show(const CoverSearchStatistics &statistics) {
|
||||
|
||||
QStringList providers(statistics.total_images_by_provider_.keys());
|
||||
qSort(providers);
|
||||
|
||||
ui_->summary->setText(tr("Got %1 covers out of %2 (%3 failed)")
|
||||
.arg(statistics.chosen_images_)
|
||||
.arg(statistics.chosen_images_ + statistics.missing_images_)
|
||||
.arg(statistics.missing_images_));
|
||||
|
||||
for (const QString& provider : providers) {
|
||||
AddLine(tr("Covers from %1").arg(provider), QString::number(statistics.chosen_images_by_provider_[provider]));
|
||||
}
|
||||
|
||||
if (!providers.isEmpty()) {
|
||||
AddSpacer();
|
||||
}
|
||||
|
||||
AddLine(tr("Total network requests made"), QString::number(statistics.network_requests_made_));
|
||||
AddLine(tr("Average image size"), statistics.AverageDimensions());
|
||||
AddLine(tr("Total bytes transferred"), statistics.bytes_transferred_ ? Utilities::PrettySize(statistics.bytes_transferred_) : "0 bytes");
|
||||
|
||||
details_layout_->addStretch();
|
||||
|
||||
show();
|
||||
|
||||
}
|
||||
|
||||
void CoverSearchStatisticsDialog::AddLine(const QString &label, const QString &value) {
|
||||
|
||||
QLabel *label1 = new QLabel(label);
|
||||
QLabel *label2 = new QLabel(value);
|
||||
|
||||
label1->setProperty("type", "label");
|
||||
label2->setProperty("type", "value");
|
||||
|
||||
QHBoxLayout* layout = new QHBoxLayout;
|
||||
layout->addWidget(label1);
|
||||
layout->addWidget(label2);
|
||||
details_layout_->addLayout(layout);
|
||||
|
||||
}
|
||||
|
||||
void CoverSearchStatisticsDialog::AddSpacer() {
|
||||
details_layout_->addSpacing(20);
|
||||
}
|
||||
|
||||
53
src/covermanager/coversearchstatisticsdialog.h
Normal file
53
src/covermanager/coversearchstatisticsdialog.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef COVERSEARCHSTATISTICSDIALOG_H
|
||||
#define COVERSEARCHSTATISTICSDIALOG_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include "coversearchstatistics.h"
|
||||
|
||||
class Ui_CoverSearchStatisticsDialog;
|
||||
|
||||
class QVBoxLayout;
|
||||
|
||||
class CoverSearchStatisticsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CoverSearchStatisticsDialog(QWidget *parent = nullptr);
|
||||
~CoverSearchStatisticsDialog();
|
||||
|
||||
void Show(const CoverSearchStatistics& statistics);
|
||||
|
||||
private:
|
||||
void AddLine(const QString &label, const QString &value);
|
||||
void AddSpacer();
|
||||
|
||||
private:
|
||||
Ui_CoverSearchStatisticsDialog *ui_;
|
||||
QVBoxLayout *details_layout_;
|
||||
};
|
||||
|
||||
#endif // COVERSEARCHSTATISTICSDIALOG_H
|
||||
|
||||
87
src/covermanager/coversearchstatisticsdialog.ui
Normal file
87
src/covermanager/coversearchstatisticsdialog.ui
Normal file
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CoverSearchStatisticsDialog</class>
|
||||
<widget class="QDialog" name="CoverSearchStatisticsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>425</width>
|
||||
<height>214</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Fetch completed</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="summary">
|
||||
<property name="text">
|
||||
<string>Got %1 covers out of %2 (%3 failed)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="details">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Sunken</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>CoverSearchStatisticsDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>CoverSearchStatisticsDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
87
src/covermanager/currentartloader.cpp
Normal file
87
src/covermanager/currentartloader.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QTemporaryFile>
|
||||
#include <QUrl>
|
||||
|
||||
#include "currentartloader.h"
|
||||
#include "core/application.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
|
||||
CurrentArtLoader::CurrentArtLoader(Application *app, QObject *parent)
|
||||
: QObject(parent),
|
||||
app_(app),
|
||||
temp_file_pattern_(QDir::tempPath() + "/strawberry-art-XXXXXX.jpg"),
|
||||
id_(0)
|
||||
{
|
||||
options_.scale_output_image_ = false;
|
||||
options_.pad_output_image_ = false;
|
||||
//QIcon nocover = IconLoader::Load("nocover");
|
||||
//options_.default_output_image_ = nocover.pixmap(nocover.availableSizes().last()).toImage();
|
||||
options_.default_output_image_ = QImage(":/pictures/noalbumart.png");
|
||||
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)), SLOT(TempArtLoaded(quint64, QImage)));
|
||||
|
||||
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(LoadArt(Song)));
|
||||
}
|
||||
|
||||
CurrentArtLoader::~CurrentArtLoader() {}
|
||||
|
||||
void CurrentArtLoader::LoadArt(const Song &song) {
|
||||
last_song_ = song;
|
||||
id_ = app_->album_cover_loader()->LoadImageAsync(options_, last_song_);
|
||||
}
|
||||
|
||||
void CurrentArtLoader::TempArtLoaded(quint64 id, const QImage &image) {
|
||||
|
||||
if (id != id_) return;
|
||||
id_ = 0;
|
||||
|
||||
QString uri;
|
||||
QString thumbnail_uri;
|
||||
QImage thumbnail;
|
||||
|
||||
if (!image.isNull()) {
|
||||
temp_art_.reset(new QTemporaryFile(temp_file_pattern_));
|
||||
temp_art_->setAutoRemove(true);
|
||||
temp_art_->open();
|
||||
image.save(temp_art_->fileName(), "JPEG");
|
||||
|
||||
// Scale the image down to make a thumbnail. It's a bit crap doing it here
|
||||
// since it's the GUI thread, but the alternative is hard.
|
||||
temp_art_thumbnail_.reset(new QTemporaryFile(temp_file_pattern_));
|
||||
temp_art_thumbnail_->open();
|
||||
temp_art_thumbnail_->setAutoRemove(true);
|
||||
thumbnail = image.scaledToHeight(120, Qt::SmoothTransformation);
|
||||
thumbnail.save(temp_art_thumbnail_->fileName(), "JPEG");
|
||||
|
||||
uri = "file://" + temp_art_->fileName();
|
||||
thumbnail_uri = "file://" + temp_art_thumbnail_->fileName();
|
||||
}
|
||||
|
||||
emit ArtLoaded(last_song_, uri, image);
|
||||
emit ThumbnailLoaded(last_song_, thumbnail_uri, thumbnail);
|
||||
|
||||
}
|
||||
71
src/covermanager/currentartloader.h
Normal file
71
src/covermanager/currentartloader.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CURRENTARTLOADER_H
|
||||
#define CURRENTARTLOADER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
|
||||
class Application;
|
||||
|
||||
class QImage;
|
||||
class QTemporaryFile;
|
||||
|
||||
class CurrentArtLoader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CurrentArtLoader(Application *app, QObject *parent = nullptr);
|
||||
~CurrentArtLoader();
|
||||
|
||||
const AlbumCoverLoaderOptions &options() const { return options_; }
|
||||
const Song &last_song() const { return last_song_; }
|
||||
|
||||
public slots:
|
||||
void LoadArt(const Song &song);
|
||||
|
||||
signals:
|
||||
void ArtLoaded(const Song &song, const QString &uri, const QImage &image);
|
||||
void ThumbnailLoaded(const Song &song, const QString &uri, const QImage &image);
|
||||
|
||||
private slots:
|
||||
void TempArtLoaded(quint64 id, const QImage &image);
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
AlbumCoverLoaderOptions options_;
|
||||
|
||||
QString temp_file_pattern_;
|
||||
|
||||
std::unique_ptr<QTemporaryFile> temp_art_;
|
||||
std::unique_ptr<QTemporaryFile> temp_art_thumbnail_;
|
||||
quint64 id_;
|
||||
|
||||
Song last_song_;
|
||||
};
|
||||
|
||||
#endif // CURRENTARTLOADER_H
|
||||
216
src/covermanager/discogscoverprovider.cpp
Normal file
216
src/covermanager/discogscoverprovider.cpp
Normal file
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2012, Martin Björklund <mbj4668@gmail.com>
|
||||
* Copyright 2016, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
//#include <qjson/parser.h>
|
||||
#include <QJson/Parser>
|
||||
|
||||
#include <QVariant>
|
||||
#include <QNetworkReply>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QStringList>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include "discogscoverprovider.h"
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/utilities.h"
|
||||
|
||||
const char *DiscogsCoverProvider::kUrl = "https://api.discogs.com/database/search";
|
||||
|
||||
const char *DiscogsCoverProvider::kAccessKeyB64 = "dGh6ZnljUGJlZ1NEeXBuSFFxSVk=";
|
||||
const char *DiscogsCoverProvider::kSecretAccessKeyB64 = "ZkFIcmlaSER4aHhRSlF2U3d0bm5ZVmdxeXFLWUl0UXI=";
|
||||
|
||||
DiscogsCoverProvider::DiscogsCoverProvider(QObject *parent) : CoverProvider("Discogs", parent), network_(new NetworkAccessManager(this)) {}
|
||||
|
||||
bool DiscogsCoverProvider::StartSearch(const QString &artist, const QString &album, int id) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
DiscogsCoverSearchContext *ctx = new DiscogsCoverSearchContext;
|
||||
ctx->id = id;
|
||||
ctx->artist = artist;
|
||||
ctx->album = album;
|
||||
ctx->state = DiscogsCoverSearchContext::State_Init;
|
||||
pending_requests_.insert(id, ctx);
|
||||
SendSearchRequest(ctx);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void DiscogsCoverProvider::SendSearchRequest(DiscogsCoverSearchContext *ctx) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
typedef QPair<QString, QString> Arg;
|
||||
typedef QList<Arg> ArgList;
|
||||
|
||||
typedef QPair<QByteArray, QByteArray> EncodedArg;
|
||||
typedef QList<EncodedArg> EncodedArgList;
|
||||
|
||||
QString type;
|
||||
|
||||
switch (ctx->state) {
|
||||
case DiscogsCoverSearchContext::State_Init:
|
||||
type = "master";
|
||||
ctx->state = DiscogsCoverSearchContext::State_MastersRequested;
|
||||
break;
|
||||
case DiscogsCoverSearchContext::State_MastersRequested:
|
||||
type = "release";
|
||||
ctx->state = DiscogsCoverSearchContext::State_ReleasesRequested;
|
||||
break;
|
||||
default:
|
||||
EndSearch(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
ArgList args = ArgList()
|
||||
<< Arg("key", QByteArray::fromBase64(kAccessKeyB64))
|
||||
<< Arg("secret", QByteArray::fromBase64(kSecretAccessKeyB64));
|
||||
|
||||
if (!ctx->artist.isEmpty()) {
|
||||
args.append(Arg("artist", ctx->artist.toLower()));
|
||||
}
|
||||
if (!ctx->album.isEmpty()) {
|
||||
args.append(Arg("release_title", ctx->album.toLower()));
|
||||
}
|
||||
args.append(Arg("type", type));
|
||||
|
||||
QUrlQuery url_query;
|
||||
QUrl url(kUrl);
|
||||
QStringList query_items;
|
||||
|
||||
// Encode the arguments
|
||||
for (const Arg &arg : args) {
|
||||
EncodedArg encoded_arg(QUrl::toPercentEncoding(arg.first), QUrl::toPercentEncoding(arg.second));
|
||||
query_items << QString(encoded_arg.first + "=" + encoded_arg.second);
|
||||
url_query.addQueryItem(encoded_arg.first, encoded_arg.second);
|
||||
}
|
||||
|
||||
// Sign the request
|
||||
const QByteArray data_to_sign = QString("GET\n%1\n%2\n%3").arg(url.host(), url.path(), query_items.join("&")).toLatin1();
|
||||
//const QByteArray signature(Utilities::HmacSha256(kSecretAccessKey, data_to_sign));
|
||||
const QByteArray signature(Utilities::HmacSha256(QByteArray::fromBase64(kSecretAccessKeyB64), data_to_sign));
|
||||
|
||||
// Add the signature to the request
|
||||
url_query.addQueryItem("Signature", QUrl::toPercentEncoding(signature.toBase64()));
|
||||
|
||||
url.setQuery(url_query);
|
||||
QNetworkReply *reply = network_->get(QNetworkRequest(url));
|
||||
|
||||
NewClosure(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(QueryError(QNetworkReply::NetworkError, QNetworkReply*, int)), reply, ctx->id);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleSearchReply(QNetworkReply*, int)), reply, ctx->id);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void DiscogsCoverProvider::HandleSearchReply(QNetworkReply *reply, int id) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
//QString text(reply->readAll());
|
||||
//qLog(Debug) << text;
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
DiscogsCoverSearchContext *ctx;
|
||||
if (!pending_requests_.contains(id)) {
|
||||
// the request was cancelled while we were waiting for the reply
|
||||
qLog(Debug) << "Discogs: got reply for cancelled request" << id;
|
||||
return;
|
||||
}
|
||||
ctx = pending_requests_.value(id);
|
||||
|
||||
QJson::Parser parser;
|
||||
bool ok;
|
||||
bool found = false;
|
||||
QVariantMap reply_map = parser.parse(reply, &ok).toMap();
|
||||
|
||||
if (!ok || !reply_map.contains("results")) {
|
||||
// this is an error; either parse error or bad response from the server
|
||||
EndSearch(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantList results = reply_map["results"].toList();
|
||||
|
||||
for (const QVariant &result : results) {
|
||||
QVariantMap result_map = result.toMap();
|
||||
// In order to use less round-trips, we cheat here. Instead of
|
||||
// following the "resource_url", and then scan all images in the
|
||||
// resource, we go directly to the largest primary image by
|
||||
// constructing the primary image's url from the thmub's url.
|
||||
if (result_map.contains("thumb")) {
|
||||
CoverSearchResult cover_result;
|
||||
cover_result.image_url = QUrl(result_map["thumb"].toString().replace("R-90-", "R-"));
|
||||
if (result_map.contains("title")) {
|
||||
cover_result.description = result_map["title"].toString();
|
||||
}
|
||||
ctx->results.append(cover_result);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
EndSearch(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, no results
|
||||
switch (ctx->state) {
|
||||
case DiscogsCoverSearchContext::State_MastersRequested:
|
||||
// search again, this time for releases
|
||||
SendSearchRequest(ctx);
|
||||
break;
|
||||
default:
|
||||
EndSearch(ctx);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DiscogsCoverProvider::CancelSearch(int id) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__ << id;
|
||||
|
||||
delete pending_requests_.take(id);
|
||||
}
|
||||
|
||||
void DiscogsCoverProvider::QueryError(QNetworkReply::NetworkError error, QNetworkReply *reply, int id) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
}
|
||||
|
||||
void DiscogsCoverProvider::EndSearch(DiscogsCoverSearchContext *ctx) {
|
||||
|
||||
//qLog(Debug) << __PRETTY_FUNCTION__;
|
||||
|
||||
(void)pending_requests_.remove(ctx->id);
|
||||
emit SearchFinished(ctx->id, ctx->results);
|
||||
delete ctx;
|
||||
|
||||
}
|
||||
90
src/covermanager/discogscoverprovider.h
Normal file
90
src/covermanager/discogscoverprovider.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2012, Martin Björklund <mbj4668@gmail.com>
|
||||
* Copyright 2016, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DISCOGSCOVERPROVIDER_H
|
||||
#define DISCOGSCOVERPROVIDER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QXmlStreamReader>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "coverprovider.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
|
||||
// This struct represents a single search-for-cover request. It identifies and describes the request.
|
||||
struct DiscogsCoverSearchContext {
|
||||
enum State { State_Init, State_MastersRequested, State_ReleasesRequested };
|
||||
|
||||
// the unique request identifier
|
||||
int id;
|
||||
|
||||
// the search query
|
||||
QString artist;
|
||||
QString album;
|
||||
|
||||
State state;
|
||||
|
||||
CoverSearchResults results;
|
||||
};
|
||||
Q_DECLARE_METATYPE(DiscogsCoverSearchContext)
|
||||
|
||||
class DiscogsCoverProvider : public CoverProvider {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DiscogsCoverProvider(QObject *parent = nullptr);
|
||||
|
||||
static const char *kUrl;
|
||||
|
||||
static const char *kRequestTokenURL;
|
||||
static const char *kAuthorizeURL;
|
||||
static const char *kAccessTokenURL;
|
||||
|
||||
static const char *kAccessKey;
|
||||
static const char *kSecretAccessKey;
|
||||
|
||||
static const char *kAccessKeyB64;
|
||||
static const char *kSecretAccessKeyB64;
|
||||
|
||||
bool StartSearch(const QString &artist, const QString &album, int id);
|
||||
void CancelSearch(int id);
|
||||
|
||||
private slots:
|
||||
void QueryError(QNetworkReply::NetworkError error, QNetworkReply *reply, int id);
|
||||
void HandleSearchReply(QNetworkReply* reply, int id);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager *network_;
|
||||
QHash<int, DiscogsCoverSearchContext*> pending_requests_;
|
||||
|
||||
void SendSearchRequest(DiscogsCoverSearchContext *ctx);
|
||||
void ReadItem(QXmlStreamReader *reader, CoverSearchResults *results);
|
||||
void ReadLargeImage(QXmlStreamReader *reader, CoverSearchResults *results);
|
||||
void QueryFinished(QNetworkReply *reply, int id);
|
||||
void EndSearch(DiscogsCoverSearchContext *ctx);
|
||||
|
||||
};
|
||||
|
||||
#endif // DISCOGSCOVERPROVIDER_H
|
||||
|
||||
121
src/covermanager/lastfmcompat.cpp
Normal file
121
src/covermanager/lastfmcompat.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "lastfmcompat.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
namespace lastfm {
|
||||
namespace compat {
|
||||
|
||||
#ifdef HAVE_LIBLASTFM1
|
||||
|
||||
XmlQuery EmptyXmlQuery() { return XmlQuery(); }
|
||||
|
||||
bool ParseQuery(const QByteArray &data, XmlQuery *query, bool *connection_problems) {
|
||||
|
||||
const bool ret = query->parse(data);
|
||||
|
||||
if (connection_problems) {
|
||||
*connection_problems = !ret && query->parseError().enumValue() == lastfm::ws::MalformedResponse;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ParseUserList(QNetworkReply *reply, QList<User> *users) {
|
||||
|
||||
lastfm::XmlQuery lfm;
|
||||
if (!lfm.parse(reply->readAll())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*users = lastfm::UserList(lfm).users();
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
#else // HAVE_LIBLASTFM1
|
||||
|
||||
XmlQuery EmptyXmlQuery() {
|
||||
|
||||
QByteArray dummy;
|
||||
return XmlQuery(dummy);
|
||||
|
||||
}
|
||||
|
||||
bool ParseQuery(const QByteArray &data, XmlQuery *query, bool *connection_problems) {
|
||||
|
||||
try {
|
||||
*query = lastfm::XmlQuery(data);
|
||||
#ifdef Q_OS_WIN32
|
||||
if (lastfm::ws::last_parse_error != lastfm::ws::NoError) {
|
||||
return false;
|
||||
}
|
||||
#endif // Q_OS_WIN32
|
||||
}
|
||||
catch (lastfm::ws::ParseError e) {
|
||||
qLog(Error) << "Last.fm parse error: " << e.enumValue();
|
||||
if (connection_problems) {
|
||||
*connection_problems = e.enumValue() == lastfm::ws::MalformedResponse;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (std::runtime_error &e) {
|
||||
qLog(Error) << __FUNCTION__ << e.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (connection_problems) {
|
||||
*connection_problems = false;
|
||||
}
|
||||
|
||||
// Check for app errors.
|
||||
if (QDomElement(*query).attribute("status") == "failed") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool ParseUserList(QNetworkReply *reply, QList<User> *users) {
|
||||
|
||||
try {
|
||||
*users = lastfm::User::list(reply);
|
||||
#ifdef Q_OS_WIN32
|
||||
if (lastfm::ws::last_parse_error != lastfm::ws::NoError) {
|
||||
return false;
|
||||
}
|
||||
#endif // Q_OS_WIN32
|
||||
}
|
||||
catch (std::runtime_error &e) {
|
||||
qLog(Error) << __FUNCTION__ << e.what();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
#endif // HAVE_LIBLASTFM1
|
||||
}
|
||||
}
|
||||
|
||||
54
src/covermanager/lastfmcompat.h
Normal file
54
src/covermanager/lastfmcompat.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LASTFMCOMPAT_H
|
||||
#define LASTFMCOMPAT_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_LIBLASTFM1
|
||||
#include <lastfm5/misc.h>
|
||||
#include <lastfm5/User.h>
|
||||
#include <lastfm5/ws.h>
|
||||
#include <lastfm5/XmlQuery.h>
|
||||
#else
|
||||
#include <lastfm5/misc.h>
|
||||
#include <lastfm5/User>
|
||||
#include <lastfm5/ws.h>
|
||||
#include <lastfm5/XmlQuery>
|
||||
#endif
|
||||
|
||||
namespace lastfm {
|
||||
namespace compat {
|
||||
|
||||
lastfm::XmlQuery EmptyXmlQuery();
|
||||
bool ParseQuery(const QByteArray &data, lastfm::XmlQuery *query, bool *connection_problems = nullptr);
|
||||
bool ParseUserList(QNetworkReply *reply, QList<lastfm::User> *users);
|
||||
|
||||
#ifdef HAVE_LIBLASTFM1
|
||||
typedef lastfm::User AuthenticatedUser;
|
||||
#else
|
||||
typedef lastfm::AuthenticatedUser AuthenticatedUser;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endif // LASTFMCOMPAT_H
|
||||
|
||||
91
src/covermanager/lastfmcoverprovider.cpp
Normal file
91
src/covermanager/lastfmcoverprovider.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
Application name Strawberry Music Player
|
||||
API key 211990b4c96782c05d1536e7219eb56e
|
||||
Shared secret 80fd738f49596e9709b1bf9319c444a8
|
||||
Registered to jonaskvinge
|
||||
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "lastfmcoverprovider.h"
|
||||
#include "lastfmcompat.h"
|
||||
|
||||
#include "coverprovider.h"
|
||||
#include "albumcoverfetcher.h"
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "core/network.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
const char *LastFmCoverProvider::kApiKey = "211990b4c96782c05d1536e7219eb56e";
|
||||
const char *LastFmCoverProvider::kSecret = "80fd738f49596e9709b1bf9319c444a8";
|
||||
|
||||
LastFmCoverProvider::LastFmCoverProvider(QObject *parent) : CoverProvider("last.fm", parent), network_(new NetworkAccessManager(this)) {
|
||||
lastfm::ws::ApiKey = kApiKey;
|
||||
lastfm::ws::SharedSecret = kSecret;
|
||||
lastfm::setNetworkAccessManager(network_);
|
||||
}
|
||||
|
||||
bool LastFmCoverProvider::StartSearch(const QString &artist, const QString &album, int id) {
|
||||
|
||||
//qLog(Debug) << "LastFmCoverProvider artist:" << artist << "album:" << album;
|
||||
|
||||
QMap<QString, QString> params;
|
||||
params["method"] = "album.search";
|
||||
params["album"] = album + " " + artist;
|
||||
|
||||
QNetworkReply* reply = lastfm::ws::post(params);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(QueryFinished(QNetworkReply*, int)), reply, id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LastFmCoverProvider::QueryFinished(QNetworkReply *reply, int id) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
CoverSearchResults results;
|
||||
|
||||
lastfm::XmlQuery query(lastfm::compat::EmptyXmlQuery());
|
||||
if (lastfm::compat::ParseQuery(reply->readAll(), &query)) {
|
||||
// parse the list of search results
|
||||
QList<lastfm::XmlQuery> elements = query["results"]["albummatches"].children("album");
|
||||
|
||||
for (const lastfm::XmlQuery& element : elements) {
|
||||
CoverSearchResult result;
|
||||
result.description = element["artist"].text() + " - " + element["name"].text();
|
||||
result.image_url = QUrl(element["image size=extralarge"].text());
|
||||
results << result;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Drop through and emit an empty list of results.
|
||||
}
|
||||
|
||||
emit SearchFinished(id, results);
|
||||
}
|
||||
59
src/covermanager/lastfmcoverprovider.h
Normal file
59
src/covermanager/lastfmcoverprovider.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LASTFMCOVERPROVIDER_H
|
||||
#define LASTFMCOVERPROVIDER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
|
||||
#include "albumcoverfetcher.h"
|
||||
#include "coverprovider.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
// A built-in cover provider which fetches covers from last.fm.
|
||||
class LastFmCoverProvider : public CoverProvider {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LastFmCoverProvider(QObject *parent = nullptr);
|
||||
|
||||
bool StartSearch(const QString &artist, const QString &album, int id);
|
||||
|
||||
static const char *kApiKey;
|
||||
static const char *kSecret;
|
||||
|
||||
private slots:
|
||||
void QueryFinished(QNetworkReply *reply, int id);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager *network_;
|
||||
QMap <QNetworkReply *, int> pending_queries_;
|
||||
};
|
||||
|
||||
#endif // LASTFMCOVERPROVIDER_H
|
||||
|
||||
119
src/covermanager/musicbrainzcoverprovider.cpp
Normal file
119
src/covermanager/musicbrainzcoverprovider.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include <QXmlStreamReader>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include "core/closure.h"
|
||||
#include "core/network.h"
|
||||
|
||||
#include "musicbrainzcoverprovider.h"
|
||||
|
||||
using std::mem_fun;
|
||||
|
||||
namespace {
|
||||
|
||||
static const char *kReleaseSearchUrl = "https://musicbrainz.org/ws/2/release/";
|
||||
static const char *kAlbumCoverUrl = "https://coverartarchive.org/release/%1/front";
|
||||
} // namespace
|
||||
|
||||
MusicbrainzCoverProvider::MusicbrainzCoverProvider(QObject *parent): CoverProvider("MusicBrainz", parent), network_(new NetworkAccessManager(this)) {}
|
||||
|
||||
bool MusicbrainzCoverProvider::StartSearch(const QString &artist, const QString &album, int id) {
|
||||
|
||||
// Find release information.
|
||||
QUrl url(kReleaseSearchUrl);
|
||||
QString query = QString("release:\"%1\" AND artist:\"%2\"").arg(album.trimmed().replace('"', "\\\"")).arg(artist.trimmed().replace('"', "\\\""));
|
||||
QUrlQuery url_query;
|
||||
url_query.addQueryItem("query", query);
|
||||
url_query.addQueryItem("limit", "5");
|
||||
url.setQuery(url_query);
|
||||
QNetworkRequest request(url);
|
||||
|
||||
QNetworkReply *reply = network_->get(request);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(ReleaseSearchFinished(QNetworkReply*, int)), reply, id);
|
||||
|
||||
cover_names_[id] = QString("%1 - %2").arg(artist, album);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void MusicbrainzCoverProvider::ReleaseSearchFinished(QNetworkReply *reply, int id) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
QList<QString> releases;
|
||||
|
||||
QXmlStreamReader reader(reply);
|
||||
while (!reader.atEnd()) {
|
||||
QXmlStreamReader::TokenType type = reader.readNext();
|
||||
if (type == QXmlStreamReader::StartElement && reader.name() == "release") {
|
||||
QStringRef release_id = reader.attributes().value("id");
|
||||
if (!release_id.isEmpty()) {
|
||||
releases.append(release_id.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const QString& release_id : releases) {
|
||||
QUrl url(QString(kAlbumCoverUrl).arg(release_id));
|
||||
QNetworkReply *reply = network_->head(QNetworkRequest(url));
|
||||
image_checks_.insert(id, reply);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(ImageCheckFinished(int)), id);
|
||||
}
|
||||
}
|
||||
|
||||
void MusicbrainzCoverProvider::ImageCheckFinished(int id) {
|
||||
|
||||
QList<QNetworkReply*> replies = image_checks_.values(id);
|
||||
int finished_count = std::count_if(replies.constBegin(), replies.constEnd(), mem_fun(&QNetworkReply::isFinished));
|
||||
if (finished_count == replies.size()) {
|
||||
QString cover_name = cover_names_.take(id);
|
||||
QList<CoverSearchResult> results;
|
||||
for (QNetworkReply* reply : replies) {
|
||||
reply->deleteLater();
|
||||
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() < 400) {
|
||||
CoverSearchResult result;
|
||||
result.description = cover_name;
|
||||
result.image_url = reply->url();
|
||||
results.append(result);
|
||||
}
|
||||
}
|
||||
image_checks_.remove(id);
|
||||
emit SearchFinished(id, results);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MusicbrainzCoverProvider::CancelSearch(int id) {
|
||||
|
||||
QList<QNetworkReply*> replies = image_checks_.values(id);
|
||||
for (QNetworkReply* reply : replies) {
|
||||
reply->abort();
|
||||
reply->deleteLater();
|
||||
}
|
||||
image_checks_.remove(id);
|
||||
|
||||
}
|
||||
53
src/covermanager/musicbrainzcoverprovider.h
Normal file
53
src/covermanager/musicbrainzcoverprovider.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MUSICBRAINZCOVERPROVIDER_H
|
||||
#define MUSICBRAINZCOVERPROVIDER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QMultiMap>
|
||||
|
||||
#include "coverprovider.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
class MusicbrainzCoverProvider : public CoverProvider {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MusicbrainzCoverProvider(QObject *parent = nullptr);
|
||||
|
||||
// CoverProvider
|
||||
virtual bool StartSearch(const QString &artist, const QString &album, int id);
|
||||
virtual void CancelSearch(int id);
|
||||
|
||||
private slots:
|
||||
void ReleaseSearchFinished(QNetworkReply *reply, int id);
|
||||
void ImageCheckFinished(int id);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager *network_;
|
||||
QMultiMap<int, QNetworkReply *> image_checks_;
|
||||
QMap<int, QString> cover_names_;
|
||||
};
|
||||
|
||||
#endif // MUSICBRAINZCOVERPROVIDER_H
|
||||
|
||||
Reference in New Issue
Block a user