From d3c9d265881a0b2c810a1b3c9826f16a5162885e Mon Sep 17 00:00:00 2001 From: Baptiste Parmantier Date: Tue, 22 Sep 2026 11:12:05 +0200 Subject: [PATCH] docs(readme): rewrite the architecture section to match the tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every structural claim in this block is false. Verified: there is no root `src/` (`ls src` → No such file or directory, the tree is `crates/*/src`); there is no `layout/` directory (`ls crates/rustmotion-core/src/layout` → No such file or directory — taffy replaced it); the `Widget` trait does not exist (`grep -rn "Widget" crates/` returns exactly two hits, both comments, one of which is `crates/rustmotion-core/src/traits/painter.rs:1`: "`Painter` trait — replaces the old `Widget::render`"); the pipeline is box_tree → layout_pass → paint_pass, not "measure → layout → paint"; and the `Component` enum in `crates/rustmotion-components/src/lib.rs:355-418` has 60 variants, not 51 (CLAUDE.md's "Composants disponibles (60)" is the correct count). The "CLI Reference" section (README.md:88-104) documents only `rustmotion render` and 9 of its flags, while `crates/rustmotion/src/cli/mod.rs` declares 11 subcommands (Render, Concat, Still, Captions, Validate, Batch, Schema, Info, Skills, Completions, plus nested) — `validate`, the gate CLAUDE.md makes mandatory before delivering any scenario, is entirely absent, as are `--watch`, `--frames`, `--var`, `--props`, `--strict-anim`. The branch HEAD (`4d54504 fix(crates): ship the README with every published crate`) adds `readme = "../../README.md"` to all four crate manifests, so this document becomes the front page of rustmotion, rustmotion-core, rustmotion-components and rustmotion-html on crates.io and docs.rs. A contributor following it looks for `src/components/` and implements `Widget`. Refs #220 --- README.md | 158 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 128 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index f9b34b6a..15f3dd20 100644 --- a/README.md +++ b/README.md @@ -87,19 +87,117 @@ Once installed, Claude Code automatically loads the skills when you work in that ## CLI Reference +### `rustmotion validate` + +Schema + geometry checks, with no render. This is the gate every generated scenario is expected to pass before use. + +| Flag | Description | Default | +|---|---|---| +| `-f, --file ` | Path to the JSON scenario file | (required) | +| `--report ` | Write a machine-readable JSON report of all violations | | +| `--fix` | Auto-fix safe violations in place (`auto_scroll: true`, drop `white-space` back to wrap, `text-autofit: true`) — refuses templated scenarios (`include`/`for-each`/`use`) | `false` | +| `--strict-anim` | Sample animated frames and reapply renderer transforms to detect per-frame viewport overflow (slower) | `false` | +| `--lenient` | Treat geometry violations as warnings instead of errors | `false` | +| `--props ` | Load variable overrides from a JSON object file | | +| `--var ` | Set a single variable override (repeatable); `--var` wins over `--props` | | + ### `rustmotion render` | Flag | Description | Default | |---|---|---| -| `input` | Path to the JSON scenario file | (required) | +| `-f, --file ` | Path to the JSON scenario file (or `--json ` for inline input) | (required) | | `-o, --output` | Output file path | `output.mp4` | | `--frame ` | Render a single frame to PNG (0-indexed) | | +| `--frames ` | Render only frames `START..=END` as a standalone segment with its own windowed audio slice, for joining later with `rustmotion concat`. Mutually exclusive with `--frame`/`--watch`; only mp4/webm/mov are implemented for a range | | | `--codec ` | Video codec: `h264`, `h265`, `vp9`, `prores` | `h264` | | `--crf <0-51>` | Constant Rate Factor (lower = better quality) | `23` | | `--format ` | Output format: `mp4`, `webm`, `mov`, `gif`, `png-seq` | auto from extension | | `--transparent` | Transparent background (PNG sequence, WebM, ProRes 4444) | `false` | +| `--hardware-acceleration` | Probe `ffmpeg -encoders` and use this machine's hardware encoder (VideoToolbox/NVENC/QSV/AMF) when available; explicit message and software fallback otherwise | `false` | +| `-w, --watch` | Watch the input file and re-render on change (not compatible with `--props`/`--var`) | `false` | +| `--no-validate` | Skip the implicit validate pass (schema + geometry + variables) before rendering | `false` | +| `--lenient` | Treat geometry violations as warnings during the implicit validate pass | `false` | +| `--strict-anim` | Sample animated frames for per-frame viewport overflow during the implicit validate pass | `false` | +| `--props ` | Load variable overrides from a JSON object file | | +| `--var ` | Set a single variable override (repeatable); `--var` wins over `--props` | | | `--output-format json` | Machine-readable JSON output for CI pipelines | | | `-q, --quiet` | Suppress all output except errors | | +| `--threads ` | Number of parallel rendering threads (global flag) | all cores | + +### `rustmotion concat` + +Joins segment files — e.g. several `render --frames a-b` outputs from the same scenario — via ffmpeg's concat demuxer (`-c copy`, no re-encoding). Requires ffmpeg on PATH. + +```bash +rustmotion concat seg1.mp4 seg2.mp4 -o out.mp4 +``` + +### `rustmotion still` + +Exports a single frame as a still image (PNG/JPEG/WebP). + +| Flag | Description | Default | +|---|---|---| +| `-f, --file ` | Path to the JSON scenario file | (required) | +| `-o, --output` | Output file path | `still.png` | +| `--time ` | Time to capture | `0.0` | +| `--format ` | Image format: `png`, `jpeg`, `webp` | from extension | +| `--quality <1-100>` | JPEG quality | `90` | +| `--props` / `--var` | Variable overrides, same as `render` | | + +### `rustmotion captions` + +Generates word-level caption timings from audio (via a local `whisper.cpp` binary) or by importing subtitles. + +```bash +rustmotion captions voice.mp3 -o words.json +rustmotion captions --from-srt subs.srt -o words.json +``` + +| Flag | Description | Default | +|---|---|---| +| `audio` | Audio file to transcribe (mutually exclusive with `--from-srt`/`--from-vtt`) | | +| `-o, --output` | Output JSON file (stdout if omitted) | | +| `--model` | Whisper model name (`tiny`, `base`, `small`, `medium`, `large-v3`) or a path to a `.bin` | `base` | +| `--lang` | Spoken language code (auto-detected if omitted) | | +| `--from-srt` / `--from-vtt` | Import cues from a subtitle file instead of transcribing | | + +### `rustmotion batch` + +Renders one video per line of a JSONL data file, substituting each line's fields as variable overrides. + +| Flag | Description | Default | +|---|---|---| +| `-f, --file ` | Path to the scenario template (JSON or HTML dialect) | (required) | +| `--data ` | JSONL file, one object of variable overrides per line | (required) | +| `--output-dir ` | Directory to write output files into | (required) | +| `--name-template` | Output filename template (`{field}`, `{index}`) | `"{index}.mp4"` | +| `--codec` / `--crf` / `--format` / `--transparent` | Same as `render` | | +| `--jobs ` | Videos to render in parallel (the render itself already uses all cores via rayon) | `1` | + +### `rustmotion schema` + +Prints the JSON Schema for scenario files (editor autocompletion, LLM prompts). + +```bash +rustmotion schema -o schema.json +``` + +### `rustmotion info` + +Shows information about a scenario (duration, scene count, dimensions, ...). + +```bash +rustmotion info scenario.json +``` + +### `rustmotion skills` + +Manages the built-in Claude Code skills — `install [--global]`, `uninstall [--global]`, `list`, `show `. See [Claude Code Skills](#claude-code-skills). + +### `rustmotion completions` + +Generates or installs shell completions — `install`, `uninstall`, `generate `. See [Shell Completions](#shell-completions). --- @@ -2025,42 +2123,42 @@ Transparency is supported with `--transparent` for PNG sequences, WebM (VP9), an - **JSON Schema:** schemars (auto-generated from Rust types) - **Parallelism:** rayon (multi-threaded frame rendering) -## Architecture +rustmotion ships 60 components, each implementing the `Painter` trait, through a CSS-inspired **box_tree → layout_pass → paint_pass** pipeline: -rustmotion uses a Flutter-inspired **measure → layout → paint** pipeline built on Skia: +1. **box_tree** — builds a tree of `BoxNode { css: CssStyle, children, intrinsic }` from the resolved JSON components +2. **layout_pass** — runs [taffy](https://github.com/DioxusLabs/taffy) to compute each node's `BoxLayout { x, y, width, height }`; leaves that carry an `IntrinsicMeasure` (text, images, codeblocks, ...) are measured through a `measure_fn` +3. **paint_pass** — walks the tree top-down, applies transform/opacity, paints decorations (background, border, shadow), and delegates content painting to the component's `Painter` implementation -``` -src/ -├── components/ # 51 components (each implements Widget trait) -│ ├── chart/ # Chart sub-modules (bar, line, pie, radar, etc.) -│ └── *.rs # One file per component -├── engine/ -│ ├── render/ # Render pipeline (component, scene, background, transforms) -│ ├── codeblock/ # Codeblock rendering (highlight, chrome, reveal, diff) -│ ├── animator.rs # Animation resolver, easing, spring solver -│ └── renderer.rs # Skia drawing primitives -├── schema/ # Data models -│ ├── scenario.rs # Scenario, View, Scene, VideoConfig -│ ├── style.rs # LayerStyle, FontWeight, layout types -│ ├── background.rs # Animated backgrounds -│ ├── animation.rs # EasingType, presets -│ └── video.rs # AnimationEffect, shapes, fills -├── layout/ # Flex/grid layout engines -├── traits/ # Widget, Styled, Animatable, Timed, Container -└── macros.rs # impl_traits! macro +```rust +pub trait Painter { + fn paint_content(&self, canvas: &Canvas, layout: &BoxLayout, props: &AnimatedProperties, ctx: &PaintCtx); + fn intrinsic_size(&self, available: AvailableSize, ctx: &MeasureCtx) -> Option<(f32, f32)> { None } +} ``` -Every component implements the `Widget` trait: +`PaintCtx` carries `time`, `scene_duration`, `fps`, `frame_index`, `video_width`, `video_height`, `stagger_offset`. -```rust -trait Widget { - fn paint(&self, canvas: &Canvas, ctx: &PaintContext) -> Result<()>; - fn measure(&self, constraints: &Constraints) -> (f32, f32); - fn layout(&self, constraints: &Constraints) -> LayoutNode; -} +### Workspace layout + +``` +crates/ +├── rustmotion-core/src/ +│ ├── css/ # CssStyle, units, cascade, taffy bridge, animation resolution +│ ├── engine/ # box_tree, layout_pass, paint_pass, animator, transitions, Skia primitives +│ ├── schema/ # Scenario, Scene, VideoConfig, style, background, animation, codeblock models +│ └── traits/ # Painter, Animatable, Timed, Styled +├── rustmotion-components/src/ +│ ├── lib.rs # `Component` enum (60 variants) + dispatch (as_painter, as_animatable, ...) +│ ├── box_builder.rs # JSON components → BuiltScene (box tree + stagger delays) +│ ├── chart/ # bar/line/pie/radar/scatter/radial/funnel/waterfall sub-modules +│ └── *.rs # one file per component (Painter implementation) +└── rustmotion/src/ + ├── cli/ # the `rustmotion` binary (clap subcommands) + ├── encode/ # video/audio encoders and muxing + └── loader.rs # JSON/HTML → ResolvedScenario ``` -`PaintContext` provides timing, layout dimensions, parent info, and resolved animated properties in a single struct. +The `rustmotion` crate is where the binary lives — a crate with only a `[lib]` target installs nothing executable via `cargo install`. ## License