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:
2026-07-20 03:23:28 +08:00
parent 026ff94b5e
commit 28c4426236
604 changed files with 243 additions and 172 deletions
+27
View File
@@ -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
)
+152
View File
@@ -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);
}
}
}
+146
View File
@@ -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
+22
View File
@@ -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
)
+662
View File
@@ -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();
}
}
}
+276
View File
@@ -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
+22
View File
@@ -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
)
+46
View File
@@ -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.");
}
}
+47
View File
@@ -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
+22
View File
@@ -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
)
+74
View File
@@ -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"));
}
}
+58
View File
@@ -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
+344
View File
@@ -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 &params = (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);
}
}
+121
View File
@@ -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