Two more built-in presets follow Default in Settings > Labels > Label
Color Presets. Bright is 16 saturated hues spaced around the colour
wheel plus a gray, named Red to Gray. Roles names the labels after what
clips are (B-Roll, Footage, Dialogue, Graphics, Interview, Sequence,
Visual Effects, Bin, Titles, Music, Sound Effects, Voice-Over, Captions,
Ambience, Color Grade, Stock), ordered so the label defaults for new
video, movies, audio, stills, sequences, Dynamic Link items, bins and
captions land on the matching role. Like Default they can be chosen
and reverted to but not deleted, and an imported preset with one of
their names is renamed "<name> (imported)".
A Label Color Presets row under Label Defaults picks the 16 label names
and colours from a preset. The dropdown lists the built-in "Default"
(the labels FilmCraft ships with) and the presets in the Label Presets
folder beside the preferences, and reads "<name> (Modified)" once the
labels differ from the chosen preset. Icon buttons: Revert (back to the
preset's colours and names), Import (copies a preset file into the
folder and selects it), Export (saves the dialog's labels to a file
named as the preset), Show Folder, and Delete (user presets only).
Choosing a preset or reverting applies on OK like the other fields;
Cancel discards it.
Presets are small JSON files: a name and 16 labels, each a name and a
labels, names trimmed and at most 64 characters, valid colours); a bad
file shows its error in the dialog and changes nothing. The same is
reachable as labels.presets, labels.importPreset, labels.exportPreset
and labels.deletePreset, with the selection stored as labels.preset.
Problem: Quit, Cmd+Q or the window's close button exited at once with
unsaved changes, leaving them to the recovery journal, so every launch
after an unsaved session offered a recovery (docs/project-files.md: no
"Save changes?" prompt on quit yet).
Fix: a close request with unsaved changes is cancelled and the existing
Save Project prompt asks Cancel / Don't Save / Save (automation ids
quit.*), as in Premiere. Don't Save discards the changes and their
recovery snapshot; Save on a never-saved project (here and in Close
Project) opens the Save dialog and returns to the prompt when cancelled.
The control channel's app.quit still quits without asking.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ja.tsv rows for the new appearance strings (no duplicate Dark/Light);
uk.tsv gets Next Appearance Mode, which the pt-br menu catalog now has.
- appearance.colorTheme is legacy input but no longer goes stale: it follows the
mode and theme choices (the theme a fixed mode shows, the dark theme in Auto).
- docs/project-files.md documents appearanceMode / darkTheme / lightTheme.
- #489's menu/settings label test compares with the theme cards' choices.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The engine already keeps one playhead per sequence while a project is open, and the
project file's view already stores each sequence's zoom, scroll and track heights, but
not its playhead: after saving and reopening, every sequence started at zero.
ProjectView gains `playheads` (sequence id -> Tick), written beside the tabs and views
and restored with them when "Restore open sequences when opening projects" is on. Files
written before it have none and open as they did. Values from a file are not trusted:
ids that are not sequences are dropped, playheads are clamped to 0..ProjectView::MAX_PLAYHEAD
and snapped to a frame of their sequence.
Tests: each_sequence_reopens_with_its_playhead_where_it_was_left (fails without the
fix), hostile playheads in a_damaged_view_in_a_project_file_is_cleaned_up_or_ignored,
a deleted sequence's playhead is not written, and the format round trip carries it.
Also adds my contributor credit entry (people.toml).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qq8BK1LmohzE1ga4NgAdWM
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>
- 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
Premiere Pro 26.5.2 reopens a project with the same sequence tabs, in the
same order, the same one shown, each at the zoom it was left at. FilmCraft
opened every project on its first sequence; the "Restore open sequences when
opening projects" preference existed and nothing read it.
A saved project file now has a `view` beside `project`: the open sequences in
tab order, the active one, and each sequence's view from NEST12. It is not
part of the project: no undo step, no "edited" mark, and no schema bump (older
builds ignore it). `file.open` restores it when the preference is on.
Nothing in the view is trusted. Ids that are not sequences of the project and
repeats are dropped, numbers are clamped, and a view that cannot be read is
ignored instead of failing the open. A view with no open sequence leaves the
project on its first sequence, so a project saved by a script or the CLI does
not open on an empty Timeline.
Opening or recovering a project now tells the frontend which sequence is
shown, so the Timeline takes that sequence's view.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem: a Final Cut 7 XML whose Basic Motion filter sets only `scale`
makes the importer add the Motion effect with its defaults, where
`position` and `anchor` are the NaN "auto" sentinel. Placing a clip
resolves those with `resolve_auto_points`; the interchange import did
not (any format: the clips are built with the effect defaults).
serde_json writes NaN as `null`, and the reader then refused the file:
"project file is damaged: invalid type: null, expected f64". The project
could be saved but never opened again.
Solution:
- project: a point parameter's value (`ParamValue::Vec2`) reads a `null`
coordinate back as NaN, so "auto" survives a save. Only there: `Vec2`
itself is unchanged, and a `null` in a mask vertex or any other point
is still a damaged file.
- project: `Project::resolve_placed_auto_points` resolves the points of
the clips in chosen sequences. Points measured in the frame use the
sequence size. `anchor` is measured in the clip's source
(`Project::source_size`, a subclip through its parent, as the renderer
resolves it) and is resolved only for sources the caller says have
their final size.
- engine: the import resolves in the edits it already makes, so the undo
steps are what they were. In "Import ...": everything but the anchors
of file media, whose size is only what the document says so far (an
EDL says nothing and the importer assumes the sequence size). In each
"Link Media": the anchors of that file's clips, from the probed size.
A clip whose file is not found keeps an "auto" anchor, which the
renderer centres in whatever size the media has, as before this
change.
- engine: a later Link Media (relink.rs `apply`, behind `media.relink`
and `media.autoRelink`) resolves those waiting anchors in its own undo
step, when it has read the file. Nothing else resolves them: a file
that merely comes back at its old path, or a forced relink that does
not read the file, leaves the anchor "auto" (still rendered centred).
Why no schema step and no repair on load: the file's shape is not
changed. Files with `null` points were written by every build before
this one, at the current schema version, so a migration (keyed on the
version) cannot select them, and a bump would only make every new
project unreadable to those builds. They open now and their points are
"auto", exactly as in the session that wrote them; resolving them on
load would mean guessing the size of media that was never linked.
docs/project-files.md describes `null` points.
Tests: project
`point_params_keep_auto_through_json_and_no_other_point_reads_null` and
`placed_auto_points_resolve_once_the_source_size_is_final`; format
`auto_points_written_as_null_open_again`; engine
(interchange_auto_points_tests.rs): import with the media missing, save,
reopen, undo, redo; a linked file whose real size differs from the
declared and the sequence size (undo of Link Media and of the import,
redo); an EDL clip smaller than its sequence; import with the media
missing, save, reopen, relink (anchor becomes a number, undo, redo, a
second relink leaves it). All fail without the change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One Preferences window (app menu ▸ Settings ▸ <category>, Edit ▸ Preferences in-window, General =
Cmd+,): category list, schema-driven pages, Help / Reset… / Cancel / OK. Settings live in the
per-user preferences.json (version 2, v1 migrated, bad values repaired), reachable via
prefs.get/set/reset{category} and the new prefs.schema; app.settings.<category> opens a page;
every control has a settings.<key> automation id.
Wired: still/transition default durations, auto-scroll, snap playhead, return to beginning, play
after rendering, step many, selection-tool roll/ripple, theme/highlight/contrast, tooltips, at
startup + recent projects, label names/colours/defaults, indeterminate timebase, default media
scaling, media cache location/policy/clean, frame cache budget, cpal device/buffer/rate/output
mapping, transcription defaults + auto-transcribe on import, text smart quotes/ligatures/font.
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>
- crates/speech (L2): Transcriber trait, FixedTranscriber, Whisper model catalogue (pinned,
SHA-256 verified) with optional downloader (feature `download`: ureq + rustls + RustCrypto),
pure-Rust Whisper inference on candle with word timestamps (feature `whisper`), log-mel, VAD
word tightening, MFCC speaker clustering. Both features off by default and for wasm.
- project: per-media-item transcripts (Project::transcripts); schema v9 with a no-op v8->v9
migration and a v8 fixture.
- edit::transcript: sequence transcript derived from clip transcripts, search, paragraphs,
word ranges, pause/filler detection, ripple delete of many ranges, caption layout.
- engine: transcript.* commands (generate, set, delete, inspect, search, select, extract, lift,
renameSpeaker, removeFillers, removePauses, createCaptions, models, downloadModel);
Session::transcriber for hosts/tests; features `whisper` / `speech-download` (also on the
desktop and CLI apps). Without whisper, transcript.generate fails with a clear error.
- ui: Text panel Transcript tab (Transcribe button, speaker paragraphs of clickable words,
Shift+click selection -> In/Out, extract/lift/remove fillers/create captions, search) with
text.transcript.* automation ids; headless UI test.
- docs/transcripts.md, crates/speech/README.md, architecture and project-files updates.
whisper-tiny measures 12.2% WER over 797 words on local samples.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- file.projectManager {destination, mode: collect|consolidate, sequences?,
excludeUnused, handles, preset, includeProxies, includePreviews, dryRun, wait}.
- Collect copies every file the chosen sequences use (nested sequences and subclips
followed), optionally proxies and render previews; consolidate writes only the used
media range plus handles with a transcode preset and re-bases media time so edits,
keyframes and markers stay put. Unused clips can be excluded.
- Dry run reports original / resulting disk space and the file plan; the copy runs
as a job and writes the project last with the new paths and fingerprints.
- UI: Project Manager dialog (sequences, mode, preset, options, destination,
Calculate → sizes) with automation ids pm.*.
- Tests: collected project opens with its media moved away and renders the same
frames; consolidate trims to used range + handles and matches (>38 dB) the original.
- ROADMAP: M11 done.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- media.createProxies: background job (jobs.list progress) transcoding with our own
encoders — ProRes 422 Proxy ¼/½, ProRes LT ½, H.264 ¼/½ (audio included) — into
<media dir>/Proxies or a chosen folder; attached when the job finishes
(Session::poll_persistence applies finished media jobs).
- media.attachProxies (duration ±1 frame and frame rate must match; size/aspect may
differ), media.detachProxies, media.reconnectFullRes, media.toggleProxies
(Preferences ▸ Media ▸ enableProxies; bumps the view revision without dirtying the
project), media.proxyPresets.
- Proxy frames are smaller; ProxySource reports the original size and the compositor
derives its pixel scale from the frame, so Motion and effects render the same
picture (44 dB PSNR vs full res with Motion + Gaussian Blur, 48 dB at ½).
Export always renders full resolution.
- ExportSettings::prores_profile (proxy / lt / standard / hq).
- project.ingestSettings + file.import: copy (fingerprint-verified), transcode,
create proxies, copy and create proxies.
- UI: Toggle Proxies button on both monitors (lit when on), Create Proxies dialog,
Project panel Proxy submenu and P badges.
- Measured (one core, 4K H.264 testsrc2): CPU composite 201 → 35 ms/frame at ¼ with
ProRes Proxy ¼, 277 → 131 ms at ½ with H.264 ¼; decode 9.2 → 0.7 ms with H.264 ¼.
- Tests: create+attach+toggle same composite modulo scale, attach checks, export
ignores proxies, ingest copy + proxies, ignored 4K benchmark, headless UI test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Missing media no longer fails: opening a project scans for missing files
(`media.findMissing`, `file.open` → missingMedia). Missing clips render our own
offline slate (render::offline: red striped field, warning triangle, "MEDIA NOT
FOUND", file name, hint) with silent audio, and the Link Media dialog opens.
- Media identity: imports record the size and a fast content fingerprint (hash of
size + first/last MiB) in MediaClip::identity (optional field, older builds
ignore it, no schema bump).
- media.relink checks file name / extension / fingerprint ("Clip ID") / duration /
media start / frame size+rate+channels, refuses a mismatched fingerprint unless
forced, supports Align Timecode (Project::shift_media_time), and relinks the
others automatically by the derived folder remap (Windows/macOS separators).
- media.autoRelink {from,to} | {folder}, media.search, media.replaceFootage,
media.makeOffline {deleteFiles?}, media.offlineAll, media.status, media.linkMedia.
- Media pool caches sources per reference (path, offline flag), so relink, Make
Offline and undo take effect at once; proxy-aware resolution and a full-res
provider that export uses.
- codecs::gop no longer holds its lock while decoding: rayon decoders inside a
parallel export could steal another frame of the same source and deadlock.
- UI: Link Media dialog (rows, match options, folder search with preview, Locate…,
Link, Offline, Offline All, Cancel), Make Offline dialog, Project panel offline
markers and context menu, media thumbnails keyed by media reference.
- Tests: moved media opens offline (slate rendered) and relinks others by folder,
prefix remap and folder search, fingerprint mismatch refused, Make Offline,
headless UI test of the dialog.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Engine autosave module: Preferences (autoSave.enabled/intervalMinutes=5/maxVersions=20/
saveCurrentProject/recoveryJournal/recoveryIntervalSeconds), persisted in the per-user data
dir; prefs.get/set/reset commands.
- Background worker (filmcraft-autosave thread): the UI thread only hands over the Arc<Project>
snapshot; JSON encoding and atomic writes happen on the worker. Premiere-style auto-saves to
<project dir>/Auto-Save/<name>-YYYY-MM-DD_HH-MM-SS.fcproj (local time), only when changed,
rotated to Maximum Project Versions, optionally also saving the project.
- Recovery journal: debounced snapshot of unsaved changes in <data>/Recovery/<session>/ guarded
by an OS file lock; on launch, sessions whose lock is free and that left a snapshot are offered.
Clean quit removes the journal (or keeps it, marked cleanExit, if changes were unsaved).
- Commands: file.recover, file.discardRecovery, file.recoveryList, file.autoSaveNow,
file.autoSaveStatus, file.listAutoSaves.
- UI: Preferences ▸ Auto Save page (Edit ▸ Preferences, Cmd+,; FilmCraft ▸ Settings… on macOS),
"Recover Unsaved Changes" dialog at startup, Revert confirmation; all with automation ids.
- Desktop: --recover / --no-recover / --data-dir; local-time offset via chrono (clock).
- Verified in the app: edits, kill -9, relaunch, dialog, Recover restores the edits (unsaved, original path).
- docs/project-files.md: auto-save, recovery and measured costs (2,000 clips: 2.5 ms encode,
1.4 MB, ~30 ms atomic write+fsync, all off the UI thread). ROADMAP M11 updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- New crate filmcraft-format (L3): files are an envelope {format, schema_version, generator,
project}; a chain of single-step JSON migrations (v1 bare Project → v2 envelope) upgrades old
files on load; newer-than-supported files are refused with a clear "update FilmCraft" error;
damaged/foreign files report as such.
- atomic_write: temp file in the same dir + fsync + rename + dir fsync; used for every project
write (FsServices), so a crash mid-save never corrupts the project.
- Engine: file.save/saveAs/open go through the format crate; new file.saveCopy (Save a Copy…,
Cmd+Alt+S) and file.revert; the first save over an upgraded file keeps
"<name> (schema vN backup).fcproj". Project.version dropped (the envelope carries it).
- Hand-made v1 fixtures (minimal + edit without later serde-default fields) in
crates/format/tests/fixtures; 2,000-clip encode/decode timing test.
- docs/project-files.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>