- new facade API: video stream overrides (colorspace/range/interlacing/
premultiply), pixel aspect, image-sequence params, stream enable,
source start time, and colorspace candidates - all undoable
- project explorer proxy actions now run through FacadeProxyTask and
the facade media-management functions (ProxyManager references in
projectexplorer.cpp drop from 5 call sites to a comment)
- footage properties dialog reads/writes through the facade; its two
app-side undo command classes are gone
- handle-model fix: the footage handle is a heap state object, not a
plain pointer cast - oakengine_footage_borrow() wraps app-held
Footage nodes correctly (nine UB reinterpret_casts caught by the
DialogFootageProperties tests)
- oakengine_export_render_ex covers the dialog's entire option surface
(formats, codecs, pix fmts, audio params, ranges incl. still frame,
subtitles, scaling, threads, custom OCIO color transform names,
per-codec key/value options) - zero feature reduction
- the dialog's Start now creates a FacadeExportTask that drives
oakengine_export_render_ex instead of constructing ExportTask in the
UI; progress flows through the facade callback and cancel through
oakengine_export_cancel (OAKENGINE_E_CANCELLED preserves the
keep-dialog-open semantics)
- two real fixes: audio sample format 0 no longer means an
AAC-unsupported u8_p (default is f32_p), and image-sequence exports
use the engine's real [#####] placeholder instead of a made-up -%04d
Drives ConformManager for every audio-streaming footage with the same
AudioParams the export will use, before starting ExportTask - matching
the app's preview prewarm semantics. Root-cause analysis showed the
export's own wait-for-conform path already worked; this remains as a
first-export speedup and a belt-and-braces guarantee
- clip-level loop mode (off/loop/clamp), undoable
- per-channel audio levels as one-frame RMS at a timestamp
- waveform min/max buckets over a footage range, rendered on demand
- two real fixes uncovered by this family: conform completion signals
were starved by msleep-only waits (audio renders always came back
empty), and incomplete tickets from conform-pending renders are now
retried until the conform is ready
- relink through Footage::set_filename with the engine's natural
clear+reprobe cascade; find_offline_footage recovers offline media
by exact filename match under a search directory
- synchronous proxy generate (event-loop driven like the export
family), state query, enable/disable, path, and delete, mirroring
the app's proxy dialog assembly
- documented as non-undoable, matching the app's existing semantics
- undoable track removal (content restored on undo) and true-move
track reordering assembled from the edge commands (the engine has
no move-track API)
- track height/mute/lock getters and setters, documented as
non-undoable to match the engine's current semantics
- undoable marker add/remove/rename; duplicate timestamps are
rejected with E_STATE instead of hitting the engine's debug assert
- keyframed state, count, read (time in sequence timebase + mapped
values), add/remove, easing read/write (linear/bezier/hold with
control points), and clear - all undoable with full undo/redo
assertions
- easing set commands are minimal capi-local UndoCommands matching
the app-layer semantics (engine has none of its own); same-time
duplicates are rejected with E_STATE instead of hitting the engine's
debug assert
- project node enumeration, type/name/label metadata, input
introspection (id, mapped value type, connection state)
- typed parameter read/write for the eight common NodeValue kinds
(int/float/bool/rational/color/vec2-4/combo/string), undoable via
NodeParamSetSplitStandardValueCommand - the split-track value path,
chosen after proving the standard-value command stores whole
variants into a single component track
- graph operations (add/remove/connect/disconnect) reusing the
engine's undo commands, all covered by undo/redo assertions
All four are undoable and reuse the timeline/ command classes verbatim
(BlockSplitCommand, TrackRippleRemoveAreaCommand, BlockTrimCommand,
ReplaceBlockWithGap+PlaceBlock), with undo/redo assertions covering
split halves, media-in alignment on trim, ripple shift amounts, and
full restoration on undo
- oakengine_export_render drives ExportTask synchronously (offline
render + encode) with a progress callback, codec probing, and a
thread-local error channel; exporter.h keeps clear of the visibility
macro header
- oak-cli transcode now defaults to mp4 (H.264/AAC) with --format ppm
keeping the raw output path
- two real concurrency bugs found by the facade's own test: ExportTask
deadlocks when start()ed synchronously (queued conform handshake
needs an event loop), and the progress callback must be captured by
value because it fires on the task thread
- oakengine_sequence_add_track and add_footage_clip are the facade's
first editing primitives: undoable track creation and clip placement
with full range validation, clip enumeration, and gap filtering
- oak-cli transcode closes the loop: media file -> import -> clip ->
render, producing scaled PPM frames and a WAV from just the C ABI
- engine fix uncovered by transcode: the render worker used an invalid
empty AudioParams for IPC render frames, crashing any sequence that
contains audio; it now derives them from the rendered node itself
- sequence_new hardens its defaults against missing audio config keys
- oakengine_footage_probe inspects media without a project (decoder
probe): stream counts, per-stream video info (dimensions, rate,
duration, color tags, interlacing), audio info, duration, decoder
name, source start time; thread_local last_error for the NULL-handle
failure paths
- oakengine_project_import_footage adds probed footage to a project
through the same undoable command path as the app
- oak-cli probe prints decoder/duration/per-stream details and runs as
a ctest everywhere (no GL)
- dual ownership documented: probe handles are owned, imported footage
is borrowed from its project
- oakengine_sequence_get_video_params (width/height/pixel aspect) with
assertions for the default 1920x1080 square-pixel sequence and the
invalid-handle path
- oak-cli now renders at the sequence's real dimensions instead of the
hardcoded 1920x1080
- oak-cli info prints project/sequence/footage details; oak-cli render
writes PPM frames and a PCM WAV through the facade only - no engine
C++ headers, no Qt headers, links just liboakengine
- exit code 2 marks rendering-unavailable so ctest can skip cleanly on
machines without a GL render backend; info test runs everywhere
- facade fix uncovered by the CLI: project load now absolutizes the
path, so relative footage paths can't be mistaken for a moved
project; the fixture project now carries probed footage wired into
its sequence so it renders real content
- packaged like the other binaries (Linux bin install, macOS bundle,
Windows DLL copies); DESTDIR-verified
- oakengine_renderer_create/set_mode/last_error, render_frame (sync,
60s timeout, CPU frames via the existing worker pool),
render_audio (planar float), cancel; frames and audio buffers are
owned handles with borrowed data pointers
- output colorspace names map to OCIO display transforms, with
graceful fallback to reference-space output
- oakengine_renderer_test: parameter validation and error paths need
no GL and always run; render assertions gate on
DynamicRenderer backend availability and SKIP cleanly otherwise
- fix a facade bug found by its own test: wait_for_ticket leaked a
connected lambda capturing a stack reference, which a subsequent
cancelled ticket could fire into reused stack memory
- oakengine/export.h establishes the OAKENGINE_API visibility macros;
include/oakengine/ipc.h is the first pure-C surface (41 functions:
shm, frame slot pool, and the worker IPC messages as POD<->JSON
build/parse), implemented in engine/src/capi/
- the IPC implementations move to engine/src/oliveimpl (namespace
olive::engine::internal::ipc); engine/render/ipc/*.h are rebuilt as
same-name/same-API wrapper classes forwarding across the C boundary
- FrameSlotMeta is shared with the C header verbatim so the app/worker
wire format (v1) is bit-identical; static_asserts pin sizeof and
field offsets
- spscringbuffer.h moves to include/oakengine/ as an inline-only
header (no symbols, not ABI)
- new pure-C test oakengine_ipc_test (make_oakengine_test, no GL)
covers shm, frame pool, message round-trips and the layout asserts;
full gtest suite stays green (1986 tests)
Physical split: app/{audio,cli,codec,common,config,node,pluginSupport,
render,task,timeline,undo,tool,shaders} plus coreengine, version and
ui/icons+colorcoding move to a new top-level engine/ tree, built as
liboakengine.so (shared). The render backends (oakgl/oakvulkan) move
with it and link the engine library instead of embedding a static
render-core subset (libolive-rendercore is gone).
- oak-render-worker now links liboakengine instead of the whole
libolive-editor object set: 336MB -> 2.9MB, no Qt Widgets UI
- the editor links liboakengine for the engine and keeps only UI
objects in libolive-editor
- install/packaging: GNUInstallDirs libdir on Linux, bundle copy on
macOS, oakengine.dll staged for NSIS, AppImage validation entry
- fix backend lookup for the new layout: DynamicRenderer searched
../app but backends now live in engine/; a stale pre-split liboakgl
in the build tree got dlopened instead, re-initialized and later
destroyed the interposed engine statics (full-suite segfault at
DialogSequenceParameterTab, found via gdb watchpoint)
- NodeFactory's menu creation moves to UI-side widget/menu/factorymenu
(the factory only exposes its node library read-only now)
- DiskManager's cache-settings dialog is created through a registered
std::function handler (registered by Core at startup)
- OlivePluginInstance creates progress UIs through a
PluginProgressReporter interface (Null fallback headless) and queries
the active viewer through a provider callback, both registered by Core
- factory.h, diskmanager and pluginSupport no longer reference any
widget//dialog//panel//window headers or classes
EngineCore (new app/coreengine.{h,cpp}) owns every engine-safe part of
the old Core singleton: CoreParams, lifecycle of the engine managers,
UndoStack, tool/snapping/timecode state, locale, autorecovery, recent
projects, footage filters, clipboard, project registry, type
declarations, and the proxy toggle. UI dependencies are inverted
through hooks instead: status-bar/cache-full signals and std::function
handlers for image-sequence confirmation, footage relink, OTIO import,
project save/close and layout load (same pattern as
Config::ErrorHandler).
Core (app/) now derives from EngineCore and keeps only UI behavior:
the main window, dialogs, panel heuristics, import/export flows and
project lifecycle presentation. Its public API is unchanged (all
inherited), and Core::instance() covariantly static_casts the engine
singleton. The render worker constructs EngineCore directly, making it
the first binary that no longer needs the UI side of Core.
~25 engine call sites move from core.h to coreengine.h; a dozen more
drop a vestigial core.h include (gaining direct includes for symbols
they were borrowing transitively). Full gtest suite green (1986 tests,
0 failures).
- slider DisplayType enums sink to node/sliderdisplaytype.h (canonical
engine home); FloatSlider/RationalSlider alias them for compatibility
- DropWithoutSequenceBehavior enum sinks to common/dropworkflowbehavior.h
- Config errors now go through a registered ErrorHandler hook instead of
QMessageBox with a MainWindow parent; the style default no longer
depends on the UI style manager
- MainWindowLayoutInfo moves to node/project/serializer/ and its panel
dependency is reduced to a plain std::map alias (PanelLayoutInfo),
breaking the engine -> PanelWidget -> KDDockWidgets chain
- ProjectImportErrorDialog moves from task/ to dialog/projectimport/
- remove confirmed-redundant UI includes and give project.h/import.h/
project.cpp the direct includes they were borrowing transitively
- project.h includes folder/sequence headers directly (it used both
types in its own API all along)
core was added with EXCLUDE_FROM_ALL, which also excluded its install
rules, so liboakcore.so never made it into packages. It now installs to
the platform libdir (/usr/lib on this machine, verified via DESTDIR).
Even statically linked runtime libraries must not leak symbols: the
linker version script now whitelists oakcore_* only. nm reports 188
oakcore_* exports and zero anything else.
liboakcore is now a shared library that exposes only a C ABI:
- every value class (Rational, TimeRange, Color, Bezier, AudioParams,
SampleBuffer) and the free-function groups (StringUtils, fraction
utils, Timecode) is wrapped in an opaque-handle C API under
core/include/olive/core/oakcore/ (init/copy/free + self-first
functions), implemented in core/src/capi/
- consumers keep the original C++ API unchanged through same-name
wrapper classes that hold the handle and forward across the C
boundary; original implementations moved to core/src/oliveimpl
(namespace olive::core::internal) and are hidden from export
- TimeRangeList/TimeRangeListFrameIterator are reimplemented inline
over the wrapper (iterators/containers don't cross C ABI)
- generic Value container stays internal (unused by consumers) and is
no longer part of the public umbrella header
- hidden visibility + OAKCORE_BUILD export macro; nm shows zero
olive::* symbols exported
- install into the platform's standard libdir (GNUInstallDirs);
Windows DLLs next to the executables, macOS into the app bundle
- TimelineWorkArea::in/out/length now return by value: the wrapped
TimeRange getters return values, and forwarding them through const
references dangled (found via RenderWorkerFootageTest crash)
- tests: 9 new pure C ABI test executables (oakcore_*_test) covering
every public C function; 4 stale legacy core tests removed (they
targeted a long-renamed API and were never built due to a malformed
option() that also kept OLIVECORE_BUILD_TESTS off)
- CI/CD: oakcore.dll staged for NSIS, liboakcore.so added to the
AppImage validation list, build-tree DLL copies on Windows
The load/save of the per-footage custom divider attribute was lost when
the proxy changes were re-applied during the three-way commit split;
ProxyManager.FootagePersistsCustomProxyParams caught it.
- oak-render-worker now builds from worker/ (own CMakeLists.txt) as a
peer of app/; RenderWorkerPool resolves the new build-tree location
- deduplicated the Linux install() rules for the worker
- worker-spawning tests resolve build/worker instead of build/app
- cd.yml: Windows staging copies the worker from its new output path
- media color primaries/transfer tags now flow from the FFmpeg probe
through VideoParams into Footage::get_colorspace_to_use(); precedence
is user override > media tags > project default
- export nclc tags derive from the output colorspace (PQ/HLG/BT.2020,
P3, sRGB, Rec.601, Rec.709) instead of hardcoded BT.709
- new OCIO Color Grading (Log) node (lift/gamma/gain) and White Balance
node (kelvin temperature + tint, HDR-safe)
- LUT whitelist extended to 9 OCIO-supported formats
- waveform scope gains an RGB parade mode (GPU and software paths)
- tests updated for the new colorspace precedence
- playback timer uses the audio output device as its master clock: the
PortAudio callback counts consumed frames (including underrun
zero-fill) so video cannot drift away from what is heard; wall clock
remains as fallback when no clocked output is running
- output clock compensates for device output latency; new Preferences >
Audio buffer size setting (0 = auto)
- SampleBuffer::speed() now uses linear interpolation instead of
nearest-neighbor sampling
- regression tests: audio-clock driven timer (fwd/rev/speed), wall
clock fallback, interpolation correctness
- FootageJob::should_use_proxy() centralizes the proxy decision; worker
pre-decode now honors the render mode so exports always decode the
original media (previously every frame was pre-decoded from proxies)
- global Tools > Use Proxy Media toggle with footage invalidation
- ffmpeg -progress parsing for real percentage feedback while generating
- divider mode (1/2, 1/4, 1/8 of source resolution) with UI, proxy
filename tags and per-footage persistence (pdivider)
- Media Offline warning slat rendered for missing footage
- regression tests: export isolation, relink invalidation, offline slat,
progress parsing, divider arguments
Fake tests rewritten to assert real behavior:
- audio_smoke: conversion tests now actually Convert() samples and verify
output; waveform length/summary assertions tightened to exact values
- plugin_format_conversion: RowBytes/U8ToU16/LoadImageFile now call real
production code (VideoParams::GetBytesPerPixel, sws scaler, OIIO decode
of tests/img.png with known pixel values)
- core_color: HSV round trip now verifies fromHsv(toHsv(c)) == c instead
of comparing toHsv against its own accessors
- core_bezier/node_inputimmediate: expected values replaced with
independently derived constants instead of re-running the code under test
- common_commandlineparser/common_debug/common_jobtime: capture
stdout/stderr/qDebug and assert actual output content
- proxy_manager: ProxyFinished test now drives a real proxy job instead of
emitting the signal itself; proxy_dialog/panel/proxy/preferences/timeruler
tests assert real widget state
- viewer_smoke/preview_autocacher/render_misc: zero-assertion tests given
observable-state assertions or removed where nothing is observable
Duplicates removed:
- plugin_smoke_test.cpp: 18 tests duplicated from plugin_paraminstance /
plugin_support_* / plugin_renderer_readback (751 -> 180 lines)
- module_smoke HumanStrings tests covered precisely by ui_humanstrings_test
- render_misc duplicate kDefaultInterpolation constant check
Removed by policy (skip allowed, never disabled):
- all DISABLED_ prefixes: re-enabled as real offscreen tests or deleted
- ffmpeg_decoder_hw: hardcoded personal path replaced with
OAK_TEST_HW_DECODE_FILE env var, GTEST_SKIP when unset
Also:
- config_test: restore Config defaults after run (cross-test pollution)
- render_worker_footage: drop /tmp debug-output scaffolding
- previewaudiodevice construction test asserts the real bugfix
- OpenGLRenderer: hold the viewer-owned QOpenGLContext in a QPointer so
DestroyInternal() safely skips it when the context has already been
destroyed by Qt's shared-context lifecycle. Fixes a SIGSEGV when the
full gtest suite ran MainWindow.ConstructsOffscreenWithPanelsAndMenus
after earlier viewer tests.
- PreviewAudioDevice: add SetParams() deriving bytes_per_frame from the
audio format (bytes per sample * channel count) instead of staying 0.
Enzo GD, administrator of the Olive Facebook user group, gave this
project generous promotional support in its early days. The shared body
text now carries a special-thanks line in both the About dialog and the
first-run Welcome dialog; zh_CN translation included.
- Runners: ubuntu-24.04, macos-15, windows-latest (free tier) instead
of WarpBuild 8x/12x instances
- openfx-misc parallelism adapts to nproc (GitHub runners have fewer
vCPUs than the WarpBuild instances)
- Schedule a daily 03:17 UTC run so ccache entries never hit GitHub's
7-day cache expiry and system dependencies stay current
WarpBuild's cache service is billed; GitHub's cache is free (10 GB
repo limit with LRU eviction, ample for ccache entries). All three
cache sites — ccache for the matrix and dynamic-backend jobs, plus the
macOS OpenTimelineIO build — now use actions/cache@v4, which also
removes the Windows-specific fallback step.
Point CCACHE_DIR and the cache action at a fixed workspace directory
instead; checkout cleans untracked files before the restore, so the
ordering is safe.
- Restore ccache with WarpBuilds/cache@v1 on all three OS and the
dynamic-backend job (per-OS keys with branch fallback), and wire it
up through CMAKE_C/CXX_COMPILER_LAUNCHER; builds print ccache -s so
hit rates are visible in CI logs
- Cache the macOS OpenTimelineIO build (keyed on v0.16.0) and skip the
rebuild on cache hit
- Install ccache via apt/brew/MSYS2 as needed; CCACHE_MAXSIZE=1G
- Runner sizes 16x/32x -> 8x (macOS 12x kept); openfx-misc make -j8 to
match
The test assumed msleep(100) measures close to 100 ms (expecting ~2
frames at 24fps vs ~6 at 60fps). On the loaded macOS CI runner the
24fps leg overslept ~2.5x, producing 6 frames at both rates. Measure
the actual interval per leg and compare against the expected frame
count with a 1-frame tolerance, and use highly distinct timebases
(1fps vs 240fps) for the rate-ordering check.
- Linux CD builds no longer disable Vulkan: the AppImage, deb, rpm and
Arch packages now build and ship liboakvulkan (the AppImage deploys
it via linuxdeploy --library so libvulkan is bundled too, and the
verify step checks both backend libraries); deb/rpm dependencies gain
libvulkan1/vulkan-loader; the Arch PKGBUILD gains vulkan-headers
- macOS CD installs vulkan-loader and exports VULKAN_SDK so
find_package(Vulkan) locates the Homebrew loader (previously the
Vulkan backend silently never built there)
- ffmpeg_bridge now installs to the standard lib directory with
/../lib RPATH instead of the non-standard ffmpeg_bridge/bin
layout (verified: editor, worker, oakgl and oakvulkan all resolve it)
- build guides (EN/ZH) document the macOS Vulkan backend dependencies
and the VULKAN_SDK variable
Version is no longer hardcoded: at configure time CMake uses the tag
name when HEAD is exactly on a tag (leading "v" stripped), otherwise
the first 8 hex digits of the commit hash. When git is unavailable
(e.g. source tarball) it falls back to the contents of version.txt,
which is now the single place to bump the release version.
Also remove the temporary qDebug() flood in the audio playback path
(ViewerWidget::QueueNextAudioBuffer / ReceivedAudioBufferForPlayback,
AudioManager::PushToOutput).