task: massively simplified task system
Made various changes and fixes to the task system: - Tasks are built around QtConcurrent rather than QThread. Reduces code complexity significantly. - Task error reporting is now streamlined in both TaskManager and TaskDialog. - Moved ProjectImport/Save/LoadManager to the app/task folder
This commit is contained in:
@@ -19,11 +19,6 @@ add_subdirectory(opengl)
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
|
||||
render/backend/exporter.h
|
||||
render/backend/exporter.cpp
|
||||
render/backend/exportparams.h
|
||||
render/backend/exportparams.cpp
|
||||
|
||||
render/backend/renderbackend.h
|
||||
render/backend/renderbackend.cpp
|
||||
|
||||
|
||||
@@ -1,373 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "exporter.h"
|
||||
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
#include "render/backend/opengl/openglbackend.h"
|
||||
#include "render/colormanager.h"
|
||||
#include "render/pixelformat.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
Exporter::Exporter(ViewerOutput *viewer_node,
|
||||
ColorManager *color_manager,
|
||||
const ExportParams& params,
|
||||
QObject* parent) :
|
||||
QObject(parent),
|
||||
viewer_node_(viewer_node),
|
||||
params_(params),
|
||||
renderer_(nullptr),
|
||||
export_status_(false),
|
||||
export_msg_(tr("Export hasn't started yet"))
|
||||
{
|
||||
encoder_ = Encoder::CreateFromID(params_.encoder(), params_);
|
||||
|
||||
video_done_ = !params_.video_enabled();
|
||||
audio_done_ = !params_.audio_enabled();
|
||||
|
||||
debug_timer_.setInterval(5000);
|
||||
connect(&debug_timer_, &QTimer::timeout, this, &Exporter::DebugTimerMessage);
|
||||
|
||||
connect(this, &Exporter::ExportEnded, this, &Exporter::deleteLater);
|
||||
|
||||
if (params_.has_custom_range()) {
|
||||
export_range_ = params_.custom_range();
|
||||
} else {
|
||||
export_range_ = TimeRange(0, viewer_node_->GetLength());
|
||||
}
|
||||
|
||||
if (params_.video_enabled()) {
|
||||
|
||||
// If a transformation matrix is applied to this video, create it here
|
||||
if (params_.video_scaling_method() != ExportParams::kStretch) {
|
||||
transform_ = GenerateMatrix(params_.video_scaling_method(),
|
||||
viewer_node_->video_params().width(),
|
||||
viewer_node_->video_params().height(),
|
||||
params_.video_params().width(),
|
||||
params_.video_params().height());
|
||||
}
|
||||
|
||||
// Create color processor
|
||||
color_processor_ = ColorProcessor::Create(color_manager,
|
||||
color_manager->GetReferenceColorSpace(),
|
||||
params.color_transform());
|
||||
}
|
||||
}
|
||||
|
||||
bool Exporter::GetExportStatus() const
|
||||
{
|
||||
return export_status_;
|
||||
}
|
||||
|
||||
const QString &Exporter::GetExportError() const
|
||||
{
|
||||
return export_msg_;
|
||||
}
|
||||
|
||||
void Exporter::Cancel()
|
||||
{
|
||||
if (renderer_) {
|
||||
renderer_->CancelQueue();
|
||||
renderer_->deleteLater();
|
||||
renderer_ = nullptr;
|
||||
}
|
||||
|
||||
SetExportMessage(tr("User cancelled export"));
|
||||
ExportStopped();
|
||||
}
|
||||
|
||||
void Exporter::StartExporting()
|
||||
{
|
||||
// Default to error state until ExportEnd is called
|
||||
export_status_ = false;
|
||||
|
||||
// Create renderers
|
||||
renderer_ = new OpenGLBackend();
|
||||
renderer_->SetViewerNode(viewer_node_);
|
||||
|
||||
if (!video_done_) {
|
||||
renderer_->SetPixelFormat(params_.video_params().format());
|
||||
renderer_->SetMode(params_.video_params().mode());
|
||||
|
||||
waiting_for_frame_ = 0;
|
||||
}
|
||||
|
||||
if (!audio_done_) {
|
||||
renderer_->SetSampleFormat(params_.audio_params().format());
|
||||
}
|
||||
|
||||
// Open encoder and wait for result
|
||||
connect(encoder_, &Encoder::OpenSucceeded, this, &Exporter::EncoderOpenedSuccessfully, Qt::QueuedConnection);
|
||||
connect(encoder_, &Encoder::OpenFailed, this, &Exporter::EncoderOpenFailed, Qt::QueuedConnection);
|
||||
connect(encoder_, &Encoder::AudioComplete, this, &Exporter::AudioEncodeComplete, Qt::QueuedConnection);
|
||||
|
||||
QMetaObject::invokeMethod(encoder_,
|
||||
"Open",
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void Exporter::SetExportMessage(const QString &s)
|
||||
{
|
||||
export_msg_ = s;
|
||||
}
|
||||
|
||||
void Exporter::ExportSucceeded()
|
||||
{
|
||||
if (!audio_done_ || !video_done_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (renderer_) {
|
||||
renderer_->deleteLater();
|
||||
renderer_ = nullptr;
|
||||
}
|
||||
|
||||
export_status_ = true;
|
||||
|
||||
connect(encoder_, &Encoder::Closed, this, &Exporter::EncoderClosed);
|
||||
|
||||
QMetaObject::invokeMethod(encoder_,
|
||||
"Close",
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void Exporter::ExportStopped()
|
||||
{
|
||||
emit ExportEnded();
|
||||
encoder_->deleteLater();
|
||||
}
|
||||
|
||||
void Exporter::EncodeFrame()
|
||||
{
|
||||
while (cached_frames_.contains(waiting_for_frame_)) {
|
||||
FramePtr frame = cached_frames_.take(waiting_for_frame_);
|
||||
|
||||
// Encode (may require re-associating alpha?)
|
||||
QMetaObject::invokeMethod(encoder_,
|
||||
"WriteFrame",
|
||||
Qt::QueuedConnection,
|
||||
OLIVE_NS_ARG(FramePtr, frame),
|
||||
OLIVE_NS_ARG(rational, waiting_for_frame_));
|
||||
|
||||
waiting_for_frame_ += params_.video_params().time_base();
|
||||
|
||||
// Calculate progress
|
||||
emit ProgressChanged(waiting_for_frame_.toDouble() / viewer_node_->GetLength().toDouble());
|
||||
}
|
||||
|
||||
if (waiting_for_frame_ >= viewer_node_->GetLength()) {
|
||||
video_done_ = true;
|
||||
debug_timer_.stop();
|
||||
|
||||
ExportSucceeded();
|
||||
}
|
||||
}
|
||||
|
||||
QMatrix4x4 Exporter::GenerateMatrix(ExportParams::VideoScalingMethod method, int source_width, int source_height, int dest_width, int dest_height)
|
||||
{
|
||||
QMatrix4x4 preview_matrix;
|
||||
|
||||
if (method == ExportParams::kStretch) {
|
||||
return preview_matrix;
|
||||
}
|
||||
|
||||
float export_ar = static_cast<float>(dest_width) / static_cast<float>(dest_height);
|
||||
float source_ar = static_cast<float>(source_width) / static_cast<float>(source_height);
|
||||
|
||||
if (qFuzzyCompare(export_ar, source_ar)) {
|
||||
return preview_matrix;
|
||||
}
|
||||
|
||||
if ((export_ar > source_ar) == (method == ExportParams::kFit)) {
|
||||
preview_matrix.scale(source_ar / export_ar, 1.0F);
|
||||
} else {
|
||||
preview_matrix.scale(1.0F, export_ar / source_ar);
|
||||
}
|
||||
|
||||
return preview_matrix;
|
||||
}
|
||||
|
||||
FramePtr FrameColorConvert(ColorProcessorPtr processor, FramePtr frame)
|
||||
{
|
||||
qDebug() << "Converting" << frame->timestamp();
|
||||
|
||||
// OCIO conversion requires a frame in 32F format
|
||||
if (frame->format() != PixelFormat::PIX_FMT_RGBA32F) {
|
||||
frame = PixelFormat::ConvertPixelFormat(frame, PixelFormat::PIX_FMT_RGBA32F);
|
||||
}
|
||||
|
||||
// Color conversion must be done with unassociated alpha, and the pipeline is always associated
|
||||
ColorManager::DisassociateAlpha(frame);
|
||||
|
||||
// Convert color space
|
||||
processor->ConvertFrame(frame);
|
||||
|
||||
// Re-associate alpha
|
||||
ColorManager::ReassociateAlpha(frame);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
void Exporter::FrameRendered(FramePtr frame)
|
||||
{
|
||||
// Start color space conversion in another thread
|
||||
QFutureWatcher<FramePtr>* watcher = new QFutureWatcher<FramePtr>();
|
||||
|
||||
connect(watcher, &QFutureWatcher<FramePtr>::finished, this, &Exporter::FrameColorFinished);
|
||||
|
||||
QFuture<FramePtr> future = QtConcurrent::run(FrameColorConvert,
|
||||
color_processor_,
|
||||
frame);
|
||||
|
||||
watcher->setFuture(future);
|
||||
}
|
||||
|
||||
void Exporter::AudioRendered()
|
||||
{
|
||||
/*
|
||||
// Retrieve the audio filename
|
||||
QString cache_fn = audio_backend_->CachePathName();
|
||||
|
||||
QMetaObject::invokeMethod(encoder_,
|
||||
"WriteAudio",
|
||||
Qt::QueuedConnection,
|
||||
OLIVE_NS_ARG(AudioRenderingParams, audio_backend_->params()),
|
||||
Q_ARG(const QString&, cache_fn),
|
||||
OLIVE_NS_ARG(TimeRange, export_range_));
|
||||
*/
|
||||
}
|
||||
|
||||
void Exporter::AudioEncodeComplete()
|
||||
{
|
||||
audio_done_ = true;
|
||||
|
||||
ExportSucceeded();
|
||||
}
|
||||
|
||||
void Exporter::EncoderOpenedSuccessfully()
|
||||
{
|
||||
/*
|
||||
// Invalidate caches
|
||||
if (!video_done_) {
|
||||
// First we generate the hashes so we know exactly how many frames we need
|
||||
video_backend_->SetOperatingMode(VideoRenderWorker::kHashOnly);
|
||||
connect(video_backend_, &VideoRenderBackend::QueueComplete, this, &Exporter::VideoHashesComplete);
|
||||
|
||||
video_backend_->InvalidateCache(export_range_, nullptr);
|
||||
}
|
||||
|
||||
if (!audio_done_) {
|
||||
// We set the audio backend to render the full sequence to the disk
|
||||
connect(audio_backend_, &AudioRenderBackend::AudioComplete, this, &Exporter::AudioRendered);
|
||||
|
||||
audio_backend_->InvalidateCache(export_range_, nullptr);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void Exporter::EncoderOpenFailed()
|
||||
{
|
||||
SetExportMessage(tr("Failed to open encoder"));
|
||||
ExportStopped();
|
||||
}
|
||||
|
||||
void Exporter::EncoderClosed()
|
||||
{
|
||||
emit ProgressChanged(100);
|
||||
ExportStopped();
|
||||
}
|
||||
|
||||
void Exporter::VideoHashesComplete()
|
||||
{
|
||||
/*
|
||||
// We've got our hashes, time to kick off actual rendering
|
||||
disconnect(video_backend_, &VideoRenderBackend::QueueComplete, this, &Exporter::VideoHashesComplete);
|
||||
|
||||
// Determine what frames will be hashed
|
||||
TimeRangeList ranges;
|
||||
ranges.append(TimeRange(0, viewer_node_->GetLength()));
|
||||
|
||||
// Set video backend to render mode but NOT hash or download
|
||||
video_backend_->SetOperatingMode(VideoRenderWorker::kRenderOnly);
|
||||
video_backend_->SetOnlySignalLastFrameRequested(false);
|
||||
video_backend_->SetFrameGenerationParams(params_.video_params().width(), params_.video_params().height(), transform_);
|
||||
|
||||
connect(video_backend_, &VideoRenderBackend::GeneratedFrame, this, &Exporter::FrameRendered);
|
||||
|
||||
// Remove duplicate frames from cache invalidation
|
||||
const QMap<rational, QByteArray>& time_hash_map = video_backend_->frame_cache()->time_hash_map();
|
||||
QList<QByteArray> hashes_already_seen;
|
||||
QMap<rational, QByteArray>::const_iterator i;
|
||||
|
||||
for (i=time_hash_map.begin(); i!=time_hash_map.end(); i++) {
|
||||
if (hashes_already_seen.contains(i.value())) {
|
||||
ranges.RemoveTimeRange(TimeRange(i.key(), i.key() + params_.video_params().time_base()));
|
||||
} else {
|
||||
hashes_already_seen.append(i.value());
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const TimeRange& range, ranges) {
|
||||
video_backend_->InvalidateCache(range, nullptr);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void Exporter::DebugTimerMessage()
|
||||
{
|
||||
qDebug() << "Still waiting for" << waiting_for_frame_.toDouble();
|
||||
}
|
||||
|
||||
void Exporter::FrameColorFinished()
|
||||
{
|
||||
if (!renderer_) {
|
||||
return;
|
||||
}
|
||||
|
||||
QFutureWatcher<FramePtr>* watcher = static_cast< QFutureWatcher<FramePtr>* >(sender());
|
||||
FramePtr frame = watcher->result();
|
||||
watcher->deleteLater();
|
||||
|
||||
debug_timer_.stop();
|
||||
|
||||
const QMap<rational, QByteArray>& time_hash_map = viewer_node_->video_frame_cache()->time_hash_map();
|
||||
|
||||
QByteArray this_hash = time_hash_map.value(frame->timestamp());
|
||||
|
||||
qDebug() << "Received" << this_hash.toHex();
|
||||
|
||||
QList<rational> matching_times = time_hash_map.keys(this_hash);
|
||||
|
||||
foreach (const rational& t, matching_times) {
|
||||
qDebug() << " Matches" << t.toDouble();
|
||||
|
||||
cached_frames_.insert(t, frame);
|
||||
}
|
||||
|
||||
qDebug() << " Waiting for" << waiting_for_frame_.toDouble();
|
||||
|
||||
debug_timer_.start();
|
||||
|
||||
EncodeFrame();
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -1,124 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef EXPORTER_H
|
||||
#define EXPORTER_H
|
||||
|
||||
#include <QMatrix4x4>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
#include <QObject>
|
||||
|
||||
#include "codec/encoder.h"
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "render/backend/exportparams.h"
|
||||
#include "render/backend/renderbackend.h"
|
||||
#include "render/colorprocessor.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class Exporter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Exporter(ViewerOutput* viewer_node,
|
||||
ColorManager* color_manager,
|
||||
const ExportParams& params,
|
||||
QObject* parent = nullptr);
|
||||
|
||||
bool GetExportStatus() const;
|
||||
const QString& GetExportError() const;
|
||||
|
||||
void Cancel();
|
||||
|
||||
static QMatrix4x4 GenerateMatrix(ExportParams::VideoScalingMethod method, int source_width, int source_height, int dest_width, int dest_height);
|
||||
|
||||
public slots:
|
||||
void StartExporting();
|
||||
|
||||
signals:
|
||||
void ProgressChanged(double);
|
||||
|
||||
void ExportEnded();
|
||||
|
||||
protected:
|
||||
void SetExportMessage(const QString& s);
|
||||
|
||||
private:
|
||||
void ExportSucceeded();
|
||||
|
||||
void ExportStopped();
|
||||
|
||||
void EncodeFrame();
|
||||
|
||||
ViewerOutput* viewer_node_;
|
||||
|
||||
ColorProcessorPtr color_processor_;
|
||||
|
||||
ExportParams params_;
|
||||
|
||||
// Renderers
|
||||
RenderBackend* renderer_;
|
||||
|
||||
// Export transform
|
||||
QMatrix4x4 transform_;
|
||||
|
||||
bool video_done_;
|
||||
|
||||
bool audio_done_;
|
||||
|
||||
Encoder* encoder_;
|
||||
|
||||
bool export_status_;
|
||||
|
||||
QString export_msg_;
|
||||
|
||||
TimeRange export_range_;
|
||||
|
||||
rational waiting_for_frame_;
|
||||
|
||||
QHash<rational, FramePtr> cached_frames_;
|
||||
|
||||
QTimer debug_timer_;
|
||||
|
||||
private slots:
|
||||
void FrameRendered(FramePtr frame);
|
||||
|
||||
void AudioRendered();
|
||||
|
||||
void AudioEncodeComplete();
|
||||
|
||||
void EncoderOpenedSuccessfully();
|
||||
|
||||
void EncoderOpenFailed();
|
||||
|
||||
void EncoderClosed();
|
||||
|
||||
void VideoHashesComplete();
|
||||
|
||||
void DebugTimerMessage();
|
||||
|
||||
void FrameColorFinished();
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // EXPORTER_H
|
||||
@@ -1,77 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "exportparams.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
ExportParams::ExportParams() :
|
||||
video_scaling_method_(kStretch),
|
||||
has_custom_range_(false)
|
||||
{
|
||||
}
|
||||
|
||||
const QString &ExportParams::encoder() const
|
||||
{
|
||||
return encoder_id_;
|
||||
}
|
||||
|
||||
void ExportParams::set_encoder(const QString &id)
|
||||
{
|
||||
encoder_id_ = id;
|
||||
}
|
||||
|
||||
bool ExportParams::has_custom_range() const
|
||||
{
|
||||
return has_custom_range_;
|
||||
}
|
||||
|
||||
const TimeRange &ExportParams::custom_range() const
|
||||
{
|
||||
return custom_range_;
|
||||
}
|
||||
|
||||
void ExportParams::set_custom_range(const TimeRange &custom_range)
|
||||
{
|
||||
has_custom_range_ = true;
|
||||
custom_range_ = custom_range;
|
||||
}
|
||||
|
||||
const ExportParams::VideoScalingMethod &ExportParams::video_scaling_method() const
|
||||
{
|
||||
return video_scaling_method_;
|
||||
}
|
||||
|
||||
void ExportParams::set_video_scaling_method(const ExportParams::VideoScalingMethod &video_scaling_method)
|
||||
{
|
||||
video_scaling_method_ = video_scaling_method;
|
||||
}
|
||||
|
||||
const ColorTransform &ExportParams::color_transform() const
|
||||
{
|
||||
return color_transform_;
|
||||
}
|
||||
|
||||
void ExportParams::set_color_transform(const ColorTransform &color_transform)
|
||||
{
|
||||
color_transform_ = color_transform;
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -1,67 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef EXPORTPARAMS_H
|
||||
#define EXPORTPARAMS_H
|
||||
|
||||
#include "codec/encoder.h"
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "render/colortransform.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class ExportParams : public EncodingParams {
|
||||
public:
|
||||
enum VideoScalingMethod {
|
||||
kFit,
|
||||
kStretch,
|
||||
kCrop
|
||||
};
|
||||
|
||||
ExportParams();
|
||||
|
||||
const QString& encoder() const;
|
||||
void set_encoder(const QString& id);
|
||||
|
||||
bool has_custom_range() const;
|
||||
const TimeRange& custom_range() const;
|
||||
void set_custom_range(const TimeRange& custom_range);
|
||||
|
||||
const VideoScalingMethod& video_scaling_method() const;
|
||||
void set_video_scaling_method(const VideoScalingMethod& video_scaling_method);
|
||||
|
||||
const ColorTransform& color_transform() const;
|
||||
void set_color_transform(const ColorTransform& color_transform);
|
||||
|
||||
private:
|
||||
QString encoder_id_;
|
||||
|
||||
VideoScalingMethod video_scaling_method_;
|
||||
|
||||
bool has_custom_range_;
|
||||
TimeRange custom_range_;
|
||||
|
||||
ColorTransform color_transform_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // EXPORTPARAMS_H
|
||||
Reference in New Issue
Block a user