Files
aiongg 0a881266c8 CI: xtask ci in the dev profile, one test binary per crate, nextest (#2860)
* Tests: one integration-test binary per crate

Each crate with several tests/*.rs files keeps them as modules of tests/it/,
so cargo links one test executable per crate instead of one per file. Files
that change process-wide state or time a workload stay binaries of their own.

* Profiles: line tables for workspace crates, no debug info for dependencies

* Windows ARM64: nightly and on release pushes instead of every push to main

* xtask ci: clippy and tests in the dev profile

The release profile's thin LTO and single codegen unit made linking the test
binaries take most of the gate. The codec and audio crates stay at opt-level 3
in the dev profile, so their tests run at release speed.

* xtask ci: nextest, doctests, no examples, one wasm check

Tests run with cargo nextest when it is installed (cargo test otherwise) and
build with --lib --bins --tests, so examples aren't linked; doctests run
separately. The wasm check keeps going past the first failing crate. VP8
fixture generation locks across processes, since nextest runs each test in
its own.

* VP9 synthetic fixtures: write aside and rename

A test in another process could read a fixture ffmpeg was still writing.

* Fuzz tests: a third of each sweep by default, all of it with FILMCRAFT_FUZZ_ITERS

Every seed, mutation kind and thread count still runs. Header bytes each get
one overwrite value and two bit flips in rotation, so every value and bit
index still occurs across the region. FILMCRAFT_FUZZ_ITERS=N runs the whole
sweep plus N random rounds per seed, as before.

* Platform fallback tests: one failure point per hybrid path

The failure-anywhere test injected a failure at four points around every
restart point; the hybrid's paths differ only in the first GOP, around the
second and last restart points, at the end and never. The replay test needs
the first two GOPs.

* Export tests: shorter ranges with the same checks

The bitrate test measures 0.5–1.5 s, where each preset reaches its own
target (on 0–3 s the simple demo left two presets at the same rate). The
every-preset test exports three frames per preset.

* Audio tests: effects in parallel, a table-driven FFT for the Opus quality metric

The extreme-settings test keeps both settings and the 40 s tail for every
effect, one thread each. The Opus quality metric's FFT builds its twiddles
once and allocates nothing per transform (same algorithm and summation order).

* CI: dev-profile gate with nextest

xtask ci no longer builds with --release, so the release-profile overrides
go; nextest is installed for the test step.

* CI: apt packages through the cached install-apt-packages action

* Review fixes: fuzz tests with a panic hook in their own binaries, loud fixture lock

The fuzz tests of av1, h264, hevc, mpeg2v, vp8 and vp9 count panics through
a process-wide hook, so a panic in another test of the same process would
fail them; they are binaries of their own again. The VP8 fixture lock fails
the test instead of skipping it silently, and the full-sweep doc comments say
how many truncation points the full run samples.

* Fuzz tests: rotate the default subset per run (FILMCRAFT_FUZZ_SEED replays one)

* platform tests: decode-switch tests in their own binaries, one NVENC lock
2026-10-23 13:34:16 -04:00

13 KiB
Raw Permalink Blame History

Monitor view options

The View menu and each monitor's wrench menu set how the Source and Program monitors show their picture. It is all UI state (MonitorView in crates/ui-egui/src/state.rs, serde): agents read it with ui.inspect (ui.program, ui.source) and set it with ui.set {"program": {…}}, or run the view.* UI commands. Commands act on params.monitor ("program" / "source"), else on the focused monitor (Program by default). Code: crates/ui-egui/src/panels/monitor_view.rs.

Command Menu Effect
view.playbackRes.<full|half|quarter|eighth|sixteenth> View ▸ Playback Resolution render scale while playing
view.pausedRes.<…> View ▸ Paused Resolution render scale while stopped (default Full)
view.highQualityPlayback View ▸ High Quality Playback play at the paused resolution when it is higher
view.display.<composite|alpha|red|green|blue> View ▸ Display Mode composite, or one channel as greyscale (colour channels premultiplied)
view.display.multicam Display Mode ▸ Multi-Camera the Program's multi-camera view (multicam.toggleView)
view.display.audioWaveform, view.display.videoAndWaveform Display Mode ▸ Audio Waveform / Video and Audio Waveform Split Source Monitor: the clip's waveform (click to move the source playhead); audio-only clips always show it
view.display.comparison Display Mode ▸ Comparison View Program: the reference frame (left) beside the current frame; the reference starts at the playhead and is stepped or reset with the buttons under it, or view.compare.setReference {time|seconds}
view.magnification.<fit|10|25|50|75|100|150|200|400|800|1600> View ▸ Magnification and the zoom dropdown 100% = one frame pixel per screen pixel. The mouse wheel (or a pinch) over a monitor zooms about the pointer, from Fit too, within 10%–1600%; drag with the middle mouse button, or with the Hand tool, to pan; Fit resets both. Each monitor keeps its own zoom and pan
view.showRulers, view.showGuides, view.lockGuides, view.clearGuides View toggles take {"enabled": bool}
view.addGuide View ▸ Add Guide… dialog, or {"orientation": "vertical|horizontal", "position": px}
view.snapInProgramMonitor View ▸ Snap in Program Monitor graphic moves snap to the frame edges / centre and the guides (6 pt)
view.safeMargins Guide Templates ▸ Safe Margins action- and title-safe boxes
view.guideTemplates.save, .manage, .apply, .delete Guide Templates ▸ Save Guides as Template… / Manage Guides… templates are stored in the user preferences (guides.templates)

Guides are in frame pixels. Drag out of the top ruler for a horizontal guide or the left ruler for a vertical one; drag a guide to move it (unless locked) and drop it outside the picture to remove it.

Automation ids: <monitor>.zoom, <monitor>.zoom.<level>, <monitor>.settings.<command suffix> (wrench menu items, e.g. program.settings.display.alpha), <monitor>.ruler.top|left, <monitor>.guide.<n>, program.compare.reference|prev|next|set, source.waveform, and in the dialogs guides.add.vertical|horizontal|position|ok|cancel, guides.save.name|ok|cancel, guides.manage.row.<n>|apply|delete|close.

Source playback

Video files, audio files, generated media and media subclips play in the Source monitor with its Play/Pause button or Space while Source has focus. The Source clock and playhead are independent of the active sequence; auditioning does not create timeline clips or change the project. Starting Source stops Program playback, and starting Program stops Source, so only one monitor owns audio. Scrubbing while playing seeks the Source clock and restarts its audio at that position. Replacing the open source stops playback. Playback stops at the last frame, and Play there starts again. Play auditions the full source span by default. Play In to Out and Play to Out address the marked Source range when Source has focus. The Source wrench menu can loop the marked range; Program keeps its separate loop setting.

Normal forward playback has sound. With Source focused, L plays forward and J backward; pressing the same key again doubles the speed up to 8×, K stops, Shuttle Slow Right/Left play at ¼ speed, and the left/right frame-step keys step the Source playhead. As in Program, shuttle speeds other than normal forward play without sound, and backward playback stops on the first frame. Source sequences are not yet supported and report an explanatory error. A failed audio device leaves the picture advancing with a status message; a decoding error stops Source.

Commands: source.playback.play, source.playback.stop, source.playback.toggle (no parameters), available through the UI command registry, CLI bridge and MCP. playback.toggle, playback.forward, playback.reverse, playback.slowForward and playback.slowReverse also accept monitor: "source"|"program" to override focus. ui.inspect.sourcePlayback reports playing, audioClock and playhead. Test: cargo test --release -p filmcraft-ui-egui --test it source_playback_ui::.

Drag a marked Source range to the timeline

An unmarked Source clip shows In at its beginning and Out on its last frame. These are implicit limits: opening a clip does not write marks or dirty the project. Set In/Out with I/O or the transport buttons; the saved marks are reused when reopening that clip. Out includes its frame.

Drag the Video only, Audio only, or Video + audio icons between Fit and playback resolution to an open sequence. Drop video on a video track and audio on an audio track. Both uses the dropped track and its corresponding track of the other kind, creating linked clips in one undoable edit. Unavailable stream choices are disabled. Dragging the video picture also brings its available streams; a vertical drag from an audio waveform brings sound, while a horizontal drag scrubs. The drag captures the selected range at its start. Source playback pauses when lifting a clip. A locked or unavailable destination reports an error without partially placing the pair.

Automation ids: source.drag.video, source.drag.audio, source.drag.both, source.picture, and source.waveform. Placement dispatches timeline.place with sourceIn, duration, video and audio; the stream flags default to true for existing callers. Engine tests cover range bounds, stream choices, linked placement, invalid parameters and undo/redo. UI tests exercise actual drags: cargo test --release -p filmcraft-ui-egui --test it source_drag_ui::.

Ctrl+Shift+X (Cmd+Shift+X on macOS) clears In/Out on the focused Source clip, including split points, and restores the implicit full span. It is undoable and leaves Program marks unchanged. The command markers.clearInOut accepts target: "source"|"program"; omitting it addresses Program. Source drag icons use the Windows hand cursor because its native Grab mapping is crossed arrows.

Source range braces use the playhead's theme color and the gray selection band is translucent, so the ruler ticks remain visible. This shared egui implementation applies to Windows, macOS and Linux. The clear shortcut uses the primary modifier: Ctrl on Windows/Linux, Cmd on macOS; I/O, Space and the three drag gestures are common to all three platforms. Runtime verification for this contribution was performed on Windows; macOS/Linux have not been run locally.

Adjust Source marks on the ruler

Drag either In/Out handle to trim the selected span, or drag the interior gray band to move both marks while keeping the duration. Handles show an original red bracket and arrows on hover. Edits snap to source frames, stay within the clip and retain at least one frame. A gesture previews without editing the project and commits one undoable project.setMarks command on release. Escape cancels; opening another source or a concurrent project edit also cancels the draft. Subclip handles are currently disabled.

Automation ids are source.range.in, source.range.out, source.range.body and source.settings.loop. Clicking the band's interior still scrubs.

Source-focused Go to In/Out, clear individual/both marks, clip-marker commands and previous/next marker address the open Source clip. Shift+Left/Right step multiple Source frames using the preference. UI callers can override focus with monitor: "source"|"program"; engine marker/navigation commands accept target: "source"|"program" and default to Program. Source marker edits leave sequence markers and the Program playhead unchanged.

Coverage: engine source_monitor::tests validates frame bounds and hostile inputs; UI source_drag_ui covers release, cancellation, range movement and command routing; source_playback_ui covers marked-range stopping, looping and the last-frame boundary.

Program range handles

Program uses the same In/Out hover cues, frame-snapped trimming, duration-preserving range movement and one-edit undo behavior. It targets the active sequence through project.setMarks and leaves an independently opened Source clip's marks and playhead unchanged. Program shows only explicitly set marks; clearing them hides the corresponding handles. Range movement requires both Program marks. Empty sequences disable the handles. Changing the active sequence cancels an unfinished draft. Automation ids: program.range.in, program.range.out and program.range.body. Both monitors share the original brace artwork, playhead-blue color and translucent gray band. Runtime review remains Windows-only.

The playhead has drag priority above the range band and trim handles, including a full-span selection. Drag it to scrub; the unoccupied upper ruler can also scrub. Drag the gray band away from the playhead to translate the marked range.

Zoom the time ruler

Under each monitor's time ruler is a zoom scroll bar like the Timeline's. Its thumb is the part of the clip or sequence the ruler shows: drag a handle at either end to zoom in or out, drag the middle to scroll, or press beside the thumb to move it there. A thumb filling the bar shows the whole duration; the ruler zooms in as far as five frames. The playhead, markers, In/Out and ticks follow the part shown, and clicking or scrubbing the ruler sets a time within it; what lies outside it is not drawn. While the monitor plays, a zoomed ruler follows the playhead as the Timeline does (Settings ▸ Timeline ▸ Timeline Playback Auto-Scrolling): it pages when the playhead runs off the right edge or jumps before the left one, or keeps the playhead in the middle with smooth scrolling, and keeps its width; scrubbing and clicking never move it. Each monitor keeps its own range in its view state (time_zoom: start and end as fractions of the duration, null for all of it). Automation ids: <monitor>.zoomBar (the thumb) and <monitor>.zoomBar.track.

Export Frame

The camera button in either monitor opens Export Frame. Drag its white header to move the dialog. The suggested name is the source filename without its extension, or the sequence name, without an added .Still suffix. Choose a name, format and depth, use Browse to choose the destination folder (or edit Path), and click OK. Existing files require an explicit Replace confirmation. Cancel, the header close button, Escape, and clicking outside the dialog (including the top menus or mode tabs) dismiss it without writing anything. The dialog's own dropdowns and Browse button keep it open. The white header and dark body use original generic controls. Source details and frame time appear below the settings. Frame time uses the same hours/minutes/seconds/frames timecode as its monitor, including the Program sequence's drop-frame setting, and stays captured while the dialog is open. The last successful folder is reused during the current app session.

PNG and TIFF support 8 or 16 bits per channel. The 16-bit path quantizes directly from the float image, preserving precision beyond 8 bits. JPEG (quality 95) and BMP support 8-bit output; choosing either resets Depth to 8 Bit and disables 16 Bit. The command rejects unsupported format/depth combinations. In the browser the still is offered as a download. Import into project adds the saved still as one undoable edit. Failed writes retain the settings and report an error.

The monitor, item and frame time are captured when the settings open. Export uses the original full-resolution media, even with proxies or reduced preview resolution enabled. Source exports the source picture at its media playhead; Program exports the sequence composite, effects and captions at its timeline playhead. Audio-only Source clips have no picture to export.

The shared file.exportFrame command accepts target: "source" | "program" (Program by default), format: "png" | "jpeg" | "tiff" | "bmp", depth: 8 | 16 (default 8), optional item for Source or sequence for Program, and an exact integer time in ticks. Supplying path executes directly; invoking it through the UI without a path opens settings, routed to the focused monitor unless an explicit monitor/target is provided.