began support of timeline tracks/channels

This commit is contained in:
itsmattkc
2019-08-15 23:40:22 +10:00
parent fb7c7f1bdb
commit 7d9d12ee0b
25 changed files with 831 additions and 373 deletions
+27 -3
View File
@@ -219,8 +219,10 @@ void Core::CreateNewFolder()
// FIXME: Test code
#include "node/block/clip/clip.h"
#include "node/block/gap/gap.h"
#include "node/input/media/media.h"
#include "node/output/timeline/timeline.h"
#include "node/output/track/track.h"
#include "node/output/viewer/viewer.h"
#include "node/generator/solid/solid.h"
#include "panel/panelmanager.h"
@@ -271,16 +273,38 @@ void Core::CreateNewSequence()
new_sequence);
// FIXME: Test code
GapBlock* gb = new GapBlock();
gb->set_length(1);
new_sequence->AddNode(gb);
ClipBlock* cb = new ClipBlock();
cb->set_length(2);
new_sequence->AddNode(cb);
TrackOutput* to = new TrackOutput();
new_sequence->AddNode(to);
ClipBlock* cb2 = new ClipBlock();
cb2->set_length(2);
new_sequence->AddNode(cb2);
TrackOutput* to2 = new TrackOutput();
new_sequence->AddNode(to2);
TimelineOutput* tb = new TimelineOutput();
new_sequence->AddNode(tb);
ViewerOutput* vo = new ViewerOutput();
vo->AttachViewer(olive::panel_focus_manager->MostRecentlyFocused<ViewerPanel>());
new_sequence->AddNode(vo);
NodeParam::ConnectEdge(tb->texture_output(), vo->texture_input());
Block::ConnectBlocks(gb, cb);
Block::ConnectBlocks(cb, to);
Block::ConnectBlocks(cb2, to2);
NodeParam::ConnectEdge(to->texture_output(), vo->texture_input());
NodeParam::ConnectEdge(to->track_output(), tb->track_input());
vo->AttachViewer(olive::panel_focus_manager->MostRecentlyFocused<ViewerPanel>());
tb->AttachTimeline(olive::panel_focus_manager->MostRecentlyFocused<TimelinePanel>());
olive::panel_focus_manager->MostRecentlyFocused<NodePanel>()->SetGraph(new_sequence.get());
// End test code
+1
View File
@@ -14,6 +14,7 @@
# 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(blend)
add_subdirectory(block)
add_subdirectory(generator)
add_subdirectory(input)
+24
View File
@@ -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(alphaover)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
node/blend/blend.h
node/blend/blend.cpp
PARENT_SCOPE
)
+22
View File
@@ -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}
node/blend/alphaover/alphaover.h
node/blend/alphaover/alphaover.cpp
PARENT_SCOPE
)
+17
View File
@@ -0,0 +1,17 @@
#include "alphaover.h"
AlphaOverBlend::AlphaOverBlend()
{
}
void AlphaOverBlend::Process(const rational &time)
{
Q_UNUSED(time)
// FIXME: Write Alpha Over Formula
// Note that alpha will always be premultiplied by this point
//GLuint base_tex = base_input_->get_value(time).value<GLuint>();
}
+15
View File
@@ -0,0 +1,15 @@
#ifndef ALPHAOVER_H
#define ALPHAOVER_H
#include "node/blend/blend.h"
class AlphaOverBlend : public BlendNode
{
public:
AlphaOverBlend();
public slots:
virtual void Process(const rational &time) override;
};
#endif // ALPHAOVER_H
+22
View File
@@ -0,0 +1,22 @@
#include "blend.h"
BlendNode::BlendNode()
{
base_input_ = new NodeInput("base_in");
base_input_->add_data_input(NodeParam::kTexture);
AddParameter(base_input_);
blend_input_ = new NodeInput("blend_in");
blend_input_->add_data_input(NodeParam::kTexture);
AddParameter(blend_input_);
}
NodeInput *BlendNode::base_input()
{
return base_input_;
}
NodeInput *BlendNode::blend_input()
{
return blend_input_;
}
+21
View File
@@ -0,0 +1,21 @@
#ifndef BLEND_H
#define BLEND_H
#include "node/node.h"
class BlendNode : public Node
{
public:
BlendNode();
NodeInput* base_input();
NodeInput* blend_input();
private:
NodeInput* base_input_;
NodeInput* blend_input_;
};
#endif // BLEND_H
+4 -4
View File
@@ -28,14 +28,14 @@ Block::Block()
previous_input_->add_data_input(NodeParam::kBlock);
AddParameter(previous_input_);
block_output_ = new NodeOutput("block_out");
block_output_->set_data_type(NodeParam::kBlock);
AddParameter(block_output_);
next_input_ = new NodeInput("next_block");
next_input_->add_data_input(NodeParam::kBlock);
AddParameter(next_input_);
block_output_ = new NodeOutput("block_out");
block_output_->set_data_type(NodeParam::kBlock);
AddParameter(block_output_);
texture_output_ = new NodeOutput("tex_out");
texture_output_->set_data_type(NodeParam::kTexture);
AddParameter(texture_output_);
+24
View File
@@ -53,6 +53,11 @@ void Node::AddParameter(NodeParam *param)
connect(param, SIGNAL(EdgeRemoved(NodeEdgePtr)), this, SIGNAL(EdgeRemoved(NodeEdgePtr)));
}
void Node::RemoveParameter(NodeParam *param)
{
delete param;
}
void Node::InvalidateCache(const rational &start_range, const rational &end_range)
{
QList<NodeParam *> params = parameters();
@@ -154,6 +159,25 @@ QList<Node *> Node::GetExclusiveDependencies()
return deps;
}
bool Node::OutputsTo(Node *n)
{
QList<NodeParam*> params = parameters();
foreach (NodeParam* param, params) {
if (param->type() == NodeParam::kOutput) {
QVector<NodeEdgePtr> edges = param->edges();
foreach (NodeEdgePtr edge, edges) {
if (edge->input()->parent() == n) {
return true;
}
}
}
}
return false;
}
QVariant Node::PtrToValue(void *ptr)
{
return reinterpret_cast<quintptr>(ptr);
+12
View File
@@ -121,6 +121,11 @@ public:
*/
QList<Node*> GetExclusiveDependencies();
/**
* @brief Returns whether this Node outputs data to the Node `n` in any way
*/
bool OutputsTo(Node* n);
/**
* @brief Convert a pointer to a value that can be sent between NodeParams
*/
@@ -142,6 +147,13 @@ protected:
*/
void AddParameter(NodeParam* param);
/**
* @brief Deletes the parameter from this Node
*
* The NodeParam object is destroyed in the process.
*/
void RemoveParameter(NodeParam* param);
/**
* @brief Signal all dependent Nodes that anything cached between start_range and end_range is now invalid and
* requires re-rendering
+1
View File
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
add_subdirectory(timeline)
add_subdirectory(track)
add_subdirectory(viewer)
set(OLIVE_SOURCES
+58 -261
View File
@@ -26,19 +26,14 @@
#include "node/graph.h"
TimelineOutput::TimelineOutput() :
current_block_(this),
attached_timeline_(nullptr)
{
}
track_input_ = new NodeInput("track_in");
track_input_->add_data_input(NodeParam::kTrack);
AddParameter(track_input_);
Block::Type TimelineOutput::type()
{
return kEnd;
}
Block *TimelineOutput::copy()
{
return new TimelineOutput();
connect(this, SIGNAL(EdgeAdded(NodeEdgePtr)), this, SLOT(TrackConnectionAdded(NodeEdgePtr)));
connect(this, SIGNAL(EdgeRemoved(NodeEdgePtr)), this, SLOT(TrackConnectionRemoved(NodeEdgePtr)));
}
QString TimelineOutput::Name()
@@ -58,308 +53,110 @@ QString TimelineOutput::Category()
QString TimelineOutput::Description()
{
return tr("Node for communicating between a Timeline panel and the node graph. Also represents the end of a"
"Sequence.");
return tr("Node for communicating between a Timeline panel and the node graph.");
}
void TimelineOutput::AttachTimeline(TimelinePanel *timeline)
{
if (attached_timeline_ != nullptr) {
disconnect(attached_timeline_, SIGNAL(RequestInsertBlockAtIndex(Block*, int)), this, SLOT(InsertBlockAtIndex(Block*, int)));
disconnect(attached_timeline_, SIGNAL(RequestPlaceBlock(Block*, rational)), this, SLOT(PlaceBlock(Block*, rational)));
//TimelineView* view = attached_timeline_->view();
//disconnect(view, SIGNAL(RequestInsertBlockAtIndex(Block*, int)), this, SLOT(InsertBlockAtIndex(Block*, int)));
//disconnect(view, SIGNAL(RequestPlaceBlock(Block*, rational)), this, SLOT(PlaceBlock(Block*, rational)));
// Remove existing UI objects from TimelinePanel
attached_timeline_->Clear();
}
attached_timeline_ = timeline;
if (attached_timeline_ != nullptr) {
attached_timeline_->Clear();
// FIXME: TEST CODE ONLY
attached_timeline_->SetTimebase(rational(1001, 30000));
// END TEST CODE
Block* previous_block = attached_block();
while (previous_block != nullptr) {
// FIXME: Dynamic cast is a dumb way of doing this
ClipBlock* clip_block = dynamic_cast<ClipBlock*>(previous_block);
if (clip_block != nullptr) {
attached_timeline_->AddClip(clip_block);
}
previous_block = previous_block->previous();
if (attached_track() != nullptr) {
// Defer to the track to make all the block UI items necessary
attached_track()->GenerateBlockWidgets();
}
TimelineView* view = attached_timeline_->view();
//TimelineView* view = attached_timeline_->view();
connect(view, SIGNAL(RequestInsertBlockAtIndex(Block*, int)), this, SLOT(InsertBlockAtIndex(Block*, int)));
connect(view, SIGNAL(RequestPlaceBlock(Block*, rational)), this, SLOT(PlaceBlock(Block*, rational)));
//connect(view, SIGNAL(RequestInsertBlockAtIndex(Block*, int)), this, SLOT(InsertBlockAtIndex(Block*, int)));
//connect(view, SIGNAL(RequestPlaceBlock(Block*, rational)), this, SLOT(PlaceBlock(Block*, rational)));
}
}
void TimelineOutput::set_length(const rational &)
NodeInput *TimelineOutput::track_input()
{
// Prevent length changing on this Block
}
void TimelineOutput::Refresh()
{
QVector<Block*> detect_attached_blocks;
Block* previous = attached_block();
while (previous != nullptr) {
detect_attached_blocks.prepend(previous);
if (attached_timeline_ != nullptr && !block_cache_.contains(previous)) {
// FIXME: Remove the need to cast this
attached_timeline_->AddClip(static_cast<ClipBlock*>(previous));
}
previous = previous->previous();
}
if (attached_timeline_ != nullptr) {
foreach (Block* b, block_cache_) {
if (!detect_attached_blocks.contains(b)) {
attached_timeline_->RemoveClip(static_cast<ClipBlock*>(b));
}
}
}
block_cache_ = detect_attached_blocks;
Block::Refresh();
return track_input_;
}
void TimelineOutput::Process(const rational &time)
{
// Run default node processing
Block::Process(time);
Q_UNUSED(time)
}
// This node representso the end of the timeline, so being beyond its in point is considered the end of the sequence
if (time >= in()) {
texture_output()->set_value(0);
current_block_ = this;
TrackOutput *TimelineOutput::attached_track()
{
return ValueToPtr<TrackOutput>(track_input_->get_value(0));
}
void TimelineOutput::TrackConnectionAdded(NodeEdgePtr edge)
{
if (edge->input() != track_input()) {
return;
}
// If we're here, we need to find the current clip to display
// attached_block() is guaranteed to not be nullptr if we didn't return before
current_block_ = attached_block();
TrackOutput* track = attached_track();
// If the time requested is an earlier Block, traverse earlier until we find it
while (time < current_block_->in()) {
current_block_ = current_block_->previous();
// Traverse through Tracks caching and connecting them
while (track != nullptr) {
track_cache_.append(track);
connect(track, SIGNAL(BlockAdded(Block*)), this, SLOT(TrackAddedBlock(Block*)));
connect(track, SIGNAL(BlockRemoved(Block*)), this, SLOT(TrackRemovedBlock(Block*)));
track->GenerateBlockWidgets();
track = track->next_track();
}
// If the time requested is in a later Block, traverse later
while (time >= current_block_->out()) {
current_block_ = current_block_->next();
// FIXME: TEST CODE ONLY
if (attached_timeline_ != nullptr) {
attached_timeline_->SetTimebase(rational(1001, 30000));
}
// At this point, we must have found the correct block so we use its texture output to produce the image
texture_output()->set_value(current_block_->texture_output()->get_value(time));
// END TEST CODE
}
void TimelineOutput::InsertBlockBetweenBlocks(Block *block, Block *before, Block *after)
void TimelineOutput::TrackConnectionRemoved(NodeEdgePtr edge)
{
AddBlockToGraph(block);
Block::DisconnectBlocks(before, after);
Block::ConnectBlocks(before, block);
Block::ConnectBlocks(block, after);
}
void TimelineOutput::InsertBlockAfter(Block *block, Block *before)
{
InsertBlockBetweenBlocks(block, before, before->next());
}
Block *TimelineOutput::attached_block()
{
return ValueToPtr<Block>(previous_input()->get_value(0));
}
void TimelineOutput::PrependBlock(Block *block)
{
AddBlockToGraph(block);
if (block_cache_.isEmpty()) {
ConnectBlockInternal(block);
} else {
Block::ConnectBlocks(block, block_cache_.first());
}
}
void TimelineOutput::InsertBlockAtIndex(Block *block, int index)
{
AddBlockToGraph(block);
if (block_cache_.isEmpty()) {
// If there are no blocks connected, the index doesn't matter. Just connect it.
ConnectBlockInternal(block);
} else if (index == 0) {
// If the index is 0, it goes at the very beginning
PrependBlock(block);
} else if (index >= block_cache_.size()) {
// Append Block at the end
AppendBlock(block);
} else {
// Insert Block just before the Block currently at that index so that it becomes the new Block at that index
InsertBlockBetweenBlocks(block, block_cache_.at(index - 1), block_cache_.at(index));
}
}
void TimelineOutput::AppendBlock(Block *block)
{
AddBlockToGraph(block);
if (block_cache_.isEmpty()) {
ConnectBlockInternal(block);
} else {
InsertBlockBetweenBlocks(block, block_cache_.last(), this);
}
}
void TimelineOutput::ConnectBlockInternal(Block *block)
{
AddBlockToGraph(block);
Block::ConnectBlocks(block, this);
}
void TimelineOutput::AddBlockToGraph(Block *block)
{
// Find the parent graph
NodeGraph* graph = static_cast<NodeGraph*>(parent());
graph->AddNodeWithDependencies(block);
}
void TimelineOutput::PlaceBlock(Block *block, rational start)
{
AddBlockToGraph(block);
if (block->in() == start) {
if (edge->input() != track_input()) {
return;
}
// Place block at the beginning
if (start == 0) {
// FIXME: Remove existing
PrependBlock(block);
return;
foreach (TrackOutput* track, track_cache_) {
disconnect(track, SIGNAL(BlockAdded(Block*)), this, SLOT(TrackAddedBlock(Block*)));
disconnect(track, SIGNAL(BlockRemoved(Block*)), this, SLOT(TrackRemovedBlock(Block*)));
}
// Check if the placement location is past the end of the timeline
if (start >= in()) {
if (start > in()) {
// If so, insert a gap here
GapBlock* gap = new GapBlock();
gap->set_length(start - in());
track_cache_.clear();
// Then append them
AppendBlock(gap);
}
AppendBlock(block);
return;
}
// Check if the Block is placed at the in point of an existing Block, in which case a simple insert between will
// suffice
for (int i=1;i<block_cache_.size();i++) {
Block* comparison = block_cache_.at(i);
if (comparison->in() == start) {
Block* previous = block_cache_.at(i-1);
// InsertBlockAtIndex() could work here, but this function is faster since we've already found the Blocks
InsertBlockBetweenBlocks(block, previous, comparison);
return;
}
if (attached_timeline_ != nullptr) {
attached_timeline_->Clear();
}
}
void TimelineOutput::RemoveBlock(Block *block)
void TimelineOutput::TrackAddedBlock(Block *block)
{
GapBlock* gap = new GapBlock();
gap->set_length(block->length());
Block* previous = block->previous();
Block* next = block->next();
// Remove block
RippleRemoveBlock(block);
if (previous == nullptr) {
// Block must be at the beginning
PrependBlock(gap);
} else {
InsertBlockBetweenBlocks(gap, previous, next);
if (attached_timeline_ != nullptr) {
attached_timeline_->view()->AddBlock(block);
}
}
void TimelineOutput::RippleRemoveBlock(Block *block)
void TimelineOutput::TrackRemovedBlock(Block *block)
{
Block* previous = block->previous();
Block* next = block->next();
if (previous != nullptr) {
Block::DisconnectBlocks(previous, block);
}
if (next != nullptr) {
Block::DisconnectBlocks(block, next);
}
if (previous != nullptr && next != nullptr) {
Block::ConnectBlocks(previous, next);
if (attached_timeline_ != nullptr) {
attached_timeline_->view()->RemoveBlock(block);
}
}
void TimelineOutput::SplitBlock(Block *block, rational time)
{
if (time < block->in() || time >= block->out()) {
return;
}
rational original_length = block->length();
block->set_length(time - block->in());
Block* copy = block->copy();
copy->set_length(original_length - block->length());
InsertBlockAfter(copy, block);
}
void TimelineOutput::SpliceBlock(Block *inner, Block *outer, rational inner_in)
{
Q_ASSERT(inner_in >= outer->in() && inner_in < outer->out());
// Cache original length
rational original_length = outer->length();
// Set outer clip to the clip that PRECEDES the inner clip
outer->set_length(inner_in - outer->in());
// Insert inner clip between BEFORE clip and its next clip
InsertBlockAfter(inner, outer);
// Create the AFTER clip
Block* copy = outer->copy();
copy->set_length(original_length - outer->length() - inner->length());
InsertBlockAfter(copy, inner);
}
+19 -79
View File
@@ -22,21 +22,18 @@
#define TIMELINEOUTPUT_H
#include "node/block/block.h"
#include "node/output/track/track.h"
#include "panel/timeline/timeline.h"
/**
* @brief Node that represents the end of the Timeline as well as a time traversal Node
*/
class TimelineOutput : public Block
class TimelineOutput : public Node
{
Q_OBJECT
public:
TimelineOutput();
virtual Type type() override;
virtual Block* copy() override;
virtual QString Name() override;
virtual QString id() override;
virtual QString Category() override;
@@ -44,101 +41,44 @@ public:
void AttachTimeline(TimelinePanel* timeline);
virtual void set_length(const rational &length) override;
virtual void Refresh() override;
NodeInput* track_input();
public slots:
virtual void Process(const rational &time) override;
private:
/**
* @brief Sets this Block as the only block in the Timeline (creating essentially a one clip sequence)
*/
void ConnectBlockInternal(Block* block);
/**
* @brief Disconnects t
*/
void RemoveBlockInternal();
/**
* @brief Adds a Block to the parent graph so it can be connected to other Nodes
*
* Also runs through Node's dependencies (the Nodes whose outputs are connected to this Node's inputs)
*/
void AddBlockToGraph(Block* block);
Block* attached_block();
QVector<Block*> block_cache_;
Block* current_block_;
TrackOutput* attached_track();
TimelinePanel* attached_timeline_;
NodeInput* track_input_;
/**
* @brief A cache of connected Tracks
*/
QVector<TrackOutput*> track_cache_;
private slots:
/**
* @brief Adds Block `block` at the very beginning of the Sequence before all other clips
* @brief Slot for when the track connection is added
*/
void PrependBlock(Block* block);
void TrackConnectionAdded(NodeEdgePtr edge);
/**
* @brief Inserts Block `block` at a specific index (0 is the start of the timeline)
*
* If the index == 0, this function does the same as PrependBlock(). If the index >= the current number of blocks,
* this function is the same as AppendBlock().
* @brief Slot for when the track connection is removed
*/
void InsertBlockAtIndex(Block* block, int index);
void TrackConnectionRemoved(NodeEdgePtr edge);
/**
* @brief Inserts a Block between two other Blocks
*
* Disconnects `before` and `after`, and connects them to `block` with `block` in between.
* @brief Slot for when a connected Track has added a Block so we can update the UI
*/
void InsertBlockBetweenBlocks(Block* block, Block* before, Block* after);
void TrackAddedBlock(Block* block);
/**
* @brief Inserts Block after another Block
*
* Equivalent to calling InsertBlockBetweenBlocks(block, before, before->next())
* @brief Slot for when a connected Track has added a Block so we can update the UI
*/
void InsertBlockAfter(Block* block, Block* before);
void TrackRemovedBlock(Block* block);
/**
* @brief Adds Block `block` at the very end of the Sequence after all other clips
*/
void AppendBlock(Block* block);
/**
* @brief Destructively places `block` at the in point `start`
*
* The Block is guaranteed to be placed at the starting point specified. If there are Blocks in this area, they are
* either trimmed or removed to make space for this Block. Additionally, if the Block is placed beyond the end of
* the Sequence, a GapBlock is inserted to compensate.
*/
void PlaceBlock(Block* block, rational start);
/**
* @brief Removes a Block and places a Gap in its place
*/
void RemoveBlock(Block* block);
/**
* @brief Removes a Block pushing all subsequent Blocks earlier to take up the space
*/
void RippleRemoveBlock(Block* block);
/**
* @brief Splits `block` into two Blocks at the Sequence point `time`
*/
void SplitBlock(Block* block, rational time);
/**
* @brief Inserts Block `inner` between Block `outer`, splitting and shortening it to fit without changing the overall
* length
*/
void SpliceBlock(Block* inner, Block* outer, rational inner_in);
};
#endif // TIMELINEOUTPUT_H
+22
View File
@@ -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}
node/output/track/track.h
node/output/track/track.cpp
PARENT_SCOPE
)
+350
View File
@@ -0,0 +1,350 @@
/***
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 "track.h"
#include <QDebug>
#include "node/block/gap/gap.h"
#include "node/graph.h"
TrackOutput::TrackOutput() :
current_block_(this)
{
track_input_ = new NodeInput("track_in");
track_input_->add_data_input(NodeParam::kTrack);
AddParameter(track_input_);
track_output_ = new NodeOutput("track_out");
track_output_->set_data_type(NodeParam::kTrack);
AddParameter(track_output_);
}
Block::Type TrackOutput::type()
{
return kEnd;
}
Block *TrackOutput::copy()
{
return new TrackOutput();
}
QString TrackOutput::Name()
{
return tr("Track");
}
QString TrackOutput::id()
{
return "org.olivevideoeditor.Olive.track";
}
QString TrackOutput::Category()
{
return tr("Output");
}
QString TrackOutput::Description()
{
return tr("Node for representing and processing a single array of Blocks sorted by time. Also represents the end of "
"a Sequence.");
}
void TrackOutput::set_length(const rational &)
{
// Prevent length changing on this Block
}
void TrackOutput::Refresh()
{
QVector<Block*> detect_attached_blocks;
Block* previous = attached_block();
while (previous != nullptr) {
detect_attached_blocks.prepend(previous);
if (!block_cache_.contains(previous)) {
emit BlockAdded(previous);
}
previous = previous->previous();
}
foreach (Block* b, block_cache_) {
if (!detect_attached_blocks.contains(b)) {
emit BlockRemoved(b);
}
}
block_cache_ = detect_attached_blocks;
Block::Refresh();
}
void TrackOutput::GenerateBlockWidgets()
{
foreach (Block* block, block_cache_) {
emit BlockAdded(block);
}
}
TrackOutput *TrackOutput::next_track()
{
return ValueToPtr<TrackOutput>(track_input_->get_value(0));
}
NodeOutput* TrackOutput::track_output()
{
return track_output_;
}
void TrackOutput::Process(const rational &time)
{
// Run default node processing
Block::Process(time);
// Set track output correctly
track_output_->set_value(PtrToValue(this));
// This node representso the end of the timeline, so being beyond its in point is considered the end of the sequence
if (time >= in()) {
texture_output()->set_value(0);
current_block_ = this;
return;
}
// If we're here, we need to find the current clip to display
// attached_block() is guaranteed to not be nullptr if we didn't return before
current_block_ = attached_block();
// If the time requested is an earlier Block, traverse earlier until we find it
while (time < current_block_->in()) {
current_block_ = current_block_->previous();
}
// If the time requested is in a later Block, traverse later
while (time >= current_block_->out()) {
current_block_ = current_block_->next();
}
// At this point, we must have found the correct block so we use its texture output to produce the image
texture_output()->set_value(current_block_->texture_output()->get_value(time));
}
void TrackOutput::InsertBlockBetweenBlocks(Block *block, Block *before, Block *after)
{
AddBlockToGraph(block);
Block::DisconnectBlocks(before, after);
Block::ConnectBlocks(before, block);
Block::ConnectBlocks(block, after);
}
void TrackOutput::InsertBlockAfter(Block *block, Block *before)
{
InsertBlockBetweenBlocks(block, before, before->next());
}
Block *TrackOutput::attached_block()
{
return ValueToPtr<Block>(previous_input()->get_value(0));
}
void TrackOutput::PrependBlock(Block *block)
{
AddBlockToGraph(block);
if (block_cache_.isEmpty()) {
ConnectBlockInternal(block);
} else {
Block::ConnectBlocks(block, block_cache_.first());
}
}
void TrackOutput::InsertBlockAtIndex(Block *block, int index)
{
AddBlockToGraph(block);
if (block_cache_.isEmpty()) {
// If there are no blocks connected, the index doesn't matter. Just connect it.
ConnectBlockInternal(block);
} else if (index == 0) {
// If the index is 0, it goes at the very beginning
PrependBlock(block);
} else if (index >= block_cache_.size()) {
// Append Block at the end
AppendBlock(block);
} else {
// Insert Block just before the Block currently at that index so that it becomes the new Block at that index
InsertBlockBetweenBlocks(block, block_cache_.at(index - 1), block_cache_.at(index));
}
}
void TrackOutput::AppendBlock(Block *block)
{
AddBlockToGraph(block);
if (block_cache_.isEmpty()) {
ConnectBlockInternal(block);
} else {
InsertBlockBetweenBlocks(block, block_cache_.last(), this);
}
}
void TrackOutput::ConnectBlockInternal(Block *block)
{
AddBlockToGraph(block);
Block::ConnectBlocks(block, this);
}
void TrackOutput::AddBlockToGraph(Block *block)
{
// Find the parent graph
NodeGraph* graph = static_cast<NodeGraph*>(parent());
graph->AddNodeWithDependencies(block);
}
void TrackOutput::PlaceBlock(Block *block, rational start)
{
AddBlockToGraph(block);
if (block->in() == start) {
return;
}
// Place block at the beginning
if (start == 0) {
// FIXME: Remove existing
PrependBlock(block);
return;
}
// Check if the placement location is past the end of the timeline
if (start >= in()) {
if (start > in()) {
// If so, insert a gap here
GapBlock* gap = new GapBlock();
gap->set_length(start - in());
// Then append them
AppendBlock(gap);
}
AppendBlock(block);
return;
}
// Check if the Block is placed at the in point of an existing Block, in which case a simple insert between will
// suffice
for (int i=1;i<block_cache_.size();i++) {
Block* comparison = block_cache_.at(i);
if (comparison->in() == start) {
Block* previous = block_cache_.at(i-1);
// InsertBlockAtIndex() could work here, but this function is faster since we've already found the Blocks
InsertBlockBetweenBlocks(block, previous, comparison);
return;
}
}
}
void TrackOutput::RemoveBlock(Block *block)
{
GapBlock* gap = new GapBlock();
gap->set_length(block->length());
Block* previous = block->previous();
Block* next = block->next();
// Remove block
RippleRemoveBlock(block);
if (previous == nullptr) {
// Block must be at the beginning
PrependBlock(gap);
} else {
InsertBlockBetweenBlocks(gap, previous, next);
}
}
void TrackOutput::RippleRemoveBlock(Block *block)
{
Block* previous = block->previous();
Block* next = block->next();
if (previous != nullptr) {
Block::DisconnectBlocks(previous, block);
}
if (next != nullptr) {
Block::DisconnectBlocks(block, next);
}
if (previous != nullptr && next != nullptr) {
Block::ConnectBlocks(previous, next);
}
}
void TrackOutput::SplitBlock(Block *block, rational time)
{
if (time < block->in() || time >= block->out()) {
return;
}
rational original_length = block->length();
block->set_length(time - block->in());
Block* copy = block->copy();
copy->set_length(original_length - block->length());
InsertBlockAfter(copy, block);
}
void TrackOutput::SpliceBlock(Block *inner, Block *outer, rational inner_in)
{
Q_ASSERT(inner_in >= outer->in() && inner_in < outer->out());
// Cache original length
rational original_length = outer->length();
// Set outer clip to the clip that PRECEDES the inner clip
outer->set_length(inner_in - outer->in());
// Insert inner clip between BEFORE clip and its next clip
InsertBlockAfter(inner, outer);
// Create the AFTER clip
Block* copy = outer->copy();
copy->set_length(original_length - outer->length() - inner->length());
InsertBlockAfter(copy, inner);
}
+161
View File
@@ -0,0 +1,161 @@
/***
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 TRACKOUTPUT_H
#define TRACKOUTPUT_H
#include "node/block/block.h"
#include "panel/timeline/timeline.h"
/**
* @brief A time traversal Node for sorting through one channel/track of Blocks
*/
class TrackOutput : public Block
{
Q_OBJECT
public:
TrackOutput();
virtual Type type() override;
virtual Block* copy() override;
virtual QString Name() override;
virtual QString id() override;
virtual QString Category() override;
virtual QString Description() override;
virtual void set_length(const rational &length) override;
virtual void Refresh() override;
void GenerateBlockWidgets();
TrackOutput* next_track();
NodeOutput *track_output();
signals:
/**
* @brief Signal emitted when a Block is added to this Track
*/
void BlockAdded(Block* block);
/**
* @brief Signal emitted when a Block is removed from this Track
*/
void BlockRemoved(Block* block);
public slots:
virtual void Process(const rational &time) override;
private:
/**
* @brief Sets this Block as the only block in the Timeline (creating essentially a one clip sequence)
*/
void ConnectBlockInternal(Block* block);
/**
* @brief Disconnects t
*/
void RemoveBlockInternal();
/**
* @brief Adds a Block to the parent graph so it can be connected to other Nodes
*
* Also runs through Node's dependencies (the Nodes whose outputs are connected to this Node's inputs)
*/
void AddBlockToGraph(Block* block);
Block* attached_block();
QVector<Block*> block_cache_;
Block* current_block_;
NodeInput* track_input_;
NodeOutput* track_output_;
private slots:
/**
* @brief Adds Block `block` at the very beginning of the Sequence before all other clips
*/
void PrependBlock(Block* block);
/**
* @brief Inserts Block `block` at a specific index (0 is the start of the timeline)
*
* If the index == 0, this function does the same as PrependBlock(). If the index >= the current number of blocks,
* this function is the same as AppendBlock().
*/
void InsertBlockAtIndex(Block* block, int index);
/**
* @brief Inserts a Block between two other Blocks
*
* Disconnects `before` and `after`, and connects them to `block` with `block` in between.
*/
void InsertBlockBetweenBlocks(Block* block, Block* before, Block* after);
/**
* @brief Inserts Block after another Block
*
* Equivalent to calling InsertBlockBetweenBlocks(block, before, before->next())
*/
void InsertBlockAfter(Block* block, Block* before);
/**
* @brief Adds Block `block` at the very end of the Sequence after all other clips
*/
void AppendBlock(Block* block);
/**
* @brief Destructively places `block` at the in point `start`
*
* The Block is guaranteed to be placed at the starting point specified. If there are Blocks in this area, they are
* either trimmed or removed to make space for this Block. Additionally, if the Block is placed beyond the end of
* the Sequence, a GapBlock is inserted to compensate.
*/
void PlaceBlock(Block* block, rational start);
/**
* @brief Removes a Block and places a Gap in its place
*/
void RemoveBlock(Block* block);
/**
* @brief Removes a Block pushing all subsequent Blocks earlier to take up the space
*/
void RippleRemoveBlock(Block* block);
/**
* @brief Splits `block` into two Blocks at the Sequence point `time`
*/
void SplitBlock(Block* block, rational time);
/**
* @brief Inserts Block `inner` between Block `outer`, splitting and shortening it to fit without changing the overall
* length
*/
void SpliceBlock(Block* inner, Block* outer, rational inner_in);
};
#endif // TRACKOUTPUT_H
+13
View File
@@ -134,6 +134,18 @@ NodeEdgePtr NodeParam::ConnectEdge(NodeOutput *output, NodeInput *input)
}
}
// Refuse to make a connection that is incompatible
if (!input->can_accept_type(output->data_type())) {
qWarning() << tr("Tried to make an invalid Node connection");
return nullptr;
}
// Ensure both nodes are in the same graph
if (output->parent()->parent() != input->parent()->parent()) {
qWarning() << tr("Tried to connect two nodes that aren't part of the same graph");
return nullptr;
}
NodeEdgePtr edge = std::make_shared<NodeEdge>(output, input);
output->edges_.append(edge);
@@ -196,6 +208,7 @@ QString NodeParam::GetDefaultDataTypeName(const DataType& type)
case kMatrix: return tr("Matrix");
case kBlock: return tr("Block");
case kFootage: return tr("Footage");
case kTrack: return tr("Track");
case kAny: return tr("Any");
}
+1
View File
@@ -66,6 +66,7 @@ public:
kMatrix,
kBlock,
kFootage,
kTrack,
kAny
};
+4 -1
View File
@@ -25,7 +25,6 @@
RendererProcessor::RendererProcessor() :
started_(false)
{
}
QString RendererProcessor::Name()
@@ -45,6 +44,8 @@ QString RendererProcessor::Description()
void RendererProcessor::Process(const rational &time)
{
Q_UNUSED(time)
/*
// Ensure we have started
Start();
@@ -52,6 +53,8 @@ void RendererProcessor::Process(const rational &time)
qWarning() << tr("An error occurred starting the Renderer node");
return;
}
*/
}
void RendererProcessor::Release()
+9 -9
View File
@@ -67,6 +67,15 @@ public:
*/
void SetParameters(const int& width, const int& height, const olive::PixelFormat& format);
/**
* @brief Return current instance of a RenderThread (or nullptr if there is none)
*
* This function attempts a dynamic_cast on QThread::currentThread() to RendererThread, which will return nullptr if
* the cast fails (e.g. if this function is called from the main thread rather than a RendererThread).
*/
static RendererThread *CurrentThread();
private:
/**
* @brief Allocate and start the multithreaded backend
*/
@@ -77,15 +86,6 @@ public:
*/
void Stop();
/**
* @brief Return current instance of a RenderThread (or nullptr if there is none)
*
* This function attempts a dynamic_cast on QThread::currentThread() to RendererThread, which will return nullptr if
* the cast fails (e.g. if this function is called from the main thread rather than a RendererThread).
*/
static RendererThread *CurrentThread();
private:
QVector<RendererThreadPtr> threads_;
bool started_;
-10
View File
@@ -55,16 +55,6 @@ void TimelinePanel::Clear()
view_->Clear();
}
void TimelinePanel::AddClip(ClipBlock *clip)
{
view_->AddBlock(clip);
}
void TimelinePanel::RemoveClip(ClipBlock *clip)
{
view_->RemoveBlock(clip);
}
void TimelinePanel::SetTimebase(const rational &timebase)
{
ruler_->SetTimebase(timebase);
-4
View File
@@ -33,10 +33,6 @@ public:
void Clear();
void AddClip(ClipBlock* clip);
void RemoveClip(ClipBlock* clip);
void SetTimebase(const rational& timebase);
TimelineView* view();
+2 -2
View File
@@ -161,7 +161,7 @@ FramePtr PixelService::ConvertPixelFormat(FramePtr frame, const olive::PixelForm
}
break;
}
case olive::PIX_FMT_RGBA16F: // 16-bit Integer -> 16-bit Integer
case olive::PIX_FMT_RGBA16F: // 16-bit Integer -> 16-bit Float
{
qfloat16* destination = reinterpret_cast<qfloat16*>(converted->data());
for (int i=0;i<pix_count;i++) {
@@ -169,7 +169,7 @@ FramePtr PixelService::ConvertPixelFormat(FramePtr frame, const olive::PixelForm
}
break;
}
case olive::PIX_FMT_RGBA32F: // 16-bit Integer -> 16-bit Integer
case olive::PIX_FMT_RGBA32F: // 16-bit Integer -> 32-bit Float
{
float* destination = reinterpret_cast<float*>(converted->data());
for (int i=0;i<pix_count;i++) {
@@ -49,6 +49,7 @@ void NodeParamViewWidgetBridge::CreateWidgets()
case NodeParam::kBlock:
case NodeParam::kTexture:
case NodeParam::kMatrix:
case NodeParam::kTrack:
break;
case NodeParam::kInt:
// FIXME: LabelSlider in INTEGER mode
@@ -118,6 +119,7 @@ void NodeParamViewWidgetBridge::WidgetCallback()
case NodeParam::kBlock:
case NodeParam::kTexture:
case NodeParam::kMatrix:
case NodeParam::kTrack:
break;
case NodeParam::kInt:
// FIXME: LabelSlider in INTEGER mode