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
+57 -58
View File
@@ -164,15 +164,15 @@ MainWindow::~MainWindow()
void MainWindow::load_layout(const SerializedLayoutInfo &info)
{
foreach (Folder *folder, info.open_folders) {
foreach (OakEngineNode *folder, info.open_folders) {
open_folder(folder, true);
}
foreach (Sequence *sequence, info.open_sequences) {
foreach (OakEngineNode *sequence, info.open_sequences) {
open_sequence(sequence, info.open_sequences.size() == 1);
}
foreach (ViewerOutput *viewer, info.open_viewers) {
foreach (OakEngineNode *viewer, info.open_viewers) {
open_node_in_viewer(viewer);
}
@@ -219,19 +219,21 @@ SerializedLayoutInfo MainWindow::save_layout() const
for (int i = 0; i < folder_panels_.size(); i++) {
auto panel = folder_panels_.at(i);
info.open_folders.push_back(panel->get_root());
info.open_folders.push_back(panel->get_root().handle());
correct_panel_data_if_necessary(panel->uniqueName(), i, info, layout);
}
for (int i = 0; i < timeline_panels_.size(); i++) {
auto panel = timeline_panels_.at(i);
info.open_sequences.push_back(panel->get_sequence());
info.open_sequences.push_back(
reinterpret_cast<OakEngineNode *>(panel->get_sequence()));
correct_panel_data_if_necessary(panel->uniqueName(), i, info, layout);
}
for (int i = 0; i < viewer_panels_.size(); i++) {
auto panel = viewer_panels_.at(i);
info.open_viewers.push_back(panel->get_connected_viewer());
info.open_viewers.push_back(
reinterpret_cast<OakEngineNode *>(panel->get_connected_viewer()));
correct_panel_data_if_necessary(panel->uniqueName(), i, info, layout);
}
@@ -240,11 +242,11 @@ SerializedLayoutInfo MainWindow::save_layout() const
return info;
}
TimelinePanel *MainWindow::open_sequence(Sequence *sequence, bool enable_focus)
TimelinePanel *MainWindow::open_sequence(OakEngineNode *sequence, bool enable_focus)
{
// See if this sequence is already open, and switch to it if so
foreach (TimelinePanel *tl, timeline_panels_) {
if (tl->get_connected_viewer() == sequence) {
if (reinterpret_cast<OakEngineNode *>(tl->get_connected_viewer()) == sequence) {
tl->raise();
return tl;
}
@@ -270,23 +272,23 @@ TimelinePanel *MainWindow::open_sequence(Sequence *sequence, bool enable_focus)
return panel;
}
void MainWindow::close_sequence(Sequence *sequence)
void MainWindow::close_sequence(OakEngineNode *sequence)
{
// We defer to RemoveTimelinePanel() to close the panels, which may delete and remove indices from timeline_panels_.
// We make a copy so that our array here doesn't get ruined by what RemoveTimelinePanel() does
QList<TimelinePanel *> copy = timeline_panels_;
foreach (TimelinePanel *tp, copy) {
if (tp->get_connected_viewer() == sequence) {
if (reinterpret_cast<OakEngineNode *>(tp->get_connected_viewer()) == sequence) {
remove_timeline_panel(tp);
}
}
}
bool MainWindow::is_sequence_open(Sequence *sequence) const
bool MainWindow::is_sequence_open(OakEngineNode *sequence) const
{
foreach (TimelinePanel *tp, timeline_panels_) {
if (tp->get_connected_viewer() == sequence) {
if (reinterpret_cast<OakEngineNode *>(tp->get_connected_viewer()) == sequence) {
return true;
}
}
@@ -294,13 +296,14 @@ bool MainWindow::is_sequence_open(Sequence *sequence) const
return false;
}
void MainWindow::open_folder(Folder *i, bool floating)
void MainWindow::open_folder(OakEngineNode *i, bool floating)
{
ProjectPanel *panel =
append_panel_internal(QStringLiteral("FolderPanel"), folder_panels_);
panel->set_project(i->project());
panel->set_root(i);
oak::Node folder(i);
panel->set_project(folder.project());
panel->set_root(folder);
if (floating) {
panel->setFloating(floating);
@@ -313,13 +316,13 @@ void MainWindow::open_folder(Folder *i, bool floating)
&MainWindow::folder_panel_close_requested);
}
void MainWindow::open_node_in_viewer(ViewerOutput *node)
void MainWindow::open_node_in_viewer(OakEngineNode *node)
{
ViewerPanel *existing = nullptr;
for (auto it = viewer_panels_.cbegin(); it != viewer_panels_.cend(); it++) {
ViewerPanel *it2 = (*it);
if (it2->get_connected_viewer() == node) {
if (reinterpret_cast<OakEngineNode *>(it2->get_connected_viewer()) == node) {
existing = it2;
break;
}
@@ -338,7 +341,7 @@ void MainWindow::open_node_in_viewer(ViewerOutput *node)
connect(viewer, &ViewerPanel::close_requested, this,
&MainWindow::viewer_close_requested);
auto sub = bridge_->subscribe(
reinterpret_cast<void *>(node),
node,
OAKENGINE_EVENT_NODE_REMOVED_FROM_GRAPH);
removed_from_graph_subs_[node] = sub;
}
@@ -405,7 +408,7 @@ void MainWindow::toggle_maximized_panel()
}
}
void MainWindow::set_project(Project *p)
void MainWindow::set_project(OakEngineProject *p)
{
if (project_ == p) {
return;
@@ -413,19 +416,22 @@ void MainWindow::set_project(Project *p)
if (project_) {
// Clear all data
param_panel_->set_contexts(QVector<Node *>());
node_panel_->set_contexts(QVector<Node *>());
param_panel_->set_contexts(QVector<oak::Node>());
node_panel_->set_contexts(QVector<oak::Node>());
// Close any nodes open in TimeBasedWidgets
foreach (PanelWidget *panel, PanelManager::instance()->panels()) {
TimeBasedPanel *tbp = dynamic_cast<TimeBasedPanel *>(panel);
if (tbp && tbp->get_connected_viewer() &&
tbp->get_connected_viewer()->project() == project_) {
oak::Node(reinterpret_cast<OakEngineNode *>(
tbp->get_connected_viewer()))
.project()
.handle() == project_) {
if (dynamic_cast<TimelinePanel *>(tbp)) {
// Prefer our CloseSequence function which will delete any unnecessary timeline panels
close_sequence(
static_cast<Sequence *>(tbp->get_connected_viewer()));
reinterpret_cast<OakEngineNode *>(tbp->get_connected_viewer()));
} else {
tbp->disconnect_viewer_node();
}
@@ -444,7 +450,7 @@ void MainWindow::set_project(Project *p)
}
project_ = p;
project_panel_->set_project(p);
project_panel_->set_project(oak::Project(p));
if (project_) {
project_panel_->setFocus(Qt::OtherFocusReason);
@@ -486,7 +492,7 @@ void MainWindow::set_application_progress_value(int value)
#endif
}
void MainWindow::select_footage(const QVector<Footage *> &e)
void MainWindow::select_footage(const QVector<OakEngineNode *> &e)
{
select_footage_for_project_panel(e, project_panel_);
for (ProjectPanel *p : folder_panels_) {
@@ -558,12 +564,7 @@ void MainWindow::timeline_panel_selection_changed(const QVector<OakEngineBlock *
if (PanelManager::instance()->currently_focused(false) == panel) {
update_node_panel_context_from_timeline_panel(panel);
QVector<Block *> native_blocks;
native_blocks.reserve(blocks.size());
for (OakEngineBlock *b : blocks) {
native_blocks.append(reinterpret_cast<Block *>(b));
}
sequence_viewer_panel_->set_timeline_selected_blocks(native_blocks);
sequence_viewer_panel_->set_timeline_selected_blocks(blocks);
}
}
@@ -580,9 +581,9 @@ void MainWindow::reveal_viewer_in_project(OakEngineNode *r)
// Rather than just using the resident ProjectPanel, find the most recently focused one since
// that's probably the one people will want
auto panels = PanelManager::instance()->get_panels_of_type<ProjectPanel>();
ViewerOutput *viewer = reinterpret_cast<ViewerOutput *>(r);
oak::Node node(r);
foreach (ProjectPanel *p, panels) {
if (p->select_item(viewer)) {
if (p->select_item(node)) {
break;
}
}
@@ -591,20 +592,18 @@ void MainWindow::reveal_viewer_in_project(OakEngineNode *r)
void MainWindow::reveal_viewer_in_footage_viewer(OakEngineNode *r,
const TimeRange &range)
{
ViewerOutput *viewer = reinterpret_cast<ViewerOutput *>(r);
footage_viewer_panel_->connect_viewer_node(viewer);
footage_viewer_panel_->connect_viewer_node(r);
auto command = oakengine_undo_command_create_multi();
OakEngineWorkarea *wa = reinterpret_cast<OakEngineWorkarea *>(viewer->get_work_area());
if (!viewer->get_work_area()->enabled()) {
OakEngineWorkarea *wa = oakengine_viewer_get_workarea_handle(r);
int64_t old_in_num, old_in_den, old_out_num, old_out_den;
int old_enabled;
oakengine_workarea_get(wa, &old_in_num, &old_in_den,
&old_out_num, &old_out_den, &old_enabled);
if (!old_enabled) {
oakengine_workarea_set_enabled_undoable(wa, 1, command);
}
{
int64_t old_in_num, old_in_den, old_out_num, old_out_den;
int old_enabled;
oakengine_workarea_get(wa, &old_in_num, &old_in_den,
&old_out_num, &old_out_den, &old_enabled);
oakengine_workarea_set_range_undoable(wa,
range.in().numerator(), range.in().denominator(),
range.out().numerator(), range.out().denominator(),
@@ -669,11 +668,10 @@ void MainWindow::viewer_close_requested()
void MainWindow::viewer_with_panel_removed_from_graph(OakEngineNode *source)
{
ViewerOutput *vo = reinterpret_cast<ViewerOutput *>(source);
ViewerPanel *panel = nullptr;
foreach (ViewerPanel *p, viewer_panels_) {
if (p->get_connected_viewer() == vo) {
if (p->get_connected_viewer() == source) {
panel = p;
break;
}
@@ -682,7 +680,7 @@ void MainWindow::viewer_with_panel_removed_from_graph(OakEngineNode *source)
if (panel) {
remove_panel_internal(viewer_panels_, panel);
panel->deleteLater();
auto it = removed_from_graph_subs_.find(vo);
auto it = removed_from_graph_subs_.find(source);
if (it != removed_from_graph_subs_.end()) {
bridge_->unsubscribe(it.value());
removed_from_graph_subs_.erase(it);
@@ -741,7 +739,7 @@ void MainWindow::remove_timeline_panel(TimelinePanel *panel)
}
}
void MainWindow::timeline_focused(ViewerOutput *viewer)
void MainWindow::timeline_focused(OakEngineNode *viewer)
{
sequence_viewer_panel_->connect_viewer_node(viewer);
multicam_panel_->connect_viewer_node(viewer);
@@ -750,7 +748,7 @@ void MainWindow::timeline_focused(ViewerOutput *viewer)
// Update the global status bar info chips (resolution + frame rate)
if (viewer) {
VideoParams vp = viewer_output_video_params(viewer);
oak::VideoParams vp = viewer_output_video_params(viewer);
status_bar_->set_sequence_info(vp.width(), vp.height(),
vp.frame_rate().to_double());
} else {
@@ -861,7 +859,7 @@ void MainWindow::save_custom_shortcuts()
}
}
void MainWindow::update_audio_monitor_params(ViewerOutput *viewer)
void MainWindow::update_audio_monitor_params(OakEngineNode *viewer)
{
if (!audio_monitor_panel_->is_playing()) {
audio_monitor_panel_->set_params(viewer ? viewer_output_audio_params(viewer) :
@@ -872,29 +870,30 @@ void MainWindow::update_audio_monitor_params(ViewerOutput *viewer)
void MainWindow::update_node_panel_context_from_timeline_panel(TimelinePanel *panel)
{
// Add selected blocks (if any)
const QVector<Block *> &blocks = panel->get_selected_blocks();
QVector<Node *> context(blocks.size());
const QVector<OakEngineBlock *> blocks = panel->get_selected_blocks();
QVector<oak::Node> context(blocks.size());
for (int i = 0; i < blocks.size(); i++) {
context[i] = blocks.at(i);
context[i] = oak::Node(reinterpret_cast<OakEngineNode *>(blocks.at(i)));
}
// If no selected blocks, set the context to the sequence
ViewerOutput *viewer = panel->get_connected_viewer();
OakEngineNode *viewer = panel->get_connected_viewer();
if (viewer && context.isEmpty()) {
context.append(viewer);
context.append(oak::Node(viewer));
}
node_panel_->set_contexts(context);
param_panel_->set_contexts(context);
}
void MainWindow::select_footage_for_project_panel(const QVector<Footage *> &e,
void MainWindow::select_footage_for_project_panel(const QVector<OakEngineNode *> &e,
ProjectPanel *p)
{
p->deselect_all();
for (Footage *f : e) {
if (p->get_root()->has_child_recursive(f)) {
p->select_item(f, false);
for (OakEngineNode *f : e) {
oak::Node node(f);
if (p->get_root().has_child_recursive(node)) {
p->select_item(node, false);
}
}
}
@@ -908,7 +907,7 @@ void MainWindow::focused_panel_changed(PanelWidget *panel)
if (NodePanel *node_panel = dynamic_cast<NodePanel *>(panel)) {
// Set param view contexts to these
const QVector<Node *> &new_ctxs = node_panel->get_contexts();
const QVector<oak::Node> &new_ctxs = node_panel->get_contexts();
if (new_ctxs != param_panel_->get_contexts()) {
param_panel_->set_contexts(new_ctxs);