- 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.
This commit is contained in:
Andreas 2013-05-06 21:22:05 +02:00
parent f7c46c724a
commit 4e2ded9178
11 changed files with 1034 additions and 21 deletions

View File

@ -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

View File

@ -0,0 +1,78 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 <http://www.gnu.org/licenses/>.
*/
#include "albumcoverexporter.h"
#include "coverexportrunnable.h"
#include "core/song.h"
#include <QFile>
#include <QThreadPool>
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();
}

View File

@ -0,0 +1,66 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 <http://www.gnu.org/licenses/>.
*/
#ifndef ALBUMCOVEREXPORTER_H
#define ALBUMCOVEREXPORTER_H
#include "coverexportrunnable.h"
#include "core/song.h"
#include "ui/albumcoverexport.h"
#include <QObject>
#include <QQueue>
#include <QTimer>
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<CoverExportRunnable*> requests_;
QThreadPool* thread_pool_;
int exported_;
int skipped_;
int all_;
};
#endif // ALBUMCOVEREXPORTER_H

View File

@ -0,0 +1,202 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 <http://www.gnu.org/licenses/>.
*/
#include "albumcoverexporter.h"
#include "core/song.h"
#include "core/tagreaderclient.h"
#include <QFile>
#include <QUrl>
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();
}

View File

@ -0,0 +1,56 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 <http://www.gnu.org/licenses/>.
*/
#ifndef COVEREXPORTRUNNABLE_H
#define COVEREXPORTRUNNABLE_H
#include "core/song.h"
#include "ui/albumcoverexport.h"
#include <QObject>
#include <QRunnable>
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

View File

@ -0,0 +1,95 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 <http://www.gnu.org/licenses/>.
*/
#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 = "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);
}

71
src/ui/albumcoverexport.h Normal file
View File

@ -0,0 +1,71 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
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 <http://www.gnu.org/licenses/>.
*/
#ifndef ALBUMCOVEREXPORT_H
#define ALBUMCOVEREXPORT_H
#include <QDialog>
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

244
src/ui/albumcoverexport.ui Normal file
View File

@ -0,0 +1,244 @@
<?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>391</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>:/icon.png</normaloff>:/icon.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">
<property name="placeholderText">
<string notr="true">folder</string>
</property>
</widget>
</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>x</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>

View File

@ -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 <QActionGroup>
#include <QPushButton>
#include <QContextMenuEvent>
#include <QEvent>
#include <QFileDialog>
@ -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<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();
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 ; i<ui_->albums->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();
}
}

View File

@ -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<QListWidgetItem*> context_menu_items_;
QProgressBar* progress_bar_;
QPushButton* abort_progress_;
int jobs_;
LibraryBackend* library_backend_;

View File

@ -53,12 +53,15 @@
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<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">
<widget class="QSearchField" name="filter" native="true">
<property name="placeholderText" stdset="0">
<string>Enter search terms here</string>
</property>
@ -86,18 +89,109 @@
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<item>
<widget class="QPushButton" name="fetch">
<property name="text">
<string>Fetch Missing Covers</string>
<layout class="QVBoxLayout" name="verticalLayout_4" stretch="0,0">
<property name="topMargin">
<number>10</number>
</property>
<property name="iconSize">
<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>16</width>
<height>16</height>
<width>40</width>
<height>20</height>
</size>
</property>
</widget>
</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>
@ -181,9 +275,6 @@
</customwidgets>
<tabstops>
<tabstop>artists</tabstop>
<tabstop>filter</tabstop>
<tabstop>view</tabstop>
<tabstop>fetch</tabstop>
<tabstop>albums</tabstop>
</tabstops>
<resources>