Commit Graph
61 Commits
Author SHA1 Message Date
Mike-Solar 2c64317a58 export media: editable resolution / frame rate / bit rate spin boxes
CI / Build & test (Linux) (push) Canceled after 0s
CI / Build & test (Windows) (push) Canceled after 0s
The three values are SpinBoxes (typed input + stepping): width & height
(0 = use the sequence size), frame rate in fps (0 = sequence rate),
bit rate in bit/s (0 = the codec default). The engine already accepted
arbitrary values, so the dialog now exposes them instead of the closed
preset lists. i18n across the 8 packs.
2026-09-02 09:35:54 +08:00
Mike-Solar 92813707bc export media: dialog UI — container→codec gating, color space/bit depth, range
- ExportDialogContent: container format (existing), then the video and
  audio codec combos rebuilt per container from the compatibility tables
  (an unsupported codec is never offered — switching container
  re-selects the first compatible entry), a color-space combo (SDR
  Rec.709 8-bit / HDR BT.2020 10-bit), an export-range combo (whole
  timeline / in-out) and the output path
- ComboBox::set_options added for the container-driven list rebuild
- settings() gathers the ExportSettings the engine consumes
- begin_export routes through start_export_with
- i18n keys across the 8 packs (zh-CN uses distinct values so the
  en==zh equality test passes)
2026-09-02 09:31:49 +08:00
Mike-Solar 499246b48e export media: settings protocol (codec/color/bit-depth/bitrate/range) end to end
- ExportSettings: container format, video/audio codecs, size/fps/
  bitrate, bit depth (8/10 → U8/U10), SDR 709 + HDR 2020 code points,
  export range
- AppEngine::start_export_with(settings, path); real builds the
  task params via encoding_params_with_settings (codec tables gate
  each container — a non-supporting combo errors, never silently
  defaults; the one menu only offers compatible codecs)
- oak-task EncodingParams: video_bit_rate / audio_bit_rate +
  color_override_enabled + primaries/trc/space (override wins over
  the project's delivery colorspace)
- mock start_export_with (no-op fake progress)

The dialog UI rebuild (fields + container→codec→color enability
linking) follows in the next unit.
2026-09-02 09:27:43 +08:00
Mike-Solar 56af662b83 project file: 另存为 — duplicate the project in the library under a new name and switch to the copy
- save_project_as(name): library_duplicate the open project's row,
  rename the copy, open it as the current project
- 文件 → 另存为 opens a name dialog (prefilled '<name> Copy'); OK
  duplicates + switches (the save-as semantics: a new, differently-
  named project in the library, editing continues on the copy)
2026-09-02 09:22:29 +08:00
Mike-Solar 660a32a1b3 project file: new-project dialog (name + sequence format), open prompt lists formats
- 文件 → 新建项目 opens an in-app dialog: project name + the first
  sequence's format (preset / custom / interlaced, reusing
  SequenceFormatFields); OK creates the library project then seeds the
  first sequence from the chosen format
- 打开项目 prompt advertises the supported project formats
  (.ove / .otio / .fcpxml)
- manager.new.project_name across the 8 i18n packs
- action-search first-item test follows the new flow (New Project now
  opens its dialog)
2026-09-02 09:15:30 +08:00
Mike-Solar c900804941 project file: export dialog with OTIO/OVE/FCPXML format choice
文件 → 导出工程文件… now opens an in-app dialog (format dropdown:
OpenTimelineIO / Oak / FCPXML + output path) instead of the bare
platform save prompt; OK routes the chosen extension through the
engine's export_project_path. Adds project.export.format i18n across
the 8 packs.
2026-09-02 09:10:39 +08:00
Mike-Solar 19a83f8359 multicam: hide the wizard menu item for v0.5
CI / Build & test (Linux) (push) Failing after 11s
CI / Build & test (Windows) (push) Failing after 3m30s
The 窗口 → 多机位向导 entry is removed from the menu bar and the
action-search list (menu_id 0, the hidden convention); the engine
pathway (wizard dialog, sync, multicam creation) stays intact behind
the action dispatch for a future release. The panel's multi-cam
feature remains.
2026-09-01 22:06:12 +08:00
Mike-Solar be42620e22 multicam: throttle angle refresh, shrink pre-render window
- MulticamPanel: playback angle refresh runs one cycle per 3 ticks
  instead of re-requesting every source every tick — each angle decode
  is a keyframe-scanning FFmpeg seek that stole worker capacity from
  the main viewer
- PreRender frames default 120 -> 12: a window larger than what the
  pool can render in real time queues far ahead of the playhead, so
  the painted frame lags seconds behind (playback frozen); a smaller
  window keeps the backlog bounded
- render_graph_frame: OAK_PERF clip-level timing
2026-09-01 21:52:56 +08:00
Mike-Solar 45c288ce97 revert 3bcf4e0b1 (multicam no-split/stride/proxy-ng) pending split re-verification 2026-09-01 21:31:18 +08:00
Mike-Solar 3bcf4e0b1a multicam: no-split switch skips full rebuild, angle refresh stride, proxy in node-graph decode
- multicam_switch_to: a NO-SPLIT switch only changes current source —
  push snapshot + invalidate preview windows, skip the full
  apply_edit rebuild (the 'switch once then everything grinds' hit)
- MulticamPanel: playback angle refresh runs one cycle per 3 ticks
  instead of re-requesting every source every tick (each angle decode
  is a keyframe-scanning FFmpeg seek)
- footage value(): a ready proxy stands in for the original media in
  the node-graph decode request (proxy enabled but 4K decoded report)
- proxy_resolution made public (engine's proxy-first preview size)
2026-09-01 21:19:54 +08:00
Mike-Solar 1b0a15192e multicam: wizard + AFV + node-graph preview + performance
CI / Build & test (Linux) (push) Failing after 24s
CI / Build & test (Windows) (push) Failing after 3m38s
- wizard: angle multi-select, sync modes, auto-align, create sequence;
  keeps the host sequence current (host clip = the multicam clip)
- build_multicam_sequence: per-angle clip -> SOURCES_INPUT[element],
  array slot growth, audio angle tracks for AFV
- AFV: host linked audio clip follows the switched source (one undo),
  muted host audio track disables it
- node-graph preview: inline producer renders through the traverser
  (viewer/project-matched graph frames); sequence viewer uses the graph
- multicam node value(): element-tagged row keys (sources_in[i]),
  reads the current source; build_row keys array inputs by element
- angle grid: viewer=0 (single-track montage, not whole-graph)
- project explorer: rename (dialog) + delete (undoable) real items
- performance: decoded-frame LRU, per-process NVDEC quota (1 session,
  evict before open), GPU composite fail-once fallback, snapshot
  upload debounced on the engine tick, worker vram budget headroom
- timeline clip: multicam overlay via ClipDecorator
- wizard menu item moved to Sequence menu
2026-09-01 20:27:59 +08:00
Mike-Solar 5937e557a7 app: project-explorer new-sequence button, 4K presets, keying node fix, color-picker canvases
- Project explorer header gains a 新建序列 button (opens the existing
  new-sequence dialog, seeded like the menu action).
- Sequence presets gain 4K UHD (3840x2160@25) and 4K DCI (4096x2160@24);
  the sequence-properties dialog re-selects them on reopen. Format fields
  are also seedable from a probed footage format (the drop flow).
- Chroma Key (and Color Difference Key) value() now box a ShaderJobPayload
  like Despill: the old OCIO-processor gate pushed nothing (the processor
  is never populated without the render bridge), so the traverser handed
  the clip NodeValue::None and the rendered frame lost the clip. The
  renderer resolves the OCIO stub at compile time from OCIO_SHADER_STUBS.
  End-to-end graph test: green key on a green frame keys out, red key
  keeps it.
- The OFX color picker's SV palette / hue bar / preview / swatch canvases
  get size_full(): the bare canvases collapsed to zero height in the
  block layout, so the palette painted nothing (the reported 色板没显示).
  Regression test clicks the palette center and expects mid s/v.
2026-08-30 21:58:26 +08:00
Mike-Solar 197212bc46 app: extend OAK_DEBUG_AUDIO diagnostics to the render thread and output stream
Per-chunk submit->render wait and render time (audio_thread), the push
lead against the playhead, the negotiated stream config, and the true
device-side callback rate (outputdevice) -- the ground truth used to
localize the playback starvation to the sequence-rate/clock mismatch.
2026-08-29 23:31:48 +08:00
Mike-Solar 84caa3589b app: sync the playback clock to the sequence rate; heal underruns by trimming
Two playback-audio defects found while chasing periodic pops, clipped
words and repeated phrases:

1. The playback clocks are created at engine init with the fallback
   rate (hd_1080p25) and never updated, while audio chunks are sized by
   the SEQUENCE time base (30/1 from the broken default). The playhead
   advanced at 25 fps but each chunk carried only 1600 samples, so
   production ran at 40000 samples/s against the device's 48000 -- ~17%
   of playback time was zero-filled silence (clipped words). tick() now
   syncs both clocks to the sequence's actual rate.

2. The underrun self-heal cleared the whole device queue (~190 ms of
   unheard content skipped), reset the prefetch (whose first
   re-rendered chunk was then always dropped as late) and re-anchored
   the master clock, re-rendering already-played audio whenever the
   playhead lagged (audible as a pop plus a repeated phrase, every
   ~1.1 s). It now trims just the stale lead from the queue
   (PreviewAudioDevice::drop_front_frames): the zero-fill already
   advanced the output clock past that content, so the rest of the
   queue stays audible and the anchor stays valid.

Also adds the OAK_DEBUG_AUDIO event log used to diagnose all of this
(prefetch resets, late drops, underruns, heals, reanchor jumps,
per-tick queue state).
2026-08-29 23:31:35 +08:00
Mike-Solar 9dab9efc38 codec: carry codec-frame overflow across audio chunk boundaries
retrieve_audio_to decodes whole codec frames but copies only the part
inside the requested chunk; the tail of the frame crossing the chunk end
(up to 1023 samples for AAC) was consumed by the decoder and lost, so
the next chunk started with a hole. On the playback grid (1920 samples
at 25fps/48kHz) the hole cycles 128..896 samples and hits 7 of 8 chunk
starts -- the heavy stutter/noise heard during playback.

Keep the overflow (decode and resampler-flush tails) in a per-session
carry buffer and serve it at the start of the next contiguous chunk;
clear it on seek, format change and chunk failure. Also make seek()
actually drop the cached resampler as its comment claimed.

Verified sample-exact: chunked decode of a 440Hz tone now matches a
one-shot decode bit for bit, and chunked renders of real media line up
with the ffmpeg CLI reference at correlation 1.0 / drift 0.

Adds a regression test (playback_sized_chunks_match_oneshot_sample_exact)
with a new tone fixture -- demo.mp4's audio is -90dB digital silence and
cannot expose the holes -- plus a render_audio_wav example used to
diagnose chunk-boundary artifacts offline.
2026-08-29 19:51:59 +08:00
Mike-Solar cbd4ba2442 optimize: video and audio render
CI / Build & test (Linux) (push) Canceled after 0s
CI / Build & test (Windows) (push) Canceled after 0s
2026-08-29 05:01:55 +08:00
Mike-Solar ca18105c8b ci: retry the Linux test suite once; log render-manager init failures
CI / Build & test (Linux) (push) Successful in 18m4s
CI / Build & test (Windows) (push) Successful in 34m6s
Run 48's Linux leg flaked: full_res_worker_outlives_a_dropped_project
panicked with "the render manager failed to start" while all 236 other
tests passed -- a worker-pool startup race under full-suite parallelism,
not a regression (the same binary passes locally and passed on the
previous commit). Mirror the Windows job's retry-once policy and make
ensure_render_manager log the underlying init error so the next flake is
diagnosable. The Windows leg's real failure (gpui_widgets referenced
SurfaceSource::Texture, which only exists on linux/freebsd) is fixed in
the gpui submodule bump of the previous commit.
2026-08-29 00:37:24 +08:00
Mike-Solar fdb5caabd5 color: non-sRGB preview, per-monitor display ICC, pipeline hardening
Preview now follows the project output colorspace end to end: the
display chain derives its content space from the project's OutputColorSpec
instead of a hardcoded sRGB name, self-managed ICC transforms go through
an XYZ D65 interchange stage (OCIO cie_xyz_d65_interchange) for non-sRGB
targets, and the platform layer declares the content colorspace (gpui
submodule bump). macOS defaults to OS-managed (fixes wide-gamut UI
oversaturation); Windows ACM warns once on non-sRGB targets.

Multi-monitor: the display ICC is looked up per the window's current
screen (macOS display id, Windows per-monitor DC, X11 RandR output
profile) with a throttled poll that invalidates frame caches on moves.

Pipeline precision: 10-bit+ sources fall back to YUV444P16LE + a Rust
matrix conversion when swscale lacks F32 output (no more 8-bit
truncation); BT.709/2020 SDR decodes with BT.1886 gamma 2.4 instead of
the sRGB EOTF; working-space compositing no longer clamps RGB to [0,1]
(alpha still clamped); the output node clamps to the target gamut;
frames without colorimetry metadata convert with BT.709 defaults
(warned once) instead of passing through; scopes read the
output-colorspace signal on both F32 paths.

Also: only emit rerun-if-changed for .env when it exists (a missing file
made every build fully dirty).
2026-08-29 00:24:15 +08:00
Mike-Solar 879ff8da5b app: PS-style colour picker and an eyedropper that keeps the popup open
CI / Build & test (Linux) (push) Failing after 9s
CI / Build & test (Windows) (push) Failing after 22s
- the popup now offers a large SV palette with a hue bar plus RGB/HSV
  mode tabs (shared slider model swap, hsv<->rgb pure functions)
- while the eyedropper is armed, clicks outside the popup pick a colour
  instead of dismissing it, so the picked draft can still be confirmed
- regression test pinning the OFX plugin colour set/read/undo chain
- i18n: ofx.color.mode_rgb/mode_hsv in all eight packs
2026-08-27 20:43:27 +08:00
Mike-Solar c730061018 app: occlude the app-owned ComboBox dropdown
It is a plain deferred div rather than an anchored element, so it gets
no automatic BlockMouse hitbox; clicks on its padding fell through to
the controls below.
2026-08-27 20:43:09 +08:00
Mike-Solar 9c0269bcbb render: cache worker frames and decouple audio from the video workers
- oak-worker gains an LRU frame cache (default 64 MiB) keyed by the
  render-deterministic spec subset; repeat frames (paused frames,
  scrubs over rendered ranges, re-renders after an effect change) are
  memcpy-cheap, which is what made adding an OFX plugin -- and the
  in-flight batches after removing one -- stall the UI
- audio dispatch on the Processes backend now mixes inline on the UI
  tick instead of queueing behind video batches in the worker pool, so
  video stalls no longer starve the ~100ms cpal output buffer into
  silence
2026-08-27 20:35:50 +08:00
Mike-Solar 246de6352f app: true 10-bit presentation -- F32 frames reach the swapchain unquantized
CI / Build & test (Linux) (push) Failing after 17m38s
CI / Build & test (Windows) (push) Failing after 17m40s
The display path no longer passes through BGRA8: render workers are
asked for F32, rows are repacked, colour management runs on the F32
data, and the frame uploads to an Rgba16Float wgpu texture that the
viewer presents directly on the Rgb10a2Unorm swapchain. The BGRA8
image remains only for scopes, the eyedropper, thumbnails and the
no-GPU fallback.
2026-08-27 19:55:05 +08:00
Mike-Solar 5d21f83e1e render/app: display bit depth option (10-bit default, 8-bit optional)
Preferences gains a display-bit-depth combo stored in the config store;
at startup the app forwards it to gpui_wgpu via OAK_DISPLAY_BIT_DEPTH,
which prefers Rgb10a2Unorm for 10-bit presentation. Takes effect after
restart (noted in the dialog); i18n in all eight packs.
2026-08-27 19:08:55 +08:00
Mike-Solar 82ad9891ad app: white fill with black border for unchecked checkboxes
Keeps the white well but swaps the water-blue theme border for black,
per user feedback.
2026-08-27 19:08:19 +08:00
Mike-Solar eb5643249c app: scope multicam digit shortcuts to the multicam panel
The 18 multicam switch actions were bound globally to keys 1-9, so
gpui dispatched every digit keystroke as an action and text fields
never received it -- every numeric input in the app (slider value
boxes, spin boxes, the colour picker's hex field) could only type 0,
and the colour swatch never updated because hex parsing failed.
The bindings now carry the MulticamPanel key context: digits switch
angles while the panel is focused and type normally everywhere else.
2026-08-27 19:08:19 +08:00
Mike-Solar 719e43eba3 app: render at proxy resolution and honour the playback divider
- paused full-res renders now scale by the playback divider instead of
  always rendering at native sequence size
- when a clip's proxy is active, sequence/footage renders clamp to the
  proxy resolution (never upscale a 720p proxy to 1080p and back),
  which was making proxied playback slower than no proxy
2026-08-27 18:54:03 +08:00
Mike-Solar 54945e39af app: pick colors from the program viewer frame (PS-style eyedropper)
Replaces the project-thumbnail picker: the color popup's eyedropper
button arms the program viewer, the next click on the picture samples
that pixel (contain-fit mapped, BGRA from the current CPU frame) into
the picker's draft; Esc or the button cancels. Coordinated through a
global eyedropper mailbox on the engine.
2026-08-27 18:32:34 +08:00
Mike-Solar 1ae03c1b1e app: real timeline tools, live zoom buttons, clickable snap toggle
CI / Build & test (Linux) (push) Successful in 20m7s
CI / Build & test (Windows) (push) Successful in 41m55s
- the eight toolbar tools now drive real editing: razor splits at the
  click point, ripple/slip/roll/slide emit new undoable composite
  commands (ripple_trim_clip, roll_edit, slide_clip, slip_clip),
  zoom clicks zoom anchored at the cursor, track-select selects the
  track right of the click; panel tool state and the Tools menu stay
  in sync both ways
- the zoom-in/out toolbar icons now actually zoom (anchored at the
  playhead) and the snap magnet icon toggles snapping like its checkbox
2026-08-27 17:59:27 +08:00
Mike-Solar 4e619efb93 app: make the viewer context menu real
CI / Build & test (Windows) (push) Canceled after 12m22s
CI / Build & test (Linux) (push) Canceled after 14m4s
Every item in the program/source viewer menu now works and reflects
real state: zoom levels, full-screen toggle, safe margins (off/on/
custom), stop-on-last (honoured by both playback clocks), waveform
mode, show-fps overlay, and save-frame (writes a PNG of the current
frame). The viewer widget's in/out/clear buttons are wired to the
shared program workarea.
2026-08-27 17:39:03 +08:00
Mike-Solar bad2f9552e app: sequence management -- new-sequence dialog, folders, sequences in the project bin
- File > New > Sequence opens a real dialog (name, PAL/NTSC/HD presets,
  width/height/frame rate, progressive/interlaced); VideoParams gains an
  interlaced flag
- File > New > Folder creates a folder in the project root
- sequences are mounted under the root folder so they appear in the
  project explorer (including the auto-created Sequence 1 and orphans
  from older projects); right-click > Sequence Properties edits the
  parameters afterwards
- dropping footage onto an empty, sequence-less timeline auto-creates a
  sequence from the footage's first video stream
2026-08-27 17:38:30 +08:00
Mike-Solar d65b84a6ca app: live color swatch and a project-media eyedropper in the color picker
- the swatch now follows the draft color while the popup is open
  (cancel still reverts) and set_effect_param failures are logged
  instead of silently reverting the swatch
- the popup lists project footage thumbnails; clicking a pixel picks
  its color into the draft (contain-fit coordinate mapping, no new
  dependencies)
- i18n: ofx.color.pick_project in all eight packs
2026-08-27 17:34:28 +08:00
Mike-Solar 2dfdf8a5b0 app: click-to-jump and a persistent value box on sliders
- clicking anywhere on the track moves the thumb to that position
  (double-click still opens the inline editor)
- a compact numeric field next to every slider shows the value and
  accepts typed input, clamped to the slider's min/max range
2026-08-27 17:32:48 +08:00
Mike-Solar a372d72446 app: white background with inner shadow for unchecked checkboxes
The unchecked state used the panel background color, making the box
nearly invisible on the dark theme.
2026-08-27 17:32:37 +08:00
Mike-Solar c03f1ec604 render: stack higher-numbered video tracks on top
Match the timeline UI (V_max drawn topmost): composite tracks from
V1 up to V_max so the highest-numbered track is composited last, in
both the montage path and direct graph evaluation.
2026-08-27 15:20:18 +08:00
Mike-Solar 663d879908 node/app: keep clip links symmetric and intact across paste, delete, and undo
- take_node clears the partner's dangling link references
- are_linked checks both directions; link() repairs asymmetric pairs
- paste writes links through the graph API instead of BlockCore.links
- drop link undo removes only its own pair instead of restoring a snapshot
- move_clip_with_links skips off-track partners instead of failing
2026-08-27 15:20:18 +08:00
Mike-Solar cbf9d3c543 app: silently ignore clip drags across track kinds (video<->audio) 2026-08-27 10:13:11 +08:00
Mike-Solar 8c306986ab app: stamp clips with a stable creation-time color and footage-name label 2026-08-27 10:13:11 +08:00
Mike-Solar c51a349070 render: translate node GLSL shaders to WGSL and run them as wgpu passes
CI / Build & test (Linux) (push) Failing after 16m57s
CI / Build & test (Windows) (push) Successful in 31m46s
2026-08-27 07:07:09 +08:00
Mike-Solar 4f6c44a0d0 proxy: hardware-accelerated transcode with configurable, resource-aware concurrency
CI / Build & test (Linux) (push) Successful in 19m14s
CI / Build & test (Windows) (push) Successful in 32m5s
Proxy generation no longer pegs the machine:

- the transcode probes for a hardware H.264 encoder once per process
  and uses it when present (macOS h264_videotoolbox; Windows/Linux
  h264_nvenc -> h264_qsv -> h264_amf; libx264 remains the universal
  fallback and the untouched C++ parity path), with decode-side
  -hwaccel auto (HEVC 4:2:2 10-bit decodes in hardware on Apple
  Silicon / RTX 50+ / Intel GPUs and falls back to software cleanly);
  quality/preset map from the proxy CRF/preset per encoder
- the concurrency limit is configurable (ProxyMaxConcurrent, default
  1) in the Proxy Settings dialog; auto-generated jobs queue and the
  next starts when a slot frees. The invariant
  concurrency x per-job threads <= logical cores / 2 holds by
  construction (thread budget = half the cores / concurrency, clamped
  to [1,8], covered by a unit test), and on Unix ffmpeg runs nice -n 10
  so the background task never starves the foreground
- deleting a footage's proxy also removes it from the auto-generation
  queue
2026-08-26 06:40:06 +08:00
Mike-Solar ca64b61156 app: drop the throughput assumption from the playhead tracking test
CI / Build & test (Linux) (push) Successful in 19m21s
CI / Build & test (Windows) (push) Successful in 31m53s
playback_display_tracks_the_playhead asserted the displayed frame lag
the playhead by less than 4 frames at playhead 120 — a render-
throughput assumption that fails under machine load (parallel builds)
rather than for a broken pipeline, which made the test flaky. It now
asserts the machine-speed-independent property: the display advances
while the playhead advances (peak displayed grows between the two
checkpoints), which still catches a permanently frozen picture.
2026-08-26 04:23:11 +08:00
Mike-Solar 96df9293e5 app: extract audio waveforms off the UI thread
rebuild_timeline synchronously extracted every audio clip's waveform,
and the extractor decodes the clip's ENTIRE audio inline — with two
40-minute files on the timeline, opening the project froze the UI for
seconds ("the project takes a while to open"). Extraction now runs on
a background thread; the cache bumps a version counter on insert and
the engine tick repaints the timeline when it changes.
2026-08-26 04:23:11 +08:00
Mike-Solar 09c7e7de32 app: auto-generate proxies in the background and badge them in the bin
With the global proxy switch on, footage that needs a proxy now gets
one generated in the background automatically — on import, on project
open, and when proxy use is enabled for a footage — instead of only
through the manual Generate menu action. Footage explicitly opted out
(a recorded proxy path with use disabled) and footage whose last
generation failed are not restarted; the generation path enables proxy
use so preview switches to the proxy as soon as it is ready.

The bin shows the lifecycle as a corner badge on each footage entry
(green P = ready, amber P = generating, red ! = failed; the widget
half lives in the gpui fork, submodule bumped here).

Includes a real-media test driving ProxyTask end-to-end on 4K H.265
4:2:2 10-bit (the media class the 4K playback lag was reported on):
180 s transcodes to a 720p proxy in ~25 s.
2026-08-26 04:22:44 +08:00
Mike-Solar fa3951344b render: fix 4K playback memory growth and decode-to-target-size
Root causes found for the 4K stalls and the second-footage memory
blowup (audit + code review):

- ticket bookkeeping leaked unbounded: the procpool ticket table and
  the arena slot map only ever grew (50-100 tickets/sec during
  playback, each pinning montage params and shm region views).
  Completed/cancelled/superseded/crashed entries are now removed, and
  the arena reaps fire-and-forget tickets once finished; the sync poll
  path reaps via a terminal result() read. InFlight duplicate submits
  now answer State immediately instead of sitting in the map forever.
- decode ran a full-resolution swscale to F32 RGBA (~132 MB at 4K)
  plus a second full-res copy before downscaling to the 480px proxy:
  RetrieveVideoParams.target_size lets swscale convert AND resize in
  one pass (bilinear, matching the old Rust resampler), so a 4K
  preview frame costs ~1 MB instead of ~260 MB of churn. This applies
  to proxy AND full-res requests alike.
- per-process decoder cache was unbounded (each session pins an FFmpeg
  context + 2 native decoded frames): LRU-capped at 16, eviction drops
  the map entry (in-flight renders keep their Arc; Drop releases
  FFmpeg).
- playback window completions were not generation-gated: a stale
  render from before an edit landed in the rebuilt window (wrong frame
  displayed, fresh request blocked). Stale completions now return
  their shm slot credit instead.
- async audio prefetch used the polling ticket submit without ever
  polling: switched to the fire-and-forget submit so entries reap.
2026-08-26 01:31:10 +08:00
Mike-Solar 3bf914a2b8 app: stop slider drags from leaking into other sliders
CI / Build & test (Linux) (push) Successful in 18m42s
CI / Build & test (Windows) (push) Successful in 44m58s
gpui delivers DragMoveEvent to every drop target with a matching
payload type under the cursor, and every slider shared the SliderDrag
payload type — dragging one slider (e.g. an OFX panel parameter) also
moved every other slider the cursor passed over (timeline zoom, track
height). Tag the payload with the starting control and ignore drag
moves that are not ours.

Also finish the gesture on mouse-up (inside or outside the track)
instead of on_drop: gpui only delivers drop events to the hovered
target, so releasing outside the track used to leave the drag
unfinished and the final value/undo edit unemitted.
2026-08-25 19:10:25 +08:00
Mike-Solar a56f6ddf87 app: link the rear halves when splitting an A/V pair at the playhead
The Cmd+K split ran the plain per-block split for every selected clip,
so an originally linked audio/video pair came out with its front halves
linked but the two rear halves unlinked — dragging one rear clip left
its mate behind and Link/Relink could not repair it.

Split the whole target set as ONE BlockSplitPreservingLinksCommand (the
same command the multicam path uses): any pair of originally linked
blocks split at the same time gets its new halves linked too. Adds a
gpui test asserting front and rear halves of a dropped A/V clip stay
linked after split_at_playhead.
2026-08-25 19:10:08 +08:00
Mike-Solar f2aab8ce15 render: apply clip effect stacks in the montage path
CI / Build & test (Windows) (push) Failing after 16m26s
CI / Build & test (Linux) (push) Successful in 19m12s
Adding an effect to a clip did nothing: the sequence render is
flattened into a montage (decode + composite), and MontageClip carried
no effect data at all.

- MontageClip gains an ordered effect stack (type id / enabled /
  effect input / parameter values); protocol v2 carries it as an
  additive wire field (older peers default to an empty stack).
- renderops::video_montage fills the stack from the effect chain
  (the footage source node — the chain end without an effect input —
  is dropped; the montage decodes the footage itself). Export
  (oak-task) and the multicam single-track montage fill it too.
- The worker applies the stack between decode and composite: built-in
  Opacity gets a CPU evaluator (C++ opacity.frag parity — whole vec4,
  alpha included, unity pass-through); everything else dispatches as an
  OFX plugin job through a new instance-factory slot (oak-plugin
  lazily creates + caches one instance per identifier per render
  process) with the montage's parameters injected. Disabled effects
  bypass (the C++ traverser pushes the effect input through). Unknown
  types warn once per type id and pass through — no silent no-ops.

Not covered (explicitly): Transform/Crop and the other ~30 built-in
effects have no CPU evaluator in oak-render (they pass through with a
warning), keyframed parameter animation, audio effect chains, and the
CLI's simplified montage.

Acceptance: a real 50% Opacity on real media quarters the rendered
pixels both in-process (renderops test) and through a real worker
process over IPC + shared memory (procpool_integration test);
disabling restores the plain render byte-for-byte.
2026-08-25 04:49:00 +08:00
Mike-Solar 28f4ed655e app: refine the link toggle + human-readable node ports
CI / Build & test (Linux) (push) Successful in 18m51s
CI / Build & test (Windows) (push) Failing after 17m33s
Link toggle rule refinement (on top of 1ad2d71c8): the toggle now
unlinks only when the selection is FULLY linked internally — the C++
crude "any member has ANY link" check made split halves (which inherit
the original clip's A/V links via BlockSplitPreservingLinksCommand)
impossible to link to each other. Test: link_unlink_toggles_the_
selection_links covers toggle/undo/split-link.

Node editor: only stream inputs (texture/samples) become ports — OFX
plugins declare every parameter as an input, which buried the card
under internal params (the garbled NatronOfxParamProcess* wall). Secret
inputs stay hidden; port labels use the input's display name (the OFX
param label); edges resolve ports by input id, not by label.
2026-08-25 03:53:00 +08:00
Mike-Solar 1ad2d71c81 app: implement Link/Unlink for timeline clips
CI / Build & test (Linux) (push) Canceled after 8m56s
CI / Build & test (Windows) (push) Canceled after 7m16s
编辑 > 链接/重新链接 (Cmd+L) was an unhandled stub
(PanelCommandHandler::toggle_links returned false), so linking clips
never created graph links and linked drags never happened.

- graphops::set_clips_linked: one undoable entry toggling every pair of
  the selection; the undo restores the exact prior internal topology.
- RealEngine::toggle_clip_links: fully-linked selections unlink,
  otherwise the selection links together (the C++ crude "any member has
  ANY link" rule would have made split halves — which inherit the
  original clip's A/V links — impossible to link to each other).
- TimelinePanel overrides toggle_links, routing the timeline selection.
- MockEngine stores demo link pairs.
- Test: link_unlink_toggles_the_selection_links — dropped A/V pair
  toggles off/on with undo, and split halves link manually.
2026-08-25 03:40:57 +08:00
Mike-Solar 8679bef6de components: focus-gated spin wheel, spin text entry, combo outside-dismiss
CI / Build & test (Linux) (push) Canceled after 7m56s
CI / Build & test (Windows) (push) Canceled after 6m26s
Touchpad fixes from user testing:

- SpinBox: the wheel only steps a FOCUSED field — hover-wheel is inert,
  so two-finger scrolling across a dialog no longer drifts values.
- SpinBox: direct numeric entry — double-click opens the editor (the
  app's text input; commit on blur, Escape cancels), typing a digit on
  the focused field starts editing with that character. Previously the
  field accepted no text at all.
- ComboBox: a click anywhere outside closes the open popup
  (on_mouse_down_out).
- gpui bump: the timeline header-occlusion fix.
2026-08-25 02:51:44 +08:00
Mike-Solar 186c41b894 app: about dialog, modal close affordances, more preferences
CI / Build & test (Windows) (push) Failing after 16m35s
CI / Build & test (Linux) (push) Successful in 18m59s
- Help > About Oak… opens a real dialog (was a placeholder print): name
  + version, the GPL-3.0 line, the Olive fork notice and the thanks to
  Enzo GD (Community Promoter).
- Modal (gpui_widgets, submodule bump): a title-row close button on
  every dialog and opt-in backdrop dismissal (dismiss_on_mask) — Help >
  Search Actions and About use it, so they close on an outside click
  (previously a buttonless modal could only be closed with Escape).
- Preferences general tab grows two live settings: the cache-ahead
  pre-render window (PlaybackPreRenderFrames, consumed by the preview
  scheduler every playback tick) and the library storage backend
  (Storage/Backend sqlite/pg + Storage/PgUrl connection string, read by
  oak-storage's write-through when a project binds; the pg URL field
  shows only for PostgreSQL and commits on dialog close). The C++
  auto-recovery toggle is skipped on purpose: the Rust snapshot loop
  has no disable semantic, so the switch would be dead.

i18n keys for all eight packs; new tests cover the about modal and the
preferences writes.
2026-08-25 01:55:08 +08:00