task: massively simplified task system

Made various changes and fixes to the task system:

- Tasks are built around QtConcurrent rather than QThread. Reduces
  code complexity significantly.
- Task error reporting is now streamlined in both TaskManager and
  TaskDialog.
- Moved ProjectImport/Save/LoadManager to the app/task folder
This commit is contained in:
itsmattkc
2020-05-19 02:44:32 +10:00
parent c4b0a53174
commit b9cd83eff1
49 changed files with 864 additions and 1332 deletions
-1
View File
@@ -26,7 +26,6 @@ CLITaskDialog::CLITaskDialog(Task *task, QObject* parent) :
CLIProgressDialog(task->GetTitle(), parent)
{
// FIXME: Still developing this, don't try to use
task->Start();
}
OLIVE_NAMESPACE_EXIT
+45 -17
View File
@@ -45,13 +45,14 @@
#include "panel/panelmanager.h"
#include "panel/project/project.h"
#include "panel/viewer/viewer.h"
#include "project/projectimportmanager.h"
#include "project/projectloadmanager.h"
#include "project/projectsavemanager.h"
#include "render/backend/opengl/opengltexturecache.h"
#include "render/colormanager.h"
#include "render/diskmanager.h"
#include "render/pixelformat.h"
#include "task/cache/cache.h"
#include "task/project/import/import.h"
#include "task/project/load/load.h"
#include "task/project/save/save.h"
#include "task/taskmanager.h"
#include "ui/style/style.h"
#include "undo/undostack.h"
@@ -239,7 +240,7 @@ void Core::ImportFiles(const QStringList &urls, ProjectViewModel* model, Folder*
return;
}
ProjectImportManager* pim = new ProjectImportManager(model, parent, urls);
ProjectImportTask* pim = new ProjectImportTask(model, parent, urls);
if (!pim->GetFileCount()) {
// No files to import
@@ -247,9 +248,10 @@ void Core::ImportFiles(const QStringList &urls, ProjectViewModel* model, Folder*
return;
}
connect(pim, &ProjectImportManager::ImportComplete, this, &Core::ImportTaskComplete, Qt::BlockingQueuedConnection);
TaskDialog* task_dialog = new TaskDialog(pim, tr("Importing..."), main_window());
connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::ImportTaskComplete);
task_dialog->open();
}
@@ -482,8 +484,19 @@ void Core::AddOpenProject(ProjectPtr p)
emit ProjectOpened(p.get());
}
void Core::ImportTaskComplete(QUndoCommand *command)
void Core::AddOpenProjectFromTask(Task *task)
{
QList<ProjectPtr> projects = static_cast<ProjectLoadTask*>(task)->GetLoadedProjects();
foreach (ProjectPtr p, projects) {
AddOpenProject(p);
}
}
void Core::ImportTaskComplete(Task* task)
{
QUndoCommand *command = static_cast<ProjectImportTask*>(task)->GetCommand();
undo_stack_.pushIfHasChildren(command);
}
@@ -601,11 +614,11 @@ void Core::StartGUI(bool full_screen)
void Core::SaveProjectInternal(ProjectPtr project)
{
// Create save manager
ProjectSaveManager* psm = new ProjectSaveManager(project);
ProjectSaveTask* psm = new ProjectSaveTask(project);
TaskDialog* task_dialog = new TaskDialog(psm, tr("Save Project"), main_window_);
connect(psm, &ProjectSaveManager::ProjectSaveSucceeded, this, &Core::ProjectSaveSucceeded);
connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::ProjectSaveSucceeded);
TaskDialog* task_dialog = new TaskDialog(psm, tr("Save Project"), main_window());
task_dialog->open();
}
@@ -619,8 +632,10 @@ void Core::SaveAutorecovery()
}
}
void Core::ProjectSaveSucceeded(ProjectPtr p)
void Core::ProjectSaveSucceeded(Task* task)
{
ProjectPtr p = static_cast<ProjectSaveTask*>(task)->GetProject();
PushRecentlyOpenedProject(p->filename());
p->set_modified(false);
@@ -885,20 +900,19 @@ void Core::OpenProjectInternal(const QString &filename)
}
}
ProjectLoadManager* plm = new ProjectLoadManager(filename);
ProjectLoadTask* plm = new ProjectLoadTask(filename);
if (gui_active_) {
// We use a blocking queued connection here because we want to ensure we have this project instance before the
// ProjectLoadManager is destroyed
connect(plm, &ProjectLoadManager::ProjectLoaded, this, &Core::AddOpenProject, Qt::BlockingQueuedConnection);
TaskDialog* task_dialog = new TaskDialog(plm, tr("Load Project"), main_window());
connect(task_dialog, &TaskDialog::TaskSucceeded, this, &Core::AddOpenProjectFromTask);
task_dialog->open();
} else {
connect(plm, &ProjectLoadManager::ProjectLoaded, this, &Core::AddOpenProject);
//connect(plm, &ProjectLoadManager::ProjectLoaded, this, &Core::AddOpenProject);
CLITaskDialog task_dialog(plm);
@@ -1100,6 +1114,20 @@ bool Core::CloseAllProjects(bool auto_open_new)
return true;
}
void Core::CacheActiveSequence(bool in_out_only)
{
TimeBasedPanel* p = PanelManager::instance()->MostRecentlyFocused<TimeBasedPanel>();
if (p && p->GetConnectedViewer()) {
// FIXME: Hardcoded divider...
// FIXME: Consider preventing caching the footage viewer
CacheTask* task = new CacheTask(p->GetConnectedViewer(), 2, in_out_only);
TaskDialog* dialog = new TaskDialog(task, tr("Caching Sequence"), main_window_);
dialog->open();
}
}
bool Core::CloseAllProjects()
{
return CloseAllProjects(true);
+9 -2
View File
@@ -229,6 +229,11 @@ public:
*/
bool CloseAllProjects(bool auto_open_new);
/**
* @brief Runs a modal cache task on the currently active sequence
*/
void CacheActiveSequence(bool in_out_only);
public slots:
/**
* @brief Starts an open file dialog to load a project from file
@@ -477,14 +482,16 @@ private:
private slots:
void SaveAutorecovery();
void ProjectSaveSucceeded(OLIVE_NAMESPACE::ProjectPtr p);
void ProjectSaveSucceeded(Task *task);
/**
* @brief Adds a project to the "open projects" list
*/
void AddOpenProject(OLIVE_NAMESPACE::ProjectPtr p);
void ImportTaskComplete(QUndoCommand* command);
void AddOpenProjectFromTask(Task* task);
void ImportTaskComplete(Task *task);
bool ConfirmImageSequence(const QString &filename);
+18 -186
View File
@@ -31,21 +31,18 @@
#include <QStandardPaths>
#include "core.h"
#include "dialog/task/task.h"
#include "project/item/sequence/sequence.h"
#include "project/project.h"
#include "render/backend/exportparams.h"
#include "render/pixelformat.h"
#include "ui/icons/icons.h"
#include "window/mainwindow/mainwindow.h"
OLIVE_NAMESPACE_ENTER
ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
QDialog(parent),
viewer_node_(viewer_node),
previously_selected_format_(0),
exporter_(nullptr),
cancelled_(false)
previously_selected_format_(0)
{
QHBoxLayout* layout = new QHBoxLayout(this);
@@ -53,11 +50,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
splitter->setChildrenCollapsible(false);
layout->addWidget(splitter);
QWidget* outer_preferences_area = new QWidget();
QVBoxLayout* outer_preferences_layout = new QVBoxLayout(outer_preferences_area);
preferences_area_ = new QWidget();
outer_preferences_layout->addWidget(preferences_area_);
QGridLayout* preferences_layout = new QGridLayout(preferences_area_);
preferences_layout->setMargin(0);
@@ -142,44 +135,17 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
preferences_tabs->addTab(audio_area, tr("Audio"));
preferences_layout->addWidget(preferences_tabs, row, 0, 1, 4);
QHBoxLayout* progress_bar_layout = new QHBoxLayout();
progress_bar_layout->setMargin(0);
outer_preferences_layout->addLayout(progress_bar_layout);
progress_bar_ = new QProgressBar();
progress_bar_->setEnabled(false);
progress_bar_->setValue(0);
progress_bar_layout->addWidget(progress_bar_);
export_cancel_btn_ = new QPushButton();
export_cancel_btn_->setIcon(icon::Error);
export_cancel_btn_->setEnabled(false);
connect(export_cancel_btn_, &QPushButton::clicked, this, &ExportDialog::CancelExport);
progress_bar_layout->addWidget(export_cancel_btn_);
QHBoxLayout* time_label_layout = new QHBoxLayout();
time_label_layout->setMargin(0);
outer_preferences_layout->addLayout(time_label_layout);
elapsed_label_ = new QLabel();
elapsed_label_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
time_label_layout->addWidget(elapsed_label_);
remaining_label_ = new QLabel();
remaining_label_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
time_label_layout->addWidget(remaining_label_);
UpdateTimeLabels();
row++;
buttons_ = new QDialogButtonBox();
buttons_->setCenterButtons(true);
buttons_->addButton(tr("Export"), QDialogButtonBox::AcceptRole);
buttons_->addButton(QDialogButtonBox::Cancel);
connect(buttons_, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttons_, SIGNAL(rejected()), this, SLOT(reject()));
outer_preferences_layout->addWidget(buttons_);
connect(buttons_, &QDialogButtonBox::accepted, this, &ExportDialog::accept);
connect(buttons_, &QDialogButtonBox::rejected, this, &ExportDialog::reject);
preferences_layout->addWidget(buttons_, row, 0, 1, 4);
splitter->addWidget(outer_preferences_area);
splitter->addWidget(preferences_area_);
QWidget* preview_area = new QWidget();
QVBoxLayout* preview_layout = new QVBoxLayout(preview_area);
@@ -190,7 +156,6 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
splitter->addWidget(preview_area);
// Set default filename
// FIXME: Use Sequence name and project filename to construct this
SetDefaultFilename();
// Set up available export formats
@@ -227,9 +192,6 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
preview_viewer_->ConnectViewerNode(viewer_node_);
preview_viewer_->SetColorMenuEnabled(false);
preview_viewer_->SetColorTransform(video_tab_->CurrentOCIOColorSpace());
progress_timer_.setInterval(1000);
connect(&progress_timer_, &QTimer::timeout, this, &ExportDialog::UpdateTimeLabels);
}
void ExportDialog::accept()
@@ -314,46 +276,13 @@ void ExportDialog::accept()
}
}
// Set up export parameters
exporter_ = new Exporter(viewer_node_, color_manager_, GenerateParams());
connect(exporter_, &Exporter::ExportEnded, this, &ExportDialog::ExporterIsDone);
connect(exporter_, &Exporter::ProgressChanged, this, &ExportDialog::ProgressUpdated);
#ifdef Q_OS_WINDOWS
Core::instance()->main_window()->SetTaskbarButtonState(TBPF_NORMAL);
#endif
export_start_ = QDateTime::currentMSecsSinceEpoch();
flt_progress_ = 0;
progress_timer_.start();
QMetaObject::invokeMethod(exporter_, "StartExporting", Qt::QueuedConnection);
SetUIElementsEnabled(false);
ExportTask* task = new ExportTask(viewer_node_, color_manager_, GenerateParams());
TaskDialog* td = new TaskDialog(task, tr("Export"), this);
td->open();
}
void ExportDialog::closeEvent(QCloseEvent *e)
{
if (exporter_) {
QMessageBox b(this);
b.setIcon(QMessageBox::Question);
b.setWindowModality(Qt::WindowModal);
b.setWindowTitle(tr("Still Exporting"));
b.setText(tr("This sequence is still being exported. Do you wish to cancel it?"));
b.addButton(QMessageBox::Yes);
b.addButton(QMessageBox::No);
if (b.exec() == QMessageBox::Yes) {
CancelExport();
} else {
e->ignore();
return;
}
}
preview_viewer_->ConnectViewerNode(nullptr);
QDialog::closeEvent(e);
@@ -548,17 +477,6 @@ void ExportDialog::SetDefaultFilename()
filename_edit_->setText(file_location);
}
void ExportDialog::SetUIElementsEnabled(bool enabled)
{
preferences_area_->setEnabled(enabled);
buttons_->setEnabled(enabled);
progress_bar_->setEnabled(!enabled);
export_cancel_btn_->setEnabled(!enabled);
elapsed_label_->setEnabled(!enabled);
remaining_label_->setEnabled(!enabled);
}
int ExportDialog::AlignEvenNumber(double d)
{
return qCeil(d * 0.5) * 2;
@@ -607,40 +525,6 @@ ExportParams ExportDialog::GenerateParams() const
return params;
}
void ExportDialog::UpdateTimeLabels()
{
qint64 elapsed, remaining;
if (exporter_) {
elapsed = QDateTime::currentMSecsSinceEpoch() - export_start_;
if (flt_progress_ > 0) {
remaining = qRound64((1.0 - flt_progress_) * static_cast<double>(elapsed) / flt_progress_);
} else {
remaining = 0;
}
} else {
elapsed = 0;
remaining = 0;
}
elapsed_label_->setText(tr("Elapsed: %1").arg(TimeToString(elapsed)));
remaining_label_->setText(tr("Remaining: %1").arg(TimeToString(remaining)));
}
void ExportDialog::ProgressUpdated(double p)
{
int i_prog = qRound(100.0 * p);
progress_bar_->setValue(i_prog);
#ifdef Q_OS_WINDOWS
UpdateTaskbarProgress(i_prog);
#endif
flt_progress_ = p;
}
QString ExportDialog::TimeToString(int64_t ms)
{
int64_t total_seconds = ms / 1000;
@@ -659,66 +543,14 @@ void ExportDialog::UpdateViewerDimensions()
preview_viewer_->SetOverrideSize(static_cast<int>(video_tab_->width_slider()->GetValue()),
static_cast<int>(video_tab_->height_slider()->GetValue()));
preview_viewer_->SetMatrix(Exporter::GenerateMatrix(static_cast<ExportParams::VideoScalingMethod>(video_tab_->scaling_method_combobox()->currentData().toInt()),
viewer_node_->video_params().width(),
viewer_node_->video_params().height(),
static_cast<int>(video_tab_->width_slider()->GetValue()),
static_cast<int>(video_tab_->height_slider()->GetValue())));
QMatrix4x4 transform =
ExportParams::GenerateMatrix(static_cast<ExportParams::VideoScalingMethod>(video_tab_->scaling_method_combobox()->currentData().toInt()),
viewer_node_->video_params().width(),
viewer_node_->video_params().height(),
static_cast<int>(video_tab_->width_slider()->GetValue()),
static_cast<int>(video_tab_->height_slider()->GetValue()));
preview_viewer_->SetMatrix(transform);
}
void ExportDialog::ExporterIsDone()
{
progress_timer_.stop();
if (exporter_->GetExportStatus()) {
QMessageBox b(this);
b.setIcon(QMessageBox::Information);
b.setWindowModality(Qt::WindowModal);
b.setWindowTitle(tr("Export Status"));
b.setText(tr("Export completed successfully."));
b.addButton(QMessageBox::Ok);
b.exec();
QDialog::accept();
} else {
if (!cancelled_) {
#ifdef Q_OS_WINDOWS
Core::instance()->main_window()->SetTaskbarButtonState(TBPF_ERROR);
#endif
QMessageBox b(this);
b.setIcon(QMessageBox::Critical);
b.setWindowModality(Qt::WindowModal);
b.setWindowTitle(tr("Export Status"));
b.setText(tr("Export failed: %1").arg(exporter_->GetExportError()));
b.addButton(QMessageBox::Ok);
b.exec();
}
SetUIElementsEnabled(true);
}
exporter_ = nullptr;
cancelled_ = false;
#ifdef Q_OS_WINDOWS
Core::instance()->main_window()->SetTaskbarButtonState(TBPF_NOPROGRESS);
#endif
}
void ExportDialog::CancelExport()
{
if (exporter_) {
cancelled_ = true;
exporter_->Cancel();
}
}
#ifdef Q_OS_WINDOWS
void ExportDialog::UpdateTaskbarProgress(int progress)
{
Core::instance()->main_window()->SetTaskbarButtonProgress(progress, 100);
}
#endif
OLIVE_NAMESPACE_EXIT
+1 -28
View File
@@ -31,7 +31,7 @@
#include "exportcodec.h"
#include "exportformat.h"
#include "exportvideotab.h"
#include "render/backend/exporter.h"
#include "task/export/export.h"
#include "widget/viewer/viewer.h"
OLIVE_NAMESPACE_ENTER
@@ -53,8 +53,6 @@ private:
void LoadPresets();
void SetDefaultFilename();
void SetUIElementsEnabled(bool enabled);
static int AlignEvenNumber(double d);
ExportParams GenerateParams() const;
@@ -75,8 +73,6 @@ private:
QLineEdit* filename_edit_;
QComboBox* format_combobox_;
Exporter* exporter_;
ExportVideoTab* video_tab_;
ExportAudioTab* audio_tab_;
@@ -84,19 +80,8 @@ private:
ColorManager* color_manager_;
QProgressBar* progress_bar_;
QTimer progress_timer_;
QLabel* elapsed_label_;
QLabel* remaining_label_;
qint64 export_start_;
double flt_progress_;
QWidget* preferences_area_;
QDialogButtonBox* buttons_;
QPushButton* export_cancel_btn_;
bool cancelled_;
enum Format {
kFormatDNxHD,
@@ -138,18 +123,6 @@ private slots:
void UpdateViewerDimensions();
void ExporterIsDone();
void CancelExport();
void UpdateTimeLabels();
void ProgressUpdated(double p);
#ifdef Q_OS_WINDOWS
void UpdateTaskbarProgress(int progress);
#endif
};
OLIVE_NAMESPACE_EXIT
@@ -3,7 +3,6 @@
#include <QDialog>
#include "render/backend/exportparams.h"
#include "widget/slider/integerslider.h"
OLIVE_NAMESPACE_ENTER
+1 -1
View File
@@ -28,8 +28,8 @@
#include "core.h"
#include "exportadvancedvideodialog.h"
#include "render/backend/exportparams.h"
#include "render/colormanager.h"
#include "task/export/exportparams.h"
OLIVE_NAMESPACE_ENTER
+40
View File
@@ -21,9 +21,12 @@
#include "progress.h"
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
#include <QVBoxLayout>
#include "window/mainwindow/mainwindow.h"
OLIVE_NAMESPACE_ENTER
ProgressDialog::ProgressDialog(const QString& message, const QString& title, QWidget *parent) :
@@ -56,9 +59,46 @@ ProgressDialog::ProgressDialog(const QString& message, const QString& title, QWi
cancel_layout->addStretch();
}
void ProgressDialog::showEvent(QShowEvent *e)
{
QDialog::showEvent(e);
#ifdef Q_OS_WINDOWS
Core::instance()->main_window()->SetTaskbarButtonState(TBPF_NORMAL);
#endif
}
void ProgressDialog::closeEvent(QCloseEvent *e)
{
QDialog::closeEvent(e);
#ifdef Q_OS_WINDOWS
Core::instance()->main_window()->SetTaskbarButtonState(TBPF_NOPROGRESS);
#endif
}
void ProgressDialog::SetProgress(int value)
{
bar_->setValue(value);
#ifdef Q_OS_WINDOWS
Core::instance()->main_window()->SetTaskbarButtonProgress(value, 100);
#endif
}
void ProgressDialog::ShowErrorMessage(const QString &title, const QString &message)
{
#ifdef Q_OS_WINDOWS
Core::instance()->main_window()->SetTaskbarButtonState(TBPF_ERROR);
#endif
QMessageBox b(this);
b.setIcon(QMessageBox::Critical);
b.setWindowModality(Qt::WindowModal);
b.setWindowTitle(title);
b.setText(message);
b.addButton(QMessageBox::Ok);
b.exec();
}
OLIVE_NAMESPACE_EXIT
+8
View File
@@ -34,12 +34,20 @@ class ProgressDialog : public QDialog
public:
ProgressDialog(const QString &message, const QString &title, QWidget* parent = nullptr);
protected:
virtual void showEvent(QShowEvent* e) override;
virtual void closeEvent(QCloseEvent *) override;
public slots:
void SetProgress(int value);
signals:
void Cancelled();
protected:
void ShowErrorMessage(const QString& title, const QString& message);
private:
QProgressBar* bar_;
+32 -39
View File
@@ -20,76 +20,69 @@
#include "task.h"
#include <QMessageBox>
#include <QThread>
#include <QtConcurrent/QtConcurrent>
OLIVE_NAMESPACE_ENTER
TaskDialog::TaskDialog(Task* task, const QString& title, QWidget *parent) :
ProgressDialog(task->GetTitle(), title, parent),
task_(task),
task_failed_(false)
destroy_on_close_(true)
{
thread_ = new QThread();
// Clear task when this dialog is destroyed
task_->setParent(this);
// Connect the save manager progress signal to the progress bar update on the dialog
connect(task_, &Task::ProgressChanged, this, &TaskDialog::SetProgress, Qt::QueuedConnection);
// Connect error reporting
connect(task_, &Task::Failed, this, &TaskDialog::TaskFailed, Qt::QueuedConnection);
// Connect cancel signal (must be a direct connection or it'll be queued after the task has already finished)
// Connect cancel signal (must be a direct connection or it'll be queued after the task has
// already finished)
connect(this, &TaskDialog::Cancelled, task_, &Task::Cancel, Qt::DirectConnection);
// Connect cleanup functions (ensure everything new'd in this function is deleteLater'd)
connect(task_, &Task::Finished, this, &TaskDialog::close, Qt::QueuedConnection);
// When task is finished, signal thread to quit
connect(task_, &Task::Finished, thread_, &QThread::quit, Qt::QueuedConnection);
// When thread has quit, delete both task and thread
connect(thread_, &QThread::finished, task_, &Task::deleteLater, Qt::QueuedConnection);
connect(thread_, &QThread::finished, thread_, &QThread::deleteLater, Qt::QueuedConnection);
}
void TaskDialog::showEvent(QShowEvent *e)
{
QDialog::showEvent(e);
ProgressDialog::showEvent(e);
// Create a separate thread to run this task in
thread_->start();
// Create watcher for when the task finishes
QFutureWatcher<bool>* task_watcher = new QFutureWatcher<bool>();
// Move the task to this thread
task_->moveToThread(thread_);
// Listen for when the task finishes
connect(task_watcher, &QFutureWatcher<bool>::finished,
this, &TaskDialog::TaskFinished, Qt::QueuedConnection);
// Start the task
QMetaObject::invokeMethod(task_, "Start", Qt::QueuedConnection);
// Run task in another thread with QtConcurrent
task_watcher->setFuture(QtConcurrent::run(task_, &Task::Run));
}
void TaskDialog::closeEvent(QCloseEvent *e)
{
// Show error if the task failed
if (!task_->IsCancelled() && task_failed_) {
QMessageBox::critical(this,
tr("Error"),
task_error_,
QMessageBox::Ok);
}
// Cancel task if it is running
task_->Cancel();
// Standard close function
QDialog::closeEvent(e);
ProgressDialog::closeEvent(e);
// Clean up this dialog (FIXME: Is this necessary?)
deleteLater();
// Clean up this task and dialog
if (destroy_on_close_) {
deleteLater();
}
}
void TaskDialog::TaskFailed(const QString &s)
void TaskDialog::TaskFinished()
{
task_failed_ = true;
task_error_ = s;
QFutureWatcher<bool>* task_watcher = static_cast<QFutureWatcher<bool>*>(sender());
if (task_watcher->result()) {
emit TaskSucceeded(task_);
} else {
ShowErrorMessage(tr("Task Failed"), task_->GetError());
emit TaskFailed(task_);
}
task_watcher->deleteLater();
close();
}
OLIVE_NAMESPACE_EXIT
+32 -5
View File
@@ -30,23 +30,50 @@ class TaskDialog : public ProgressDialog
{
Q_OBJECT
public:
/**
* @brief TaskDialog Constructor
*
* Creates a TaskDialog. The TaskDialog takes ownership of the Task and will destroy it on close.
* Connect to the Task::Succeeded() if you want to retrieve information from the task before it
* gets destroyed.
*/
TaskDialog(Task *task, const QString &title, QWidget* parent = nullptr);
/**
* @brief Set whether TaskDialog should destroy itself (and the task) when it's closed
*
* This is TRUE by default.
*/
void SetDestroyOnClose(bool e)
{
destroy_on_close_ = e;
}
/**
* @brief Returns this dialog's task
*/
Task* GetTask() const
{
return task_;
}
protected:
virtual void showEvent(QShowEvent* e) override;
virtual void closeEvent(QCloseEvent* e) override;
signals:
void TaskSucceeded(Task* task);
void TaskFailed(Task* task);
private:
Task* task_;
QThread* thread_;
bool task_failed_;
QString task_error_;
bool destroy_on_close_;
private slots:
void TaskFailed(const QString& s);
void TaskFinished();
};
+2
View File
@@ -35,6 +35,8 @@ TaskManagerPanel::TaskManagerPanel(QWidget* parent) :
// Connect task view to the task manager
connect(TaskManager::instance(), &TaskManager::TaskAdded, view_, &TaskView::AddTask);
connect(TaskManager::instance(), &TaskManager::TaskRemoved, view_, &TaskView::RemoveTask);
connect(TaskManager::instance(), &TaskManager::TaskFailed, view_, &TaskView::TaskFailed);
// Set strings
Retranslate();
-6
View File
@@ -20,12 +20,6 @@ set(OLIVE_SOURCES
${OLIVE_SOURCES}
project/project.h
project/project.cpp
project/projectimportmanager.h
project/projectimportmanager.cpp
project/projectloadmanager.h
project/projectloadmanager.cpp
project/projectsavemanager.h
project/projectsavemanager.cpp
project/projectviewmodel.h
project/projectviewmodel.cpp
PARENT_SCOPE
-5
View File
@@ -19,11 +19,6 @@ add_subdirectory(opengl)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
render/backend/exporter.h
render/backend/exporter.cpp
render/backend/exportparams.h
render/backend/exportparams.cpp
render/backend/renderbackend.h
render/backend/renderbackend.cpp
-373
View File
@@ -1,373 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program 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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "exporter.h"
#include <QtConcurrent/QtConcurrent>
#include "render/backend/opengl/openglbackend.h"
#include "render/colormanager.h"
#include "render/pixelformat.h"
OLIVE_NAMESPACE_ENTER
Exporter::Exporter(ViewerOutput *viewer_node,
ColorManager *color_manager,
const ExportParams& params,
QObject* parent) :
QObject(parent),
viewer_node_(viewer_node),
params_(params),
renderer_(nullptr),
export_status_(false),
export_msg_(tr("Export hasn't started yet"))
{
encoder_ = Encoder::CreateFromID(params_.encoder(), params_);
video_done_ = !params_.video_enabled();
audio_done_ = !params_.audio_enabled();
debug_timer_.setInterval(5000);
connect(&debug_timer_, &QTimer::timeout, this, &Exporter::DebugTimerMessage);
connect(this, &Exporter::ExportEnded, this, &Exporter::deleteLater);
if (params_.has_custom_range()) {
export_range_ = params_.custom_range();
} else {
export_range_ = TimeRange(0, viewer_node_->GetLength());
}
if (params_.video_enabled()) {
// If a transformation matrix is applied to this video, create it here
if (params_.video_scaling_method() != ExportParams::kStretch) {
transform_ = GenerateMatrix(params_.video_scaling_method(),
viewer_node_->video_params().width(),
viewer_node_->video_params().height(),
params_.video_params().width(),
params_.video_params().height());
}
// Create color processor
color_processor_ = ColorProcessor::Create(color_manager,
color_manager->GetReferenceColorSpace(),
params.color_transform());
}
}
bool Exporter::GetExportStatus() const
{
return export_status_;
}
const QString &Exporter::GetExportError() const
{
return export_msg_;
}
void Exporter::Cancel()
{
if (renderer_) {
renderer_->CancelQueue();
renderer_->deleteLater();
renderer_ = nullptr;
}
SetExportMessage(tr("User cancelled export"));
ExportStopped();
}
void Exporter::StartExporting()
{
// Default to error state until ExportEnd is called
export_status_ = false;
// Create renderers
renderer_ = new OpenGLBackend();
renderer_->SetViewerNode(viewer_node_);
if (!video_done_) {
renderer_->SetPixelFormat(params_.video_params().format());
renderer_->SetMode(params_.video_params().mode());
waiting_for_frame_ = 0;
}
if (!audio_done_) {
renderer_->SetSampleFormat(params_.audio_params().format());
}
// Open encoder and wait for result
connect(encoder_, &Encoder::OpenSucceeded, this, &Exporter::EncoderOpenedSuccessfully, Qt::QueuedConnection);
connect(encoder_, &Encoder::OpenFailed, this, &Exporter::EncoderOpenFailed, Qt::QueuedConnection);
connect(encoder_, &Encoder::AudioComplete, this, &Exporter::AudioEncodeComplete, Qt::QueuedConnection);
QMetaObject::invokeMethod(encoder_,
"Open",
Qt::QueuedConnection);
}
void Exporter::SetExportMessage(const QString &s)
{
export_msg_ = s;
}
void Exporter::ExportSucceeded()
{
if (!audio_done_ || !video_done_) {
return;
}
if (renderer_) {
renderer_->deleteLater();
renderer_ = nullptr;
}
export_status_ = true;
connect(encoder_, &Encoder::Closed, this, &Exporter::EncoderClosed);
QMetaObject::invokeMethod(encoder_,
"Close",
Qt::QueuedConnection);
}
void Exporter::ExportStopped()
{
emit ExportEnded();
encoder_->deleteLater();
}
void Exporter::EncodeFrame()
{
while (cached_frames_.contains(waiting_for_frame_)) {
FramePtr frame = cached_frames_.take(waiting_for_frame_);
// Encode (may require re-associating alpha?)
QMetaObject::invokeMethod(encoder_,
"WriteFrame",
Qt::QueuedConnection,
OLIVE_NS_ARG(FramePtr, frame),
OLIVE_NS_ARG(rational, waiting_for_frame_));
waiting_for_frame_ += params_.video_params().time_base();
// Calculate progress
emit ProgressChanged(waiting_for_frame_.toDouble() / viewer_node_->GetLength().toDouble());
}
if (waiting_for_frame_ >= viewer_node_->GetLength()) {
video_done_ = true;
debug_timer_.stop();
ExportSucceeded();
}
}
QMatrix4x4 Exporter::GenerateMatrix(ExportParams::VideoScalingMethod method, int source_width, int source_height, int dest_width, int dest_height)
{
QMatrix4x4 preview_matrix;
if (method == ExportParams::kStretch) {
return preview_matrix;
}
float export_ar = static_cast<float>(dest_width) / static_cast<float>(dest_height);
float source_ar = static_cast<float>(source_width) / static_cast<float>(source_height);
if (qFuzzyCompare(export_ar, source_ar)) {
return preview_matrix;
}
if ((export_ar > source_ar) == (method == ExportParams::kFit)) {
preview_matrix.scale(source_ar / export_ar, 1.0F);
} else {
preview_matrix.scale(1.0F, export_ar / source_ar);
}
return preview_matrix;
}
FramePtr FrameColorConvert(ColorProcessorPtr processor, FramePtr frame)
{
qDebug() << "Converting" << frame->timestamp();
// OCIO conversion requires a frame in 32F format
if (frame->format() != PixelFormat::PIX_FMT_RGBA32F) {
frame = PixelFormat::ConvertPixelFormat(frame, PixelFormat::PIX_FMT_RGBA32F);
}
// Color conversion must be done with unassociated alpha, and the pipeline is always associated
ColorManager::DisassociateAlpha(frame);
// Convert color space
processor->ConvertFrame(frame);
// Re-associate alpha
ColorManager::ReassociateAlpha(frame);
return frame;
}
void Exporter::FrameRendered(FramePtr frame)
{
// Start color space conversion in another thread
QFutureWatcher<FramePtr>* watcher = new QFutureWatcher<FramePtr>();
connect(watcher, &QFutureWatcher<FramePtr>::finished, this, &Exporter::FrameColorFinished);
QFuture<FramePtr> future = QtConcurrent::run(FrameColorConvert,
color_processor_,
frame);
watcher->setFuture(future);
}
void Exporter::AudioRendered()
{
/*
// Retrieve the audio filename
QString cache_fn = audio_backend_->CachePathName();
QMetaObject::invokeMethod(encoder_,
"WriteAudio",
Qt::QueuedConnection,
OLIVE_NS_ARG(AudioRenderingParams, audio_backend_->params()),
Q_ARG(const QString&, cache_fn),
OLIVE_NS_ARG(TimeRange, export_range_));
*/
}
void Exporter::AudioEncodeComplete()
{
audio_done_ = true;
ExportSucceeded();
}
void Exporter::EncoderOpenedSuccessfully()
{
/*
// Invalidate caches
if (!video_done_) {
// First we generate the hashes so we know exactly how many frames we need
video_backend_->SetOperatingMode(VideoRenderWorker::kHashOnly);
connect(video_backend_, &VideoRenderBackend::QueueComplete, this, &Exporter::VideoHashesComplete);
video_backend_->InvalidateCache(export_range_, nullptr);
}
if (!audio_done_) {
// We set the audio backend to render the full sequence to the disk
connect(audio_backend_, &AudioRenderBackend::AudioComplete, this, &Exporter::AudioRendered);
audio_backend_->InvalidateCache(export_range_, nullptr);
}
*/
}
void Exporter::EncoderOpenFailed()
{
SetExportMessage(tr("Failed to open encoder"));
ExportStopped();
}
void Exporter::EncoderClosed()
{
emit ProgressChanged(100);
ExportStopped();
}
void Exporter::VideoHashesComplete()
{
/*
// We've got our hashes, time to kick off actual rendering
disconnect(video_backend_, &VideoRenderBackend::QueueComplete, this, &Exporter::VideoHashesComplete);
// Determine what frames will be hashed
TimeRangeList ranges;
ranges.append(TimeRange(0, viewer_node_->GetLength()));
// Set video backend to render mode but NOT hash or download
video_backend_->SetOperatingMode(VideoRenderWorker::kRenderOnly);
video_backend_->SetOnlySignalLastFrameRequested(false);
video_backend_->SetFrameGenerationParams(params_.video_params().width(), params_.video_params().height(), transform_);
connect(video_backend_, &VideoRenderBackend::GeneratedFrame, this, &Exporter::FrameRendered);
// Remove duplicate frames from cache invalidation
const QMap<rational, QByteArray>& time_hash_map = video_backend_->frame_cache()->time_hash_map();
QList<QByteArray> hashes_already_seen;
QMap<rational, QByteArray>::const_iterator i;
for (i=time_hash_map.begin(); i!=time_hash_map.end(); i++) {
if (hashes_already_seen.contains(i.value())) {
ranges.RemoveTimeRange(TimeRange(i.key(), i.key() + params_.video_params().time_base()));
} else {
hashes_already_seen.append(i.value());
}
}
foreach (const TimeRange& range, ranges) {
video_backend_->InvalidateCache(range, nullptr);
}
*/
}
void Exporter::DebugTimerMessage()
{
qDebug() << "Still waiting for" << waiting_for_frame_.toDouble();
}
void Exporter::FrameColorFinished()
{
if (!renderer_) {
return;
}
QFutureWatcher<FramePtr>* watcher = static_cast< QFutureWatcher<FramePtr>* >(sender());
FramePtr frame = watcher->result();
watcher->deleteLater();
debug_timer_.stop();
const QMap<rational, QByteArray>& time_hash_map = viewer_node_->video_frame_cache()->time_hash_map();
QByteArray this_hash = time_hash_map.value(frame->timestamp());
qDebug() << "Received" << this_hash.toHex();
QList<rational> matching_times = time_hash_map.keys(this_hash);
foreach (const rational& t, matching_times) {
qDebug() << " Matches" << t.toDouble();
cached_frames_.insert(t, frame);
}
qDebug() << " Waiting for" << waiting_for_frame_.toDouble();
debug_timer_.start();
EncodeFrame();
}
OLIVE_NAMESPACE_EXIT
-124
View File
@@ -1,124 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program 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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef EXPORTER_H
#define EXPORTER_H
#include <QMatrix4x4>
#include <QString>
#include <QTimer>
#include <QObject>
#include "codec/encoder.h"
#include "node/output/viewer/viewer.h"
#include "render/backend/exportparams.h"
#include "render/backend/renderbackend.h"
#include "render/colorprocessor.h"
OLIVE_NAMESPACE_ENTER
class Exporter : public QObject
{
Q_OBJECT
public:
Exporter(ViewerOutput* viewer_node,
ColorManager* color_manager,
const ExportParams& params,
QObject* parent = nullptr);
bool GetExportStatus() const;
const QString& GetExportError() const;
void Cancel();
static QMatrix4x4 GenerateMatrix(ExportParams::VideoScalingMethod method, int source_width, int source_height, int dest_width, int dest_height);
public slots:
void StartExporting();
signals:
void ProgressChanged(double);
void ExportEnded();
protected:
void SetExportMessage(const QString& s);
private:
void ExportSucceeded();
void ExportStopped();
void EncodeFrame();
ViewerOutput* viewer_node_;
ColorProcessorPtr color_processor_;
ExportParams params_;
// Renderers
RenderBackend* renderer_;
// Export transform
QMatrix4x4 transform_;
bool video_done_;
bool audio_done_;
Encoder* encoder_;
bool export_status_;
QString export_msg_;
TimeRange export_range_;
rational waiting_for_frame_;
QHash<rational, FramePtr> cached_frames_;
QTimer debug_timer_;
private slots:
void FrameRendered(FramePtr frame);
void AudioRendered();
void AudioEncodeComplete();
void EncoderOpenedSuccessfully();
void EncoderOpenFailed();
void EncoderClosed();
void VideoHashesComplete();
void DebugTimerMessage();
void FrameColorFinished();
};
OLIVE_NAMESPACE_EXIT
#endif // EXPORTER_H
+3 -1
View File
@@ -16,12 +16,14 @@
add_subdirectory(cache)
add_subdirectory(conform)
add_subdirectory(export)
add_subdirectory(project)
add_subdirectory(proxy)
add_subdirectory(render)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
task/task.h
task/task.cpp
task/taskmanager.h
task/taskmanager.cpp
PARENT_SCOPE
+7 -153
View File
@@ -22,181 +22,35 @@
#include <QLinkedList>
#include "common/timecodefunctions.h"
#include "project/item/sequence/sequence.h"
#include "render/backend/opengl/openglbackend.h"
OLIVE_NAMESPACE_ENTER
CacheTask::CacheTask(ViewerOutput* viewer, int divider, bool in_out_only) :
viewer_(viewer),
RenderTask(viewer),
in_out_only_(in_out_only),
divider_(divider)
{
SetTitle(tr("Caching \"%1\"").arg(viewer_->media_name()));
SetTitle(tr("Caching \"%1\"").arg(viewer->media_name()));
}
struct TimeHashFuturePair {
rational time;
QFuture<QByteArray> hash_future;
};
struct HashFrameFuturePair {
QByteArray hash;
QFuture<FramePtr> frame_future;
};
struct HashDownloadFuturePair {
QByteArray hash;
QFuture<void> download_future;
};
struct HashTimePair {
rational time;
QByteArray hash;
};
void CacheTask::Action()
bool CacheTask::Run()
{
OpenGLBackend backend;
RenderMode::Mode mode = RenderMode::kOffline;
PixelFormat::Format format = PixelFormat::instance()->GetConfiguredFormatForMode(mode);
backend.SetAudioEnabled(false);
backend.SetViewerNode(viewer_);
backend.SetPixelFormat(format);
backend.SetMode(mode);
backend.SetDivider(divider_);
backend.SetSampleFormat(SampleFormat::kInternalFormat);
// Get list of invalidated ranges
TimeRangeList range_to_cache = viewer_->video_frame_cache()->GetInvalidatedRanges();
TimeRangeList range_to_cache = viewer()->video_frame_cache()->GetInvalidatedRanges();
// If we're caching only in-out, limit the range to that
if (in_out_only_) {
Sequence* s = static_cast<Sequence*>(viewer_->parent());
Sequence* s = static_cast<Sequence*>(viewer()->parent());
if (s->workarea()->enabled()) {
range_to_cache = range_to_cache.Intersects(s->workarea()->range());
}
}
// Get hashes for each frame
QLinkedList<TimeHashFuturePair> hash_list;
while (!range_to_cache.isEmpty()) {
const TimeRange& range = range_to_cache.first();
Render(range_to_cache, 2);
const rational& timebase = viewer_->video_params().time_base();
rational time = range.in();
rational snapped = Timecode::snap_time_to_timebase(time, timebase);
rational next;
if (snapped > time) {
next = snapped;
snapped -= timebase;
} else {
next = snapped + timebase;
}
hash_list.append({snapped, backend.Hash(snapped, false)});
range_to_cache.RemoveTimeRange(TimeRange(snapped, next));
}
// Determine any duplicates
QMap< QByteArray, QLinkedList<rational> > times_to_render;
foreach (const TimeHashFuturePair& i, hash_list) {
times_to_render[i.hash_future.result()].append(i.time);
}
// Render all frames necessary
QLinkedList<HashFrameFuturePair> render_lookup_table;
{
QLinkedList<HashTimePair> sorted_times;
QLinkedList<HashTimePair>::iterator sorted_iterator;
// Rendering is more efficient if we cache in order
QMap< QByteArray, QLinkedList<rational> >::const_iterator i;
for (i=times_to_render.constBegin(); i!=times_to_render.constEnd(); i++) {
const QByteArray& hash = i.key();
const rational& time = i.value().first();
bool inserted = false;
for (sorted_iterator=sorted_times.begin(); sorted_iterator!=sorted_times.end(); sorted_iterator++) {
if (sorted_iterator->time > time) {
sorted_times.insert(sorted_iterator, {time, hash});
inserted = true;
break;
}
}
if (!inserted) {
sorted_times.append({time, hash});
}
}
foreach (const HashTimePair& p, sorted_times) {
render_lookup_table.append({p.hash, backend.RenderFrame(p.time, false, false)});
}
}
OIIO::TypeDesc output_desc = PixelFormat::GetOIIOTypeDesc(format);
OIIO::ImageSpec output_spec(viewer_->video_params().width() / divider_,
viewer_->video_params().height() / divider_,
PixelFormat::ChannelCount(format),
output_desc);
// Start downloading frames that have finished
{
int counter = 0;
int nb_frames = render_lookup_table.size();
QLinkedList<HashDownloadFuturePair> download_futures;
// Iterators
QLinkedList<HashFrameFuturePair>::iterator i;
QLinkedList<HashDownloadFuturePair>::iterator j;
while (!render_lookup_table.isEmpty() || !download_futures.isEmpty()) {
i = render_lookup_table.begin();
while (i != render_lookup_table.end()) {
if (i->frame_future.isFinished()) {
FramePtr f = i->frame_future.result();
// Start multithreaded download here
download_futures.append({i->hash,
QtConcurrent::run(FrameHashCache::SaveCacheFrame, i->hash, f)});
i = render_lookup_table.erase(i);
} else {
i++;
}
}
j = download_futures.begin();
while (j != download_futures.end()) {
if (j->download_future.isFinished()) {
// Place it in the cache
const QLinkedList<rational>& times_with_hash = times_to_render.value(j->hash);
foreach (const rational& t, times_with_hash) {
viewer_->video_frame_cache()->SetHash(t, j->hash);
}
// Signal process
counter++;
emit ProgressChanged(qRound(100.0 * static_cast<double>(counter) / static_cast<double>(nb_frames)));
j = download_futures.erase(j);
} else {
j++;
}
}
}
}
return true;
}
OLIVE_NAMESPACE_EXIT
+6 -7
View File
@@ -21,23 +21,22 @@
#ifndef CACHETASK_H
#define CACHETASK_H
#include "node/output/viewer/viewer.h"
#include "task/task.h"
#include <QtConcurrent/QtConcurrent>
#include "task/render/render.h"
OLIVE_NAMESPACE_ENTER
class CacheTask : public Task
class CacheTask : public RenderTask
{
Q_OBJECT
public:
CacheTask(ViewerOutput* viewer, int divider, bool in_out_only);
protected:
virtual void Action() override;
public slots:
virtual bool Run() override;
private:
ViewerOutput* viewer_;
bool in_out_only_;
int divider_;
+7 -5
View File
@@ -31,10 +31,11 @@ ConformTask::ConformTask(AudioStreamPtr stream, const AudioRenderingParams& para
SetTitle(tr("Conforming Audio %1:%2").arg(stream_->footage()->filename(), QString::number(stream_->index())));
}
void ConformTask::Action()
bool ConformTask::Run()
{
if (stream_->footage()->decoder().isEmpty()) {
emit Failed(tr("Failed to find decoder to conform audio stream"));
SetError(tr("Failed to find decoder to conform audio stream"));
return false;
} else {
DecoderPtr decoder = Decoder::CreateFromID(stream_->footage()->decoder());
@@ -42,10 +43,11 @@ void ConformTask::Action()
connect(decoder.get(), &Decoder::IndexProgress, this, &ConformTask::ProgressChanged);
if (decoder->ConformAudio(&IsCancelled(), params_)) {
emit Succeeded();
if (!decoder->ConformAudio(&IsCancelled(), params_)) {
SetError(tr("Failed to conform audio"));
return false;
} else {
emit Failed(QStringLiteral("Failed to conform audio"));
return true;
}
}
}
+2 -2
View File
@@ -32,8 +32,8 @@ class ConformTask : public Task
public:
ConformTask(AudioStreamPtr stream, const AudioRenderingParams& params);
protected:
virtual void Action() override;
public slots:
virtual bool Run() override;
private:
AudioStreamPtr stream_;
+24
View File
@@ -0,0 +1,24 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2019 Olive Team
#
# This program 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
task/export/export.h
task/export/export.cpp
task/export/exportparams.h
task/export/exportparams.cpp
PARENT_SCOPE
)
@@ -18,40 +18,22 @@
***/
#include "task.h"
#include "export.h"
OLIVE_NAMESPACE_ENTER
Task::Task() :
title_(tr("Task"))
ExportTask::ExportTask(ViewerOutput* viewer_node,
ColorManager* color_manager,
const ExportParams& params) :
RenderTask(viewer_node),
color_manager_(color_manager),
params_(params)
{
}
void Task::Start()
bool ExportTask::Run()
{
Action();
emit Finished();
}
const QString &Task::GetTitle()
{
return title_;
}
void Task::Cancel()
{
CancelableObject::Cancel();
}
void Task::SetErrorText(const QString &s)
{
error_ = s;
}
void Task::SetTitle(const QString &s)
{
title_ = s;
return true;
}
OLIVE_NAMESPACE_EXIT
+50
View File
@@ -0,0 +1,50 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program 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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef EXPORTTASK_H
#define EXPORTTASK_H
#include "exportparams.h"
#include "node/output/viewer/viewer.h"
#include "render/colorprocessor.h"
#include "task/render/render.h"
#include "task/task.h"
OLIVE_NAMESPACE_ENTER
class ExportTask : public RenderTask
{
Q_OBJECT
public:
ExportTask(ViewerOutput *viewer_node, ColorManager *color_manager, const ExportParams &params);
public slots:
virtual bool Run() override;
private:
ColorManager* color_manager_;
ExportParams params_;
};
OLIVE_NAMESPACE_EXIT
#endif // EXPORTTASK_H
@@ -74,4 +74,30 @@ void ExportParams::set_color_transform(const ColorTransform &color_transform)
color_transform_ = color_transform;
}
QMatrix4x4 ExportParams::GenerateMatrix(ExportParams::VideoScalingMethod method,
int source_width, int source_height,
int dest_width, int dest_height)
{
QMatrix4x4 preview_matrix;
if (method == ExportParams::kStretch) {
return preview_matrix;
}
float export_ar = static_cast<float>(dest_width) / static_cast<float>(dest_height);
float source_ar = static_cast<float>(source_width) / static_cast<float>(source_height);
if (qFuzzyCompare(export_ar, source_ar)) {
return preview_matrix;
}
if ((export_ar > source_ar) == (method == ExportParams::kFit)) {
preview_matrix.scale(source_ar / export_ar, 1.0F);
} else {
preview_matrix.scale(1.0F, export_ar / source_ar);
}
return preview_matrix;
}
OLIVE_NAMESPACE_EXIT
@@ -21,6 +21,8 @@
#ifndef EXPORTPARAMS_H
#define EXPORTPARAMS_H
#include <QMatrix4x4>
#include "codec/encoder.h"
#include "node/output/viewer/viewer.h"
#include "render/colortransform.h"
@@ -50,6 +52,10 @@ public:
const ColorTransform& color_transform() const;
void set_color_transform(const ColorTransform& color_transform);
static QMatrix4x4 GenerateMatrix(ExportParams::VideoScalingMethod method,
int source_width, int source_height,
int dest_width, int dest_height);
private:
QString encoder_id_;
+24
View File
@@ -0,0 +1,24 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2019 Olive Team
#
# This program 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
add_subdirectory(import)
add_subdirectory(load)
add_subdirectory(save)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
PARENT_SCOPE
)
+22
View File
@@ -0,0 +1,22 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2019 Olive Team
#
# This program 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
task/project/import/import.h
task/project/import/import.cpp
PARENT_SCOPE
)
@@ -18,7 +18,7 @@
***/
#include "projectimportmanager.h"
#include "import.h"
#include <QDir>
#include <QFileInfo>
@@ -29,7 +29,8 @@
OLIVE_NAMESPACE_ENTER
ProjectImportManager::ProjectImportManager(ProjectViewModel *model, Folder *folder, const QStringList &filenames) :
ProjectImportTask::ProjectImportTask(ProjectViewModel *model, Folder *folder, const QStringList &filenames) :
command_(nullptr),
model_(model),
folder_(folder)
{
@@ -42,27 +43,29 @@ ProjectImportManager::ProjectImportManager(ProjectViewModel *model, Folder *fold
SetTitle(tr("Importing %1 files").arg(file_count_));
}
const int &ProjectImportManager::GetFileCount()
const int &ProjectImportTask::GetFileCount() const
{
return file_count_;
}
void ProjectImportManager::Action()
bool ProjectImportTask::Run()
{
QUndoCommand* command = new QUndoCommand();
command_ = new QUndoCommand();
int imported = 0;
Import(folder_, filenames_, imported, command);
Import(folder_, filenames_, imported, command_);
if (IsCancelled()) {
delete command;
delete command_;
command_ = nullptr;
return false;
} else {
emit ImportComplete(command);
return true;
}
}
void ProjectImportManager::Import(Folder *folder, const QFileInfoList &import, int &counter, QUndoCommand* parent_command)
void ProjectImportTask::Import(Folder *folder, const QFileInfoList &import, int &counter, QUndoCommand* parent_command)
{
foreach (const QFileInfo& file_info, import) {
if (IsCancelled()) {
@@ -24,28 +24,32 @@
#include <QFileInfoList>
#include <QUndoCommand>
#include "projectviewmodel.h"
#include "project/projectviewmodel.h"
#include "task/task.h"
OLIVE_NAMESPACE_ENTER
class ProjectImportManager : public Task
class ProjectImportTask : public Task
{
Q_OBJECT
public:
ProjectImportManager(ProjectViewModel* model, Folder* folder, const QStringList& filenames);
ProjectImportTask(ProjectViewModel* model, Folder* folder, const QStringList& filenames);
const int& GetFileCount();
const int& GetFileCount() const;
protected:
virtual void Action() override;
QUndoCommand* GetCommand() const
{
return command_;
}
signals:
void ImportComplete(QUndoCommand* command);
public slots:
virtual bool Run() override;
private:
void Import(Folder* folder, const QFileInfoList &import, int& counter, QUndoCommand *parent_command);
QUndoCommand* command_;
ProjectViewModel* model_;
Folder* folder_;
+22
View File
@@ -0,0 +1,22 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2019 Olive Team
#
# This program 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
task/project/load/load.h
task/project/load/load.cpp
PARENT_SCOPE
)
@@ -18,7 +18,7 @@
***/
#include "projectloadmanager.h"
#include "load.h"
#include <QApplication>
#include <QFile>
@@ -28,13 +28,13 @@
OLIVE_NAMESPACE_ENTER
ProjectLoadManager::ProjectLoadManager(const QString &filename) :
ProjectLoadTask::ProjectLoadTask(const QString &filename) :
filename_(filename)
{
SetTitle(tr("Loading '%1'").arg(filename));
}
void ProjectLoadManager::Action()
bool ProjectLoadTask::Run()
{
QFile project_file(filename_);
@@ -57,7 +57,7 @@ void ProjectLoadManager::Action()
moveToThread(qApp->thread());
if (!IsCancelled()) {
emit ProjectLoaded(project);
projects_.append(project);
}
} else {
reader.skipCurrentElement();
@@ -68,14 +68,18 @@ void ProjectLoadManager::Action()
}
}
project_file.close();
if (reader.hasError()) {
qDebug() << "Found XML error:" << reader.errorString();
emit Failed(reader.errorString());
SetError(reader.errorString());
return false;
} else {
emit Succeeded();
return true;
}
project_file.close();
} else {
SetError(tr("Failed to read file \"%1\" for reading.").arg(filename_));
return false;
}
}
@@ -26,19 +26,23 @@
OLIVE_NAMESPACE_ENTER
class ProjectLoadManager : public Task
class ProjectLoadTask : public Task
{
Q_OBJECT
public:
ProjectLoadManager(const QString& filename);
ProjectLoadTask(const QString& filename);
protected:
virtual void Action() override;
const QList<ProjectPtr>& GetLoadedProjects()
{
return projects_;
}
signals:
void ProjectLoaded(OLIVE_NAMESPACE::ProjectPtr project);
public slots:
virtual bool Run() override;
private:
QList<ProjectPtr> projects_;
QString filename_;
};
+22
View File
@@ -0,0 +1,22 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2019 Olive Team
#
# This program 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
task/project/save/save.h
task/project/save/save.cpp
PARENT_SCOPE
)
@@ -18,20 +18,20 @@
***/
#include "projectsavemanager.h"
#include "save.h"
#include <QFile>
#include <QXmlStreamWriter>
OLIVE_NAMESPACE_ENTER
ProjectSaveManager::ProjectSaveManager(ProjectPtr project) :
ProjectSaveTask::ProjectSaveTask(ProjectPtr project) :
project_(project)
{
SetTitle(tr("Saving '%1'").arg(project->filename()));
}
void ProjectSaveManager::Action()
bool ProjectSaveTask::Run()
{
QFile project_file(project_->filename());
@@ -52,10 +52,12 @@ void ProjectSaveManager::Action()
writer.writeEndDocument();
project_file.close();
}
emit Succeeded();
emit ProjectSaveSucceeded(project_);
return true;
} else {
SetError(tr("Failed to open file \"%1\" for writing.").arg(project_->filename()));
return false;
}
}
OLIVE_NAMESPACE_EXIT
@@ -26,17 +26,19 @@
OLIVE_NAMESPACE_ENTER
class ProjectSaveManager : public Task
class ProjectSaveTask : public Task
{
Q_OBJECT
public:
ProjectSaveManager(ProjectPtr project);
ProjectSaveTask(ProjectPtr project);
signals:
void ProjectSaveSucceeded(OLIVE_NAMESPACE::ProjectPtr p);
ProjectPtr GetProject() const
{
return project_;
}
protected:
virtual void Action() override;
public slots:
virtual bool Run() override;
private:
ProjectPtr project_;
+6 -4
View File
@@ -38,10 +38,11 @@ ProxyTask::ProxyTask(VideoStreamPtr stream, int divider) :
}
}
void ProxyTask::Action()
bool ProxyTask::Run()
{
if (stream_->footage()->decoder().isEmpty()) {
emit Failed(tr("Failed to find decoder to conform audio stream"));
SetError(tr("Failed to find decoder to conform audio stream"));
return false;
} else {
DecoderPtr decoder = Decoder::CreateFromID(stream_->footage()->decoder());
@@ -50,9 +51,10 @@ void ProxyTask::Action()
connect(decoder.get(), &Decoder::IndexProgress, this, &ProxyTask::ProgressChanged);
if (decoder->ProxyVideo(&IsCancelled(), divider_)) {
emit Succeeded();
return true;
} else {
emit Failed(QStringLiteral("Failed to generate proxy"));
SetError(tr("Failed to generate proxy"));
return false;
}
}
}
+2 -2
View File
@@ -31,8 +31,8 @@ class ProxyTask : public Task
public:
ProxyTask(VideoStreamPtr stream, int divider);
protected:
virtual void Action() override;
public slots:
virtual bool Run() override;
private:
VideoStreamPtr stream_;
+22
View File
@@ -0,0 +1,22 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2019 Olive Team
#
# This program 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.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
task/render/render.h
task/render/render.cpp
PARENT_SCOPE
)
+186
View File
@@ -0,0 +1,186 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program 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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#include "render.h"
#include "common/timecodefunctions.h"
#include "render/backend/opengl/openglbackend.h"
OLIVE_NAMESPACE_ENTER
RenderTask::RenderTask(ViewerOutput* viewer) :
viewer_(viewer)
{
}
struct TimeHashFuturePair {
rational time;
QFuture<QByteArray> hash_future;
};
struct HashFrameFuturePair {
QByteArray hash;
QFuture<FramePtr> frame_future;
};
struct HashDownloadFuturePair {
QByteArray hash;
QFuture<void> download_future;
};
struct HashTimePair {
rational time;
QByteArray hash;
};
void RenderTask::Render(TimeRangeList range_to_cache, int divider)
{
OpenGLBackend backend;
RenderMode::Mode mode = RenderMode::kOffline;
PixelFormat::Format format = PixelFormat::instance()->GetConfiguredFormatForMode(mode);
backend.SetAudioEnabled(false);
backend.SetViewerNode(viewer_);
backend.SetPixelFormat(format);
backend.SetMode(mode);
backend.SetDivider(divider);
backend.SetSampleFormat(SampleFormat::kInternalFormat);
// Get hashes for each frame
QLinkedList<TimeHashFuturePair> hash_list;
while (!range_to_cache.isEmpty()) {
const TimeRange& range = range_to_cache.first();
const rational& timebase = viewer_->video_params().time_base();
rational time = range.in();
rational snapped = Timecode::snap_time_to_timebase(time, timebase);
rational next;
if (snapped > time) {
next = snapped;
snapped -= timebase;
} else {
next = snapped + timebase;
}
hash_list.append({snapped, backend.Hash(snapped, false)});
range_to_cache.RemoveTimeRange(TimeRange(snapped, next));
}
// Determine any duplicates
QMap< QByteArray, QLinkedList<rational> > times_to_render;
foreach (const TimeHashFuturePair& i, hash_list) {
times_to_render[i.hash_future.result()].append(i.time);
}
// Render all frames necessary
QLinkedList<HashFrameFuturePair> render_lookup_table;
{
QLinkedList<HashTimePair> sorted_times;
QLinkedList<HashTimePair>::iterator sorted_iterator;
// Rendering is more efficient if we cache in order
QMap< QByteArray, QLinkedList<rational> >::const_iterator i;
for (i=times_to_render.constBegin(); i!=times_to_render.constEnd(); i++) {
const QByteArray& hash = i.key();
const rational& time = i.value().first();
bool inserted = false;
for (sorted_iterator=sorted_times.begin(); sorted_iterator!=sorted_times.end(); sorted_iterator++) {
if (sorted_iterator->time > time) {
sorted_times.insert(sorted_iterator, {time, hash});
inserted = true;
break;
}
}
if (!inserted) {
sorted_times.append({time, hash});
}
}
foreach (const HashTimePair& p, sorted_times) {
render_lookup_table.append({p.hash, backend.RenderFrame(p.time, false, false)});
}
}
OIIO::TypeDesc output_desc = PixelFormat::GetOIIOTypeDesc(format);
OIIO::ImageSpec output_spec(viewer_->video_params().width() / divider,
viewer_->video_params().height() / divider,
PixelFormat::ChannelCount(format),
output_desc);
// Start downloading frames that have finished
{
int counter = 0;
int nb_frames = render_lookup_table.size();
QLinkedList<HashDownloadFuturePair> download_futures;
// Iterators
QLinkedList<HashFrameFuturePair>::iterator i;
QLinkedList<HashDownloadFuturePair>::iterator j;
while (!render_lookup_table.isEmpty() || !download_futures.isEmpty()) {
i = render_lookup_table.begin();
while (i != render_lookup_table.end()) {
if (i->frame_future.isFinished()) {
FramePtr f = i->frame_future.result();
// Start multithreaded download here
download_futures.append({i->hash,
QtConcurrent::run(FrameHashCache::SaveCacheFrame, i->hash, f)});
i = render_lookup_table.erase(i);
} else {
i++;
}
}
j = download_futures.begin();
while (j != download_futures.end()) {
if (j->download_future.isFinished()) {
// Place it in the cache
const QLinkedList<rational>& times_with_hash = times_to_render.value(j->hash);
foreach (const rational& t, times_with_hash) {
viewer_->video_frame_cache()->SetHash(t, j->hash);
}
// Signal process
counter++;
emit ProgressChanged(qRound(100.0 * static_cast<double>(counter) / static_cast<double>(nb_frames)));
j = download_futures.erase(j);
} else {
j++;
}
}
}
}
}
OLIVE_NAMESPACE_EXIT
+49
View File
@@ -0,0 +1,49 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive Team
This program 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.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef RENDERTASK_H
#define RENDERTASK_H
#include "node/output/viewer/viewer.h"
#include "task/task.h"
OLIVE_NAMESPACE_ENTER
class RenderTask : public Task
{
public:
RenderTask(ViewerOutput* viewer);
protected:
void Render(TimeRangeList range_to_cache, int divider = 1);
ViewerOutput* viewer() const
{
return viewer_;
}
private:
ViewerOutput* viewer_;
};
OLIVE_NAMESPACE_EXIT
#endif // RENDERTASK_H
+43 -42
View File
@@ -52,59 +52,68 @@ public:
/**
* @brief Task Constructor
*/
Task();
Task() :
title_(tr("Task")),
error_(tr("Unknown error"))
{
}
/**
* @brief Retrieve the current title of this Task
*/
const QString& GetTitle();
const QString& GetTitle()
{
return title_;
}
/**
* @brief Returns the error that occurred if Run() returns false
*/
const QString& GetError()
{
return error_;
}
public slots:
/**
* @brief Try to start this Task
* @brief Run this task
*
* The main function for starting this Task. If this task is currently waiting, this function will start a new thread
* and set the status to kWorking.
* @return True if the task completed successfully, false if not.
*
* This function also checks its dependency Tasks and will only start if all of them are complete. If they are still
* working, this function will return FALSE and the status will continue to be kWaiting. If any of them failed, this
* Task will also fail - this function will return FALSE and the status will be set to kError.
* \see GetError() if this returns false.
*/
void Start();
virtual bool Run() = 0;
/**
* @brief Reset state so that Run() can be called again.
*
* Override this if your class holds any persistent state that should be cleared/modified before
* it's safe for Run() to run again.
*/
virtual void Reset(){}
/**
* @brief Cancel the Task
*
* Sends a signal to the Task to stop as soon as possible. Always call this directly or connect with
* Qt::DirectConnection, or else it'll be queued *after* the task has already finished.
* Sends a signal to the Task to stop as soon as possible. Always call this directly or connect
* with Qt::DirectConnection, or else it'll be queued *after* the task has already finished.
*/
void Cancel();
void Cancel()
{
CancelableObject::Cancel();
}
protected:
/**
* @brief The main Task function which is run in a separate thread
*
* Action() is the function that gets called once the separate thread has been created. This function should be
* overridden in subclasses.
*
* It's also recommended to emit ProgressChanged() throughout your Action() so that any attached ProgressBars can
* show accurate progress information.
*
* @return
*
* TRUE if the Task could complete successfully. FALSE if not. Note that FALSE should only be returned if the Task
* could not finish, not if the Task found a negative result (see Task documentation for details). Before returning
* FALSE, it's recommended to use set_error() to signal to the user what caused the failure.
*/
virtual void Action() = 0;
/**
* @brief Set the error message
*
* It is recommended to use this if your Action() function ever returns FALSE to tell the user why the failure
* occurred.
*/
void SetErrorText(const QString& s);
void SetError(const QString& s)
{
error_ = s;
}
/**
* @brief Set the Task title
@@ -113,7 +122,10 @@ protected:
* and shouldn't need to change during the life of the Task. To show an error message, it's recommended to use
* set_error() instead.
*/
void SetTitle(const QString& s);
void SetTitle(const QString& s)
{
title_ = s;
}
signals:
/**
@@ -127,17 +139,6 @@ signals:
*/
void ProgressChanged(int p);
void Succeeded();
void Failed(const QString& error);
void Finished();
/**
* @brief Signal emitted when this Task is removed from TaskManager
*/
void Removed();
private:
QString title_;
+29 -177
View File
@@ -27,45 +27,22 @@ OLIVE_NAMESPACE_ENTER
TaskManager* TaskManager::instance_ = nullptr;
TaskManager::TaskManager() :
active_thread_count_(0)
TaskManager::TaskManager()
{
// Initialize threads to run tasks on
threads_.resize(QThread::idealThreadCount());
for (int i=0;i<threads_.size();i++) {
QThread* t = new QThread(this);
t->start(QThread::IdlePriority);
threads_.replace(i, {t, false});
}
}
TaskManager::~TaskManager()
{
// First send the signal to all tasks to start cancelling
foreach (const TaskContainer& task_info, tasks_) {
if (task_info.status == kWorking) {
task_info.task->Cancel();
}
thread_pool_.clear();
foreach (Task* t, tasks_) {
t->Cancel();
}
// Next, signal each thread to quit as its next event in the queue
foreach (const ThreadContainer& tc, threads_) {
tc.thread->quit();
}
thread_pool_.waitForDone();
// Wait for each thread's event queue to finish
foreach (const ThreadContainer& tc, threads_) {
tc.thread->wait();
// This is technically unnecessary since each QThread is a child of this object, but we may as well
delete tc.thread;
}
// Finally delete all task objects (they shouldn't have been deleted by TaskSucceeded() or TaskFailed() because our
// event queue shouldn't be active by this point
foreach (const TaskContainer& task_info, tasks_) {
delete task_info.task;
foreach (Task* t, tasks_) {
t->deleteLater();
}
}
@@ -92,171 +69,46 @@ int TaskManager::GetTaskCount() const
Task *TaskManager::GetFirstTask() const
{
return tasks_.first().task;
return tasks_.begin().value();
}
void TaskManager::AddTask(Task* t)
{
// Connect Task's status signal to the Callback
connect(t, &Task::Succeeded, this, &TaskManager::TaskSucceeded, Qt::QueuedConnection);
connect(t, &Task::Failed, this, &TaskManager::TaskFailed, Qt::QueuedConnection);
connect(t, &Task::Finished, this, &TaskManager::TaskFinished, Qt::QueuedConnection);
// Create a watcher for signalling
QFutureWatcher<bool>* watcher = new QFutureWatcher<bool>();
connect(watcher, &QFutureWatcher<bool>::finished, this, &TaskManager::TaskFinished);
// Add the Task to the queue
tasks_.append({t, kWaiting});
tasks_.insert(watcher, t);
// Run task concurrently
watcher->setFuture(QtConcurrent::run(t, &Task::Run));
// Emit signal that a Task was added
emit TaskAdded(t);
emit TaskListChanged();
// Scan through queue and start any Tasks that can (including this one)
StartNextWaiting();
}
void TaskManager::StartNextWaiting()
{
// If there are no tasks in the queue, there is nothing to be done
if (tasks_.isEmpty()) {
return;
}
// If all threads are occupied, nothing to be done
if (active_thread_count_ == threads_.size()) {
return;
}
// Create a list of tasks that are waiting
QList<Task*> waiting_tasks;
foreach (const TaskContainer& task_info, tasks_) {
if (task_info.status == kWaiting) {
waiting_tasks.append(task_info.task);
}
}
// No tasks waiting to start
if (waiting_tasks.isEmpty()) {
return;
}
// For any inactive threads,
for (int i=0;i<threads_.size();i++) {
if (!threads_.at(i).active) {
// This thread is inactive and needs a new Task
Task* task = waiting_tasks.takeFirst();
task->moveToThread(threads_.at(i).thread);
threads_[i].active = true;
active_thread_count_++;
SetTaskStatus(task, kWorking);
QMetaObject::invokeMethod(task,
"Start",
Qt::QueuedConnection);
if (active_thread_count_ == threads_.size() || waiting_tasks.isEmpty()) {
break;
}
}
}
}
void TaskManager::DeleteTask(Task *t)
{
if (GetTaskStatus(t) == kWorking) {
// Send a signal to the task to cancel, it will likely continue to cancel in the background after it's removed
t->Cancel();
}
// Remove instances of Task from queue
for (int i=0;i<tasks_.size();i++) {
if (tasks_.at(i).task == t) {
tasks_.removeAt(i);
break;
}
}
emit t->Removed();
emit TaskListChanged();
if (GetTaskStatus(t) != kWorking) {
// If the task isn't doing anything, we can simply delete it
t->deleteLater();
}
}
void TaskManager::TaskFinished()
{
Task* task_sender = static_cast<Task*>(sender());
QFutureWatcher<bool>* watcher = static_cast<QFutureWatcher<bool>*>(sender());
Task* t = tasks_.value(watcher);
// Set this thread's active value to false
for (int i=0;i<threads_.size();i++) {
if (threads_.at(i).thread == task_sender->thread()) {
threads_[i].active = false;
}
tasks_.remove(watcher);
if (watcher->result()) {
// Task completed successfully
emit TaskRemoved(t);
t->deleteLater();
} else {
// Task failed, keep it so the user can see the error message
emit TaskFailed(t);
failed_tasks_.append(t);
}
// See if we can delete this task
if (GetTaskStatus(task_sender) == kFinished) {
DeleteTask(task_sender);
} else if (GetTaskStatus(task_sender) == kError) {
// If this task has already been deleted, we'll free its memory now
bool was_deleted = true;
watcher->deleteLater();
for (int i=0;i<tasks_.size();i++) {
if (tasks_.at(i).task == task_sender) {
was_deleted = false;
break;
}
}
if (was_deleted) {
task_sender->deleteLater();
}
}
// Decrement the active thread count
active_thread_count_--;
// Signal that the task has finished
emit TaskListChanged();
// Start any tasks that could start now
StartNextWaiting();
}
TaskManager::TaskStatus TaskManager::GetTaskStatus(Task *t)
{
foreach (const TaskContainer& container, tasks_) {
if (container.task == t) {
return container.status;
}
}
return kError;
}
void TaskManager::SetTaskStatus(Task *t, TaskStatus status)
{
for (int i=0;i<tasks_.size();i++) {
TaskContainer& cont = tasks_[i];
if (cont.task == t) {
cont.status = status;
break;
}
}
}
void TaskManager::TaskSucceeded()
{
SetTaskStatus(static_cast<Task*>(sender()), kFinished);
}
void TaskManager::TaskFailed()
{
SetTaskStatus(static_cast<Task*>(sender()), kError);
}
OLIVE_NAMESPACE_EXIT
+16 -67
View File
@@ -21,6 +21,7 @@
#ifndef TASKMANAGER_H
#define TASKMANAGER_H
#include <QtConcurrent/QtConcurrent>
#include <QVector>
#include <QUndoCommand>
@@ -95,79 +96,31 @@ signals:
*/
void TaskListChanged();
/**
* @brief Signal emitted when a task is deleted
*/
void TaskRemoved(Task* t);
/**
* @brief Signal emitted when a task fails
*/
void TaskFailed(Task* t);
private:
/**
* @brief The Status enum
*
* All states that a Task can be in. When subclassing, you don't need to set the Task's status as the base class
* does that automatically.
*/
enum TaskStatus {
/// This Task is yet to start
kWaiting,
/// This Task is currently running (see Action())
kWorking,
/// This Task has completed successfully
kFinished,
/// This Task failed and could not complete
kError
};
struct TaskContainer {
Task* task;
TaskStatus status;
};
struct ThreadContainer {
QThread* thread;
bool active;
};
/**
* @brief Scan through the task queue and start any Tasks that are able to start
*
* This function is run whenever a Task is added and whenever a Task finishes. It determines how many Tasks are
* currently running and therefore how many Tasks can be started (if any). It will then start ones that can.
*
* This function is aware of "dependency Tasks" and if a Task is waiting but has a dependency that hasn't finished,
* it will skip to the next one.
*
* Like AddTask, this function is NOT thread-safe and currently only intended to be run from the main thread.
*/
void StartNextWaiting();
/**
* @brief Removes the Task from the queue and deletes it
*
* Recommended for use after a Task has completed or errorred.
*
* @param t
*
* Task to delete
*/
void DeleteTask(Task* t);
TaskStatus GetTaskStatus(Task* t);
void SetTaskStatus(Task* t, TaskStatus status);
/**
* @brief Internal task array
*/
QVector<TaskContainer> tasks_;
QHash<QFutureWatcher<bool>*, Task*> tasks_;
/**
* @brief Background threads to run tasks on
* @brief Internal list of failed tasks
*/
QVector<ThreadContainer> threads_;
QLinkedList<Task*> failed_tasks_;
/**
* @brief Value for how many threads are currently active
* @brief Task thread pool
*/
int active_thread_count_;
QThreadPool thread_pool_;
/**
* @brief TaskManager singleton instance
@@ -175,10 +128,6 @@ private:
static TaskManager* instance_;
private slots:
void TaskSucceeded();
void TaskFailed();
void TaskFinished();
};
+14 -1
View File
@@ -46,7 +46,20 @@ TaskView::TaskView(QWidget* parent) :
void TaskView::AddTask(Task *t)
{
// Create TaskViewItem (UI representation of a Task) and connect it
layout_->insertWidget(layout_->count()-1, new TaskViewItem(t));
TaskViewItem* item = new TaskViewItem(t);
items_.insert(t, item);
layout_->insertWidget(layout_->count()-1, item);
}
void TaskView::TaskFailed(Task *t)
{
items_.value(t)->Failed();
}
void TaskView::RemoveTask(Task *t)
{
items_.value(t)->deleteLater();
items_.remove(t);
}
OLIVE_NAMESPACE_EXIT
+8
View File
@@ -50,9 +50,17 @@ public slots:
*/
void AddTask(Task* t);
void TaskFailed(Task* t);
void RemoveTask(Task* t);
private:
QWidget* central_widget_;
QVBoxLayout* layout_;
QHash<Task*, TaskViewItem*> items_;
};
OLIVE_NAMESPACE_EXIT
+5 -1
View File
@@ -61,8 +61,12 @@ TaskViewItem::TaskViewItem(Task* task, QWidget *parent) :
// Connect to the task
connect(task_, &Task::ProgressChanged, progress_bar_, &QProgressBar::setValue);
connect(task_, &Task::Removed, this, &TaskViewItem::deleteLater);
connect(cancel_btn_, &QPushButton::clicked, task_, &Task::Cancel, Qt::DirectConnection);
}
void TaskViewItem::Failed()
{
task_status_lbl_->setText(task_->GetError());
}
OLIVE_NAMESPACE_EXIT
+2
View File
@@ -45,6 +45,8 @@ class TaskViewItem : public QFrame
public:
TaskViewItem(Task *task, QWidget* parent = nullptr);
void Failed();
private:
QLabel* task_name_lbl_;
QProgressBar* progress_bar_;
+2 -12
View File
@@ -29,7 +29,6 @@
#include "dialog/actionsearch/actionsearch.h"
#include "dialog/task/task.h"
#include "panel/panelmanager.h"
#include "task/cache/cache.h"
#include "tool/tool.h"
#include "ui/style/style.h"
#include "undo/undostack.h"
@@ -613,21 +612,12 @@ void MainMenu::OpenRecentItemTriggered()
void MainMenu::SequenceCacheTriggered()
{
TimeBasedPanel* p = PanelManager::instance()->MostRecentlyFocused<TimeBasedPanel>();
if (p && p->GetConnectedViewer()) {
// FIXME: Hardcoded divider...
// FIXME: Consider preventing caching the footage viewer
CacheTask* task = new CacheTask(p->GetConnectedViewer(), 2, false);
TaskDialog* dialog = new TaskDialog(task, tr("Caching Sequence"), parentWidget());
dialog->open();
}
Core::instance()->CacheActiveSequence(false);
}
void MainMenu::SequenceCacheInOutTriggered()
{
qDebug() << "STUB";
Core::instance()->CacheActiveSequence(true);
}
void MainMenu::Retranslate()