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:
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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().
|
||||
|
||||
@@ -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--;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 ¶ms)
|
||||
void ViewerDisplayWidget::set_video_params(const oak::VideoParams ¶ms)
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -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 ¶ms);
|
||||
void set_video_params(const oak::VideoParams ¶ms);
|
||||
|
||||
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_;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -44,7 +44,7 @@ ViewerDisplayWidget *ViewerWindow::display_widget() const
|
||||
return display_widget_;
|
||||
}
|
||||
|
||||
void ViewerWindow::set_video_params(const VideoParams ¶ms)
|
||||
void ViewerWindow::set_video_params(const oak::VideoParams ¶ms)
|
||||
{
|
||||
width_ = params.width();
|
||||
height_ = params.height();
|
||||
|
||||
@@ -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 ¶ms);
|
||||
void set_video_params(const oak::VideoParams ¶ms);
|
||||
|
||||
/**
|
||||
* @brief Used to adjust resulting picture to be the right aspect ratio
|
||||
|
||||
Reference in New Issue
Block a user