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:
@@ -16,12 +16,14 @@
|
||||
|
||||
add_subdirectory(cache)
|
||||
add_subdirectory(conform)
|
||||
add_subdirectory(export)
|
||||
add_subdirectory(project)
|
||||
add_subdirectory(proxy)
|
||||
add_subdirectory(render)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
task/task.h
|
||||
task/task.cpp
|
||||
task/taskmanager.h
|
||||
task/taskmanager.cpp
|
||||
PARENT_SCOPE
|
||||
|
||||
Vendored
+7
-153
@@ -22,181 +22,35 @@
|
||||
|
||||
#include <QLinkedList>
|
||||
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "project/item/sequence/sequence.h"
|
||||
#include "render/backend/opengl/openglbackend.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
CacheTask::CacheTask(ViewerOutput* viewer, int divider, bool in_out_only) :
|
||||
viewer_(viewer),
|
||||
RenderTask(viewer),
|
||||
in_out_only_(in_out_only),
|
||||
divider_(divider)
|
||||
{
|
||||
SetTitle(tr("Caching \"%1\"").arg(viewer_->media_name()));
|
||||
SetTitle(tr("Caching \"%1\"").arg(viewer->media_name()));
|
||||
}
|
||||
|
||||
struct TimeHashFuturePair {
|
||||
rational time;
|
||||
QFuture<QByteArray> hash_future;
|
||||
};
|
||||
|
||||
struct HashFrameFuturePair {
|
||||
QByteArray hash;
|
||||
QFuture<FramePtr> frame_future;
|
||||
};
|
||||
|
||||
struct HashDownloadFuturePair {
|
||||
QByteArray hash;
|
||||
QFuture<void> download_future;
|
||||
};
|
||||
|
||||
struct HashTimePair {
|
||||
rational time;
|
||||
QByteArray hash;
|
||||
};
|
||||
|
||||
void CacheTask::Action()
|
||||
bool CacheTask::Run()
|
||||
{
|
||||
OpenGLBackend backend;
|
||||
|
||||
RenderMode::Mode mode = RenderMode::kOffline;
|
||||
PixelFormat::Format format = PixelFormat::instance()->GetConfiguredFormatForMode(mode);
|
||||
|
||||
backend.SetAudioEnabled(false);
|
||||
backend.SetViewerNode(viewer_);
|
||||
backend.SetPixelFormat(format);
|
||||
backend.SetMode(mode);
|
||||
backend.SetDivider(divider_);
|
||||
backend.SetSampleFormat(SampleFormat::kInternalFormat);
|
||||
|
||||
// Get list of invalidated ranges
|
||||
TimeRangeList range_to_cache = viewer_->video_frame_cache()->GetInvalidatedRanges();
|
||||
TimeRangeList range_to_cache = viewer()->video_frame_cache()->GetInvalidatedRanges();
|
||||
|
||||
// If we're caching only in-out, limit the range to that
|
||||
if (in_out_only_) {
|
||||
Sequence* s = static_cast<Sequence*>(viewer_->parent());
|
||||
Sequence* s = static_cast<Sequence*>(viewer()->parent());
|
||||
|
||||
if (s->workarea()->enabled()) {
|
||||
range_to_cache = range_to_cache.Intersects(s->workarea()->range());
|
||||
}
|
||||
}
|
||||
// Get hashes for each frame
|
||||
QLinkedList<TimeHashFuturePair> hash_list;
|
||||
|
||||
while (!range_to_cache.isEmpty()) {
|
||||
const TimeRange& range = range_to_cache.first();
|
||||
Render(range_to_cache, 2);
|
||||
|
||||
const rational& timebase = viewer_->video_params().time_base();
|
||||
|
||||
rational time = range.in();
|
||||
rational snapped = Timecode::snap_time_to_timebase(time, timebase);
|
||||
rational next;
|
||||
|
||||
if (snapped > time) {
|
||||
next = snapped;
|
||||
snapped -= timebase;
|
||||
} else {
|
||||
next = snapped + timebase;
|
||||
}
|
||||
|
||||
hash_list.append({snapped, backend.Hash(snapped, false)});
|
||||
range_to_cache.RemoveTimeRange(TimeRange(snapped, next));
|
||||
}
|
||||
|
||||
// Determine any duplicates
|
||||
QMap< QByteArray, QLinkedList<rational> > times_to_render;
|
||||
foreach (const TimeHashFuturePair& i, hash_list) {
|
||||
times_to_render[i.hash_future.result()].append(i.time);
|
||||
}
|
||||
|
||||
// Render all frames necessary
|
||||
QLinkedList<HashFrameFuturePair> render_lookup_table;
|
||||
{
|
||||
QLinkedList<HashTimePair> sorted_times;
|
||||
QLinkedList<HashTimePair>::iterator sorted_iterator;
|
||||
|
||||
// Rendering is more efficient if we cache in order
|
||||
QMap< QByteArray, QLinkedList<rational> >::const_iterator i;
|
||||
for (i=times_to_render.constBegin(); i!=times_to_render.constEnd(); i++) {
|
||||
const QByteArray& hash = i.key();
|
||||
const rational& time = i.value().first();
|
||||
|
||||
bool inserted = false;
|
||||
|
||||
for (sorted_iterator=sorted_times.begin(); sorted_iterator!=sorted_times.end(); sorted_iterator++) {
|
||||
if (sorted_iterator->time > time) {
|
||||
sorted_times.insert(sorted_iterator, {time, hash});
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inserted) {
|
||||
sorted_times.append({time, hash});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const HashTimePair& p, sorted_times) {
|
||||
render_lookup_table.append({p.hash, backend.RenderFrame(p.time, false, false)});
|
||||
}
|
||||
}
|
||||
|
||||
OIIO::TypeDesc output_desc = PixelFormat::GetOIIOTypeDesc(format);
|
||||
OIIO::ImageSpec output_spec(viewer_->video_params().width() / divider_,
|
||||
viewer_->video_params().height() / divider_,
|
||||
PixelFormat::ChannelCount(format),
|
||||
output_desc);
|
||||
|
||||
// Start downloading frames that have finished
|
||||
{
|
||||
int counter = 0;
|
||||
int nb_frames = render_lookup_table.size();
|
||||
|
||||
QLinkedList<HashDownloadFuturePair> download_futures;
|
||||
|
||||
// Iterators
|
||||
QLinkedList<HashFrameFuturePair>::iterator i;
|
||||
QLinkedList<HashDownloadFuturePair>::iterator j;
|
||||
|
||||
while (!render_lookup_table.isEmpty() || !download_futures.isEmpty()) {
|
||||
i = render_lookup_table.begin();
|
||||
|
||||
while (i != render_lookup_table.end()) {
|
||||
if (i->frame_future.isFinished()) {
|
||||
FramePtr f = i->frame_future.result();
|
||||
|
||||
// Start multithreaded download here
|
||||
download_futures.append({i->hash,
|
||||
QtConcurrent::run(FrameHashCache::SaveCacheFrame, i->hash, f)});
|
||||
|
||||
i = render_lookup_table.erase(i);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
j = download_futures.begin();
|
||||
|
||||
while (j != download_futures.end()) {
|
||||
if (j->download_future.isFinished()) {
|
||||
// Place it in the cache
|
||||
const QLinkedList<rational>& times_with_hash = times_to_render.value(j->hash);
|
||||
foreach (const rational& t, times_with_hash) {
|
||||
viewer_->video_frame_cache()->SetHash(t, j->hash);
|
||||
}
|
||||
|
||||
// Signal process
|
||||
counter++;
|
||||
emit ProgressChanged(qRound(100.0 * static_cast<double>(counter) / static_cast<double>(nb_frames)));
|
||||
|
||||
j = download_futures.erase(j);
|
||||
} else {
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
Vendored
+6
-7
@@ -21,23 +21,22 @@
|
||||
#ifndef CACHETASK_H
|
||||
#define CACHETASK_H
|
||||
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "task/task.h"
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
#include "task/render/render.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class CacheTask : public Task
|
||||
class CacheTask : public RenderTask
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CacheTask(ViewerOutput* viewer, int divider, bool in_out_only);
|
||||
|
||||
protected:
|
||||
virtual void Action() override;
|
||||
public slots:
|
||||
virtual bool Run() override;
|
||||
|
||||
private:
|
||||
ViewerOutput* viewer_;
|
||||
|
||||
bool in_out_only_;
|
||||
|
||||
int divider_;
|
||||
|
||||
@@ -31,10 +31,11 @@ ConformTask::ConformTask(AudioStreamPtr stream, const AudioRenderingParams& para
|
||||
SetTitle(tr("Conforming Audio %1:%2").arg(stream_->footage()->filename(), QString::number(stream_->index())));
|
||||
}
|
||||
|
||||
void ConformTask::Action()
|
||||
bool ConformTask::Run()
|
||||
{
|
||||
if (stream_->footage()->decoder().isEmpty()) {
|
||||
emit Failed(tr("Failed to find decoder to conform audio stream"));
|
||||
SetError(tr("Failed to find decoder to conform audio stream"));
|
||||
return false;
|
||||
} else {
|
||||
DecoderPtr decoder = Decoder::CreateFromID(stream_->footage()->decoder());
|
||||
|
||||
@@ -42,10 +43,11 @@ void ConformTask::Action()
|
||||
|
||||
connect(decoder.get(), &Decoder::IndexProgress, this, &ConformTask::ProgressChanged);
|
||||
|
||||
if (decoder->ConformAudio(&IsCancelled(), params_)) {
|
||||
emit Succeeded();
|
||||
if (!decoder->ConformAudio(&IsCancelled(), params_)) {
|
||||
SetError(tr("Failed to conform audio"));
|
||||
return false;
|
||||
} else {
|
||||
emit Failed(QStringLiteral("Failed to conform audio"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ class ConformTask : public Task
|
||||
public:
|
||||
ConformTask(AudioStreamPtr stream, const AudioRenderingParams& params);
|
||||
|
||||
protected:
|
||||
virtual void Action() override;
|
||||
public slots:
|
||||
virtual bool Run() override;
|
||||
|
||||
private:
|
||||
AudioStreamPtr stream_;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
task/export/export.h
|
||||
task/export/export.cpp
|
||||
task/export/exportparams.h
|
||||
task/export/exportparams.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -18,40 +18,22 @@
|
||||
|
||||
***/
|
||||
|
||||
#include "task.h"
|
||||
#include "export.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
Task::Task() :
|
||||
title_(tr("Task"))
|
||||
ExportTask::ExportTask(ViewerOutput* viewer_node,
|
||||
ColorManager* color_manager,
|
||||
const ExportParams& params) :
|
||||
RenderTask(viewer_node),
|
||||
color_manager_(color_manager),
|
||||
params_(params)
|
||||
{
|
||||
}
|
||||
|
||||
void Task::Start()
|
||||
bool ExportTask::Run()
|
||||
{
|
||||
Action();
|
||||
|
||||
emit Finished();
|
||||
}
|
||||
|
||||
const QString &Task::GetTitle()
|
||||
{
|
||||
return title_;
|
||||
}
|
||||
|
||||
void Task::Cancel()
|
||||
{
|
||||
CancelableObject::Cancel();
|
||||
}
|
||||
|
||||
void Task::SetErrorText(const QString &s)
|
||||
{
|
||||
error_ = s;
|
||||
}
|
||||
|
||||
void Task::SetTitle(const QString &s)
|
||||
{
|
||||
title_ = s;
|
||||
return true;
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -0,0 +1,50 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef EXPORTTASK_H
|
||||
#define EXPORTTASK_H
|
||||
|
||||
#include "exportparams.h"
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "render/colorprocessor.h"
|
||||
#include "task/render/render.h"
|
||||
#include "task/task.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class ExportTask : public RenderTask
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExportTask(ViewerOutput *viewer_node, ColorManager *color_manager, const ExportParams ¶ms);
|
||||
|
||||
public slots:
|
||||
virtual bool Run() override;
|
||||
|
||||
private:
|
||||
ColorManager* color_manager_;
|
||||
|
||||
ExportParams params_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // EXPORTTASK_H
|
||||
@@ -0,0 +1,103 @@
|
||||
/***
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
QMatrix4x4 ExportParams::GenerateMatrix(ExportParams::VideoScalingMethod method,
|
||||
int source_width, int source_height,
|
||||
int dest_width, int dest_height)
|
||||
{
|
||||
QMatrix4x4 preview_matrix;
|
||||
|
||||
if (method == ExportParams::kStretch) {
|
||||
return preview_matrix;
|
||||
}
|
||||
|
||||
float export_ar = static_cast<float>(dest_width) / static_cast<float>(dest_height);
|
||||
float source_ar = static_cast<float>(source_width) / static_cast<float>(source_height);
|
||||
|
||||
if (qFuzzyCompare(export_ar, source_ar)) {
|
||||
return preview_matrix;
|
||||
}
|
||||
|
||||
if ((export_ar > source_ar) == (method == ExportParams::kFit)) {
|
||||
preview_matrix.scale(source_ar / export_ar, 1.0F);
|
||||
} else {
|
||||
preview_matrix.scale(1.0F, export_ar / source_ar);
|
||||
}
|
||||
|
||||
return preview_matrix;
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -0,0 +1,73 @@
|
||||
/***
|
||||
|
||||
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 <QMatrix4x4>
|
||||
|
||||
#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);
|
||||
|
||||
static QMatrix4x4 GenerateMatrix(ExportParams::VideoScalingMethod method,
|
||||
int source_width, int source_height,
|
||||
int dest_width, int dest_height);
|
||||
|
||||
private:
|
||||
QString encoder_id_;
|
||||
|
||||
VideoScalingMethod video_scaling_method_;
|
||||
|
||||
bool has_custom_range_;
|
||||
TimeRange custom_range_;
|
||||
|
||||
ColorTransform color_transform_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // EXPORTPARAMS_H
|
||||
@@ -0,0 +1,24 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(import)
|
||||
add_subdirectory(load)
|
||||
add_subdirectory(save)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
task/project/import/import.h
|
||||
task/project/import/import.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,141 @@
|
||||
/***
|
||||
|
||||
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 "import.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "core.h"
|
||||
#include "codec/decoder.h"
|
||||
#include "project/item/footage/footage.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
ProjectImportTask::ProjectImportTask(ProjectViewModel *model, Folder *folder, const QStringList &filenames) :
|
||||
command_(nullptr),
|
||||
model_(model),
|
||||
folder_(folder)
|
||||
{
|
||||
foreach (const QString& f, filenames) {
|
||||
filenames_.append(f);
|
||||
}
|
||||
|
||||
file_count_ = Core::CountFilesInFileList(filenames_);
|
||||
|
||||
SetTitle(tr("Importing %1 files").arg(file_count_));
|
||||
}
|
||||
|
||||
const int &ProjectImportTask::GetFileCount() const
|
||||
{
|
||||
return file_count_;
|
||||
}
|
||||
|
||||
bool ProjectImportTask::Run()
|
||||
{
|
||||
command_ = new QUndoCommand();
|
||||
|
||||
int imported = 0;
|
||||
|
||||
Import(folder_, filenames_, imported, command_);
|
||||
|
||||
if (IsCancelled()) {
|
||||
delete command_;
|
||||
command_ = nullptr;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectImportTask::Import(Folder *folder, const QFileInfoList &import, int &counter, QUndoCommand* parent_command)
|
||||
{
|
||||
foreach (const QFileInfo& file_info, import) {
|
||||
if (IsCancelled()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if this file is a directory
|
||||
if (file_info.isDir()) {
|
||||
|
||||
// QDir::entryList only returns filenames, we can use entryInfoList() to get full paths
|
||||
QFileInfoList entry_list = QDir(file_info.absoluteFilePath()).entryInfoList();
|
||||
|
||||
// Strip out "." and ".." (for some reason QDir::NoDotAndDotDot doesn't work with entryInfoList, so we have to
|
||||
// check manually)
|
||||
for (int i=0;i<entry_list.size();i++) {
|
||||
if (entry_list.at(i).fileName() == "." || entry_list.at(i).fileName() == "..") {
|
||||
entry_list.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
// Only proceed if the empty actually has files in it
|
||||
if (!entry_list.isEmpty()) {
|
||||
// Create a folder corresponding to the directory
|
||||
|
||||
ItemPtr f = std::make_shared<Folder>();
|
||||
|
||||
f->set_name(file_info.fileName());
|
||||
|
||||
// Create undoable command that adds the items to the model
|
||||
new ProjectViewModel::AddItemCommand(model_,
|
||||
folder,
|
||||
f,
|
||||
parent_command);
|
||||
|
||||
// Recursively follow this path
|
||||
Import(static_cast<Folder*>(f.get()), entry_list, counter, parent_command);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
FootagePtr f = std::make_shared<Footage>();
|
||||
|
||||
f->set_filename(file_info.absoluteFilePath());
|
||||
f->set_name(file_info.fileName());
|
||||
f->set_timestamp(file_info.lastModified());
|
||||
|
||||
// Probe will fail if a project isn't set because ImageStream and its derivatives try to connect to the project's
|
||||
// ColorManager instance
|
||||
// FIXME: Perhaps re-think this approach at some point
|
||||
f->set_project(model_->project());
|
||||
|
||||
Decoder::ProbeMedia(f.get(), &IsCancelled());
|
||||
|
||||
f->set_project(nullptr);
|
||||
|
||||
if (f->status() != Footage::kInvalid) {
|
||||
// Create undoable command that adds the items to the model
|
||||
new ProjectViewModel::AddItemCommand(model_,
|
||||
folder,
|
||||
f,
|
||||
parent_command);
|
||||
}
|
||||
|
||||
counter++;
|
||||
|
||||
emit ProgressChanged((counter * 100) / file_count_);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -0,0 +1,65 @@
|
||||
/***
|
||||
|
||||
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 PROJECTIMPORTMANAGER_H
|
||||
#define PROJECTIMPORTMANAGER_H
|
||||
|
||||
#include <QFileInfoList>
|
||||
#include <QUndoCommand>
|
||||
|
||||
#include "project/projectviewmodel.h"
|
||||
#include "task/task.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class ProjectImportTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ProjectImportTask(ProjectViewModel* model, Folder* folder, const QStringList& filenames);
|
||||
|
||||
const int& GetFileCount() const;
|
||||
|
||||
QUndoCommand* GetCommand() const
|
||||
{
|
||||
return 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_;
|
||||
|
||||
QFileInfoList filenames_;
|
||||
|
||||
int file_count_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // PROJECTIMPORTMANAGER_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
task/project/load/load.h
|
||||
task/project/load/load.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,86 @@
|
||||
/***
|
||||
|
||||
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 "load.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFile>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include "common/xmlutils.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
ProjectLoadTask::ProjectLoadTask(const QString &filename) :
|
||||
filename_(filename)
|
||||
{
|
||||
SetTitle(tr("Loading '%1'").arg(filename));
|
||||
}
|
||||
|
||||
bool ProjectLoadTask::Run()
|
||||
{
|
||||
QFile project_file(filename_);
|
||||
|
||||
if (project_file.open(QFile::ReadOnly | QFile::Text)) {
|
||||
QXmlStreamReader reader(&project_file);
|
||||
|
||||
while (XMLReadNextStartElement(&reader)) {
|
||||
if (reader.name() == QStringLiteral("olive")) {
|
||||
while(XMLReadNextStartElement(&reader)) {
|
||||
if (reader.name() == QStringLiteral("version")) {
|
||||
qDebug() << "Project version:" << reader.readElementText();
|
||||
} else if (reader.name() == QStringLiteral("project")) {
|
||||
ProjectPtr project = std::make_shared<Project>();
|
||||
|
||||
project->set_filename(filename_);
|
||||
|
||||
project->Load(&reader, &IsCancelled());
|
||||
|
||||
// Ensure project is in main thread
|
||||
moveToThread(qApp->thread());
|
||||
|
||||
if (!IsCancelled()) {
|
||||
projects_.append(project);
|
||||
}
|
||||
} else {
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
project_file.close();
|
||||
|
||||
if (reader.hasError()) {
|
||||
SetError(reader.errorString());
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
} else {
|
||||
SetError(tr("Failed to read file \"%1\" for reading.").arg(filename_));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -0,0 +1,52 @@
|
||||
/***
|
||||
|
||||
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 PROJECTLOADMANAGER_H
|
||||
#define PROJECTLOADMANAGER_H
|
||||
|
||||
#include "project/project.h"
|
||||
#include "task/task.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class ProjectLoadTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ProjectLoadTask(const QString& filename);
|
||||
|
||||
const QList<ProjectPtr>& GetLoadedProjects()
|
||||
{
|
||||
return projects_;
|
||||
}
|
||||
|
||||
public slots:
|
||||
virtual bool Run() override;
|
||||
|
||||
private:
|
||||
QList<ProjectPtr> projects_;
|
||||
|
||||
QString filename_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // PROJECTLOADMANAGER_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
task/project/save/save.h
|
||||
task/project/save/save.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
/***
|
||||
|
||||
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 "save.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
ProjectSaveTask::ProjectSaveTask(ProjectPtr project) :
|
||||
project_(project)
|
||||
{
|
||||
SetTitle(tr("Saving '%1'").arg(project->filename()));
|
||||
}
|
||||
|
||||
bool ProjectSaveTask::Run()
|
||||
{
|
||||
QFile project_file(project_->filename());
|
||||
|
||||
if (project_file.open(QFile::WriteOnly | QFile::Text)) {
|
||||
QXmlStreamWriter writer(&project_file);
|
||||
writer.setAutoFormatting(true);
|
||||
|
||||
writer.writeStartDocument();
|
||||
|
||||
writer.writeStartElement("olive");
|
||||
|
||||
writer.writeTextElement("version", "0.2.0");
|
||||
|
||||
project_->Save(&writer);
|
||||
|
||||
writer.writeEndElement(); // olive
|
||||
|
||||
writer.writeEndDocument();
|
||||
|
||||
project_file.close();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
SetError(tr("Failed to open file \"%1\" for writing.").arg(project_->filename()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -0,0 +1,50 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef PROJECTSAVEMANAGER_H
|
||||
#define PROJECTSAVEMANAGER_H
|
||||
|
||||
#include "project/project.h"
|
||||
#include "task/task.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class ProjectSaveTask : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ProjectSaveTask(ProjectPtr project);
|
||||
|
||||
ProjectPtr GetProject() const
|
||||
{
|
||||
return project_;
|
||||
}
|
||||
|
||||
public slots:
|
||||
virtual bool Run() override;
|
||||
|
||||
private:
|
||||
ProjectPtr project_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // PROJECTSAVEMANAGER_H
|
||||
@@ -38,10 +38,11 @@ ProxyTask::ProxyTask(VideoStreamPtr stream, int divider) :
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyTask::Action()
|
||||
bool ProxyTask::Run()
|
||||
{
|
||||
if (stream_->footage()->decoder().isEmpty()) {
|
||||
emit Failed(tr("Failed to find decoder to conform audio stream"));
|
||||
SetError(tr("Failed to find decoder to conform audio stream"));
|
||||
return false;
|
||||
} else {
|
||||
DecoderPtr decoder = Decoder::CreateFromID(stream_->footage()->decoder());
|
||||
|
||||
@@ -50,9 +51,10 @@ void ProxyTask::Action()
|
||||
connect(decoder.get(), &Decoder::IndexProgress, this, &ProxyTask::ProgressChanged);
|
||||
|
||||
if (decoder->ProxyVideo(&IsCancelled(), divider_)) {
|
||||
emit Succeeded();
|
||||
return true;
|
||||
} else {
|
||||
emit Failed(QStringLiteral("Failed to generate proxy"));
|
||||
SetError(tr("Failed to generate proxy"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ class ProxyTask : public Task
|
||||
public:
|
||||
ProxyTask(VideoStreamPtr stream, int divider);
|
||||
|
||||
protected:
|
||||
virtual void Action() override;
|
||||
public slots:
|
||||
virtual bool Run() override;
|
||||
|
||||
private:
|
||||
VideoStreamPtr stream_;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
task/render/render.h
|
||||
task/render/render.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,186 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "render.h"
|
||||
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "render/backend/opengl/openglbackend.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
RenderTask::RenderTask(ViewerOutput* viewer) :
|
||||
viewer_(viewer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
struct TimeHashFuturePair {
|
||||
rational time;
|
||||
QFuture<QByteArray> hash_future;
|
||||
};
|
||||
|
||||
struct HashFrameFuturePair {
|
||||
QByteArray hash;
|
||||
QFuture<FramePtr> frame_future;
|
||||
};
|
||||
|
||||
struct HashDownloadFuturePair {
|
||||
QByteArray hash;
|
||||
QFuture<void> download_future;
|
||||
};
|
||||
|
||||
struct HashTimePair {
|
||||
rational time;
|
||||
QByteArray hash;
|
||||
};
|
||||
|
||||
void RenderTask::Render(TimeRangeList range_to_cache, int divider)
|
||||
{
|
||||
OpenGLBackend backend;
|
||||
|
||||
RenderMode::Mode mode = RenderMode::kOffline;
|
||||
PixelFormat::Format format = PixelFormat::instance()->GetConfiguredFormatForMode(mode);
|
||||
|
||||
backend.SetAudioEnabled(false);
|
||||
backend.SetViewerNode(viewer_);
|
||||
backend.SetPixelFormat(format);
|
||||
backend.SetMode(mode);
|
||||
backend.SetDivider(divider);
|
||||
backend.SetSampleFormat(SampleFormat::kInternalFormat);
|
||||
|
||||
// Get hashes for each frame
|
||||
QLinkedList<TimeHashFuturePair> hash_list;
|
||||
|
||||
while (!range_to_cache.isEmpty()) {
|
||||
const TimeRange& range = range_to_cache.first();
|
||||
|
||||
const rational& timebase = viewer_->video_params().time_base();
|
||||
|
||||
rational time = range.in();
|
||||
rational snapped = Timecode::snap_time_to_timebase(time, timebase);
|
||||
rational next;
|
||||
|
||||
if (snapped > time) {
|
||||
next = snapped;
|
||||
snapped -= timebase;
|
||||
} else {
|
||||
next = snapped + timebase;
|
||||
}
|
||||
|
||||
hash_list.append({snapped, backend.Hash(snapped, false)});
|
||||
range_to_cache.RemoveTimeRange(TimeRange(snapped, next));
|
||||
}
|
||||
|
||||
// Determine any duplicates
|
||||
QMap< QByteArray, QLinkedList<rational> > times_to_render;
|
||||
foreach (const TimeHashFuturePair& i, hash_list) {
|
||||
times_to_render[i.hash_future.result()].append(i.time);
|
||||
}
|
||||
|
||||
// Render all frames necessary
|
||||
QLinkedList<HashFrameFuturePair> render_lookup_table;
|
||||
{
|
||||
QLinkedList<HashTimePair> sorted_times;
|
||||
QLinkedList<HashTimePair>::iterator sorted_iterator;
|
||||
|
||||
// Rendering is more efficient if we cache in order
|
||||
QMap< QByteArray, QLinkedList<rational> >::const_iterator i;
|
||||
for (i=times_to_render.constBegin(); i!=times_to_render.constEnd(); i++) {
|
||||
const QByteArray& hash = i.key();
|
||||
const rational& time = i.value().first();
|
||||
|
||||
bool inserted = false;
|
||||
|
||||
for (sorted_iterator=sorted_times.begin(); sorted_iterator!=sorted_times.end(); sorted_iterator++) {
|
||||
if (sorted_iterator->time > time) {
|
||||
sorted_times.insert(sorted_iterator, {time, hash});
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inserted) {
|
||||
sorted_times.append({time, hash});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const HashTimePair& p, sorted_times) {
|
||||
render_lookup_table.append({p.hash, backend.RenderFrame(p.time, false, false)});
|
||||
}
|
||||
}
|
||||
|
||||
OIIO::TypeDesc output_desc = PixelFormat::GetOIIOTypeDesc(format);
|
||||
OIIO::ImageSpec output_spec(viewer_->video_params().width() / divider,
|
||||
viewer_->video_params().height() / divider,
|
||||
PixelFormat::ChannelCount(format),
|
||||
output_desc);
|
||||
|
||||
// Start downloading frames that have finished
|
||||
{
|
||||
int counter = 0;
|
||||
int nb_frames = render_lookup_table.size();
|
||||
|
||||
QLinkedList<HashDownloadFuturePair> download_futures;
|
||||
|
||||
// Iterators
|
||||
QLinkedList<HashFrameFuturePair>::iterator i;
|
||||
QLinkedList<HashDownloadFuturePair>::iterator j;
|
||||
|
||||
while (!render_lookup_table.isEmpty() || !download_futures.isEmpty()) {
|
||||
i = render_lookup_table.begin();
|
||||
|
||||
while (i != render_lookup_table.end()) {
|
||||
if (i->frame_future.isFinished()) {
|
||||
FramePtr f = i->frame_future.result();
|
||||
|
||||
// Start multithreaded download here
|
||||
download_futures.append({i->hash,
|
||||
QtConcurrent::run(FrameHashCache::SaveCacheFrame, i->hash, f)});
|
||||
|
||||
i = render_lookup_table.erase(i);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
j = download_futures.begin();
|
||||
|
||||
while (j != download_futures.end()) {
|
||||
if (j->download_future.isFinished()) {
|
||||
// Place it in the cache
|
||||
const QLinkedList<rational>& times_with_hash = times_to_render.value(j->hash);
|
||||
foreach (const rational& t, times_with_hash) {
|
||||
viewer_->video_frame_cache()->SetHash(t, j->hash);
|
||||
}
|
||||
|
||||
// Signal process
|
||||
counter++;
|
||||
emit ProgressChanged(qRound(100.0 * static_cast<double>(counter) / static_cast<double>(nb_frames)));
|
||||
|
||||
j = download_futures.erase(j);
|
||||
} else {
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -0,0 +1,49 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef RENDERTASK_H
|
||||
#define RENDERTASK_H
|
||||
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "task/task.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class RenderTask : public Task
|
||||
{
|
||||
public:
|
||||
RenderTask(ViewerOutput* viewer);
|
||||
|
||||
protected:
|
||||
void Render(TimeRangeList range_to_cache, int divider = 1);
|
||||
|
||||
ViewerOutput* viewer() const
|
||||
{
|
||||
return viewer_;
|
||||
}
|
||||
|
||||
private:
|
||||
ViewerOutput* viewer_;
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // RENDERTASK_H
|
||||
+43
-42
@@ -52,59 +52,68 @@ public:
|
||||
/**
|
||||
* @brief Task Constructor
|
||||
*/
|
||||
Task();
|
||||
Task() :
|
||||
title_(tr("Task")),
|
||||
error_(tr("Unknown error"))
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieve the current title of this Task
|
||||
*/
|
||||
const QString& GetTitle();
|
||||
const QString& GetTitle()
|
||||
{
|
||||
return title_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the error that occurred if Run() returns false
|
||||
*/
|
||||
const QString& GetError()
|
||||
{
|
||||
return error_;
|
||||
}
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Try to start this Task
|
||||
* @brief Run this task
|
||||
*
|
||||
* The main function for starting this Task. If this task is currently waiting, this function will start a new thread
|
||||
* and set the status to kWorking.
|
||||
* @return True if the task completed successfully, false if not.
|
||||
*
|
||||
* This function also checks its dependency Tasks and will only start if all of them are complete. If they are still
|
||||
* working, this function will return FALSE and the status will continue to be kWaiting. If any of them failed, this
|
||||
* Task will also fail - this function will return FALSE and the status will be set to kError.
|
||||
* \see GetError() if this returns false.
|
||||
*/
|
||||
void Start();
|
||||
virtual bool Run() = 0;
|
||||
|
||||
/**
|
||||
* @brief Reset state so that Run() can be called again.
|
||||
*
|
||||
* Override this if your class holds any persistent state that should be cleared/modified before
|
||||
* it's safe for Run() to run again.
|
||||
*/
|
||||
virtual void Reset(){}
|
||||
|
||||
/**
|
||||
* @brief Cancel the Task
|
||||
*
|
||||
* Sends a signal to the Task to stop as soon as possible. Always call this directly or connect with
|
||||
* Qt::DirectConnection, or else it'll be queued *after* the task has already finished.
|
||||
* Sends a signal to the Task to stop as soon as possible. Always call this directly or connect
|
||||
* with Qt::DirectConnection, or else it'll be queued *after* the task has already finished.
|
||||
*/
|
||||
void Cancel();
|
||||
void Cancel()
|
||||
{
|
||||
CancelableObject::Cancel();
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief The main Task function which is run in a separate thread
|
||||
*
|
||||
* Action() is the function that gets called once the separate thread has been created. This function should be
|
||||
* overridden in subclasses.
|
||||
*
|
||||
* It's also recommended to emit ProgressChanged() throughout your Action() so that any attached ProgressBars can
|
||||
* show accurate progress information.
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* TRUE if the Task could complete successfully. FALSE if not. Note that FALSE should only be returned if the Task
|
||||
* could not finish, not if the Task found a negative result (see Task documentation for details). Before returning
|
||||
* FALSE, it's recommended to use set_error() to signal to the user what caused the failure.
|
||||
*/
|
||||
virtual void Action() = 0;
|
||||
|
||||
/**
|
||||
* @brief Set the error message
|
||||
*
|
||||
* It is recommended to use this if your Action() function ever returns FALSE to tell the user why the failure
|
||||
* occurred.
|
||||
*/
|
||||
void SetErrorText(const QString& s);
|
||||
void SetError(const QString& s)
|
||||
{
|
||||
error_ = s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the Task title
|
||||
@@ -113,7 +122,10 @@ protected:
|
||||
* and shouldn't need to change during the life of the Task. To show an error message, it's recommended to use
|
||||
* set_error() instead.
|
||||
*/
|
||||
void SetTitle(const QString& s);
|
||||
void SetTitle(const QString& s)
|
||||
{
|
||||
title_ = s;
|
||||
}
|
||||
|
||||
signals:
|
||||
/**
|
||||
@@ -127,17 +139,6 @@ signals:
|
||||
*/
|
||||
void ProgressChanged(int p);
|
||||
|
||||
void Succeeded();
|
||||
|
||||
void Failed(const QString& error);
|
||||
|
||||
void Finished();
|
||||
|
||||
/**
|
||||
* @brief Signal emitted when this Task is removed from TaskManager
|
||||
*/
|
||||
void Removed();
|
||||
|
||||
private:
|
||||
QString title_;
|
||||
|
||||
|
||||
+29
-177
@@ -27,45 +27,22 @@ OLIVE_NAMESPACE_ENTER
|
||||
|
||||
TaskManager* TaskManager::instance_ = nullptr;
|
||||
|
||||
TaskManager::TaskManager() :
|
||||
active_thread_count_(0)
|
||||
TaskManager::TaskManager()
|
||||
{
|
||||
// Initialize threads to run tasks on
|
||||
threads_.resize(QThread::idealThreadCount());
|
||||
|
||||
for (int i=0;i<threads_.size();i++) {
|
||||
QThread* t = new QThread(this);
|
||||
t->start(QThread::IdlePriority);
|
||||
threads_.replace(i, {t, false});
|
||||
}
|
||||
}
|
||||
|
||||
TaskManager::~TaskManager()
|
||||
{
|
||||
// First send the signal to all tasks to start cancelling
|
||||
foreach (const TaskContainer& task_info, tasks_) {
|
||||
if (task_info.status == kWorking) {
|
||||
task_info.task->Cancel();
|
||||
}
|
||||
thread_pool_.clear();
|
||||
|
||||
foreach (Task* t, tasks_) {
|
||||
t->Cancel();
|
||||
}
|
||||
|
||||
// Next, signal each thread to quit as its next event in the queue
|
||||
foreach (const ThreadContainer& tc, threads_) {
|
||||
tc.thread->quit();
|
||||
}
|
||||
thread_pool_.waitForDone();
|
||||
|
||||
// Wait for each thread's event queue to finish
|
||||
foreach (const ThreadContainer& tc, threads_) {
|
||||
tc.thread->wait();
|
||||
|
||||
// This is technically unnecessary since each QThread is a child of this object, but we may as well
|
||||
delete tc.thread;
|
||||
}
|
||||
|
||||
// Finally delete all task objects (they shouldn't have been deleted by TaskSucceeded() or TaskFailed() because our
|
||||
// event queue shouldn't be active by this point
|
||||
foreach (const TaskContainer& task_info, tasks_) {
|
||||
delete task_info.task;
|
||||
foreach (Task* t, tasks_) {
|
||||
t->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,171 +69,46 @@ int TaskManager::GetTaskCount() const
|
||||
|
||||
Task *TaskManager::GetFirstTask() const
|
||||
{
|
||||
return tasks_.first().task;
|
||||
return tasks_.begin().value();
|
||||
}
|
||||
|
||||
void TaskManager::AddTask(Task* t)
|
||||
{
|
||||
// Connect Task's status signal to the Callback
|
||||
connect(t, &Task::Succeeded, this, &TaskManager::TaskSucceeded, Qt::QueuedConnection);
|
||||
connect(t, &Task::Failed, this, &TaskManager::TaskFailed, Qt::QueuedConnection);
|
||||
connect(t, &Task::Finished, this, &TaskManager::TaskFinished, Qt::QueuedConnection);
|
||||
// Create a watcher for signalling
|
||||
QFutureWatcher<bool>* watcher = new QFutureWatcher<bool>();
|
||||
connect(watcher, &QFutureWatcher<bool>::finished, this, &TaskManager::TaskFinished);
|
||||
|
||||
// Add the Task to the queue
|
||||
tasks_.append({t, kWaiting});
|
||||
tasks_.insert(watcher, t);
|
||||
|
||||
// Run task concurrently
|
||||
watcher->setFuture(QtConcurrent::run(t, &Task::Run));
|
||||
|
||||
// Emit signal that a Task was added
|
||||
emit TaskAdded(t);
|
||||
emit TaskListChanged();
|
||||
|
||||
// Scan through queue and start any Tasks that can (including this one)
|
||||
StartNextWaiting();
|
||||
}
|
||||
|
||||
void TaskManager::StartNextWaiting()
|
||||
{
|
||||
// If there are no tasks in the queue, there is nothing to be done
|
||||
if (tasks_.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If all threads are occupied, nothing to be done
|
||||
if (active_thread_count_ == threads_.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a list of tasks that are waiting
|
||||
QList<Task*> waiting_tasks;
|
||||
foreach (const TaskContainer& task_info, tasks_) {
|
||||
if (task_info.status == kWaiting) {
|
||||
waiting_tasks.append(task_info.task);
|
||||
}
|
||||
}
|
||||
|
||||
// No tasks waiting to start
|
||||
if (waiting_tasks.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For any inactive threads,
|
||||
for (int i=0;i<threads_.size();i++) {
|
||||
if (!threads_.at(i).active) {
|
||||
// This thread is inactive and needs a new Task
|
||||
Task* task = waiting_tasks.takeFirst();
|
||||
|
||||
task->moveToThread(threads_.at(i).thread);
|
||||
|
||||
threads_[i].active = true;
|
||||
active_thread_count_++;
|
||||
|
||||
SetTaskStatus(task, kWorking);
|
||||
|
||||
QMetaObject::invokeMethod(task,
|
||||
"Start",
|
||||
Qt::QueuedConnection);
|
||||
|
||||
if (active_thread_count_ == threads_.size() || waiting_tasks.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TaskManager::DeleteTask(Task *t)
|
||||
{
|
||||
if (GetTaskStatus(t) == kWorking) {
|
||||
// Send a signal to the task to cancel, it will likely continue to cancel in the background after it's removed
|
||||
t->Cancel();
|
||||
}
|
||||
|
||||
// Remove instances of Task from queue
|
||||
for (int i=0;i<tasks_.size();i++) {
|
||||
if (tasks_.at(i).task == t) {
|
||||
tasks_.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
emit t->Removed();
|
||||
emit TaskListChanged();
|
||||
|
||||
if (GetTaskStatus(t) != kWorking) {
|
||||
// If the task isn't doing anything, we can simply delete it
|
||||
t->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void TaskManager::TaskFinished()
|
||||
{
|
||||
Task* task_sender = static_cast<Task*>(sender());
|
||||
QFutureWatcher<bool>* watcher = static_cast<QFutureWatcher<bool>*>(sender());
|
||||
Task* t = tasks_.value(watcher);
|
||||
|
||||
// Set this thread's active value to false
|
||||
for (int i=0;i<threads_.size();i++) {
|
||||
if (threads_.at(i).thread == task_sender->thread()) {
|
||||
threads_[i].active = false;
|
||||
}
|
||||
tasks_.remove(watcher);
|
||||
|
||||
if (watcher->result()) {
|
||||
// Task completed successfully
|
||||
emit TaskRemoved(t);
|
||||
t->deleteLater();
|
||||
} else {
|
||||
// Task failed, keep it so the user can see the error message
|
||||
emit TaskFailed(t);
|
||||
failed_tasks_.append(t);
|
||||
}
|
||||
|
||||
// See if we can delete this task
|
||||
if (GetTaskStatus(task_sender) == kFinished) {
|
||||
DeleteTask(task_sender);
|
||||
} else if (GetTaskStatus(task_sender) == kError) {
|
||||
// If this task has already been deleted, we'll free its memory now
|
||||
bool was_deleted = true;
|
||||
watcher->deleteLater();
|
||||
|
||||
for (int i=0;i<tasks_.size();i++) {
|
||||
if (tasks_.at(i).task == task_sender) {
|
||||
was_deleted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (was_deleted) {
|
||||
task_sender->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
// Decrement the active thread count
|
||||
active_thread_count_--;
|
||||
|
||||
// Signal that the task has finished
|
||||
emit TaskListChanged();
|
||||
|
||||
// Start any tasks that could start now
|
||||
StartNextWaiting();
|
||||
}
|
||||
|
||||
TaskManager::TaskStatus TaskManager::GetTaskStatus(Task *t)
|
||||
{
|
||||
foreach (const TaskContainer& container, tasks_) {
|
||||
if (container.task == t) {
|
||||
return container.status;
|
||||
}
|
||||
}
|
||||
|
||||
return kError;
|
||||
}
|
||||
|
||||
void TaskManager::SetTaskStatus(Task *t, TaskStatus status)
|
||||
{
|
||||
for (int i=0;i<tasks_.size();i++) {
|
||||
TaskContainer& cont = tasks_[i];
|
||||
|
||||
if (cont.task == t) {
|
||||
cont.status = status;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TaskManager::TaskSucceeded()
|
||||
{
|
||||
SetTaskStatus(static_cast<Task*>(sender()), kFinished);
|
||||
}
|
||||
|
||||
void TaskManager::TaskFailed()
|
||||
{
|
||||
SetTaskStatus(static_cast<Task*>(sender()), kError);
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
+16
-67
@@ -21,6 +21,7 @@
|
||||
#ifndef TASKMANAGER_H
|
||||
#define TASKMANAGER_H
|
||||
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QVector>
|
||||
#include <QUndoCommand>
|
||||
|
||||
@@ -95,79 +96,31 @@ signals:
|
||||
*/
|
||||
void TaskListChanged();
|
||||
|
||||
/**
|
||||
* @brief Signal emitted when a task is deleted
|
||||
*/
|
||||
void TaskRemoved(Task* t);
|
||||
|
||||
/**
|
||||
* @brief Signal emitted when a task fails
|
||||
*/
|
||||
void TaskFailed(Task* t);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief The Status enum
|
||||
*
|
||||
* All states that a Task can be in. When subclassing, you don't need to set the Task's status as the base class
|
||||
* does that automatically.
|
||||
*/
|
||||
enum TaskStatus {
|
||||
/// This Task is yet to start
|
||||
kWaiting,
|
||||
|
||||
/// This Task is currently running (see Action())
|
||||
kWorking,
|
||||
|
||||
/// This Task has completed successfully
|
||||
kFinished,
|
||||
|
||||
/// This Task failed and could not complete
|
||||
kError
|
||||
};
|
||||
|
||||
struct TaskContainer {
|
||||
Task* task;
|
||||
TaskStatus status;
|
||||
};
|
||||
|
||||
struct ThreadContainer {
|
||||
QThread* thread;
|
||||
bool active;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Scan through the task queue and start any Tasks that are able to start
|
||||
*
|
||||
* This function is run whenever a Task is added and whenever a Task finishes. It determines how many Tasks are
|
||||
* currently running and therefore how many Tasks can be started (if any). It will then start ones that can.
|
||||
*
|
||||
* This function is aware of "dependency Tasks" and if a Task is waiting but has a dependency that hasn't finished,
|
||||
* it will skip to the next one.
|
||||
*
|
||||
* Like AddTask, this function is NOT thread-safe and currently only intended to be run from the main thread.
|
||||
*/
|
||||
void StartNextWaiting();
|
||||
|
||||
/**
|
||||
* @brief Removes the Task from the queue and deletes it
|
||||
*
|
||||
* Recommended for use after a Task has completed or errorred.
|
||||
*
|
||||
* @param t
|
||||
*
|
||||
* Task to delete
|
||||
*/
|
||||
void DeleteTask(Task* t);
|
||||
|
||||
TaskStatus GetTaskStatus(Task* t);
|
||||
|
||||
void SetTaskStatus(Task* t, TaskStatus status);
|
||||
|
||||
/**
|
||||
* @brief Internal task array
|
||||
*/
|
||||
QVector<TaskContainer> tasks_;
|
||||
QHash<QFutureWatcher<bool>*, Task*> tasks_;
|
||||
|
||||
/**
|
||||
* @brief Background threads to run tasks on
|
||||
* @brief Internal list of failed tasks
|
||||
*/
|
||||
QVector<ThreadContainer> threads_;
|
||||
QLinkedList<Task*> failed_tasks_;
|
||||
|
||||
/**
|
||||
* @brief Value for how many threads are currently active
|
||||
* @brief Task thread pool
|
||||
*/
|
||||
int active_thread_count_;
|
||||
QThreadPool thread_pool_;
|
||||
|
||||
/**
|
||||
* @brief TaskManager singleton instance
|
||||
@@ -175,10 +128,6 @@ private:
|
||||
static TaskManager* instance_;
|
||||
|
||||
private slots:
|
||||
void TaskSucceeded();
|
||||
|
||||
void TaskFailed();
|
||||
|
||||
void TaskFinished();
|
||||
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user