It was becoming inevitable to get rid of this, it was continuously an exception that had to be made to the DAG concept seeing as blocks are never really "dependents" on each other, i.e. the images they produce have nothing to do with other blocks. The only exception is a transition which will also be easier to accomplish with this slightly different design.
430 lines
9.6 KiB
C++
430 lines
9.6 KiB
C++
/***
|
|
|
|
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_type_(kTrackTypeNone),
|
|
block_invalidate_cache_stack_(0),
|
|
index_(-1)
|
|
{
|
|
track_input_ = new NodeInput("track_in");
|
|
track_input_->set_data_type(NodeParam::kTrack);
|
|
track_input_->set_dependent(false);
|
|
AddParameter(track_input_);
|
|
|
|
track_output_ = new NodeOutput("track_out");
|
|
AddParameter(track_output_);
|
|
}
|
|
|
|
void TrackOutput::set_track_type(const TrackType &track_type)
|
|
{
|
|
track_type_ = track_type;
|
|
}
|
|
|
|
const TrackType& TrackOutput::track_type()
|
|
{
|
|
return track_type_;
|
|
}
|
|
|
|
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::Refresh()
|
|
{
|
|
QVector<Block*> detect_attached_blocks;
|
|
|
|
Block* prev = previous();
|
|
while (prev != nullptr) {
|
|
detect_attached_blocks.prepend(prev);
|
|
|
|
if (!block_cache_.contains(prev)) {
|
|
emit BlockAdded(prev);
|
|
}
|
|
|
|
prev = prev->previous();
|
|
}
|
|
|
|
foreach (Block* b, block_cache_) {
|
|
if (!detect_attached_blocks.contains(b)) {
|
|
// If the current block was removed, stop referencing it
|
|
if (current_block_ == b) {
|
|
current_block_ = this;
|
|
}
|
|
|
|
emit BlockRemoved(b);
|
|
}
|
|
}
|
|
|
|
block_cache_ = detect_attached_blocks;
|
|
|
|
Block::Refresh();
|
|
}
|
|
|
|
const int &TrackOutput::Index()
|
|
{
|
|
return index_;
|
|
}
|
|
|
|
void TrackOutput::SetIndex(const int &index)
|
|
{
|
|
index_ = index;
|
|
}
|
|
|
|
TrackOutput *TrackOutput::next_track()
|
|
{
|
|
return ValueToPtr<TrackOutput>(track_input_->get_realtime_value_of_connected_output());
|
|
}
|
|
|
|
NodeInput *TrackOutput::track_input()
|
|
{
|
|
return track_input_;
|
|
}
|
|
|
|
NodeOutput* TrackOutput::track_output()
|
|
{
|
|
return track_output_;
|
|
}
|
|
|
|
Block *TrackOutput::BlockContainingTime(const rational &time)
|
|
{
|
|
foreach (Block* block, block_cache_) {
|
|
if (block->in() < time && block->out() > time) {
|
|
return block;
|
|
} else if (block->out() == time) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
Block *TrackOutput::NearestBlockBefore(const rational &time)
|
|
{
|
|
foreach (Block* block, block_cache_) {
|
|
// Blocks are sorted by time, so the first Block who's out point is at/after this time is the correct Block
|
|
if (block->out() >= time) {
|
|
return block;
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
Block *TrackOutput::NearestBlockAfter(const rational &time)
|
|
{
|
|
foreach (Block* block, block_cache_) {
|
|
// Blocks are sorted by time, so the first Block after this time is the correct Block
|
|
if (block->in() >= time) {
|
|
return block;
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
const QVector<Block *> &TrackOutput::Blocks()
|
|
{
|
|
return block_cache_;
|
|
}
|
|
|
|
void TrackOutput::InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from)
|
|
{
|
|
/*// We intercept IC signals from Blocks since we may be performing several options and they may over-signal
|
|
if (from == previous_input() && block_invalidate_cache_stack_ == 0) {
|
|
Node::InvalidateCache(qMax(start_range, rational(0)), qMin(end_range, in()), from);
|
|
} else {
|
|
}*/
|
|
Node::InvalidateCache(start_range, end_range, from);
|
|
}
|
|
|
|
QVariant TrackOutput::Value(NodeOutput *output)
|
|
{
|
|
if (output == track_output_) {
|
|
// Set track output correctly
|
|
return PtrToValue(this);
|
|
/*} else if (output == buffer_output()) {
|
|
ValidateCurrentBlock(in);
|
|
|
|
if (current_block_ != this) {
|
|
// At this point, we must have found the correct block so we use its texture output to produce the image
|
|
return current_block_->buffer_output()->get_value(in, out);
|
|
}
|
|
|
|
// No texture is valid
|
|
return 0;*/
|
|
}
|
|
|
|
// Run default node processing
|
|
return Block::Value(output);
|
|
}
|
|
|
|
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::InsertBlockBefore(Block* block, Block* after)
|
|
{
|
|
Block* before = after->previous();
|
|
|
|
// If a block precedes this one, just insert between them
|
|
if (before != nullptr) {
|
|
InsertBlockBetweenBlocks(block, before, after);
|
|
} else {
|
|
AddBlockToGraph(block);
|
|
|
|
// Otherwise, just connect the block since there's no before clip to insert between
|
|
Block::ConnectBlocks(block, after);
|
|
}
|
|
}
|
|
|
|
void TrackOutput::InsertBlockAfter(Block *block, Block *before)
|
|
{
|
|
InsertBlockBetweenBlocks(block, before, before->next());
|
|
}
|
|
|
|
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);
|
|
|
|
BlockInvalidateCache();
|
|
|
|
if (block_cache_.isEmpty()) {
|
|
ConnectBlockInternal(block);
|
|
} else {
|
|
InsertBlockBetweenBlocks(block, block_cache_.last(), this);
|
|
}
|
|
|
|
UnblockInvalidateCache();
|
|
|
|
// Invalidate area that block was added to
|
|
InvalidateCache(block->in(), in());
|
|
}
|
|
|
|
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::ValidateCurrentBlock(const rational &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()) {
|
|
current_block_ = this;
|
|
return;
|
|
}
|
|
|
|
// If we're here, we need to find the current clip to display
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
void TrackOutput::BlockInvalidateCache()
|
|
{
|
|
block_invalidate_cache_stack_++;
|
|
}
|
|
|
|
void TrackOutput::UnblockInvalidateCache()
|
|
{
|
|
block_invalidate_cache_stack_--;
|
|
}
|
|
|
|
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)
|
|
{
|
|
BlockInvalidateCache();
|
|
|
|
rational remove_in = block->in();
|
|
|
|
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);
|
|
}
|
|
|
|
UnblockInvalidateCache();
|
|
|
|
InvalidateCache(remove_in, in());
|
|
|
|
// FIXME: Should there be removing the Blocks from the graph?
|
|
}
|
|
|
|
void TrackOutput::ReplaceBlock(Block *old, Block *replace)
|
|
{
|
|
Q_ASSERT(old->length() == replace->length());
|
|
|
|
Block* previous = old->previous();
|
|
Block* next = old->next();
|
|
|
|
BlockInvalidateCache();
|
|
|
|
AddBlockToGraph(replace);
|
|
|
|
// Disconnect old block from its surroundings
|
|
if (previous != nullptr) {
|
|
Block::DisconnectBlocks(previous, old);
|
|
Block::ConnectBlocks(previous, replace);
|
|
}
|
|
|
|
if (next != nullptr) {
|
|
Block::DisconnectBlocks(old, next);
|
|
Block::ConnectBlocks(replace, next);
|
|
}
|
|
|
|
UnblockInvalidateCache();
|
|
|
|
InvalidateCache(replace->in(), replace->out());
|
|
}
|
|
|
|
TrackOutput *TrackOutput::TrackFromBlock(Block *block)
|
|
{
|
|
Block* n = block;
|
|
|
|
// Find last valid block in Sequence and assume its a track
|
|
while (n->next() != nullptr) {
|
|
n = n->next();
|
|
}
|
|
|
|
// Downside of this approach is the usage of dynamic_cast, alternative would be looping through all known tracks and
|
|
// seeing if the contain the Block, but this seems slower
|
|
return dynamic_cast<TrackOutput*>(n);
|
|
}
|