export: made progress on headless export
Still incomplete and unusable, but less so than before.
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@ bool ProjectLoadTask::Run()
|
||||
|
||||
project_file.close();
|
||||
|
||||
emit ProgressChanged(1);
|
||||
|
||||
if (reader.hasError()) {
|
||||
SetError(reader.errorString());
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user