Transcript::normalize sized the speaker label table from the largest word
speaker index, so one word with a u32::MAX index made transcript.set allocate
billions of labels before check() could run. Indices at or past MAX_SPEAKERS
(1024) now lose their speaker instead.
essentialSound.setType takes dialogue, music, sfx and ambience, but
ducking.against went through serde, which knows only the labels
(Dialogue, SFX...), so the name setType had just taken was refused.
ducking.against now reads either spelling with AudioType::parse and
still refuses names that are not a type.
Luma Key only made pixels below Cutoff transparent, so a light
background could not be keyed. Add a Key Out Brights option (default
off) that inverts the matte; projects without the param render as before.
Tested: luma_key_can_key_out_brights (default, missing param, inverted).
Fixes#2648
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* fix(graphics): Style buttons show and toggle the Type-tool selection
With characters selected by the Type tool, the Faux Bold, Faux Italic,
All Caps, Small Caps, Underline and Strikethrough buttons showed the
layer's value, not the selection's. A click wrote the inverse of the
layer value to the selection, so a second click could not turn a style
off again.
The buttons are now on when every selected character has the style
(its own override, else the layer's value). New
graphic_design::range_styles lists the overrides of a character range.
generated with Claude Code
* fix(graphics): Style buttons show the layer's style without a selection
Without a Type-tool selection the list of selected characters is empty,
and "every selected character has the style" was true for it. So an
unstyled layer showed Faux Bold, Faux Italic, Underline and
Strikethrough as on, and a click wrote false. The buttons now show the
layer's value when no characters are selected (found by CodeRabbit).
generated with Claude Code
* perf(project): range_styles builds only the selected range
The Style row reads the selection's styles on every redraw, once per button.
range_styles expanded every character of the text and then sliced; it now
allocates only start..end and merges the runs that overlap it, in their
order. A test compares it with the expanded text for every range, including
overlapping and out-of-range runs; the caret and past-the-end cases are
tested too.
generated with Claude Code
* test(graphics): cover Small Caps and a mixed-caps selection
The selected-characters test toggles Small Caps too, and the mixed-selection
test makes "Dr" All Caps before extending to "Drive", so both caps buttons
are off and a click makes all of it All Caps.
generated with Claude Code
* effects: give 4-Color Gradient a position for each colour (#3682)
The four colour points were hard-coded a quarter in from the corners, so
the gradient could not be arranged (the reporter had no position for the
fourth colour). Point 1-4 are now parameters; a point left at its NaN
default keeps the old position, and a project saved before they existed
has no such params, so both render exactly as before.
Test: a project without the points and one with the old positions set
explicitly render the same as the default; moving Point 4 shifts the
colours.
Fixes#3682
* effects: resolve the 4-Color Gradient points to the quarter positions (#3682)
* M3: Add shared custom colors and complete marker editing
* #1435: refine native colors and range endpoint flags
Use the macOS Colors panel for custom label colors and reuse the established egui picker on other hosts. Preserve draft confirmation, exact sRGB values, native text shortcuts and project ownership. Compact the swatches and center the add control, split range glyphs across their endpoints, localize the new controls, protect newer marker edits from stale dialogs, and cover the behavior with rendered UI and hit-test regressions.
Co-authored-by: Codex <noreply@openai.com>
* #1435: preserve imported palettes and marker double-click ownership
Union saved OTIO palettes across timelines with deduplication, bounded capacity,
first-name preservation and explicit overflow warnings. Keep null authors
consistent with omitted fields, and consume marker double-clicks before the
work-area gesture so opening the editor does not alter project history.
Correct the CSV URL column and schema documentation. Add six regressions for
palette retention/capacity, author handling, and physical overlapping gestures.
Validation: full cargo xtask ci (3666 passed, 45 skipped), 42 rendered UI tests,
and strict workspace/all-target Clippy on Rust 1.99.1.
Co-authored-by: Codex <noreply@openai.com>
* #1435: retain OTIO swatch names and generator labels
Ignore orphan name entries when retaining saved palette names so input ordering
does not hide valid names after the first 64 entries. Keep names bounded by the
retained palette and preserve existing valid names.
Include generator preset labels alongside custom label colors in OTIO metadata.
Add regressions for orphan-prefixed name maps and three generator kinds with
preset/custom colors through actual export/import round trips.
Validation: both regressions failed before fixes; all 72 interchange tests and
full cargo xtask ci pass (3668 tests, 45 skipped), plus strict workspace/all-target
Clippy on Rust 1.99.1.
Co-authored-by: Codex <noreply@openai.com>
* #1435: preserve distinct OTIO generator item identity
Use exported FilmCraft item IDs for generator media identity so identical
content can retain distinct source names, labels and custom colors. Repeated
references to one source item still share a single imported media entry.
Preserve content-based fallback for missing or invalid item IDs. Cover black
video, bars and tone, and color mattes with distinct items/shared references,
plus legacy and malformed identity metadata.
Validation: distinct-generator regression failed before fix; legacy compatibility
passed. All 74 interchange tests and final full cargo xtask ci pass (3670 tests,
45 skipped); strict workspace/all-target Clippy also passes on Rust 1.99.1.
Co-authored-by: Codex <noreply@openai.com>
* #1435: align Edit Marker layout and add safe marker navigation
Give the editor a wide aligned form, larger Notes area, side action column,
and grouped palette with vertical marker types. Expand the Web Link field
with the form and keep actions accessible in small windows.
Previous/Next saves valid edits as one Undo step and stays with the original
sequence/source owner, including coincident markers. Invalid drafts stay open;
unchanged navigation and Done add no history. Keep native/shared color pickers,
Author, Notes, URL validation and immediate marker creation.
Validation: all 45 rendered sequence/marker UI tests pass, including five new
navigation/owner/validation/layout regressions. Native macOS layout, picker
Cancel and exact single-step Undo restoration verified. Full standard cargo
xtask ci passes 3675 tests (45 skipped), doctests and all remaining gates;
strict Rust 1.99.1 workspace/all-target Clippy also passes.
Co-authored-by: Codex <noreply@openai.com>
* #1435: refine Edit Marker proportions and action alignment
Narrow the dialog to 680 logical pixels and keep the core order Name,
Time/Duration, Notes, Color and marker type. Place Author after the type-specific
options as secondary metadata. Preserve our palette, custom picker and range controls.
Use consistently sized, centered rounded action buttons with a primary Done action.
Shorten Delete Marker to Delete and remove the navigation helper and unused translations.
Keep the existing navigation save/validation/Undo semantics and automation IDs.
Validation: all 45 rendered marker/sequence UI tests pass, including small-window
action accessibility. Actual macOS layout and no-op Previous/Next/Cancel verified.
Full standard cargo xtask ci passes 3675 tests (45 skipped), doctests, formatting,
Clippy, translations, layers, assets and 49 WASM crates. Strict Rust 1.99.1
workspace/all-target Clippy passes separately.
Co-authored-by: Codex <noreply@openai.com>
* #1435: tighten marker layout and validate text editing
Use a narrower aligned form with Author below Name, bounded scrolling Notes,
left-aligned colors, centered actions and an inline Web Link URL/open icon.
Remove the legacy Flash Cue choice while preserving imported marker types.
Constrain timecode typing/paste to complete numeric colon-separated fields,
validate drop-frame labels and frame bounds, and disable Done for invalid timing.
Preserve high-frame-rate timecodes and point-marker semantics when Duration is
unchecked. Route native/menu clipboard and text Undo commands to focused fields;
explicit engine commands retain project Undo and selection semantics.
Validation: standard cargo xtask ci passes 3679 tests (45 standard skips),
doctests, fmt, strict workspace/all-target Clippy, i18n, layers, 69 assets and
49 WASM crates. Also passed 48 rendered marker/sequence tests and strict Rust
1.99.1 Clippy. Actual macOS clipboard, bounded Notes, point save and exact Undo
were checked. Windows/Linux desktop interaction remains pending.
Co-authored-by: Codex <noreply@openai.com>
* M0: Wait for frame-worker recovery before asserting
Do not treat a nonempty request queue as proof of worker death in the
panic-recovery regression. Keep waiting for all injected faults and enough
post-fault completions, then drain the queue without adding more seeks.
Retain the exact fault-count, successful-recovery and empty-queue assertions
and bound the wait with a two-minute deadline.
The inherited test failed twice in independent fork CI. It did not reproduce
locally: baseline passed 20 normal and 10 delayed-report runs; the repair
passed 50 normal and 10 delayed-report runs. No product or marker UI changes.
Validation: standard cargo xtask ci passed all 4485 tests (52 standard skips),
doctests, formatting, strict workspace/all-target Clippy, i18n, layers,
69 assets and 49 WASM crates.
Assisted-by: OpenAI Codex
---------
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Codex <codex@openai.com>
New Three-Way Color Corrector (Obsolete folder, like Color Balance): a
hue/balance wheel and a level shift for shadows, midtones and
highlights, tonal range thresholds with softness, and Saturation. All
parameters go through the generic param path (keyframable, undoable).
CPU (FxOp::ThreeWay) and GPU (OP_THREE_WAY) share the arithmetic; the
wheel is converted to an RGB offset on the CPU so the shader stays
simple. Defaults are the identity.
Tested: three_way_color_corrector_works_per_tonal_range (identity, each
wheel affects only its range, saturation 0 is grey) and two GPU/CPU
parity cases in fx_tests.
Fixes#3465
* feat(text): strikethrough for graphic text
Text layers get a Strikethrough toggle next to Underline: per layer
(`strikethrough` parameter, Properties ▸ Text ▸ Style "S") and per
character (`graphics.setCharStyle` style `strikethrough`, stored in the
layer's style runs).
The stroke uses the font's OS/2 strikeout position and size; a font that
leaves them out gets a stroke centred on half the x-height. Strokes are
added to the layout's decoration rectangles next to underlines, so every
renderer and the path export draw them without further changes. Vertical
text gets a stroke down the middle of each cell.
generated with Claude Code
* contributors: add alexanderalber
generated with Claude Code
* graphics properties: draw the letter-button marks in a helper
Keeps the Properties scroll closure under the cognitive-complexity limit with the new
Strikethrough button.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* #3415: fit graphics template width alongside height
Scale independent layer width and its keyframes when fitting graphics templates to another canvas. Add static and animated regression coverage and document the behavior.
Validation: contributor reports all requested verification passed; agent ran no tests or builds.
Assisted-by: OpenAI Codex
* #3415: fit omitted scale defaults in imported templates
Materialize definition defaults for both scale axes before fitting, preserving explicit values and animation. Cover imported text and shape layers with omitted parameters and document the behavior.
Validation: contributor confirmed verification passed. Agent ran no tests or builds.
Assisted-by: OpenAI Codex
Found while testing crates/project/src/keyframe.rs:222 (Param::set_at)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Caption text was always the family's SemiBold: caption_style hard-coded
the style. CaptionStyle gains font_style (captions.setStyle `fontStyle`,
default "SemiBold", serde default so old projects load unchanged),
resolved like graphic text: the nearest weight when the family has no
face of that name, SemiBold when empty.
- render_caption uses it (Program monitor and burned-in export)
- captions.list reports it; the Text panel's Captions tab offers the
family's styles next to the font
- Upgrade Caption to Graphic carries it to the text layer's font style;
graphics.fonts.used lists it and file.replaceFonts matches and
replaces it
- docs/captions.md, docs/graphics.md
The default keeps default_style_renders_like_the_reference green
(pixel-identical); font_style_is_used renders Regular and Bold against
SemiBold.
Fixes#3357
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retain Unicode letters and digits in the shared slug helper so non-Latin names do not all export as template.fcgt/user:template. Refs #3237 (non-ASCII-name subcase only).
AI-assisted-by: OpenAI Codex
Signed-off-by: jnohclee-rgb <336857373+jnohclee-rgb@users.noreply.github.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)
An edit made with a Type-tool selection goes to the selected characters
(graphics.setCharStyle), but the Fill colour, the RGB fields and the other
character controls kept reading the layer's value, so the picker looked
unchanged after the change applied. They now read the selected
characters' own style, falling back to the layer's where nothing is set.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011K1peyrh5MdZ6ttHkVq8Gh
A track can carry a colour label, as many NLEs offer. The strip at the
left edge of a track header opens a menu with None and the 16 labels
(with the user's label names and colours, as in Edit > Label). The label
tints the header and the lane.
- `Track.label: Option<Label>`, serde default and skipped when None, so
older projects load and plain tracks save as before
- `timeline.setTrack {label}` (a label name; "" / "None" / null clears),
an undoable Track Settings step; unknown names are refused
- `sequence.inspect` reports each track's `label`
- automation ids `timeline.track.<V1|A1…>.color` and `.color.<Label>`
Renaming, adding and deleting tracks from the header are covered by the
open PRs for #654 and #1630, so this only adds the colour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L8DvNHjchh5Gf2q24UsWQn
Problem: `cargo xtask parity` (#781, #782) measured only the menu bar; the
Effects panel's effects and transitions had no measured parity figure
(docs/gaps.md G7).
Fix: `--area effects|transitions` compares the catalogue the engine's
effect definitions list in the Effects panel (new `effect_catalog`
example in filmcraft-project, the panel's own top-folder filter) with a
reference of folder paths or bare names (default the local, never
committed plan/premiere/<area>.json). Same report, caps and `--min` as
menus; placement by folder; Presets / Lumetri Presets and the other
area's items are out of scope, so one panel snapshot serves both areas.
`--area menus` (default) is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: Premiere 26's redesigned mask tools give rectangle masks
rounded corners; FilmCraft masks only had sharp corners.
Fix: MaskPath::with_rounded_corners replaces each sharp corner between
straight edges by a circular fillet (two vertices joined by a cubic arc,
handles 4/3·tan(φ/4)·r, shrunk to fit half of each edge; hostile radii
and indices are clamped or ignored). masks.add takes `cornerRadius` for
a rounded rectangle, and the new masks.roundCorners command rounds an
existing mask's corners (or the listed vertices) in one undo step, on
every Mask Path keyframe so animated paths keep interpolating.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: a bin stayed wherever it was created. `project.moveToBin` only
moved project items, and bin rows and cards in the Project panel could not
be dragged, so the only way to nest bins was to create them inside each
other.
Fix: `project.moveToBin` also takes bin ids in `items` (as `project.delete`
does) and moves the bin with everything in it, skipping the root and any
move that would put a bin inside itself. In the Project panel, bin rows
(List view) and bin cards (Icon view) can be dragged: dropping one on
another bin nests it there, and dropping it on empty space moves it into
the bin the view shows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: a project had nowhere to keep free text (to-dos, instructions
for whoever opens it next, frame numbers).
Fix: plain-text `Project::notes` (schema v15, no-op migration so older
builds refuse instead of dropping it), `project.notes` /
`project.setNotes {text, merge?}` engine commands (undoable, capped at
1 MiB; calls with one `merge` key share an undo step), and a
Window > Project Notes panel whose editor (`notes.text`) commits every
change, one typing session per undo step. Translations for es, ja,
zh-cn (and the panel title for pt-br, uk).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The caption style's `font` was stored and settable through
captions.setStyle, but the burn-in renderer always drew Inter SemiBold.
Lay out, wrap and measure captions with the track's family (Inter when
the name is empty or not installed), so the Program monitor and burned-in
exports use the chosen font.
Add a font dropdown to the caption style strip in the Text panel's
Captions tab (automation id `text.captions.style.font`, options
`text.captions.style.font.option.<family>`, registered only while
visible). macOS-private faces (names starting with '.') are not offered:
some lack a space glyph and run words together.
Tests: renderer regression test (a non-Inter family changes the pixels;
failed before), fallback test for empty/unknown names, and a headless UI
test that picks Noto Serif from the dropdown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Transform effect listed a shutter angle but had no override, so motion blur never used it. Checking Use Effect Shutter Angle now samples the animated transform across that angle.
Relaxed and Aggressive previously only scaled two internal multipliers, so the matte stayed at Default. Switching Setting now writes Premiere-like matte values and the renderer uses that set, including Custom for hand-tuned keys.
source_monitor markers and format's audio-stream clamp now detach with Arc::make_mut;
restore the final braces in clip_ops.rs and project/src/lib.rs (cargo fmt);
scripted.rs is back to main (the test covered key dispatch, which this PR doesn't change).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>