export: made progress on headless export

Still incomplete and unusable, but less so than before.
This commit is contained in:
itsmattkc
2020-06-17 18:06:50 +10:00
parent c3c26a8e24
commit a818e17c55
14 changed files with 337 additions and 88 deletions
+30
View File
@@ -0,0 +1,30 @@
/***
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 "cliexportmanager.h"
OLIVE_NAMESPACE_ENTER
CLIExportManager::CLIExportManager()
{
}
OLIVE_NAMESPACE_EXIT
+36
View File
@@ -0,0 +1,36 @@
/***
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 CLIEXPORTMANAGER_H
#define CLIEXPORTMANAGER_H
#include "task/export/export.h"
OLIVE_NAMESPACE_ENTER
class CLIExportManager : public QObject
{
public:
CLIExportManager();
};
OLIVE_NAMESPACE_EXIT
#endif // CLIEXPORTMANAGER_H
+5 -4
View File
@@ -27,9 +27,10 @@ OLIVE_NAMESPACE_ENTER
CLIProgressDialog::CLIProgressDialog(const QString& title, QObject *parent) :
QObject(parent),
title_(title),
progress_(0),
progress_(-1),
drawn_(false)
{
SetProgress(0);
}
void CLIProgressDialog::Update()
@@ -68,7 +69,7 @@ void CLIProgressDialog::Update()
std::cout << "[";
// Get UI bar progress
int bar_prog = qRound(progress_ * 0.01 * progress_bar_columns);
int bar_prog = qRound(progress_ * progress_bar_columns);
// Draw filled in bar
for (int i=0;i<bar_prog;i++) {
@@ -90,10 +91,10 @@ void CLIProgressDialog::Update()
std::cout << " ";
}
std::cout << progress_ << "% " << std::flush;
std::cout << qRound(progress_ * 100.0) << "% " << std::endl << std::flush;
}
void CLIProgressDialog::SetProgress(int p)
void CLIProgressDialog::SetProgress(double p)
{
if (progress_ != p) {
progress_ = p;
+2 -2
View File
@@ -34,14 +34,14 @@ public:
CLIProgressDialog(const QString &title, QObject* parent = nullptr);
public slots:
void SetProgress(int p);
void SetProgress(double p);
private:
void Update();
QString title_;
int progress_;
double progress_;
bool drawn_;
+8 -2
View File
@@ -23,9 +23,15 @@
OLIVE_NAMESPACE_ENTER
CLITaskDialog::CLITaskDialog(Task *task, QObject* parent) :
CLIProgressDialog(task->GetTitle(), parent)
CLIProgressDialog(task->GetTitle(), parent),
task_(task)
{
// FIXME: Still developing this, don't try to use
connect(task_, &Task::ProgressChanged, this, &CLITaskDialog::SetProgress);
}
bool CLITaskDialog::Run()
{
return task_->Start();
}
OLIVE_NAMESPACE_EXIT
+6
View File
@@ -28,9 +28,15 @@ OLIVE_NAMESPACE_ENTER
class CLITaskDialog : public CLIProgressDialog
{
Q_OBJECT
public:
CLITaskDialog(Task *task, QObject* parent = nullptr);
bool Run();
private:
Task* task_;
};
OLIVE_NAMESPACE_EXIT
+57
View File
@@ -158,6 +158,63 @@ void EncodingParams::SetExportLength(const rational &export_length)
export_length_ = export_length;
}
void EncodingParams::Save(QXmlStreamWriter *writer) const
{
writer->writeStartElement(QStringLiteral("encode"));
writer->writeTextElement(QStringLiteral("filename"), filename_);
writer->writeStartElement(QStringLiteral("video"));
writer->writeAttribute(QStringLiteral("enabled"), QString::number(video_enabled_));
if (video_enabled_) {
writer->writeTextElement(QStringLiteral("codec"), QString::number(video_codec_));
writer->writeTextElement(QStringLiteral("width"), QString::number(video_params_.width()));
writer->writeTextElement(QStringLiteral("height"), QString::number(video_params_.height()));
writer->writeTextElement(QStringLiteral("format"), QString::number(video_params_.format()));
writer->writeTextElement(QStringLiteral("timebase"), video_params_.time_base().toString());
writer->writeTextElement(QStringLiteral("divider"), QString::number(video_params_.divider()));
writer->writeTextElement(QStringLiteral("bitrate"), QString::number(video_bit_rate_));
writer->writeTextElement(QStringLiteral("maxbitrate"), QString::number(video_max_bit_rate_));
writer->writeTextElement(QStringLiteral("bufsize"), QString::number(video_buffer_size_));
writer->writeTextElement(QStringLiteral("threads"), QString::number(video_threads_));
if (!video_opts_.isEmpty()) {
writer->writeStartElement(QStringLiteral("opts"));
QHash<QString, QString>::const_iterator i;
for (i=video_opts_.constBegin(); i!=video_opts_.constEnd(); i++) {
writer->writeStartElement(QStringLiteral("entry"));
writer->writeTextElement(QStringLiteral("key"), i.key());
writer->writeTextElement(QStringLiteral("value"), i.value());
writer->writeEndElement(); // entry
}
writer->writeEndElement(); // opts
}
}
writer->writeEndElement(); // video
writer->writeStartElement(QStringLiteral("audio"));
writer->writeAttribute(QStringLiteral("enabled"), QString::number(audio_enabled_));
if (audio_enabled_) {
writer->writeTextElement(QStringLiteral("codec"), QString::number(audio_codec_));
writer->writeTextElement(QStringLiteral("samplerate"), QString::number(audio_params_.sample_rate()));
writer->writeTextElement(QStringLiteral("channellayout"), QString::number(audio_params_.channel_layout()));
writer->writeTextElement(QStringLiteral("format"), QString::number(audio_params_.format()));
}
writer->writeEndElement(); // audio
writer->writeEndElement(); // encode
}
Encoder* Encoder::CreateFromID(const QString &id, const EncodingParams& params)
{
Q_UNUSED(id)
+3
View File
@@ -23,6 +23,7 @@
#include <memory>
#include <QString>
#include <QXmlStreamWriter>
#include "codec/exportcodec.h"
#include "codec/exportformat.h"
@@ -69,6 +70,8 @@ public:
const rational& GetExportLength() const;
void SetExportLength(const rational& GetExportLength);
virtual void Save(QXmlStreamWriter* writer) const;
private:
QString filename_;
+151 -55
View File
@@ -81,18 +81,17 @@ Core *Core::instance()
return &instance_;
}
bool Core::Start()
int Core::execute(QCoreApplication* a)
{
// Reset config (Config sets to default on construction already, but we do it again here as a workaround that fixes
// the fact that some of the config paths set by default rely on the app name having been set (in main())
Config::Current().SetDefaults();
int exit_code = 1;
// Start core
OLIVE_NAMESPACE::Core::instance()->Start();
//
// Parse command line arguments
//
QCoreApplication* app = QCoreApplication::instance();
QCommandLineParser parser;
parser.addHelpOption();
parser.addVersionOption();
@@ -111,7 +110,7 @@ bool Core::Start()
parser.addOption(headless_export_option);
// Parse options
parser.process(*app);
parser.process(*a);
QStringList args = parser.positionalArguments();
@@ -120,6 +119,44 @@ bool Core::Start()
startup_project_ = args.first();
}
gui_active_ = !parser.isSet(headless_export_option);
if (gui_active_) {
// Start GUI
StartGUI(parser.isSet(fullscreen_option));
// If we have a startup
QMetaObject::invokeMethod(this, "OpenStartupProject", Qt::QueuedConnection);
// Run application loop and receive exit code
exit_code = a->exec();
} else {
if (parser.isSet(headless_export_option)) {
// Start a headless export
if (StartHeadlessExport()) {
exit_code = 0;
}
}
}
// Clear core memory
OLIVE_NAMESPACE::Core::instance()->Stop();
return exit_code;
}
void Core::Start()
{
// Reset config (Config sets to default on construction already, but we do it again here as a workaround that fixes
// the fact that some of the config paths set by default rely on the app name having been set (in main())
Config::Current().SetDefaults();
// Declare custom types for Qt signal/slot system
DeclareTypesForQt();
@@ -141,53 +178,6 @@ bool Core::Start()
//
qInfo() << "Using Qt version:" << qVersion();
gui_active_ = !parser.isSet(headless_export_option);
if (gui_active_) {
// Start GUI
StartGUI(parser.isSet(fullscreen_option));
// Load startup project
if (!startup_project_.isEmpty() && !QFileInfo::exists(startup_project_)) {
QMessageBox::warning(main_window(),
tr("Failed to open startup file"),
tr("The project \"%1\" doesn't exist. A new project will be started instead.").arg(startup_project_),
QMessageBox::Ok);
startup_project_.clear();
}
if (startup_project_.isEmpty()) {
// If no load project is set, create a new one on open
CreateNewProject();
} else {
OpenProjectInternal(startup_project_);
}
return true;
} else {
if (parser.isSet(headless_export_option)) {
if (startup_project_.isEmpty()) {
qCritical().noquote() << tr("You must specify a project file to export");
} else {
OpenProjectInternal(startup_project_);
qDebug() << "Ready for exporting!";
return true;
}
}
// Error fallback
return false;
}
}
void Core::Stop()
@@ -539,6 +529,106 @@ void Core::ProjectWasModified(bool e)
}
}
bool Core::StartHeadlessExport()
{
if (startup_project_.isEmpty()) {
qCritical().noquote() << tr("You must specify a project file to export");
return false;
}
if (!QFileInfo::exists(startup_project_)) {
qCritical().noquote() << tr("Specified project does not exist");
return false;
}
// Start a load task and try running it
ProjectLoadTask plm(startup_project_);
CLITaskDialog task_dialog(&plm);
if (task_dialog.Run()) {
ProjectPtr p = plm.GetLoadedProjects().first();
QList<ItemPtr> items = p->get_items_of_type(Item::kSequence);
// Check if this project contains sequences
if (items.isEmpty()) {
qCritical().noquote() << tr("Project contains no sequences, nothing to export");
return false;
}
SequencePtr sequence = nullptr;
// Check if this project contains multiple sequences
if (items.size() > 1) {
qInfo().noquote() << tr("This project has multiple sequences. Which do you wish to export?");
for (int i=0;i<items.size();i++) {
std::cout << "[" << i << "] " << items.at(i)->name().toStdString();
}
QTextStream stream(stdin);
QString sequence_read;
int sequence_index = -1;
QString quit_code = QStringLiteral("q");
std::string prompt = tr("Enter number (or %1 to cancel): ").arg(quit_code).toStdString();
forever {
std::cout << prompt;
stream.readLineInto(&sequence_read);
if (!QString::compare(sequence_read, quit_code, Qt::CaseInsensitive)) {
return false;
}
bool ok;
sequence_index = sequence_read.toInt(&ok);
if (ok && sequence_index >= 0 && sequence_index < items.size()) {
break;
} else {
qCritical().noquote() << tr("Invalid sequence number");
}
}
sequence = std::static_pointer_cast<Sequence>(items.at(sequence_index));
} else {
sequence = std::static_pointer_cast<Sequence>(items.first());
}
ExportParams params;
ExportTask export_task(sequence->viewer_output(), p->color_manager(), params);
CLITaskDialog export_dialog(&export_task);
if (export_dialog.Run()) {
qInfo().noquote() << tr("Export succeeded");
return true;
} else {
qInfo().noquote() << tr("Export failed: %1").arg(export_task.GetError());
return false;
}
} else {
qCritical().noquote() << tr("Project failed to load: %1").arg(plm.GetError());
return false;
}
}
void Core::OpenStartupProject()
{
// Load startup project
if (!startup_project_.isEmpty() && !QFileInfo::exists(startup_project_)) {
QMessageBox::warning(main_window_,
tr("Failed to open startup file"),
tr("The project \"%1\" doesn't exist. A new project will be started instead.").arg(startup_project_),
QMessageBox::Ok);
startup_project_.clear();
}
if (startup_project_.isEmpty()) {
// If no load project is set, create a new one on open
CreateNewProject();
} else {
OpenProjectInternal(startup_project_);
}
}
void Core::DeclareTypesForQt()
{
qRegisterMetaType<rational>();
@@ -559,6 +649,7 @@ void Core::DeclareTypesForQt()
qRegisterMetaType<OLIVE_NAMESPACE::SampleJob>();
qRegisterMetaType<OLIVE_NAMESPACE::ShaderJob>();
qRegisterMetaType<OLIVE_NAMESPACE::GenerateJob>();
qRegisterMetaType<OLIVE_NAMESPACE::VideoParams>();
}
void Core::StartGUI(bool full_screen)
@@ -877,6 +968,11 @@ bool Core::SaveProjectAs(ProjectPtr p)
GetProjectFilter());
if (!fn.isEmpty()) {
QString extension(QStringLiteral(".ove"));
if (!fn.endsWith(extension, Qt::CaseInsensitive)) {
fn.append(extension);
}
p->set_filename(fn);
SaveProjectInternal(p);
@@ -927,7 +1023,7 @@ void Core::OpenProjectInternal(const QString &filename)
//connect(plm, &ProjectLoadManager::ProjectLoaded, this, &Core::AddOpenProject);
CLITaskDialog task_dialog(plm);
}
}
+12 -6
View File
@@ -66,12 +66,14 @@ public:
*/
static Core* instance();
int execute(QCoreApplication *a);
/**
* @brief Start Olive Core
*
* Main application launcher. Parses command line arguments and constructs main window (if entering a GUI mode).
*/
bool Start();
void Start();
/**
* @brief Stop Olive Core
@@ -405,11 +407,6 @@ private:
*/
void PushRecentlyOpenedProject(const QString &s);
/**
* @brief Internal project open
*/
void OpenProjectInternal(const QString& filename);
/**
* @brief Declare custom types/classes for Qt's signal/slot system
*
@@ -507,6 +504,15 @@ private slots:
void ProjectWasModified(bool e);
bool StartHeadlessExport();
void OpenStartupProject();
/**
* @brief Internal project open
*/
void OpenProjectInternal(const QString& filename);
};
OLIVE_NAMESPACE_EXIT
+1 -19
View File
@@ -86,23 +86,5 @@ int main(int argc, char *argv[]) {
avfilter_register_all();
#endif
int exit_code;
// Start core
if (OLIVE_NAMESPACE::Core::instance()->Start()) {
// Run application loop and receive exit code
exit_code = a.exec();
} else {
// Core failed to start, exit now
exit_code = 1;
}
// Clear core memory
OLIVE_NAMESPACE::Core::instance()->Stop();
return exit_code;
return OLIVE_NAMESPACE::Core::instance()->execute(&a);
}
+22
View File
@@ -100,4 +100,26 @@ QMatrix4x4 ExportParams::GenerateMatrix(ExportParams::VideoScalingMethod method,
return preview_matrix;
}
void ExportParams::Save(QXmlStreamWriter *writer) const
{
writer->writeStartElement(QStringLiteral("export"));
writer->writeTextElement(QStringLiteral("encoder"), encoder_id_);
writer->writeTextElement(QStringLiteral("vscale"), QString::number(video_scaling_method_));
writer->writeTextElement(QStringLiteral("range"), QString::number(has_custom_range_));
writer->writeTextElement(QStringLiteral("customrangein"), custom_range_.in().toString());
writer->writeTextElement(QStringLiteral("customrangeout"), custom_range_.out().toString());
// FIXME: Change this when color chains are implemented
writer->writeTextElement(QStringLiteral("color"), color_transform_.output());
EncodingParams::Save(writer);
writer->writeEndElement(); // export
}
OLIVE_NAMESPACE_EXIT
+2
View File
@@ -56,6 +56,8 @@ public:
int source_width, int source_height,
int dest_width, int dest_height);
virtual void Save(QXmlStreamWriter* writer) const override;
private:
QString encoder_id_;
+2
View File
@@ -70,6 +70,8 @@ bool ProjectLoadTask::Run()
project_file.close();
emit ProgressChanged(1);
if (reader.hasError()) {
SetError(reader.errorString());
return false;