/*** 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 . ***/ #include "block.h" #include #include "node/output/track/track.h" #include "transition/transition.h" OLIVE_NAMESPACE_ENTER Block::Block() : previous_(nullptr), next_(nullptr) { length_input_ = new NodeInput("length_in", NodeParam::kRational); length_input_->set_connectable(false); length_input_->set_is_keyframable(false); AddInput(length_input_); disconnect(length_input_, &NodeInput::ValueChanged, this, &Block::InputChanged); connect(length_input_, &NodeInput::ValueChanged, this, &Block::LengthInputChanged); media_in_input_ = new NodeInput("media_in_in", NodeParam::kRational); media_in_input_->set_connectable(false); media_in_input_->set_is_keyframable(false); AddInput(media_in_input_); enabled_input_ = new NodeInput("enabled_in", NodeParam::kBoolean); enabled_input_->set_connectable(false); enabled_input_->set_is_keyframable(false); enabled_input_->set_standard_value(true); AddInput(enabled_input_); speed_input_ = new NodeInput("speed_in", NodeParam::kFloat); speed_input_->set_standard_value(1.0); speed_input_->set_property(QStringLiteral("view"), QStringLiteral("percent")); AddInput(speed_input_); // A block's length must be greater than 0 set_length_and_media_out(1); } QList Block::Category() const { return {kCategoryTimeline}; } const rational &Block::in() const { return in_point_; } const rational &Block::out() const { return out_point_; } void Block::set_in(const rational &in) { in_point_ = in; } void Block::set_out(const rational &out) { out_point_ = out; } rational Block::length() const { return length_input_->get_standard_value().value(); } void Block::set_length_and_media_out(const rational &length) { Q_ASSERT(length > 0); if (length == this->length()) { return; } rational old_length = this->length(); set_length_internal(length); LengthChangedEvent(old_length, length, Timeline::kTrimOut); } void Block::set_length_and_media_in(const rational &length) { Q_ASSERT(length > 0); if (length == this->length()) { return; } // Calculate media_in adjustment set_media_in(SequenceToMediaTime(in() + (this->length() - length))); rational old_length = this->length(); // Set the length without setting media out set_length_internal(length); LengthChangedEvent(old_length, length, Timeline::kTrimIn); } Block *Block::previous() { return previous_; } Block *Block::next() { return next_; } void Block::set_previous(Block *previous) { previous_ = previous; } void Block::set_next(Block *next) { next_ = next; } rational Block::media_in() const { return media_in_input_->get_standard_value().value(); } void Block::set_media_in(const rational &media_in) { media_in_input_->set_standard_value(QVariant::fromValue(media_in)); } bool Block::is_enabled() const { return enabled_input_->get_standard_value().toBool(); } void Block::set_enabled(bool e) { enabled_input_->set_standard_value(e); emit EnabledChanged(); } rational Block::SequenceToMediaTime(const rational &sequence_time) const { // These constants are not considered "values" per se, so we don't modify them if (sequence_time == RATIONAL_MIN || sequence_time == RATIONAL_MAX) { return sequence_time; } rational local_time = sequence_time - in(); // FIXME: Doesn't handle reversing if (speed_input_->is_keyframing() || speed_input_->is_connected()) { // FIXME: We'll need to calculate the speed hoo boy } else { double speed_value = speed_input_->get_standard_value().toDouble(); if (qIsNull(speed_value)) { // Effectively holds the frame at the in point local_time = 0; } else if (!qFuzzyCompare(speed_value, 1.0)) { // Multiply time local_time = rational::fromDouble(local_time.toDouble() * speed_value); } } return local_time + media_in(); } rational Block::MediaToSequenceTime(const rational &media_time) const { // These constants are not considered "values" per se, so we don't modify them if (media_time == RATIONAL_MIN || media_time == RATIONAL_MAX) { return media_time; } rational sequence_time = media_time - media_in(); // FIXME: Doesn't handle reversing if (speed_input_->is_keyframing() || speed_input_->is_connected()) { // FIXME: We'll need to calculate the speed hoo boy } else { double speed_value = speed_input_->get_standard_value().toDouble(); if (qIsNull(speed_value)) { // Effectively holds the frame at the in point, also prevents divide by zero sequence_time = 0; } else if (!qFuzzyCompare(speed_value, 1.0)) { // Multiply time sequence_time = rational::fromDouble(sequence_time.toDouble() / speed_value); } } return sequence_time + in(); } void Block::LoadInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data) { if (reader->name() == QStringLiteral("link")) { xml_node_data.block_links.append({this, reader->readElementText().toULongLong()}); } else { Node::LoadInternal(reader, xml_node_data); } } void Block::SaveInternal(QXmlStreamWriter *writer) const { foreach (Block* link, linked_clips_) { writer->writeTextElement(QStringLiteral("link"), QString::number(reinterpret_cast(link))); } } QList Block::GetInputsToHash() const { QList inputs = Node::GetInputsToHash(); // Ignore these inputs inputs.removeOne(media_in_input_); inputs.removeOne(speed_input_); inputs.removeOne(length_input_); return inputs; } void Block::LengthChangedEvent(const rational &, const rational &, const Timeline::MovementMode &) { } void Block::set_length_internal(const rational &length) { length_input_->set_standard_value(QVariant::fromValue(length)); } void Block::LengthInputChanged() { emit LengthChanged(length()); } bool Block::Link(Block *a, Block *b) { if (a == b || !a || !b) { return false; } // Prevent duplicate link entries (assume that we only need to check one clip since this should be the only function // that adds to the linked array) if (Block::AreLinked(a, b)) { return false; } a->linked_clips_.append(b); b->linked_clips_.append(a); emit a->LinksChanged(); emit b->LinksChanged(); return true; } void Block::Link(const QList& blocks) { foreach (Block* a, blocks) { foreach (Block* b, blocks) { Link(a, b); } } } bool Block::Unlink(Block *a, Block *b) { if (a == b || !a || !b) { return false; } if (!Block::AreLinked(a, b)) { return false; } a->linked_clips_.removeOne(b); b->linked_clips_.removeOne(a); emit a->LinksChanged(); emit b->LinksChanged(); return true; } void Block::Unlink(const QList &blocks) { foreach (Block* a, blocks) { foreach (Block* b, blocks) { Unlink(a, b); } } } bool Block::AreLinked(Block *a, Block *b) { return a->linked_clips_.contains(b); } const QVector &Block::linked_clips() { return linked_clips_; } bool Block::HasLinks() { return !linked_clips_.isEmpty(); } bool Block::IsBlock() const { return true; } void Block::Retranslate() { Node::Retranslate(); length_input_->set_name(tr("Length")); media_in_input_->set_name(tr("Media In")); enabled_input_->set_name(tr("Enabled")); speed_input_->set_name(tr("Speed")); } NodeInput *Block::length_input() const { return length_input_; } NodeInput *Block::media_in_input() const { return media_in_input_; } NodeInput *Block::speed_input() const { return speed_input_; } void Block::Hash(QCryptographicHash &, const rational &) const { // A block does nothing by default, so we hash nothing } OLIVE_NAMESPACE_EXIT