From 4e2ded9178263c16a9f5336b2c18d1df953c3b90 Mon Sep 17 00:00:00 2001 From: Andreas Date: Mon, 6 May 2013 21:22:05 +0200 Subject: [PATCH] - Finished album cover exporter from keirangtp's branch "export-covers". - Updated export dialog layout. - Added option to export downloaded and/or embedded covers. - Auto. cover fetcher and exporter is now cancelable. - Minor covermanager layout improvements. - Covermanager shows count of total and missing covers. Fixed issue 520. --- src/CMakeLists.txt | 7 + src/covers/albumcoverexporter.cpp | 78 +++++++++ src/covers/albumcoverexporter.h | 66 ++++++++ src/covers/coverexportrunnable.cpp | 202 ++++++++++++++++++++++++ src/covers/coverexportrunnable.h | 56 +++++++ src/ui/albumcoverexport.cpp | 95 +++++++++++ src/ui/albumcoverexport.h | 71 +++++++++ src/ui/albumcoverexport.ui | 244 +++++++++++++++++++++++++++++ src/ui/albumcovermanager.cpp | 107 ++++++++++++- src/ui/albumcovermanager.h | 14 +- src/ui/albumcovermanager.ui | 115 ++++++++++++-- 11 files changed, 1034 insertions(+), 21 deletions(-) create mode 100644 src/covers/albumcoverexporter.cpp create mode 100644 src/covers/albumcoverexporter.h create mode 100644 src/covers/coverexportrunnable.cpp create mode 100644 src/covers/coverexportrunnable.h create mode 100644 src/ui/albumcoverexport.cpp create mode 100644 src/ui/albumcoverexport.h create mode 100644 src/ui/albumcoverexport.ui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index db3a2f80b..7fc5e7511 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -114,10 +114,12 @@ set(SOURCES core/urlhandler.cpp core/utilities.cpp + covers/albumcoverexporter.cpp covers/albumcoverfetcher.cpp covers/albumcoverfetchersearch.cpp covers/albumcoverloader.cpp covers/amazoncoverprovider.cpp + covers/coverexportrunnable.cpp covers/coverprovider.cpp covers/coverproviders.cpp covers/coversearchstatistics.cpp @@ -321,6 +323,7 @@ set(SOURCES ui/about.cpp ui/addstreamdialog.cpp ui/albumcoverchoicecontroller.cpp + ui/albumcoverexport.cpp ui/albumcovermanager.cpp ui/albumcovermanagerlist.cpp ui/albumcoversearcher.cpp @@ -417,10 +420,12 @@ set(HEADERS core/taskmanager.h core/urlhandler.h + covers/albumcoverexporter.h covers/albumcoverfetcher.h covers/albumcoverfetchersearch.h covers/albumcoverloader.h covers/amazoncoverprovider.h + covers/coverexportrunnable.h covers/coverprovider.h covers/coverproviders.h covers/coversearchstatisticsdialog.h @@ -590,6 +595,7 @@ set(HEADERS ui/about.h ui/addstreamdialog.h ui/albumcoverchoicecontroller.h + ui/albumcoverexport.h ui/albumcovermanager.h ui/albumcovermanagerlist.h ui/albumcoversearcher.h @@ -711,6 +717,7 @@ set(UI ui/about.ui ui/addstreamdialog.ui + ui/albumcoverexport.ui ui/albumcovermanager.ui ui/albumcoversearcher.ui ui/appearancesettingspage.ui diff --git a/src/covers/albumcoverexporter.cpp b/src/covers/albumcoverexporter.cpp new file mode 100644 index 000000000..1e5255f01 --- /dev/null +++ b/src/covers/albumcoverexporter.cpp @@ -0,0 +1,78 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#include "albumcoverexporter.h" +#include "coverexportrunnable.h" +#include "core/song.h" + +#include +#include + +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(); +} diff --git a/src/covers/albumcoverexporter.h b/src/covers/albumcoverexporter.h new file mode 100644 index 000000000..53593bf2e --- /dev/null +++ b/src/covers/albumcoverexporter.h @@ -0,0 +1,66 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#ifndef ALBUMCOVEREXPORTER_H +#define ALBUMCOVEREXPORTER_H + +#include "coverexportrunnable.h" +#include "core/song.h" +#include "ui/albumcoverexport.h" + +#include +#include +#include + +class QThreadPool; + +class AlbumCoverExporter : public QObject { + Q_OBJECT + + public: + AlbumCoverExporter(QObject* parent = 0); + 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 requests_; + QThreadPool* thread_pool_; + + int exported_; + int skipped_; + int all_; +}; + +#endif // ALBUMCOVEREXPORTER_H diff --git a/src/covers/coverexportrunnable.cpp b/src/covers/coverexportrunnable.cpp new file mode 100644 index 000000000..600e816fc --- /dev/null +++ b/src/covers/coverexportrunnable.cpp @@ -0,0 +1,202 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#include "albumcoverexporter.h" +#include "core/song.h" +#include "core/tagreaderclient.h" + +#include +#include + +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(); +} diff --git a/src/covers/coverexportrunnable.h b/src/covers/coverexportrunnable.h new file mode 100644 index 000000000..89e70e5f1 --- /dev/null +++ b/src/covers/coverexportrunnable.h @@ -0,0 +1,56 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#ifndef COVEREXPORTRUNNABLE_H +#define COVEREXPORTRUNNABLE_H + +#include "core/song.h" +#include "ui/albumcoverexport.h" + +#include +#include + +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 diff --git a/src/ui/albumcoverexport.cpp b/src/ui/albumcoverexport.cpp new file mode 100644 index 000000000..7f5fdfb8e --- /dev/null +++ b/src/ui/albumcoverexport.cpp @@ -0,0 +1,95 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#include "albumcoverexport.h" +#include "ui_albumcoverexport.h" + +#include + +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 = "folder"; + } + 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); +} diff --git a/src/ui/albumcoverexport.h b/src/ui/albumcoverexport.h new file mode 100644 index 000000000..fc099e075 --- /dev/null +++ b/src/ui/albumcoverexport.h @@ -0,0 +1,71 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#ifndef ALBUMCOVEREXPORT_H +#define ALBUMCOVEREXPORT_H + +#include + +class Ui_AlbumCoverExport; + +// Controller for the "Export covers" dialog. +class AlbumCoverExport : public QDialog { + Q_OBJECT + + public: + AlbumCoverExport(QWidget* parent = 0); + ~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 diff --git a/src/ui/albumcoverexport.ui b/src/ui/albumcoverexport.ui new file mode 100644 index 000000000..771ce95e7 --- /dev/null +++ b/src/ui/albumcoverexport.ui @@ -0,0 +1,244 @@ + + + AlbumCoverExport + + + + 0 + 0 + 391 + 412 + + + + + 0 + 0 + + + + Export covers + + + + :/icon.png:/icon.png + + + + + + Output + + + + + + Enter a filename for exported covers (no extension): + + + + + + + folder + + + + + + + Export downloaded covers + + + true + + + + + + + Export embedded covers + + + false + + + + + + + + + + Existing covers + + + + + + Do not overwrite + + + + + + + Overwrite all + + + + + + + Overwrite smaller ones only + + + + + + + + + + Size + + + + + + + 50 + false + + + + Scale size + + + + + + + + + Size: + + + + + + + + 0 + 0 + + + + Qt::ImhDigitsOnly + + + + + + 4 + + + + + + + x + + + + + + + Qt::ImhDigitsOnly + + + + + + 4 + + + + + + + Pixel + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 200 + 20 + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + AlbumCoverExport + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AlbumCoverExport + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/ui/albumcovermanager.cpp b/src/ui/albumcovermanager.cpp index 33349e482..027d50c4e 100644 --- a/src/ui/albumcovermanager.cpp +++ b/src/ui/albumcovermanager.cpp @@ -22,6 +22,7 @@ #include "core/application.h" #include "core/logging.h" #include "core/utilities.h" +#include "covers/albumcoverexporter.h" #include "covers/albumcoverfetcher.h" #include "covers/albumcoverloader.h" #include "covers/coverproviders.h" @@ -32,8 +33,10 @@ #include "playlist/songmimedata.h" #include "widgets/forcescrollperpixel.h" #include "ui/albumcoverchoicecontroller.h" +#include "ui/albumcoverexport.h" #include +#include #include #include #include @@ -60,10 +63,13 @@ AlbumCoverManager::AlbumCoverManager(Application* app, album_cover_choice_controller_(new AlbumCoverChoiceController(this)), cover_fetcher_(new AlbumCoverFetcher(app_->cover_providers(), this, network)), cover_searcher_(NULL), + cover_export_(NULL), + cover_exporter_(new AlbumCoverExporter(this)), artist_icon_(IconLoader::Load("x-clementine-artist")), all_artists_icon_(IconLoader::Load("x-clementine-album")), context_menu_(new QMenu(this)), progress_bar_(new QProgressBar(this)), + abort_progress_(new QPushButton(this)), jobs_(0), library_backend_(library_backend) { @@ -72,6 +78,7 @@ AlbumCoverManager::AlbumCoverManager(Application* app, // 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-playback-start")); @@ -91,10 +98,15 @@ AlbumCoverManager::AlbumCoverManager(Application* app, no_cover_icon_ = QPixmap::fromImage(square_nocover); cover_searcher_ = new AlbumCoverSearcher(no_cover_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); @@ -102,7 +114,7 @@ AlbumCoverManager::AlbumCoverManager(Application* app, QShortcut* close = new QShortcut(QKeySequence::Close, this); connect(close, SIGNAL(activated()), SLOT(close())); - ResetFetchCoversButton(); + EnableCoversButtons(); } AlbumCoverManager::~AlbumCoverManager() { @@ -149,6 +161,9 @@ void AlbumCoverManager::Init() { 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); @@ -163,6 +178,7 @@ void AlbumCoverManager::Init() { 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())); @@ -223,11 +239,14 @@ void AlbumCoverManager::CancelRequests() { QSet::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(); - ResetFetchCoversButton(); + EnableCoversButtons(); } static bool CompareNocase(const QString& left, const QString& right) { @@ -240,7 +259,7 @@ static bool CompareAlbumNameNocase(const LibraryBackend::Album& left, } void AlbumCoverManager::Reset() { - ResetFetchCoversButton(); + EnableCoversButtons(); ui_->artists->clear(); new QListWidgetItem(all_artists_icon_, tr("All artists"), ui_->artists, All_Artists); @@ -257,8 +276,14 @@ void AlbumCoverManager::Reset() { } } -void AlbumCoverManager::ResetFetchCoversButton() { +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) { @@ -338,10 +363,24 @@ void AlbumCoverManager::UpdateFilter() { 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); - item->setHidden(ShouldHide(*item, filter, hide)); + bool should_hide = ShouldHide(*item, filter, hide); + item->setHidden(should_hide); + + if (!should_hide) { + total_count++; + if (item->icon().cacheKey() == no_cover_icon_.cacheKey()) { + without_cover++; + } + } } + + ui_->total_albums->setText(QString::number(total_count)); + ui_->without_cover->setText(QString::number(without_cover)); } bool AlbumCoverManager::ShouldHide( @@ -387,6 +426,7 @@ void AlbumCoverManager::FetchAlbumCovers() { progress_bar_->setMaximum(jobs_); progress_bar_->show(); + abort_progress_->show(); fetch_statistics_ = CoverSearchStatistics(); UpdateStatusText(); } @@ -402,7 +442,7 @@ void AlbumCoverManager::AlbumCoverFetched(quint64 id, const QImage& image, } if (cover_fetching_tasks_.isEmpty()) { - ResetFetchCoversButton(); + EnableCoversButtons(); } fetch_statistics_ += statistics; @@ -427,6 +467,7 @@ void AlbumCoverManager::UpdateStatusText() { 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); @@ -516,6 +557,7 @@ void AlbumCoverManager::FetchSingleCover() { progress_bar_->setMaximum(jobs_); progress_bar_->show(); + abort_progress_->show(); UpdateStatusText(); } @@ -701,3 +743,56 @@ void AlbumCoverManager::SaveAndSetCover(QListWidgetItem *item, const QImage &ima 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 ; ialbums->count() ; ++i) { + QListWidgetItem* item = ui_->albums->item(i); + + // skip hidden and coverless albums + if (item->isHidden() || item->icon().cacheKey() == no_cover_icon_.cacheKey()) { + continue; + } + + cover_exporter_->AddExportRequest(ItemAsSong(item)); + } + + progress_bar_->setMaximum(cover_exporter_->request_count()); + progress_bar_->show(); + abort_progress_->show(); + + cover_exporter_->StartExporting(); +} + +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(); + } +} diff --git a/src/ui/albumcovermanager.h b/src/ui/albumcovermanager.h index e0737d729..30d1287dc 100644 --- a/src/ui/albumcovermanager.h +++ b/src/ui/albumcovermanager.h @@ -29,6 +29,8 @@ #include "covers/coversearchstatistics.h" class AlbumCoverChoiceController; +class AlbumCoverExport; +class AlbumCoverExporter; class AlbumCoverFetcher; class AlbumCoverSearcher; class Application; @@ -39,6 +41,7 @@ class Ui_CoverManager; class QListWidgetItem; class QMenu; class QNetworkAccessManager; +class QPushButton; class QProgressBar; class AlbumCoverManager : public QMainWindow { @@ -58,7 +61,8 @@ class AlbumCoverManager : public QMainWindow { void Reset(); void Init(); - void ResetFetchCoversButton(); + void EnableCoversButtons(); + void DisableCoversButtons(); SongList GetSongsInAlbum(const QModelIndex& index) const; SongList GetSongsInAlbums(const QModelIndexList& indexes) const; @@ -79,8 +83,10 @@ class AlbumCoverManager : public QMainWindow { 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(); @@ -98,6 +104,7 @@ class AlbumCoverManager : public QMainWindow { void LoadSelectedToPlaylist(); void UpdateCoverInList(QListWidgetItem* item, const QString& cover); + void UpdateExportStatus(int exported, int bad, int count); private: enum ArtistItemType { @@ -133,8 +140,6 @@ class AlbumCoverManager : public QMainWindow { Song ItemAsSong(QListWidgetItem* item); - void CancelRequests(); - void UpdateStatusText(); bool ShouldHide(const QListWidgetItem& item, const QString& filter, HideCovers hide) const; void SaveAndSetCover(QListWidgetItem* item, const QImage& image); @@ -157,6 +162,8 @@ class AlbumCoverManager : public QMainWindow { CoverSearchStatistics fetch_statistics_; AlbumCoverSearcher* cover_searcher_; + AlbumCoverExport* cover_export_; + AlbumCoverExporter* cover_exporter_; QIcon artist_icon_; QIcon all_artists_icon_; @@ -167,6 +174,7 @@ class AlbumCoverManager : public QMainWindow { QList context_menu_items_; QProgressBar* progress_bar_; + QPushButton* abort_progress_; int jobs_; LibraryBackend* library_backend_; diff --git a/src/ui/albumcovermanager.ui b/src/ui/albumcovermanager.ui index 8999df779..d95b7aff2 100644 --- a/src/ui/albumcovermanager.ui +++ b/src/ui/albumcovermanager.ui @@ -53,12 +53,15 @@ 0 - + + 6 + + 0 - + Enter search terms here @@ -86,18 +89,109 @@ + + + + + + 6 + - - - Fetch Missing Covers + + + 10 - + + 10 + + + + + Total albums: + + + + + + + Without cover: + + + + + + + + + 10 + + + 10 + + + + + Qt::RightToLeft + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + - 16 - 16 + 40 + 20 - + + + + + + 0 + + + + + Fetch Missing Covers + + + + 16 + 16 + + + + + + + + Export Covers + + + + @@ -181,9 +275,6 @@ artists - filter - view - fetch albums