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>
A moves[] entry gave its time only as integer ticks. Any other form, or
a missing clip or track, dropped the entry: alone it failed with "need
`moves`", next to other entries only those moved and the call said ok.
`time` now goes through tick_or_time_p like timeline.place's sourceIn,
and an entry without a usable clip, track or time is refused by its
index before anything moves.
* fix(export): an image-sequence export reports the stills it wrote
An export to `a.png` as an image sequence writes `a000.png`, `a001.png` and so on, but the
job's result, `file.exportMedia`'s `path` and the CLI's "exported" line all named `a.png`,
which does not exist.
The report now names the first still, the name `file.import` and `probe --image-sequence`
take for a sequence. `file.exportMedia` and `export.quick` name it as `path` up front, from
the frame count `export.resolve` uses, and add `lastPath`. The CLI prints both. A single
file keeps its path.
Fixes#4009
* fix(automation): a cancelled image-sequence export is cleaned up by its stills' name
Since `file.exportMedia` names an image sequence's first still as `path`, the MCP cancel
clean-up took `a000` as the stem: it left `a001.png` and every later still, and the `a.srt`
sidecar, behind. The result now also carries `basePath`, the name the stills are numbered
from, and the clean-up goes by it.
* fix(automation): an image-sequence cancel clean-up keeps the base name and finds .jpeg stills
An image-sequence export numbers every still, a single frame included, so a file under its base
name (`a.png`) is never its own: cancelling one deleted a user's `a.png` if it was saved during
the export. The stills of a `.jpeg` sequence were left behind instead, since the extension was
read as a format name, and `jpeg` names Motion-JPEG there; `file.exportMedia` keeps `.jpeg`
for a JPEG sequence.
* fix(automation): a cancelled image sequence removes only the stills it numbered
The clean-up matched any `<stem><3+ digits>.<ext>` written during the export, so cancelling an
export to `a.jpeg` could delete `a1000.jpeg`, the first still of an export to `a1.jpeg` in the
same folder. It now takes the digits and the last index from the result's `lastPath` and removes
only those names: `a000.jpeg` to `a120.jpeg` for a 121-frame export.
* media: name Apple Intermediate Codec and say why a clip is unreadable
Part of #3646. A MOV in Apple Intermediate Codec (fourcc icod, the
editing codec of iMovie and Final Cut) imported with its codec named
"unknown", and its frames failed with "no decoder for unknown video".
Only the log showed that error: the Project panel's badge said
"Unreadable" and nothing more.
- codecs: codec_label names icod "Apple Intermediate Codec". The
"no decoder" errors of make_video_decoder and software_video_decoder
use that label for sample entries the parser keeps as Unknown, so
the error reads "no decoder for Apple Intermediate Codec video".
Other codecs keep their short names in that error.
- ui-egui: an Unreadable badge shows on hover the error the media pool
recorded, on cards (Icon and Freeform view) and in List view, which
now registers its badge as project.item.<id>.offline as cards do.
This covers every cause the pool records, such as "H.264: field
pictures", not only this codec.
- docs: an Apple Intermediate Codec row in codec-parity.md, and the
hover in project-files.md.
FilmCraft still has no AIC decoder. Apple publishes no specification
for it, so a clean-room decoder (AGENTS.md section 2) needs a
maintainer ruling, and #3646 stays open for that.
Tests: apple_intermediate_codec_is_named_though_nothing_decodes_it in
filmcraft-codecs (the label and all three errors), and
unreadable_badge_says_why_on_hover in the ui-egui it suite (both views).
Developed with an AI assistant (Árbol, running Muse).
* ui: keep the List view's badges beside a long name (#3646)
In List view, a name that left less than 40 px of its column hid the
item's offline icon and proxy chip, and with them the Unreadable
badge's automation id and the reason it shows on hover. The badges now
keep their room at the end of the Name column, and a long name is cut
short before them. Only a column too narrow for the badges leaves them
out. Rows that showed their badges before, and rows without badges,
draw as they did.
unreadable_badge_says_why_on_hover imports a file whose name is wider
than the Name column, so its List view pass covers this case. Found by
CodeRabbit in its review of the pull request.
Developed with an AI assistant (Árbol, running Muse).
* codecs, ui: doc comments for codec_label and media_badge (#3646)
Both functions gained a role with the Apple Intermediate Codec change:
codec_label also names the codec in the no-decoder errors, and
media_badge records why an Unreadable file cannot be read. Their doc
comments say so.
Developed with an AI assistant (Árbol, running Muse).
* ui: end a List-view name cut short with "…" and show it in full on hover (#3646)
Keeping the badges' room at the end of the Name column can cut short a name
that used to fit, and the cut left no sign of it. The List view now draws the
name with the cards' elided_name, so a cut name ends in "…", and hovering it
shows the whole name, as the cards already do. Names that fit draw as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
CEA-608 has no code for some characters (the ellipsis, CJK) and the SCC
and MCC writers dropped them, or sent a close basic-set one (’ as '),
without saying so. The CEA-708 service in MCC sent ? the same way, for
instance for an em dash.
charset_warnings lists each such character once, with how often and
the first caption that has it, using the writers' own tables, and
captions.export returns the list as warnings. The list is capped at
20 characters per stream plus one summary line.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
TTML and DFXP exports always wrote xml:lang="en", whatever the caption
track's language. The document now carries the language: export takes
the track's, TTML import reads xml:lang (a language tag of at most 35
characters, else a warning), and captions.import takes an optional
language that wins over both.
* interchange: EDL media relinked with Align Timecode lands on the file's own time (#3990)
An EDL's source times are timecodes, but its media items had no start
timecode, so Align Timecode had nothing to align from and a clip relinked
after an offline import kept its timecode as media time (the file's last
frame). EDL media now starts at timecode 0, and the relink shift reads each
timecode at its own file's rate, as the import-time rebase does.
* engine: EDL relink test checks the placeholder is listed missing, not its offline flag (#3990)
#4000 stops flagging EDL clips named only by clip name offline, so the setup asks media.findMissing instead, which lists the item either way.
* engine: the EDL relink test also runs with 25 fps media (#3990)
make_movie_at takes the frame rate. Under a 24 fps EDL a 25 fps file's timecode must be read at 25 fps: with the old shift (old_rate.tick_of(b - a)) the clip lands at -149 s instead of 1 s.
* cli: --save runs on one project take turns, so no edit is lost (#3924)
A run reads the project, edits it and writes the whole file back, so two
at once silently dropped the edits of whichever saved first. A run that
saves over the file it opened now holds an OS lock on `.<name>.lock` next
to the project from before its read until it exits. The next run says so
on stderr, waits, then edits what the first one saved.
The OS drops the lock however the process ends, so a crash leaves nothing
stale. If the lock can't be taken the run warns and saves as it did before.
The desktop app and `mcp` sessions don't take this lock.
* cli: the save lock goes by the path being saved over (#3924)
A save through a symlink replaces the link with a plain file. Resolving
the path therefore gave the runs before and after that save different
locks, and a run still waiting on the old lock could overwrite a later
run's save. The lock now uses the path as written, so replacing the link
can't change which lock a run takes.
* G13: TikTok, Instagram Reels and YouTube Shorts export presets
Three Web & Social presets for short-form vertical video: H.264
1080×1920 (9:16), scaled to fill, at the sequence's frame rate, AAC
48 kHz stereo, with Loudness Normalization on at −14 LUFS and a −1 dBTP
ceiling, the level the platforms play at; 10, 8 and 12 Mbps VBR. They
share a short_form() helper; Social Vertical is unchanged.
short_form_presets_are_vertical_and_loudness_normalized checks size,
rate, scaling, bitrate, audio and loudness. Real exports measured
−14.1 LUFS from both a −19 and a −8.4 LUFS source.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* G13: the short-form presets test checks each maximum bitrate
CodeRabbit on #3721: assert max_bitrate_kbps (12, 10 and 16 Mbps) and
the VBR 1 pass mode next to the target bitrate, so a change to the
ceiling alone fails the test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* History: each step shows the icon of the tool or command that made it
Each undo and redo step now keeps the id of the command that made it,
through undo, redo, merged and grouped steps and the history limit.
The History panel paints that command's icon before each label: a
timeline tool's icon for the commands the tool runs, FilmCraft's own
icon for other commands that have one, and a plain square for the rest
and for edits made outside a command. Undone steps draw their icon dimmed
like their labels. The Open row has no icon. history.list is unchanged.
* History: ripple trims show the Ripple tool's icon
A ripple trim's undo step records its own id, timeline.trim.ripple,
whether it comes from a Ripple tool drag, a ripple edit point in trim
mode or a dynamic ripple trim, and the History panel shows the Ripple
tool's icon for it. Trim mode nudges, Extend Edit to Playhead and dynamic
trims record the edit point's kind (timeline.trim, timeline.roll or the
ripple id) instead of the outer trim command, so a regular trim shows the
Selection tool's icon and a roll shows the Rolling tool's icon. Trim
Previous and Next Edit to Playhead show the Selection tool's icon.
* History: icon targets on a part-scrolled row match the drawn icon
The History panel registered each row's icon target from the row rect after clipping it to the scroll area, so on a row partly scrolled out the target moved off the drawn icon, and an icon scrolled out entirely was still registered. The target is now the drawn icon's rect clipped to the scroll area, registered only while part of it shows.
* History: Apply Default Transitions to Selection shows the plain square
The trim. family gave trim.applyDefaultTransition the Rolling tool's icon, although the command adds transitions at the selected edit points and makes no rolling edit. It now shows the plain square, as the sequence transition commands do.
* Export: the time left counts down between batches; the status bar shows it once a second, in place
An export finishes its frames in batches of up to 16. The estimate measured the speed to the
latest reading, so between batches the speed sagged and the time left grew, then fell when the
next batch landed: about ±13 % on a heavy 4K60 export, enough to flip between 1:04:50 and 56:11.
It now measures the speed between batch arrivals and counts down from the last one; a batch three
intervals late falls back to the speed up to now, so a stall still makes the estimate grow.
The status bar redrew its text every frame, right-aligned against the bar, in Inter's proportional
digits, so "Exporting… 46%" moved sideways with every change of the time left. The text now
changes at most once a second, its digits sit in cells of equal width, and its room only grows
while the job runs (from the start it fits 100 % and a time under an hour).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Export: no "0 s left" while the last batch is late; the status text has room for any time left from the start
The countdown between batches reached zero when the last batch took longer than the speed said,
and showed "0 s left" until it was three intervals late; it now gives way to the speed up to now as
soon as it runs out. The status bar's room was for a time under an hour, so an export's first
estimate over an hour still moved the text once; it is now for 99:59:59 from the start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Export: the queue's and the Progress panel's job text change once a second, with digits of one width
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ui-egui: tabular_label is read by screen readers as a label
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 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>