* 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
* packaging: use [[ for bash conditional tests (SonarCloud S7688)
SonarCloud flags every bare [ conditional in bash scripts under packaging/ as a reliability smell (rule S7688). Convert the flagged [ ... ] tests to [[ ... ]]: safer word-splitting and no glob surprises, same semantics for these string and file tests.
* tools: quit the super-linear regex scans in the spec table extractors (SonarCloud S8786)
SonarCloud flags five regular expressions in the AV1/HEVC extract_tables.py tools as super-linear: a lazy (.*) under re.S that rescans the whole remaining spec text at every position, a \s*([^|]+?)\s* sandwich, and \s+(.*) tails that can absorb the same whitespace repeatedly.
- the fence block reader now walks the ~~~~ c / tilde-run pairs with two linear searches instead of the lazy .*? findall; block-pair and 7+ tilde-run semantics are unchanged
- the symbol-row capture restricts its first and last characters away from [|] so the lazy group's backtracking jumps are gone (identical for every non-blank cell; a cell of only spaces is no longer captured, which the table rows never contain)
- the empty-comma cell splitter no longer lets \s* swallow the newline it anchors on; consecutive line breaks inside a cell no longer split (spec cells are single-line rows)
- initValue / ScalingList value captures start on a non-space so \\s+ and .* no longer share a split point (captured text is unchanged for all real values)
Verified old vs new run on synthetic spec markdown: byte-identical spé tables output and stderr.
SonarCloud flags every bare [ conditional in bash scripts under packaging/ as a reliability smell (rule S7688). Convert the flagged [ ... ] tests to [[ ... ]]: safer word-splitting and no glob surprises, same semantics for these string and file tests.
* Use chrono for UTC/calendar conversion instead of hand-rolled copies
Replaces the hand-rolled civil_from_days / days_from_civil copies (app log
timestamp, Media Browser date column, auto-save file-name stamp, MXF header
timestamp, golden-image attribution date) with chrono as a workspace
dependency. The existing chrono entry on apps/filmcraft (clock feature) was
lifted to the workspace. Output formats are unchanged and pinned by tests.
AGENTS.md now says time goes through chrono.
Follow-up to storytold/cadcraft#1785
* Never panic on a clock outside chrono's range; keep 5-digit years unprefixed
chrono's From<SystemTime> unwraps internally, so a clock past year 262143
panicked. logging::timestamp and golden::today now read the duration since the
epoch and go through the fallible DateTime::from_timestamp, reading an
out-of-range or pre-epoch clock as the epoch as the old saturating code did.
timestamp, today and media_browser::format_date write the year with {:04}
rather than %Y, which prefixes a '+' past year 9999.
Tests cover the far-future and year-10000 cases for timestamp and format_date.
* Don't build an unrepresentable SystemTime in the far-future test
UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows'
SystemTime and panicked before timestamp() ran, failing the Windows
test job. Build it with checked_add and only assert where it exists.
* time: preserve out-of-range years instead of the epoch (FredApps review)
`DateTime::from_timestamp(..).unwrap_or_default()` changed the existing output:
inputs the old hand-rolled civil-from-days converted (e.g. `10_000_000_000_000`
s → 318857-05-20 17:46:40) collapsed to `1970-01-01 00:00:00`. Restore the old
conversion domain with a cycle-based helper: reduce by whole 400-year Gregorian
cycles (146_097 days), convert the in-range remainder with chrono, and carry the
year back as i128 (saturating to the output type). Only the year leaves chrono's
range; month, day and time of day are always exact.
Sites (all UTC):
- apps/filmcraft logging.rs `timestamp` (SystemTime, u64 secs): pre-1970 still
clamps to the epoch; a far-future clock keeps its year. u64::MAX/2 s →
"292277026596-12-04T15:30:07.000Z", 10e12 s → "318857-05-20T17:46:40.000Z"
(was "1970-01-01..."). SystemTime cases built with a checked add (Windows-safe).
- crates/engine media_browser.rs `format_date` (u64): 10e12 s → "318857-05-20
17:46", u64::MAX → "584554051223-11-09 07:00" (was "1970-01-01 00:00").
- crates/format autosave.rs `civil` (i64 + i32 offset): keeps years, e.g.
i64::MAX → 292277026596-12-04 15:30:07, i64::MIN → -292277022657-01-27. The
separate `unix + offset_secs` overflow guard (checked_add) is kept: only an
overflowing shift (old code wrapped it to garbage) reads as the epoch.
- crates/mxf from_unix (i64): keeps month/day/time, clamps only `year` to the
u16 field as before. 10e12 s → year 65535, May 20 17:46:40; -10e12 s → year 0,
Aug 13 06:13:20.
- crates/testkit golden.rs `today` (u64): same helper; pinned via `utc_parts`.
chrono stays the only calendar logic (NaiveDate::from_num_days_from_ce_opt on the
in-cycle remainder). No new chrono features; still default-features = false, std.
* Name the 400-year range extension as the one exception to the time rule
The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.
* Say the library computes month and day; time-of-day is plain arithmetic
Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
* security(exr): validate chunks before frame allocation
* exr: raise the decoded-buffer cap to 2 GiB so 8K float plates open
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* security(prores): bound decoded plane storage
* prores: raise the decoded-plane cap to 1 GiB so 8K 4444 with alpha decodes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* security(vp9): bound frame allocation
* vp9: raise the frame-size cap to 2^26 luma samples so level 6.x 8K frames decode
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* vp9: keep the frame-size cap below the imports
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* security(vp8): bound frame allocation
* vp8: raise the frame-size cap to 2^26 luma samples so 8K frames still decode
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(bitstream): validate NAL prefix size
* bitstream: accept every NAL length width the avcC/hvcC field can declare (1 to 4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* security(engine): confine stored media filesystem access
* relink: index symlinked files; skip only symlinked folders
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* security(render): bound expanded frame plans
* render: count plan layers as they are pushed instead of re-summing every step
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* security(mxf): share resolved descriptor metadata
* mxf: keep every source-package track in the no-material-package fallback
Two packages may reuse a track id and number; comparing only those dropped a real track.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* codecs: walk open-GOP leading pictures back in a loop, not by recursion
MxfVideo::sync_before and MpegVideo::sync_before recursed one GOP back
per leading picture, so a crafted index whose every GOP leads its key
frame recursed once per GOP: tens of thousands of frames for a small
file, a plausible stack overflow in dev builds.
Both now use gop::open_gop_sync, a loop over the same steps. It stops
when the index's sync sample lies past the picture instead of looping,
and reads pts and keys with get().
Fixes#2803
* gop: keep decode_start's doc comment on decode_start
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* security(text): keep font scans within configured folders
* fonts: still find symlinked font files; skip only symlinked folders
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fonts: rustfmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* security(ui): bound export folder elision work
* export mode: keep location_link's doc comment on the function
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* security(tts): make malformed-token scanning linear
* tts-text: find a pause marker's ] by byte, so a multi-byte character at byte 64 doesn't hide it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(control): bound synthetic pointer events
* control: check ui.click's count before queuing any input events
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* security(engine): bound graphic template thumbnails
* graphic templates: scale a capped thumbnail to fit both sides of the canvas
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* security(proxies): reserve outputs atomically
* proxies: ReservedOutput::file returns an Option instead of expect()
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Raise a loaded project's next_id past the ids already in it (fixes#2809)
* Demo project: start new ids past its fixed transition and marker ids (#2809)
* Count transcript, source graphic and narration keys as ids in use on load (#2809)
* Format tests: a transcript's key raises next_id on load (#2809)
* Count ids that are only referenced (clip items, subclip parents, transition ends, sends, outputs, cameras, merged clips) on load (#2809)
* Scroll the timeline clip menu when it is taller than the screen (#2852)
The clip menu has about 35 items and ran off the bottom of short
screens with no way to reach the rest. It now scrolls inside the window,
as the panel menu already does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Let the clip menu move up before it scrolls
The scroll area only took the height left below the pointer, so the menu
stayed where it was opened and scrolled even when the whole menu fit on
the screen, and the clip menu tests in clip_dialogs_ui could no longer
click the items past that height. Asking for the full height lets the
popup move up as before; it only scrolls when the screen is too short.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* #703: test that timeline track lines are visible and the playhead head is short
The lines between timeline tracks were drawn in the timeline background colour, so they could not be seen, nothing marked the top of the highest video track, and the playhead head covered the whole ruler. The tests check that separator-coloured lines run under every track and above the top video track, and that the head stays within the bottom 14 points of the ruler. Without the fix no such lines are drawn and the head is 38 points tall.
* #130: test that timecodes and menus use the Premiere-style type
The big timecodes were 15 pt semibold and the menu bar's menus used egui's default text size. The tests check that the timeline, program and reference timecodes are painted in 16 pt regular in the timecode colour, and that File menu items are drawn at 13 pt. Without the fix the timecodes are 15 pt semibold and the menu items are 12 pt.
* #1084: test that the Import captions file button label is legible in light mode
The label was always white, so it disappeared against the light grey button in light mode. The test shows the empty Captions tab in the dark and light themes and requires at least 3:1 contrast between the label and the button fill. Without the fix the light theme fails at 1.04:1.
* #1309: test that the sort arrow does not cover a sorted column's name
In a narrow List-view column the sort arrow was painted over the end of the clipped column name. The test narrows the sorted Name column and requires the name's visible clip rect to end left of the arrow. Without the fix the name stays visible up to x=67 while the arrow starts at x=60.
* #1084: hold the Import captions label to 4.5:1 contrast
The label is normal-size text (12 pt semibold), so WCAG AA asks for 4.5:1, not the 3:1 the test allowed. Both themes clear 4.5:1 today; with the fix reverted the label is at 1.04:1.
* #130: menu bar titles use the 13 pt menu text, like their menus
#130 styled the menus' items with theme::menu_style through MenuConfig, which egui applies to submenus only. The bar's own titles kept egui's default 12 pt, dimmer button text. MenuBar::style takes the same style, so the titles and their menus now share one size and colour, as in a native menu bar. The #130 test now checks the File title too; without this change it fails at 12 pt.
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- Read and write CLI inputs/outputs on a blocking pool instead of stalling
the async runtime (rust:S7493).
- Document the FLAC STREAMINFO MD5 as the RFC 9639 integrity signature it
is, and validate the AV1 generator's spec path before reading it.
- Normalize the DCT basis literals that read as numeric suffixes.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>