build: split the engine into liboakengine.so; worker drops the UI entirely
Physical split: app/{audio,cli,codec,common,config,node,pluginSupport,
render,task,timeline,undo,tool,shaders} plus coreengine, version and
ui/icons+colorcoding move to a new top-level engine/ tree, built as
liboakengine.so (shared). The render backends (oakgl/oakvulkan) move
with it and link the engine library instead of embedding a static
render-core subset (libolive-rendercore is gone).
- oak-render-worker now links liboakengine instead of the whole
libolive-editor object set: 336MB -> 2.9MB, no Qt Widgets UI
- the editor links liboakengine for the engine and keeps only UI
objects in libolive-editor
- install/packaging: GNUInstallDirs libdir on Linux, bundle copy on
macOS, oakengine.dll staged for NSIS, AppImage validation entry
- fix backend lookup for the new layout: DynamicRenderer searched
../app but backends now live in engine/; a stale pre-split liboakgl
in the build tree got dlopened instead, re-initialized and later
destroyed the interposed engine statics (full-suite segfault at
DialogSequenceParameterTab, found via gdb watchpoint)
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 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(clip)
|
||||
add_subdirectory(gap)
|
||||
add_subdirectory(subtitle)
|
||||
add_subdirectory(transition)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/block/block.h
|
||||
node/block/block.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,152 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "block.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "node/inputdragger.h"
|
||||
#include "node/sliderdisplaytype.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super Node
|
||||
|
||||
const QString Block::k_length_input = QStringLiteral("length_in");
|
||||
|
||||
Block::Block()
|
||||
: previous_(nullptr)
|
||||
, next_(nullptr)
|
||||
, track_(nullptr)
|
||||
{
|
||||
add_input(k_length_input, NodeValue::k_rational,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable |
|
||||
k_input_flag_hidden));
|
||||
set_input_property(k_length_input, QStringLiteral("min"),
|
||||
QVariant::fromValue(Rational(0, 1)));
|
||||
set_input_property(k_length_input, QStringLiteral("view"),
|
||||
slider::k_time);
|
||||
set_input_property(k_length_input, QStringLiteral("viewlock"), true);
|
||||
|
||||
set_input_flag(k_enabled_input, k_input_flag_not_connectable);
|
||||
set_input_flag(k_enabled_input, k_input_flag_not_keyframable);
|
||||
|
||||
set_flag(k_dont_show_in_param_view);
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> Block::category() const
|
||||
{
|
||||
return { k_category_timeline };
|
||||
}
|
||||
|
||||
Rational Block::length() const
|
||||
{
|
||||
return get_standard_value(k_length_input).value<Rational>();
|
||||
}
|
||||
|
||||
void Block::set_length_and_media_out(const Rational &length)
|
||||
{
|
||||
if (length == this->length()) {
|
||||
return;
|
||||
}
|
||||
|
||||
set_length_internal(length);
|
||||
}
|
||||
|
||||
void Block::set_length_and_media_in(const Rational &length)
|
||||
{
|
||||
if (length == this->length()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the length without setting media out
|
||||
set_length_internal(length);
|
||||
}
|
||||
|
||||
bool Block::is_enabled() const
|
||||
{
|
||||
return get_standard_value(k_enabled_input).toBool();
|
||||
}
|
||||
|
||||
void Block::set_enabled(bool e)
|
||||
{
|
||||
set_standard_value(k_enabled_input, e);
|
||||
|
||||
emit enabled_changed();
|
||||
}
|
||||
|
||||
void Block::InputValueChangedEvent(const QString &input, int element)
|
||||
{
|
||||
super::InputValueChangedEvent(input, element);
|
||||
|
||||
if (input == k_length_input) {
|
||||
emit length_changed();
|
||||
} else if (input == k_enabled_input) {
|
||||
emit enabled_changed();
|
||||
}
|
||||
}
|
||||
|
||||
void Block::set_length_internal(const Rational &length)
|
||||
{
|
||||
set_standard_value(k_length_input, QVariant::fromValue(length));
|
||||
}
|
||||
|
||||
void Block::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_length_input, tr("Length"));
|
||||
set_input_name(k_enabled_input, tr("Enabled"));
|
||||
}
|
||||
|
||||
void Block::invalidate_cache(const TimeRange &range, const QString &from,
|
||||
int element, InvalidateCacheOptions options)
|
||||
{
|
||||
TimeRange r;
|
||||
|
||||
if (from == k_length_input) {
|
||||
// We must intercept the signal here
|
||||
r = TimeRange(qMin(length(), last_length_), RATIONAL_MAX);
|
||||
|
||||
if (!NodeInputDragger::is_input_being_dragged()) {
|
||||
last_length_ = length();
|
||||
}
|
||||
|
||||
options.insert(QStringLiteral("lengthevent"), true);
|
||||
} else {
|
||||
r = range;
|
||||
}
|
||||
|
||||
super::invalidate_cache(r, from, element, options);
|
||||
}
|
||||
|
||||
void Block::set_previous_next(Block *previous, Block *next)
|
||||
{
|
||||
if (previous) {
|
||||
previous->set_next(next);
|
||||
}
|
||||
if (next) {
|
||||
next->set_previous(previous);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 OAK_BLOCK_H
|
||||
#define OAK_BLOCK_H
|
||||
|
||||
#include "node/node.h"
|
||||
#include "timeline/timelinecommon.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class TransitionBlock;
|
||||
|
||||
/**
|
||||
* @brief A Node that represents a block of time, also displayable on a Timeline
|
||||
*/
|
||||
class Block : public Node {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Block();
|
||||
|
||||
virtual QVector<CategoryID> category() const override;
|
||||
|
||||
const Rational &in() const
|
||||
{
|
||||
return in_point_;
|
||||
}
|
||||
|
||||
const Rational &out() const
|
||||
{
|
||||
return out_point_;
|
||||
}
|
||||
|
||||
void set_in(const Rational &in)
|
||||
{
|
||||
in_point_ = in;
|
||||
}
|
||||
|
||||
void set_out(const Rational &out)
|
||||
{
|
||||
out_point_ = out;
|
||||
}
|
||||
|
||||
Rational length() const;
|
||||
virtual void set_length_and_media_out(const Rational &length);
|
||||
virtual void set_length_and_media_in(const Rational &length);
|
||||
|
||||
TimeRange range() const
|
||||
{
|
||||
return TimeRange(in(), out());
|
||||
}
|
||||
|
||||
Block *previous() const
|
||||
{
|
||||
return previous_;
|
||||
}
|
||||
|
||||
Block *next() const
|
||||
{
|
||||
return next_;
|
||||
}
|
||||
|
||||
void set_previous(Block *previous)
|
||||
{
|
||||
previous_ = previous;
|
||||
}
|
||||
|
||||
void set_next(Block *next)
|
||||
{
|
||||
next_ = next;
|
||||
}
|
||||
|
||||
Track *track() const
|
||||
{
|
||||
return track_;
|
||||
}
|
||||
|
||||
void set_track(Track *track)
|
||||
{
|
||||
track_ = track;
|
||||
emit track_changed(track_);
|
||||
}
|
||||
|
||||
bool is_enabled() const;
|
||||
void set_enabled(bool e);
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
virtual void invalidate_cache(
|
||||
const TimeRange &range, const QString &from, int element = -1,
|
||||
InvalidateCacheOptions options = InvalidateCacheOptions()) override;
|
||||
|
||||
static const QString k_length_input;
|
||||
|
||||
static void set_previous_next(Block *previous, Block *next);
|
||||
|
||||
public slots:
|
||||
|
||||
signals:
|
||||
void enabled_changed();
|
||||
|
||||
void length_changed();
|
||||
|
||||
void preview_changed();
|
||||
|
||||
void track_changed(Track *track);
|
||||
|
||||
protected:
|
||||
virtual void InputValueChangedEvent(const QString &input,
|
||||
int element) override;
|
||||
|
||||
Block *previous_;
|
||||
Block *next_;
|
||||
|
||||
private:
|
||||
void set_length_internal(const Rational &length);
|
||||
|
||||
Rational in_point_;
|
||||
Rational out_point_;
|
||||
Track *track_;
|
||||
|
||||
Rational last_length_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_BLOCK_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 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/block/clip/clip.h
|
||||
node/block/clip/clip.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,662 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "clip.h"
|
||||
|
||||
#include "config/config.h"
|
||||
#include "node/block/transition/transition.h"
|
||||
#include "node/output/track/track.h"
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "node/sliderdisplaytype.h"
|
||||
#include "node/sliderdisplaytype.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super Block
|
||||
|
||||
const QString ClipBlock::k_buffer_in = QStringLiteral("buffer_in");
|
||||
const QString ClipBlock::k_media_in_input = QStringLiteral("media_in_in");
|
||||
const QString ClipBlock::k_speed_input = QStringLiteral("speed_in");
|
||||
const QString ClipBlock::k_reverse_input = QStringLiteral("reverse_in");
|
||||
const QString ClipBlock::k_maintain_audio_pitch_input =
|
||||
QStringLiteral("maintain_audio_pitch_in");
|
||||
const QString ClipBlock::k_auto_cache_input = QStringLiteral("autocache_in");
|
||||
const QString ClipBlock::k_loop_mode_input = QStringLiteral("loop_in");
|
||||
|
||||
ClipBlock::ClipBlock()
|
||||
: in_transition_(nullptr)
|
||||
, out_transition_(nullptr)
|
||||
, connected_viewer_(nullptr)
|
||||
{
|
||||
add_input(k_media_in_input, NodeValue::k_rational,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable));
|
||||
set_input_property(k_media_in_input, QStringLiteral("view"),
|
||||
slider::k_time);
|
||||
set_input_property(k_media_in_input, QStringLiteral("viewlock"), true);
|
||||
|
||||
add_input(k_speed_input, NodeValue::k_float, 1.0,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable));
|
||||
set_input_property(k_speed_input, QStringLiteral("view"),
|
||||
slider::k_percentage);
|
||||
set_input_property(k_speed_input, QStringLiteral("min"), 0.0);
|
||||
|
||||
add_input(k_reverse_input, NodeValue::k_boolean, false,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable));
|
||||
|
||||
add_input(k_maintain_audio_pitch_input, NodeValue::k_boolean, false,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable));
|
||||
|
||||
add_input(k_auto_cache_input, NodeValue::k_boolean, false,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable));
|
||||
|
||||
prepend_input(k_buffer_in, NodeValue::k_none,
|
||||
InputFlags(k_input_flag_not_keyframable));
|
||||
//SetValueHintForInput(kBufferIn, ValueHint(NodeValue::kBuffer));
|
||||
|
||||
set_effect_input(k_buffer_in);
|
||||
|
||||
add_input(k_loop_mode_input, NodeValue::k_combo, 0,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable));
|
||||
}
|
||||
|
||||
QString ClipBlock::name() const
|
||||
{
|
||||
if (connected_viewer_ && !connected_viewer_->get_label().isEmpty()) {
|
||||
return connected_viewer_->get_label();
|
||||
} else if (track()) {
|
||||
if (track()->type() == Track::k_video) {
|
||||
return tr("Video Clip");
|
||||
} else if (track()->type() == Track::k_audio) {
|
||||
return tr("Audio Clip");
|
||||
}
|
||||
}
|
||||
|
||||
return tr("Clip");
|
||||
}
|
||||
|
||||
QString ClipBlock::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.clip");
|
||||
}
|
||||
|
||||
QString ClipBlock::description() const
|
||||
{
|
||||
return tr("A time-based node that represents a media source.");
|
||||
}
|
||||
|
||||
void ClipBlock::set_length_and_media_out(const Rational &length)
|
||||
{
|
||||
if (length == this->length()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reverse()) {
|
||||
// Calculate media_in adjustment
|
||||
Rational proposed_media_in = sequence_to_media_time(
|
||||
this->length() - length, k_stm_ignore_reverse | k_stm_ignore_loop);
|
||||
set_media_in(proposed_media_in);
|
||||
}
|
||||
|
||||
super::set_length_and_media_out(length);
|
||||
}
|
||||
|
||||
void ClipBlock::set_length_and_media_in(const Rational &length)
|
||||
{
|
||||
if (length == this->length()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rational old_length = this->length();
|
||||
|
||||
super::set_length_and_media_in(length);
|
||||
|
||||
if (!reverse()) {
|
||||
// Calculate media_in adjustment
|
||||
set_media_in(sequence_to_media_time(old_length - length, k_stm_ignore_loop));
|
||||
}
|
||||
}
|
||||
|
||||
Rational ClipBlock::media_in() const
|
||||
{
|
||||
return get_standard_value(k_media_in_input).value<Rational>();
|
||||
}
|
||||
|
||||
Node::ValueHint ClipBlock::get_value_hint_for_input(const QString &input,
|
||||
int element) const
|
||||
{
|
||||
if (input == k_buffer_in) {
|
||||
// The buffer input takes whatever the connected node provides, so it
|
||||
// is declared as kNone and carries no stored hint. When the connected
|
||||
// node pushes more than one value type (a footage pushes both a
|
||||
// kTexture job and a kSamples job), a typeless lookup falls back to
|
||||
// the last value in the table, which may feed audio samples into a
|
||||
// video clip and produce a black frame. Prefer the value type that
|
||||
// matches this clip's track.
|
||||
switch (get_track_type()) {
|
||||
case Track::k_video:
|
||||
return ValueHint(QVector<NodeValue::Type>{ NodeValue::k_texture });
|
||||
case Track::k_audio:
|
||||
return ValueHint(QVector<NodeValue::Type>{ NodeValue::k_samples });
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return super::get_value_hint_for_input(input, element);
|
||||
}
|
||||
|
||||
void ClipBlock::set_media_in(const Rational &media_in)
|
||||
{
|
||||
set_standard_value(k_media_in_input, QVariant::fromValue(media_in));
|
||||
|
||||
request_invalidated_from_connected();
|
||||
}
|
||||
|
||||
void ClipBlock::set_autocache(bool e)
|
||||
{
|
||||
set_standard_value(k_auto_cache_input, e);
|
||||
}
|
||||
|
||||
void ClipBlock::discard_cache()
|
||||
{
|
||||
if (Node *connected = get_connected_output(k_buffer_in)) {
|
||||
Track::Type type = get_track_type();
|
||||
if (type == Track::k_video) {
|
||||
connected->video_frame_cache()->invalidate(
|
||||
TimeRange(RATIONAL_MIN, RATIONAL_MAX));
|
||||
} else if (type == Track::k_audio) {
|
||||
connected->audio_playback_cache()->invalidate(
|
||||
TimeRange(RATIONAL_MIN, RATIONAL_MAX));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rational ClipBlock::sequence_to_media_time(const Rational &sequence_time,
|
||||
uint64_t flags) 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 media_time = sequence_time;
|
||||
|
||||
if (reverse() && !(flags & k_stm_ignore_reverse)) {
|
||||
media_time = length() - media_time;
|
||||
}
|
||||
|
||||
if (!(flags & k_stm_ignore_speed)) {
|
||||
double speed_value = speed();
|
||||
if (qIsNull(speed_value)) {
|
||||
// Effectively holds the frame at the in point
|
||||
media_time = 0;
|
||||
} else if (!qFuzzyCompare(speed_value, 1.0)) {
|
||||
// Multiply time
|
||||
media_time =
|
||||
Rational::from_double(media_time.to_double() * speed_value);
|
||||
}
|
||||
}
|
||||
|
||||
media_time += media_in();
|
||||
|
||||
/*if (!(flags & kSTMIgnoreLoop)
|
||||
&& this->loop_mode() != kLoopModeOff
|
||||
&& connected_viewer_
|
||||
&& !connected_viewer_->GetLength().isNull()
|
||||
&& (media_time < 0 || media_time >= connected_viewer_->GetLength())) {
|
||||
if (loop_mode() == kLoopModeLoop) {
|
||||
while (media_time < 0) {
|
||||
media_time += connected_viewer_->GetLength();
|
||||
}
|
||||
while (media_time >= connected_viewer_->GetLength()) {
|
||||
media_time -= connected_viewer_->GetLength();
|
||||
}
|
||||
} else if (loop_mode() == kLoopModeClamp) {
|
||||
media_time = std::clamp(media_time, Rational(0), connected_viewer_->GetLength()-connected_viewer_->GetVideoParams().frame_rate_as_time_base());
|
||||
}
|
||||
}*/
|
||||
|
||||
return media_time;
|
||||
}
|
||||
|
||||
Rational ClipBlock::media_to_sequence_time(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();
|
||||
|
||||
double speed_value = speed();
|
||||
if (qIsNull(speed_value)) {
|
||||
// Speed zero holds the frame at the in point, so map to that frame
|
||||
sequence_time = media_in();
|
||||
} else if (!qFuzzyCompare(speed_value, 1.0)) {
|
||||
// Divide time
|
||||
sequence_time =
|
||||
Rational::from_double(sequence_time.to_double() / speed_value);
|
||||
}
|
||||
|
||||
if (reverse()) {
|
||||
sequence_time = length() - sequence_time;
|
||||
}
|
||||
|
||||
return sequence_time;
|
||||
}
|
||||
|
||||
void ClipBlock::request_range_from_connected(const TimeRange &range)
|
||||
{
|
||||
Track::Type type = get_track_type();
|
||||
|
||||
if (type == Track::k_video || type == Track::k_audio) {
|
||||
if (Node *connected = get_connected_output(k_buffer_in)) {
|
||||
TimeRange max_range = media_range();
|
||||
if (type == Track::k_video) {
|
||||
// Handle thumbnails
|
||||
request_range_for_cache(connected->thumbnail_cache(), max_range,
|
||||
range, true, false);
|
||||
{
|
||||
TimeRange thumb_range = range.intersected(max_range);
|
||||
if (get_adjusted_thumbnail_range(&thumb_range)) {
|
||||
connected->thumbnail_cache()->request(
|
||||
this->track()->sequence(), thumb_range);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle video cache
|
||||
request_range_for_cache(connected->video_frame_cache(), max_range,
|
||||
range, true, is_autocaching());
|
||||
} else if (type == Track::k_audio) {
|
||||
// Handle waveforms
|
||||
request_range_for_cache(
|
||||
connected->waveform_cache(), max_range, range, true,
|
||||
(OAK_CONFIG("TimelineWaveformMode").toInt() ==
|
||||
Timeline::k_waveforms_enabled));
|
||||
|
||||
// Handle audio cache
|
||||
request_range_for_cache(connected->audio_playback_cache(),
|
||||
max_range, range, true, is_autocaching());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClipBlock::request_invalidated_from_connected(bool force_all,
|
||||
const TimeRange &intersect)
|
||||
{
|
||||
Track::Type type = get_track_type();
|
||||
|
||||
if (type == Track::k_video || type == Track::k_audio) {
|
||||
if (Node *connected = get_connected_output(k_buffer_in)) {
|
||||
TimeRange max_range = media_range();
|
||||
|
||||
if (!intersect.length().isNull()) {
|
||||
max_range = max_range.intersected(intersect);
|
||||
}
|
||||
|
||||
if (type == Track::k_video) {
|
||||
// Handle thumbnails
|
||||
TimeRange thumb_range = max_range;
|
||||
if (get_adjusted_thumbnail_range(&thumb_range)) {
|
||||
request_invalidated_for_cache(connected->thumbnail_cache(),
|
||||
thumb_range);
|
||||
}
|
||||
|
||||
// Handle video cache
|
||||
if (is_autocaching() || force_all) {
|
||||
request_invalidated_for_cache(connected->video_frame_cache(),
|
||||
max_range);
|
||||
}
|
||||
} else if (type == Track::k_audio) {
|
||||
// Handle waveforms
|
||||
if (OAK_CONFIG("TimelineWaveformMode").toInt() ==
|
||||
Timeline::k_waveforms_enabled) {
|
||||
request_invalidated_for_cache(connected->waveform_cache(),
|
||||
max_range);
|
||||
}
|
||||
|
||||
// Handle audio cache
|
||||
if (is_autocaching() || force_all) {
|
||||
request_invalidated_for_cache(
|
||||
connected->audio_playback_cache(), max_range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClipBlock::request_range_for_cache(PlaybackCache *cache,
|
||||
const TimeRange &max_range,
|
||||
const TimeRange &range, bool invalidate,
|
||||
bool request)
|
||||
{
|
||||
TimeRange r = range.intersected(max_range);
|
||||
|
||||
if (invalidate) {
|
||||
cache->invalidate(r);
|
||||
}
|
||||
|
||||
if (request) {
|
||||
cache->request(this->track()->sequence(), r);
|
||||
}
|
||||
}
|
||||
|
||||
void ClipBlock::request_invalidated_for_cache(PlaybackCache *cache,
|
||||
const TimeRange &max_range)
|
||||
{
|
||||
TimeRangeList invalid = cache->get_invalidated_ranges(max_range);
|
||||
|
||||
for (const PlaybackCache::Passthrough &p : cache->get_passthroughs()) {
|
||||
invalid.remove(p);
|
||||
}
|
||||
|
||||
for (const TimeRange &r : invalid) {
|
||||
request_range_for_cache(cache, max_range, r, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
bool ClipBlock::get_adjusted_thumbnail_range(TimeRange *r) const
|
||||
{
|
||||
switch (static_cast<Timeline::ThumbnailMode>(
|
||||
OAK_CONFIG("TimelineThumbnailMode").toInt())) {
|
||||
case Timeline::k_thumbnail_off:
|
||||
// Don't cache any range
|
||||
return false;
|
||||
case Timeline::k_thumbnail_in_out: {
|
||||
// Only cache in point
|
||||
Rational in = this->media_range().in();
|
||||
if (r->contains(in)) {
|
||||
// Cache only the in point
|
||||
*r = TimeRange(in, in + thumbnail_cache()->get_timebase());
|
||||
return true;
|
||||
} else {
|
||||
// Cache nothing
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case Timeline::k_thumbnail_on:
|
||||
// Cache entire range
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClipBlock::invalidate_cache(const TimeRange &range, const QString &from,
|
||||
int element, InvalidateCacheOptions options)
|
||||
{
|
||||
Q_UNUSED(element)
|
||||
|
||||
// If signal is from texture input, transform all times from media time to sequence time
|
||||
if (from == k_buffer_in) {
|
||||
// Render caches where necessary
|
||||
if (are_caches_enabled()) {
|
||||
request_range_from_connected(range);
|
||||
}
|
||||
|
||||
// Adjust range from media time to sequence time
|
||||
TimeRange adj;
|
||||
double speed_value = speed();
|
||||
|
||||
if (qIsNull(speed_value)) {
|
||||
// Handle 0 speed by invalidating the whole clip
|
||||
adj = TimeRange(RATIONAL_MIN, RATIONAL_MAX);
|
||||
} else {
|
||||
adj = TimeRange(media_to_sequence_time(range.in()),
|
||||
media_to_sequence_time(range.out()));
|
||||
}
|
||||
|
||||
// Find connected viewer node
|
||||
auto viewers = find_input_nodes_connected_to_input<ViewerOutput>(
|
||||
NodeInput(this, k_buffer_in), 1);
|
||||
ViewerOutput *new_connected_viewer =
|
||||
viewers.isEmpty() ? nullptr : viewers.first();
|
||||
|
||||
if (new_connected_viewer != connected_viewer_) {
|
||||
if (connected_viewer_) {
|
||||
disconnect(connected_viewer_->get_markers(),
|
||||
&TimelineMarkerList::marker_added, this,
|
||||
&ClipBlock::preview_changed);
|
||||
disconnect(connected_viewer_->get_markers(),
|
||||
&TimelineMarkerList::marker_removed, this,
|
||||
&ClipBlock::preview_changed);
|
||||
disconnect(connected_viewer_->get_markers(),
|
||||
&TimelineMarkerList::marker_modified, this,
|
||||
&ClipBlock::preview_changed);
|
||||
}
|
||||
|
||||
connected_viewer_ = new_connected_viewer;
|
||||
|
||||
if (connected_viewer_) {
|
||||
connect(connected_viewer_->get_markers(),
|
||||
&TimelineMarkerList::marker_added, this,
|
||||
&ClipBlock::preview_changed);
|
||||
connect(connected_viewer_->get_markers(),
|
||||
&TimelineMarkerList::marker_removed, this,
|
||||
&ClipBlock::preview_changed);
|
||||
connect(connected_viewer_->get_markers(),
|
||||
&TimelineMarkerList::marker_modified, this,
|
||||
&ClipBlock::preview_changed);
|
||||
}
|
||||
}
|
||||
|
||||
super::invalidate_cache(adj, from, element, options);
|
||||
} else {
|
||||
// Otherwise, pass signal along normally
|
||||
super::invalidate_cache(range, from, element, options);
|
||||
}
|
||||
}
|
||||
|
||||
void ClipBlock::LinkChangeEvent()
|
||||
{
|
||||
block_links_.clear();
|
||||
|
||||
foreach (Node *n, links()) {
|
||||
ClipBlock *b = dynamic_cast<ClipBlock *>(n);
|
||||
|
||||
if (b) {
|
||||
block_links_.append(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClipBlock::InputConnectedEvent(const QString &input, int element,
|
||||
Node *output)
|
||||
{
|
||||
super::InputConnectedEvent(input, element, output);
|
||||
|
||||
if (input == k_buffer_in) {
|
||||
connect(output->thumbnail_cache(), &FrameHashCache::invalidated, this,
|
||||
&Block::preview_changed);
|
||||
connect(output->waveform_cache(), &AudioPlaybackCache::invalidated,
|
||||
this, &Block::preview_changed);
|
||||
connect(output->video_frame_cache(), &FrameHashCache::invalidated, this,
|
||||
&Block::preview_changed);
|
||||
connect(output->audio_playback_cache(),
|
||||
&AudioPlaybackCache::invalidated, this, &Block::preview_changed);
|
||||
connect(output->thumbnail_cache(), &FrameHashCache::validated, this,
|
||||
&Block::preview_changed);
|
||||
connect(output->waveform_cache(), &AudioPlaybackCache::validated, this,
|
||||
&Block::preview_changed);
|
||||
connect(output->video_frame_cache(), &FrameHashCache::validated, this,
|
||||
&Block::preview_changed);
|
||||
connect(output->audio_playback_cache(), &AudioPlaybackCache::validated,
|
||||
this, &Block::preview_changed);
|
||||
}
|
||||
}
|
||||
|
||||
void ClipBlock::InputDisconnectedEvent(const QString &input, int element,
|
||||
Node *output)
|
||||
{
|
||||
super::InputDisconnectedEvent(input, element, output);
|
||||
|
||||
if (input == k_buffer_in) {
|
||||
disconnect(output->thumbnail_cache(), &FrameHashCache::invalidated,
|
||||
this, &Block::preview_changed);
|
||||
disconnect(output->waveform_cache(), &AudioPlaybackCache::invalidated,
|
||||
this, &Block::preview_changed);
|
||||
disconnect(output->video_frame_cache(), &FrameHashCache::invalidated,
|
||||
this, &Block::preview_changed);
|
||||
disconnect(output->audio_playback_cache(),
|
||||
&AudioPlaybackCache::invalidated, this,
|
||||
&Block::preview_changed);
|
||||
disconnect(output->thumbnail_cache(), &FrameHashCache::validated, this,
|
||||
&Block::preview_changed);
|
||||
disconnect(output->waveform_cache(), &AudioPlaybackCache::validated,
|
||||
this, &Block::preview_changed);
|
||||
disconnect(output->video_frame_cache(), &FrameHashCache::validated,
|
||||
this, &Block::preview_changed);
|
||||
disconnect(output->audio_playback_cache(),
|
||||
&AudioPlaybackCache::validated, this,
|
||||
&Block::preview_changed);
|
||||
}
|
||||
}
|
||||
|
||||
void ClipBlock::InputValueChangedEvent(const QString &input, int element)
|
||||
{
|
||||
super::InputValueChangedEvent(input, element);
|
||||
|
||||
if (input == k_auto_cache_input) {
|
||||
if (is_autocaching()) {
|
||||
request_invalidated_from_connected();
|
||||
} else {
|
||||
Track::Type type = get_track_type();
|
||||
|
||||
if (Node *connected = get_connected_output(k_buffer_in)) {
|
||||
if (type == Track::k_video) {
|
||||
emit connected->video_frame_cache()->cancel_all();
|
||||
} else if (type == Track::k_audio) {
|
||||
emit connected->audio_playback_cache()->cancel_all();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (input == k_loop_mode_input) {
|
||||
emit preview_changed();
|
||||
}
|
||||
}
|
||||
|
||||
TimeRange ClipBlock::input_time_adjustment(const QString &input, int element,
|
||||
const TimeRange &input_time,
|
||||
bool clamp) const
|
||||
{
|
||||
Q_UNUSED(element)
|
||||
|
||||
if (input == k_buffer_in) {
|
||||
return TimeRange(sequence_to_media_time(input_time.in()),
|
||||
sequence_to_media_time(input_time.out()));
|
||||
}
|
||||
|
||||
return super::input_time_adjustment(input, element, input_time, clamp);
|
||||
}
|
||||
|
||||
TimeRange ClipBlock::output_time_adjustment(const QString &input, int element,
|
||||
const TimeRange &input_time) const
|
||||
{
|
||||
Q_UNUSED(element)
|
||||
|
||||
if (input == k_buffer_in) {
|
||||
return TimeRange(media_to_sequence_time(input_time.in()),
|
||||
media_to_sequence_time(input_time.out()));
|
||||
}
|
||||
|
||||
return super::output_time_adjustment(input, element, input_time);
|
||||
}
|
||||
|
||||
void ClipBlock::value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const
|
||||
{
|
||||
Q_UNUSED(globals)
|
||||
|
||||
// We discard most values here except for the buffer we received
|
||||
NodeValue data = value[k_buffer_in];
|
||||
|
||||
table->clear();
|
||||
if (data.type() != NodeValue::k_none) {
|
||||
table->push(data);
|
||||
}
|
||||
}
|
||||
|
||||
void ClipBlock::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_buffer_in, tr("Buffer"));
|
||||
set_input_name(k_media_in_input, tr("Media In"));
|
||||
set_input_name(k_speed_input, tr("Speed"));
|
||||
set_input_name(k_reverse_input, tr("Reverse"));
|
||||
set_input_name(k_maintain_audio_pitch_input, tr("Maintain Audio Pitch"));
|
||||
set_input_name(k_loop_mode_input, tr("Loop"));
|
||||
set_combo_box_strings(k_loop_mode_input, { tr("None"), tr("Loop"), tr("Clamp") });
|
||||
}
|
||||
|
||||
void ClipBlock::add_cache_passthrough_from(ClipBlock *other)
|
||||
{
|
||||
if (auto tc = this->video_frame_cache()) {
|
||||
if (auto oc = other->video_frame_cache()) {
|
||||
tc->set_passthrough(oc);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto tc = this->audio_playback_cache()) {
|
||||
if (auto oc = other->audio_playback_cache()) {
|
||||
tc->set_passthrough(oc);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto tc = this->thumbnails()) {
|
||||
if (auto oc = other->thumbnails()) {
|
||||
tc->set_passthrough(oc);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto tc = this->waveform()) {
|
||||
if (auto oc = other->waveform()) {
|
||||
tc->set_passthrough(oc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClipBlock::ConnectedToPreviewEvent()
|
||||
{
|
||||
request_invalidated_from_connected();
|
||||
}
|
||||
|
||||
TimeRange ClipBlock::media_range() const
|
||||
{
|
||||
return input_time_adjustment(k_buffer_in, -1, TimeRange(0, length()), false);
|
||||
}
|
||||
|
||||
MultiCamNode *ClipBlock::find_multicam()
|
||||
{
|
||||
auto v = find_input_nodes_connected_to_input<MultiCamNode>(
|
||||
NodeInput(this, k_buffer_in), 1);
|
||||
if (v.empty()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return v.first();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 OAK_CLIPBLOCK_H
|
||||
#define OAK_CLIPBLOCK_H
|
||||
|
||||
#include "audio/audiovisualwaveform.h"
|
||||
#include "codec/decoder.h"
|
||||
#include "node/block/block.h"
|
||||
#include "node/input/multicam/multicamnode.h"
|
||||
#include "node/output/track/track.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ViewerOutput;
|
||||
|
||||
/**
|
||||
* @brief Node that represents a block of Media
|
||||
*/
|
||||
class ClipBlock : public Block {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ClipBlock();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(ClipBlock)
|
||||
|
||||
virtual QString name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QString description() const override;
|
||||
|
||||
virtual void set_length_and_media_out(const Rational &length) override;
|
||||
virtual void set_length_and_media_in(const Rational &length) override;
|
||||
|
||||
Track::Type get_track_type() const
|
||||
{
|
||||
if (track()) {
|
||||
return track()->type();
|
||||
} else {
|
||||
return Track::k_none;
|
||||
}
|
||||
}
|
||||
|
||||
virtual Node::ValueHint
|
||||
get_value_hint_for_input(const QString &input, int element = -1) const override;
|
||||
|
||||
Rational media_in() const;
|
||||
void set_media_in(const Rational &media_in);
|
||||
|
||||
bool is_autocaching() const
|
||||
{
|
||||
return get_standard_value(k_auto_cache_input).toBool();
|
||||
}
|
||||
void set_autocache(bool e);
|
||||
|
||||
void discard_cache();
|
||||
|
||||
virtual void invalidate_cache(const TimeRange &range, const QString &from,
|
||||
int element,
|
||||
InvalidateCacheOptions options) override;
|
||||
|
||||
virtual TimeRange input_time_adjustment(const QString &input, int element,
|
||||
const TimeRange &input_time,
|
||||
bool clamp) const override;
|
||||
|
||||
virtual TimeRange
|
||||
output_time_adjustment(const QString &input, int element,
|
||||
const TimeRange &input_time) const override;
|
||||
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
void
|
||||
request_invalidated_from_connected(bool force_all = false,
|
||||
const TimeRange &intersect = TimeRange());
|
||||
|
||||
double speed() const
|
||||
{
|
||||
return get_standard_value(k_speed_input).toDouble();
|
||||
}
|
||||
|
||||
bool reverse() const
|
||||
{
|
||||
return get_standard_value(k_reverse_input).toBool();
|
||||
}
|
||||
|
||||
void set_reverse(bool e)
|
||||
{
|
||||
set_standard_value(k_reverse_input, e);
|
||||
}
|
||||
|
||||
bool maintain_audio_pitch() const
|
||||
{
|
||||
return get_standard_value(k_maintain_audio_pitch_input).toBool();
|
||||
}
|
||||
|
||||
void set_maintain_audio_pitch(bool e)
|
||||
{
|
||||
set_standard_value(k_maintain_audio_pitch_input, e);
|
||||
}
|
||||
|
||||
TransitionBlock *in_transition()
|
||||
{
|
||||
return in_transition_;
|
||||
}
|
||||
|
||||
void set_in_transition(TransitionBlock *t)
|
||||
{
|
||||
in_transition_ = t;
|
||||
}
|
||||
|
||||
TransitionBlock *out_transition()
|
||||
{
|
||||
return out_transition_;
|
||||
}
|
||||
|
||||
void set_out_transition(TransitionBlock *t)
|
||||
{
|
||||
out_transition_ = t;
|
||||
}
|
||||
|
||||
const QVector<Block *> &block_links() const
|
||||
{
|
||||
return block_links_;
|
||||
}
|
||||
|
||||
FrameHashCache *connected_video_cache() const
|
||||
{
|
||||
if (Node *n = get_connected_output(k_buffer_in)) {
|
||||
return n->video_frame_cache();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AudioPlaybackCache *connected_audio_cache() const
|
||||
{
|
||||
if (Node *n = get_connected_output(k_buffer_in)) {
|
||||
return n->audio_playback_cache();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
FrameHashCache *thumbnails()
|
||||
{
|
||||
if (Node *n = get_connected_output(k_buffer_in)) {
|
||||
return n->thumbnail_cache();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AudioWaveformCache *waveform()
|
||||
{
|
||||
if (Node *n = get_connected_output(k_buffer_in)) {
|
||||
return n->waveform_cache();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void add_cache_passthrough_from(ClipBlock *other);
|
||||
|
||||
ViewerOutput *connected_viewer() const
|
||||
{
|
||||
return connected_viewer_;
|
||||
}
|
||||
|
||||
virtual TimeRange get_video_cache_range() const override
|
||||
{
|
||||
return TimeRange(0, length());
|
||||
}
|
||||
|
||||
virtual TimeRange get_audio_cache_range() const override
|
||||
{
|
||||
return TimeRange(0, length());
|
||||
}
|
||||
|
||||
virtual void ConnectedToPreviewEvent() override;
|
||||
|
||||
TimeRange media_range() const;
|
||||
|
||||
/**
|
||||
* @brief Get currently set loop mode
|
||||
*/
|
||||
LoopMode loop_mode() const
|
||||
{
|
||||
return static_cast<LoopMode>(get_standard_value(k_loop_mode_input).toInt());
|
||||
}
|
||||
|
||||
void set_loop_mode(LoopMode l)
|
||||
{
|
||||
set_standard_value(k_loop_mode_input, int(l));
|
||||
}
|
||||
|
||||
MultiCamNode *find_multicam();
|
||||
|
||||
static const QString k_buffer_in;
|
||||
static const QString k_media_in_input;
|
||||
static const QString k_speed_input;
|
||||
static const QString k_reverse_input;
|
||||
static const QString k_maintain_audio_pitch_input;
|
||||
static const QString k_loop_mode_input;
|
||||
|
||||
static const QString k_auto_cache_input;
|
||||
|
||||
protected:
|
||||
virtual void LinkChangeEvent() override;
|
||||
|
||||
virtual void InputConnectedEvent(const QString &input, int element,
|
||||
Node *output) override;
|
||||
|
||||
virtual void InputDisconnectedEvent(const QString &input, int element,
|
||||
Node *output) override;
|
||||
|
||||
virtual void InputValueChangedEvent(const QString &input,
|
||||
int element) override;
|
||||
|
||||
private:
|
||||
enum SequenceToMediaTimeFlag {
|
||||
k_stm_none = 0x0,
|
||||
k_stm_ignore_reverse = 0x1,
|
||||
k_stm_ignore_speed = 0x2,
|
||||
k_stm_ignore_loop = 0x4
|
||||
};
|
||||
|
||||
Rational sequence_to_media_time(const Rational &sequence_time,
|
||||
uint64_t flags = k_stm_none) const;
|
||||
|
||||
Rational media_to_sequence_time(const Rational &media_time) const;
|
||||
|
||||
void request_range_from_connected(const TimeRange &range);
|
||||
|
||||
void request_range_for_cache(PlaybackCache *cache, const TimeRange &max_range,
|
||||
const TimeRange &range, bool invalidate,
|
||||
bool request);
|
||||
void request_invalidated_for_cache(PlaybackCache *cache,
|
||||
const TimeRange &max_range);
|
||||
|
||||
bool get_adjusted_thumbnail_range(TimeRange *r) const;
|
||||
|
||||
QVector<Block *> block_links_;
|
||||
|
||||
TransitionBlock *in_transition_;
|
||||
TransitionBlock *out_transition_;
|
||||
|
||||
ViewerOutput *connected_viewer_;
|
||||
|
||||
private:
|
||||
Rational last_media_in_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TIMELINEBLOCK_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 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/block/gap/gap.h
|
||||
node/block/gap/gap.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,46 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "gap.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
GapBlock::GapBlock()
|
||||
{
|
||||
}
|
||||
|
||||
QString GapBlock::name() const
|
||||
{
|
||||
return tr("Gap");
|
||||
}
|
||||
|
||||
QString GapBlock::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.gap");
|
||||
}
|
||||
|
||||
QString GapBlock::description() const
|
||||
{
|
||||
return tr("A time-based node that represents an empty space.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 OAK_GAPBLOCK_H
|
||||
#define OAK_GAPBLOCK_H
|
||||
|
||||
#include "node/block/block.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Node that represents nothing in its respective track for a certain period of time
|
||||
*/
|
||||
class GapBlock : public Block {
|
||||
Q_OBJECT
|
||||
public:
|
||||
GapBlock();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(GapBlock)
|
||||
|
||||
virtual QString name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QString description() const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TIMELINEBLOCK_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 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/block/subtitle/subtitle.cpp
|
||||
node/block/subtitle/subtitle.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,74 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "subtitle.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super ClipBlock
|
||||
|
||||
const QString SubtitleBlock::k_text_in = QStringLiteral("text_in");
|
||||
|
||||
SubtitleBlock::SubtitleBlock()
|
||||
{
|
||||
add_input(k_text_in, NodeValue::k_text,
|
||||
InputFlags(k_input_flag_not_connectable | k_input_flag_not_keyframable));
|
||||
|
||||
set_input_flag(k_buffer_in, k_input_flag_hidden);
|
||||
set_input_flag(k_length_input, k_input_flag_hidden);
|
||||
set_input_flag(k_media_in_input, k_input_flag_hidden);
|
||||
set_input_flag(k_speed_input, k_input_flag_hidden);
|
||||
set_input_flag(k_reverse_input, k_input_flag_hidden);
|
||||
set_input_flag(k_maintain_audio_pitch_input, k_input_flag_hidden);
|
||||
|
||||
// Undo block flag that hides in param view
|
||||
set_flag(k_dont_show_in_param_view, false);
|
||||
}
|
||||
|
||||
QString SubtitleBlock::name() const
|
||||
{
|
||||
if (get_text().isEmpty()) {
|
||||
return tr("Subtitle");
|
||||
} else {
|
||||
return get_text();
|
||||
}
|
||||
}
|
||||
|
||||
QString SubtitleBlock::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.subtitle");
|
||||
}
|
||||
|
||||
QString SubtitleBlock::description() const
|
||||
{
|
||||
return tr(
|
||||
"A time-based node representing a single subtitle element for a certain period of time.");
|
||||
}
|
||||
|
||||
void SubtitleBlock::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_text_in, tr("Text"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 OAK_SUBTITLEBLOCK_H
|
||||
#define OAK_SUBTITLEBLOCK_H
|
||||
|
||||
#include "node/block/clip/clip.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class SubtitleBlock : public ClipBlock {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SubtitleBlock();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(SubtitleBlock)
|
||||
|
||||
virtual QString name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QString description() const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
static const QString k_text_in;
|
||||
|
||||
QString get_text() const
|
||||
{
|
||||
return get_standard_value(k_text_in).toString();
|
||||
}
|
||||
|
||||
void set_text(const QString &text)
|
||||
{
|
||||
set_standard_value(k_text_in, text);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_SUBTITLEBLOCK_H
|
||||
@@ -0,0 +1,25 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 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(crossdissolve)
|
||||
add_subdirectory(diptocolor)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
node/block/transition/transition.h
|
||||
node/block/transition/transition.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 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/block/transition/crossdissolve/crossdissolvetransition.h
|
||||
node/block/transition/crossdissolve/crossdissolvetransition.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,95 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "crossdissolvetransition.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
CrossDissolveTransition::CrossDissolveTransition()
|
||||
{
|
||||
}
|
||||
|
||||
QString CrossDissolveTransition::name() const
|
||||
{
|
||||
return tr("Cross Dissolve");
|
||||
}
|
||||
|
||||
QString CrossDissolveTransition::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.crossdissolve");
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> CrossDissolveTransition::category() const
|
||||
{
|
||||
return { k_category_transition };
|
||||
}
|
||||
|
||||
QString CrossDissolveTransition::description() const
|
||||
{
|
||||
return tr("Smoothly transition between two clips.");
|
||||
}
|
||||
|
||||
ShaderCode
|
||||
CrossDissolveTransition::get_shader_code(const ShaderRequest &request) const
|
||||
{
|
||||
Q_UNUSED(request)
|
||||
|
||||
return ShaderCode(
|
||||
FileFunctions::read_file_as_string(":/shaders/crossdissolve.frag"),
|
||||
QString());
|
||||
}
|
||||
|
||||
void CrossDissolveTransition::SampleJobEvent(const SampleBuffer &from_samples,
|
||||
const SampleBuffer &to_samples,
|
||||
SampleBuffer &out_samples,
|
||||
double time_in) const
|
||||
{
|
||||
for (size_t i = 0; i < out_samples.sample_count(); i++) {
|
||||
double this_sample_time =
|
||||
out_samples.audio_params().samples_to_time(i).to_double() + time_in;
|
||||
double progress = get_total_progress(this_sample_time);
|
||||
|
||||
for (int j = 0; j < out_samples.audio_params().channel_count(); j++) {
|
||||
out_samples.data(j)[i] = 0;
|
||||
|
||||
if (from_samples.is_allocated()) {
|
||||
if (i < from_samples.sample_count()) {
|
||||
out_samples.data(j)[i] += from_samples.data(j)[i] *
|
||||
transform_curve(1.0 - progress);
|
||||
}
|
||||
}
|
||||
|
||||
if (to_samples.is_allocated()) {
|
||||
// Offset input samples from the end
|
||||
size_t remain =
|
||||
(out_samples.sample_count() - to_samples.sample_count());
|
||||
if (i >= remain) {
|
||||
qint64 in_index = i - remain;
|
||||
out_samples.data(j)[i] +=
|
||||
to_samples.data(j)[in_index] * transform_curve(progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 OAK_CROSSDISSOLVETRANSITION_H
|
||||
#define OAK_CROSSDISSOLVETRANSITION_H
|
||||
|
||||
#include "node/block/transition/transition.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class CrossDissolveTransition : public TransitionBlock {
|
||||
Q_OBJECT
|
||||
public:
|
||||
CrossDissolveTransition();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(CrossDissolveTransition)
|
||||
|
||||
virtual QString name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QVector<CategoryID> category() const override;
|
||||
virtual QString description() const override;
|
||||
|
||||
//virtual void Retranslate() override;
|
||||
|
||||
virtual ShaderCode
|
||||
get_shader_code(const ShaderRequest &request) const override;
|
||||
|
||||
protected:
|
||||
virtual void SampleJobEvent(const SampleBuffer &from_samples,
|
||||
const SampleBuffer &to_samples,
|
||||
SampleBuffer &out_samples,
|
||||
double time_in) const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_CROSSDISSOLVETRANSITION_H
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2022 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/block/transition/diptocolor/diptocolortransition.h
|
||||
node/block/transition/diptocolor/diptocolortransition.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,80 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "diptocolortransition.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
const QString DipToColorTransition::k_color_input = QStringLiteral("color_in");
|
||||
|
||||
#define super TransitionBlock
|
||||
|
||||
DipToColorTransition::DipToColorTransition()
|
||||
{
|
||||
add_input(k_color_input, NodeValue::k_color,
|
||||
QVariant::fromValue(Color(0, 0, 0)));
|
||||
}
|
||||
|
||||
QString DipToColorTransition::name() const
|
||||
{
|
||||
return tr("Dip To Color");
|
||||
}
|
||||
|
||||
QString DipToColorTransition::id() const
|
||||
{
|
||||
return QStringLiteral("org.olivevideoeditor.Olive.diptocolor");
|
||||
}
|
||||
|
||||
QVector<Node::CategoryID> DipToColorTransition::category() const
|
||||
{
|
||||
return { k_category_transition };
|
||||
}
|
||||
|
||||
QString DipToColorTransition::description() const
|
||||
{
|
||||
return tr("Transition between clips by dipping to a color.");
|
||||
}
|
||||
|
||||
ShaderCode
|
||||
DipToColorTransition::get_shader_code(const ShaderRequest &request) const
|
||||
{
|
||||
Q_UNUSED(request)
|
||||
|
||||
return ShaderCode(
|
||||
FileFunctions::read_file_as_string(":/shaders/diptoblack.frag"),
|
||||
QString());
|
||||
}
|
||||
|
||||
void DipToColorTransition::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_color_input, tr("Color"));
|
||||
}
|
||||
|
||||
void DipToColorTransition::ShaderJobEvent(const NodeValueRow &value,
|
||||
ShaderJob *job) const
|
||||
{
|
||||
job->insert(k_color_input, value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 OAK_DIPTOCOLORTRANSITION_H
|
||||
#define OAK_DIPTOCOLORTRANSITION_H
|
||||
|
||||
#include "node/block/transition/transition.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class DipToColorTransition : public TransitionBlock {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DipToColorTransition();
|
||||
|
||||
NODE_DEFAULT_FUNCTIONS(DipToColorTransition)
|
||||
|
||||
virtual QString name() const override;
|
||||
virtual QString id() const override;
|
||||
virtual QVector<CategoryID> category() const override;
|
||||
virtual QString description() const override;
|
||||
|
||||
virtual ShaderCode
|
||||
get_shader_code(const ShaderRequest &request) const override;
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
static const QString k_color_input;
|
||||
|
||||
protected:
|
||||
virtual void ShaderJobEvent(const NodeValueRow &value,
|
||||
ShaderJob *job) const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_DIPTOCOLORTRANSITION_H
|
||||
@@ -0,0 +1,344 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 "transition.h"
|
||||
|
||||
#include "node/block/clip/clip.h"
|
||||
#include "node/output/track/track.h"
|
||||
#include "node/sliderdisplaytype.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
#define super Block
|
||||
|
||||
const QString TransitionBlock::k_out_block_input = QStringLiteral("out_block_in");
|
||||
const QString TransitionBlock::k_in_block_input = QStringLiteral("in_block_in");
|
||||
const QString TransitionBlock::k_curve_input = QStringLiteral("curve_in");
|
||||
const QString TransitionBlock::k_center_input = QStringLiteral("center_in");
|
||||
|
||||
TransitionBlock::TransitionBlock()
|
||||
: connected_out_block_(nullptr)
|
||||
, connected_in_block_(nullptr)
|
||||
{
|
||||
add_input(k_out_block_input, NodeValue::k_none,
|
||||
InputFlags(k_input_flag_not_keyframable));
|
||||
|
||||
add_input(k_in_block_input, NodeValue::k_none,
|
||||
InputFlags(k_input_flag_not_keyframable));
|
||||
|
||||
add_input(k_curve_input, NodeValue::k_combo,
|
||||
InputFlags(k_input_flag_not_keyframable | k_input_flag_not_connectable));
|
||||
|
||||
add_input(k_center_input, NodeValue::k_rational,
|
||||
InputFlags(k_input_flag_not_keyframable | k_input_flag_not_connectable));
|
||||
set_input_property(k_center_input, QStringLiteral("view"),
|
||||
slider::k_time);
|
||||
set_input_property(k_center_input, QStringLiteral("viewlock"), true);
|
||||
|
||||
set_flag(k_dont_show_in_param_view, false);
|
||||
}
|
||||
|
||||
void TransitionBlock::retranslate()
|
||||
{
|
||||
super::retranslate();
|
||||
|
||||
set_input_name(k_out_block_input, tr("From"));
|
||||
set_input_name(k_in_block_input, tr("To"));
|
||||
set_input_name(k_curve_input, tr("Curve"));
|
||||
set_input_name(k_center_input, tr("Center Offset"));
|
||||
|
||||
// These must correspond to the CurveType enum
|
||||
set_combo_box_strings(k_curve_input,
|
||||
{ tr("Linear"), tr("Exponential"), tr("Logarithmic") });
|
||||
}
|
||||
|
||||
Rational TransitionBlock::in_offset() const
|
||||
{
|
||||
if (is_dual_transition()) {
|
||||
return length() / 2 + offset_center();
|
||||
} else if (connected_in_block()) {
|
||||
return length();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Rational TransitionBlock::out_offset() const
|
||||
{
|
||||
if (is_dual_transition()) {
|
||||
return length() / 2 - offset_center();
|
||||
} else if (connected_out_block()) {
|
||||
return length();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Rational TransitionBlock::offset_center() const
|
||||
{
|
||||
return get_standard_value(k_center_input).value<Rational>();
|
||||
}
|
||||
|
||||
void TransitionBlock::set_offset_center(const Rational &r)
|
||||
{
|
||||
set_standard_value(k_center_input, QVariant::fromValue(r));
|
||||
}
|
||||
|
||||
void TransitionBlock::set_offsets_and_length(const Rational &in_offset,
|
||||
const Rational &out_offset)
|
||||
{
|
||||
Rational len = in_offset + out_offset;
|
||||
Rational center = len / 2 - in_offset;
|
||||
|
||||
set_length_and_media_out(len);
|
||||
set_offset_center(center);
|
||||
}
|
||||
|
||||
Block *TransitionBlock::connected_out_block() const
|
||||
{
|
||||
return connected_out_block_;
|
||||
}
|
||||
|
||||
Block *TransitionBlock::connected_in_block() const
|
||||
{
|
||||
return connected_in_block_;
|
||||
}
|
||||
|
||||
double TransitionBlock::get_total_progress(const double &time) const
|
||||
{
|
||||
return get_internal_transition_time(time) / length().to_double();
|
||||
}
|
||||
|
||||
double TransitionBlock::get_out_progress(const double &time) const
|
||||
{
|
||||
if (out_offset() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return std::clamp(
|
||||
1.0 - (get_internal_transition_time(time) / out_offset().to_double()), 0.0,
|
||||
1.0);
|
||||
}
|
||||
|
||||
double TransitionBlock::get_in_progress(const double &time) const
|
||||
{
|
||||
if (in_offset() == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return std::clamp(
|
||||
(get_internal_transition_time(time) - out_offset().to_double()) /
|
||||
in_offset().to_double(),
|
||||
0.0, 1.0);
|
||||
}
|
||||
|
||||
double TransitionBlock::get_internal_transition_time(const double &time) const
|
||||
{
|
||||
return time;
|
||||
}
|
||||
|
||||
void TransitionBlock::insert_transition_times(AcceleratedJob *job,
|
||||
const double &time) const
|
||||
{
|
||||
// Provides total transition progress from 0.0 (start) - 1.0 (end)
|
||||
job->insert(QStringLiteral("ove_tprog_all"),
|
||||
NodeValue(NodeValue::k_float, get_total_progress(time), this));
|
||||
|
||||
// Provides progress of out section from 1.0 (start) - 0.0 (end)
|
||||
job->insert(QStringLiteral("ove_tprog_out"),
|
||||
NodeValue(NodeValue::k_float, get_out_progress(time), this));
|
||||
|
||||
// Provides progress of in section from 0.0 (start) - 1.0 (end)
|
||||
job->insert(QStringLiteral("ove_tprog_in"),
|
||||
NodeValue(NodeValue::k_float, get_in_progress(time), this));
|
||||
}
|
||||
|
||||
void TransitionBlock::value(const NodeValueRow &value,
|
||||
const NodeGlobals &globals,
|
||||
NodeValueTable *table) const
|
||||
{
|
||||
NodeValue out_buffer = value[k_out_block_input];
|
||||
NodeValue in_buffer = value[k_in_block_input];
|
||||
NodeValue::Type data_type = (out_buffer.type() != NodeValue::k_none) ?
|
||||
out_buffer.type() :
|
||||
in_buffer.type();
|
||||
|
||||
NodeValue::Type job_type = NodeValue::k_none;
|
||||
QVariant push_job;
|
||||
|
||||
if (data_type == NodeValue::k_texture) {
|
||||
// This must be a visual transition
|
||||
ShaderJob job;
|
||||
|
||||
if (out_buffer.type() != NodeValue::k_none) {
|
||||
job.insert(k_out_block_input, out_buffer);
|
||||
} else {
|
||||
job.insert(k_out_block_input, NodeValue(NodeValue::k_texture, nullptr));
|
||||
}
|
||||
|
||||
if (in_buffer.type() != NodeValue::k_none) {
|
||||
job.insert(k_in_block_input, in_buffer);
|
||||
} else {
|
||||
job.insert(k_in_block_input, NodeValue(NodeValue::k_texture, nullptr));
|
||||
}
|
||||
|
||||
job.insert(k_curve_input, value);
|
||||
|
||||
double time = globals.time().in().to_double();
|
||||
insert_transition_times(&job, time);
|
||||
|
||||
ShaderJobEvent(value, &job);
|
||||
|
||||
job_type = NodeValue::k_texture;
|
||||
push_job = QVariant::fromValue(Texture::job(globals.vparams(), job));
|
||||
} else if (data_type == NodeValue::k_samples) {
|
||||
// This must be an audio transition
|
||||
SampleBuffer from_samples = out_buffer.to_samples();
|
||||
SampleBuffer to_samples = in_buffer.to_samples();
|
||||
|
||||
if (from_samples.is_allocated() || to_samples.is_allocated()) {
|
||||
double time_in = globals.time().in().to_double();
|
||||
double time_out = globals.time().out().to_double();
|
||||
|
||||
const AudioParams ¶ms = (from_samples.is_allocated()) ?
|
||||
from_samples.audio_params() :
|
||||
to_samples.audio_params();
|
||||
|
||||
SampleBuffer out_samples;
|
||||
|
||||
if (params.is_valid()) {
|
||||
int nb_samples = params.time_to_samples(time_out - time_in);
|
||||
|
||||
out_samples = SampleBuffer(params, nb_samples);
|
||||
SampleJobEvent(from_samples, to_samples, out_samples, time_in);
|
||||
}
|
||||
|
||||
job_type = NodeValue::k_samples;
|
||||
push_job = QVariant::fromValue(out_samples);
|
||||
}
|
||||
}
|
||||
|
||||
if (!push_job.isNull()) {
|
||||
table->push(job_type, push_job, this);
|
||||
}
|
||||
}
|
||||
|
||||
void TransitionBlock::invalidate_cache(const TimeRange &range,
|
||||
const QString &from, int element,
|
||||
InvalidateCacheOptions options)
|
||||
{
|
||||
TimeRange r = range;
|
||||
|
||||
if (from == k_out_block_input || from == k_in_block_input) {
|
||||
Block *n = dynamic_cast<Block *>(get_connected_output(from));
|
||||
if (n) {
|
||||
r = Track::transform_range_from_block(n, r);
|
||||
}
|
||||
}
|
||||
|
||||
super::invalidate_cache(r, from, element, options);
|
||||
}
|
||||
|
||||
double TransitionBlock::transform_curve(double linear) const
|
||||
{
|
||||
switch (static_cast<CurveType>(get_standard_value(k_curve_input).toInt())) {
|
||||
case k_linear:
|
||||
break;
|
||||
case k_exponential:
|
||||
linear *= linear;
|
||||
break;
|
||||
case k_logarithmic:
|
||||
linear = std::sqrt(linear);
|
||||
break;
|
||||
}
|
||||
|
||||
return linear;
|
||||
}
|
||||
|
||||
void TransitionBlock::InputConnectedEvent(const QString &input, int element,
|
||||
Node *output)
|
||||
{
|
||||
Q_UNUSED(element)
|
||||
|
||||
if (input == k_out_block_input) {
|
||||
// If node is not a block, this will just be null
|
||||
if ((connected_out_block_ = dynamic_cast<ClipBlock *>(output))) {
|
||||
connected_out_block_->set_out_transition(this);
|
||||
}
|
||||
} else if (input == k_in_block_input) {
|
||||
// If node is not a block, this will just be null
|
||||
if ((connected_in_block_ = dynamic_cast<ClipBlock *>(output))) {
|
||||
connected_in_block_->set_in_transition(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TransitionBlock::InputDisconnectedEvent(const QString &input, int element,
|
||||
Node *output)
|
||||
{
|
||||
Q_UNUSED(element)
|
||||
Q_UNUSED(output)
|
||||
|
||||
if (input == k_out_block_input) {
|
||||
if (connected_out_block_) {
|
||||
connected_out_block_->set_out_transition(nullptr);
|
||||
connected_out_block_ = nullptr;
|
||||
}
|
||||
} else if (input == k_in_block_input) {
|
||||
if (connected_in_block_) {
|
||||
connected_in_block_->set_in_transition(nullptr);
|
||||
connected_in_block_ = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimeRange TransitionBlock::input_time_adjustment(const QString &input,
|
||||
int element,
|
||||
const TimeRange &input_time,
|
||||
bool clamp) const
|
||||
{
|
||||
if (input == k_in_block_input || input == k_out_block_input) {
|
||||
Block *block = dynamic_cast<Block *>(get_connected_output(input));
|
||||
if (block) {
|
||||
// Retransform time as if it came from the track
|
||||
return input_time + in() - block->in();
|
||||
}
|
||||
}
|
||||
|
||||
return super::input_time_adjustment(input, element, input_time, clamp);
|
||||
}
|
||||
|
||||
TimeRange
|
||||
TransitionBlock::output_time_adjustment(const QString &input, int element,
|
||||
const TimeRange &input_time) const
|
||||
{
|
||||
if (input == k_in_block_input || input == k_out_block_input) {
|
||||
Block *block = dynamic_cast<Block *>(get_connected_output(input));
|
||||
if (block) {
|
||||
return input_time + block->in() - in();
|
||||
}
|
||||
}
|
||||
|
||||
return super::output_time_adjustment(input, element, input_time);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2022 Olive Team
|
||||
Modifications Copyright (C) 2025 mikesolar
|
||||
|
||||
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 OAK_TRANSITIONBLOCK_H
|
||||
#define OAK_TRANSITIONBLOCK_H
|
||||
|
||||
#include "node/block/block.h"
|
||||
|
||||
namespace olive
|
||||
{
|
||||
|
||||
class ClipBlock;
|
||||
|
||||
class TransitionBlock : public Block {
|
||||
Q_OBJECT
|
||||
public:
|
||||
TransitionBlock();
|
||||
|
||||
virtual void retranslate() override;
|
||||
|
||||
Rational in_offset() const;
|
||||
Rational out_offset() const;
|
||||
|
||||
/**
|
||||
* @brief Return the "middle point" of the transition, relative to the transition
|
||||
*
|
||||
* Used to calculate in/out offsets.
|
||||
*
|
||||
* 0 means the center of the transition is right in the middle and the in and out offsets will
|
||||
* be equal.
|
||||
*/
|
||||
Rational offset_center() const;
|
||||
void set_offset_center(const Rational &r);
|
||||
|
||||
void set_offsets_and_length(const Rational &in_offset,
|
||||
const Rational &out_offset);
|
||||
|
||||
bool is_dual_transition() const
|
||||
{
|
||||
return connected_out_block() && connected_in_block();
|
||||
}
|
||||
|
||||
Block *connected_out_block() const;
|
||||
Block *connected_in_block() const;
|
||||
|
||||
double get_total_progress(const double &time) const;
|
||||
double get_out_progress(const double &time) const;
|
||||
double get_in_progress(const double &time) const;
|
||||
|
||||
virtual void value(const NodeValueRow &value, const NodeGlobals &globals,
|
||||
NodeValueTable *table) const override;
|
||||
|
||||
virtual void invalidate_cache(
|
||||
const TimeRange &range, const QString &from, int element = -1,
|
||||
InvalidateCacheOptions options = InvalidateCacheOptions()) override;
|
||||
|
||||
static const QString k_out_block_input;
|
||||
static const QString k_in_block_input;
|
||||
static const QString k_curve_input;
|
||||
static const QString k_center_input;
|
||||
|
||||
protected:
|
||||
virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob *job) const
|
||||
{
|
||||
}
|
||||
|
||||
virtual void SampleJobEvent(const SampleBuffer &from_samples,
|
||||
const SampleBuffer &to_samples,
|
||||
SampleBuffer &out_samples, double time_in) const
|
||||
{
|
||||
}
|
||||
|
||||
double transform_curve(double linear) const;
|
||||
|
||||
virtual void InputConnectedEvent(const QString &input, int element,
|
||||
Node *output) override;
|
||||
|
||||
virtual void InputDisconnectedEvent(const QString &input, int element,
|
||||
Node *output) override;
|
||||
|
||||
virtual TimeRange input_time_adjustment(const QString &input, int element,
|
||||
const TimeRange &input_time,
|
||||
bool clamp) const override;
|
||||
|
||||
virtual TimeRange
|
||||
output_time_adjustment(const QString &input, int element,
|
||||
const TimeRange &input_time) const override;
|
||||
|
||||
private:
|
||||
enum CurveType { k_linear, k_exponential, k_logarithmic };
|
||||
|
||||
double get_internal_transition_time(const double &time) const;
|
||||
|
||||
void insert_transition_times(AcceleratedJob *job, const double &time) const;
|
||||
|
||||
ClipBlock *connected_out_block_;
|
||||
|
||||
ClipBlock *connected_in_block_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // OAK_TRANSITIONBLOCK_H
|
||||
Reference in New Issue
Block a user