Commit Graph
19 Commits
Author SHA1 Message Date
Mike-Solar b2d353fe99 fix(app): footage drop auto-creates the media's track
A fresh sequence has no tracks and a drop rejected with 'display track
does not exist'; wrong-kind drops also rejected. Dropping footage now
creates a matching track first (the Premiere convention), so the
drag-to-timeline path works on empty timelines too.
2026-08-16 19:46:05 +08:00
Mike-Solar 061aab9180 fix(app): full-res worker crash + source playback frozen + cli/worker DS batch
- full-res source jobs carried only the footage node box: dropping the
  project mid-flight left the node dangling (crash in the worker's free
  path) and the node was also freed twice (at renderer creation AND at
  release). The request now carries an addref'd project copy and the
  node is freed exactly once; regression test drops the project before
  the worker runs
- the source clock ticked at the SEQUENCE length, so playing footage
  with an empty sequence froze the source playhead at 0; the source
  clock now loops at the selected footage's probed duration
- oak-cli/oak-worker: DeepSeek's refactor batch (clap migration, engine
  FFI consumers); the stale exporter-family test flipped to the real
  contract (mp4 is written)
- engine: render_audio smoke test on an empty sequence
2026-08-16 19:07:07 +08:00
Mike-Solar e346ea5338 feat(app): P5 — async full-res render, full preferences, shortcut map
- viewers show the 480px proxy immediately and a background thread
  fills the sequence-resolution frame (per-monitor in-flight job,
  generation-based staleness, playback skips full-res)
- preferences dialog complete: cache dir (now consumed by
  default_disk_cache_path), proxy policy/divider, snapshot interval
  (write-through era autosave), default transition length, audio
  in/out devices (new facade device-enumeration exports; audio init
  from config — playback was never creating the audio instance),
  language/theme/renderer backend, all persisted via config
- shortcut map (src/shortcuts.rs): space/J/K/L, I/O, S split, A/^A,
  ⌘Z/⌘⇧Z, ⌘N/⌘O/⌘S/⌘E/⌘Q, frame step, Home, track zoom; dispatch
  shares the menu action path and stays silent over modals
- screenshots: preferences dialog zh/en captured and reviewed
2026-08-16 18:05:03 +08:00
Mike-Solar 92daff1a83 feat(engine,app): timeline markers, work area, cross-track move (M12 P4)
- facade: oakengine_sequence_set_workarea_undoable (enable+range as one
  undo record); marker add/remove/list already existed and are now
  covered by it_timeline (10 new tests: markers, workarea, cross-track)
- fixes: stubs workarea_get honored NULL out-params (is_enabled always
  failed); export tasks honor custom ranges (export_params_pod dropped
  them; oaktask EncodingParams carries has_custom_range)
- app: markers on the ruler (gpui diamond markers), menu
  sequence-add/remove marker, set/clear work area (selected-clip bounds
  or playhead+1), ruler drag previews live and commits one undoable
  record (C++ ruler semantics); export uses the work area when enabled
- cross-track clip moves were already wired; now covered end to end
2026-08-16 16:06:19 +08:00
Mike-Solar 693e2df5eb feat(app): project browser on real project data + drag-drop import (M12 P3)
- ProjectDataSource<RealEngine> reads the real bin tree (roots/
  children via the facade folder/footage enumeration)
- AppEngine::import_footage implemented (facade project_import_footage,
  last_error surfaced); double-click opens in the source viewer
- dragging files onto the browser imports them (also fixes a real gpui
  bug: ExternalPaths arrives as the bare type, not Arc-wrapped, so
  drops never fired)
- tests: real-engine import->browser listing, widget drop routing,
  facade folder enumeration failure matrix
2026-08-16 15:05:01 +08:00
Mike-Solar 0f82092e95 refactor(app): retire manual-save semantics (M13 D5)
- AppEngine: save_project -> export_project_path(explicit path);
  project_modified removed (write-through has no dirty flag)
- File menu's save/save-as are the export path (extension-dispatched
  ove/otio/fcpxml); the library row export path is unchanged
- facade project_save/is_modified stay frozen, documented as the
  legacy export surface
- docs: M13 D5 checked off; M12 inventory updated
2026-08-16 14:17:45 +08:00
Mike-Solar 025dc88c25 feat(storage,app): PostgreSQL backend (D3) + project manager window (D4)
D3: oakdb+pg:// fully wired (shared sea-orm entities, BIGSERIAL DDL,
connect-probe instead of pool retry on dead servers); Storage/Backend=pg
+ Storage/PgUrl config; 13 OAK_TEST_PG_URL-gated PG tests (verified
against a Docker postgres:16), always-on clean-error tests otherwise.

D4: DaVinci-style project manager — list with derived stats, create/
rename/duplicate/delete (confirm)/import/export (native dialogs,
ove/otio/fcpxml), shown at startup and from the file menu; facade
oakengine_library_* exports (list/create/delete/rename/duplicate/
import/export + project_load_library that binds write-through);
save/save-as menu becomes 'export project file', open splits into
from-library/from-file; status bar shows library write state; storage
activates on app start and flushes on exit; spawn_modal reentrancy
fixed (window-callback path) with a doc note.

Also: the P1 audio test's environment probe was lost in the ffi purge;
restored on cpal (the output device is cpal now).
2026-08-16 10:39:31 +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 48d3027d50 feat(engine,app): node editor bound to the facade graph (M12 P2)
facade (API only extended): oakengine_sequence_as_node /
_sequence_node_count / _sequence_node_at / _sequence_remove_node;
remove validates ownership by project UUID (arena slot/generation
collide across projects). it_node covers enumeration/edits and the
NULL/illegal matrix.

app: NodeGraphDataSource enumerates the current sequence's graph —
footage / effect / clip / output cards at their context positions
(deterministic role-grid fallback), real edges plus synthesized
clip->sequence tex_in edges; connect/disconnect/remove/drag-release
all go through undoable facade commands, drag previews stay local.
2026-08-14 13:33:49 +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 f0517fe6af feat(app): UI walkthrough pass — faithful screenshots, dock labels, timeline clips, status bar, meter
- screenshot example inits i18n and captures both zh-CN and en-US
  (docs/screenshot-window{,-en}.png)
- dock tabs size to content (no more 64px truncation); viewer/panel
  titles follow the design (素材查看器·name etc.)
- mock project carries V1/V2 video + A1/A2 audio clips rendered as
  rounded green bars; timeline clip geometry/rounded corners match the
  design; status bar visible with full content; audio meter strip
  docked at the program viewer's right edge; project explorer rows
  have icons
- tests/waveform_e2e.rs: waveform cache extracts real peaks and hits
  cache on re-query (P4 acceptance)
2026-08-13 23:37:57 +08:00
Mike-Solar 5afa95c80e feat(render,app): footage decode lands (M12 P0) + UI density pass
engine:
- oakrender eval footage hook decodes via oakcodec (JobSpec::Footage
  carries filename/stream); ticket/ffi/manager wiring, real-media
  decode test with programmatically generated MPEG-2
- oakrender bridge/codec.rs + node.rs: direct oakcodec/oaknode calls;
  the crate's dlsym module is gone (project_deep_copy/sync_copy remain
  documented always-fail stubs — never implemented in oaknode)
- oakaudio waveform/decoder path adjustments for the decode hook

app (gpui + gpui_widgets):
- menu bar scrubbing: hovering another top-level title while a menu is
  open switches to it; popup width is content-aware (CJK-aware) instead
  of fixed 160px
- density pass: window rem 16 -> 14px, menu rows 26 -> 22px, dock tabs
  32 -> 26px, viewer transport tightened
- open/import/save-as use the native platform file dialogs
  (prompt_for_paths / prompt_for_new_path; multi-select import);
  MockEngine records imported footage for tests
- project explorer Tree/Icons toggle is localized (explorer.tree /
  explorer.icons widget keys)
2026-08-13 18:16:10 +08:00
Mike-Solar 06f47235b2 feat(engine,app): source-monitor frames + real audio meter
source monitor:
- new facade exports oakengine_renderer_create_for_node (render any
  node, not just sequences) and oakengine_project_footage_at (fetch a
  footage node by index); it_render covers the e2e render plus the
  NULL/illegal matrix
- RealEngine renders the selected footage's real frames to the source
  viewer (per-node renderer slot, frame cache invalidated on selection
  change, synthetic fallback kept)
- known gap (documented): pixels stay transparent black until
  oakrender's footage decode hook lands — the render surface itself is
  real end to end

audio meter:
- oakaudio manager can now report per-channel linear peaks of the
  buffered output (PreviewAudioDevice::peek_tail + levelmeter analysis
  of the newest 8192 frames, packed/planar F32)
- new facade export oakengine_audio_output_levels (negative codes pass
  through); RealEngine's AudioMeterDataSource reads it instead of
  returning hardcoded silence
- tests: module peak readback + facade validation/readback matrix
2026-08-12 14:19:18 +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 7b295b7661 feat(app): scopes (histogram/waveform/vectorscope) fed by rendered frames
- program viewer gains Picture/Scopes tabs; the scopes page hosts the
  gpui_widgets histogram, waveform and vectorscope side by side
- RealEngine analyzes the same F32 RGBA samples it renders (BT.709 luma,
  normalized Cb/Cr); the mock engine analyzes its synthetic frame
  through the same path; results ride the per-frame cache
- AppEngine::scope_data(monitor) exposes ScopeData to panels
- unit tests for the analysis math + a gpui test rendering the scopes
  tab; zh/en i18n keys added
2026-08-11 17:04:42 +08:00
Mike-Solar f908724a7a feat(app): real engine renders true frames to the viewers
- RealEngine caches a per-sequence renderer and serves cpu_frame via
  oakengine_renderer_render_frame (F32 RGBA, 480px-long-edge proxy)
  instead of the synthetic test pattern; edits/undo invalidate the
  per-frame cache
- frames.rs: F32 RGBA -> BGRA8 downconvert extracted as
  f32_rgba_to_bgra_image (shared with the synthetic fallback)
- ffi.rs: renderer/frame C ABI declarations + OakEngineFrame /
  OakEngineRenderer handle mirrors, oakrender_manager_init/available
- new real_render_frame_e2e test renders through the dylib; mock engine
  and headless tests unaffected
- known gaps (documented in code): source monitor still synthetic
  (facade renders sequences only), render is synchronous on the UI
  thread, renderer not rebuilt on sequence geometry change
2026-08-11 16:10:14 +08:00
Mike-Solar 063837446d feat(app): link liboakengine.dylib via frozen C ABI; UI fixes
- App no longer depends on the oakengine rlib: build.rs links the
  built liboakengine.dylib (+rpath, -export_dynamic, IOSurface) and
  src/oakui/ffi.rs declares the pure-C surface; RealEngine calls only
  the frozen oakengine_* C ABI
- host_syms.rs provides the oakcore_*/fb_* host symbols the dylib
  imports via dynamic lookup
- Fix Preferences dialog crash (spawn_modal reentrancy) with a
  regression test
- Timeline toolbar and viewer transport render C++-era icons (16px
  grid, dark/light themes) with localized tooltips
- i18n: complete en-US table, add untranslated-key detection test
- New dialogs module (preferences, export, progress)
2026-08-11 00:25:20 +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