With no colour tags, FilmCraft decodes with BT.709 transfer and
primaries and the matrix by frame size, and probe and media.colorInfo
showed those values with nothing saying they were defaults, so an
untagged file looked the same as one tagged BT.709.
VideoStreamInfo gets color_assumed (optional, left out when unknown):
which of the matrix, transfer and primaries no tag in the file gave.
MP4/MOV and Matroska take it from the same sources as the colour, APV
from its colour description, and AVI, MXF and Y4M mark all three.
probe prints it as video.color_assumed and media.colorInfo as assumed,
next to the unchanged metadata.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* interchange: EDL and OMF export reports name the video tracks they leave out (#4132)
* edl: report no video exported when the chosen track is empty
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* edl: rustfmt
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>
The OTIO importer took the sequence rate from global_start_time or the
first item with a source_range, whatever its rate. A gap or clip at rate
48000 is valid OTIO, so the sequence came out at 48000 fps and the
engine refused the import. Both now skip a rate no sequence can run at.
An EDL line with no `* SOURCE FILE:` guesses its file from the clip name in
the EDL's folder. When that file wasn't there the importer flagged the clip
offline, which Link Media reads as "made offline": the folder search, Relink
Others and the prompt on open all skip it. It is now a missing file, as an
unfound `* SOURCE FILE:` is.
A project file can hold any i64 sequence start timecode and any u32 audio
channel count and sample rate (load-time validation checks neither). The EDL
writer added the start timecode to every record frame unchecked; the AAF and
OMF writers multiplied channels by bytes per sample and by the sample rate,
and wav_header did the same in u16. Channel counts, frame counts and block
sizes were then cut down with `as` (65 536 channels became 0 in OMF and in
the embedded WAVE essence), and aiff_header's chunk sizes wrapped. Debug
builds panicked; release builds wrote wrapped values. All of it saturates
now, as does frames_round's next frame. Regression tests for EDL, AAF and
OMF; found by mutation-fuzzing project files.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Found while testing crates/interchange/src/edl.rs:118 (parse_event_line, num_at parsing as i64) and :498 (import_event, tick_of(l2.dur))
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
* 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>
Import standalone video elements as picture-only clips, retaining media
paths, timing, adjustments, markers, transitions and connected placement.
Warn when a video references an unsupported generator or source.
Add synthetic importer and engine regressions covering zero-duration still
assets, missing-media relinking, save/reopen, undo/redo and rendered pixels.
Document the FCPXML 1.10 specification and supported behavior.
Validation: interchange tests and the engine slideshow regression pass,
as do scoped Clippy, formatting, layers, assets and WASM. Full CI on Rust
1.95 stops at the existing ProRes nonminimal_bool lint; unrelated CI
repairs are excluded from this change.
The reporter's original XML imports all 55 slides, 54 transitions and
55 media items in a temporary local check; reporter files are not included.
Fixes#2837
Co-authored-by: Codex <noreply@openai.com>
* 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
Bounded item_path, item_media, and base_item in interchange helpers
and ale::export_items with a 16-step bound matching crates/project.
Cyclic subclip chains (self-referencing or mutual cycles) and overly
deep chains now terminate safely without causing a stack overflow abort.
Closes#2788
CodeRabbit review: import_with returns early for AAF and OMF, so the
shared bound_start_timecodes pass never ran for them and an AAF with an
absurd start timecode kept it. The AAF and OMF importers now run the
pass themselves (the test fails without it for AAF; OMF carries no
sequence start timecode).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Review feedback: the FCP7 timebase clamp and the OTIO rate fallback happen
while parsing, so the post-import pass saw a plausible rate and said
nothing; only the FCPXML path warned. Each importer now checks the
document once and warns when a timebase or rate is beyond the limit
(OTIO's warning names audio sample rates such as DSD, which are above it).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
FCPXML src, FCP7 pathurl and OTIO target_url are URLs, but only file://
ones were decoded: a relative src="Clip%20A.mov" looked for a file
literally named Clip%20A.mov. Relative URLs are now decoded too (plain
names from EDL / AAF are not), and the OTIO exporter writes its relative
target_url percent-encoded so a name with % or spaces round-trips.
The web relink fallbacks the issue also asks for stay open.
Refs #2195
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scaled() multiplied a duration by an integral speed with a plain `*`. An EDL
with a record-out timecode of 2147483647 hours and an `M2 ... 72.0` speed
change overflowed it (attempt to multiply with overflow, a panic in debug
builds, wrapped time in release). The same function serves the FCP7, FCPXML
and OTIO importers, so mutation fuzzing hit it from all four text formats.
The multiply saturates, like Tick's own arithmetic.
Regression tests: the EDL imported and exported in every format
(tests/edl.rs) and scaled() unit assertions; both panic at common.rs scaled
without the fix.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An importer accepted any positive frame rate and any start timecode. A rate
near i64::MAX per second (an FCPXML frameDuration of 1/9223372036854775807s,
an FCP7 timebase of 9223372036854775807) made frame_at clamp to i64::MAX, and
frames_round overflowed adding 1 on export as soon as the sequence had a start
timecode or a marker; an NTSC timebase overflowed multiplying by 1000 on
import. An FCP7 <frame>18446744073709551615</frame> start timecode saturates
to i64::MAX and overflowed the EDL export's rec_frames.
Rates are limited to 1 000 000 per second (far above video and the 192000 Hz
OTIO audio uses, far below the overflow): the FCP7 timebase is clamped, an
OTIO rate beyond it uses the default, and one pass after every import resets a
sequence or media rate beyond it, and start timecodes beyond 360 000 000 000
frames, with a warning in the report. The pass covers every format, so
fcpxml.rs is not touched.
Regression tests: tests/rates.rs (FCPXML absurd frameDuration, every export
format) and tests/fcp7.rs (absurd timebases with and without ntsc, absurd
start timecode). With the limits lifted they panic in frames_round, in the
NTSC multiply and in edl.rs rec_frames.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Use a checked localhost prefix probe so Unicode file URLs retain their media paths without slicing inside a UTF-8 character. Add public converter, resolver and EDL import regressions.
Fixes#1591
Co-authored-by: Codex <noreply@openai.com>
Premiere writes a stereo clip to FCP7 XML as two mono clip items on two
tracks (sourcetrack trackindex 1 and 2). The importer already picks
channel 0 and channel 1 for them, but left both centred, and a centred
one-channel clip plays on both sides. So both channels summed on L and
on R: +3 dB per channel for an uncorrelated stereo effect (and its L-R
doubled), +6 dB for dual-mono dialogue.
Import: a clip that plays one channel of a split two-channel file is
panned to that channel's side (channel 0 left, channel 1 right). An
Audio Pan filter on the clip item still overrides it.
Export: a one-channel clip always writes its Audio Pan, also when it is
centred, so a centred Breakout to Mono round-trips centred.
Measured with filmcraft-cli (import, export WAV): a split stereo file
and a split dual-mono file now come out at 0.00 dB and sample-identical
to the source (before: +3.01 dB and +6.02 dB per channel); a mono file
on a mono track was and stays at 0 dB.
Tests (crates/interchange/tests/fcp7.rs):
split_stereo_halves_are_panned_to_their_side and
split_stereo_half_keeps_its_audio_pan fail without the import change;
centred_mono_breakout_round_trips_centred fails without the export
change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012HrAqZmrrDLRpxBJatxvaJ
Timeline clipitem markers already round-tripped; source-media annotations on
master clips were omitted with an empty export report. Write those markers on
<clip> so re-import keeps them.
Fixesstorytold/filmcraft#900.
Export was overwriting Stack markers with the using clip's markers, so a nest with no instance markers lost its sequence markers, and instance markers were imported onto the nested sequence. Keep sequence markers on the Stack and store clip instance markers in metadata.filmcraft.clip.
FCPXML writes point markers as one frame at the sequence rate. Import
compared duration to 1 tick, so a 24fps one-frame marker became a
range. Treat duration ≤ one sequence frame as a FilmCraft point.
A split-stereo left clip exported trackindex 1 and re-imported as full stereo,
losing the left/right split. Treat trackindex 1 as channel 0 when another
audio clip item of the same file in the sequence uses a higher index.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps #359's contiguous_starts audio timing, now per stream in Mp4Audio /
MkvAudio (starts and preroll in source sample frames). MP4 audio streams and
their decoders are built from the same (track, info) pairs so they stay
index-aligned. Adapts new main code to audio_streams / TrackItem::audio_stream.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
write_clip rounded start, end, in and out to frames independently, so
a clip placed by seconds at 23.976 (start 0, in 20 s, 3 s long) was
written as start 0 / end 72 / in 480 / out 551: 71 source frames for
72 record frames.
At normal speed and on a freeze frame the source span is the record
span, so out is now in + (end - start). Speed-changed clips keep their
independently rounded out. The master duration is raised to out when
rounding pushes out past it; pproTicksIn/Out still carry exact ticks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Update MediaInfo to store all discovered audio streams in audio_streams
- Retain independent stream metadata and decoder state for MP4/MOV and Matroska/WebM
- Update audio_placement_specs to allocate successive tracks via ensure_audio_tracks
- Isolate clip rendering to read assigned audio stream rather than replaying stream 0
- Bump project schema to v13 with backwards compatibility for legacy v12 projects
Resolves#312Fixes#200Fixes#315
A nested sequence was left out of both formats as a gap, with a report entry
telling the user to "render and replace" it first (a command FilmCraft does
not have). An AAF with nested compositions was read with gaps in their place.
What Premiere Pro 26.5.2 does, read off files it exported for a sequence with
a nest:
- AAF: the nested sequence is a composition mob of its own, and its clips are
source clips that point at it.
- OMF: the nested sequence's sound is rendered into the document as one clip
named after the sequence.
Export
- AAF: a nested sequence is written as a composition mob (no usage code; the
exported sequence stays the top-level one). A clip of it is a source clip
at that mob's first slot of the clip's kind, its start in that slot's edit
units. Nests in nests are followed; a nest used twice is written once. With
embedded or consolidated audio, the media of the clips inside a nest is
prepared with the rest (`NestNeeds::Inside`).
- OMF: `audio_needs` lists the range of the nested sequence each nest clip
plays (`NestNeeds::Render`); the engine mixes it through the export audio
pipeline with what is on the clip, and it is written as media. Without that
essence the nest is a gap and the report names the sequence.
Import (AAF)
- A source clip that points at a composition becomes a clip of a nested
sequence; the composition is read once.
- Premiere Pro's layout is read: all video tracks in one slot (a nested scope
with a segment per track; before, only the last segment was read, so a
Premiere AAF came in with one video track), and "top-level" tagged on nested
compositions too (top-level are those no other composition uses).
- A composition without a frame size takes that of the first picture it
shows, not of the first picture in the file.
A sequence that claims to be inside itself is a gap with a report entry in
both directions; the walks are bounded (16 levels).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>