Commit Graph
100 Commits
Author SHA1 Message Date
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 a2f6ba8b44 Merge pull request #72 from storytold/pr/docs-hw-decode-correction
Docs: hardware decode correction; log the 2026-10-06 community batch
2026-10-06 11:39:47 -04:00
Brandon ThomasandClaude Opus 5.5 83481c146f Docs: hardware decode was only active in the desktop app since #41; log the 2026-10-06 community batch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 11:39:41 -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 37361900a9 Merge pull request #38 from midasdf/feat/japanese-interface-language
i18n: Japanese interface language (uses an installed system font)
2026-10-06 11:37:57 -04:00
Brandon Thomas e89feeac4d Merge pull request #58 from punkhop/fix/speed-ripples-linked-clips-once
edit: a speed change with ripple moves later clips once
2026-10-06 11:37:52 -04:00
Brandon Thomas 555bfe7c18 Merge pull request #57 from punkhop/fix/ripple-delete-split-edit
edit: ripple delete closes one gap for a clip with a split edit
2026-10-06 11:37:47 -04:00
Brandon Thomas 5dd09386f6 Merge pull request #56 from punkhop/fix/ripple-trim-keeps-split-edit
edit: a ripple trim takes a later split edit along
2026-10-06 11:37:42 -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
Brandon Thomas 8a795324f4 Merge pull request #50 from punkhop/fix/replace-reports-short-media
engine: Replace With Clip reports clips that run past the new media
2026-10-06 11:37:11 -04:00
Brandon Thomas eb3ca3e811 Merge pull request #49 from punkhop/fix/replace-from-bin-keep-source-in
engine: clip.replaceFromBin can keep the clip's source In
2026-10-06 11:37:05 -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 5bc07026c7 Merge pull request #43 from punkhop/fix/import-auto-points
engine: a project saved after an interchange import opens again
2026-10-06 11:36:46 -04:00
Brandon Thomas a3b077877f Merge pull request #51 from punkhop/fix/interchange-export-reports-dropped-graphics
interchange: an export names every title it cannot carry
2026-10-06 11:36:41 -04:00
Brandon Thomas a407099019 Merge pull request #46 from punkhop/fix/export-interchange-unknown-format
engine: file.exportInterchange refuses an unknown format
2026-10-06 11:36:36 -04:00
Brandon Thomas bccc3dd031 Merge pull request #42 from punkhop/fix/mp4-audio-thumps
codecs: resampled MP4 audio reads apply the edit list once
2026-10-06 11:36:31 -04:00
Brandon Thomas bd40764756 Merge pull request #69 from storytold/pr/windows-arm64
Windows on ARM64: release builds, PE header check, install test on ARM64 hardware
2026-10-06 10:10:16 -04:00
Brandon ThomasandClaude Opus 5.5 e6d693cc24 Windows ARM64 workflow: pin third-party actions by commit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:51:06 -04:00
Brandon ThomasandClaude Opus 5.5 ac906666d9 Windows on ARM64: release builds, PE header check, install test on ARM64 hardware
Release builds add an arm64 MSI and portable zip, cross-compiled on the x64
runner (signing and WiX as for x64). package.ps1 checks each binary's PE
header before packaging: Machine must match -Arch, the app must be a GUI
program and the CLI a console program. It skips the CLI smoke run when an
ARM64 build can't run on the host. A new Windows ARM64 workflow packages the
ARM64 MSI, installs it on a Windows 11 ARM64 runner, checks both installed
exes are ARM64, runs the CLI, and uninstalls. Ported from printcraft#66.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:38:38 -04:00
Brandon ThomasandClaude Opus 5.5 6a9fdce2ef codecs: released decoders are dropped off the cache lock; dirty-pool bit-exact test
trim_decoders dropped the decoders it took from idle caches while the requesting cache's lock was held. Dropping a decoder can block (a VideoToolbox session waits for its frames in flight), so the render path paid that latency under the lock. trim_decoders now returns the released decoders and frame_late keeps them in a local declared before its lock guard, so they drop after the guard on every return path; the cap is enforced exactly as before.

New tests/pool_bitexact.rs: primes the frame pool with 0xAA-filled plane buffers, then decodes an H.264 fixture into them; every frame must match ffmpeg bit for bit, and the pool must report the reuse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:16:09 -04:00
Brandon ThomasandClaude Opus 5.5 47c6b03681 filmcraft: --help, --version and argument errors show a message box on Windows
Windows release builds have no console (windows_subsystem = "windows", added on main after this branch), so --help printed nothing and an unknown option or a bad FILMCRAFT_CONTROL_PORT quit with exit code 2 and no visible message. The text is still printed, and on Windows release builds also shown in a message box.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 09:05:59 -04:00
Brandon Thomas 39d81aee14 Merge remote-tracking branch 'origin/main' into pr-47-fix 2026-10-06 08:46:53 -04:00
Brandon ThomasandClaude Opus 5.5 2f49131f64 edit: speed changes refuse NaN and never overflow the timeline
set_speed_group accepted NaN (speed <= 0.0 is false for NaN), and a tiny speed (1e-7) saturated the new duration to i64::MAX, after which the ripple shift overflowed (panic in debug, wraparound in release). Non-finite speeds are refused, the new duration is capped at what the timeline can hold, and the ripple shift uses checked arithmetic; a refused edit changes nothing (it works on a copy). Regression test with hostile speeds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 08:46:30 -04:00
Brandon Thomas 61ab6f5c7d Merge pull request #63 from storytold/pr/release-0.2.1
Release: FilmCraft v0.2.1
2026-10-06 08:01:17 -04:00
Brandon ThomasandClaude Opus 5.5 e659671bdf Release: FilmCraft v0.2.1
Fixes the Windows installer: the Start Menu shortcut and Add/Remove Programs icon were blank, and the shortcut description was wrong. Release builds no longer open a console window behind the app. Export can no longer panic (filmcraft-export never-crash).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 08:01:04 -04:00
Brandon Thomas bc1523a33f Merge pull request #62 from storytold/pr/never-crash-export
Never crash: filmcraft-export without expect()/unreachable!()
2026-10-06 07:58:18 -04:00
Brandon ThomasandClaude Opus 5.5 136424dc93 Never crash: filmcraft-export without expect()/unreachable!()
The five 'mux created' / 'audio' expects in the stepped exporter now return
an ExportError::Encode instead of panicking if the writer or audio pipeline
is missing, and the non-stepped dispatch returns Unsupported instead of
unreachable!(). The crate root gets the never-crash deny attribute, so every
FilmCraft production crate now carries it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 07:58:07 -04:00
Brandon Thomas 6bf2756cde Merge pull request #61 from storytold/pr/windows-packaging
Windows packaging: Start Menu/Add-Remove icon, no console window, correct description
2026-10-06 07:54:51 -04:00
Brandon ThomasandClaude Opus 5.5 c60d3f9c4c release.yml: explicit if for the wasm-bindgen install (shellcheck SC2015)
packaging-lint has failed on main since this step was added; same behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 07:53:46 -04:00
Brandon ThomasandClaude Opus 5.5 7fee797ee7 Windows packaging: Start Menu/Add-Remove icon, no console window, correct description
The MSI Icon id had no .ico/.exe extension, which Windows Installer needs for
advertised shortcuts and ARPPRODUCTICON, so both showed a blank icon. Release
builds of the desktop app are now GUI-subsystem (no console window; the CLI stays
a console program), the shortcut/summary description names what FilmCraft does, and
packaging-lint rejects an Icon id without an extension. Found in printcraft#57.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 07:51:02 -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
Brandon Thomas 5012fbd598 Merge pull request #19 from storybored379/pr/deterministic-export
Export: same file on every machine (fixed mux interleave, H.264 slices by frame size)
2026-10-06 07:27:44 -04:00
Brandon Thomas 1f7e4f802e Merge pull request #39 from storybored379/fix/ttml-timing
Captions: report invalid TTML timing parameters on import
2026-10-06 07:27:28 -04:00
Brandon ThomasandClaude Opus 5.5 5d1165d06b MCP: cancelled exports delete only their own files
remove_partial removed every '<stem>[digits].<any ext>' file in the output folder modified since the export started, so cancelling an export to trailer.mp4 also deleted trailer.aep or trailer2.psd if they were saved during the export. It now removes only the output file, numbered stills with the output's extension, and the .srt/.vtt caption sidecar. Regression test with decoy files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:54:24 -04:00
Brandon Thomas ada55eb625 Merge pull request #35 from storytold/pr/gpu-effects
GPU2: standard effects on the GPU compositor (#30)
2026-10-05 16:27:05 -04:00
Brandon ThomasandClaude Opus 5.5 adfd9a66de docs: point agents to the shared test corpora (#36)
Real-file test oracles live in storytold/photocraft-corpus, explained in
craftrules standards/test-corpora.md. Never commit large binary fixtures
here; fetch them pinned and sha256-verified.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:24:17 -04:00
Brandon ThomasandClaude Opus 5.5 e9a0c49d15 Docs: hardware decode on macOS and 31 GPU effects landed (#30)
ROADMAP honest assessment, Where we are lacking and Running now; README status and codec note. 35 crates (platform).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 07:50:45 -04:00
Brandon ThomasandClaude Opus 5.5 4aea06e2ae GPU2: standard effects on the GPU compositor (#30)
A media clip whose enabled standard effects all have a GPU implementation
plans as a GPU layer carrying them (render::plan::LayerFx): parameters are
evaluated on the CPU at the frame's time (render::gpufx::FxOp::eval, shared
with the CPU path so both clamp hostile values identically), the source is
drawn into an Rgba32Float working image at the size the CPU decodes it, the
effects run as WGSL compute passes (gpu::fx, fx.wgsl) with the CPU
reference's math, and the result is placed with Motion, opacity and blend
mode as before. Layers without effects keep the single draw.

GPU effects: Brightness & Contrast, ProcAmp, Tint, Black & White, Color
Balance, Leave Color, Change to Color, Color Pass, Color Replace, Channel
Mix, ASC CDL, Gamma Correction, Levels, Extract, Invert, Posterize, Alpha
Adjust, Gaussian / Camera / Directional Blur (+ legacy aliases), Sharpen,
Unsharp Mask, Crop, Edge Feather, Transform, H/V Flip, Mirror, Offset.
Masked effects, opacity masks, non-finite parameters and minifying
Transform/Crop zooms stay on the CPU. Devices without compute shaders run
the effects on the CPU inside the compositor.

Also fixes a debug-build overflow in Image::transformed for non-finite maps.
Parity tests (exact f32 checks and composited plans), a golden test that
plans effect chains as GPU layers, and the stack3-fx playback benchmark.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 07:50:20 -04:00
Brandon Thomas e9a01dbf38 Merge pull request #33 from storytold/pr/hw-decode-videotoolbox
HW1: platform crate + VideoToolbox H.264/HEVC hardware decode (#30)
2026-10-05 07:26:47 -04:00
Brandon Thomas 2e8df0541a Merge pull request #34 from storytold/pr/honest-assessment
Docs: honest parity assessment (ROADMAP, README, agent docs)
2026-10-05 06:32:58 -04:00
Brandon ThomasandClaude Opus 5.5 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
Brandon Thomas 98792afd1c Merge pull request #32 from storytold/pr/gpu-blend-modes
GPU1: blend modes on the GPU compositor (#30)
2026-10-05 05:28:35 -04:00
Brandon ThomasandClaude Opus 5.5 03f5439d9a HW1: plane views borrow the pixel-buffer lock guard (#30)
PlaneView's slices were tied to the pixel buffer's lifetime, not the lock's, so nothing stopped a later edit from reading plane memory after CVPixelBufferUnlockBaseAddress. They now borrow the Locked guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 04:53:28 -04:00
Brandon ThomasandClaude Opus 5.5 c3ec0e85d3 HW1: platform crate + VideoToolbox H.264/HEVC hardware decode (#30)
- New crates/platform (filmcraft-platform, L5): OS media FFI only. The one
  crate allowed `unsafe` (own [lints] with unsafe_code = "deny",
  #[allow(unsafe_code)] only on the videotoolbox module, SAFETY comment on
  every block, safe Result-returning public API). AGENTS.md §0.3 names the
  exception; docs/adr/0001-platform-ffi.md records why and the rules.
- VideoToolbox decoder for avcC / hvcC, 8/10-bit 4:2:0 and 4:2:2: session from
  the sample entry's parameter sets with hardware required, asynchronous
  decode (2 in flight), biplanar CVPixelBuffer copied to planar Yuv8/Yuv16,
  reorder buffer of the stream's own depth, RASL skipped after a starting CRA.
  Output callback under catch_unwind.
- HybridDecoder: transparent mid-stream fallback to the software decoder
  (replays samples since the last restart point, drops already-returned
  pictures). New codecs::software_video_decoder (built-in factories only) and
  codecs::hw (switch, counters, NalStreamInfo shared with software decoders;
  vui_color / sar_par helpers now used by both).
- Settings ▸ Playback ▸ Hardware decoding: Auto / Off (engine applies it to
  the codecs switch); desktop app, CLI/MCP and bench call register().
- perf.stats decode.framesDecoded and decode.hardware counters.
- Tests: bit-exact parity with the software decoders (H.264 High, HEVC Main
  open GOP, Main 10; resets, reseeks, flushes), forced-failure fallback,
  mutation fuzz of samples and parameter sets, cross-platform fallback with a
  stand-in decoder, setting Off, HEVC 4:2:2 vs ffmpeg.
- Bench --hw auto|off; results in docs/performance.md (4K CPU/frame
  ~86-119 ms -> ~4 ms).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 04:53:28 -04:00
Brandon ThomasandClaude Opus 5.5 6102a3794a GPU1: blend modes on the GPU compositor (#30)
All 27 blend modes now composite on the GPU instead of sending the
whole frame to the CPU.

- render::plan: PlanLayer carries its Blend; plan_frame no longer falls
  back to a CPU frame for non-Normal modes. Media, graphic clips,
  multicam angles and CPU-rendered layer images (standard effects) keep
  their clip's mode; inside a transition the mode is Normal, as in the
  CPU reference. execute_cpu composites with each layer's mode.
- gpu: Normal keeps the single fixed-function pass. Dissolve also uses
  fixed-function "over": the CPU's 64-bit pixel hash is reproduced in
  WGSL on u32 pairs (pattern bit-exact). The other 25 modes copy the
  accumulator region under the layer's quad into a backdrop texture and
  composite in fs_blend with the CPU math (sRGB-encoded straight colour,
  W3C formulas, back to linear, same premultiplied mix and clamping).
- Tests: per-mode GPU-vs-CPU parity (gpu crate: overlapping transformed
  layers, partial opacity/alpha, empty/partial backdrop; golden: every
  mode through plan_frame incl. an effect layer, blended clips in a
  cross dissolve), exact-value edge cases (black/white, Divide by 0,
  Dodge/Burn at 0 and 1, zero alpha) within 4.9e-4, Dissolve pattern
  exact, off-output layers, quad bounds.
- Bench: new stack3-blend playback scenario (V2 Screen, V3 Overlay);
  CPU ms/frame 151-214 -> 86-89, Full 19/173 and 138/54 -> 191/1, 192/0.
- Docs: architecture §5, gpu crate docs, testing, performance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 03:25:16 -04:00
Brandon Thomas 4f06306e45 Merge pull request #31 from storytold/pr/release-0.2.0
Release: FilmCraft v0.2.0
2026-10-05 02:45:48 -04:00
Brandon ThomasandClaude Opus 5.5 798b1591ba Release: FilmCraft v0.2.0
Bump the workspace version 0.1.1 -> 0.2.0 (cargo xtask version set 0.2.0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:45:36 -04:00
Brandon Thomas 694e40546c Merge pull request #27 from storytold/pr/never-crash-clean-crates
Never crash: deny the panicking lints in the already-clean crates and apps
2026-10-04 16:19:27 -04:00
Brandon Thomas 72edec524d Merge pull request #26 from storytold/pr/never-crash-ui
ui-egui: never crash (panels without expect/unreachable)
2026-10-04 16:19:08 -04:00
Brandon Thomas bc57ebee66 Merge pull request #25 from storytold/pr/never-crash-guard
engine: last-resort guard around commands and background jobs
2026-10-04 16:18:49 -04:00
Brandon Thomas 0171afa89d Merge pull request #24 from storytold/pr/never-crash-engine
engine, edit: never crash (commands without expect/unwrap/panic)
2026-10-04 16:18:29 -04:00
Brandon Thomas b081fd20df Merge pull request #21 from storytold/pr/never-crash-export-render
render, gpu, cli: never crash (no expect/unreachable, deny the panicking lints)
2026-10-04 16:18:11 -04:00
Brandon Thomas 1fb70e6841 Merge pull request #22 from storytold/pr/never-crash-h264enc
h264enc: never crash (encode returns Result; no unwrap/expect/unreachable)
2026-10-04 16:17:52 -04:00
Brandon Thomas 9cf892a7b0 Merge pull request #20 from storytold/pr/never-crash-importers
Importers: never crash (interchange, captions, project, text, automation)
2026-10-04 16:17:33 -04:00
Brandon Thomas de57e91c80 Merge pull request #18 from storytold/pr/never-crash-codecs
codecs: never crash (damaged MP4/TS headers; no expect/unreachable)
2026-10-04 16:17:14 -04:00
Brandon Thomas 28563e8030 Merge pull request #16 from storytold/pr/never-crash-codecs-small
Codecs: never crash (ac3, audio-dsp, opus, mpeg2v, prores, dnx)
2026-10-04 16:16:55 -04:00
Brandon Thomas e62fabb1ea Merge pull request #15 from storytold/pr/never-crash-containers
Containers: never crash (mpegts, ogg, mxf, matroska)
2026-10-04 16:16:36 -04:00
Brandon Thomas 08ebb8fd46 Merge pull request #14 from storytold/pr/never-crash-av1
av1: never crash (no expect, deny the panicking lints)
2026-10-04 16:16:18 -04:00
Brandon Thomas a366c41d7d Merge pull request #13 from storytold/pr/never-crash-hevc
hevc: never crash (no unwrap/expect, deny the panicking lints)
2026-10-04 16:15:58 -04:00
Brandon Thomas 4436d4d887 Merge pull request #12 from storytold/pr/never-crash-h264
h264: never crash (no unwrap/expect, deny the panicking lints)
2026-10-04 16:15:39 -04:00
Brandon Thomas 51e5adc627 Merge pull request #11 from storytold/pr/never-crash-vp9
vp9: never crash (no unwrap/expect/panic, deny them)
2026-10-04 16:15:19 -04:00
Brandon Thomas 9fdb8598a3 Merge pull request #17 from storytold/pr/never-crash-time
time: never crash (zero rates and timebases, huge timecodes)
2026-10-04 16:15:00 -04:00
Brandon ThomasandClaude Opus 5.5 b16e0889f3 Never crash: deny the panicking lints in the already-clean crates and apps
aac, bitstream, cfb, color, format, frame, geom, isobmff, media, scopes, speech, the desktop
app and the web app have no production unwrap/expect/panic/unreachable (the web app's two
`expect`s — frame-cache eviction and chunked file reads — become `let … else`); each now
carries `#![deny(clippy::unwrap_used, …)]` outside tests so it stays that way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 16:08:23 -04:00
Brandon ThomasandClaude Opus 5.5 d40a371724 ui-egui: never crash — panels without expect/unreachable
- Timeline, monitors, Reference, Effect Controls and Export mode: a missing active sequence,
  track or clip (stale ids from a damaged project, or a clip deleted mid-drag) shows the empty
  state or skips the row instead of `expect`.
- Frame queue pops without `expect`; Graphics text editing, clip dialogs, Lumetri panel and
  Project rename without `expect`/`unreachable!`.
- The deliberate fault-injection panics (UI pass and frame jobs, used to test the guard) go
  through one documented `crash::injected_fault`.
- `#![deny(clippy::unwrap_used, …)]` outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 16:07:12 -04:00
Brandon ThomasandClaude Opus 5.5 3a392ae8da Clippy 1.99: fix new lints so cargo xtask ci is green again
Rust/clippy 1.99 added `chunks_exact_to_as_chunks` and tightened
`needless_late_init` and `drain_collect`, failing `clippy -D warnings`
on main.

- `chunks_exact(N)` / `chunks_exact_mut(N)` with a constant N become
  `as_chunks::<N>()` / `as_chunks_mut::<N>()` (fixed-size arrays, so
  several byte reads drop their per-element indexing). relink's
  fingerprint uses the `(chunks, remainder)` split; its hash output is
  unchanged.
- Late-initialised `let`s in ac3 bit allocation and opus CELT
  bands/rate become `let x = if/match …`.
- `dpb.drain(..).collect()` becomes `std::mem::take(&mut dpb)`.

No behaviour change; `cargo xtask ci` passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:55:23 -04:00
Brandon ThomasandClaude Opus 5.5 75ed688e09 engine: last-resort guard around commands and background jobs
The UI already turns a panic in the UI pass into an error window (ui-egui `crash` hook +
`catch_unwind` around the frame), and frame workers survive panics. Two gaps remained:
- commands run headless (CLI, MCP over stdio) or over the control channel had no guard: a panic
  in one command ended the process and the unsaved session;
- background jobs (export, render previews/audio, proxies, Project Manager, mask tracking,
  scene detection) ran their body unguarded: a panic killed the thread and left the job
  "running" forever.

`Session::execute` now runs the command under `catch_unwind` and returns
`EngineError::Other("internal error in `<id>` …")`; every job body is wrapped by
`export_tools::guard_job`, which marks the job finished with an error. Regression test
`export_tools::guard_tests::panicking_job_is_reported_as_failed`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:23:18 -04:00
Brandon ThomasandClaude Opus 5.5 31ae72f015 engine, edit: never crash — commands without expect/unwrap/panic
- Commands: place/nest/sequence settings/import/multicam/subclip/audio channels/extract audio,
  graphics reset duration, dynamic trim and Make Subsequence return errors instead of `expect`
  when an item, sequence or track is missing (stale ids from a damaged project or a racing
  agent); File ▸ Import detects caption/interchange documents without a second `expect`.
- `find_spec` (graphics) returns `UnknownCommand` instead of `expect`.
- Built-in effect lookups (presets, demo project) fall back to a bare instance instead of
  `panic!`/`expect`; the demo project falls back to an empty one.
- Multicam record base, shortcut presets and field parsing without `expect`.
- edit: caption merge/move and transcript caption blocks without `expect`.
- Both crates deny unwrap/expect/panic/unimplemented/todo/unreachable outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:02:01 -04:00
Brandon ThomasandClaude Opus 5.5 b05833ef67 h264enc: never crash — no unwrap/expect/unreachable, encode returns Result
- `Encoder::encode` returns `Result` (it was `try_encode(..).expect(..)`, panicking on short
  planes); tests and the example unwrap explicitly.
- P/B macroblock analysis without a reference codes intra instead of `expect`/`unwrap`;
  spatial direct without a list-1 picture predicts zero motion.
- Rate control prior, DPB sliding window, DTS queue, reconstruction buffers, CAVLC writer,
  SAD rows and forward 8x8 transforms without `unwrap`/`expect`; skip-macroblock arms in
  `write_mb` debug-assert instead of `unreachable!`.
- `#![deny(clippy::unwrap_used, …)]` outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:14:06 -04:00
Brandon ThomasandClaude Opus 5.5 5628fc55bc render, gpu, cli: never crash — no expect/unreachable, deny the panicking lints
- gpu: `composite` returns its output view by value (a cheap handle clone) taken from the
  render-target setup, so the accumulation/output views no longer `expect` their slots.
- render: lightning bolt subdivision, tracker pyramid and the Lumetri preset instance without
  `expect`.
- cli: dotted parameter insertion without `expect`; the render/mcp backend arms report a usage
  error instead of `unreachable!`.
- Each crate denies unwrap/expect/panic/unimplemented/todo/unreachable outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:04:42 -04:00
Brandon ThomasandClaude Opus 5.5 f0e6b12819 Importers: never crash — interchange, captions, project, text, automation
- interchange: exporters (FCP7 XML, FCPXML, OTIO) skip a missing nested sequence or media with a
  report warning instead of `expect`/`unwrap`; EDL event grouping and M2 lines, the XML writer
  and the AAF/OMF dispatch arms have no panics. The WAVE header saturates instead of overflowing
  for >4 GiB descriptors (found by fuzzing: a mutated EDL exported to OMF), regression test
  `wav::tests::huge_wav_header_saturates`.
- captions: EBU STL frame rate read with `get()`; MCC rows, TTML text and the STL dispatch arm
  without `expect`/`unreachable!`.
- project: bin moves, 5.1 pan lanes and text/shape layer constructors without `expect`.
- text: font resolution and line layout without `expect` (the bundled-face lookup keeps one
  documented `#[allow(clippy::expect_used)]`: the faces are compiled in).
- automation: the MCP bridge returns an error instead of `expect` when not connected.
- Each crate denies unwrap/expect/panic/unimplemented/todo/unreachable outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:38:13 -04:00
Brandon ThomasandClaude Opus 5.5 6aa3c37342 codecs: never crash — damaged MP4/TS headers, no expect/unreachable
Mutation fuzzing (mutated ffmpeg-made files through `open_bytes`) found three import crashes:
- an MP4/MOV track with an empty sample-description list: `entries[0]` index out of bounds
  (video and audio paths);
- an MP4/MOV track with timescale 0: division by zero in the duration;
- an MPEG-TS stream whose PTS span gave a ~0 fps rate: division by zero in `tick_of`.

Tracks now need samples, a sample description and a timescale to count as playable; frame rates
that round to zero fall back to the default; the TS/PS rate choice is a small function that never
returns a zero rate. Regression tests: `mp4::tests::damaged_track_headers_never_panic`,
`mpeg::tests::damaged_timestamps_never_give_a_zero_rate`.

Also: GOP cache, MKV/MP4/Ogg/MPEG audio paths, MXF and VP9 conversion without
`expect`/`unreachable!`; the crate denies the panicking lints outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:20:30 -04:00
Brandon ThomasandClaude Opus 5.5 0f678aa105 h264: dequantisation wraps instead of overflowing on huge levels
Mutation fuzzing found a corrupt H.264 transport stream whose chroma DC levels overflowed
`chroma_dc_dequant_420` ("attempt to multiply with overflow" in debug builds, a decoder worker
panic). The 4x4 / 8x8 / DC dequantisers now use wrapping arithmetic, which is what release
builds always computed. Regression test `huge_levels_dequantise_without_overflow_panics`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:56:22 -04:00
Brandon ThomasandClaude Opus 5.5 953f0aca8b time: never crash — total conversions for zero rates, saturating timecodes
Mutation fuzzing found two crashes on import: an MP4 track with timescale 0 panicked in
`Tick::from_rational` (division by zero) and an MPEG-TS stream whose timestamps gave a zero
frame rate panicked in `FrameRate::tick_of`. A FrameRate with a zero num/den can also come from a
project file (it's serde).

- `Tick::{from_units, from_rational, to_rational_floor, mul_ratio}` return 0 for a zero
  denominator instead of dividing by zero.
- `FrameRate::sane()`: an invalid (zero / negative) rate counts as the default rate in
  `frame_duration`, `frame_at`, `tick_of` and `timecode_base`.
- `Tick::clamp` no longer panics when the bounds cross.
- Timecode parsing and drop-frame conversion use saturating math (huge typed or agent-sent
  timecodes overflowed).
- Regression test `hostile_rates_and_timecodes_never_panic`.
- `#![deny(clippy::unwrap_used, …)]` outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:51:31 -04:00
Brandon ThomasandClaude Opus 5.5 46b286edaf Codecs: never crash — ac3, audio-dsp, opus, mpeg2v, prores, dnx
- ac3: LFE channel taken without `expect`.
- audio-dsp: the channel matrix match is exhaustive (no `unreachable!`); an unknown internal
  parameter reads as 0 (debug-asserted) instead of `panic!`.
- opus: dual-stereo bands and SILK frames take the optional buffer / range decoder with `if let`.
- mpeg2v: 64-bit peek through `get()` + `first_chunk`; the literal VLC table parser no longer
  panics (debug-asserted).
- prores, dnx: IDCT rows through `as_chunks`; ProRes block store and alpha slices without
  unwrap/expect; DNx header timecode through `get()`.
- dnx: `Encoder::new` returns `Result` instead of panicking on a raster the profile can't code
  (e.g. 0x0), with a regression test; missing CID table is an error.
- Each crate denies unwrap/expect/panic/unimplemented/todo/unreachable outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:43:19 -04:00
Brandon ThomasandClaude Opus 5.5 fa40a3c030 Containers: never crash — mpegts, ogg, mxf, matroska without expect/unreachable
- mpegts: a PES unit is built before it is stored (no `as_mut().expect`); stream maps are
  drained with `let … else` instead of `remove().expect`.
- ogg: completing a packet and reading the page granule no longer expect.
- mxf writer: the picture descriptor uses `let … else` on the picture configuration.
- matroska: the `Lacing::None` arm returns the single frame instead of `unreachable!`.
- Each crate denies unwrap/expect/panic/unimplemented/todo/unreachable outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:37:58 -04:00
Brandon ThomasandClaude Opus 5.5 7190fb9009 av1: never crash — no expect in the decoder, deny the panicking lints
- CDEF and inter-prediction filter rows: fixed-size views through `get()` + `first_chunk(_mut)`
  instead of slice + `try_into().expect()`.
- A missing reference frame predicts from the current frame instead of `expect`.
- Frame slots, output and in-flight queues, scalable-layer selection: no `expect`.
- `#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented,
  clippy::todo, clippy::unreachable)]` outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:35:48 -04:00
Brandon ThomasandClaude Opus 5.5 970653628f hevc: never crash — no expect/unwrap in the decoder, deny them
- SAO runs and 8-tap filter rows: fixed-size lanes through `get()` + `first_chunk(_mut)`
  instead of slice + `try_into().expect()`.
- Decoder: a slice segment with no started picture is an error; the output queue pop no longer
  expects.
- Weighted prediction without a parsed table falls back to the default (unweighted) prediction
  instead of `expect`.
- Tile boundaries accumulate without `last().unwrap()`.
- `#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented,
  clippy::todo, clippy::unreachable)]` outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:31:02 -04:00
Brandon ThomasandClaude Opus 5.5 a87ba26844 h264: never crash — no unwrap/expect in the decoder, deny them
- I_PCM samples, deblocking lines, transform rows, block copies: fixed-size views through
  `get()` + `first_chunk(_mut)` instead of slice + `try_into().unwrap()`. A truncated I_PCM
  macroblock is `Error::Invalid` from the read itself.
- Decoder: a missing parameter set or a slice without a started picture is an error, not an
  `expect`; the output queue pop no longer expects.
- CAVLC table builder parses its literal codes without `from_str_radix().unwrap()`.
- `#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented,
  clippy::todo, clippy::unreachable)]` outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:30:55 -04:00
Brandon ThomasandClaude Opus 5.5 d499b0c20e vp9: never crash — no unwrap/expect/panic in the decoder, deny them
- Transforms, 8-tap filters, probability tables: fixed-size views through `first_chunk(_mut)`
  instead of slice + `try_into().unwrap()/expect()` (same code on the happy path).
- Tile sizes read with `get()`; a short tile header is `Error::Invalid`.
- Buffer pool tolerates a poisoned lock.
- A failed post-processing thread spawn no longer panics: the job reaches the thread over a
  channel, so it is still in hand and runs inline.
- Without the `threads` feature an unpublished reference band reads as empty instead of panicking.
- `#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented,
  clippy::todo, clippy::unreachable)]` outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:15:05 -04:00
Brandon Thomas 7b96f22684 Merge pull request #10 from storytold/pr/never-crash-steering
Rules: never crash (AGENTS.md §0, clippy.toml, forbid unsafe)
2026-10-04 11:14:08 -04:00
Brandon ThomasandClaude Opus 5.5 e10449de2c Rules: never crash — AGENTS.md §0, clippy.toml for tests, forbid unsafe
- AGENTS.md: a new §0 "Never crash" at the top of the rules (why, outranks feature work, the bans,
  Result/? patterns, hostile input numbers, bounded recursion, lock poisoning, the last-resort
  guard, a regression test per crash fix), linking craftrules/standards/never-crash.md. §3.1 now
  points to it.
- CLAUDE.md non-negotiables, CONTRIBUTING.md and docs/contributing.md: short Never crash notes
  pointing to AGENTS.md §0.
- clippy.toml: allow unwrap/expect/panic/indexing in tests, so crates can deny the panicking lints
  in production code without touching their tests.
- [workspace.lints.rust] unsafe_code = "forbid" (no unsafe or allow(unsafe_code) in the workspace).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:06:55 -04:00
Brandon ThomasandClaude Opus 5.5 d0048b0256 Merge PR #8 from storybored379
Timeline: Slide moves linked audio with the video

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:50:15 -04:00
Brandon ThomasandClaude Opus 5.5 ff62f8af27 Merge PR #7 from storybored379
Timeline: transitions stay on their cuts after ripple trim, roll, slide and ripple speed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:50:14 -04:00
Brandon ThomasandClaude Opus 5.5 3c5e913879 Merge PR #6 from storybored379
Export: start/end times without a range mean a custom range

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:50:13 -04:00
Brandon ThomasandClaude Opus 5.5 7dd3ee4d77 Merge PR #5 from storybored379
Media: AIFF files import (enable symphonia's aiff reader)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:50:12 -04:00
Brandon Thomas c1224d0ede Merge pull request #9 from storytold/pr/license-standard
Licensing: standard LICENSE files, NOTICE, ArtCraft brand terms
2026-10-04 10:09:00 -04:00
Brandon ThomasandClaude Opus 5.5 5d7cc4ef66 Licensing: standard LICENSE files, NOTICE, ArtCraft brand terms
- LICENSE-MIT / LICENSE-APACHE from the Crafting Apps standard (copyright "ArtCraft Team and the
  FilmCraft contributors"; Apache text in the canonical apache.org layout)
- NOTICE (new): license, brand carve-out, third-party list from ATTRIBUTION.md
- docs/brand/LICENSE-brand.txt (new): ArtCraft brand terms; brand sidecars, ATTRIBUTION.md and
  AGENTS.md §1.8 point to it (owner "ArtCraft Team"; forks and modified versions remove the marks)
- README: "## License and credits" section per the standard (absorbs "Credits and clean room"),
  badge anchor #license-and-credits, alt text "License: MIT OR Apache-2.0"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:05:12 -04:00
Brandon ThomasandClaude Opus 5.5 f3f603a934 Rules: never panic — Result<T, E> everywhere, no unchecked indexing / zero division on untrusted input, background threads catch panics, fuzz parsers (AGENTS.md §3.1, CLAUDE.md, contributing)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 08:13:08 -04:00
Brandon Thomas 0fb2ba99ca Merge PR #4 from storybored379
Control: screenshots and ui.elements wait for the UI to settle
2026-10-04 02:51:55 -04:00
Brandon Thomas 3b33cb36c3 Merge PR #3 from storybored379
Timeline: fit a new empty sequence again when its first clip arrives
2026-10-04 02:51:24 -04:00
Brandon Thomas 8d7503ceae Merge PR #2 from storybored379
Media: apply the container's display rotation (portrait phone video)
2026-10-04 02:51:19 -04:00
Brandon Thomas 52b51f9784 Merge PR #1 from storybored379
Sequences: refuse a zero or negative frame rate instead of panicking
2026-10-04 02:51:16 -04:00
Brandon ThomasandClaude Opus 5.5 919d54fffc Test: ui.elements answers within a few frames once the timeline is at rest, after zooming and scrolling
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 02:50:17 -04:00
Brandon ThomasandClaude Opus 5.5 969aae8fad Test: later clips keep the user's zoom once the sequence has content (only the first clip into an empty sequence re-fits)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 02:50:17 -04:00
Brandon ThomasandClaude Opus 5.5 e2afbed6c2 Tests for display rotation: Matroska roll → quarter turns (incl. flips, odd and malformed angles), MP4 mirrored/scaled matrices
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 02:50:16 -04:00