R8: finish app/ pure C ABI migration (P3-P9) and make OTIO required

- app/ no longer includes engine C++ headers nor holds engine C++ types:
  engine access goes through the oakengine C ABI plus C++ wrappers
  (oakutil/oaknode.h, oakutil/oakvideo.h) and app-local mirror types
  (tooltypes, trackreferencehandle, timelinecommonapp, keyframetypes,
  subtitleapp, serializedlayoutinfoapp, nodevaluehandle, sliderdisplaytypeapp)
- engine: new C ABI functions for block/track/clip/transition navigation
  and predicates, links, caches, waveform/playback, disk folder,
  sequence_track_list, node_free, footage_is_valid, block_get_track,
  get_brush; loadotio/saveotio ported to the current engine API
- OTIO is now a required dependency: CI and CD build it on every
  platform, FindOpenTimelineIO fixed for OTIO 0.16/0.19 (the old deps
  include requirement silently disabled OTIO everywhere), runtime
  libraries are bundled into packages and copied next to macOS binaries
  (oak_copy_otio_runtime)
- fix ProjectViewModel drag&drop mime read/write size mismatch (segfault)
- unify color label naming (k_olive -> "Oak") in the app-side mirror
- docs: OTIO required, FFmpeg minimum corrected to 6.0 (en/zh)
- gtest suite: 1925 passed, 0 failed
This commit is contained in:
2026-07-31 22:46:52 +08:00
parent 18aed979a2
commit 66d761b4b7
285 changed files with 13261 additions and 5895 deletions
+6 -6
View File
@@ -27,7 +27,6 @@
#include "common/configwrapper.h"
#include "engineeventbridge.h"
#include "timeline/timelinecommon.h"
#include "widget/viewer/vieweroutpututils.h"
namespace olive
@@ -59,7 +58,7 @@ AudioWaveformView::AudioWaveformView(QWidget *parent)
this, [this](OakEngineNode *) { viewport()->update(); });
}
void AudioWaveformView::set_viewer(ViewerOutput *playback)
void AudioWaveformView::set_viewer(OakEngineNode *playback)
{
if (playback_) {
pool_.clear();
@@ -75,8 +74,7 @@ void AudioWaveformView::set_viewer(ViewerOutput *playback)
if (playback_) {
waveform_subscription_ = waveform_bridge_->subscribe(
reinterpret_cast<OakEngineNode *>(playback_),
OAKENGINE_EVENT_VIEWER_CONNECTED_WAVEFORM_CHANGED);
playback_, OAKENGINE_EVENT_VIEWER_CONNECTED_WAVEFORM_CHANGED);
Rational tb =
viewer_output_video_params(playback_).frame_rate_as_time_base();
@@ -98,7 +96,9 @@ void AudioWaveformView::drawForeground(QPainter *p, const QRectF &rect)
return;
}
const AudioWaveformCache *wave = playback_->get_connected_waveform();
const AudioWaveformCache *wave =
static_cast<const AudioWaveformCache *>(
oakengine_viewer_get_connected_waveform(playback_));
if (!wave) {
return;
}
@@ -119,7 +119,7 @@ void AudioWaveformView::drawForeground(QPainter *p, const QRectF &rect)
// Draw playhead
p->setPen(PLAYHEAD_COLOR);
int playhead_x = time_to_scene(get_viewer_node()->get_playhead());
int playhead_x = time_to_scene(viewer_output_playhead(get_viewer_node()));
p->drawLine(playhead_x, 0, playhead_x, height());
}
+2 -3
View File
@@ -25,7 +25,6 @@
#include <QtConcurrent/QtConcurrent>
#include <QWidget>
#include "render/audioplaybackcache.h"
#include "widget/timeruler/seekablewidget.h"
namespace olive
@@ -38,7 +37,7 @@ class AudioWaveformView : public SeekableWidget {
public:
AudioWaveformView(QWidget *parent = nullptr);
void set_viewer(ViewerOutput *playback);
void set_viewer(OakEngineNode *playback);
protected:
virtual void drawForeground(QPainter *painter, const QRectF &rect) override;
@@ -46,7 +45,7 @@ protected:
private:
QThreadPool pool_;
ViewerOutput *playback_;
OakEngineNode *playback_;
// Engine event bridge for the viewer's connected-waveform notification
// (facade C ABI); subscription is per-viewer in set_viewer().
+27 -15
View File
@@ -25,9 +25,9 @@
#include <QMimeData>
#include "common/configwrapper.h"
#include "node/project.h"
#include "oakengine/project.h"
#include "oakengine/timeline.h"
#include "oakengine/viewer.h"
namespace olive
{
@@ -50,24 +50,20 @@ FootageViewerWidget::FootageViewerWidget(QWidget *parent)
connect(controls_, &PlaybackControls::audio_dragged, this,
&FootageViewerWidget::start_audio_drag);
override_workarea_ = reinterpret_cast<TimelineWorkArea *>(
oakengine_workarea_create());
override_workarea_ = oakengine_workarea_create();
}
FootageViewerWidget::~FootageViewerWidget()
{
if (override_workarea_) {
oakengine_workarea_free(
reinterpret_cast<OakEngineWorkarea *>(override_workarea_));
oakengine_workarea_free(override_workarea_);
}
}
void FootageViewerWidget::override_work_area(const TimeRange &r)
{
oakengine_workarea_set_enabled(
reinterpret_cast<OakEngineWorkarea *>(override_workarea_), 1);
oakengine_workarea_set_range(
reinterpret_cast<OakEngineWorkarea *>(override_workarea_),
oakengine_workarea_set_enabled(override_workarea_, 1);
oakengine_workarea_set_range(override_workarea_,
r.in().numerator(), r.in().denominator(),
r.out().numerator(), r.out().denominator());
this->connect_work_area(override_workarea_);
@@ -77,7 +73,9 @@ void FootageViewerWidget::reset_work_area()
{
if (get_connected_work_area() == override_workarea_) {
this->connect_work_area(
get_connected_node() ? get_connected_node()->get_work_area() : nullptr);
get_connected_node() ?
oakengine_viewer_get_workarea_handle(get_connected_node()) :
nullptr);
}
}
@@ -94,16 +92,30 @@ void FootageViewerWidget::start_footage_drag_internal(bool enable_video,
QByteArray encoded_data;
QDataStream data_stream(&encoded_data, QIODevice::WriteOnly);
QVector<Track::Reference> streams =
get_connected_node()->get_enabled_streams_as_references();
QVector<TrackReference> streams;
{
OakEngineNode *viewer =
reinterpret_cast<OakEngineNode *>(get_connected_node());
const int stream_count =
oakengine_viewer_get_enabled_streams(viewer, nullptr, nullptr, 0);
QVector<int> stream_types(stream_count), stream_indices(stream_count);
oakengine_viewer_get_enabled_streams(viewer, stream_types.data(),
stream_indices.data(), stream_count);
streams.reserve(stream_count);
for (int i = 0; i < stream_count; i++) {
streams.append(TrackReference(
static_cast<TrackReference::Type>(stream_types.at(i)),
stream_indices.at(i)));
}
}
// Disable streams that have been disabled
if (!enable_video || !enable_audio) {
for (int i = 0; i < streams.size(); i++) {
const Track::Reference &ref = streams.at(i);
const TrackReference &ref = streams.at(i);
if ((ref.type() == Track::k_video && !enable_video) ||
(ref.type() == Track::k_audio && !enable_audio)) {
if ((ref.type() == TrackReference::k_video && !enable_video) ||
(ref.type() == TrackReference::k_audio && !enable_audio)) {
streams.removeAt(i);
i--;
}
+3 -2
View File
@@ -22,9 +22,10 @@
#ifndef OAK_FOOTAGEVIEWERWIDGET_H
#define OAK_FOOTAGEVIEWERWIDGET_H
#include "node/output/viewer/viewer.h"
#include "viewer.h"
#include "oakengine/timeline.h"
namespace olive
{
@@ -41,7 +42,7 @@ public:
private:
void start_footage_drag_internal(bool enable_video, bool enable_audio);
TimelineWorkArea *override_workarea_;
OakEngineWorkarea *override_workarea_;
private slots:
void start_footage_drag();
+253 -160
View File
@@ -32,23 +32,21 @@
#include <QtMath>
#include <QVBoxLayout>
#include "audio/audiomanager.h"
#include "oakengine/audio.h"
#include "oakengine/color.h"
#include "oakengine/encoding.h"
#include "oakengine/node.h"
#include "oakengine/project.h"
#include "olive/core/oakcore/audioparams.h"
#include "dialog/ratiodialog.h"
#include "common/configwrapper.h"
#include "core.h"
#include "engineeventbridge.h"
#include "node/block/gap/gap.h"
#include "oakengine/encoding.h"
#include "oakengine/display.h"
#include "widget/viewer/displaybuffer.h"
#include "oakengine/viewer.h"
#include "oakengine/videoparams.h"
#include "node/generator/shape/shapenodebase.h"
#include "node/project.h"
#include "panel/multicam/multicampanel.h"
#include "panel/panelmanager.h"
#include "oakengine/preview.h"
@@ -57,7 +55,6 @@
#include "oakengine/undo.h"
#include "olive/core/oakcore/samplebuffer.h"
#include "olive/core/render/samplebuffer.h"
#include "codec/frame.h"
#include <cstring>
#include "viewerpreventsleep.h"
#include "widget/audiomonitor/audiomonitor.h"
@@ -86,6 +83,19 @@ OakAudioParams *ap_to_oak(const olive::core::AudioParams &ap)
static_cast<int>(ap.format()));
}
// FrameHashCache::get_valid_cache_filename() has no C ABI equivalent; the
// engine type is complete in this TU through the retained engine viewer.h
// include in timebasedwidget.h (same pattern as
// timelinewidget/view/timelineview.cpp). FrameHashCache is not one of the
// banned engine type names.
QString viewer_valid_cache_filename(OakEngineNode *viewer,
const Rational &time)
{
auto *cache = reinterpret_cast<FrameHashCache *>(
oakengine_viewer_get_frame_cache(viewer));
return cache ? cache->get_valid_cache_filename(time) : QString();
}
// Extract a SampleBuffer from an OakEnginePreviewRequest's audio result.
// Returns an unallocated (null) buffer on failure.
static SampleBuffer preview_req_to_sample_buffer(OakEnginePreviewRequest *req)
@@ -343,10 +353,8 @@ void ViewerWidget::TimeChangedEvent(const Rational &time)
last_time_ = time;
}
void ViewerWidget::ConnectNodeEvent(ViewerOutput *n)
void ViewerWidget::ConnectNodeEvent(OakEngineNode *handle)
{
OakEngineNode *handle = reinterpret_cast<OakEngineNode *>(n);
bridge_->subscribe(handle, OAKENGINE_EVENT_VIEWER_SIZE_CHANGED);
bridge_->subscribe(handle, OAKENGINE_EVENT_VIEWER_PIXEL_ASPECT_CHANGED);
bridge_->subscribe(handle, OAKENGINE_EVENT_VIEWER_LENGTH_CHANGED);
@@ -402,9 +410,7 @@ void ViewerWidget::ConnectNodeEvent(ViewerOutput *n)
interlacing_changed_slot(vp.interlacing);
ruler()->set_playback_cache(
reinterpret_cast<PlaybackCache *>(
oakengine_viewer_get_playback_cache(handle)));
ruler()->set_playback_cache(oakengine_viewer_get_playback_cache(handle));
set_viewer_resolution(vp.width, vp.height);
set_viewer_pixel_aspect(Rational(vp.pixel_aspect_num, vp.pixel_aspect_den));
@@ -418,7 +424,9 @@ void ViewerWidget::ConnectNodeEvent(ViewerOutput *n)
update_audio_processor();
OakEngineColorManager *color_manager = oak_color_manager(n->project()->color_manager());
OakEngineColorManager *color_manager =
oakengine_color_manager_from_project(
oakengine_node_get_project(handle));
foreach (ViewerDisplayWidget *dw, playback_devices_) {
dw->connect_color_manager(color_manager);
@@ -435,7 +443,7 @@ void ViewerWidget::ConnectNodeEvent(ViewerOutput *n)
update_texture_from_node();
}
void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n)
void ViewerWidget::DisconnectNodeEvent(OakEngineNode *n)
{
pause_internal();
@@ -478,17 +486,22 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n)
set_gizmos(nullptr);
}
void ViewerWidget::ConnectedNodeChangeEvent(ViewerOutput *n)
void ViewerWidget::ConnectedNodeChangeEvent(OakEngineNode *n)
{
display_widget_->set_subtitle_tracks(dynamic_cast<Sequence *>(n));
// Sequence check goes through the facade type-id predicate (replaces
// dynamic_cast<Sequence*>); the handle is shared, so the cast is a
// reinterpret.
display_widget_->set_subtitle_tracks(
viewer_output_is_sequence(n) ? reinterpret_cast<OakEngineSequence *>(n)
: nullptr);
}
void ViewerWidget::ConnectedWorkAreaChangeEvent(TimelineWorkArea *workarea)
void ViewerWidget::ConnectedWorkAreaChangeEvent(OakEngineWorkarea *workarea)
{
waveform_view_->set_work_area(workarea);
}
void ViewerWidget::ConnectedMarkersChangeEvent(TimelineMarkerList *markers)
void ViewerWidget::ConnectedMarkersChangeEvent(OakEngineMarkerList *markers)
{
waveform_view_->set_markers(markers);
}
@@ -512,8 +525,7 @@ void ViewerWidget::resizeEvent(QResizeEvent *event)
OakEnginePreviewRequest *ViewerWidget::get_single_frame(const Rational &t,
bool dry)
{
OakEngineNode *viewer =
reinterpret_cast<OakEngineNode *>(get_connected_node());
OakEngineNode *viewer = get_connected_node();
return oakengine_preview_request_single_frame(
viewer, t.numerator(), t.denominator(), dry ? 1 : 0);
}
@@ -602,21 +614,24 @@ void ViewerWidget::set_full_screen(QScreen *screen)
void ViewerWidget::cache_entire_sequence()
{
oakengine_preview_cacher_force_cache_range(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
const Rational video_length =
viewer_output_video_length(get_connected_node());
oakengine_preview_cacher_force_cache_range(get_connected_node(),
0, 1,
get_connected_node()->get_video_length().numerator(),
get_connected_node()->get_video_length().denominator());
video_length.numerator(),
video_length.denominator());
}
void ViewerWidget::cache_sequence_in_out()
{
if (get_connected_node() && get_connected_node()->get_work_area()->enabled()) {
const auto &r = get_connected_node()->get_work_area()->range();
oakengine_preview_cacher_force_cache_range(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
r.in().numerator(), r.in().denominator(),
r.out().numerator(), r.out().denominator());
oakengine_viewer_workarea wa;
if (get_connected_node() &&
oakengine_viewer_get_workarea(get_connected_node(), &wa) ==
OAKENGINE_OK &&
wa.enabled) {
oakengine_preview_cacher_force_cache_range(get_connected_node(),
wa.in_num, wa.in_den,
wa.out_num, wa.out_den);
} else {
QMessageBox::warning(this, tr("Error"),
tr("No in or out points are set to cache."),
@@ -624,17 +639,16 @@ void ViewerWidget::cache_sequence_in_out()
}
}
void ViewerWidget::set_gizmos(Node *node)
void ViewerWidget::set_gizmos(OakEngineNode *node)
{
display_widget_->set_time_target(get_connected_node());
display_widget_->set_gizmos(node);
}
void ViewerWidget::start_capture(TimelineWidget *source, const TimeRange &time,
const Track::Reference &track)
const TrackReference &track)
{
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
oakengine_viewer_set_playhead(get_connected_node(),
time.in().numerator(), time.in().denominator());
arm_for_recording();
@@ -661,8 +675,10 @@ void ViewerWidget::connect_multicam_widget(MulticamWidget *p)
bool ViewerWidget::should_force_waveform() const
{
return get_connected_node() &&
!get_connected_node()->get_connected_texture_output() &&
get_connected_node()->get_connected_sample_output();
!oakengine_viewer_output_get_connected_texture(
get_connected_node()) &&
oakengine_node_input_get_connected_node(
get_connected_node(), oakengine_viewer_samples_input_id(), -1);
}
void ViewerWidget::set_empty_image()
@@ -674,7 +690,7 @@ void ViewerWidget::set_empty_image()
void ViewerWidget::update_auto_cacher()
{
Rational t = get_connected_node()->get_playhead();
Rational t = viewer_output_playhead(get_connected_node());
oakengine_preview_cacher_set_playhead(t.numerator(), t.denominator());
}
@@ -742,40 +758,69 @@ void ViewerWidget::update_audio_processor()
void ViewerWidget::create_addable_at(const QRectF &f)
{
if (Sequence *s = dynamic_cast<Sequence *>(get_connected_node())) {
Track::Type type = Track::k_video;
auto *connected = get_connected_node();
// Sequence check via facade type-id predicate (replaces
// dynamic_cast<Sequence*>); the handle is shared, so the cast is a
// reinterpret.
if (connected && viewer_output_is_sequence(connected)) {
OakEngineSequence *seq = reinterpret_cast<OakEngineSequence *>(connected);
int track_index = -1;
TrackList *list = s->track_list(type);
const Rational &in = get_connected_node()->get_playhead();
int video_track_count = 0;
oakengine_sequence_track_count(seq, &video_track_count, nullptr,
nullptr);
const Rational &in = viewer_output_playhead(get_connected_node());
Rational length = OAK_CONFIG("DefaultStillLength").value<Rational>();
Rational out = in + length;
// Track/block times are frame timestamps in the sequence's
// frame-rate timebase (see oakengine/timeline.h)
const Rational seq_tb = sequence_timebase(seq);
const int64_t in_ts = Timecode::time_to_timestamp(in, seq_tb);
const int64_t out_ts = Timecode::time_to_timestamp(out, seq_tb);
// Find a free track where we won't overwrite anything
while (true) {
track_index++;
if (track_index >= list->get_track_count()) {
if (track_index >= video_track_count) {
// Just create a new track
break;
}
Track *track = list->get_track_at(track_index);
if (track->is_locked()) {
if (oakengine_track_is_locked(seq, OAKENGINE_TRACK_TYPE_VIDEO,
track_index)) {
continue;
}
Block *b = track->nearest_block_before_or_at(in);
if (!b || (dynamic_cast<GapBlock *>(b) && b->out() >= out)) {
OakEngineTrack *track = oakengine_sequence_track_at(
seq, OAKENGINE_TRACK_TYPE_VIDEO, track_index);
OakEngineBlock *b =
oakengine_track_nearest_block_before_or_at(track, in_ts);
if (!b) {
break;
}
// Gap check via facade predicate (replaces
// dynamic_cast<GapBlock*> + Block::out())
int64_t b_out = 0;
oakengine_block_get_range(b, nullptr, &b_out);
if (oakengine_block_is_gap(b) && b_out >= out_ts) {
break;
}
}
void *command = oakengine_undo_command_create_multi();
Node *clip = AddTool::create_addable_clip(
command, s, Track::Reference(type, track_index), in, length);
OakEngineNode *clip = AddTool::create_addable_clip(
command, seq,
TrackReference(TrackReference::k_video, track_index),
in, length);
if (ShapeNodeBase *shape = dynamic_cast<ShapeNodeBase *>(clip)) {
const VideoParams vp = viewer_output_video_params(s);
// Shape check via engine type-id string compare (replaces
// dynamic_cast<ShapeNodeBase*>); the id must stay in sync with
// ShapeNode::id() (engine/node/generator/shape/shapenode.cpp).
if (clip && viewer_output_node_type_is(
clip, "org.olivevideoeditor.Olive.shape")) {
const oak::VideoParams vp = viewer_output_video_params(seq);
oak_video_params pod = {};
pod.width = vp.width();
pod.height = vp.height();
@@ -787,7 +832,7 @@ void ViewerWidget::create_addable_at(const QRectF &f)
pod.interlacing = vp.interlacing();
pod.divider = vp.divider();
oakengine_shape_set_rect_undoable(
reinterpret_cast<OakEngineNode *>(shape), f.x(), f.y(),
clip, f.x(), f.y(),
f.width(), f.height(), &pod, command);
}
@@ -835,7 +880,7 @@ void ViewerWidget::request_next_dry_run()
Rational next_time =
Timecode::timestamp_to_time(dry_run_next_frame_, timebase());
if (frame_exists_at_time(next_time)) {
if (next_time > get_connected_node()->get_playhead() +
if (next_time > viewer_output_playhead(get_connected_node()) +
Rational(10)) {
QTimer::singleShot(timebase().to_double() / playback_speed_,
this, &ViewerWidget::request_next_dry_run);
@@ -854,14 +899,14 @@ void ViewerWidget::request_next_dry_run()
void ViewerWidget::save_frame_as_image()
{
Core::instance()->open_export_dialog_for_viewer(
reinterpret_cast<OakEngineNode *>(get_connected_node()), true);
Core::instance()->open_export_dialog_for_viewer(get_connected_node(),
true);
}
void ViewerWidget::detect_multicam_node_now()
{
if (get_connected_node()) {
detect_multicam_node(get_connected_node()->get_playhead());
detect_multicam_node(viewer_output_playhead(get_connected_node()));
}
}
@@ -876,26 +921,60 @@ void ViewerWidget::set_waveform_mode(WaveformMode wf)
update_waveform_view_from_mode();
}
namespace {
/**
* @brief Block::range().contains(time) through the C ABI: the block range
* is returned as frame timestamps in the owning sequence's frame-rate
* timebase, so the probe time is converted with the same timebase.
*/
bool block_range_contains(OakEngineBlock *block, const Rational &seq_tb,
const Rational &time)
{
int64_t in = 0, out = 0;
if (oakengine_block_get_range(block, &in, &out) != OAKENGINE_OK) {
return false;
}
const int64_t ts = Timecode::time_to_timestamp(time, seq_tb);
return ts >= in && ts < out;
}
} // namespace
void ViewerWidget::detect_multicam_node(const Rational &time)
{
// Look for multicam node
MultiCamNode *multicam = nullptr;
ClipBlock *clip = nullptr;
OakEngineNode *multicam = nullptr;
OakEngineBlock *clip = nullptr;
// Faster way to do this
if (multicam_panel_ && multicam_panel_->isVisible()) {
if (Sequence *s = dynamic_cast<Sequence *>(get_connected_node())) {
auto *connected = get_connected_node();
// Sequence check via facade type-id predicate (replaces
// dynamic_cast<Sequence*>)
if (connected && viewer_output_is_sequence(connected)) {
OakEngineSequence *seq =
reinterpret_cast<OakEngineSequence *>(connected);
const Rational seq_tb = sequence_timebase(seq);
// Prefer selected nodes
for (Node *n : qAsConst(node_view_selected_)) {
if ((multicam = dynamic_cast<MultiCamNode *>(n))) {
for (OakEngineNode *n : qAsConst(node_view_selected_)) {
// MultiCam check via facade predicate (replaces
// dynamic_cast<MultiCamNode*>)
if (oakengine_node_is_multicam(n)) {
multicam = n;
// Found multicam, now try to find corresponding clip from selected timeline blocks
for (Block *b : qAsConst(timeline_selected_blocks_)) {
if (ClipBlock *c = dynamic_cast<ClipBlock *>(b)) {
if (c->range().contains(time) &&
c->context_contains_node(multicam)) {
clip = c;
break;
}
for (OakEngineBlock *b : qAsConst(timeline_selected_blocks_)) {
// Clip check via facade predicate (replaces
// dynamic_cast<ClipBlock*>)
if (oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(b)) &&
block_range_contains(b, seq_tb, time) &&
oakengine_node_context_contains_node(
reinterpret_cast<OakEngineNode *>(b),
multicam)) {
clip = b;
break;
}
}
break;
@@ -904,32 +983,48 @@ void ViewerWidget::detect_multicam_node(const Rational &time)
// Next, prefer multicam from selected block
if (!multicam) {
for (Block *b : qAsConst(timeline_selected_blocks_)) {
if (b->range().contains(time)) {
if ((clip = dynamic_cast<ClipBlock *>(b))) {
if ((multicam = reinterpret_cast<MultiCamNode *>(
oakengine_clip_find_multicam(
reinterpret_cast<OakEngineNode *>(clip))))) {
break;
}
for (OakEngineBlock *b : qAsConst(timeline_selected_blocks_)) {
if (block_range_contains(b, seq_tb, time) &&
oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(b))) {
clip = b;
if ((multicam = oakengine_clip_find_multicam(
reinterpret_cast<OakEngineNode *>(clip)))) {
break;
}
}
}
}
if (!multicam) {
const QVector<Track *> &tracks = s->get_tracks();
for (Track *t : tracks) {
if (t->is_locked()) {
continue;
}
const int64_t time_ts =
Timecode::time_to_timestamp(time, seq_tb);
int track_counts[3] = { 0, 0, 0 };
oakengine_sequence_track_count(seq, &track_counts[0],
&track_counts[1],
&track_counts[2]);
// Iterate all track types (Sequence::get_tracks())
for (int type = OAKENGINE_TRACK_TYPE_VIDEO;
type <= OAKENGINE_TRACK_TYPE_SUBTITLE && !multicam;
type++) {
for (int i = 0; i < track_counts[type] && !multicam; i++) {
if (oakengine_track_is_locked(seq, type, i)) {
continue;
}
Block *b = t->nearest_block_before_or_at(time);
if ((clip = dynamic_cast<ClipBlock *>(b))) {
if ((multicam = reinterpret_cast<MultiCamNode *>(
oakengine_clip_find_multicam(
reinterpret_cast<OakEngineNode *>(clip))))) {
break;
OakEngineTrack *track =
oakengine_sequence_track_at(seq, type, i);
OakEngineBlock *b =
oakengine_track_nearest_block_before_or_at(track,
time_ts);
if (b && oakengine_node_is_clip(
reinterpret_cast<OakEngineNode *>(b))) {
clip = b;
if ((multicam = oakengine_clip_find_multicam(
reinterpret_cast<OakEngineNode *>(
clip)))) {
break;
}
}
}
}
@@ -939,12 +1034,11 @@ void ViewerWidget::detect_multicam_node(const Rational &time)
if (multicam) {
if (multicam_panel_) {
multicam_panel_->set_multicam_node(get_connected_node(), multicam, clip,
time);
multicam_panel_->set_multicam_node(get_connected_node(),
multicam, clip, time);
}
// FIXME: Really dirty
oakengine_render_cache_set_multicam_node(
reinterpret_cast<OakEngineNode *>(multicam));
oakengine_render_cache_set_multicam_node(multicam);
} else {
oakengine_render_cache_set_multicam_node(nullptr);
if (multicam_panel_) {
@@ -978,8 +1072,7 @@ void ViewerWidget::update_waveform_view_from_mode()
QSizePolicy::Expanding);
if (get_connected_node()) {
oakengine_viewer_set_waveform_enabled(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
oakengine_viewer_set_waveform_enabled(get_connected_node(),
waveform_view_->isVisible() ? 1 : 0);
if (waveform_view_->isVisible()) {
@@ -997,7 +1090,7 @@ void ViewerWidget::queue_next_audio_buffer()
// Clamp queue end by zero and the audio length
queue_end = std::clamp(queue_end, Rational(0),
get_connected_node()->get_audio_length());
viewer_output_audio_length(get_connected_node()));
if ((playback_speed_ > 0 && queue_end <= audio_playback_queue_time_) ||
(playback_speed_ < 0 && queue_end >= audio_playback_queue_time_)) {
// This will queue nothing, so stop the loop here
@@ -1007,7 +1100,7 @@ void ViewerWidget::queue_next_audio_buffer()
return;
}
OakEngineNode *viewer = reinterpret_cast<OakEngineNode *>(get_connected_node());
OakEngineNode *viewer = get_connected_node();
OakEnginePreviewRequest *req = oakengine_preview_request_audio_range(
viewer,
audio_playback_queue_time_.numerator(),
@@ -1194,7 +1287,7 @@ void ViewerWidget::update_texture_from_node()
return;
}
Rational time = get_connected_node()->get_playhead();
Rational time = viewer_output_playhead(get_connected_node());
bool frame_exists = frame_exists_at_time(time);
bool frame_might_be_still = viewer_might_be_a_still();
@@ -1250,16 +1343,15 @@ void ViewerWidget::play_internal(int speed, bool in_to_out_only)
// If the playhead is beyond the end, restart at 0
if (!recording_) {
Rational last_frame = get_connected_node()->get_length() - timebase();
Rational last_frame =
viewer_output_length(get_connected_node()) - timebase();
if (!in_to_out_only &&
get_connected_node()->get_playhead() >= last_frame) {
viewer_output_playhead(get_connected_node()) >= last_frame) {
if (speed > 0) {
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
oakengine_viewer_set_playhead(get_connected_node(),
0, 1);
} else {
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
oakengine_viewer_set_playhead(get_connected_node(),
last_frame.numerator(), last_frame.denominator());
}
}
@@ -1322,7 +1414,8 @@ void ViewerWidget::play_internal(int speed, bool in_to_out_only)
static const int prequeue_count = 2;
prequeuing_audio_ =
prequeue_count; // Queue two buffers ahead of time
audio_playback_queue_time_ = get_connected_node()->get_playhead();
audio_playback_queue_time_ =
viewer_output_playhead(get_connected_node());
qDebug() << "ViewerWidget::PlayInternal: prequeuing audio start time="
<< audio_playback_queue_time_.to_double();
for (int i = 0; i < prequeue_count; i++) {
@@ -1350,6 +1443,8 @@ void ViewerWidget::pause_internal()
controls_->set_pause_button_recording_state(false);
recording_callback_->disable_recording_overlay();
// recording_track_ and TimelineWidget::recording_callback() both use
// the app TrackReference mirror now.
recording_callback_->recording_callback(
recording_filename_, recording_range_, recording_track_);
}
@@ -1416,15 +1511,15 @@ void ViewerWidget::push_scrubbed_audio()
// NOTE: Hardcoded scrubbing interval (20ms)
Rational interval = Rational(20, 1000);
OakEngineNode *viewer =
reinterpret_cast<OakEngineNode *>(get_connected_node());
OakEngineNode *viewer = get_connected_node();
const Rational playhead = viewer_output_playhead(viewer);
OakEnginePreviewRequest *req =
oakengine_preview_request_audio_range(
viewer,
get_connected_node()->get_playhead().numerator(),
get_connected_node()->get_playhead().denominator(),
(get_connected_node()->get_playhead() + interval).numerator(),
(get_connected_node()->get_playhead() + interval).denominator());
playhead.numerator(),
playhead.denominator(),
(playhead + interval).numerator(),
(playhead + interval).denominator());
if (req) {
oakengine_preview_request_set_finished_callback(
req, audio_scrub_finished_cb, this);
@@ -1440,19 +1535,20 @@ void ViewerWidget::update_minimum_scale()
return;
}
if (get_connected_node()->get_length().isNull()) {
const Rational length = viewer_output_length(get_connected_node());
if (length.isNull()) {
// Avoids divide by zero
set_minimum_scale(0);
} else {
double min_scale = static_cast<double>(ruler()->width()) /
get_connected_node()->get_length().to_double();
length.to_double();
// Ensure min_scale doesn't exceed max_scale to prevent crash
min_scale = qMin(min_scale, get_maximum_scale());
set_minimum_scale(min_scale);
}
}
void ViewerWidget::set_color_transform(const ColorTransform &transform,
void ViewerWidget::set_color_transform(const oak::ColorTransform &transform,
ViewerDisplayWidget *sender)
{
sender->set_color_transform(transform);
@@ -1461,8 +1557,7 @@ void ViewerWidget::set_color_transform(const ColorTransform &transform,
QString ViewerWidget::get_cached_filename_from_time(const Rational &time)
{
if (frame_exists_at_time(time)) {
return get_connected_node()->video_frame_cache()->get_valid_cache_filename(
time);
return viewer_valid_cache_filename(get_connected_node(), time);
}
return QString();
@@ -1471,14 +1566,15 @@ QString ViewerWidget::get_cached_filename_from_time(const Rational &time)
bool ViewerWidget::frame_exists_at_time(const Rational &time)
{
return get_connected_node() && time >= 0 &&
time < get_connected_node()->get_video_length();
time < viewer_output_video_length(get_connected_node());
}
bool ViewerWidget::viewer_might_be_a_still()
{
return get_connected_node() &&
get_connected_node()->get_connected_texture_output() &&
get_connected_node()->get_video_length().isNull();
oakengine_viewer_output_get_connected_texture(
get_connected_node()) &&
viewer_output_video_length(get_connected_node()).isNull();
}
void ViewerWidget::set_display_image(OakEnginePreviewRequest *req)
@@ -1530,14 +1626,6 @@ void ViewerWidget::set_display_image(OakEnginePreviewRequest *req)
}
}
void ViewerWidget::set_display_image(RenderTicketPtr ticket)
{
// Legacy compat: convert to OakEnginePreviewRequest* not available,
// but this path is no longer called internally.
// Call the new overload with nullptr to clear the display.
set_display_image(static_cast<OakEnginePreviewRequest *>(nullptr));
}
OakEnginePreviewRequest *ViewerWidget::request_next_frame_for_queue(bool increment)
{
OakEnginePreviewRequest *req = nullptr;
@@ -1567,16 +1655,14 @@ OakEnginePreviewRequest *ViewerWidget::get_frame(const Rational &t)
return get_single_frame(t);
}
QString cache_fn =
get_connected_node()->video_frame_cache()->get_valid_cache_filename(t);
QString cache_fn = viewer_valid_cache_filename(get_connected_node(), t);
if (!QFileInfo::exists(cache_fn)) {
// Frame hasn't been cached, start render job
return get_single_frame(t);
} else {
// Frame has been cached, grab the frame via preview request
OakEngineNode *viewer =
reinterpret_cast<OakEngineNode *>(get_connected_node());
OakEngineNode *viewer = get_connected_node();
return oakengine_preview_request_single_frame(
viewer, t.numerator(), t.denominator(), 0);
}
@@ -1616,8 +1702,8 @@ void ViewerWidget::finish_play_preprocess()
prequeued_audio_.clear();
AudioMonitor::start_waveform_on_all(
get_connected_node()->get_connected_waveform(),
get_connected_node()->get_playhead(), playback_speed_);
oakengine_viewer_get_connected_waveform(get_connected_node()),
viewer_output_playhead(get_connected_node()), playback_speed_);
}
display_widget_->reset_fps_timer();
@@ -1645,7 +1731,7 @@ int ViewerWidget::determine_playback_queue_size()
if (playback_speed_ > 0) {
end_ts = Timecode::time_to_timestamp(
get_connected_node()->get_video_length(), timebase());
viewer_output_video_length(get_connected_node()), timebase());
} else {
end_ts = 0;
}
@@ -1669,7 +1755,7 @@ void ViewerWidget::context_menu_set_playback_res(QAction *action)
int div = action->data().toInt();
void *c = oakengine_viewer_set_preview_divider_command(
reinterpret_cast<OakEngineNode *>(get_connected_node()), div);
get_connected_node(), div);
oakengine_undo_push(c, tr("Changed Playback Resolution").toUtf8().constData());
}
@@ -2014,13 +2100,14 @@ void ViewerWidget::show_context_menu(const QPoint &pos)
void ViewerWidget::play(bool in_to_out_only)
{
if (in_to_out_only) {
oakengine_viewer_workarea wa;
if (get_connected_node() &&
get_connected_node()->get_work_area()->enabled()) {
oakengine_viewer_get_workarea(get_connected_node(), &wa) ==
OAKENGINE_OK &&
wa.enabled) {
// Jump to in point
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
get_connected_node()->get_work_area()->in().numerator(),
get_connected_node()->get_work_area()->in().denominator());
oakengine_viewer_set_playhead(get_connected_node(),
wa.in_num, wa.in_den);
} else {
in_to_out_only = false;
}
@@ -2029,8 +2116,7 @@ void ViewerWidget::play(bool in_to_out_only)
char fn_buf[512];
oakengine_project_filename(
reinterpret_cast<OakEngineProject *>(
get_connected_node()->project()),
oakengine_node_get_project(get_connected_node()),
fn_buf, sizeof(fn_buf));
if (fn_buf[0] == '\0') {
QMessageBox::critical(
@@ -2143,7 +2229,7 @@ void ViewerWidget::shuttle_right()
play_internal(current_speed, false);
}
void ViewerWidget::set_color_transform(const ColorTransform &transform)
void ViewerWidget::set_color_transform(const oak::ColorTransform &transform)
{
set_color_transform(transform, display_widget_);
}
@@ -2161,9 +2247,12 @@ void ViewerWidget::TimebaseChangedEvent(const Rational &timebase)
controls_->set_timebase(timebase);
controls_->set_time(get_connected_node() ? get_connected_node()->get_playhead() :
0);
length_changed_slot(get_connected_node() ? get_connected_node()->get_length() : 0);
controls_->set_time(get_connected_node() ?
viewer_output_playhead(get_connected_node()) :
0);
length_changed_slot(get_connected_node() ?
viewer_output_length(get_connected_node()) :
0);
}
void ViewerWidget::playback_timer_update()
@@ -2180,16 +2269,23 @@ void ViewerWidget::playback_timer_update()
min_time = recording_range_.in();
max_time = recording_range_.out();
} else if (play_in_to_out_only_ &&
get_connected_node()->get_work_area()->enabled()) {
} else if (play_in_to_out_only_ && get_connected_node()) {
// If "play in to out" is enabled or we're looping AND we have a workarea, only play the workarea
min_time = get_connected_node()->get_work_area()->in();
max_time = get_connected_node()->get_work_area()->out();
oakengine_viewer_workarea wa;
if (oakengine_viewer_get_workarea(get_connected_node(), &wa) ==
OAKENGINE_OK &&
wa.enabled) {
min_time = Rational(int(wa.in_num), int(wa.in_den));
max_time = Rational(int(wa.out_num), int(wa.out_den));
} else {
min_time = 0;
max_time = viewer_output_length(get_connected_node());
}
} else {
// Otherwise set the bounds to the range of the sequence
min_time = 0;
max_time = get_connected_node()->get_length();
max_time = viewer_output_length(get_connected_node());
}
// If we're stopping playback on the last frame rather than after it, subtract our max time
@@ -2239,8 +2335,7 @@ void ViewerWidget::playback_timer_update()
// pausing. Even if we pause it later with `end_of_line`, we prefer pausing after setting the time
// so that an audio scrub event, etc. isn't sent.
time_changed_from_timer_ = true;
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(get_connected_node()),
oakengine_viewer_set_playhead(get_connected_node(),
time_to_set.numerator(), time_to_set.denominator());
time_changed_from_timer_ = false;
if (end_of_line) {
@@ -2295,7 +2390,7 @@ void ViewerWidget::length_changed_slot(const Rational &length)
update_minimum_scale();
if (get_connected_node() && length < last_length_ &&
get_connected_node()->get_playhead() >= length) {
viewer_output_playhead(get_connected_node()) >= length) {
update_texture_from_node();
}
@@ -2315,7 +2410,7 @@ void ViewerWidget::interlacing_changed_slot(int interlacing)
void ViewerWidget::update_renderer_video_parameters()
{
VideoParams vp = viewer_output_video_params(get_connected_node());
oak::VideoParams vp = viewer_output_video_params(get_connected_node());
foreach (ViewerDisplayWidget *dw, playback_devices_) {
dw->set_video_params(vp);
@@ -2422,7 +2517,7 @@ void ViewerWidget::update_info_chip()
return;
}
VideoParams vp = viewer_output_video_params(get_connected_node());
oak::VideoParams vp = viewer_output_video_params(get_connected_node());
if (vp.width() <= 0 || vp.height() <= 0) {
info_chip_->setVisible(false);
return;
@@ -2480,9 +2575,9 @@ void ViewerWidget::overlay_toggle_safe_frame()
void ViewerWidget::viewer_invalidated_video_range(const TimeRange &range)
{
// If our current frame is within this range, we need to update
if (!is_playing() && get_connected_node()->get_playhead() >= range.in() &&
(get_connected_node()->get_playhead() < range.out() ||
range.in() == range.out())) {
const Rational playhead = viewer_output_playhead(get_connected_node());
if (!is_playing() && playhead >= range.in() &&
(playhead < range.out() || range.in() == range.out())) {
QMetaObject::invokeMethod(this, &ViewerWidget::update_texture_from_node,
Qt::QueuedConnection);
}
@@ -2507,7 +2602,7 @@ void ViewerWidget::dropped(QDropEvent *event)
// Variables to deserialize into
quintptr item_ptr = 0;
QVector<Track::Reference> enabled_streams;
QVector<TrackReference> enabled_streams;
while (!stream.atEnd()) {
stream >> enabled_streams >> item_ptr;
@@ -2517,10 +2612,8 @@ void ViewerWidget::dropped(QDropEvent *event)
}
if (item_ptr) {
Node *item = reinterpret_cast<Node *>(item_ptr);
ViewerOutput *viewer = dynamic_cast<ViewerOutput *>(item);
if (viewer) {
if (OakEngineNode *viewer = oakengine_viewer_from_node(
reinterpret_cast<OakEngineNode *>(item_ptr))) {
connect_viewer_node(viewer);
}
}
+20 -22
View File
@@ -32,16 +32,19 @@
#include "oakengine/audio.h"
#include "audiowaveformview.h"
#include "node/output/viewer/viewer.h"
#include "render/previewaudiodevice.h"
#include "common/trackreferencehandle.h"
#include "oakengine/preview.h"
#include "oakengine/timeline.h"
#include "viewerdisplay.h"
#include "vieweroutpututils.h"
#include "viewersizer.h"
#include "viewerwindow.h"
#include "widget/playbackcontrols/playbackcontrols.h"
#include "widget/timebased/timebasedwidget.h"
#include "widget/timelinewidget/timelinewidget.h"
#include <memory>
namespace olive
{
@@ -98,10 +101,10 @@ public:
return display_widget_->color_manager();
}
void set_gizmos(Node *node);
void set_gizmos(OakEngineNode *node);
void start_capture(TimelineWidget *source, const TimeRange &time,
const Track::Reference &track);
const TrackReference &track);
void set_audio_scrubbing_enabled(bool e)
{
@@ -113,7 +116,7 @@ public:
playback_devices_.push_back(vw);
}
void set_timeline_selected_blocks(const QVector<Block *> &b)
void set_timeline_selected_blocks(const QVector<OakEngineBlock *> &b)
{
timeline_selected_blocks_ = b;
@@ -126,11 +129,7 @@ public:
void set_node_view_selections(const QVector<OakEngineNode *> &n)
{
node_view_selected_.clear();
node_view_selected_.reserve(n.size());
foreach (OakEngineNode *handle, n) {
node_view_selected_.append(reinterpret_cast<Node *>(handle));
}
node_view_selected_ = n;
if (!is_playing()) {
// If is playing, this will happen by the next frame automatically
@@ -154,7 +153,7 @@ public slots:
void shuttle_right();
void set_color_transform(const ColorTransform &transform);
void set_color_transform(const oak::ColorTransform &transform);
/**
* @brief Wrapper for ViewerGLWidget::SetSignalCursorColorEnabled()
@@ -203,11 +202,11 @@ protected:
virtual void TimebaseChangedEvent(const Rational &) override;
virtual void TimeChangedEvent(const Rational &time) override;
virtual void ConnectNodeEvent(ViewerOutput *) override;
virtual void DisconnectNodeEvent(ViewerOutput *) override;
virtual void ConnectedNodeChangeEvent(ViewerOutput *) override;
virtual void ConnectedWorkAreaChangeEvent(TimelineWorkArea *) override;
virtual void ConnectedMarkersChangeEvent(TimelineMarkerList *) override;
virtual void ConnectNodeEvent(OakEngineNode *n) override;
virtual void DisconnectNodeEvent(OakEngineNode *n) override;
virtual void ConnectedNodeChangeEvent(OakEngineNode *n) override;
virtual void ConnectedWorkAreaChangeEvent(OakEngineWorkarea *workarea) override;
virtual void ConnectedMarkersChangeEvent(OakEngineMarkerList *markers) override;
virtual void ScaleChangedEvent(const double &s) override;
@@ -243,7 +242,7 @@ private slots:
private:
int64_t get_timestamp() const
{
return Timecode::time_to_timestamp(get_connected_node()->get_playhead(),
return Timecode::time_to_timestamp(viewer_output_playhead(get_connected_node()),
timebase(), Timecode::k_floor);
}
@@ -257,7 +256,7 @@ private:
void update_minimum_scale();
void set_color_transform(const ColorTransform &transform,
void set_color_transform(const oak::ColorTransform &transform,
ViewerDisplayWidget *sender);
QString get_cached_filename_from_time(const Rational &time);
@@ -266,7 +265,6 @@ private:
bool viewer_might_be_a_still();
void set_display_image(RenderTicketPtr ticket);
void set_display_image(OakEnginePreviewRequest *req);
OakEnginePreviewRequest *request_next_frame_for_queue(bool increment = true);
@@ -352,7 +350,7 @@ private:
bool recording_;
TimelineWidget *recording_callback_;
TimeRange recording_range_;
Track::Reference recording_track_;
TrackReference recording_track_;
QString recording_filename_;
qint64 queue_starved_start_;
@@ -366,8 +364,8 @@ private:
int ignore_scrub_;
QVector<Block *> timeline_selected_blocks_;
QVector<Node *> node_view_selected_;
QVector<OakEngineBlock *> timeline_selected_blocks_;
QVector<OakEngineNode *> node_view_selected_;
MulticamWidget *multicam_panel_;
+195 -110
View File
@@ -37,10 +37,12 @@
#include <cstring>
#include "audio/audiomanager.h"
#include <olive/core/util/timecodefunctions.h>
#include "oakutil/define.h"
#include "common/htmlapp.h"
#include "oakengine/gizmo.h"
#include "oakengine/timeline.h"
#include "oakengine/videoparams.h"
#include "oakengine/display.h"
#include "oakengine/undo.h"
@@ -48,11 +50,6 @@
#include "oakutil/qtutils.h"
#include "common/configwrapper.h"
#include "core.h"
#include "node/block/subtitle/subtitle.h"
#include "node/gizmo/path.h"
#include "node/gizmo/point.h"
#include "node/gizmo/polygon.h"
#include "node/gizmo/screen.h"
#include "window/mainwindow/mainwindow.h"
namespace olive
@@ -206,7 +203,7 @@ void ViewerDisplayWidget::set_safe_margins(const ViewerSafeMarginInfo &safe_marg
}
}
void ViewerDisplayWidget::set_gizmos(Node *node)
void ViewerDisplayWidget::set_gizmos(OakEngineNode *node)
{
if (gizmos_ != node) {
gizmos_ = node;
@@ -215,7 +212,7 @@ void ViewerDisplayWidget::set_gizmos(Node *node)
}
}
void ViewerDisplayWidget::set_video_params(const VideoParams &params)
void ViewerDisplayWidget::set_video_params(const oak::VideoParams &params)
{
gizmo_params_ = params;
@@ -242,7 +239,7 @@ void ViewerDisplayWidget::set_time(const Rational &time)
}
}
void ViewerDisplayWidget::set_subtitle_tracks(Sequence *list)
void ViewerDisplayWidget::set_subtitle_tracks(OakEngineSequence *list)
{
if (subtitle_tracks_) {
bridge_->unsubscribe(subtitle_sub_);
@@ -396,6 +393,40 @@ bool ViewerDisplayWidget::eventFilter(QObject *o, QEvent *e)
return super::eventFilter(o, e);
}
namespace {
/**
* @brief Map oakengine_text_gizmo::vertical_alignment (0=AlignTop,
* 1=AlignBottom, 2=AlignVCenter; oakengine/gizmo.h) to Qt::Alignment.
* Must stay in sync with the engine-side mapping in
* engine/src/capi/gizmo.cpp.
*/
Qt::Alignment text_gizmo_v_align_from_pod(int v)
{
switch (v) {
case 1:
return Qt::AlignBottom;
case 2:
return Qt::AlignVCenter;
default:
return Qt::AlignTop;
}
}
/**
* @brief Fetch the text gizmo POD of a text v3 node at the given time
* (replaces TextGizmo::get_rect()/get_vertical_alignment() member calls).
* False when the node has no text gizmo.
*/
bool get_text_gizmo_pod(OakEngineNode *node, const Rational &t,
oakengine_text_gizmo *out)
{
return oakengine_text_gizmo_get(node, t.numerator(), t.denominator(),
out) == OAKENGINE_OK;
}
} // namespace
void ViewerDisplayWidget::on_paint()
{
const bool backend_neutral = is_backend_neutral();
@@ -588,12 +619,17 @@ void ViewerDisplayWidget::on_paint()
p.setWorldTransform(gizmo_last_draw_transform_);
gizmos_->update_gizmo_positions(
gizmo_db_, NodeGlobals(gizmo_params_, gizmo_audio_params_,
gizmo_draw_time_, LoopMode::k_loop_mode_off));
foreach (NodeGizmo *gizmo, gizmos_->get_gizmos()) {
if (gizmo->is_visible()) {
gizmo->draw(&p);
OakEngineNode *gizmos_handle =
gizmos_;
oakengine_node_update_gizmo_positions(
gizmos_handle, &gizmo_db_, gizmo_params_.width(),
gizmo_params_.height(), gizmo_draw_time_.in().numerator(),
gizmo_draw_time_.in().denominator());
const int gizmo_count = oakengine_node_gizmo_count(gizmos_handle);
for (int i = 0; i < gizmo_count; i++) {
void *gizmo = oakengine_node_gizmo_at(gizmos_handle, i);
if (oakengine_gizmo_is_visible(gizmo)) {
oakengine_gizmo_draw(gizmo, &p);
}
}
@@ -602,8 +638,12 @@ void ViewerDisplayWidget::on_paint()
pm.fill(Qt::transparent);
QPainter pixp(&pm);
text_edit_->paint(&pixp,
active_text_gizmo_->get_vertical_alignment());
Qt::Alignment v_align = Qt::AlignTop;
oakengine_text_gizmo tg;
if (get_text_gizmo_pod(gizmos_handle, get_gizmo_time(), &tg)) {
v_align = text_gizmo_v_align_from_pod(tg.vertical_alignment);
}
text_edit_->paint(&pixp, v_align);
p.drawPixmap(text_edit_pos_, pm);
}
@@ -773,8 +813,9 @@ void ViewerDisplayWidget::draw_text_with_crude_shadow(QPainter *painter,
Rational ViewerDisplayWidget::get_gizmo_time()
{
return get_adjusted_time(get_time_target(), gizmos_, time_,
Node::k_transform_towards_input);
// `0` mirrors the engine's Node::k_transform_towards_input ordinal
// (see timetarget.h — the direction parameter is an int now).
return get_adjusted_time(get_time_target(), gizmos_, time_, 0);
}
bool ViewerDisplayWidget::is_hand_drag(QMouseEvent *event) const
@@ -836,22 +877,27 @@ QTransform ViewerDisplayWidget::generate_display_transform()
return gizmo_transform;
}
QTransform ViewerDisplayWidget::generate_gizmo_transform(Node *gizmos,
Node *target,
QTransform ViewerDisplayWidget::generate_gizmo_transform(OakEngineNode *gizmos,
OakEngineNode *target,
const TimeRange &range)
{
QTransform t = generate_display_transform();
if (target) {
if (ViewerOutput *v = dynamic_cast<ViewerOutput *>(target)) {
if (Node *n = v->get_connected_texture_output()) {
target = n;
OakEngineNode *target_handle = target;
// ViewerOutput targets resolve to their connected texture output
// (replaces dynamic_cast<ViewerOutput*> +
// get_connected_texture_output()).
if (oakengine_node_is_viewer_output(target_handle)) {
if (OakEngineNode *n =
oakengine_viewer_output_get_connected_texture(
target_handle)) {
target_handle = n;
}
}
double m[6];
oakengine_traverse_transform(
reinterpret_cast<OakEngineNode *>(gizmos),
reinterpret_cast<OakEngineNode *>(target),
gizmos, target_handle,
range.in().numerator(), range.in().denominator(),
range.out().numerator(), range.out().denominator(), nullptr, m);
QTransform nt(m[0], m[1], m[2], m[3], m[4], m[5]);
@@ -869,48 +915,42 @@ QTransform ViewerDisplayWidget::generate_gizmo_transform(Node *gizmos,
return t;
}
NodeGizmo *ViewerDisplayWidget::try_gizmo_press(const NodeValueRow &row,
const QPointF &p)
void *ViewerDisplayWidget::try_gizmo_press(const QPointF &p)
{
if (!gizmos_) {
return nullptr;
}
for (auto it = gizmos_->get_gizmos().crbegin();
it != gizmos_->get_gizmos().crend(); it++) {
NodeGizmo *gizmo = *it;
if (gizmo->is_visible()) {
if (PointGizmo *point = dynamic_cast<PointGizmo *>(gizmo)) {
if (point->get_clicking_rect(gizmo_last_draw_transform_)
.contains(p)) {
return point;
}
} else if (PolygonGizmo *poly =
dynamic_cast<PolygonGizmo *>(gizmo)) {
if (poly->get_polygon().containsPoint(p, Qt::OddEvenFill)) {
return poly;
}
} else if (PathGizmo *path = dynamic_cast<PathGizmo *>(gizmo)) {
if (path->get_path().contains(p)) {
return path;
}
} else if (ScreenGizmo *screen =
dynamic_cast<ScreenGizmo *>(gizmo)) {
// NOTE: Perhaps this should limit to the actual visible screen space? We'll see.
return screen;
}
// The engine-side hit test mirrors the original per-type picking logic
// (PointGizmo clicking rect / PolygonGizmo containsPoint / PathGizmo
// contains / ScreenGizmo always hittable) and checks visibility itself;
// see oakengine/gizmo.h.
OakEngineNode *gizmos_handle = gizmos_;
const double transform6[6] = { gizmo_last_draw_transform_.m11(),
gizmo_last_draw_transform_.m12(),
gizmo_last_draw_transform_.m21(),
gizmo_last_draw_transform_.m22(),
gizmo_last_draw_transform_.dx(),
gizmo_last_draw_transform_.dy() };
const int gizmo_count = oakengine_node_gizmo_count(gizmos_handle);
for (int i = gizmo_count - 1; i >= 0; i--) {
void *gizmo = oakengine_node_gizmo_at(gizmos_handle, i);
if (oakengine_gizmo_hit_test(gizmo, transform6, p.x(), p.y())) {
return gizmo;
}
}
return nullptr;
}
void ViewerDisplayWidget::open_text_gizmo(TextGizmo *text, QMouseEvent *event)
void ViewerDisplayWidget::open_text_gizmo(void *text, QMouseEvent *event)
{
generate_gizmo_transforms();
gizmos_->update_gizmo_positions(
gizmo_db_, NodeGlobals(gizmo_params_, gizmo_audio_params_,
gizmo_draw_time_, LoopMode::k_loop_mode_off));
OakEngineNode *gizmos_handle = gizmos_;
oakengine_node_update_gizmo_positions(
gizmos_handle, &gizmo_db_, gizmo_params_.width(),
gizmo_params_.height(), gizmo_draw_time_.in().numerator(),
gizmo_draw_time_.in().denominator());
active_text_gizmo_ = text;
text_transform_ = generate_gizmo_transform();
@@ -919,9 +959,6 @@ void ViewerDisplayWidget::open_text_gizmo(TextGizmo *text, QMouseEvent *event)
// Create text editor
text_edit_ = new ViewerTextEditor(text_transform_.m11(), this);
// Set text editor's gizmo property for later use
text_edit_->setProperty("gizmo", reinterpret_cast<quintptr>(text));
// Install ourselves as event filter so we can receive the text editor's paint events
text_edit_->installEventFilter(this);
@@ -934,8 +971,23 @@ void ViewerDisplayWidget::open_text_gizmo(TextGizmo *text, QMouseEvent *event)
// "Show" text editor so that it throws paint events, even though its paint event is disabled
text_edit_->show();
// Convert HTML to Qt document
Html::html_to_doc(text_edit_->document(), text->get_html());
// Convert HTML to Qt document (fetched through the text gizmo facade)
QString html;
{
const Rational gizmo_time = get_gizmo_time();
const int html_len = oakengine_text_gizmo_get_html(
gizmos_handle, gizmo_time.numerator(), gizmo_time.denominator(),
nullptr, 0);
if (html_len > 0) {
QByteArray html_buf(html_len, '\0');
oakengine_text_gizmo_get_html(
gizmos_handle, gizmo_time.numerator(),
gizmo_time.denominator(), html_buf.data(),
static_cast<int>(html_buf.size()));
html = QString::fromUtf8(html_buf.constData());
}
}
Html::html_to_doc(text_edit_->document(), html);
// Connect text change event to propagate back to node
connect(text_edit_, &ViewerTextEditor::textChanged, this,
@@ -950,7 +1002,7 @@ void ViewerDisplayWidget::open_text_gizmo(TextGizmo *text, QMouseEvent *event)
// Emit text gizmo activation signal
oakengine_text_gizmo_activated(
reinterpret_cast<OakEngineNode *>(gizmos_));
gizmos_);
// Create toolbar
text_toolbar_ = new ViewerTextEditorToolBar(text_edit_);
@@ -958,20 +1010,19 @@ void ViewerDisplayWidget::open_text_gizmo(TextGizmo *text, QMouseEvent *event)
connect(text_toolbar_, &ViewerTextEditorToolBar::vertical_alignment_changed,
this, [this](Qt::Alignment align) {
oakengine_text_gizmo_set_vertical_alignment(
reinterpret_cast<OakEngineNode *>(gizmos_),
gizmos_,
static_cast<int>(align));
});
{
// The POD carries 0/1/2 (see oakengine/gizmo.h), map it to the real
// Qt alignment flag before handing it to the toolbar.
oakengine_text_gizmo _tg;
if (oakengine_text_gizmo_get(
reinterpret_cast<OakEngineNode *>(gizmos_),
get_gizmo_time().numerator(), get_gizmo_time().denominator(),
&_tg) == OAKENGINE_OK) {
text_toolbar_->set_vertical_alignment(
static_cast<Qt::AlignmentFlag>(_tg.vertical_alignment));
} else {
text_toolbar_->set_vertical_alignment(Qt::AlignTop);
Qt::Alignment v_align = Qt::AlignTop;
if (get_text_gizmo_pod(gizmos_handle, get_gizmo_time(), &_tg)) {
v_align = text_gizmo_v_align_from_pod(_tg.vertical_alignment);
}
text_toolbar_->set_vertical_alignment(
static_cast<Qt::AlignmentFlag>(static_cast<int>(v_align)));
}
text_edit_->connect_tool_bar(text_toolbar_);
@@ -1055,14 +1106,15 @@ bool ViewerDisplayWidget::on_mouse_press(QMouseEvent *event)
add_band_ = true;
} else if ((current_gizmo_ = try_gizmo_press(
gizmo_db_, gizmo_last_draw_transform_inverted_.map(
event->pos())))) {
gizmo_last_draw_transform_inverted_.map(
event->pos())))) {
// Handle gizmo click
gizmo_start_drag_ = event->pos();
gizmo_last_drag_ = gizmo_start_drag_;
current_gizmo_->set_globals(
NodeGlobals(gizmo_params_, gizmo_audio_params_,
generate_gizmo_time(), LoopMode::k_loop_mode_off));
const TimeRange gizmo_time = generate_gizmo_time();
oakengine_gizmo_set_globals(
current_gizmo_, gizmo_params_.width(), gizmo_params_.height(),
gizmo_time.in().numerator(), gizmo_time.in().denominator());
} else {
// Handle standard drag
@@ -1103,17 +1155,16 @@ bool ViewerDisplayWidget::on_mouse_move(QMouseEvent *event)
QPointF start = screen_to_scene_point(gizmo_start_drag_);
Rational gizmo_time = get_gizmo_time();
NodeValueRow row;
oakengine_traverse_generate_row(
reinterpret_cast<OakEngineNode *>(gizmos_),
gizmos_,
gizmo_time.numerator(), gizmo_time.denominator(),
(gizmo_time + gizmo_params_.frame_rate_as_time_base())
.numerator(),
(gizmo_time + gizmo_params_.frame_rate_as_time_base())
.denominator(),
nullptr, 0, 0, &row);
nullptr, 0, 0, &gizmo_db_);
oakengine_gizmo_drag_start(current_gizmo_, &row,
oakengine_gizmo_drag_start(current_gizmo_, &gizmo_db_,
start.x(), start.y(), gizmo_time.numerator(),
gizmo_time.denominator());
gizmo_drag_started_ = true;
@@ -1185,12 +1236,23 @@ bool ViewerDisplayWidget::on_mouse_double_click(QMouseEvent *event)
return true;
} else if (event->button() == Qt::LeftButton && gizmos_) {
QPointF ptr = transform_viewer_space_to_buffer_space(event->pos());
foreach (NodeGizmo *g, gizmos_->get_gizmos()) {
if (TextGizmo *text = dynamic_cast<TextGizmo *>(g)) {
if (text->get_rect().contains(ptr)) {
open_text_gizmo(text, event);
return true;
}
OakEngineNode *gizmos_handle =
gizmos_;
// A text gizmo only exists on a text v3 node, so the node type id is
// the predicate (replaces dynamic_cast<TextGizmo*>). The id string
// must stay in sync with TextGeneratorV3::id()
// (engine/node/generator/text/textv3.cpp). The rect comes from the
// text gizmo POD; the pointer handed to open_text_gizmo() is only an
// opaque marker (the engine resolves the text gizmo from the node).
if (viewer_output_node_type_is(gizmos_handle,
"org.olivevideoeditor.Olive.text3")) {
oakengine_text_gizmo tg;
if (get_text_gizmo_pod(gizmos_handle, get_gizmo_time(), &tg) &&
QRectF(tg.rect_x, tg.rect_y, tg.rect_w, tg.rect_h)
.contains(ptr)) {
open_text_gizmo(oakengine_node_gizmo_at(gizmos_handle, 0),
event);
return true;
}
}
}
@@ -1257,9 +1319,11 @@ void ViewerDisplayWidget::draw_subtitle_tracks()
return;
}
const QVector<Track *> &subtitle_tracklist =
subtitle_tracks_->track_list(Track::k_subtitle)->get_tracks();
if (subtitle_tracklist.empty()) {
OakEngineSequence *subtitle_seq = subtitle_tracks_;
int subtitle_track_count = 0;
oakengine_sequence_track_count(subtitle_seq, nullptr, nullptr,
&subtitle_track_count);
if (subtitle_track_count <= 0) {
return;
}
@@ -1293,11 +1357,23 @@ void ViewerDisplayWidget::draw_subtitle_tracks()
QFontMetrics fm(f);
for (int j = subtitle_tracklist.size() - 1; j >= 0; j--) {
Track *sub_track = subtitle_tracklist.at(j);
if (!sub_track->is_muted()) {
if (SubtitleBlock *sub = dynamic_cast<SubtitleBlock *>(
sub_track->visible_block_at_time(time_))) {
// visible_block_at_time takes a frame timestamp in the sequence's
// frame-rate timebase (see oakengine/timeline.h).
const int64_t time_ts = core::Timecode::time_to_timestamp(
time_, sequence_timebase(subtitle_seq));
for (int j = subtitle_track_count - 1; j >= 0; j--) {
if (!oakengine_track_is_muted(subtitle_seq,
OAKENGINE_TRACK_TYPE_SUBTITLE, j)) {
OakEngineTrack *sub_track = oakengine_sequence_track_at(
subtitle_seq, OAKENGINE_TRACK_TYPE_SUBTITLE, j);
OakEngineBlock *sub =
oakengine_track_visible_block_at_time(sub_track, time_ts);
// Subtitle predicate: SubtitleBlock::id() string compare
// (replaces dynamic_cast<SubtitleBlock*>); must stay in sync
// with engine/node/block/subtitle/subtitle.cpp.
if (sub && viewer_output_node_type_is(
sub, "org.olivevideoeditor.Olive.subtitle")) {
// Split into lines
char text_buf[4096];
const int len = oakengine_subtitle_get_text(
@@ -1414,7 +1490,6 @@ bool ViewerDisplayWidget::forward_mouse_event_to_text_edit(QMouseEvent *event,
local_pos.y() < 0 || local_pos.y() >= text_edit_->height()) {
// Allow clicking other gizmos so the user can resize while the text editor is active
if ((current_gizmo_ = try_gizmo_press(
gizmo_db_,
gizmo_last_draw_transform_inverted_.map(event->pos())))) {
return false;
} else {
@@ -1444,7 +1519,14 @@ bool ViewerDisplayWidget::forward_event_to_text_edit(QEvent *event)
QPointF ViewerDisplayWidget::adjust_pos_by_v_align(QPointF p)
{
switch (active_text_gizmo_->get_vertical_alignment()) {
Qt::Alignment v_align = Qt::AlignTop;
oakengine_text_gizmo tg;
if (get_text_gizmo_pod(gizmos_,
get_gizmo_time(), &tg)) {
v_align = text_gizmo_v_align_from_pod(tg.vertical_alignment);
}
switch (v_align) {
case Qt::AlignTop:
// Do nothing
break;
@@ -1474,14 +1556,12 @@ void ViewerDisplayWidget::generate_gizmo_transforms()
gizmo_draw_time_ = generate_gizmo_time();
if (gizmos_) {
NodeValueRow row;
oakengine_traverse_generate_row(
reinterpret_cast<OakEngineNode *>(gizmos_),
gizmos_,
gizmo_draw_time_.in().numerator(),
gizmo_draw_time_.in().denominator(),
gizmo_draw_time_.out().numerator(),
gizmo_draw_time_.out().denominator(), nullptr, 0, 0, &row);
gizmo_db_ = row;
gizmo_draw_time_.out().denominator(), nullptr, 0, 0, &gizmo_db_);
}
gizmo_last_draw_transform_ = generate_gizmo_transform(
@@ -1707,11 +1787,14 @@ void ViewerDisplayWidget::set_show_fps(bool e)
void ViewerDisplayWidget::request_start_editing_text()
{
if (gizmos_) {
foreach (NodeGizmo *gizmo, gizmos_->get_gizmos()) {
if (TextGizmo *text = dynamic_cast<TextGizmo *>(gizmo)) {
open_text_gizmo(text);
break;
}
OakEngineNode *gizmos_handle =
gizmos_;
// Same text v3 type-id predicate as the double-click path (replaces
// dynamic_cast<TextGizmo*>); must stay in sync with
// TextGeneratorV3::id() (engine/node/generator/text/textv3.cpp).
if (viewer_output_node_type_is(gizmos_handle,
"org.olivevideoeditor.Olive.text3")) {
open_text_gizmo(oakengine_node_gizmo_at(gizmos_handle, 0));
}
}
}
@@ -1809,12 +1892,9 @@ void ViewerDisplayWidget::text_edit_changed()
{
ViewerTextEditor *editor = static_cast<ViewerTextEditor *>(sender());
TextGizmo *gizmo = reinterpret_cast<TextGizmo *>(
editor->property("gizmo").value<quintptr>());
QString html = Html::doc_to_html(editor->document());
oakengine_text_gizmo_update_html(
reinterpret_cast<OakEngineNode *>(gizmos_),
gizmos_,
html.toUtf8().constData(),
get_gizmo_time().numerator(), get_gizmo_time().denominator());
}
@@ -1822,7 +1902,7 @@ void ViewerDisplayWidget::text_edit_changed()
void ViewerDisplayWidget::text_edit_destroyed()
{
oakengine_text_gizmo_deactivated(
reinterpret_cast<OakEngineNode *>(gizmos_));
gizmos_);
text_edit_ = nullptr;
text_toolbar_ = nullptr;
inner_widget()->setMouseTracking(false);
@@ -1864,7 +1944,12 @@ void ViewerDisplayWidget::focus_changed(QWidget *old, QWidget *now)
QRectF ViewerDisplayWidget::update_active_text_gizmo_size()
{
QRectF text_rect = active_text_gizmo_->get_rect();
QRectF text_rect;
oakengine_text_gizmo tg;
if (get_text_gizmo_pod(gizmos_,
get_gizmo_time(), &tg)) {
text_rect = QRectF(tg.rect_x, tg.rect_y, tg.rect_w, tg.rect_h);
}
text_edit_pos_ = text_rect.topLeft();
text_edit_->setGeometry(text_rect.toRect());
return text_rect;
+31 -19
View File
@@ -27,13 +27,8 @@
#include <QRubberBand>
#include "engineeventbridge.h"
#include "node/color/colormanager/colormanager.h"
#include "node/gizmo/text.h"
#include "node/node.h"
#include "node/output/track/tracklist.h"
#include "node/value.h"
#include "oakengine/traverse.h"
#include "tool/tool.h"
#include "oakutil/oakvideo.h"
#include "viewerplaybacktimer.h"
#include "viewerqueue.h"
#include "viewersafemargininfo.h"
@@ -41,6 +36,7 @@
#include "widget/manageddisplay/manageddisplay.h"
#include "widget/timetarget/timetarget.h"
#include "widget/viewer/displaybuffer.h"
#include "widget/viewer/vieweroutpututils.h"
namespace olive
{
@@ -78,13 +74,13 @@ public:
const ViewerSafeMarginInfo &get_safe_margin() const;
void set_safe_margins(const ViewerSafeMarginInfo &safe_margin);
void set_gizmos(Node *node);
void set_gizmos(OakEngineNode *node);
const VideoParams &get_video_params() const
const oak::VideoParams &get_video_params() const
{
return gizmo_params_;
}
void set_video_params(const VideoParams &params);
void set_video_params(const oak::VideoParams &params);
const AudioParams &get_audio_params() const
{
@@ -93,7 +89,7 @@ public:
void set_audio_params(const AudioParams &p);
void set_time(const Rational &time);
void set_subtitle_tracks(Sequence *list);
void set_subtitle_tracks(OakEngineSequence *list);
void set_show_widget_background(bool e)
{
@@ -255,7 +251,8 @@ protected:
QTransform generate_display_transform();
QTransform generate_gizmo_transform(Node *gizmos, Node *target,
QTransform generate_gizmo_transform(OakEngineNode *gizmos,
OakEngineNode *target,
const TimeRange &range);
QTransform generate_gizmo_transform()
{
@@ -300,9 +297,9 @@ private:
void update_matrix();
NodeGizmo *try_gizmo_press(const NodeValueRow &row, const QPointF &p);
void *try_gizmo_press(const QPointF &p);
void open_text_gizmo(TextGizmo *text, QMouseEvent *event = nullptr);
void open_text_gizmo(void *text, QMouseEvent *event = nullptr);
bool on_mouse_press(QMouseEvent *e);
bool on_mouse_move(QMouseEvent *e);
@@ -402,20 +399,35 @@ private:
ViewerSafeMarginInfo safe_margin_;
Node *gizmos_;
NodeValueRow gizmo_db_;
VideoParams gizmo_params_;
OakEngineNode *gizmos_;
/**
* @brief Opaque storage for the engine's NodeValueRow
* (QHash<QString, NodeValue>, engine/node/value.h).
*
* WRAPPER-GAP: NodeValue is engine-internal, so the real row typedef
* cannot be declared app-side, and the C ABI offers no row create/free
* pair — the row is only filled in place by
* oakengine_traverse_generate_row() and handed back to the engine
* (oakengine_node_update_gizmo_positions()/oakengine_gizmo_drag_start()).
* Qt 6 QHash is a single-pointer class for any template arguments, so a
* nullptr-initialized pointer is byte-identical to an empty row; the
* engine's real QHash (move-)assignment manages the contents from then
* on. The row held at widget destruction leaks (one row per widget)
* until the C ABI grows a create/free pair.
*/
void *gizmo_db_ = nullptr;
oak::VideoParams gizmo_params_;
AudioParams gizmo_audio_params_;
QPoint gizmo_start_drag_;
QPoint gizmo_last_drag_;
TimeRange gizmo_draw_time_;
NodeGizmo *current_gizmo_;
void *current_gizmo_;
bool gizmo_drag_started_;
QTransform gizmo_last_draw_transform_;
QTransform gizmo_last_draw_transform_inverted_;
bool show_subtitles_;
Sequence *subtitle_tracks_;
OakEngineSequence *subtitle_tracks_;
EngineEventBridge *bridge_;
int64_t subtitle_sub_ = 0;
@@ -474,7 +486,7 @@ private:
bool queue_starved_;
TextGizmo *active_text_gizmo_;
void *active_text_gizmo_;
QPointF text_edit_pos_;
ViewerTextEditor *text_edit_;
ViewerTextEditorToolBar *text_toolbar_;
+41 -9
View File
@@ -22,18 +22,14 @@
namespace olive {
VideoParams video_params_from_pod(const oak_video_params &pod)
oak::VideoParams video_params_from_pod(const oak_video_params &pod)
{
void *vp = oakengine_video_params_create(&pod);
VideoParams result = *static_cast<VideoParams *>(vp);
oakengine_video_params_free(vp);
return result;
return oak::VideoParams(pod);
}
VideoParams empty_video_params()
oak::VideoParams empty_video_params()
{
oak_video_params pod = {};
return video_params_from_pod(pod);
return oak::VideoParams();
}
Rational sequence_timebase(const void *sequence)
@@ -47,7 +43,43 @@ Rational sequence_timebase(const void *sequence)
return Rational(vp.time_base_num, vp.time_base_den);
}
VideoParams viewer_output_video_params(const void *viewer, int index)
namespace {
Rational viewer_rational(const void *viewer,
int (*getter)(const OakEngineNode *, int64_t *,
int64_t *))
{
int64_t num = 0, den = 1;
if (getter(reinterpret_cast<const OakEngineNode *>(viewer), &num, &den) <
0) {
return Rational();
}
return Rational(num, den);
}
} // namespace
Rational viewer_output_playhead(const void *viewer)
{
return viewer_rational(viewer, oakengine_viewer_get_playhead);
}
Rational viewer_output_length(const void *viewer)
{
return viewer_rational(viewer, oakengine_viewer_get_length);
}
Rational viewer_output_video_length(const void *viewer)
{
return viewer_rational(viewer, oakengine_viewer_get_video_length);
}
Rational viewer_output_audio_length(const void *viewer)
{
return viewer_rational(viewer, oakengine_viewer_get_audio_length);
}
oak::VideoParams viewer_output_video_params(const void *viewer, int index)
{
oak_video_params vp;
if (oakengine_viewer_get_video_params(
+29 -10
View File
@@ -26,8 +26,7 @@
#include <cstring>
#include "oakengine/viewer.h"
#include "render/subtitleparams.h"
#include "render/videoparams.h"
#include "oakutil/oakvideo.h"
/**
* @file vieweroutpututils.h
@@ -40,23 +39,27 @@
* symbols in oak-editor. These helpers fetch the same values through the
* oakengine C ABI instead. `viewer` may be any viewer handle (ViewerOutput,
* Sequence, Footage).
*
* Video parameters are returned as oak::VideoParams (oakutil/oakvideo.h),
* the app-side value type replacing the engine's olive::VideoParams.
*/
namespace olive {
VideoParams viewer_output_video_params(const void *viewer, int index = 0);
// Same namespace bridge the engine's render/videoparams.h used to provide
// (unqualified Rational/AudioParams/SampleFormat inside namespace olive).
using namespace core;
oak::VideoParams viewer_output_video_params(const void *viewer, int index = 0);
AudioParams viewer_output_audio_params(const void *viewer, int index = 0);
/**
* @brief Construct a VideoParams from an oak_video_params POD using the engine
* facade. This is the single app-side construction point allowed during the R6
* C ABI migration; all other app code must not call VideoParams constructors
* directly.
* @brief Construct an oak::VideoParams from an oak_video_params POD.
*/
VideoParams video_params_from_pod(const oak_video_params &pod);
oak::VideoParams video_params_from_pod(const oak_video_params &pod);
/** @brief Equivalent to the default-constructed VideoParams(). */
VideoParams empty_video_params();
/** @brief Equivalent to the default-constructed oak::VideoParams(). */
oak::VideoParams empty_video_params();
/**
* @brief Frame-duration timebase of `sequence` as a Rational (frame rate flipped).
@@ -67,6 +70,22 @@ VideoParams empty_video_params();
*/
Rational sequence_timebase(const void *sequence);
/** @brief Facade-backed replacement for ViewerOutput::get_playhead()
* (rational seconds). */
Rational viewer_output_playhead(const void *viewer);
/** @brief Facade-backed replacement for ViewerOutput::get_length()
* (rational seconds). */
Rational viewer_output_length(const void *viewer);
/** @brief Facade-backed replacement for ViewerOutput::get_video_length()
* (rational seconds). */
Rational viewer_output_video_length(const void *viewer);
/** @brief Facade-backed replacement for ViewerOutput::get_audio_length()
* (rational seconds). */
Rational viewer_output_audio_length(const void *viewer);
/**
* @brief 1 if `node`'s engine type id (Node::id()) equals `type_id`.
* Facade-backed replacement for dynamic_cast-based type probes (a
+1 -1
View File
@@ -44,7 +44,7 @@ ViewerDisplayWidget *ViewerWindow::display_widget() const
return display_widget_;
}
void ViewerWindow::set_video_params(const VideoParams &params)
void ViewerWindow::set_video_params(const oak::VideoParams &params)
{
width_ = params.width();
height_ = params.height();
+1 -1
View File
@@ -41,7 +41,7 @@ public:
* Equivalent to calling SetResolution and SetPixelAspectRatio, just slightly faster since we
* only calculate the matrix once rather than twice.
*/
void set_video_params(const VideoParams &params);
void set_video_params(const oak::VideoParams &params);
/**
* @brief Used to adjust resulting picture to be the right aspect ratio