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>
An index that declares a key size of 0 made the importer loop `count` times
(a u32 from the file) pushing an empty key each time. Reject a non-empty
collection with key size 0, and require that count * size fits in the index
stream before the loop starts.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
`path_to_file_url` treated `\\?\C:\x` (the form `canonicalize` returns on
Windows) as a UNC share named `?`, so FCP7 XML exports on Windows wrote
`file://%3F/C:/...` and Premiere opened the media offline. Drop the
`\\?\` prefix (and map `\\?\UNC\server\share` to `//server/share`) before
building the URL. Verified: the exported XML now imports into Premiere
Pro 2026 on Windows with all media online.
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>
The Root's MetaDictionary was written with no properties. pyaaf2 loads
its ClassDefinitions and TypeDefinitions sets to merge them with its
built-in model and refused every file: "cannot find index stream:
ClassDefinitions-3 index".
The MetaDictionary now has both sets (pids 0x0003, 0x0004, keyed by the
definition AUID 0x0005), empty: they only carry extensions to the
baseline model and everything FilmCraft writes is baseline. An empty
strong-reference set also writes its real key size (16 for AUIDs, 32
for MobIDs) instead of 0.
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 negative clip duration no longer panics the clamp when a nest follows its contents; a nested
sequence's claimed sample rate is clamped before it sizes the mix buffer; audio_needs lists each
nested sequence once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
The export report already said a nested sequence is written "as a single
source (reel AX)", but the reel came from the reel mode: with the default
(file name) a nest called "Nested Sequence 02" got reel NESTED_S. A nested
sequence has no tape and no file, so its reel is now AX whatever the mode.
Premiere Pro 26.5.2 writes the same for a nest (seen in an EDL it exported):
one event from reel AX, the nested sequence's name in "FROM CLIP NAME", and
the nested sequence's own time as source timecode. A test pins that event.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: a clip item whose `<file id="...">` reference has no definition
anywhere in the document was dropped without a word, so a sequence could
come back one clip short with an empty import report. FilmCraft's own
export writes titles and adjustment layers this way.
Solution: the importer adds a warning that names the clip item: `clip
"Title" refers to a file the document does not define; skipped`. Nothing
else about the import changes.
Test: `fcp7_import_reports_a_clip_item_whose_file_is_not_defined`
(FilmCraft's own export, a hand-written document, and a document whose
files are all defined).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: exporting a sequence with a graphic (title) clip to Final Cut
Pro 7 XML returned a report that listed other losses and said nothing
about the title. The XML holds a clip item for it that points at a file
the document never defines, and importing the XML gives the sequence
back without the title. OTIO (a clip with a missing media reference,
read back as offline media) and EDL (an ordinary event) were silent in
the same way; FCPXML said "clip without media skipped" and AAF "graphics,
titles and adjustment layers are not exported" without saying which clip.
Adjustment layers take the same paths.
Solution: every exporter names each graphic clip and adjustment layer
it cannot carry, one warning per clip, with what was written instead:
`graphic clip "Title" at frame 48 has no Final Cut Pro 7 XML equivalent:
it is written as a clip item without media and is not read back on
import`. One helper (`common::uncarried_clip`) builds the name for all
formats. What is written is not changed, byte for byte.
Why only a warning: FCP7 XML has a Text generator, but this crate's
importer does not read one (unknown generators become Black Video), and
a title here is a stack of text and shape layers with per-character
styles, pins and roll settings that the generator's few parameters
cannot hold. Writing one would need a new import path and would still
lose most of a title, so the honest result for now is to say what
happens. crates/interchange/README.md has the table per format.
Tests: crates/interchange/tests/uncarried_clips.rs (each format names
the title and the layer once; two titles are two lines; nothing is said
without such clips; a re-import gives back what the report says), and
the engine's `interchange_export_reports_the_title_it_cannot_carry`
through `file.exportInterchange`. The two naming tests fail without the
change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rust/clippy 1.99 added `chunks_exact_to_as_chunks` and tightened
`needless_late_init` and `drain_collect`, failing `clippy -D warnings`
on main.
- `chunks_exact(N)` / `chunks_exact_mut(N)` with a constant N become
`as_chunks::<N>()` / `as_chunks_mut::<N>()` (fixed-size arrays, so
several byte reads drop their per-element indexing). relink's
fingerprint uses the `(chunks, remainder)` split; its hash output is
unchanged.
- Late-initialised `let`s in ac3 bit allocation and opus CELT
bands/rate become `let x = if/match …`.
- `dpb.drain(..).collect()` becomes `std::mem::take(&mut dpb)`.
No behaviour change; `cargo xtask ci` passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- interchange: exporters (FCP7 XML, FCPXML, OTIO) skip a missing nested sequence or media with a
report warning instead of `expect`/`unwrap`; EDL event grouping and M2 lines, the XML writer
and the AAF/OMF dispatch arms have no panics. The WAVE header saturates instead of overflowing
for >4 GiB descriptors (found by fuzzing: a mutated EDL exported to OMF), regression test
`wav::tests::huge_wav_header_saturates`.
- captions: EBU STL frame rate read with `get()`; MCC rows, TTML text and the STL dispatch arm
without `expect`/`unreachable!`.
- project: bin moves, 5.1 pan lanes and text/shape layer constructors without `expect`.
- text: font resolution and line layout without `expect` (the bundled-face lookup keeps one
documented `#[allow(clippy::expect_used)]`: the faces are compiled in).
- automation: the MCP bridge returns an error instead of `expect` when not connected.
- Each crate denies unwrap/expect/panic/unimplemented/todo/unreachable outside tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Lumetri grades PQ / HLG working spaces in their own signal normalised to
HDR White (Basic Correction) / HDR Range (Curves) in cd/m²
(filmcraft_color::GradeSpace): exposure in stops of light, highlights above
HDR White kept, HDR Specular scales them; Rec. 709 unchanged.
- HDR static metadata (mdcv/clli, Matroska MasteringMetadata/MaxCLL/MaxFALL)
on VideoStreamInfo::hdr; PQ media use MaxCLL / mastering peak as the
BT.2390 source peak; media.colorInfo reports it.
- HSL Secondary Refine: Denoise (median of the key) and Blur (Gaussian).
- Lumetri Presets (18 original looks in 4 folders) with thumbnails rendered by
our Lumetri on a procedural preview picture; Effects panel rows and a
thumbnail grid when wide; lumetri.presets / applyPreset / presetThumbnails.
- Color Match: Comparison View toggle; Apply Match uses its reference frame.
- Goldens lumetri_hdr_pq and lumetri_presets_grid; D-Log M stays unsupported
(no published formula).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shared mob-style composition model; AAF objects in structured storage over
filmcraft-cfb; OMF objects in a Bento container; embedded / consolidated audio
supplied by the caller (essence::audio_needs, MediaOptions).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Edit: Label ▸ 16 colours + Select Label Group, Paste Attributes… (⌥⌘V, with
Scale Attribute Times), Remove Attributes…, Select All Matching, Remove
Unused, Consolidate Duplicates. File: New ▸ Sequence From Clip, Bin From
Selection (⇧B), Offline File…; Close Project (⇧⌘W, asks to save), Save All.
Clip: Make Subclip… (⌘U, Restrict Trims), Edit Subclip…, Modify ▸ Audio
Channels… (⇧G) / Timecode…, Video Options ▸ Frame Hold Options…, Add Frame
Hold (now splits at the playhead), Insert Frame Hold Segment, Field
Options…, Time Interpolation (Frame Sampling / Frame Blending / Optical
Flow, blending rendered on CPU; optical flow falls back to blending),
Fit to frame / Fill frame; Audio Options ▸ Breakout to Mono, Extract Audio
(WAV next to the project, imported); Replace With Clip ▸ From Source
Monitor / Match Frame / From Bin. Hold Filters: effects animate through a
hold unless set. Menus support nested submenus (egui and native) and follow
Premiere's order. Dialogs in panels::clip_dialogs with automation ids and
ui.set {clipDialog}.
Project schema v9 (no-op migration v8→v9, v8-subclip fixture): clip
time_interpolation / hold_filters / field_options / source_channels,
Interpretation.audio_channels, Subclip.restrict_trims.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>