717 lines
19 KiB
C++
717 lines
19 KiB
C++
/*
|
|
* Olive Community Edition - Non-Linear Video Editor
|
|
* Copyright (C) 2025 Olive CE 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 "timelineundogeneral.h"
|
|
|
|
#include "node/block/clip/clip.h"
|
|
#include "node/block/transition/transition.h"
|
|
#include "node/factory.h"
|
|
#include "node/math/math/math.h"
|
|
#include "node/math/merge/merge.h"
|
|
#include "timelineundocommon.h"
|
|
#include "timelineundotrack.h"
|
|
|
|
namespace olive
|
|
{
|
|
|
|
//
|
|
// BlockResizeCommand
|
|
//
|
|
void BlockResizeCommand::redo()
|
|
{
|
|
old_length_ = block_->length();
|
|
block_->set_length_and_media_out(new_length_);
|
|
}
|
|
|
|
void BlockResizeCommand::undo()
|
|
{
|
|
block_->set_length_and_media_out(old_length_);
|
|
}
|
|
|
|
//
|
|
// BlockResizeWithMediaInCommand
|
|
//
|
|
void BlockResizeWithMediaInCommand::redo()
|
|
{
|
|
old_length_ = block_->length();
|
|
block_->set_length_and_media_in(new_length_);
|
|
}
|
|
|
|
void BlockResizeWithMediaInCommand::undo()
|
|
{
|
|
block_->set_length_and_media_in(old_length_);
|
|
}
|
|
|
|
//
|
|
// BlockSetMediaInCommand
|
|
//
|
|
void BlockSetMediaInCommand::redo()
|
|
{
|
|
old_media_in_ = block_->media_in();
|
|
block_->set_media_in(new_media_in_);
|
|
}
|
|
|
|
void BlockSetMediaInCommand::undo()
|
|
{
|
|
block_->set_media_in(old_media_in_);
|
|
}
|
|
|
|
//
|
|
// TimelineAddTrackCommand
|
|
//
|
|
TimelineAddTrackCommand::TimelineAddTrackCommand(TrackList *timeline,
|
|
bool automerge_tracks)
|
|
: timeline_(timeline)
|
|
, merge_(nullptr)
|
|
, position_command_(nullptr)
|
|
{
|
|
// Create new track
|
|
track_ = new Track();
|
|
track_->setParent(&memory_manager_);
|
|
|
|
// Determine what input to connect it to
|
|
QString relevant_input;
|
|
|
|
if (timeline_->type() == Track::kVideo) {
|
|
relevant_input = Sequence::kTextureInput;
|
|
} else if (timeline_->type() == Track::kAudio) {
|
|
relevant_input = Sequence::kSamplesInput;
|
|
}
|
|
|
|
// If we have an input to connect to, set it as our `direct` connection
|
|
if (!relevant_input.isEmpty()) {
|
|
direct_ = NodeInput(timeline_->parent(), relevant_input);
|
|
|
|
// If we're automerging and something is already connected, determine if/how to merge it
|
|
if (automerge_tracks && direct_.IsConnected()) {
|
|
if (timeline_->type() == Track::kVideo) {
|
|
// Use merge for video
|
|
merge_ = new MergeNode();
|
|
base_ = NodeInput(merge_, MergeNode::kBaseIn);
|
|
blend_ = NodeInput(merge_, MergeNode::kBlendIn);
|
|
} else if (timeline_->type() == Track::kAudio) {
|
|
// Use math (add) for audio
|
|
merge_ = new MathNode();
|
|
base_ = NodeInput(merge_, MathNode::kParamAIn);
|
|
blend_ = NodeInput(merge_, MathNode::kParamBIn);
|
|
}
|
|
|
|
if (merge_) {
|
|
// If we got created a merge node, ensure it's parented
|
|
merge_->setParent(&memory_manager_);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TimelineAddTrackCommand::redo()
|
|
{
|
|
// Get sequence
|
|
Sequence *sequence = timeline_->parent();
|
|
|
|
// Add track to sequence
|
|
track_->setParent(timeline_->GetParentGraph());
|
|
if (timeline_->GetTrackCount() > 0) {
|
|
track_->SetTrackHeight(
|
|
timeline_->GetTrackAt(timeline_->GetTrackCount() - 1)
|
|
->GetTrackHeight());
|
|
}
|
|
timeline_->ArrayAppend();
|
|
Node::ConnectEdge(track_,
|
|
timeline_->track_input(timeline_->ArraySize() - 1));
|
|
|
|
qreal position_factor = 0.5;
|
|
if (timeline_->type() == Track::kVideo) {
|
|
position_factor = -position_factor;
|
|
}
|
|
bool create_pos_command =
|
|
(!position_command_ && (timeline_->type() == Track::kVideo ||
|
|
timeline_->type() == Track::kAudio));
|
|
if (create_pos_command) {
|
|
position_command_ = new MultiUndoCommand();
|
|
}
|
|
|
|
// Add merge if applicable
|
|
if (merge_) {
|
|
// Determine what was previously connected
|
|
Node *previous_connection = direct_.GetConnectedOutput();
|
|
|
|
// Add merge to graph
|
|
merge_->setParent(timeline_->GetParentGraph());
|
|
|
|
// Connect merge between what used to be here
|
|
Node::DisconnectEdge(previous_connection, direct_);
|
|
Node::ConnectEdge(merge_, direct_);
|
|
Node::ConnectEdge(previous_connection, base_);
|
|
Node::ConnectEdge(track_, blend_);
|
|
|
|
if (create_pos_command) {
|
|
position_command_->add_child(new NodeSetPositionCommand(
|
|
track_, sequence,
|
|
sequence->GetNodePositionInContext(sequence) +
|
|
QPointF(-1, -position_factor)));
|
|
position_command_->add_child(new NodeSetPositionCommand(
|
|
merge_, sequence,
|
|
sequence->GetNodePositionInContext(sequence)));
|
|
position_command_->add_child(
|
|
new NodeSetPositionAndDependenciesRecursivelyCommand(
|
|
merge_, sequence,
|
|
sequence->GetNodePositionInContext(sequence) +
|
|
QPointF(-1,
|
|
position_factor * timeline_->GetTrackCount())));
|
|
}
|
|
} else if (direct_.IsValid() && !direct_.IsConnected()) {
|
|
// If no merge, we have a direct connection, and nothing else is connected, connect this
|
|
Node::ConnectEdge(track_, direct_);
|
|
|
|
if (create_pos_command) {
|
|
// Just position directly next to the context node
|
|
position_command_->add_child(new NodeSetPositionCommand(
|
|
track_, sequence,
|
|
sequence->GetNodePositionInContext(sequence) +
|
|
QPointF(-1, position_factor)));
|
|
}
|
|
}
|
|
|
|
// Run position command if we created one
|
|
if (position_command_) {
|
|
position_command_->redo_now();
|
|
}
|
|
}
|
|
|
|
void TimelineAddTrackCommand::undo()
|
|
{
|
|
if (position_command_) {
|
|
position_command_->undo_now();
|
|
}
|
|
|
|
// Remove merge if applicable
|
|
if (merge_) {
|
|
Node *previous_connection = base_.GetConnectedOutput();
|
|
|
|
Node::DisconnectEdge(track_, blend_);
|
|
Node::DisconnectEdge(previous_connection, base_);
|
|
Node::DisconnectEdge(merge_, direct_);
|
|
Node::ConnectEdge(previous_connection, direct_);
|
|
|
|
merge_->setParent(&memory_manager_);
|
|
} else if (direct_.IsValid() && direct_.GetConnectedOutput() == track_) {
|
|
Node::DisconnectEdge(track_, direct_);
|
|
}
|
|
|
|
// Remove track
|
|
Node::DisconnectEdge(track_,
|
|
timeline_->track_input(timeline_->ArraySize() - 1));
|
|
timeline_->ArrayRemoveLast();
|
|
track_->setParent(&memory_manager_);
|
|
}
|
|
|
|
//
|
|
// TransitionRemoveCommand
|
|
//
|
|
void TransitionRemoveCommand::redo()
|
|
{
|
|
track_ = block_->track();
|
|
out_block_ = block_->connected_out_block();
|
|
in_block_ = block_->connected_in_block();
|
|
|
|
Q_ASSERT(out_block_ || in_block_);
|
|
|
|
TimeRange invalidate_range(block_->in(), block_->out());
|
|
|
|
if (in_block_) {
|
|
in_block_->set_length_and_media_in(in_block_->length() +
|
|
block_->in_offset());
|
|
}
|
|
|
|
if (out_block_) {
|
|
out_block_->set_length_and_media_out(out_block_->length() +
|
|
block_->out_offset());
|
|
}
|
|
|
|
if (in_block_) {
|
|
Node::DisconnectEdge(in_block_,
|
|
NodeInput(block_, TransitionBlock::kInBlockInput));
|
|
}
|
|
|
|
if (out_block_) {
|
|
Node::DisconnectEdge(
|
|
out_block_, NodeInput(block_, TransitionBlock::kOutBlockInput));
|
|
}
|
|
|
|
track_->RippleRemoveBlock(block_);
|
|
|
|
if (remove_from_graph_) {
|
|
if (!remove_command_) {
|
|
remove_command_ = CreateRemoveCommand(block_);
|
|
}
|
|
|
|
remove_command_->redo_now();
|
|
}
|
|
}
|
|
|
|
void TransitionRemoveCommand::undo()
|
|
{
|
|
if (remove_from_graph_) {
|
|
remove_command_->undo_now();
|
|
}
|
|
|
|
if (in_block_) {
|
|
track_->InsertBlockBefore(block_, in_block_);
|
|
} else {
|
|
track_->InsertBlockAfter(block_, out_block_);
|
|
}
|
|
|
|
if (in_block_) {
|
|
Node::ConnectEdge(in_block_,
|
|
NodeInput(block_, TransitionBlock::kInBlockInput));
|
|
}
|
|
|
|
if (out_block_) {
|
|
Node::ConnectEdge(out_block_,
|
|
NodeInput(block_, TransitionBlock::kOutBlockInput));
|
|
}
|
|
|
|
// These if statements must be separated because in_offset and out_offset report different things
|
|
// if only one block is connected vs two. So we have to connect the blocks first before we have
|
|
// an accurate return value from these offset functions.
|
|
if (in_block_) {
|
|
in_block_->set_length_and_media_in(in_block_->length() -
|
|
block_->in_offset());
|
|
}
|
|
|
|
if (out_block_) {
|
|
out_block_->set_length_and_media_out(out_block_->length() -
|
|
block_->out_offset());
|
|
}
|
|
}
|
|
|
|
//
|
|
// TrackListInsertGaps
|
|
//
|
|
void TrackListInsertGaps::prepare()
|
|
{
|
|
// Determine if all tracks will be affected, which will allow us to make some optimizations
|
|
foreach (Track *track, track_list_->GetTracks()) {
|
|
if (track->IsLocked()) {
|
|
continue;
|
|
}
|
|
|
|
working_tracks_.append(track);
|
|
}
|
|
|
|
QVector<Block *> blocks_to_split;
|
|
QVector<Block *> blocks_to_append_gap_to;
|
|
QVector<Track *> tracks_to_append_gap_to;
|
|
|
|
for (Track *track : qAsConst(working_tracks_)) {
|
|
for (Block *b : track->Blocks()) {
|
|
if (dynamic_cast<GapBlock *>(b) && b->in() <= point_ &&
|
|
b->out() >= point_) {
|
|
// Found a gap at the location
|
|
gaps_to_extend_.append(b);
|
|
break;
|
|
} else if (dynamic_cast<ClipBlock *>(b) && b->out() >= point_) {
|
|
bool append_gap = true;
|
|
|
|
if (b->in() == point_) {
|
|
// The only reason we should be here is if this block is at the start of the track,
|
|
// in which case no split needs to occur
|
|
b = nullptr;
|
|
} else if (b->out() > point_) {
|
|
// Block must be split as well as having a gap appended to it
|
|
blocks_to_split.append(b);
|
|
} else if (!b->next()) {
|
|
// At the end of a track, no gap needs to be added at all
|
|
append_gap = false;
|
|
}
|
|
|
|
if (append_gap) {
|
|
tracks_to_append_gap_to.append(track);
|
|
blocks_to_append_gap_to.append(b);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!blocks_to_split.isEmpty()) {
|
|
split_command_ =
|
|
new BlockSplitPreservingLinksCommand(blocks_to_split, { point_ });
|
|
}
|
|
|
|
for (int i = 0; i < blocks_to_append_gap_to.size(); i++) {
|
|
GapBlock *gap = new GapBlock();
|
|
gap->set_length_and_media_out(length_);
|
|
gap->setParent(&memory_manager_);
|
|
gaps_added_.append({ gap, blocks_to_append_gap_to.at(i),
|
|
tracks_to_append_gap_to.at(i) });
|
|
}
|
|
}
|
|
|
|
void TrackListInsertGaps::redo()
|
|
{
|
|
foreach (Block *gap, gaps_to_extend_) {
|
|
gap->set_length_and_media_out(gap->length() + length_);
|
|
}
|
|
|
|
if (split_command_) {
|
|
split_command_->redo_now();
|
|
}
|
|
|
|
foreach (auto add_gap, gaps_added_) {
|
|
add_gap.gap->setParent(add_gap.track->parent());
|
|
add_gap.track->InsertBlockAfter(add_gap.gap, add_gap.before);
|
|
}
|
|
}
|
|
|
|
void TrackListInsertGaps::undo()
|
|
{
|
|
// Remove added gaps
|
|
foreach (auto add_gap, gaps_added_) {
|
|
add_gap.gap->track()->RippleRemoveBlock(add_gap.gap);
|
|
add_gap.gap->setParent(&memory_manager_);
|
|
}
|
|
|
|
// Un-split blocks
|
|
if (split_command_) {
|
|
split_command_->undo_now();
|
|
}
|
|
|
|
// Restore original length of gaps
|
|
foreach (Block *gap, gaps_to_extend_) {
|
|
gap->set_length_and_media_out(gap->length() - length_);
|
|
}
|
|
}
|
|
|
|
//
|
|
// TrackReplaceBlockWithGapCommand
|
|
//
|
|
void TrackReplaceBlockWithGapCommand::redo()
|
|
{
|
|
// Determine if this block is connected to any transitions that should also be removed by this operation
|
|
if (handle_transitions_ && transition_remove_commands_.isEmpty()) {
|
|
CreateRemoveTransitionCommandIfNecessary(false);
|
|
CreateRemoveTransitionCommandIfNecessary(true);
|
|
}
|
|
for (auto it = transition_remove_commands_.cbegin();
|
|
it != transition_remove_commands_.cend(); it++) {
|
|
(*it)->redo_now();
|
|
}
|
|
|
|
if (block_->next()) {
|
|
// Invalidate the range inhabited by this block
|
|
TimeRange invalidate_range(block_->in(), block_->out());
|
|
|
|
// Block has a next, which means it's NOT at the end of the sequence and thus requires a gap
|
|
rational new_gap_length = block_->length();
|
|
|
|
Block *previous = block_->previous();
|
|
Block *next = block_->next();
|
|
|
|
bool previous_is_a_gap = dynamic_cast<GapBlock *>(previous);
|
|
bool next_is_a_gap = dynamic_cast<GapBlock *>(next);
|
|
|
|
if (previous_is_a_gap && next_is_a_gap) {
|
|
// Clip is preceded and followed by a gap, so we'll merge the two
|
|
existing_gap_ = static_cast<GapBlock *>(previous);
|
|
|
|
existing_merged_gap_ = static_cast<GapBlock *>(next);
|
|
new_gap_length += existing_merged_gap_->length();
|
|
track_->RippleRemoveBlock(existing_merged_gap_);
|
|
existing_merged_gap_->setParent(&memory_manager_);
|
|
} else if (previous_is_a_gap) {
|
|
// Extend this gap to fill space left by block
|
|
existing_gap_ = static_cast<GapBlock *>(previous);
|
|
} else if (next_is_a_gap) {
|
|
// Extend this gap to fill space left by block
|
|
existing_gap_ = static_cast<GapBlock *>(next);
|
|
}
|
|
|
|
if (existing_gap_) {
|
|
// Extend an existing gap
|
|
new_gap_length += existing_gap_->length();
|
|
existing_gap_->set_length_and_media_out(new_gap_length);
|
|
track_->RippleRemoveBlock(block_);
|
|
|
|
existing_gap_precedes_ = (existing_gap_ == previous);
|
|
} else {
|
|
// No gap exists to fill this space, create a new one and swap it in
|
|
if (!our_gap_) {
|
|
our_gap_ = new GapBlock();
|
|
our_gap_->set_length_and_media_out(new_gap_length);
|
|
}
|
|
|
|
our_gap_->setParent(track_->parent());
|
|
track_->ReplaceBlock(block_, our_gap_);
|
|
}
|
|
|
|
} else {
|
|
// Block is at the end of the track, simply remove it
|
|
Block *preceding = block_->previous();
|
|
track_->RippleRemoveBlock(block_);
|
|
|
|
// Determine if it's preceded by a gap, and remove that gap if so
|
|
if (dynamic_cast<GapBlock *>(preceding)) {
|
|
track_->RippleRemoveBlock(preceding);
|
|
preceding->setParent(&memory_manager_);
|
|
|
|
existing_merged_gap_ = static_cast<GapBlock *>(preceding);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TrackReplaceBlockWithGapCommand::undo()
|
|
{
|
|
if (our_gap_ || existing_gap_) {
|
|
if (our_gap_) {
|
|
// We made this gap, simply swap our gap back
|
|
track_->ReplaceBlock(our_gap_, block_);
|
|
our_gap_->setParent(&memory_manager_);
|
|
|
|
} else {
|
|
// If we're here, assume that we extended an existing gap
|
|
rational original_gap_length =
|
|
existing_gap_->length() - block_->length();
|
|
|
|
// If we merged two gaps together, restore the second one now
|
|
if (existing_merged_gap_) {
|
|
original_gap_length -= existing_merged_gap_->length();
|
|
existing_merged_gap_->setParent(track_->parent());
|
|
track_->InsertBlockAfter(existing_merged_gap_, existing_gap_);
|
|
existing_merged_gap_ = nullptr;
|
|
}
|
|
|
|
// Restore original block
|
|
if (existing_gap_precedes_) {
|
|
track_->InsertBlockAfter(block_, existing_gap_);
|
|
} else {
|
|
track_->InsertBlockBefore(block_, existing_gap_);
|
|
}
|
|
|
|
// Restore gap's original length
|
|
existing_gap_->set_length_and_media_out(original_gap_length);
|
|
|
|
existing_gap_ = nullptr;
|
|
}
|
|
|
|
} else {
|
|
// Our gap and existing gap were both null, our block must have been at the end and thus
|
|
// required no gap extension/replacement
|
|
|
|
// However, we may have removed an unnecessary gap that preceded it
|
|
if (existing_merged_gap_) {
|
|
existing_merged_gap_->setParent(track_->parent());
|
|
track_->AppendBlock(existing_merged_gap_);
|
|
existing_merged_gap_ = nullptr;
|
|
}
|
|
|
|
// Restore block
|
|
track_->AppendBlock(block_);
|
|
}
|
|
|
|
for (auto it = transition_remove_commands_.crbegin();
|
|
it != transition_remove_commands_.crend(); it++) {
|
|
(*it)->undo_now();
|
|
}
|
|
}
|
|
|
|
void TrackReplaceBlockWithGapCommand::CreateRemoveTransitionCommandIfNecessary(
|
|
bool next)
|
|
{
|
|
Block *relevant_block;
|
|
|
|
if (next) {
|
|
relevant_block = block_->next();
|
|
} else {
|
|
relevant_block = block_->previous();
|
|
}
|
|
|
|
TransitionBlock *transition_cast_test =
|
|
dynamic_cast<TransitionBlock *>(relevant_block);
|
|
|
|
if (transition_cast_test) {
|
|
if ((next && transition_cast_test->connected_out_block() == block_ &&
|
|
!transition_cast_test->connected_in_block()) ||
|
|
(!next && transition_cast_test->connected_in_block() == block_ &&
|
|
!transition_cast_test->connected_out_block())) {
|
|
TransitionRemoveCommand *command =
|
|
new TransitionRemoveCommand(transition_cast_test, true);
|
|
transition_remove_commands_.append(command);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TimelineRemoveTrackCommand::prepare()
|
|
{
|
|
list_ = track_->sequence()->track_list(track_->type());
|
|
|
|
index_ = list_->GetArrayIndexFromCacheIndex(track_->Index());
|
|
|
|
remove_command_ =
|
|
new NodeRemoveWithExclusiveDependenciesAndDisconnect(track_);
|
|
}
|
|
|
|
void TimelineRemoveTrackCommand::redo()
|
|
{
|
|
remove_command_->redo_now();
|
|
|
|
list_->parent()->InputArrayRemove(list_->track_input(), index_);
|
|
}
|
|
|
|
void TimelineRemoveTrackCommand::undo()
|
|
{
|
|
list_->parent()->InputArrayInsert(list_->track_input(), index_);
|
|
|
|
remove_command_->undo_now();
|
|
}
|
|
|
|
void TimelineAddDefaultTransitionCommand::prepare()
|
|
{
|
|
for (auto it = clips_.cbegin(); it != clips_.cend(); it++) {
|
|
ClipBlock *c = *it;
|
|
|
|
// Handle in transition
|
|
if (clips_.contains(static_cast<ClipBlock *>(c->previous()))) {
|
|
// Do nothing, assume this will be handled by a dual transition from that clip
|
|
} else if (dynamic_cast<GapBlock *>(c->previous()) || !c->previous()) {
|
|
// Create in transition
|
|
AddTransition(c, kIn);
|
|
}
|
|
|
|
// Handle out transition
|
|
if (clips_.contains(static_cast<ClipBlock *>(c->next()))) {
|
|
AddTransition(c, kOutDual);
|
|
} else if (dynamic_cast<GapBlock *>(c->next()) || !c->next()) {
|
|
// Create out transition
|
|
AddTransition(c, kOut);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TimelineAddDefaultTransitionCommand::AddTransition(
|
|
ClipBlock *c, CreateTransitionMode mode)
|
|
{
|
|
if (Track *t = c->track()) {
|
|
Node *p = nullptr;
|
|
if (t->type() == Track::kVideo) {
|
|
p = NodeFactory::CreateFromID(
|
|
OLIVE_CONFIG("DefaultVideoTransition").toString());
|
|
} else if (t->type() == Track::kAudio) {
|
|
p = NodeFactory::CreateFromID(
|
|
OLIVE_CONFIG("DefaultAudioTransition").toString());
|
|
}
|
|
|
|
rational transition_length =
|
|
OLIVE_CONFIG("DefaultTransitionLength").value<rational>();
|
|
|
|
// Resize original clip
|
|
switch (mode) {
|
|
case kIn:
|
|
ValidateTransitionLength(c, transition_length);
|
|
|
|
if (transition_length > 0) {
|
|
AdjustClipLength(c, transition_length, false);
|
|
}
|
|
break;
|
|
case kOut:
|
|
ValidateTransitionLength(c, transition_length);
|
|
|
|
if (transition_length > 0) {
|
|
AdjustClipLength(c, transition_length, true);
|
|
}
|
|
break;
|
|
case kOutDual: {
|
|
rational half_length = transition_length / 2;
|
|
|
|
ValidateTransitionLength(static_cast<ClipBlock *>(c->next()),
|
|
half_length);
|
|
ValidateTransitionLength(c, half_length);
|
|
|
|
transition_length = half_length * 2;
|
|
|
|
if (transition_length > 0) {
|
|
AdjustClipLength(static_cast<ClipBlock *>(c->next()),
|
|
half_length, false);
|
|
AdjustClipLength(c, half_length, true);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (transition_length > 0) {
|
|
if (TransitionBlock *transition =
|
|
dynamic_cast<TransitionBlock *>(p)) {
|
|
transition->set_length_and_media_out(transition_length);
|
|
|
|
// Add transition
|
|
commands_.append(new NodeAddCommand(c->parent(), transition));
|
|
|
|
// Insert block
|
|
Block *insert_after = (mode == kIn) ? c->previous() : c;
|
|
commands_.append(new TrackInsertBlockAfterCommand(
|
|
c->track(), transition, insert_after));
|
|
|
|
// Connect
|
|
switch (mode) {
|
|
case kIn:
|
|
commands_.append(new NodeEdgeAddCommand(
|
|
c,
|
|
NodeInput(transition, TransitionBlock::kInBlockInput)));
|
|
break;
|
|
case kOutDual:
|
|
commands_.append(new NodeEdgeAddCommand(
|
|
c->next(),
|
|
NodeInput(transition, TransitionBlock::kInBlockInput)));
|
|
/* fall through */
|
|
case kOut:
|
|
commands_.append(new NodeEdgeAddCommand(
|
|
c, NodeInput(transition,
|
|
TransitionBlock::kOutBlockInput)));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TimelineAddDefaultTransitionCommand::AdjustClipLength(
|
|
ClipBlock *c, const rational &transition_length, bool out)
|
|
{
|
|
rational cur_len = lengths_.value(c, c->length());
|
|
rational new_len = cur_len - transition_length;
|
|
if (out) {
|
|
commands_.append(new BlockResizeCommand(c, new_len));
|
|
} else {
|
|
commands_.append(new BlockResizeWithMediaInCommand(c, new_len));
|
|
}
|
|
lengths_.insert(c, new_len);
|
|
}
|
|
|
|
void TimelineAddDefaultTransitionCommand::ValidateTransitionLength(
|
|
ClipBlock *c, rational &transition_length)
|
|
{
|
|
rational cur_len = lengths_.value(c, c->length());
|
|
rational half_cur_len = cur_len / 2;
|
|
if (transition_length >= half_cur_len) {
|
|
transition_length = half_cur_len - timebase_;
|
|
}
|
|
}
|
|
|
|
}
|