Commit Graph

38 Commits

Author SHA1 Message Date
Brandon Thomas bbacbf2750 Merge main into #1139: frame-grid snapping moves into place_one (#746); saturating end
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:09:15 -04:00
Fabio Lussi f913243b3e fix(timeline): snap sub-frame timeline.place to the frame grid; inspect counts export's frames
timeline.place kept a sub-frame start and rounded the duration to whole
frames, so the clip ran past time + duration and the sequence ended
between frames. The start and the end now each snap to the nearest frame,
as a timeline drag already does. sequence.inspect's durationFrames now
comes from the same frame_span export uses, so the two always agree.

Fixes #1101

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:21:32 +02:00
Fabio Lussi e394061827 fix(cli): fail export/render that drew offline media unless --allow-offline
Exports and rendered frames now record which offline items (missing,
made offline, unreadable) they drew the placeholder slate for. The engine
reports them as `offlineMedia` in file.exportMedia (wait), jobs.list
export entries and export.queue items; the bridge and MCP long exports
pass it through. filmcraft-cli export and render name each item on
stderr and exit 1, unless --allow-offline accepts the placeholders.

Fixes #1054

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 20:02:15 +02:00
Brandon Thomas 539759ef18
Merge pull request #595 from MarekSpodBiedry/issue-476
Reject unknown mcp options instead of silently ignoring them (#476)
2026-10-10 14:52:19 -04:00
Brandon Thomas 3ad5a59321
Progress docs: target-app parity, gaps, alpha gate, readiness by audience (#700)
Progress docs per craftrules standards/progress-docs.md: target-app parity, gaps, roadmap with alpha gate, readiness by audience (full / mainstream / essentials) with Opus 5.5 hours, and feature, format, hardware, UI and localization parity docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:09:14 -04:00
Brandon Thomas 9847a5cbf1
Merge pull request #357 from jnohclee-rgb/dharma/filmcraft-344-help
cli: make --help print usage successfully
2026-10-10 08:09:57 -04:00
Brandon Thomas e3a00c3a25 Merge origin/main into #357; check --help after Args::parse in cli()
Rebased onto main's cli()/out! refactor: `--help` is checked as a parsed flag, so
`filmcraft-cli export --help` prints the reference too (test extended).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 05:57:22 -04:00
Brandon Thomas e0d2c9c73b MCP media_import: test the paths schema and param mapping; update docs/agents.md (#499)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 05:49:28 -04:00
MarekSpodBiedry dcf2d7f423 Reject unknown mcp options instead of silently ignoring them (#476) 2026-10-10 11:42:50 +02:00
Brandon Thomas dfdccd91e1
Merge pull request #504 from sawizzle/docs/installed-release-mcp
docs: register the MCP server from an installed release
2026-10-10 05:03:33 -04:00
Sawizzle 918a64317d docs: drop the third-party link; the Windows path is the default install folder
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:50:49 -07:00
Sawizzle 1286f135f4 docs: register the MCP server from an installed release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 17:06:15 -07:00
Vikram Bhaskaran c855c50ba9
Export sequence markers to a spreadsheet-safe CSV.
The report is sorted, uses the sequence timecode, escapes formula-like text, and does not change the edit.
2026-10-10 04:37:44 +11:00
jnohclee-rgb 0bfea22e1b cli: make --help print usage successfully
Fixes #344. Handle the leading help flag before command parsing and decoder registration; cover output and exit status with a native CLI regression.
2026-10-09 10:14:06 +08:00
Oldrich333 44248c060f CLI: a closed stdout no longer panics filmcraft-cli, and the work still finishes (#285)
`filmcraft-cli commands | head -c 10` panicked with "failed printing to stdout: Broken pipe
(os error 32)". Output now goes through `outln!` / `out!`. When stdout fails, later output is
dropped but the program does not stop: the rest of `exec`, `run` and `import` (later script
lines, `--save`, `--save-as`) still runs and the exit status still reports a failing command.
Exiting at the first failed print would report success while skipping the save, e.g.
`filmcraft-cli run edits.jsonl --project p.fcproj --save | head -1`. A closed pipe is not an
error; any other stdout write error is reported and makes the exit status 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 17:09:53 -04:00
Dario Carnelutti 283ea80a62 export: show the estimated time left of a render next to its percentage
Export mode, the status bar and the Progress panel showed "Encoding 37%" and nothing about how
long the rest would take. They now say "37% · 2:05 left".

- `Progress::eta()` (the new `pace` module) is the time left at the job's speed over the last
  15 seconds. Each call is also a reading, so the UI polling on every frame it draws the job
  feeds it, and none of the places that advance `done` had to change. A trailing window follows
  the real speed changes of an export (an intro, a banner, plain camera footage) where an
  average since the start lags; a stall makes the estimate grow instead of freezing it; a long
  idle start (the loudness pass) is not counted as the job's speed; nothing is shown until a
  second of readings has progress, and nothing once the job is done or finished.
- `format_eta`: `45 s`, `2:05`, `1:02:05`, rounded up.
- Shown in the status bar ("Exporting... 37% · 2:05 left", and the `status.job.progress`
  automation label), in the Export queue (on its own line: the column is narrow) and in the
  Progress panel. The engine adds `etaSeconds` (null while unknown) to every job of `jobs.list`
  and to the encoding items of `export.queue.list`; docs/agents.md says so.

Measured on a whole 9:29 export (193 s, Apple M1), asking every 3 s: the first estimate comes
after 6 s; from 30 s on, the predicted end of the export is on average 1.1 s early and never
more than 14.5 s off (7.5 % of the run). The status bar read "9% · 3:00 left" at 21 s and
"97% · 5 s left" at 184 s.

Tests: `pace` (steady job, speed change, stall, idle start, count going back, bounded history,
extreme values, a whole `Progress`), `format_eta`, the engine (`etaSeconds` of jobs and queue
items), and export_ui (status bar label and queue item).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 13:48:52 -06:00
Brandon Thomas bd12dcae81 Bridge: a blocking export runs as an app job, so it neither times out nor freezes the app (#91, #92)
file.exportMedia with wait: true was forwarded to the app unchanged. The app then encoded on its UI
thread: it showed no progress and answered no other control request until the encode ended, and the
control server's 60 s reply limit reported "timeout" for any longer export while it went on and
succeeded.

BridgeClient::execute now starts the export without wait and polls jobs.list until the job
finishes, returning the same {job, path, result} (or the export's error). filmcraft-cli --bridge
exec and MCP bridge mode both use it, and MCP command_run in bridge mode now gets the same progress
notifications and cancellation as headless mode.

Tested against a fake app (wait: true never reaches it, one export started, result and error
returned) and end to end against the desktop app: a blocking demo export returned its 642-frame
result with exit 0 while ui.inspect was answered in ~0.05 s throughout.

Fixes #91
Fixes #92

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 10:42:28 -04:00
Brandon Thomas 65c6b7365d MCP: render_frame and render_preview leave the playhead where it was (#114)
Both tools are annotated readOnlyHint, but a call with `seconds` moved the active sequence's
playhead (and, with Selection Follows Playhead on, the selection) to that time, in headless and
bridge mode alike.

- headless: Session::render_program_at renders the frame at `seconds` (frame-snapped, as the
  playhead would be) without touching the session.
- bridge: the Program monitor screenshot needs the live playhead there, so the tool records the
  playhead and selection, takes the screenshot and puts both back (the selection only if it changed,
  since selecting also leaves trim mode).

Regression test: rendering at 5 s and 8 s with Selection Follows Playhead on leaves playhead and
selection unchanged and still renders the requested frame (it moved the playhead to 8 s before).

Fixes #114

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:55:24 -04:00
Brandon Thomas 475c294678
Merge pull request #45 from punkhop/fix/move-linked-partners
engine: timeline.move takes linked partners along
2026-10-06 11:37:38 -04:00
Brandon Thomas 51026bd2b2
Merge pull request #60 from punkhop/fix/place-on-a-sound-track
engine: timeline.place on an audio track places the sound there
2026-10-06 11:37:32 -04:00
Brandon Thomas d886d2f322
Merge pull request #44 from punkhop/fix/unknown-track-is-an-error
engine: naming a track that does not exist is an error
2026-10-06 11:37:28 -04:00
Brandon Thomas 8dfa8fff4c
Merge pull request #52 from punkhop/fix/explicit-targets-enable
engine: a command whose targets are named in the parameters does not need a selection
2026-10-06 11:37:16 -04:00
Brandon Thomas 950879ef10
Merge pull request #59 from punkhop/fix/inspect-reports-reverse
engine: sequence.inspect reports a reversed clip
2026-10-06 11:36:58 -04:00
Brandon Thomas 7377def476
Merge pull request #48 from punkhop/fix/sequence-inspect-readback
engine: sequence.inspect reports clip ends, gain and marker comments
2026-10-06 11:36:53 -04:00
Brandon Thomas 508a5a933f
Merge pull request #28 from storybored379/pr/mcp-conventions
MCP: tool annotations, core tools, resources, export progress and cancellation
2026-10-06 07:27:58 -04:00
punkhop 4df7e75085 engine: timeline.place on an audio track places the sound there
Problem: `timeline.place {item, track: "A3"}` (an audio track given as
`track`). For an item with picture and sound it put a video item on the
audio track A3 and a second, sound clip on the default audio track. For
a sound-only item it ignored `track` and placed on the default audio
track.

Solution: `track` is where the picture goes and `audioTrack` where the
sound goes. An audio track given as `track` now places the sound alone,
on that track. What cannot be meant is a parameter error that says why,
and changes nothing: an audio track as `track` together with
`audioTrack`; a video track as `audioTrack`; an audio track for an item
with no sound; a video track alone for an item with no picture. Calls
that name a video track as `track` and an audio track as `audioTrack`
(the timeline panel's drop does) are unchanged. The parameter hint and
`docs/agents.md` say so.

Tests: track_params_tests.rs places a clip with picture and sound and a
sound-only item on audio tracks by name and by id, checks each refusal
for its error kind, its text, an untouched project and undo history,
and checks the usual forms still work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:14:24 -07:00
punkhop 7eb4f1fc7b engine: sequence.inspect reports a reversed clip
Problem: a clip set to play backward (`clip.speedDuration` with
`reverse: true`) read back from `sequence.inspect` exactly like a
forward clip: same `speed`, same source range, nothing that said it was
reversed. An agent could set it and not confirm it.

Solution: clips gain `reverse` (bool) in `sequence.inspect`, named as
in the project file and in the command. Additive; existing keys are
unchanged. `docs/agents.md` lists it and says what it means for the
source range.

Tests: sequence_inspect_tests.rs reverses a demo clip through the
command and checks that only that clip reports `reverse: true`, and
none after undo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 22:45:21 -07:00
punkhop 8f74d18829 engine: timeline.move takes linked partners along
Behaviour change to the default: with Linked Selection on (the default),
`timeline.move` now also moves the linked partners of the listed clips.
A partner lands on its own track in the same mode as the listed clip, so
a caller that lists only a picture clip can now overwrite audio at the
destination time. `"linked": false` gives the old behaviour (only the
listed clips move); the timeline panel passes it, so dragging is
unchanged.

Problem: `timeline.move {"moves": [{clip, track, time}]}` moved only the
listed clip. Its linked sound stayed where it was, out of sync, although
Linked Selection was on and `timeline.trim`, slide and select all act on
linked partners. Separately, a `time` near i64::MAX overflowed the tick
addition in the edit (a panic in debug builds, a wrapped position in
release).

Solution: a linked partner that is not itself in `moves` moves by the
same time offset and stays on its own track. A partner that is listed is
moved once, to where the list says. Two kinds of partner are left
exactly as they are: one on a locked track (the listed clip still moves,
as it did before), and one whose position would not change, which is the
case when the listed clip only changes track. Re-placing such a partner
would drop the transitions at its edges and report a move that did not
happen. A new optional `linked` parameter overrides the toggle for one
call. The command returns `{"moved": [ids]}`, the clips that moved.

No clip is moved before the sequence start. When one would be (a split
edit whose sound starts earlier, or several listed clips asked too far
left), all clips of the call land later by the same amount, so the
spacing asked for is kept. This replaces the per-clip clamp for a call
that lists several clips with a time below zero; a single clip still
lands at zero. The offsets are summed in i128 and checked on the way
back: a start past Tick::MAX is a parameter error and nothing moves.

Tests (timeline_move_tests.rs): partners follow, undo / redo, both
listed, `linked: false`, toggle off, `linked: true`; a locked partner; a
picture moved to another track at the same time leaves its sound and the
sound's crossfade untouched; the common stop at the sequence start with
a split edit and with two listed clips; hostile times (i64::MAX,
i64::MIN, junk). All fail without the change, the hostile one with the
overflow panic. docs/control-protocol.md and docs/agents.md describe the
default, the `linked` parameter and the result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:24:23 -07:00
punkhop 6de389c75a engine: a command whose targets are named in the parameters does not need a selection
Problem: a command gated on the selection refused to run although the
caller named what to act on. `clip.replaceFromBin {clips, item}` answered
"select a clip in the Project panel"; `edit.clear`, `edit.rippleDelete`,
`edit.pasteAttributes`, `clip.enable`, `clip.link`, `clip.speedDuration`
and `clip.audioGain` with `clips` answered "no clips selected". A script
or agent had to change the user's selection first. The cause is the order
in Session::execute: `enabled()` runs before the parameters are read, and
it only knows the selection.

Fix: Session::execute asks enablement per call. When `enabled()` passes,
nothing changes. When it fails and the parameters name targets under a
key the command documents as ids (`clips` / `clip`: clips of the active
sequence, `items` / `item`: project items), the same `enabled()` runs
once more with those targets standing in for the selection, and the
selections are put back before the command runs. So only the selection
condition is answered by the named targets: no sequence, an empty
clipboard or the wrong kind of clip still disable the command, ids of
nothing do not count, and a command that does not document the key
(`edit.cut`) still needs a selection. This works for every command
because the predicates already resolve their targets the way `run` does
with empty parameters. `is_enabled` (menus, `engine.commands`) is
unchanged.

`clip.audioGain` already read `clips`; its parameter doc now says so.

Tests (explicit_targets_tests.rs): the commands above with and without
parameters, undo/redo, the selections left untouched, menu enablement
unchanged, and the cases that must still be refused. Four of the five
fail without the change in execute.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:05:12 -07:00
punkhop 51ecd70763 engine: sequence.inspect reports clip ends, gain and marker comments
Problem: `sequence.inspect` is how an agent verifies an edit, but it
left out where a clip ends in the sequence and in its media, a clip's
audio gain, and the comment of a marker, so those edits could not be
checked from the readback.

Solution: each clip also reports `end`, `endFrame`, `sourceOut` (which
depends on speed, so it cannot be derived from `sourceIn` + `duration`)
and `gainDb`; each sequence marker also reports `comment`. Every
existing key stays. docs/agents.md lists what the readback holds.

Test: `sequence_inspect_reports_clip_ends_gain_and_marker_comments`
(sequence_inspect_tests.rs), on a clip at 200 %, a clip with gain and a
marker with a comment made through the commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:17:52 -07:00
punkhop 9d3975a697 engine: naming a track that does not exist is an error
Problem: `timeline.place {"audioTrack": "A2"}` on a sequence that has
only A1 placed the clip on A1, overwrote what was there and returned ok.
`track_p` returned `None` both for "no track asked for" and for "that
track does not exist", so callers fell back to the source-patched track.
It also took any first letter other than V as an audio track, accepted
track ids that are not in the sequence, and `split_at(1)` panicked on an
empty or non-ASCII name.

Solution: `track_p` returns `Result<Option<TrackId>>` and takes the
command id, like the other fallible parameter helpers. An absent (or
null) parameter is `Ok(None)` and keeps every default as it was. A name
or id the active sequence does not have is a parameter error
(`EngineError::BadParams`): "invalid parameters for `timeline.place`: no
track A2 in this sequence (`audioTrack`)". All callers propagate it:
timeline.place (`track`, `audioTrack`), timeline.move, timeline.razor,
timeline.setTrack, timeline.setTargeting, sequence.closeGap,
sequence.deleteTrack, sequence.deleteTracks, the go-to-gap commands,
sequence.transcribe and the multicam sync reference track.

Not changed: a `timeline.move` entry without a `clip`, `track` or `time`
is still skipped, as before; only a track that was named and is missing
is an error.

Test: `naming_a_missing_track_is_an_error_not_a_default` in
track_params_tests.rs (missing and hostile names, the error kind, nothing
edited, no undo step, existing tracks by name and id still resolve).
docs/agents.md notes the rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:10:19 -07:00
Brandon Thomas 3d4e6d77bb Docs: honest parity assessment in ROADMAP, README and agent docs
ROADMAP now reports two numbers: the feature checklist (~87%, what exists)
and ready for real work (~50-60%, an honest estimate). Adds a by-dimension
assessment with evidence, a prioritised "Where we are lacking" list (hardware
acceleration, a measured parity tool, real-world media corpus, workflow
acceptance tests, Windows/Linux runtime CI, delivery codecs, plugins, AI, web),
and where we're going. Fixes stale rows (M5 effect count, Running now) and logs
2026-10-05.

README: the stale "45%" status replaced with both numbers and the biggest
gaps; the codec note now lists what imports today and what's missing.

docs/agents.md: agents read the honest assessment first, prefer its gaps,
and report progress as measured vs estimated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 06:04:09 -04:00
storybored379 cad16825e3 MCP: progress notifications and cancellation for blocking exports
command_run file.exportMedia with wait: true (headless) runs as the
background export job and uses the MCP progress and cancellation utilities:

- with _meta.progressToken, notifications/progress reports frames done /
  total with the job status, at most every 100 ms (none without a token);
- notifications/cancelled stops the job at the next batch (jobs.cancel),
  deletes the partial output and sends no response;
- the session lock is only held for short polls, so ping, doc_inspect,
  jobs.list etc. are answered while it encodes.

Without wait, and in bridge mode, nothing changes. Test
export_progress_and_cancel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:51:38 -07:00
storybored379 8ee7658303 MCP: filmcraft://document and filmcraft://commands resources, with MCP 2026-07-28 cache hints
- resources/list and resources/read serve the project (tree + active
  sequence, as doc_inspect) and the command catalog (as command_list) as
  JSON, for clients that attach context from resources.
- Results are built with rmcp's types, so resultType follows the negotiated
  revision; for 2026-07-28 clients (current Claude Code) they also carry
  ttlMs and cacheScope, like the generated tools/list. The resource list is
  fixed (10 min), document reads are never cached (0).
- get_info now declares the resources capability; the instructions text
  moves to a const, unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:51:28 -07:00
storybored379 0f70015b93 MCP: tool titles and annotations, command_batch / doc_inspect / render_preview, strict arguments
- Every tool has a title and readOnly / destructive / idempotent / openWorld
  hints, set in its #[tool] attribute, so clients can run the read-only
  tools without asking and ask before edits.
- New tools: command_batch (several commands, stop at the first error by
  default), doc_inspect (project tree + active sequence in one call) and
  render_preview (= render_frame).
- An unknown tool argument is a -32602 error naming it and the accepted
  ones, instead of being ignored.
- A line that is not JSON gets a -32700 reply (id null) instead of being
  dropped without an answer: serve_io filters lines in front of rmcp.
- A panicking command no longer poisons the session lock for the rest of the
  session ("session lock poisoned"); it is reported as "internal error: …"
  (AGENTS.md §0.6).
- docs/agents.md § Conventions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:51:21 -07:00
Brandon Thomas c59d21181f M6.5: export parity — presets, Export mode settings, queue, Quick Export, image sequences
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:27:19 -04:00
Brandon Thomas 2d5267a7df M12.4: agent-friendly CLI: exec/describe/inspect/import/export, run from stdin with JSON lines and --keep-going, --save/--save-as, --bridge to the live app, exit codes; tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 21:26:44 -04:00
Brandon Thomas 4e2a8077d1 docs: public architecture, contributing, testing and agent guides
Public equivalents of the maintainer-local plan/: docs/architecture.md (layers as enforced by
xtask, Tick time base, data model, command system and undo, render/playback/export pipeline,
automation surfaces), docs/contributing.md (+ root CONTRIBUTING.md), docs/testing.md and
docs/agents.md (MCP/control channel usage, agent work loop, decision rules, Definition of Done).
CLAUDE.md points at the public docs wherever it references plan/; AGENTS.md gains a See also list;
README gains a Documentation section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:24:13 -04:00