What does filmcraft do?
Updates: if this folder has a
.installed-fromfile (written when it was copied from the repo), rungit ls-remote https://gitlab.com/e1dred/agent-skills.git HEADonce per session. Take the hash on the output line that ends in exactlyHEAD; if it differs from the file's, tell the user a newer version exists and offer to update it (see "Updating" in https://gitlab.com/e1dred/agent-skills/-/blob/main/INSTALL.md, which can merge local edits); never update without asking. Compare the hashes after trimming whitespace. No file, a clone or symlink, or no network (empty output or an error fromgit ls-remote): skip silently.
Field notes from first headless tasks, last tested on FilmCraft 0.17.1 (Linux AppImage). FilmCraft is an
early-alpha, Premiere-style video editor in Rust. Every menu item and button is an engine command; the CLI, the
MCP server and the app's control channel run the same commands. If filmcraft-cli --version is newer, re-check
anything here before relying on it.
Setup
- CLI: extract it from the AppImage (
filmcraft.appimage --appimage-extract, copyusr/bin/filmcraft-clisomewhere stable); self-contained, no window. Never run--versionon the AppImage itself. - Headless:
filmcraft-cli --project p.fcproj exec <id> key=value …; scripts are JSON lines and keep one session:filmcraft-cli --project p.fcproj run script.jsonl [--save]with lines{"id":"…","params":{…}}, or standalonefilmcraft-cli runafterfile.newProject. Subcommands:commands,describe,inspect,import,export,render,probe. - Live app:
filmcraft --control 9876(orFILMCRAFT_CONTROL_PORT); the listener needsFILMCRAFT_CONTROL_TOKEN(64 hex characters) in the environment — without it it stays closed. Bridge withfilmcraft-cli mcp --bridge 127.0.0.1:9876. - MCP:
filmcraft-cli mcp(headless) or--bridge(live).
Where things are
Read the file that matches the task; each lists the sections it holds. Use grep -n on reference/ to find a command id.
File in reference/ | Read it for | Sections |
|---|---|---|
projects-and-timeline.md | Projects, importing media, sequences, playhead, razor and trim, markers, saving, transitions, video effects and keyframes, frame holds, project notes | Projects and media; Sequences; Cutting and trimming; Markers and saving; Transitions; Video effects and keyframes; Frame holds, selection and project notes; Other editing tools |
titles-and-graphics.md | Text and shape layers, size/fit on small frames, reading layers back, vertical text and imported images | Titles; Sizing and fitting; Layer properties; Other layer tools |
audio-mixer.md | Mixer strips, values, submixes, inserts and sends; clip gain and Essential Sound | Mixer; Clip audio |
captions.md | Subtitle tracks, adding/editing/moving captions, SRT import and export | Caption tracks and SRT |
export.md | Export presets and formats, the export queue, MP4/GIF export, frame stills, interchange exports, probing | Presets; Exporting; Presets and the export queue; Stills; Probing |
command-catalogue.md | Every other command id by family (clip, timeline, sequence, trim, graphics, edit, file, project, multicam, source, prefs, shortcuts, …), each marked verified (JSON path old → new), returned a result, ran, or refused with the reason; the setup that unlocks selection-dependent commands | One section per family |
live-app.md | Driving the running app over --bridge: the ui_* tools, what works only live, running it on a hidden display | Connecting; What the live app adds |
newer-commands.md | What each version added or changed | 0.17.0; 0.17.1 |
log.md | Dated log of what was tested when | Log |
CLI essentials (verified)
filmcraft-cli commands --json # 716 commands: id, label, menu, params, enabled
filmcraft-cli describe file.import # parameter docs for one command
filmcraft-cli --project p.fcproj inspect # {project:{…}, sequence:{…}} as JSON
filmcraft-cli import clip.mp4 --project p.fcproj
filmcraft-cli --project p.fcproj export out.mp4 --scale 0.5 --quality 70
filmcraft-cli --project p.fcproj render --seconds 2 --out frame.png
filmcraft-cli probe clip.mp4 # kind, duration (ticks), video/audio stream details
- A JSONL script keeps state:
file.newProject→file.import {"paths":["clip.mp4"]}→{"items":[1]}→file.newSequenceFromClip {"items":[1]}→{"clips":[6,7],"sequence":4}→ … →file.saveAs {"path":"p.fcproj"}. - Pass
--projecton every later CLI call: without it each invocation is a fresh session.
Traps
- Times are ticks, not frames: the sequence reports 762,048,000,000 for 3 s (254,016,000,000 ticks/s);
playhead.setacceptsseconds,frame,timeortimecodeand snaps to the nearest frame (1.5 s → frame 37). - A text layer at the default size overflows a small frame.
graphics.newTextdefaults to 100 px: on a 320×180 sequence, "Hello FilmCraft" atsize:48drew 334.7 px wide from x=40 and was cut at the right edge. Fit it by settingsize(~32 → 223 px wide; checked in a render). Paragraph text withbox:[w,h]wraps, but the box CLIPS lines that do not fit its height:box:[240,60]at size 40 showed only "Hello" (the second line was cut off), so make the box tall enough for every line (about 1.3 × size per line). - Ripple edits fail across a sync-locked track:
timeline.trimwithmode:"ripple"answered "this edit would break sync: V2 is sync-locked and has a clip in the way" when a title sat on V2. Lock/unlock the other track first or trim without ripple. probe --image-sequenceonly accepts numbered stills; on a movie it errors.effects.addKeyframeheadless with a rawmediaTimedid nothing ({added:false, mediaTime:0}; 0.17.0), but in the live app{clip, effect:"opacity", param:"opacity", seconds:1.5, value:40}added one (keyframes 5 → 6; the time snaps to a frame: 624699432000). Headless, move the playhead and useeffects.toggleAnimation, orgraphics.set {…,"keyframe":true}for title properties.mixer.addInsertneeds a real audio effect id (hard_limiter, fromeffects.list {"kind":"Audio"}), not a label such asvolume;mixer.addSendneeds the submixreffrommixer.addSubmixin the same run.graphics.*commands need a title in the same session: rungraphics.newTextfirst in the same script, thengraphics.alignFrame.*,nudge*,fontSize*,leading*work headless (readgraphics.list {clip}→layers[0]). Without one they answer "no graphic clip".graphics.deleteLayerwas only accepted live (no read-back).- Selection does not survive a save/reopen: put
project.select {items:[id]}/timeline.selectin the same script as the command that needs it ("select an item in the Project panel", "no clips selected"). - FCPXML export can drop a title:
file.exportInterchange {"format":"fcpxml"}reported the graphic clip "not supported" while still writing the file; read the report before trusting an interchange export. --demoopens the built-in demo project: it inspects and renders (a 1920×1080 frame at 1 s), exported fine only as far as tested; missing media exports as placeholder frames unless--allow-offlineaccepts them.file.exportMediawaits for its file unless"wait":false; the CLIexportalways waits.
Verify before reporting
inspect sequence (clip starts/ends in frames, tracks, transitions, effects), probe for the exported file's
duration and codecs, and render a frame at the interesting times to look at. Frame counts: 3 s at 25 fps = 75
frames; a ripple trim of -10 frames gave 65.