From b9cd83eff106bd97d8891808d09ee1e9bfcf8c5f Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 19 May 2020 02:33:28 +1000 Subject: [PATCH] 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 --- app/cli/clitask/clitaskdialog.cpp | 1 - app/core.cpp | 62 ++- app/core.h | 11 +- app/dialog/export/export.cpp | 204 +--------- app/dialog/export/export.h | 29 +- app/dialog/export/exportadvancedvideodialog.h | 1 - app/dialog/export/exportvideotab.cpp | 2 +- app/dialog/progress/progress.cpp | 40 ++ app/dialog/progress/progress.h | 8 + app/dialog/task/task.cpp | 71 ++-- app/dialog/task/task.h | 37 +- app/panel/taskmanager/taskmanager.cpp | 2 + app/project/CMakeLists.txt | 6 - app/render/backend/CMakeLists.txt | 5 - app/render/backend/exporter.cpp | 373 ------------------ app/render/backend/exporter.h | 124 ------ app/task/CMakeLists.txt | 4 +- app/task/cache/cache.cpp | 160 +------- app/task/cache/cache.h | 13 +- app/task/conform/conform.cpp | 12 +- app/task/conform/conform.h | 4 +- app/task/export/CMakeLists.txt | 24 ++ app/task/{task.cpp => export/export.cpp} | 36 +- app/task/export/export.h | 50 +++ .../backend => task/export}/exportparams.cpp | 26 ++ .../backend => task/export}/exportparams.h | 6 + app/task/project/CMakeLists.txt | 24 ++ app/task/project/import/CMakeLists.txt | 22 ++ .../project/import/import.cpp} | 21 +- .../project/import/import.h} | 20 +- app/task/project/load/CMakeLists.txt | 22 ++ .../project/load/load.cpp} | 20 +- .../project/load/load.h} | 16 +- app/task/project/save/CMakeLists.txt | 22 ++ .../project/save/save.cpp} | 14 +- .../project/save/save.h} | 14 +- app/task/proxy/proxy.cpp | 10 +- app/task/proxy/proxy.h | 4 +- app/task/render/CMakeLists.txt | 22 ++ app/task/render/render.cpp | 186 +++++++++ app/task/render/render.h | 49 +++ app/task/task.h | 85 ++-- app/task/taskmanager.cpp | 206 ++-------- app/task/taskmanager.h | 83 +--- app/widget/taskview/taskview.cpp | 15 +- app/widget/taskview/taskview.h | 8 + app/widget/taskview/taskviewitem.cpp | 6 +- app/widget/taskview/taskviewitem.h | 2 + app/window/mainwindow/mainmenu.cpp | 14 +- 49 files changed, 864 insertions(+), 1332 deletions(-) delete mode 100644 app/render/backend/exporter.cpp delete mode 100644 app/render/backend/exporter.h create mode 100644 app/task/export/CMakeLists.txt rename app/task/{task.cpp => export/export.cpp} (69%) create mode 100644 app/task/export/export.h rename app/{render/backend => task/export}/exportparams.cpp (69%) rename app/{render/backend => task/export}/exportparams.h (87%) create mode 100644 app/task/project/CMakeLists.txt create mode 100644 app/task/project/import/CMakeLists.txt rename app/{project/projectimportmanager.cpp => task/project/import/import.cpp} (87%) rename app/{project/projectimportmanager.h => task/project/import/import.h} (77%) create mode 100644 app/task/project/load/CMakeLists.txt rename app/{project/projectloadmanager.cpp => task/project/load/load.cpp} (85%) rename app/{project/projectloadmanager.h => task/project/load/load.h} (80%) create mode 100644 app/task/project/save/CMakeLists.txt rename app/{project/projectsavemanager.cpp => task/project/save/save.cpp} (85%) rename app/{project/projectsavemanager.h => task/project/save/save.h} (83%) create mode 100644 app/task/render/CMakeLists.txt create mode 100644 app/task/render/render.cpp create mode 100644 app/task/render/render.h diff --git a/app/cli/clitask/clitaskdialog.cpp b/app/cli/clitask/clitaskdialog.cpp index 804b23690..69bd9b4cf 100644 --- a/app/cli/clitask/clitaskdialog.cpp +++ b/app/cli/clitask/clitaskdialog.cpp @@ -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 diff --git a/app/core.cpp b/app/core.cpp index 1a7aa0dc1..62a22c524 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -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 projects = static_cast(task)->GetLoadedProjects(); + + foreach (ProjectPtr p, projects) { + AddOpenProject(p); + } +} + +void Core::ImportTaskComplete(Task* task) +{ + QUndoCommand *command = static_cast(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(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(); + + 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); diff --git a/app/core.h b/app/core.h index 8c6043e8a..2ef4a0566 100644 --- a/app/core.h +++ b/app/core.h @@ -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); diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp index 7131290d4..b13a90781 100644 --- a/app/dialog/export/export.cpp +++ b/app/dialog/export/export.cpp @@ -31,21 +31,18 @@ #include #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(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(video_tab_->width_slider()->GetValue()), static_cast(video_tab_->height_slider()->GetValue())); - preview_viewer_->SetMatrix(Exporter::GenerateMatrix(static_cast(video_tab_->scaling_method_combobox()->currentData().toInt()), - viewer_node_->video_params().width(), - viewer_node_->video_params().height(), - static_cast(video_tab_->width_slider()->GetValue()), - static_cast(video_tab_->height_slider()->GetValue()))); + QMatrix4x4 transform = + ExportParams::GenerateMatrix(static_cast(video_tab_->scaling_method_combobox()->currentData().toInt()), + viewer_node_->video_params().width(), + viewer_node_->video_params().height(), + static_cast(video_tab_->width_slider()->GetValue()), + static_cast(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 diff --git a/app/dialog/export/export.h b/app/dialog/export/export.h index 25dd21d7d..732410bcd 100644 --- a/app/dialog/export/export.h +++ b/app/dialog/export/export.h @@ -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 diff --git a/app/dialog/export/exportadvancedvideodialog.h b/app/dialog/export/exportadvancedvideodialog.h index 81b2c524f..71d3dc6e0 100644 --- a/app/dialog/export/exportadvancedvideodialog.h +++ b/app/dialog/export/exportadvancedvideodialog.h @@ -3,7 +3,6 @@ #include -#include "render/backend/exportparams.h" #include "widget/slider/integerslider.h" OLIVE_NAMESPACE_ENTER diff --git a/app/dialog/export/exportvideotab.cpp b/app/dialog/export/exportvideotab.cpp index c6a5b1d98..476dc495a 100644 --- a/app/dialog/export/exportvideotab.cpp +++ b/app/dialog/export/exportvideotab.cpp @@ -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 diff --git a/app/dialog/progress/progress.cpp b/app/dialog/progress/progress.cpp index 89d376337..9002f00ca 100644 --- a/app/dialog/progress/progress.cpp +++ b/app/dialog/progress/progress.cpp @@ -21,9 +21,12 @@ #include "progress.h" #include +#include #include #include +#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 diff --git a/app/dialog/progress/progress.h b/app/dialog/progress/progress.h index 642dcb656..a608fb849 100644 --- a/app/dialog/progress/progress.h +++ b/app/dialog/progress/progress.h @@ -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_; diff --git a/app/dialog/task/task.cpp b/app/dialog/task/task.cpp index c7eecf1b5..cad6e32a6 100644 --- a/app/dialog/task/task.cpp +++ b/app/dialog/task/task.cpp @@ -20,76 +20,69 @@ #include "task.h" -#include -#include +#include 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* task_watcher = new QFutureWatcher(); - // Move the task to this thread - task_->moveToThread(thread_); + // Listen for when the task finishes + connect(task_watcher, &QFutureWatcher::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* task_watcher = static_cast*>(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 diff --git a/app/dialog/task/task.h b/app/dialog/task/task.h index 0521f158b..e796ff248 100644 --- a/app/dialog/task/task.h +++ b/app/dialog/task/task.h @@ -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(); }; diff --git a/app/panel/taskmanager/taskmanager.cpp b/app/panel/taskmanager/taskmanager.cpp index 542651792..16cdd3a5c 100644 --- a/app/panel/taskmanager/taskmanager.cpp +++ b/app/panel/taskmanager/taskmanager.cpp @@ -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(); diff --git a/app/project/CMakeLists.txt b/app/project/CMakeLists.txt index 3507ee25d..1b9763aba 100644 --- a/app/project/CMakeLists.txt +++ b/app/project/CMakeLists.txt @@ -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 diff --git a/app/render/backend/CMakeLists.txt b/app/render/backend/CMakeLists.txt index 25541f319..b929d7a74 100644 --- a/app/render/backend/CMakeLists.txt +++ b/app/render/backend/CMakeLists.txt @@ -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 diff --git a/app/render/backend/exporter.cpp b/app/render/backend/exporter.cpp deleted file mode 100644 index bf3cefeff..000000000 --- a/app/render/backend/exporter.cpp +++ /dev/null @@ -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 . - -***/ - -#include "exporter.h" - -#include - -#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(dest_width) / static_cast(dest_height); - float source_ar = static_cast(source_width) / static_cast(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* watcher = new QFutureWatcher(); - - connect(watcher, &QFutureWatcher::finished, this, &Exporter::FrameColorFinished); - - QFuture 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& time_hash_map = video_backend_->frame_cache()->time_hash_map(); - QList hashes_already_seen; - QMap::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* watcher = static_cast< QFutureWatcher* >(sender()); - FramePtr frame = watcher->result(); - watcher->deleteLater(); - - debug_timer_.stop(); - - const QMap& 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 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 diff --git a/app/render/backend/exporter.h b/app/render/backend/exporter.h deleted file mode 100644 index 94380aeb2..000000000 --- a/app/render/backend/exporter.h +++ /dev/null @@ -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 . - -***/ - -#ifndef EXPORTER_H -#define EXPORTER_H - -#include -#include -#include -#include - -#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 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 diff --git a/app/task/CMakeLists.txt b/app/task/CMakeLists.txt index 1e472bf2b..a3b0e06ad 100644 --- a/app/task/CMakeLists.txt +++ b/app/task/CMakeLists.txt @@ -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 diff --git a/app/task/cache/cache.cpp b/app/task/cache/cache.cpp index cc1eab71b..f06cb07df 100644 --- a/app/task/cache/cache.cpp +++ b/app/task/cache/cache.cpp @@ -22,181 +22,35 @@ #include -#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 hash_future; -}; - -struct HashFrameFuturePair { - QByteArray hash; - QFuture frame_future; -}; - -struct HashDownloadFuturePair { - QByteArray hash; - QFuture 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(viewer_->parent()); + Sequence* s = static_cast(viewer()->parent()); if (s->workarea()->enabled()) { range_to_cache = range_to_cache.Intersects(s->workarea()->range()); } } - // Get hashes for each frame - QLinkedList 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 > times_to_render; - foreach (const TimeHashFuturePair& i, hash_list) { - times_to_render[i.hash_future.result()].append(i.time); - } - - // Render all frames necessary - QLinkedList render_lookup_table; - { - QLinkedList sorted_times; - QLinkedList::iterator sorted_iterator; - - // Rendering is more efficient if we cache in order - QMap< QByteArray, QLinkedList >::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 download_futures; - - // Iterators - QLinkedList::iterator i; - QLinkedList::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& 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(counter) / static_cast(nb_frames))); - - j = download_futures.erase(j); - } else { - j++; - } - } - } - } + return true; } OLIVE_NAMESPACE_EXIT diff --git a/app/task/cache/cache.h b/app/task/cache/cache.h index afaccb47a..74ac17d14 100644 --- a/app/task/cache/cache.h +++ b/app/task/cache/cache.h @@ -21,23 +21,22 @@ #ifndef CACHETASK_H #define CACHETASK_H -#include "node/output/viewer/viewer.h" -#include "task/task.h" +#include + +#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_; diff --git a/app/task/conform/conform.cpp b/app/task/conform/conform.cpp index 091c85e35..8d6bf34a2 100644 --- a/app/task/conform/conform.cpp +++ b/app/task/conform/conform.cpp @@ -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; } } } diff --git a/app/task/conform/conform.h b/app/task/conform/conform.h index 5d0fe9fb6..669774a5f 100644 --- a/app/task/conform/conform.h +++ b/app/task/conform/conform.h @@ -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_; diff --git a/app/task/export/CMakeLists.txt b/app/task/export/CMakeLists.txt new file mode 100644 index 000000000..104ca8fdf --- /dev/null +++ b/app/task/export/CMakeLists.txt @@ -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 . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + task/export/export.h + task/export/export.cpp + task/export/exportparams.h + task/export/exportparams.cpp + PARENT_SCOPE +) diff --git a/app/task/task.cpp b/app/task/export/export.cpp similarity index 69% rename from app/task/task.cpp rename to app/task/export/export.cpp index 601faf0b6..40ed46537 100644 --- a/app/task/task.cpp +++ b/app/task/export/export.cpp @@ -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 diff --git a/app/task/export/export.h b/app/task/export/export.h new file mode 100644 index 000000000..9c8cf6182 --- /dev/null +++ b/app/task/export/export.h @@ -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 . + +***/ + +#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 diff --git a/app/render/backend/exportparams.cpp b/app/task/export/exportparams.cpp similarity index 69% rename from app/render/backend/exportparams.cpp rename to app/task/export/exportparams.cpp index c5be39bc9..a603735ee 100644 --- a/app/render/backend/exportparams.cpp +++ b/app/task/export/exportparams.cpp @@ -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(dest_width) / static_cast(dest_height); + float source_ar = static_cast(source_width) / static_cast(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 diff --git a/app/render/backend/exportparams.h b/app/task/export/exportparams.h similarity index 87% rename from app/render/backend/exportparams.h rename to app/task/export/exportparams.h index dbac39220..a74ea3c5d 100644 --- a/app/render/backend/exportparams.h +++ b/app/task/export/exportparams.h @@ -21,6 +21,8 @@ #ifndef EXPORTPARAMS_H #define EXPORTPARAMS_H +#include + #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_; diff --git a/app/task/project/CMakeLists.txt b/app/task/project/CMakeLists.txt new file mode 100644 index 000000000..5f4dcd4c0 --- /dev/null +++ b/app/task/project/CMakeLists.txt @@ -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 . + +add_subdirectory(import) +add_subdirectory(load) +add_subdirectory(save) + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + PARENT_SCOPE +) diff --git a/app/task/project/import/CMakeLists.txt b/app/task/project/import/CMakeLists.txt new file mode 100644 index 000000000..77b4f31a6 --- /dev/null +++ b/app/task/project/import/CMakeLists.txt @@ -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 . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + task/project/import/import.h + task/project/import/import.cpp + PARENT_SCOPE +) diff --git a/app/project/projectimportmanager.cpp b/app/task/project/import/import.cpp similarity index 87% rename from app/project/projectimportmanager.cpp rename to app/task/project/import/import.cpp index 9b55bbee8..270e3a7d4 100644 --- a/app/project/projectimportmanager.cpp +++ b/app/task/project/import/import.cpp @@ -18,7 +18,7 @@ ***/ -#include "projectimportmanager.h" +#include "import.h" #include #include @@ -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()) { diff --git a/app/project/projectimportmanager.h b/app/task/project/import/import.h similarity index 77% rename from app/project/projectimportmanager.h rename to app/task/project/import/import.h index f42033f58..2a439e605 100644 --- a/app/project/projectimportmanager.h +++ b/app/task/project/import/import.h @@ -24,28 +24,32 @@ #include #include -#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_; diff --git a/app/task/project/load/CMakeLists.txt b/app/task/project/load/CMakeLists.txt new file mode 100644 index 000000000..84fc458c1 --- /dev/null +++ b/app/task/project/load/CMakeLists.txt @@ -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 . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + task/project/load/load.h + task/project/load/load.cpp + PARENT_SCOPE +) diff --git a/app/project/projectloadmanager.cpp b/app/task/project/load/load.cpp similarity index 85% rename from app/project/projectloadmanager.cpp rename to app/task/project/load/load.cpp index 4d7a97483..20d05ce7d 100644 --- a/app/project/projectloadmanager.cpp +++ b/app/task/project/load/load.cpp @@ -18,7 +18,7 @@ ***/ -#include "projectloadmanager.h" +#include "load.h" #include #include @@ -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; } } diff --git a/app/project/projectloadmanager.h b/app/task/project/load/load.h similarity index 80% rename from app/project/projectloadmanager.h rename to app/task/project/load/load.h index 6343293c6..0f2d88e82 100644 --- a/app/project/projectloadmanager.h +++ b/app/task/project/load/load.h @@ -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& GetLoadedProjects() + { + return projects_; + } -signals: - void ProjectLoaded(OLIVE_NAMESPACE::ProjectPtr project); +public slots: + virtual bool Run() override; private: + QList projects_; + QString filename_; }; diff --git a/app/task/project/save/CMakeLists.txt b/app/task/project/save/CMakeLists.txt new file mode 100644 index 000000000..11cc7273d --- /dev/null +++ b/app/task/project/save/CMakeLists.txt @@ -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 . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + task/project/save/save.h + task/project/save/save.cpp + PARENT_SCOPE +) diff --git a/app/project/projectsavemanager.cpp b/app/task/project/save/save.cpp similarity index 85% rename from app/project/projectsavemanager.cpp rename to app/task/project/save/save.cpp index f04055649..db026b032 100644 --- a/app/project/projectsavemanager.cpp +++ b/app/task/project/save/save.cpp @@ -18,20 +18,20 @@ ***/ -#include "projectsavemanager.h" +#include "save.h" #include #include 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 diff --git a/app/project/projectsavemanager.h b/app/task/project/save/save.h similarity index 83% rename from app/project/projectsavemanager.h rename to app/task/project/save/save.h index fb5d1fe79..ac3933294 100644 --- a/app/project/projectsavemanager.h +++ b/app/task/project/save/save.h @@ -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_; diff --git a/app/task/proxy/proxy.cpp b/app/task/proxy/proxy.cpp index f086508c7..5ff5f9cc5 100644 --- a/app/task/proxy/proxy.cpp +++ b/app/task/proxy/proxy.cpp @@ -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; } } } diff --git a/app/task/proxy/proxy.h b/app/task/proxy/proxy.h index 10610f074..977e8cdb1 100644 --- a/app/task/proxy/proxy.h +++ b/app/task/proxy/proxy.h @@ -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_; diff --git a/app/task/render/CMakeLists.txt b/app/task/render/CMakeLists.txt new file mode 100644 index 000000000..71cd61d65 --- /dev/null +++ b/app/task/render/CMakeLists.txt @@ -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 . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + task/render/render.h + task/render/render.cpp + PARENT_SCOPE +) diff --git a/app/task/render/render.cpp b/app/task/render/render.cpp new file mode 100644 index 000000000..2b1c4345d --- /dev/null +++ b/app/task/render/render.cpp @@ -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 . + +***/ + +#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 hash_future; +}; + +struct HashFrameFuturePair { + QByteArray hash; + QFuture frame_future; +}; + +struct HashDownloadFuturePair { + QByteArray hash; + QFuture 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 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 > times_to_render; + foreach (const TimeHashFuturePair& i, hash_list) { + times_to_render[i.hash_future.result()].append(i.time); + } + + // Render all frames necessary + QLinkedList render_lookup_table; + { + QLinkedList sorted_times; + QLinkedList::iterator sorted_iterator; + + // Rendering is more efficient if we cache in order + QMap< QByteArray, QLinkedList >::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 download_futures; + + // Iterators + QLinkedList::iterator i; + QLinkedList::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& 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(counter) / static_cast(nb_frames))); + + j = download_futures.erase(j); + } else { + j++; + } + } + } + } +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/task/render/render.h b/app/task/render/render.h new file mode 100644 index 000000000..8931c5245 --- /dev/null +++ b/app/task/render/render.h @@ -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 . + +***/ + +#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 diff --git a/app/task/task.h b/app/task/task.h index 32f52af8d..fd4aaaf98 100644 --- a/app/task/task.h +++ b/app/task/task.h @@ -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_; diff --git a/app/task/taskmanager.cpp b/app/task/taskmanager.cpp index 3223ca029..d16f3c549 100644 --- a/app/task/taskmanager.cpp +++ b/app/task/taskmanager.cpp @@ -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;istart(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* watcher = new QFutureWatcher(); + connect(watcher, &QFutureWatcher::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 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;imoveToThread(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;iRemoved(); - 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(sender()); + QFutureWatcher* watcher = static_cast*>(sender()); + Task* t = tasks_.value(watcher); - // Set this thread's active value to false - for (int i=0;ithread()) { - 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;ideleteLater(); - } - } - - // 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(sender()), kFinished); -} - -void TaskManager::TaskFailed() -{ - SetTaskStatus(static_cast(sender()), kError); } OLIVE_NAMESPACE_EXIT diff --git a/app/task/taskmanager.h b/app/task/taskmanager.h index 5adcfc0a7..139cdd7be 100644 --- a/app/task/taskmanager.h +++ b/app/task/taskmanager.h @@ -21,6 +21,7 @@ #ifndef TASKMANAGER_H #define TASKMANAGER_H +#include #include #include @@ -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 tasks_; + QHash*, Task*> tasks_; /** - * @brief Background threads to run tasks on + * @brief Internal list of failed tasks */ - QVector threads_; + QLinkedList 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(); }; diff --git a/app/widget/taskview/taskview.cpp b/app/widget/taskview/taskview.cpp index a898b1917..15625eda1 100644 --- a/app/widget/taskview/taskview.cpp +++ b/app/widget/taskview/taskview.cpp @@ -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 diff --git a/app/widget/taskview/taskview.h b/app/widget/taskview/taskview.h index d7f9831b1..18acb9346 100644 --- a/app/widget/taskview/taskview.h +++ b/app/widget/taskview/taskview.h @@ -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 items_; + }; OLIVE_NAMESPACE_EXIT diff --git a/app/widget/taskview/taskviewitem.cpp b/app/widget/taskview/taskviewitem.cpp index 80f0eb88f..19b4f4b73 100644 --- a/app/widget/taskview/taskviewitem.cpp +++ b/app/widget/taskview/taskviewitem.cpp @@ -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 diff --git a/app/widget/taskview/taskviewitem.h b/app/widget/taskview/taskviewitem.h index 24297fe57..35f1bc958 100644 --- a/app/widget/taskview/taskviewitem.h +++ b/app/widget/taskview/taskviewitem.h @@ -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_; diff --git a/app/window/mainwindow/mainmenu.cpp b/app/window/mainwindow/mainmenu.cpp index 92345a34f..004b03877 100644 --- a/app/window/mainwindow/mainmenu.cpp +++ b/app/window/mainwindow/mainmenu.cpp @@ -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(); - - 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()