Commit Graph
14 Commits
Author SHA1 Message Date
Mike-Solar 9003b78176 feat: built-in effect params, clip click-select, effect drag-and-drop, project load with plugins
- serializer resolves node types through the factory's dynamic
  (runtime-registered OpenFX) entries, so a project carrying plugin
  nodes loads again (was: "unknown node type"); covered by a new
  CI-gated round-trip test driving the real fixture plugin
- built-in effect nodes expose their inputs as inspector parameters
  like the C++ parameter editor: localized input names from the
  behavior, combo option tables via the new
  NodeBehavior::input_combo_strings (16 nodes, string-for-string from
  the C++ set_combo_box_strings), connection/data inputs excluded
- effect library: live drag-and-drop — onto the inspector's effect
  stack (lands at the indicator position) and onto the node editor
  canvas (creates the node at the drop point); double-click still
  appends to the selected clip
- inspector parameter controls are no longer recreated per render
  (gpui stack view caches them per effect), so sliders drag and
  checkboxes click; the view observes the engine and silently re-syncs
  values (undo/redo land on the widgets)
- timeline: left-press selects clips (plain/keep-multi/Ctrl-Cmd
  toggle); clip moves clamp the shared delta so no clip of a linked
  group lands before frame 0 instead of failing with "invalid move
  target"
- oakplugin: createInstance-rejected instances skip the destroyInstance
  notification (the plugin never owned them); vendor-suite fetchSuite
  misses moved behind OAK_OFX_TRACE; the worker logs the discovered/
  registered plugin counts
- CI: the OFX probe step also runs the serialization round-trip test
- gpui submodule: params view caching, clip click-select, library
  drag payload, graph_position_at
2026-08-20 23:46:40 +08:00
Mike-Solar 29696479b5 fix(app): context-menu actions, clip clipboard, A/V drop, add-track, default tracks, undo divergence
- Right-clicking an unselected clip selects it first (C++ parity) —
  this is what made Cut/Delete appear to do nothing.
- Cut/Copy/Paste clipboard: clipboard_copy/cut/paste on the engine,
  clipboard clips keep footage/range/speed/track kind and stay linked
  in the pasted group; paste lands at the playhead as one undo entry.
- Dropping a video-with-audio footage places the video clip plus a
  linked audio clip at the same range in ONE 'Add Clip' undo entry.
- Add Video/Audio Track buttons in the timeline toolbar and the track
  header context menu; new sequences start with 2 video + 2 audio
  tracks (not an undoable edit).
- oaknode Graph::add_entry now reclaims the slot from the free list —
  before, a detached-then-reattached node left its slot in the free
  list, so node_count undercounted and the next add_node silently
  clobbered the restored node. This was the user's 'undo, redo, undo,
  redo and the result changed' bug; regression covered by cycle tests
  (move/trim/delete/split/add-track/linked-placement all converge).
2026-08-19 17:42:21 +08:00
Mike-Solar 194d761ade refactor(oakundo): replace the CHandle vtable layer with owned trait objects
With the C ABI facade (oakengine) retired, the frozen-ABI rationale is
gone. UndoCommand now boxes a Send Command trait (new/from_closures/
multi), dropping OakUndoCommandVtable, the userdata trampolines, the
refcount shell, the handle module, and all undostack_* handle exports.
The global facade loses its raw-pointer out-params (can_undo/can_redo
return bool, command_name returns String). oaktimeline/oaknode/
oakplugin/oaktask construct commands directly via UndoCommand::new.
oakundo src is now free of unsafe; behavior (ordering, idempotence,
done flags, groups, observers, 200-row cap) is unchanged and pinned by
the rewritten tests.
2026-08-19 00:41:49 +08:00
Mike-Solar 74b080f88a feat(oaktimeline): multicam enable/disable/switch commands, split copies the dependency graph
- oaktimeline::multicam: clip_find_multicam (buffer/tex_in depth-1
  lookup), multicam_enable/disable (rewire sequence<->clip through a
  MultiCamNode), multicam_switch (split-preserving-links at the
  playhead, each half owns an independent multicam copy, linked clips
  switched together) as single undo commands with C++ labels.
- BlockSplitCommand now duplicates the clip's whole dependency graph
  (copy_node_and_dependency_graph_minus_items) instead of just the
  block core, matching the C++ BlockSplitCommand::prepare semantics;
  undo detaches the copied subgraph, redo re-attaches identity-
  preserving.
- oaknode: fix serializer dropping edges from the first-created node
  (ptr=0 was not registered in id_map), restoring sequence_in edge
  round-trips; multicam node and clip wiring serializer round-trip
  tests.
2026-08-18 20:45:01 +08:00
Mike-Solar f2af92958a feat(app): proxy editing and audio sync, aligned with the C++ version
Proxy: preview-path proxy substitution (global UseProxyMedia AND
per-footage enabled AND on-disk ready; export always uses originals),
proxy generate/delete/reveal/enable actions, ProxyDialog with global
and per-footage custom params, Tools menu + context-menu Proxy
submenus, progress in the status bar, OVE serialization of proxy
metadata and source_start_time.

Sync: timeline context-menu Synchronize by Source Time / by Waveform /
by Waveform (Adjust Speed) with ctrl-shift-w, cache-envelope
extraction with validity masks, reference/anchor selection and
single multi-undo application (replace-with-gap, speed adjust,
re-place) mirroring timelinewidget.cpp semantics.
2026-08-18 18:58:26 +08:00
Mike-Solar 2db1615453 feat(oakplugin): wire OpenFX plugins into the node graph and renderer
- oaknode: dynamic node factory registration, PluginNode value model
  pushing PluginJobPayload, traverser texture passthrough for texture
  inputs, type-stamped RefBox::get_checked.
- oakrender: PluginExecutor dependency-inversion slot; eval resolves
  and executes plugin jobs, purple frame on failure.
- oakplugin: node_factory with full OFX param -> node input
  translation (15 types, color semantics heuristic, combo ordering,
  secret/ui_group/ui_page, clip inputs), plugin instance registry,
  render executor + duplicator installation, progress reporter and
  active-viewer provider injection points, U8/U16/F16 input
  conversion with NaN scrubbing, in-place output frame writeback fix.
- gl_bridge.rs documents the wgpu<->GL interop spike: Metal-first on
  macOS rules out wgpu-hal GL interop; offscreen GL context deferred.

End-to-end tests cover registration, param translation, CPU render
pixel assertions, identity passthrough and NaN fallback.
2026-08-18 17:15:13 +08:00
Mike-Solar b8a3beaee4 feat(probe): record and print the footage stream inventory
FootageBehavior now keeps the probed stream list (video/audio, per-stream
duration in rationals) instead of dropping it, and the probe CLI walks
that inventory to report real durations, frame rates and stream counts
rather than the previous zero placeholders.
2026-08-18 12:41:55 +08:00
Mike-Solar b36cbd6b6f refactor: purge CHandle from module internals (M14 R5)
Module-internal object references are Rust types now (values, Arc,
Mutex); CHandle remains only at the oakengine C-ABI boundary:

- oakundo: the global stack holds UndoStack/UndoCommand values
  directly (stack token is the static's address)
- oaktimeline: marker/workarea boxes carry Arc<Mutex<T>>; commands
  share the same allocation through Arc clones (readers in oakengine
  stubs and the app's graphops updated to lock)
- oaktask/oakstorage: sessions, write-through bindings and the
  database backend pass ProjectArc; the Session drops its manual
  release bookkeeping; nodeutil keeps the CHandle<->Arc boundary
  conversion (release_project restored for the app)
- oakcodec: handle.rs deleted outright (no facade entry needed it);
  texture/block placeholders are unit structs
- oakrender: copier's project handle is an identity u64; alive-count
  machinery removed; handle.rs is make_owned/get/get_mut only
- oakplugin: the instance registry is gone (its unregister key never
  matched, leaking weak entries); handle.rs is the RefBox boundary type
- oaknode/oakcommon: only dead guard/borrow helpers removed; external
  payload handles (texture/processor) documented as the boundary

Flake hunts landed along the way: the audio recording test serializes
on the shared manager lock with a normalized state; the autocacher
cancel test uses a slow producer so cancellation is deterministic.
2026-08-17 16:40:15 +08:00
Mike-Solar ab1a2e9c7b refactor: drop internal bridge/ffi layers; exporter family lands
Single-lib cleanup: the per-crate src/bridge/ and src/ffi.rs layers are
gone (oakundo/oakcommon/oaknode/oaktimeline/oakcodec/oakaudio/
oakrender/oaktask/oakplugin/oakstorage); cross-crate calls are plain
Rust, CHandle marshalling shrinks to the oakengine boundary, and tests
call the Rust APIs directly (pure C-ABI wrapper tests removed where
the domain layer already covers the behavior).

exporter.h family implemented: oakengine_export_render (CLI contract),
oakengine_export_render_with_params (was a stub), last_error and
progress callback; synchronous path reuses task_create_export +
start_sync. Fixes on the way: oaktask video ticket self-deadlock,
audio params dropped on the export path, codec encoder AAC slicing and
H.264 time base. Real-mp4 tests cover both entry points, progress and
the illegal-argument matrix.

Also: oakstorage session maps null project handles to None (version-
info path), configstore test double literal 3.14 -> 3.15 (clippy PI
lint), oakaudio output callback scratch buffer + env-aware P1 test,
cli media round-trip test uses a generated 16-frame clip (no more
minute-long debug runs).
2026-08-16 00:33:45 +08:00
Mike-Solar 2248be8567 feat(storage): real oakstorage file backends + full-timeline .ove serializer
oakstorage (new workspace member): URI dispatch, pluggable backends
(ove-xml built in, otio/fcpxml via oakotio, C-vtable foreign
registration), the M10 C API surface, version info codes, last-error
and alive accounting; round-trip tests per backend.

oaknode serializer: persists the full timeline — sequence track lists,
track block lists, block ranges/media_in/speed/flags, clip footage
references, footage filename+streams, folder children — through
<custom> behavior hooks with two-phase reference resolution; loads the
C++ <olive><project><layout> containers (golden: tests/
project_with_footage.ove); round-trip is field-by-field and
byte-idempotent.
2026-08-14 15:14:38 +08:00
Mike-Solar 4240df1d71 refactor(crates): implement std::error::Error for all module error enums
thiserror derive across oakundo/oakcommon/oaknode/oaktimeline/oaktask/
oakotio/oakcodec/oakaudio/oakrender/oakplugin/oakengine/oakstorage;
Display carries the module prefix and the Failed context, source()
stays default except oakotio's #[from] forwarding. code() mappings and
variants unchanged; each error.rs gains Display/object-safety/code
regression tests.
2026-08-14 05:45:05 +08:00
Mike-Solar 18ff60f147 feat(engine): clip move, clip effect_input, mandatory static FFmpeg
- oakengine_sequence_move_clip implemented for real (oaktimeline
  TrackMoveBlockCommand; fixes the graph-ownership/gap-anchor/ripple
  trim bugs the stub was hiding); same-track via the frozen C ABI,
  cross-track supported by the module command
- oaknode clip blocks now declare a tex_in texture input and set
  effect_input to it, so timeline clips can host effect chains; facade
  test covers effect insert/remove on a real clip
- oakffmpeg-link: FFMPEG_DIR is now mandatory with a clear panic (a
  Homebrew upgrade left the system ffmpeg .pc pointing at a deleted
  dav1d Cellar path, breaking links); reads a git-ignored workspace
  .env for IDEs that cannot inject env vars (RustRover); links the C++
  stdlib for C++ codec libs (svt-av1)
- oakengine re-exports oaknode so tests share one crate instance;
  it_node uses the direct instance's value type where it calls the
  module FFI (the --workspace dev-dependency feature split builds
  oaknode twice)
2026-08-11 23:04:48 +08:00
Mike-Solar cdda643d64 feat(engine,app): effect chain facade + app effect stack wiring
facade (oakengine/oaknode, API only extended):
- oaknode_node_get_effect_input / oaknode_node_get_flags module exports
- oakengine_clip_as_node, oakengine_node_effect_count/at/insert/remove/
  move/set_enabled, oakengine_node_identity/is_enabled/get_type_id,
  oakengine_node_factory_id_at — chain edits wrap disconnect/reconnect
  into single undo groups; insert/move/remove validated against the
  factory and the host's effect input
- it_node covers enumeration/insert/remove/move/toggle/undo-redo and
  the NULL/illegal-input matrix

app:
- RealEngine binds the selected clip's effect chain to the inspector's
  effect stack (cards, enable, reorder, remove, expansion state), all
  edits undoable through the facade
- AppEngine gains set_selected_clips / addable_effects / add_effect
  (default no-ops keep the mock untouched); the timeline selection
  drives the stack target; the inspector gets a small add-effect menu
- known module gap (documented): ClipBlockBehavior sets no effect_input
  yet, so timeline clips cannot host effects until oaknode grows tex_in
2026-08-11 20:04:44 +08:00
Mike-Solar 013a175707 refactor: workspace layout — crates/, app at root, legacy C++ removed
Single mechanical restructure commit:
- root Cargo.toml = oakapp bin + workspace; one cargo build produces
  oakapp, oak-cli, oak-worker, liboakengine.dylib
- app/rust/src -> src/ (app at repo root, no rust/ nesting)
- src/<mod>/rust -> crates/oak<mod>; src/oakcore-rs -> crates/oakcore;
  src/bindings/oakotio -> crates/oakotio; src/engine/rust ->
  crates/oakengine (keeps cdylib+staticlib+rlib)
- public C headers include/<mod>/ -> crates/oakengine/include/<mod>/
- OFX SDK headers vendored into crates/oakplugin/ofx/ (HostSupport gone)
- legacy deleted: old src/ C++ modules, engine/, core/, ffmpeg_bridge/,
  app/ (Qt), cli/worker C++, root CMakeLists, third_party/KDDockWidgets
  submodule, otio-install, all build-* output (~40GB)
- oakstorage kept but excluded from the workspace (skeleton w/ todos);
  gpui excluded (own workspace)
- verified: cargo build green, cargo test --workspace 1845/0
  (with the documented OCIO_RS_* env override for the homebrew OCIO)
2026-08-10 20:24:25 +08:00