Commit Graph
203 Commits
Author SHA1 Message Date
storybored379andOpenAI Codex ea79751a5c platform: enable existing NVENC H.264 export on Linux
Runtime-load the NVIDIA encode and CUDA driver libraries, retaining the primary context through session destruction. Preserve the Windows Direct3D path and default-off selection, and test driver rejection and deterministic software fallback.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
2026-10-08 20:40:01 -07:00
86a531ad22 Release: FreeBSD, Flatpak (x86_64 + aarch64) and AppImage zsync; README Downloads (#267)
- release.yml: new `flatpak` jobs (x86_64 on ubuntu-24.04, aarch64 on ubuntu-24.04-arm) that
  repackage the Linux tarball as filmcraft-<v>-linux-<arch>.flatpak, and a `freebsd` job that
  builds filmcraft-<v>-freebsd-x86_64.tar.gz in a FreeBSD 14.3 VM; both feed the draft release.
- AppImages embed gh-releases-zsync update information and ship a .zsync beside them; the
  Linux job checks both.
- Jobs that sign nothing (version, Linux, Flatpak, FreeBSD, web) no longer use the `release`
  environment, so a dispatch on a branch dry-runs them; the signing jobs and the draft-release
  job stay in the environment, so nothing is published from a branch.
- packaging/linux/flatpak-bundle.sh + bundle manifest (same runtime and finish-args as the
  Flathub manifest; packaging-lint checks they agree), packaging/freebsd/package.sh (with a
  --dry-run that packaging-lint runs), and a nightly FreeBSD build workflow.
- README: Downloads section listing every build; docs/releasing.md updated.

Co-authored-by: Brandon Thomas <shared2@storyteller.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 09:41:42 -04:00
Brandon ThomasandClaude Opus 5.5 87c4d1516e About: contributor and model credits compiled in (grab bag + table, self-submitted names) (#215)
* About: contributors compiled in from contributors.json (grab bag + table, sortable, name toggle)

Help ▸ About FilmCraft gets tabs About · Contributors · Models, following the craftrules standard
(standards/contributors.md): contributors/contributors.json is generated by
craftrules/scripts/contributors.py and baked into the binary by crates/ui-egui/build.rs as static
tables (nothing read at run time; the web build has them too). Each contributor line: @username,
opt-in display/real name, PRs, commits, lines +/−/Δ, binary assets +/−, first/last commit.
Sortable by every column; alphabetical ignores case and the leading @. Tabs and credits controls
register automation ids (about.tab.*, about.credits.*).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors docs: craftrules is two levels up (../../craftrules)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors: self-submitted names in contributors/people.toml; AGENTS.md credit-yourself section

build.rs reads contributors/people.toml next to the generated contributors.json and shows a name only
for entries the generator verified as written by that person (craftrules standards/contributors.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors: regenerate (verified people.toml entries)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:16:52 -04:00
Brandon ThomasandClaude Opus 5.5 853f03b1aa M-HW: NVENC on top of the shared hardware-encoding setting (#183); never-crash fixes
Merge origin/main into the NVENC branch. #183 already provides the
HardwareEncoding enum, ExportSettings.hardware_encoding, the
hardwareEncoding export param and the Export > Video > Hardware Encoding
checkbox, so the duplicates from this branch are dropped and NVENC uses
them. filmcraft-export is now an all-platform dependency of
filmcraft-platform (VideoToolbox and NVENC both register encoders).

Never-crash fixes in the NVENC backend:
- submit() rejects a driver input pitch narrower than a row (and unlocks
  the buffer); fill_nv12 uses get_mut instead of slicing rows.
- composition offsets use saturating math and i32::try_from; frame-rate,
  size and timestamp casts are checked; sizes above u16::MAX decline.
- Annex B scanning and NAL type checks use get()/first().
- A failed nvEncOpenEncodeSessionEx that returns a handle destroys it.

ffi.rs carries NVIDIA's MIT notice for nvEncodeAPI.h, listed in
ATTRIBUTION.md under a new "Third-party source code" section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 18:16:12 -04:00
Brandon Thomas 71483312b6 Merge pull request #162 from SaymonMaster14/feat/windows-vp9-av1-hwdecode
Windows: VP9 + AV1 hardware decode (Media Foundation / DXVA)

# Conflicts:
#	ROADMAP.md
#	crates/platform/README.md
2026-10-07 17:58:12 -04:00
Brandon Thomas fcf1c45583 Merge pull request #141 from SaymonMaster14/feat/windows-media-foundation-hwdecode
Windows: Media Foundation/D3D11 hardware H.264 + HEVC decode

# Conflicts:
#	README.md
#	ROADMAP.md
#	crates/platform/Cargo.toml
#	crates/platform/src/lib.rs
#	docs/architecture.md
#	docs/performance.md
2026-10-07 17:48:06 -04:00
Brandon Thomas b5f197567b Merge pull request #186 from dcarnelutti/platform/videotoolbox-hevc-encode
platform: H.265 (HEVC) export with VideoToolbox

# Conflicts:
#	crates/export/src/settings_tests.rs
#	crates/ui-egui/tests/export_ui.rs
2026-10-07 17:47:10 -04:00
Brandon Thomas 84fcbeb1b9 Merge pull request #183 from dcarnelutti/platform/videotoolbox-h264-encode
platform: opt-in hardware H.264 encoding with VideoToolbox

# Conflicts:
#	crates/engine/src/export_tests.rs
2026-10-07 17:46:57 -04:00
Brandon Thomas d18bc04023 Merge pull request #184 from dcarnelutti/render/overlay-bounds-and-buffer-reuse
render: composite only what overlays cover, and reuse the float images
2026-10-07 17:46:38 -04:00
Brandon Thomas fed7201fcf Merge pull request #185 from dcarnelutti/export/progress-eta
export: show the estimated time left of a render next to its percentage
2026-10-07 17:46:37 -04:00
Brandon Thomas 47f75df15e Merge pull request #182 from justjohn12345/pr/layer-box-visibility
Graphics: only selected layers have a box, and a hidden layer has none
2026-10-07 17:44:08 -04:00
Brandon Thomas 91a36a4a3f Merge pull request #166 from justjohn12345/pr/text-box-modes
Graphics: point text and paragraph text, as Premiere has them
2026-10-07 17:44:07 -04:00
Dario CarneluttiandClaude Sonnet 5.5 790b3a0697 platform: H.265 (HEVC) export with VideoToolbox
Adds `Format::Hevc` (Export > Format > H.265 (HEVC); `"format": "hevc"` in
`file.exportMedia`): HEVC Main, 8-bit 4:2:0, in MP4 (`hvc1`) or QuickTime, with AAC audio,
encoded by the macOS hardware encoder. There is no built-in H.265 encoder (one in pure Rust
is a project of its own, and x265 is GPL), so the format exists only where the OS has one.

- `videotoolbox_encode`: the session wrapper creates HEVC sessions too (`VtProfile::HevcMain`;
  the profile also says which codec). Keyframes are the HEVC random access points (NAL types
  16 to 21); the parameter sets are VPS / SPS / PPS; the `hvcC` record in the MP4 sample
  entry is the one VideoToolbox writes (sample description extension atoms), so profile,
  level and flags are the encoder's own. `VtEncoder::uses_hardware()` asks VideoToolbox
  whether the session runs on a hardware encoder (a test checks H.264 and HEVC), so a
  requirement dropped by accident could never become a silent software encode.
- `hardware_encode`: one adapter for both codecs. Choosing the format is the opt-in (the
  `hardware_encoding` setting is for H.264). `hevc_available()` (one small hardware session,
  created once) is registered with the new `filmcraft_export::register_format_probe`, so
  `available(Format::Hevc)`, the format list of Export mode and `exportFormats` follow the
  machine. `Format::has_builtin_encoder` says which formats need such a probe.
- `export`: `Format::is_h26x` where H.264 and H.265 behave alike (MP4 or QuickTime, AAC, size
  estimate, summary line). Two-pass VBR is refused up front ("H.265 export has no two-pass
  mode") and HDR sequences export as SDR, because there is no encoder to fall back to.
- Export mode: H.265 shows H.264's multiplexer, audio, metadata and bitrate controls, without
  profile, level, two-pass or the hardware checkbox, and says "Encoder: Hardware".

Apple M1 (8 cores), 1080p25, a 9:29 timeline (camera clip, ProRes 4444 overlays, AAC,
loudness): hardware H.265 at 10 Mb/s 195 s, hardware H.264 at 20 Mb/s 204 s (CPU 1029 s and
1042 s; the same H.264 export took 193 s and 200 s on other runs). It is not faster: the codec
that compresses further is the more expensive one (raw encoders: 166 and 189 frames/s), and
the export is bound by the CPU compositor, not by the media engine.

Quality per bit, 30 s of the same timeline against a ProRes 422 HQ export (average PSNR over
the three planes, SSIM): 20 Mb/s 46.88 dB / 0.9902 (H.264) and 46.91 dB / 0.9903 (H.265);
10 Mb/s 44.96 / 0.9863 and 45.09 / 0.9863; 6 Mb/s 43.72 / 0.9833 and 44.31 / 0.9843; 4 Mb/s
42.91 / 0.9807 and 43.64 / 0.9825; 3 Mb/s 42.37 / 0.9786 and 43.05 / 0.9807. The same picture
at 10 Mb/s and above; at 3 to 6 Mb/s H.265 needs 22 to 31 % less bitrate for the same PSNR.
Both formats therefore keep the same default bitrate. Details in docs/performance.md (HW3).

Tests: tests/hardware_encode.rs gets the HEVC counterparts of the H.264 ones (what is taken
and declined, a round trip through our HEVC decoder with luma PSNR above 30 dB, keyframes, MP4
and QuickTime exports with AAC, ffmpeg / ffprobe reading `hevc` / `Main` / `hvc1` /
`yuv420p` / BT.709, exact sizes, hostile configurations, encoders dropped at any point, the
format list agreeing with the probe, sessions on the hardware encoder); settings_tests.rs
checks names, ids, defaults, the summary, two-pass refusal and the probe; export_ui.rs the
controls H.265 shows and hides.

Stacked on the hardware H.264 encoder (the first commit of this branch).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 14:00:08 -06:00
Dario CarneluttiandClaude Sonnet 5.5 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
Dario CarneluttiandClaude Sonnet 5.5 d2ab33ba76 render: composite only what overlays cover, and reuse the float images
The CPU compositor, which is what export runs, works on premultiplied linear f32 images,
33 MB each at 1080p, and every layer was one of them. A profile of a 9:29 export with a
hardware H.264 encoder (so the encoder is no longer the bottleneck) put 61-64 % of the busy
CPU in compositing, 12 % in zeroing and copying memory, 8-11 % each in the float -> 8-bit
and RGBA -> Y'CbCr conversions and 4-7 % in decoding the ProRes overlays: a banner that is
transparent except for its bottom 100 rows still cost a whole conversion and a whole mix,
and the bottom layer paid for a zero-filled canvas and a pass that mixes it over that canvas.

Three changes, each of which gives the bits the general path gives:

- The canvas is allocated when a layer needs it, and an opaque first layer (a Y'CbCr frame
  without an alpha plane, opacity 100 %, Normal blend, the size of the output) becomes the
  canvas instead of being mixed over zeros.
- A clip of plain media whose frame has an alpha plane (a ProRes 4444 banner) is converted
  and mixed only inside the rectangle where its alpha is not zero: `VideoFrame::alpha_region`,
  `to_linear_f32_region` and `blend::composite_at` (`composite` is `composite_at` at the
  origin; Dissolve's pattern follows the pixel's place in the output). A clip with effects,
  opacity masks, frame blending, colour management or a picture that is not placed one to
  one on the output takes the general path, as before.
- `filmcraft_frame::pool` keeps idle f32 images (320 MiB at most). `take_f32_overwritten`
  hands one back with its old contents, which the conversions overwrite whole (so no
  zero-fill), and the export pipeline gives each frame's image back after the 8-bit
  conversion.

Apple M1 (8 cores), a machine that was not idle, so the two builds alternate and the
median is reported:

- Synthetic 1080p frame (`cargo run --release -p filmcraft-render --example frame_bench`,
  5 rounds): camera only 8.9 -> 6.5 ms, camera + banner 16.2 -> 7.8 ms, same picture checksum.
- A real project (1080p25 camera clip, ProRes 4444 banner and subscribe bar), 30 s with the
  banner exported with the built-in encoder, which is deterministic: 46.4 -> 36.7 s, and
  both exports are the same file (same MD5), so every frame of the real pipeline matches.
- The same project with a hardware encoder, 3 rounds: 30 s with the banner 17.4 -> 11.2 s
  (CPU 99 -> 56 s), 30 s of the camera alone 11.4 -> 8.8 s, 20 s with the subscribe bar
  11.1 -> 7.0 s.

Tests: `render/src/region_tests.rs` compares each shortcut with the general path bit for bit
(an opaque bottom layer, banners over and under a camera, an 8-bit sticker, every blend mode,
several opacities, moved, scaled and effect clips, a bigger source, reduced-resolution
rendering), each render twice so the second starts from recycled buffers, and checks which
layers take a shortcut; the frame tests check that a region is the crop of the whole
conversion, that `alpha_region` is the box around what is not transparent, and that a buffer
full of NaN never leaks into a conversion, for every pixel format. Breaking a shortcut on
purpose (a region one pixel off, an opaque layer taken as the canvas at half opacity, a
conversion that skips the alpha channel) makes them fail.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 13:35:20 -06:00
Dario CarneluttiandClaude Sonnet 5.5 4cab6bc5da platform: opt-in hardware H.264 encoding with VideoToolbox
Export encodes H.264 on the CPU with the built-in encoder, which is most of the time
of an export (about two thirds of the busy CPU in a profile of a 20 s export). On
macOS the media engine can do it: a VideoToolbox compression session behind
`filmcraft_export::VideoEncoder`, registered by `filmcraft_platform::register()`.

- `ExportSettings::hardware_encoding` (`HardwareEncoding::Off` | `Auto`; Export > Video >
  Hardware Encoding, `"hardwareEncoding": "off|auto"` in `file.exportMedia`, a boolean is
  understood too), off by default: the built-in encoder's output is byte-identical on
  every machine, a hardware encoder's is not.
- `videotoolbox_encode` (FFI, same containment as the decoder: SAFETY comments, no
  panic across the callback, safe API) and `hardware_encode` (safe adapter and factory).
- The factory declines, so the built-in encoder is used, for other formats, MXF, two-pass
  VBR, HDR, odd sizes, non-square pixels and any configuration VideoToolbox cannot create a
  hardware session for. An encoder that fails mid-export stops it with an error: unlike a
  decoder, it cannot hand a half-written stream to another one.
- No B-frames: on real 1080p footage the quality is the same with and without them
  (+-0.3 dB at equal bitrate) and the bitrate lands closer to the target, and without
  them every frame is presented at its decode time (no composition offsets, no edit list).
- `export::register_encoder` ignores a factory registered twice; `encoder_registered`.

Apple M1, 8 cores, single runs: a 9:29 timeline (camera clip, ProRes 4444 overlays with
alpha, AAC, loudness) exports in 311 s instead of 793 s (2.5x faster); its densest 134 s
in 84 s instead of 395 s (4.7x). The outputs compare at SSIM 0.990 / PSNR 46.5 dB. The encoder
alone runs 200 fps at 1080p (docs/performance.md).

Tests (tests/hardware_encode.rs): what is taken and declined, a round trip through our
software decoder, an export that decodes in our decoder and in ffmpeg, the fallback for
everything hardware declines, exact output sizes, an empty range, hostile configurations
and encoders dropped at any point; the engine's `hardwareEncoding` parameter.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 13:34:51 -06:00
Davi AndreiandClaude Sonnet 5.5 11b199dbcd Docs: NVENC H.264 hardware encoding
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMorBHwct72udS2LiC3ZHp
2026-10-07 15:42:28 -03:00
justjohn12345andClaude Opus 5.5 b6281f968f Graphics: only selected layers have a box, and a hidden layer has none
Every layer of the selected graphic was outlined on the Program monitor,
so a box stayed after its layer was deselected, and a layer whose
visibility was off kept its box and could still be clicked and dragged.

As in Premiere Pro 26: a layer has a box only while it is selected (or
typed into), and a layer whose visibility is off is not on the monitor
at all: no box, no handles, no click target.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:24:43 -04:00
Davi AndreiandClaude Sonnet 5.5 79cb1d0f8d Windows: VP9 + AV1 hardware decode through Media Foundation / DXVA (#30)
Extends the Windows backend (#141) to VP9 (profiles 0 and 2) and AV1 (main, 8/10-bit), 4:2:0.

- codecs::hw: FrameStreamInfo (vpcC / av1C): profile, bit depth, chroma, picture size and colour as
  the software decoders report them (vp9_color / av1_color are now shared with them), random-access
  samples, in-band format changes; StreamInfo lets HybridDecoder handle NAL and frame-coded streams.
- platform: media_foundation::stream (what each codec needs: DXVA profile, MFT request, input
  format), MfDecoder generalised; VP9 frames / AV1 temporal units go in as they are; the MFT's
  declared picture size (display aperture) is checked against the bitstream's.
- Declined up front: VP9 profiles 1/3, AV1 profiles 1/2, 4:2:2/4:4:4, mono, 12-bit, no codec
  extension, no DXVA profile. A key frame of another size or a different sequence header falls back
  to the software decoder.
- Tests: bit-exact parity (360p / 1080p / 2160p, seeks, resets, flush, hidden frames), forced
  failure, damaged samples and av1C, format change fallback, WebM / Matroska through the media stack.
- Bench: AV1 fixtures with libaom when ffmpeg lacks SVT-AV1.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMorBHwct72udS2LiC3ZHp
2026-10-07 12:39:45 -03:00
Brandon Thomas 5695626216 Merge pull request #156 from storytold/fix/docs-85-86-87-88-99
Docs: releasing guide, CLI export options, README and CLAUDE.md drift (#85, #86, #87, #88, #99)
2026-10-07 10:49:14 -04:00
Brandon ThomasandClaude Opus 5.5 fa67dfa372 Docs: releasing guide, CLI export options, README and CLAUDE.md drift (#85, #86, #87, #88, #99)
- docs/releasing.md (#99): release.yml points readers to it twice and it didn't exist. Written from
  the workflow and packaging scripts as they are (macOS, Windows x64/x86/arm64, Linux x86_64/aarch64
  with the glibc 2.35 floor, web; draft release, secrets).
- filmcraft-cli help (#86): export lists --scale, --quality, --no-audio and --queue, and apv among
  the formats.
- README (#87): more than 650 engine commands (671 today), not about 135.
- README (#88): the web line says the browser app ships with every release and links docs/web.md.
- CLAUDE.md (#85): the Illustrator-class sibling is ../vectorcraft (formerly DrawCraft).

Fixes #85
Fixes #86
Fixes #87
Fixes #88
Fixes #99

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 10:49:08 -04:00
Brandon Thomas 2647a2d9f8 Merge remote-tracking branch 'origin/main' into pr151
# Conflicts:
#	crates/engine/src/settings_tests.rs
#	crates/engine/src/transcript_tests.rs
2026-10-07 10:44:47 -04:00
Brandon ThomasandClaude Opus 5.5 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
carlitosteso3andClaude Opus 5.5 b8fa786cad transcripts: disabled, not failing, in builds without speech-to-text (#89, #97, #98)
- transcript.generate is enabled only with a recogniser: one installed in
  Session::transcriber, or the build's own (feature `whisper`). Otherwise
  describe, command_list enabled_only and the menu report it disabled with
  "speech-to-text is not available in this build" as the reason (#97).
  Transcribe Sequence, which runs it, follows.
- transcript.downloadModel is enabled only with `speech-download` (#98).
- file.import with Automatically transcribe clips on no longer skips
  transcription silently when it can't run: the disabled command's reason
  lands in the import's errors as "transcription: ..." (#89).
- docs/transcripts.md describes the disabled state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:14:05 -05:00
Brandon ThomasandClaude Opus 5.5 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
justjohn12345andClaude Opus 5.5 8407f233c1 Graphics: point text and paragraph text, as Premiere has them
A text layer is now one of two types, each with Premiere Pro 26's handles:

- Point text (Type tool click): every handle scales the layer about its
  anchor point, both axes alike; the font size is untouched.
- Paragraph text (Type tool drag): the dragged rectangle is a box the text
  wraps in. Handles move that side or corner of the box, the opposite side
  stays, the text re-wraps at the same size. Lines below the box are not
  shown and the bottom-right handle turns into a red plus.
- The anchor point can be dragged with the Selection tool; the layer stays.
  It is picked before anything under it, so a shape dragged by its exact
  centre moves its anchor.
- Properties > Text > wrench opens Text Properties, where Text Layer Type
  switches a layer between the two (graphics.setTextType); the text stays
  where it is.

Shapes keep stretching away from their opposite side.

New: text layer parameter box_height, graphics.newText `box`, and
textType / box / overflow / anchor / scale in graphics.list. A layer saved
before a parameter existed can now be given it with graphics.set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:54:26 -04:00
Davi AndreiandClaude Sonnet 5.5 932cdb77f5 Docs: Windows hardware decoding (platform README, ADR 0001, architecture, performance, roadmap)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMorBHwct72udS2LiC3ZHp
2026-10-07 10:07:40 -03:00
Brandon Thomas cd3fe20ef9 Merge remote-tracking branch 'origin/main' into apv-105 2026-10-07 07:09:54 -04:00
Brandon Thomas 16475596d3 Merge remote-tracking branch 'origin/main' into nest-116
# Conflicts:
#	crates/engine/src/commands.rs
2026-10-07 07:09:20 -04:00
Brandon Thomas d4e438e932 Merge remote-tracking branch 'origin/main' into apv-105 2026-10-07 07:08:58 -04:00
Brandon Thomas 7916cfb80e Merge pull request #131 from punkhop/fix/audio-plays-ahead
Audio: mix playback 200 ms ahead so a busy Mac doesn't drop sound
2026-10-07 07:08:29 -04:00
Brandon Thomas 005745d9ec Merge remote-tracking branch 'origin/main' into apv-105
# Conflicts:
#	crates/codecs/src/mp4.rs
2026-10-07 06:01:14 -04:00
Brandon Thomas 19d2f15148 Merge remote-tracking branch 'origin/main' into nest-116
# Conflicts:
#	crates/ui-egui/src/lib.rs
#	crates/ui-egui/src/panels/timeline.rs
2026-10-07 06:00:36 -04:00
Brandon Thomas a4c4d38550 Merge pull request #123 from Luskish/fix/control-close-on-malformed-input
Control: close the connection on input that isn't a request (#119)
2026-10-07 05:57:55 -04:00
Peet GuercioandClaude Opus 5.5 bb5d7ca0a6 Audio: mix playback 200 ms ahead so a busy Mac doesn't drop sound
Problem: playback decoded and mixed every ~10 ms device buffer inside the
cpal callback (AAC decode, effects, opening a clip's file at a cut, rayon,
locks). On a loaded Mac any stall missed the device deadline: static and
dropouts, different on every play.

Solution: play_ahead mixes on its own thread into a lock-free ring kept
200 ms ahead; the callback only copies. An underrun plays silence, is
counted, and the mixer skips the same span so sound stays locked to the
picture. Any device width plays the channels it shares with the mix.
perf.stats reports playback.audio (callbacks, underruns, missingFrames,
minLeadMs). The program mix moved to playback_mix so bench_audio can play a
real project through a simulated 48 kHz device and count dropouts.

Context: on a real 22-sequence project, load average 38-58, 15 s per cut:
mixing in the callback overran 8-170 times, mixing ahead 0 (ring never
below 149 ms). The web build keeps mixing in its callback (no threads).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 23:47:47 -07:00
LuskishandClaude Opus 5.5 023127e146 Control: close the connection on input that isn't a request (#119)
A line that is not a JSON object with a string `method` (an HTTP request
line, other non-JSON text, a JSON array, invalid UTF-8, a line over 4 MiB)
now gets one error reply and the connection is closed, so a browser's
cross-origin fetch can't smuggle a command in its body. Connections are
capped at 16. Same transport as LightCraft (storytold/lightcraft#129).

Fixes #119.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 22:32:13 -04:00
justjohn12345andClaude Opus 5.5 1ebc1440e7 NEST11: the architecture doc says a plain nest's layers go into the frame plan
docs/architecture.md still listed every nested sequence among the things
rendered on the CPU and handed to the GPU as one image. Since NEST11 that
holds only for nests that are not plain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 20:53:53 -04:00
justjohn12345andClaude Opus 5.5 e13151d87b NEST10: the captions doc says nested captions are part of the nest's picture
docs/captions.md still said nested sequences do not burn in their own
captions. Since NEST10 the captions of a nested sequence are drawn wherever
the nest is shown, in the Program monitor and in exports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 20:53:53 -04:00
justjohn12345andClaude Opus 5.5 9b31f4eb84 NEST15: nested sequences in AAF and OMF, the way Premiere Pro writes them
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>
2026-10-06 20:53:34 -04:00
justjohn12345andClaude Opus 5.5 eeec772671 NEST14: a project reopens with its sequence tabs and their views
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>
2026-10-06 20:23:19 -04:00
mweinbach 15787f7fe8 Codecs: add clean-room APV (RFC 9924) decoder, encoder, container support, and export presets 2026-10-06 17:24:34 -04:00
EthanSuquetandClaude Opus 5.5 3eae70b11e h264enc: strict ffmpeg oracle that sees damaged slices (#74)
ffmpeg prints "corrupt decoded frame" as a warning, so the oracle's
`-v error -ec 0` decode let a stream with a whole slice missing pass
silently. Switching that decode to `-v warning` is not enough: with
`-ec 0`, ffmpeg 7.1/8.1 flags every picture that has more than one
slice, libx264's multi-slice streams included, while the pixels are
unchanged. That is the 119/120 in #74; the encoder's bitstream is not
at fault (VideoToolbox decodes the same streams bit-identically, and
ffmpeg with strict error detection reports nothing).

The oracle now decodes with ffmpeg's default concealment, `-v warning`
and `-err_detect +crccheck+bitstream+buffer+explode`, fails on any
output or a non-zero exit, and still compares every pixel with the
encoder's reconstruction. A negative control drops and truncates
slices (CAVLC and CABAC, 3 slices) and checks the oracle reports each
one; it fails with the old flags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 16:02:53 -04:00
Brandon ThomasandClaude Opus 5.5 7f8e102b13 Fonts: the Japanese interface uses craft-fonts first; absolute CRAFT_FONTS_DIR
The merged Japanese interface (#38) looked only for a system font. When
FilmCraft is built with craft-fonts, theme::install already appends the
Japanese faces to every font family, so switching to Japanese uses them
(no system scan) and works on the web too; otherwise it falls back to an
installed system font as before. Docs use an absolute CRAFT_FONTS_DIR
(build.rs runs in crates/text, so a relative path resolves from there).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:26:14 -04:00
Brandon ThomasandClaude Opus 5.5 9953befe28 Fonts: use craft-fonts as an optional build input
crates/text/build.rs embeds the fonts of a storytold/craft-fonts checkout
named by CRAFT_FONTS_DIR as filmcraft_text::fonts::CRAFT_FONTS (native:
all of them; wasm32: only BIZ UDPGothic Regular). Unset, CRAFT_FONTS is
empty and nothing changes. A bad CRAFT_FONTS_DIR is a warning, or an
error with CRAFT_FONTS_REQUIRED=1. Never a Cargo dependency, no network.

- UI: the Japanese faces are the last fallback of every egui font family
  (BIZ UDPGothic first, Bold first in the semibold/medium families).
- Text engine: registered after the bundled faces; fallback_for picks
  them after the bundled faces and before system faces (Mincho for serif
  text, Gothic otherwise, nearest weight).
- Tests: Japanese glyphs (no tofu) in the UI and title paths, skipped
  without craft-fonts; the theme works with CRAFT_FONTS empty.
- Release: every build job checks out craft-fonts at a pinned commit and
  builds with CRAFT_FONTS_DIR + CRAFT_FONTS_REQUIRED; packages carry
  OFL-<family>.txt. NOTICE, ATTRIBUTION.md (optional build input), AGENTS.md,
  CLAUDE.md, README and docs/contributing.md describe it.

FilmCraft main bundles no Japanese font, so no font files move.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:40:29 -04:00
Brandon Thomas 5c8471c9f4 Merge pull request #41 from justjohn12345/pr/decode-memory
MEM1–MEM4 + HW1: clip memory on a timeline from 12 GB to 1.9 GB; hardware decode actually on
2026-10-06 11:38:03 -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 39ef62607d Merge pull request #47 from punkhop/fix/app-help-and-unknown-flags
filmcraft: --help prints usage and an unknown option is an error
2026-10-06 11:37:22 -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