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
+56 -31
View File
@@ -33,13 +33,11 @@
#include "oakutil/digit.h"
#include "oakutil/qtutils.h"
#include "codec/exportcodec.h"
#include "codec/exportformat.h"
#include "oakutil/oaknode.h"
#include "dialog/msgbox.h"
#include "dialog/task/task.h"
#include "exportsavepresetdialog.h"
#include "node/project.h"
#include "node/project/sequence/sequence.h"
#include "oakengine/color.h"
#include "oakengine/events.h"
#include "widget/manageddisplay/colorprocessorhandle.h"
#include "widget/viewer/vieweroutpututils.h"
@@ -47,6 +45,7 @@
#include "oakengine/project.h"
#include "oakengine/task.h"
#include "oakengine/encoding.h"
#include "oakengine/timeline.h"
#include "oakengine/viewer.h"
#include "ui/icons/icons.h"
#include "widget/timeruler/timeruler.h"
@@ -70,6 +69,22 @@ int pix_fmt_index(int codec, const QString &pix_fmt)
return oakengine_encoding_pix_fmt_index(codec, pix_fmt.toUtf8().constData());
}
// ViewerOutput::get_work_area() snapshot through the C ABI.
oakengine_viewer_workarea export_viewer_workarea(const OakEngineNode *viewer)
{
oakengine_viewer_workarea wa = {};
oakengine_viewer_get_workarea(viewer, &wa);
return wa;
}
// ViewerOutput::get_length() as a Rational through the C ABI.
Rational export_viewer_length(const OakEngineNode *viewer)
{
int64_t num = 0, den = 1;
oakengine_viewer_get_length(viewer, &num, &den);
return Rational(num, den);
}
// OakEngineEncodingParams (assembled by the dialog) -> facade POD. One-to-one with
// oak_export_options_ex; see oakengine/exporter.h for the field docs.
oak_export_options_ex params_to_ex(const OakEngineEncodingParams *p)
@@ -182,7 +197,7 @@ oak_export_options_ex params_to_ex(const OakEngineEncodingParams *p)
} // namespace
ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode,
ExportDialog::ExportDialog(OakEngineNode *viewer_node, bool stills_only_mode,
QWidget *parent)
: super(parent)
, viewer_node_(viewer_node)
@@ -253,7 +268,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode,
range_combobox_ = new QComboBox();
range_combobox_->addItem(tr("Entire Sequence"));
range_combobox_->addItem(tr("In to Out"));
range_combobox_->setEnabled(viewer_node_->get_work_area()->enabled());
range_combobox_->setEnabled(export_viewer_workarea(viewer_node_).enabled);
preferences_layout->addWidget(range_combobox_, row, 1, 1, 3);
@@ -287,13 +302,14 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode,
preferences_tabs_ = new QTabWidget();
color_manager_ = oak_color_manager(viewer_node_->project()->color_manager());
color_manager_ = oakengine_color_manager_from_project(
oakengine_node_get_project(viewer_node_));
video_tab_ = new ExportVideoTab(color_manager_);
add_preferences_tab(video_tab_, tr("Video"));
// Set video tab time and make connections
viewer_sub_ = oakengine_event_subscribe(
reinterpret_cast<OakEngineNode *>(viewer_node),
viewer_node,
OAKENGINE_EVENT_VIEWER_PLAYHEAD_CHANGED,
[](const oakengine_event *event, void *userdata) {
auto *dlg = static_cast<ExportDialog *>(userdata);
@@ -304,13 +320,12 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode,
connect(video_tab_, &ExportVideoTab::time_changed, this,
[viewer_node](const Rational &time) {
oakengine_viewer_set_playhead(
reinterpret_cast<OakEngineNode *>(viewer_node),
viewer_node,
time.numerator(), time.denominator());
});
{
int64_t pn, pd;
oakengine_viewer_get_playhead(
reinterpret_cast<OakEngineNode *>(viewer_node), &pn, &pd);
oakengine_viewer_get_playhead(viewer_node, &pn, &pd);
video_tab_->set_time(Rational(pn, pd));
}
@@ -389,7 +404,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode,
connect(format_combobox_, &ExportFormatComboBox::format_changed, this,
&ExportDialog::format_changed);
VideoParams vp = viewer_output_video_params(viewer_node_);
oak::VideoParams vp = viewer_output_video_params(viewer_node_);
video_aspect_ratio_ =
static_cast<double>(vp.width()) / static_cast<double>(vp.height());
@@ -408,7 +423,7 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, bool stills_only_mode,
&ExportDialog::resolution_changed);
connect(video_tab_, &ExportVideoTab::color_space_changed, preview_viewer_,
static_cast<void (ViewerWidget::*)(const ColorTransform &)>(
static_cast<void (ViewerWidget::*)(const oak::ColorTransform &)>(
&ViewerWidget::set_color_transform));
connect(video_tab_, &ExportVideoTab::image_sequence_check_box_changed, this,
&ExportDialog::image_sequence_check_box_changed);
@@ -800,12 +815,10 @@ void ExportDialog::load_presets()
void ExportDialog::set_default_filename()
{
Project *p = viewer_node_->project();
OakEngineProject *p = oakengine_node_get_project(viewer_node_);
char fn_buf[512];
oakengine_project_filename(
reinterpret_cast<OakEngineProject *>(p),
fn_buf, sizeof(fn_buf));
oakengine_project_filename(p, fn_buf, sizeof(fn_buf));
QDir doc_location;
if (fn_buf[0] == '\0') {
@@ -815,16 +828,25 @@ void ExportDialog::set_default_filename()
doc_location = QFileInfo(fn_buf).dir();
}
QString file_location = doc_location.filePath(viewer_node_->get_label());
QString file_location = doc_location.filePath(
oak::Node(viewer_node_).get_label());
filename_edit_->setText(file_location);
}
bool ExportDialog::sequence_has_subtitles() const
{
if (Sequence *s = dynamic_cast<Sequence *>(viewer_node_)) {
TrackList *tl = s->track_list(Track::k_subtitle);
for (Track *t : tl->get_tracks()) {
if (!t->is_muted() && !t->blocks().empty()) {
OakEngineNode *node = viewer_node_;
if (oakengine_node_is_sequence(node)) {
OakEngineSequence *seq = reinterpret_cast<OakEngineSequence *>(node);
int track_count = 0;
oakengine_sequence_track_count(seq, nullptr, nullptr, &track_count);
for (int i = 0; i < track_count; i++) {
OakEngineTrack *t = oakengine_sequence_track_at(
seq, OAKENGINE_TRACK_TYPE_SUBTITLE, i);
if (t &&
!oakengine_track_is_muted(seq, OAKENGINE_TRACK_TYPE_SUBTITLE,
i) &&
oakengine_track_block_count(t) > 0) {
return true;
}
}
@@ -842,7 +864,7 @@ void ExportDialog::set_defaults()
}
format_changed(format_combobox_->get_format());
VideoParams vp = viewer_output_video_params(viewer_node_);
oak::VideoParams vp = viewer_output_video_params(viewer_node_);
AudioParams ap = viewer_output_audio_params(viewer_node_);
video_tab_->width_slider()->set_value(vp.width());
@@ -873,7 +895,7 @@ OakEngineEncodingParams *ExportDialog::generate_params() const
oakengine_encoding_params_set_filename(
params, filename_edit_->text().trimmed().toUtf8().constData());
const Rational export_len = viewer_node_->get_length();
const Rational export_len = export_viewer_length(viewer_node_);
oakengine_encoding_params_set_export_length(
params, export_len.numerator(), export_len.denominator());
@@ -887,10 +909,10 @@ OakEngineEncodingParams *ExportDialog::generate_params() const
(export_time + tb).numerator(),
(export_time + tb).denominator());
} else if (range_combobox_->currentIndex() == k_range_in_to_out) {
const TimeRange &r = viewer_node_->get_work_area()->range();
const oakengine_viewer_workarea wa =
export_viewer_workarea(viewer_node_);
oakengine_encoding_params_set_custom_range(
params, r.in().numerator(), r.in().denominator(),
r.out().numerator(), r.out().denominator());
params, wa.in_num, wa.in_den, wa.out_num, wa.out_den);
}
if (video_tab_->scaling_method_combobox()->isEnabled()) {
@@ -985,7 +1007,7 @@ void ExportDialog::set_params(const OakEngineEncodingParams *e)
format_changed(format_combobox_->get_format());
if (oakengine_encoding_params_has_custom_range(e) &&
viewer_node_->get_work_area()->enabled()) {
export_viewer_workarea(viewer_node_).enabled) {
range_combobox_->setCurrentIndex(k_range_in_to_out);
}
@@ -1110,9 +1132,12 @@ void ExportDialog::done(int r)
Rational ExportDialog::get_export_length() const
{
if (range_combobox_->currentIndex() == k_range_in_to_out) {
return viewer_node_->get_work_area()->range().length();
const oakengine_viewer_workarea wa =
export_viewer_workarea(viewer_node_);
return Rational(wa.out_num, wa.out_den) -
Rational(wa.in_num, wa.in_den);
} else {
return viewer_node_->get_length();
return export_viewer_length(viewer_node_);
}
}
@@ -1128,7 +1153,7 @@ void ExportDialog::update_viewer_dimensions()
static_cast<int>(video_tab_->width_slider()->get_value()),
static_cast<int>(video_tab_->height_slider()->get_value()));
VideoParams vp = viewer_output_video_params(viewer_node_);
oak::VideoParams vp = viewer_output_video_params(viewer_node_);
float mat16[16];
oakengine_encoding_generate_matrix(