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:
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Vendored
+7
-153
@@ -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
|
||||
|
||||
Vendored
+6
-7
@@ -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_;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
|
||||
@@ -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 ¶ms);
|
||||
|
||||
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_;
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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_;
|
||||
@@ -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_;
|
||||
|
||||
};
|
||||
@@ -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_;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user