254 warnings (320 counting replayed-cache re-emitters) cleaned:
unused mut/imports/variables, irrefutable if-lets and unreachable
patterns, dead code removed or annotated #[allow(dead_code)] with
the reason (C++ parity value sets, cfg(test) helpers, public API
reservations), drop(&ref) no-ops removed, fn-pointer identity via
std::ptr::fn_addr_eq, the test-stubs feature declared in
oak-node's manifest, missing docs filled. Every unused-Result site
was judged individually: meaningful errors propagate, intentional
ignores are let _ = with a note.
Two pre-existing latent bugs are documented in place, behavior
preserved: app.rs's timeline-tool observer and dialogs.rs's format
subscription both drop the returned Subscription immediately, so
they never fire.
The MinGW path kept fighting the environment (the GitHub image's
MSVC INCLUDE/LIB poison the GNU compiles; ocio-sys' fork then
dragged the MSVC-only headers into g++ and died on vcruntime.h).
The Windows jobs on both workflows now:
- run on warp-windows-2025-vs2026-x64-16x (preinstalled VS 2026)
with the stable MSVC Rust toolchain;
- install dependencies through vcpkg MANIFEST mode: vcpkg.json at
the repo root carries FFmpeg with every free codec + hwaccel
(mirroring build-ffmpeg.sh's configure), pkgconf and librsvg;
the vcpkg_installed tree plus the binary-cache archives are
cached on the manifest hash with save-always;
- build OCIO bundled (ocio-sys' vendored sources are what MSVC
wants — the MSYS2 package was the workaround, not the
preference), so OCIO_RS_NO_MSVC_INCLUDES is gone;
- ship the vcpkg runtime DLLs next to the binaries in the NSIS
installer with a static-CRT release build (no vcruntime DLLs),
replacing the ntldd-based MSYS2 bundling.
FFmpeg version pinning via builtin-baseline is a documented
follow-up: the Configure step logs vcpkg list so the first green
run reports the resolved versions. docs/build.md keeps the MSYS2
flow as the local alternative and points at the CI path.
macOS has no posix_fallocate (and the libc crate rightly does not
expose it there), so the shm segment setup failed to compile. The
eager reservation is a tmpfs concern; off Linux the call is skipped
and the existing touch-every-page fallback runs instead.
The GraphInput/GraphOutput cards render through the same build path
as every other node (real graph data, fixed header accents outside
both palettes), GraphOutput as a pure sink with no output ports.
UI-layer protection keeps the pair fixed: the context menu drops the
whole edit section (cut/copy/paste/duplicate/rename/delete) for
them, and delete requests naming an endpoint are narrowed at the
panel — the endpoints and the wires hanging off them always stay,
a request left with nothing is dropped whole. The engine reports the
protected set through AppEngine::protected_graph_nodes (the real
engine resolves Graph::endpoints; the mock demo graph carries its
own marked pair).
Per docs/zh/plans/render-pipeline-threads.md §3.8:
- oak-node/nodes/graphendpoints.rs: the GraphInput/GraphOutput
virtual node pair — factory-registered but hidden from every create
menu, duplicate refused, real value() semantics (the input forwards
its feed_in row, the output publishes its tex_in as the frame).
The input endpoint also declares a connectable feed_in port
(documented deviation: footage/generator sources have no connectable
inputs, so the walk needs a feeder anchor).
- graph.rs: ensure_endpoints/endpoints/is_endpoint — idempotent,
identified by type id, default input->output edge only while the
output's tex_in is free; remove_node refuses endpoints.
- project.rs + serializer.rs: every project graph carries the pair;
a legacy file without endpoints migrates on load (roundtrip and
legacy-migration tests, re-save is idempotent).
- traverser.rs: eval_graph_bfs — the endpoint-to-endpoint Kahn
sweep. Live set = (input's forward cone U its feeder cone) INTERSECT
(output's backward cone); multi-input nodes dequeue at zero
in-degree over the live subgraph; deterministic ascending-id ready
order (Graph::edges is a BTreeSet, so insertion order is
unrecoverable — documented); time-shifted upstreams pull through
the shared DFS memo (walk_dfs, factored out of evaluate);
un-orderable remainder reports a named cycle; missing endpoints /
unreachable output are errors. Eight BFS tests cover the plan's
acceptance bullets.
- oak-render: bfs_endpoint_sweep_renders_footage_through_position —
real clip through a real Position node via the sweep, shifted
pixels asserted against a reference decode.
- Endpoint names localized in all eight i18n packs; storage/structure
tests updated for the two extra nodes.
M0a of the render-pipeline plan (docs/zh/plans/render-pipeline-threads.md):
- oak-node: every payload push site (58 across footage.rs, plugin.rs
and the nodes/* effects) now boxes the Job enum instead of the raw
payload. The enum gains CacheJob with a CacheJobPayload (path +
time + fallback value, the C++ cachejob.h shape), plus safe as_*
accessors and unsafe probe helpers beside job_ref.
- oak-render: RenderEvalHooks::resolve is one loop over the table —
a single get_checked::<Job> probe per texture value, a match
dispatch to process_footage/shader/plugin/color_transform/cache,
and recursive resolution of the job boxes embedded in a payload's
inputs (depth-capped, cycle-guarded) — replacing the four
sequential full-table scans (resolve_*_jobs, deleted).
- The disk frame cache is real: frameio.rs implements a minimal
self-describing F32 container (magic/version/dims/format/timestamp
+ payload, tmp-write + atomic rename, full header validation on
load) because the OIIO bridge is a stub and EXR is unavailable in
this build; process_cache_job genuinely reads the file before
falling back to the job's (already resolved) fallback value.
- Tests: CacheJob roundtrip (save -> resolve -> pixel equality),
missing-file fallback, nested cache-job-through-shader resolution,
plus four frameio container tests. 2330 passed, 0 failed across
the workspace.
Two user-mandated amendments:
- Decode must be GPU wherever possible and share the render GPU's
memory: hardware surfaces (NV12/P010) are imported as GPU textures
via the platform interop paths (DMA-BUF / DXGI / IOSurface /
CUDA-Vulkan), av_hwframe_transfer_data is never executed on the hw
path, and CPU decode + staging upload demotes to fallback only.
FFmpeg hwaccel first (the hwdecode.rs device model already builds
the device contexts; upstream Olive has no hw decode at all, so the
reference for this part is FFmpeg + the existing crate), hand-written
GPU decode strictly second. YUV->RGB becomes a built-in GPU pass
replacing CPU swscale. Milestone M5 becomes the GPU-decode
zero-copy track with HW_TRANSFERS zero as its acceptance counter.
- The Job graph becomes a real adjacency structure (no linear table,
no 2D array, possibly not fully connected) with a fixed pair of
virtual GraphInput/GraphOutput nodes per graph: connected by
default, undeletable, un-duplicable, shown in the node editor.
resolve is a Kahn-style BFS from the input node — multi-input joins
wait for every input, multi-output fans out, the order is
deterministic and graph-explicit, cycles error out, unreachable
nodes never run — until every branch converges at the output node.
M0 splits into M0a (Job enum + single-loop match) and M0b (virtual
endpoints + BFS + node-editor display).
Task book for the render-pipeline rearchitecture: one decode thread
and one render thread feeding queue-linked stages with the main
process presenting (the GPU's single queue makes the multi-process
backend dead weight), one dedicated OpenFX host process with
bounded respawn, GPU-resident frames end-to-end except at the CPU
OFX/export/cache boundaries, a per-backend interop table, and the
resolve rewrite to a single-loop match over a completed Job enum
(CacheJob included) following upstream Olive's
NodeTraverser::ResolveJobs. Milestones M0-M5 with the thread backend
kept behind an OAK_PIPELINE fallback switch.
The off-frame mask edit redeclared vec2 uv inside main(); naga's GLSL
frontend rejects the redeclaration, so the swirl shader failed to
translate for wgpu.
A clip drag moved only the clip under the cursor (plus its graph-linked
A/V partner); the rest of the selection stayed behind. The engine now
keeps the full timeline selection (it used to collapse it to the
effect-stack's single target) and expands ClipMoveRequested to the
grabbed clip's transitive link group UNION, when the grabbed clip is
part of the multi-selection, every other selected clip and their link
groups. Each follower stays on its own track and shifts by the same
frame delta (relative positions preserved), the group-wide clamp keeps
every clip at or after frame 0, and the whole move is one undoable
entry. Followers on locked tracks are left in place.
moving_a_multi_selection_drags_the_whole_group covers two A/V pairs:
selecting both video clips and dragging one moves all four clips by
the same delta, and one undo restores them.
Dropping a transition on the timeline failed with "the track has no
frame rate": drop_transition_at asked sequence_time_base for the TRACK
node, but the frame rate lives on the sequence (tracks carry no video
params, so the lookup always returned None).
With that fixed the drop deadlocked instead: the edge resolution ran
while holding the project lock, and the add_transition_at_seam/edge
builders lock the project internally. The function now plans under the
lock and executes after it is released.
Covered by engine_drops_a_transition_at_a_clip_edge: a head-edge drop
with no previous clip lands a single-sided transition wired into the
clip only.
The Gitea migration left CI on the self-hosted instance with every
cache commented out and the CD workflow broken at parse time
(kiname:). CI runs on GitHub-hosted runners again:
- Linux: ubuntu-latest with the rust toolchain from
dtolnay/rust-toolchain (the self-hosted runner's custom
RUSTUP_HOME/CARGO_HOME lines are gone) and the cargo + FFmpeg caches
re-enabled.
- Windows: windows-latest with msys2/setup-msys2 provisioning the
UCRT64 environment (the Gitea runner had it preinstalled); the
msys2 {0} shell, the GNU-target MSYS2 Rust, the
OCIO_RS_NO_MSVC_INCLUDES gate and the -lmsvcrt link-order workaround
carry over, as do both caches.
- macOS: new job on macos-14 (Apple Silicon) — Homebrew deps, vendored
static OCIO, cached FFmpeg, cargo check + the full suite with the
same retry-once flake policy.
CD fixes: the kiname: typo that kept the workflow from parsing, the
Warp runner labels become the standard GitHub ones, and all eight
commented-out cache blocks are restored. The gpui submodule URL
follows the move to GitHub, the README badge points at the GitHub
workflow, and the .gitea directory is dropped.
Generator effects (bars, checkerboard) can be dragged from the library
onto the timeline, where they land as a standalone five-second clip
built from the node factory; the inspector shows the generator's
parameters as the clip's own chain.
Transitions are no longer junction-only. The render planner accepts a
transition with at least one wired neighbor and blends the missing
side against transparent black, so head transitions fade in from black
and tail transitions fade out to black. add_transition_at_edge creates
those single-sided blocks (wired to just the IN or OUT block), the
default-transition command covers both ends of a lone clip, and an
effect drag dropped near a clip edge routes to the nearest seam or
edge within a one-second window.
Translating, rotating or warping content past the frame edge used to
smear the clamped edge row/column across the vacated region. The
transform, position, swirl, ripple and wave shaders now multiply the
sample by an in-bounds mask so off-frame pixels come out transparent
(and composite as black when nothing sits below). Tile deliberately
keeps its wrapping lookup.
Typing in a text input fired the global key bindings (space toggled
playback, Delete removed clips, ...). Every non-multicam binding now
carries a !EditableText context predicate so the app's own bindings
stay inactive while an editable text element has focus.
Predicate-gated bindings never match on an empty context stack, and
the app shell had no root key context — so the root element now sets
key_context("OakApp") to keep the dispatch stack non-empty
everywhere outside the panels.
The render worker process never installed a text backend, so text clips
rendered as empty frames in playback and export. The cosmic-text engine
now lives in oak-render (the crate both the app and the worker link),
and the worker installs it during runtime initialization.
Adjustment layers (docs/zh/plans/adjustment-layers-and-transitions.md):
a new timeline block type whose effect chain grades the composite of
every video track below it, over its own range (spanning clips or a
slice of one). The graph path flushes the lower tracks at the block's
track boundary and sweeps the composite through the chain via a
transient texture-source node; the montage path mirrors it with
AdjustmentSpan tickets (wire-compatible), so worker previews and
exports agree. An empty-area context menu creates one; the block
trims/moves/deletes like a clip, with undo everywhere.
Transitions: seam blocks come alive - cross dissolve/fade/wipe/slide
evaluate both neighbors through the graph path with progress from the
transition's own range (never the whole clip). Ctrl+Shift+D or the clip
menu inserts a default transition; the gpui wedges render and drag to
resize offsets undoably, and TransitionRemoveCommand now restores
offsets and edges on undo. The transitionfx node form runs the same
shaders on an adjustment layer with progress_in auto-filled from the
layer's span (explicit value wins).
Also: every built-in effect name and parameter name is now
translatable (360 node.* keys per locale, zh-CN fully translated, two
coverage tests guard future gaps); the new nodes register in
nodes/mod.rs with the factory smoke table updated; textfootage and
adjustment-layer i18n keys included.
Text is no longer a hand-written HTML effect (docs in
docs/zh/plans/text-footage-redesign.md):
- textv3 gains structured inputs - plain text, font family/size, font
color, outline (enable/color/width), glow (enable/color/radius); the
legacy text_in HTML is hidden and auto-migrated to plain text on load.
- Outline and glow render as GPU post-process chains (dilate/blur +
colorize under the text); the font color tints the raster
premultiplied. Plain text now rasterizes even with both passes off
(previously a null deferred job), fixing a use-after-free where the
handle was lifted out of an owning Option<NodeValue> before addref.
- A cosmic-text backend (the lockfile's 0.19) installs at engine
startup through the textbackend hooks and feeds the font-family combo.
- The project panel gains 添加文本素材 next to 新建序列: a text entry
in the bin that drops onto the timeline as a clip (one undo row), its
parameters shown as structured fields in the inspector (multiline
text area, no HTML anywhere). text3 is hidden from the effect add
menus; legacy text3 chains keep evaluating.
21 built-in effects reimplemented as native GPU nodes from the
OpenFX-Misc algorithm references (cleanroom, docs in
docs/zh/plans/ofx-misc-gpu-cleanroom.md):
- Color: Color Correct, Gamma, Saturation, Invert, Clamp, Grade
- Matrix/morphology: Color Matrix, Edge Detect, Dilate, Erode
- Blur: Directional Blur, Sharpen (unsharp mask)
- Merge: Dissolve, Key Mix, Premultiply, Unpremultiply
- Geometry/generators: Position, Mirror, Checkerboard, Color Bars, Ramp
Every node carries unit tests plus GPU pixel tests (28 cases over five
ofxmisc_* suites). The effect library groups built-ins by category
(color/filter/distort/keying/generator/math/general) with collapsible
group headers persisted to the config; the inspector's add menu groups
the same way. Registration wiring and the factory smoke table land with
the adjustment/transition wave sharing the same files.
The params view reapplies the engine snapshot to every control on each
render (every engine tick), which wiped the in-progress text a frame
after each keystroke - the text effect's field was effectively
untypeable. Skip the re-sync while the field is focused (the same
guard the curve editor has for drags); it re-syncs on blur and the
row's explicit commit writes the edit back. MockEngine gains a
sentinel text parameter for the regression test.
Edits invalidated the display caches, but the debounced snapshot
upload (150 ms) landed later - meanwhile the viewer had already
rendered and cached a proxy through the stale snapshot, and nothing
invalidated it again, so a paused monitor kept the pre-edit picture
until the playhead moved. Re-invalidate when the fresh snapshot is
actually installed so the next paint re-renders from it.
The transform shader sampled in a top-left-origin pixel space while
Olive's transform semantics (and every other node) are center-origin:
rotation swung the image around the top-left corner, pushing it partly
off-frame - reading exactly like an unwanted zoom. Match the C++
transform.vert projection: position (0,0) is the frame center and
rotation/scale pivot around the anchor, so rotation and scale stay
independent user controls. GPU tests pin the 90-degree landing spot
(no smearing) and the 2x scale centroid (stays centered).
- available_range declared a zero duration for every clip (the media
was 'unusable', so Resolve invalidated all of them): it now covers
the whole media - video clips in the probed frame rate, audio clips
in the probed sample rate (no more hardcoded 48000/0).
- Gap ranges mixed time bases (a 24 fps block start, a 1.0 trailing
duration): gaps now stay in the sequence's time base, the trailing
pad in whole frames.
- Empty sequences (no clips) are skipped, so a lone edited sequence
exports as a bare Timeline root instead of a SerializableCollection
Resolve cannot read.
- FCPXML: an asset used on both video and audio tracks now declares
hasVideo AND hasAudio (the first registration's track kind used to
hide the other stream); asset durations recover with available_range.
- Runtime window icon: X11 takes the embedded 512px PNG up front
(_NET_WM_ICON), Wayland resolves it from oak.desktop via the app id,
Windows reads it from the exe's icon resource.
- assets/appicon: 512px PNG + 7-size ICO rasterized from Oak_Icon.svg.
- Windows: oak.rc (ICON resource ID 1, also the Explorer icon) compiled
by build.rs through embed-resource.
- Linux packaging (deb/rpm/PKGBUILD/oak.spec): install Oak_Icon.svg
into the hicolor scalable theme alongside the existing 512px PNG.
- macOS: no change needed - cargo packager already builds the .icns
from the CD-generated icons/icon.png (same source art).
Two export killers fixed and the feature surfaced properly:
- TicketArena::allocate() reaps finished fire-and-forget slots on every
new submit, but RenderTask classified finished tickets by asking the
arena for their kind/time afterwards - any ticket that completed
before the next submit (all of them on the inline backend) failed
with 'Render ticket reported an unexpected timestamp' and the export
died after writing a header-only file. RenderTask now records each
ticket's delivery key at submit time instead.
- File > Export becomes 导出序列 (all 8 locales): the dialog offers a
sequence picker (any sequence in the project, the current one
preselected) instead of silently exporting the current one, and the
project bin's sequence context menu gains 导出序列 opening the same
dialog. Engines expose sequence_entries / current_sequence_id /
start_export_of.
- The export-project dialog drops its dead hand-typed path field (OK
already asked the platform save dialog).
- New end-to-end test: a one-second sequence exports to a real
H.264/AAC MP4 that probes and decodes back to the clip's content.
Preview renders at proxy size while a paused frame renders full-res, so
anchoring resolution_in to the render target made every sequence-pixel
effect (shape size/pos, transform offsets, corner pin points, drop
shadow distance) change apparent size whenever the transport stopped.
Pre-fill resolution_in from the sequence's video params (C++ inserts
the NodeGlobals square resolution at job-build time), covering the
nested generator job inside a merge as well; a node that inserted its
own resolution_in keeps it.
The sampled pixel lands (disarming the eyedropper) between the button
going down on the program viewer and coming back up, so the trailing
mouse-up read as an outside click: the popup closed and close_menu
discarded the just-picked draft - pick-from-viewer could never be
confirmed. Swallow the pick's own mouse-up once; a later genuine
outside click still dismisses.
naga's WGSL emitter rejects fall-through-capable GLSL switch blocks, so
every shape and despill job failed to compile and silently fell back to
the effect input - both effects were no-ops. Rewrite the type/method
dispatch as if/else chains (same semantics as the C++ shaders) and
cover all three shape types plus green-screen despill with GPU pixel
tests. Also drop the now-stale nested-payload/merge-binding TODO notes.
- process_shader_job: bind all texture params by name, recurse into nested
shader payloads (depth cap 8), fall back to frame size without inputs
- run_effect: take iterative_input so dropshadow previous_iteration_in works
- merge: actually composite inputs; keyer mask, opacity modulation, math
texture ops and mrg generator layers now bind their textures
- transform distort: real fragment-side inverse-matrix sampling
- time offset / time remap: wire NodeBehavior time adjustment hooks
- plugin: fix first-node identity colliding with unbound sentinel
oak-common is gone; its modules (configstore, xmlutils, ocioutils,
oiioutils, colormath, colortransform, videoparams, ffmpegutils, ...)
now live in oak-core alongside the value types. The render value/GPU
types moved too: backend (wgpu context + DisplayRenderer), color
(ColorProcessor over ocio-rs), texture, frame, and the commonutil
config helpers.
Fix-ups to make the merged tree build and pass tests:
- oak-core Cargo.toml: wgpu back to 25 (the moved backend code is
written against that API generation); add the toml/quick-xml/image
deps oak-common carried.
- lib.rs: drop the duplicate 'pub mod error;'.
- error.rs: unified OAKCORE_* codes; restore Error::new() and
From<OcioError> from oak-common's error type.
- backend.rs/color.rs: oak_core::/oak_render:: self-references
rewritten to crate::; the shaderfx-dependent GPU effect test moved
to oak-render's shaderfx tests (shaderfx depends on oak-node and
cannot live in oak-core).
- oak-render's error module re-exports oak_core::error::{Error,
Result}; the OAKRENDER_* codes stay as the public-code contract.
- oak-node jobs.rs: ColorProcessor imported from oak_core::color.
- Integration tests repointed at oak_core::{texture, frame, backend,
color, colormath}.
- the display-ICC regression test treats an empty OAK_DISPLAY_ICC as
unset, matching displayicc::env_override_icc.
The OCIO grading nodes previously pushed null texture handles; they now
push real ShaderJobPayloads whose GLSL is the OCIO-generated dynamic
grading-primary GPU shader — the exact code the C++ path applies, so no
approximation:
- color: grading_primary_function_shader(style) builds a dynamic
GradingPrimaryTransform (LIN/LOG) on the default config, extracts the
GLSL via GpuShaderDesc (function 'ove_grading_primary', resource
prefix ocio_, no LUT textures) and caches it per style + config id.
- eval: OCIO_GRADING_STUBS maps the two node type ids to the grading
style; process_shader_job resolves the stub and splices it into the
node's %1 marker (same wiring as the chromakey OCIO stub); the
pipeline cache key folds the stub text so a config change recompiles.
- nodes: value() pushes a ShaderJobPayload with the C++ value()
rewrite applied to the row — vec4 (RGBM x=master) grading inputs to
the vec3 GPU uniform form (lin: contrast RGB=c*m, offset RGB=c+m,
exposure RGB=2^(c+m); log: lift RGB=c+m, gain c*m, gamma c*m), plus
pivot/saturation floats, the log pivotBlack/pivotWhite normalization
range (0/1), clamp sentinels (NoClampBlack -1 / NoClampWhite 2),
white>black enforcement per frame, and localBypass=false. Generated
uniform names bind by name (the log node's OCIO_NAMESPACE_ id text
normalizes to the ocio_ resource prefix).
- Tests: grading stub generation (analytic GLSL, cache) in color,
end-to-end GPU exposure doubling for lin (+1 stop on 0.2 gray -> 0.4)
and lift for log, node payload rewrite assertions, and the
all-shaders sweep now retries grading stubs. oak-render 181,
oak-node 441, oak-app 272 lib tests pass.
Polygon and mask previously pushed null texture handles ('fake'
implementations). They now generate real ShaderJobPayloads and render
through the existing GPU shader pipeline:
- shaderfx: std140 uniform array support (Vec4Array(N)) — the parser
accepts 'uniform <type> <name>[N];' declarations, translate()
re-emits them as vec4[N] block members with per-element std140
offsets, and pack_uniforms writes array items from the new
NodeValue::Vec4Array value, padding short arrays to the declared N.
- polygon: value() collects the inherited points array (row element
keys 'points_in[i]', else the node's own per-element values — an
unconnected array resolves to the default pentagon via
GetValueAtTime parity) and pushes a ShaderJobPayload; the 'rgb'
fragment shader rasterizes the closed point loop with an odd-even
fill in screen space (center-translated, y-flipped to match the C++
point convention) and outputs color_in inside / transparent outside.
The CPU QPainterPath generate_frame stays a documented no-op.
- mask: value() pushes a single ShaderJobPayload whose new 'mask'
fragment shader folds the whole C++ chain into one GPU pass — base
texture multiplied by the polygon matte, optional invert, and the
optional feather gaussian softens the matte during sampling (the
separable blur.frag h/v iterations as a one-pass product,
density-normalized, radius capped at 16 px).
- Tests: translate/pack array coverage in shaderfx, GPU end-to-end
rasterization of the pentagon (center white, corner transparent),
mask multiply/invert/feather on real frames, and updated oak-node
payload assertions. oak-render 178, oak-node 440, oak-app 272 lib
tests pass.
Tests run in parallel inside one process; each GraphSnapshotStore::new()
used the same 'oakrender-snapshots-<pid>' root, so one test's cleanup()
deleted another test's live snapshot — the
acquire_rewrite_forces_file_rewrite_on_same_key flake on the 16-core
Windows runner (file written, then exists() == false). Append a per-store
sequence number to the directory.
- multicamnode: type_id() assertions called the method on dyn
NodeBehavior, which method resolution routed to std::any::Any's
TypeId::of — qualify via NodeBehavior::type_id so the trait method
(the &str node type id) is compared
- procpool: the per-worker GPU budget grew a security headroom (×2)
for the CUDA-OOM flood; the two budget tests now assert against the
real formula (2 GiB + 256 MiB at 1080p24, 2 workers at 4K/24 GiB)
- cli info fixture: the fixture runs 29.97 fps; the assertion expected
30/1 (stale from the older fixture)
The extra oak_task export fields (bitrates + color override code
points) added in the export-settings protocol are zero/defaults in the
CLI's sync exporter — audio/video bit rates codec-default, no explicit
color override (the project's delivery colorspace applies).
- 导出工程文件: the in-app dialog only picks the format (OTIO / OVE /
FCPXML); OK asks the system save dialog with the suggested file name
carrying the format's extension, then exports
- 打开项目: the system open dialog restricts the choosable files to
.ove/.ovexml/.otio/.fcpxml (PathPromptOptions.allowed_extensions
plumbed through gpui; Linux portal glob filter)
Mirror the .github workflow fixes:
- ocio-env.sh has no exec bit (git doesn't preserve one reliably) —
bare invocation fails 'Permission denied'; call it through bash in
both the Linux and Windows configure steps
- ocio-sys is the GIT fork, so the Windows registry-cache unpack +
'ls .../ocio-sys-0.2.1/build.rs' glob can never match (git deps have
no .crate archive); the fork's build.rs already carries the
fix — the unpack/sed block is removed
The script has no executable bit (git doesn't carry one reliably), so
the bare invocation failed with 'Permission
denied' in the Linux act runs. Every call site now uses
OCIO_RS_ENABLE_REAL=1
OCIO_RS_LINK=static instead.
Linux: 'libffnvcodec-dev' was dropped from Ubuntu noble — the NVDEC
headers are distribution-free, so install them from source
(nv-codec-headers git) like the Fedora branch already did; the
appimage step and install-deps.sh no longer apt-install the package.
Windows: ocio-sys is the GIT fork (pinned in the root manifest), so
the old crate-unpack + build.rs glob patch (registry/cache's
ocio-sys-0.2.1.crate) can never match — git dependencies don't ship a
.crate archive and unpack under registry/src/git/<hash>, hence the
"ls .../ocio-sys-0.2.1/build.rs: No such file" failure. The fork's
build.rs already carries the GNU-toolchain fix; the unpack/glob/sed
step is removed.
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.
- 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)
- 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.
- 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)
- 文件 → 新建项目 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)
文件 → 导出工程文件… 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.