Files
oak-editor/engine/include/oakengine/project.h
T
Mike-Solar 66d761b4b7 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
2026-07-31 22:46:52 +08:00

345 lines
13 KiB
C

/***
Oak - Non-Linear Video Editor
Copyright (C) 2026 Oak Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef OAKENGINE_PROJECT_H
#define OAKENGINE_PROJECT_H
#include "export.h"
#include "init.h"
/* Forward declarations from node.h (included by callers in either order). */
typedef struct OakEngineNode OakEngineNode;
/* Forward declaration for playback cache from viewer.h. */
typedef struct OakEnginePlaybackCache OakEnginePlaybackCache;
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file project.h
* @brief C ABI for Oak project files (.ove)
*
* An OakEngineProject wraps the engine's olive::Project node graph. Projects
* are created empty with oakengine_project_create() and must then be given
* content exactly once: either oakengine_project_new() for a blank project or
* oakengine_project_load() to read a project file (the engine's project
* serializers require a fresh, uninitialized project, so loading into a
* project that already has content is rejected with OAKENGINE_E_STATE).
*
* Sequences are owned by their project through Qt's QObject parent chain;
* OakEngineSequence handles are borrowed views whose lifetime follows the
* project. See timeline.h for the sequence function family.
*
* Conventions (matching oakengine/ipc.h):
* - Booleans are int (1/0).
* - Return codes: 0 (OAKENGINE_OK) on success, negative OAKENGINE_E_* on
* failure.
* - String output uses the buf/size convention: the return value is the
* number of characters that would have been written excluding the NUL,
* so buf == NULL or a short buffer queries the required size. The output
* is NUL-terminated whenever buf_size > 0. A negative return value is an
* OAKENGINE_E_* error code.
* - NULL handles are accepted everywhere and yield a no-op / zero result /
* OAKENGINE_E_INVALID.
*
* Undo/redo note: the engine's undo stack is a process-wide singleton held by
* EngineCore (EngineCore::undo_stack()), not a per-project object. The
* project_undo/project_redo family therefore operates on that global stack
* and is not scoped to `self`; oakengine_project_new() and
* oakengine_project_load() clear it, matching the application's
* new/open-project behavior.
*/
/**
* @brief Opaque project handle. Owned by the caller; release with
* oakengine_project_free().
*/
typedef struct OakEngineProject OakEngineProject;
/**
* @brief Opaque sequence (timeline) handle.
*
* Typedef'd here so project.h and timeline.h can be included in any order.
* Handles are borrowed from their owning OakEngineProject (QObject parent
* chain) and must NOT be freed; they become invalid when the project is
* freed. The sequence function family lives in oakengine/timeline.h.
*/
typedef struct OakEngineSequence OakEngineSequence;
/**
* @brief Allocate an empty project shell. Owned by the caller.
*
* The project has no content until exactly one of oakengine_project_new() or
* oakengine_project_load() succeeds on it.
*/
OAKENGINE_API OakEngineProject *oakengine_project_create(void);
/**
* @brief Destroy a project and everything it owns (including sequences).
* NULL-safe.
*/
OAKENGINE_API void oakengine_project_free(OakEngineProject *self);
/**
* @brief Initialize `self` as a new, blank project (untitled, not modified).
*
* Clears the global undo stack. Returns OAKENGINE_E_STATE if the project
* already has content (new/load may only be applied once).
*/
OAKENGINE_API int oakengine_project_new(OakEngineProject *self);
/**
* @brief Load project content from a .ove file.
*
* On success the project's filename is set to `path` and the modified flag is
* cleared; the global undo stack is cleared. Footage is validated like the
* engine does (EngineCore::validate_footage_in_loaded_project): files that
* moved together with the project are resolved relatively; missing footage is
* tolerated (no relink UI exists at this layer, so the project is accepted
* as-is). On failure a human-readable reason is written to `err` using the
* buf/size convention (err may be NULL) and a negative code is returned.
*/
OAKENGINE_API int oakengine_project_load(OakEngineProject *self,
const char *path, char *err,
int err_size);
/**
* @brief Save the project to `path`.
*
* `path` == NULL saves under the project's current filename
* (Project::filename()); saving an untitled project without a path fails with
* OAKENGINE_E_INVALID. Files are gzip-compressed unless the target name ends
* in ".ovexml" (mirrors the application behavior). On success the project's
* filename is set to the target and the modified flag is cleared
* (EngineCore::on_project_saved() semantics, minus the UI recent-list).
*/
OAKENGINE_API int oakengine_project_save(OakEngineProject *self,
const char *path);
OAKENGINE_API int oakengine_project_is_modified(const OakEngineProject *self);
/**
* @brief Set the project modified flag (Project::set_modified).
*/
OAKENGINE_API int oakengine_project_set_modified(OakEngineProject *self,
int modified);
/**
* @brief Project display name (Project::name(): the filename's base name, or
* "(untitled)"). Uses the buf/size convention.
*/
OAKENGINE_API int oakengine_project_name(const OakEngineProject *self,
char *buf, int buf_size);
/**
* @brief Full path the project was loaded from / saved to, or "" if untitled
* (Project::filename()). Uses the buf/size convention.
*/
OAKENGINE_API int oakengine_project_filename(const OakEngineProject *self,
char *buf, int buf_size);
/**
* @brief Number of footage items in the project.
*/
OAKENGINE_API int oakengine_project_footage_count(const OakEngineProject *self);
/**
* @brief Stored filename of the footage item at `index` (Footage::filename(),
* as recorded in the project file; may be relative). Uses the buf/size
* convention; returns OAKENGINE_E_NOT_FOUND for an out-of-range index.
*/
OAKENGINE_API int oakengine_project_footage_filename(
const OakEngineProject *self, int index, char *buf, int buf_size);
/**
* @brief 1 if the footage file at `index` exists on disk, 0 if not.
*
* Mirrors the engine's validation rule: the stored path is checked as-is
* first, then resolved relative to the project file's directory (footage
* that moved together with the project stays online). Returns
* OAKENGINE_E_NOT_FOUND for an out-of-range index.
*/
OAKENGINE_API int
oakengine_project_footage_is_online(const OakEngineProject *self, int index);
/* ---- Undo (global singleton stack, see the file comment above) ---------- */
OAKENGINE_API int oakengine_project_can_undo(const OakEngineProject *self);
OAKENGINE_API int oakengine_project_can_redo(const OakEngineProject *self);
OAKENGINE_API int oakengine_project_undo(OakEngineProject *self);
OAKENGINE_API int oakengine_project_redo(OakEngineProject *self);
/**
* @brief Number of sequences (timelines) in the project.
*/
OAKENGINE_API int
oakengine_project_sequence_count(const OakEngineProject *self);
/**
* @brief Borrowed handle of the sequence at `index`, or NULL when out of
* range. Do NOT free; the sequence is owned by the project.
*/
OAKENGINE_API OakEngineSequence *
oakengine_project_sequence_at(const OakEngineProject *self, int index);
/* ---- Folder operations ---------------------------------------------------- */
/**
* @brief Create a folder node named `name` under `parent` in `project`.
* Returns a borrowed handle, or NULL on failure.
*/
OAKENGINE_API OakEngineNode *oakengine_folder_create(OakEngineProject *project,
OakEngineNode *parent,
const char *name);
/**
* @brief 1 if `folder` recursively contains `child`, 0 otherwise.
* 0 when either handle is NULL or `folder` is not a Folder.
*/
OAKENGINE_API int oakengine_folder_has_child_recursive(
const OakEngineNode *folder, const OakEngineNode *child);
/**
* @brief Index of `child` in `folder`'s direct children, or
* OAKENGINE_E_NOT_FOUND. Returns OAKENGINE_E_INVALID when
* `folder` is not a Folder node.
*/
OAKENGINE_API int oakengine_folder_index_of_child(
const OakEngineNode *folder, const OakEngineNode *child);
/**
* @brief Number of direct item children of a folder
* (Folder::item_child_count()). 0 when `folder` is NULL or not a Folder.
*/
OAKENGINE_API int oakengine_folder_item_child_count(
const OakEngineNode *folder);
/**
* @brief Borrowed handle of the item child at `index`
* (Folder::item_child()). NULL when out of range or `folder` is not a
* Folder.
*/
OAKENGINE_API OakEngineNode *oakengine_folder_item_child(
const OakEngineNode *folder, int index);
/**
* @brief Static input key string for Folder children (Folder::k_child_input).
* Never freed.
*/
OAKENGINE_API const char *oakengine_folder_child_input_key(void);
/**
* @brief Add `child` to `folder` (undoable). OAKENGINE_E_INVALID when
* `folder` is not a Folder node or on NULL args.
*/
OAKENGINE_API int oakengine_folder_add_child(OakEngineNode *folder,
OakEngineNode *child);
/**
* @brief Move `node` from its current folder to `new_folder` (undoable).
* Removes the node from its old folder first — a true move, not a copy.
* Returns OAKENGINE_OK or a negative error code.
*/
OAKENGINE_API int oakengine_folder_move_child(OakEngineNode *node,
OakEngineNode *new_folder);
/**
* @brief Create a Folder::RemoveElementCommand as an opaque command pointer.
* Returns NULL on invalid arguments.
*/
OAKENGINE_API void *oakengine_folder_remove_element_command(
OakEngineNode *folder, OakEngineNode *child);
/**
* @brief Move several nodes into `dest_folder` as ONE undoable command
* (each node is removed from its old folder, then added to `dest_folder`).
* Nodes already directly inside `dest_folder` are skipped. `undo_name`
* may be NULL. Returns OAKENGINE_OK or a negative error code.
*/
OAKENGINE_API int oakengine_folder_move_children(
OakEngineNode *const *nodes, int count, OakEngineNode *dest_folder,
const char *undo_name);
/* ---- Project extras ------------------------------------------------------- */
/** @brief Root folder node of the project (Project::root()). */
OAKENGINE_API OakEngineNode *oakengine_project_root(OakEngineProject *self);
/** @brief Display name for the project that is safe for window titles
* (Project::pretty_filename()). buf/size convention. */
OAKENGINE_API int oakengine_project_pretty_filename(const OakEngineProject *self,
char *buf, int buf_size);
/** @brief Set the project's filename (Project::set_filename()).
* Returns OAKENGINE_OK or OAKENGINE_E_INVALID on NULL. */
OAKENGINE_API int oakengine_project_set_filename(OakEngineProject *self,
const char *path);
/** @brief The project's default cache directory (Project::cache_path()).
* buf/size convention. */
OAKENGINE_API int oakengine_project_cache_path(const OakEngineProject *self,
char *buf, int buf_size);
/** @brief The project's alongside cache directory
* (Project::cache_alongside_path()). buf/size convention. */
OAKENGINE_API int oakengine_project_cache_alongside_path(
const OakEngineProject *self, char *buf, int buf_size);
/** @brief Set a custom cache directory path (Project::set_custom_cache_path()).
* NULL clears it. */
OAKENGINE_API int oakengine_project_set_custom_cache_path(
OakEngineProject *self, const char *path);
/** @brief Get the custom cache directory path, or "" when none is set.
* buf/size convention; returns 0 when no custom path is set. */
OAKENGINE_API int oakengine_project_get_custom_cache_path(
const OakEngineProject *self, char *buf, int buf_size);
/** @brief Cache location setting enum value
* (Project::get_cache_location_setting()). Returns < 0 on NULL. */
OAKENGINE_API int oakengine_project_get_cache_location_setting(
const OakEngineProject *self);
/** @brief Static MIME type string for project items (Project::item_mime_type()).
* Never freed. */
OAKENGINE_API const char *oakengine_project_item_mime_type(void);
/** @brief Resolve a project node to its owning OakEngineProject
* (Project::get_project_from_object()). Returns NULL when the node is
* not part of a project or on NULL input. */
OAKENGINE_API OakEngineProject *
oakengine_project_from_object(const OakEngineNode *node);
/** @brief Get the project's color reference space name (buf/size). */
OAKENGINE_API int oakengine_project_get_color_reference_space(
const OakEngineProject *self, char *buf, int buf_size);
/** @brief Set the project's color reference space (undoable). */
OAKENGINE_API int oakengine_project_set_color_reference_space(
OakEngineProject *self, const char *colorspace);
#ifdef __cplusplus
}
#endif
#endif /* OAKENGINE_PROJECT_H */