From e9702aeb5276c3dbe7f8313dde30284513cbc74b Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Mon, 12 Aug 2019 23:24:58 +1000 Subject: [PATCH] added some basic timeline functionality --- app/node/block/gap/gap.cpp | 15 +++ app/node/block/gap/gap.h | 4 + app/node/graph.cpp | 21 ++++ app/node/graph.h | 13 +++ app/node/input/media/media.cpp | 9 +- app/node/output/timeline/timeline.cpp | 135 +++++++++++++++++------ app/node/output/timeline/timeline.h | 46 +++++++- app/panel/timeline/timeline.cpp | 7 +- app/panel/timeline/timeline.h | 4 +- app/render/colorservice.cpp | 4 +- app/render/pixelservice.cpp | 4 +- app/widget/timelineview/timelineview.cpp | 43 ++++++-- app/widget/timelineview/timelineview.h | 8 +- app/widget/timelineview/tool/import.cpp | 10 +- 14 files changed, 264 insertions(+), 59 deletions(-) diff --git a/app/node/block/gap/gap.cpp b/app/node/block/gap/gap.cpp index dd40dc465..7f480e5a9 100644 --- a/app/node/block/gap/gap.cpp +++ b/app/node/block/gap/gap.cpp @@ -29,6 +29,21 @@ Block::Type GapBlock::type() return kGap; } +QString GapBlock::Name() +{ + return tr("Gap"); +} + +QString GapBlock::id() +{ + return "org.olivevideoeditor.Olive.gap"; +} + +QString GapBlock::Description() +{ + return tr("A time-based node that represents an empty space."); +} + rational GapBlock::length() { return length_; diff --git a/app/node/block/gap/gap.h b/app/node/block/gap/gap.h index feedb25cf..eedf97582 100644 --- a/app/node/block/gap/gap.h +++ b/app/node/block/gap/gap.h @@ -34,6 +34,10 @@ public: virtual Type type() override; + virtual QString Name() override; + virtual QString id() override; + virtual QString Description() override; + virtual rational length() override; virtual void set_length(const rational &length) override; diff --git a/app/node/graph.cpp b/app/node/graph.cpp index bdb2be6ae..d31946ef2 100644 --- a/app/node/graph.cpp +++ b/app/node/graph.cpp @@ -29,6 +29,10 @@ NodeGraph::NodeGraph() void NodeGraph::AddNode(Node *node) { + if (ContainsNode(node)) { + return; + } + node->setParent(this); connect(node, SIGNAL(EdgeAdded(NodeEdgePtr)), this, SIGNAL(EdgeAdded(NodeEdgePtr))); @@ -37,7 +41,24 @@ void NodeGraph::AddNode(Node *node) emit NodeAdded(node); } +void NodeGraph::AddNodeWithDependencies(Node *node) +{ + // Add node and its connected nodes to graph + AddNode(node); + + // Add all of Block's dependencies + QList node_dependencies = node->GetDependencies(); + foreach (Node* dep, node_dependencies) { + AddNode(dep); + } +} + QList NodeGraph::nodes() { return static_qobjectlist_cast(children()); } + +bool NodeGraph::ContainsNode(Node *n) +{ + return (n->parent() == this); +} diff --git a/app/node/graph.h b/app/node/graph.h index 3d5bf2149..539e5a395 100644 --- a/app/node/graph.h +++ b/app/node/graph.h @@ -45,11 +45,24 @@ public: */ void AddNode(Node* node); + /** + * @brief Adds a node to this graph and all nodes connected to its inputs + * + * Adds the Node to the graph and runs through its inputs adding all of its dependencies (and all of their + * dependencies and so forth). + */ + void AddNodeWithDependencies(Node* node); + /** * @brief Retrieve a complete list of the nodes belonging to this graph */ QList nodes(); + /** + * @brief Returns whether a certain Node is in the graph or not + */ + bool ContainsNode(Node* n); + signals: /** * @brief Signal emitted when a Node is added to the graph diff --git a/app/node/input/media/media.cpp b/app/node/input/media/media.cpp index e2e1cba3e..6e521e1c2 100644 --- a/app/node/input/media/media.cpp +++ b/app/node/input/media/media.cpp @@ -107,15 +107,18 @@ void MediaInput::Process(const rational &time) decoder_->set_stream(footage->stream(0)); } + // Get frame from Decoder FramePtr frame = decoder_->Retrieve(time); if (frame == nullptr) { return; } - // Use OCIO - frame = PixelService::ConvertPixelFormat(frame, olive::PIX_FMT_RGBA32F); - ColorService::ConvertFrame(frame); + // Convert the frame to the Renderer format + //frame = PixelService::ConvertPixelFormat(frame, olive::PIX_FMT_RGBA16F); + + // Convert the frame to the Renderer color space + //ColorService::ConvertFrame(frame); // FIXME: Test code if (tex_buf_.IsCreated()) { diff --git a/app/node/output/timeline/timeline.cpp b/app/node/output/timeline/timeline.cpp index eae91d000..a9639dd80 100644 --- a/app/node/output/timeline/timeline.cpp +++ b/app/node/output/timeline/timeline.cpp @@ -20,6 +20,10 @@ #include "timeline.h" +#include + +#include "node/block/gap/gap.h" +#include "node/graph.h" TimelineOutput::TimelineOutput() : first_block_(nullptr), @@ -54,12 +58,11 @@ QString TimelineOutput::Description() "Sequence."); } -#include "project/item/sequence/sequence.h" - void TimelineOutput::AttachTimeline(TimelinePanel *timeline) { if (attached_timeline_ != nullptr) { - disconnect(attached_timeline_, SIGNAL(RequestInsertBlock(Block*, int)), this, SLOT(InsertBlock(Block*, int))); + disconnect(attached_timeline_, SIGNAL(RequestInsertBlockAtIndex(Block*, int)), this, SLOT(InsertBlockAtIndex(Block*, int))); + disconnect(attached_timeline_, SIGNAL(RequestPlaceBlock(Block*, rational)), this, SLOT(PlaceBlock(Block*, rational))); attached_timeline_->Clear(); } @@ -86,7 +89,8 @@ void TimelineOutput::AttachTimeline(TimelinePanel *timeline) previous_block = previous_block->previous(); } - connect(attached_timeline_, SIGNAL(RequestInsertBlock(Block*, int)), this, SLOT(InsertBlock(Block*, int))); + connect(attached_timeline_, SIGNAL(RequestInsertBlockAtIndex(Block*, int)), this, SLOT(InsertBlockAtIndex(Block*, int))); + connect(attached_timeline_, SIGNAL(RequestPlaceBlock(Block*, rational)), this, SLOT(PlaceBlock(Block*, rational))); } } @@ -154,6 +158,15 @@ void TimelineOutput::Process(const rational &time) texture_output()->set_value(current_block_->texture_output()->get_value(time)); } +void TimelineOutput::InsertBlockBetweenBlocks(Block *block, Block *before, Block *after) +{ + AddBlockToGraph(block); + + Block::DisconnectBlocks(before, after); + Block::ConnectBlocks(before, block); + Block::ConnectBlocks(block, after); +} + Block *TimelineOutput::first_block() { if (block_cache_.isEmpty()) { @@ -168,48 +181,106 @@ Block *TimelineOutput::attached_block() return ValueToPtr(previous_input()->get_value(0)); } -void TimelineOutput::InsertBlock(Block *block, int index) +void TimelineOutput::PrependBlock(Block *block) { - // Add node and its connected nodes to graph - NodeGraph* graph = static_cast(parent()); - graph->AddNode(block); + AddBlockToGraph(block); - // Add all of Block's dependencies - QList block_dependencies = block->GetDependencies(); - foreach (Node* dep, block_dependencies) { - graph->AddNode(dep); + 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. - Block::ConnectBlocks(block, this); + ConnectBlockInternal(block); } else if (index == 0) { // If the index is 0, it goes at the very beginning - Block::ConnectBlocks(block, block_cache_.first()); + PrependBlock(block); + + } else if (index >= block_cache_.size()) { + + // Append Block at the end + AppendBlock(block); } else { - // Otherwise, the block goes between two other blocks somehow - Block* before; - Block* after; - - if (index < block_cache_.size()) { - // The block goes somewhere in between some set of two blocks - before = block_cache_.at(index - 1); - after = block_cache_.at(index); - } else { - // The block goes at the very end - before = block_cache_.last(); - after = this; - } - - // Connect blocks correctly - Block::DisconnectBlocks(before, after); - Block::ConnectBlocks(before, block); - Block::ConnectBlocks(block, after); + // 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(parent()); + graph->AddNodeWithDependencies(block); +} + +void TimelineOutput::PlaceBlock(Block *block, rational start) +{ + AddBlockToGraph(block); + + if (start == 0) { + // FIXME: Remove existing + + PrependBlock(block); + return; + } + + // Check if the placement location is past the end of the timeline + if (start > in()) { + // FIXME: Remove existing + + // 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;iin() == 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; + } + } +} diff --git a/app/node/output/timeline/timeline.h b/app/node/output/timeline/timeline.h index a4533e60b..409e65805 100644 --- a/app/node/output/timeline/timeline.h +++ b/app/node/output/timeline/timeline.h @@ -50,6 +50,18 @@ 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 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); + QVector block_cache_; Block* first_block(); @@ -62,7 +74,39 @@ private: TimelinePanel* attached_timeline_; private slots: - void InsertBlock(Block* block, int index); + /** + * @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 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); }; #endif // TIMELINEOUTPUT_H diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp index c44cec8ff..bdf180c7f 100644 --- a/app/panel/timeline/timeline.cpp +++ b/app/panel/timeline/timeline.cpp @@ -37,7 +37,8 @@ TimelinePanel::TimelinePanel(QWidget *parent) : layout->addWidget(ruler_); view_ = new TimelineView(this); - connect(view_, SIGNAL(RequestInsertBlock(Block*, int)), this, SIGNAL(RequestInsertBlock(Block*, int))); + connect(view_, SIGNAL(RequestInsertBlockAtIndex(Block*, int)), this, SIGNAL(RequestInsertBlockAtIndex(Block*, int))); + connect(view_, SIGNAL(RequestPlaceBlock(Block*, rational)), this, SIGNAL(RequestPlaceBlock(Block*, rational))); layout->addWidget(view_); connect(view_->horizontalScrollBar(), SIGNAL(valueChanged(int)), ruler_, SLOT(SetScroll(int))); @@ -60,12 +61,12 @@ void TimelinePanel::Clear() void TimelinePanel::AddClip(ClipBlock *clip) { - view_->AddClip(clip); + view_->AddBlock(clip); } void TimelinePanel::RemoveClip(ClipBlock *clip) { - view_->RemoveClip(clip); + view_->RemoveBlock(clip); } void TimelinePanel::SetTimebase(const rational &timebase) diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h index 94b95c958..1e968a3b4 100644 --- a/app/panel/timeline/timeline.h +++ b/app/panel/timeline/timeline.h @@ -43,7 +43,9 @@ protected: virtual void changeEvent(QEvent* e) override; signals: - void RequestInsertBlock(Block*, int); + void RequestInsertBlockAtIndex(Block*, int); + + void RequestPlaceBlock(Block* block, rational start); private: void Retranslate(); diff --git a/app/render/colorservice.cpp b/app/render/colorservice.cpp index f8967dd03..d702ed75a 100644 --- a/app/render/colorservice.cpp +++ b/app/render/colorservice.cpp @@ -9,7 +9,9 @@ ColorService::ColorService() void ColorService::ConvertFrame(FramePtr f) { - OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); + OCIO::ConstConfigRcPtr config = OCIO::Config::CreateFromFile("/run/media/matt/Home/OpenColorIO/ocio.configs.0.7v4/nuke-default/config.ocio"); + +// OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); OCIO::ConstProcessorRcPtr processor = config->getProcessor("srgb", OCIO::ROLE_SCENE_LINEAR); diff --git a/app/render/pixelservice.cpp b/app/render/pixelservice.cpp index da79e129e..f66be7f31 100644 --- a/app/render/pixelservice.cpp +++ b/app/render/pixelservice.cpp @@ -26,8 +26,6 @@ const int kRGBAChannels = 4; -PixelService olive::pixel_service; - PixelService::PixelService() { } @@ -98,6 +96,8 @@ FramePtr PixelService::ConvertPixelFormat(FramePtr frame, const olive::PixelForm return frame; } + // FIXME: It'd be nice if this was multithreaded soon + FramePtr converted = std::make_shared(); // Copy parameters diff --git a/app/widget/timelineview/timelineview.cpp b/app/widget/timelineview/timelineview.cpp index 9b0b104df..8250013ae 100644 --- a/app/widget/timelineview/timelineview.cpp +++ b/app/widget/timelineview/timelineview.cpp @@ -50,26 +50,45 @@ TimelineView::TimelineView(QWidget *parent) : SetScale(1.0); } -void TimelineView::AddClip(ClipBlock *clip) +void TimelineView::AddBlock(Block *block) { - TimelineViewClipItem* clip_item = new TimelineViewClipItem(); + switch (block->type()) { + case Block::kClip: + { + TimelineViewClipItem* clip_item = new TimelineViewClipItem(); + ClipBlock* clip = static_cast(block); - // Set up clip with view parameters (clip item will automatically size its rect accordingly) - clip_item->SetClip(clip); - clip_item->SetScale(scale_); + // Set up clip with view parameters (clip item will automatically size its rect accordingly) + clip_item->SetClip(clip); + clip_item->SetScale(scale_); - // Add to list of clip items that can be iterated through - clip_items_.insert(clip, clip_item); + // Add to list of clip items that can be iterated through + clip_items_.insert(clip, clip_item); + + // Add item to graphics scene + scene_.addItem(clip_item); + + connect(clip, SIGNAL(Refreshed()), this, SLOT(BlockChanged())); + break; + } + case Block::kGap: + { + clip_items_.insert(block, nullptr); + break; + } + case Block::kEnd: + // Do nothing + break; + } - // Add item to graphics scene - scene_.addItem(clip_item); - connect(clip, SIGNAL(Refreshed()), this, SLOT(BlockChanged())); } -void TimelineView::RemoveClip(ClipBlock *clip) +void TimelineView::RemoveBlock(Block *block) { - delete clip_items_[clip]; + delete clip_items_[block]; + + clip_items_.remove(block); } void TimelineView::SetScale(const double &scale) diff --git a/app/widget/timelineview/timelineview.h b/app/widget/timelineview/timelineview.h index 4a578fb73..e5b5acdd0 100644 --- a/app/widget/timelineview/timelineview.h +++ b/app/widget/timelineview/timelineview.h @@ -43,9 +43,9 @@ class TimelineView : public QGraphicsView public: TimelineView(QWidget* parent); - void AddClip(ClipBlock* clip); + void AddBlock(Block* block); - void RemoveClip(ClipBlock* clip); + void RemoveBlock(Block* block); void SetScale(const double& scale); @@ -57,7 +57,9 @@ public slots: void SetTime(const int64_t time); signals: - void RequestInsertBlock(Block* block, int index); + void RequestInsertBlockAtIndex(Block* block, int index); + + void RequestPlaceBlock(Block* block, rational start); protected: virtual void mousePressEvent(QMouseEvent *event) override; diff --git a/app/widget/timelineview/tool/import.cpp b/app/widget/timelineview/tool/import.cpp index 5b8b0852e..d1fe70abc 100644 --- a/app/widget/timelineview/tool/import.cpp +++ b/app/widget/timelineview/tool/import.cpp @@ -83,6 +83,14 @@ void TimelineView::ImportTool::DragEnter(QDragEnterEvent *event) void TimelineView::ImportTool::DragMove(QDragMoveEvent *event) { if (parent()->HasGhosts()) { + // Move ghosts to the mouse cursor + foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) { + rational time = parent()->ScreenToTime(event->pos().x()); + + ghost->SetOut(ghost->Out() - ghost->In() + time); + ghost->SetIn(time); + } + event->accept(); } else { event->ignore(); @@ -114,7 +122,7 @@ void TimelineView::ImportTool::DragDrop(QDropEvent *event) // FIXME: If this doesn't have a TimelineOutput node attached, this is a memory leak. Maybe switching nodes to // shared ptrs would be a better idea. - emit parent()->RequestInsertBlock(clip, 0); + emit parent()->RequestPlaceBlock(clip, ghost->In()); } parent()->ClearGhosts();