Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- name: Install system dependencies
# xkbcommon-x11/wayland/xcb-cursor: required to LINK rustmotion-studio (gpui).
# xkbcommon-x11/wayland/xcb-cursor: required to LINK the studio (gpui).
# clippy passes without them — it never links — so the failure only shows in tests.
# asound: required by cpal, which rodio pulls in for preview audio
run: sudo apt-get update && sudo apt-get install -y libfontconfig1-dev libfreetype6-dev libxkbcommon-x11-dev libwayland-dev libxcb-cursor-dev libasound2-dev
Expand All @@ -33,21 +33,28 @@ jobs:
components: clippy
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
- name: Clippy
run: cargo clippy --workspace --all-targets -- -D warnings
# `--features rustmotion/studio` n'est pas décoratif : le studio est
# derrière un feature non-défaut depuis qu'il est un module de
# `rustmotion`, et `--workspace` seul ne compilerait plus une seule de
# ses 11 600 lignes. Sans ce drapeau, clippy reste vert sur du code que
# personne ne vérifie plus.
run: cargo clippy --workspace --all-targets --features rustmotion/studio -- -D warnings

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- name: Install system dependencies
# xkbcommon-x11/wayland/xcb-cursor: required to LINK rustmotion-studio (gpui).
# xkbcommon-x11/wayland/xcb-cursor: required to LINK the studio (gpui).
# clippy passes without them — it never links — so the failure only shows in tests.
# asound: required by cpal, which rodio pulls in for preview audio
run: sudo apt-get update && sudo apt-get install -y libfontconfig1-dev libfreetype6-dev libxkbcommon-x11-dev libwayland-dev libxcb-cursor-dev libasound2-dev
- uses: dtolnay/rust-toolchain@6bed0761d98439e5a578e2877258200ad565ba87 # stable channel, 2026-09-22
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
- name: Run tests
run: cargo test --workspace
# Même raison qu'au job clippy : sans le feature, les tests du studio
# (`tests/studio_audit_ws_g.rs`) ne sont même pas compilés.
run: cargo test --workspace --features rustmotion/studio

audit:
runs-on: ubuntu-latest
Expand All @@ -73,7 +80,7 @@ jobs:
# Via rayon-core <- exr <- image, reaches rustmotion-core/-components. Fix: >=0.9.20.
# RUSTSEC-2026-0195, RUSTSEC-2026-0194 — quick-xml 0.38.4 / 0.39.4, DoS + quadratic runtime.
# 0.38.4 via syntect reaches the published crates; 0.39.4 via dioxus-desktop/rfd is
# rustmotion-studio-only (Linux/Wayland file dialogs). Fix: >=0.41.0.
# studio-only (Linux/Wayland file dialogs). Fix: >=0.41.0.
# RUSTSEC-2026-0285 — rustls 0.23.37, TLS 1.3 handshake level-boundary bug.
# Via ureq, used by rustmotion/rustmotion-core for Google Fonts + Iconify fetches. Fix: >=0.23.45.
# RUSTSEC-2026-0104, RUSTSEC-2026-0098, RUSTSEC-2026-0099, RUSTSEC-2026-0049 — rustls-webpki
Expand Down
12 changes: 8 additions & 4 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ jobs:

- name: Install system dependencies
# Doit rester identique à ci.yaml : l'étape « Run tests » ci-dessous lance
# `cargo test --workspace`, qui compile ET LIE rustmotion-studio. Sans ces
# paquets l'édition de liens échoue et la release s'arrête avant toute
# publication.
# `cargo test --workspace --features rustmotion/studio`, qui compile ET
# LIE le studio. Sans ces paquets l'édition de liens échoue et la
# release s'arrête avant toute publication.
# xkbcommon-x11/wayland/xcb-cursor : exigés par gpui, à l'édition de liens
# seulement. clippy passe sans eux puisqu'il ne lie pas — la panne
# n'apparaît donc qu'au job de test. C'est ce qui a cassé le premier CI du
Expand Down Expand Up @@ -65,6 +65,10 @@ jobs:
# pouvait donc jamais reussir, et la release 0.6.0 s'est arretee la — apres
# avoir publie rustmotion-core, definitivement.
#
# rustmotion-studio porte `publish = false` : cargo la saute tout seul.
# Le studio n'est plus un paquet : c'est un module de `rustmotion` derrière
# le feature `studio`, donc il part avec elle et il n'y a plus rien à
# sauter. Ses dépendances (gpui-kit, gpui-component, rfd, rodio, palette)
# sont optionnelles mais doivent rester résolvables depuis le registre,
# sans quoi la publication de `rustmotion` échoue.
- name: Publish all crates
run: cargo publish --workspace --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
27 changes: 7 additions & 20 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ members = [
"crates/rustmotion-core",
"crates/rustmotion-components",
"crates/rustmotion",
"crates/rustmotion-studio",
"crates/rustmotion-html",
]

# Les cinq crates avancent ensemble. La version se change ici, et nulle part
# Les quatre crates avancent ensemble. La version se change ici, et nulle part
# ailleurs : elle était auparavant répétée dix fois — une par manifeste, plus une
# par dépendance interne — et une seule oubliée fait échouer la publication après
# que les précédentes soient parties, ce qui ne se rattrape pas.
Expand All @@ -34,6 +33,7 @@ debug-assertions = false
overflow-checks = false

# The image crate's encoders are generic and monomorphize into the calling
# crate, so the studio itself must be optimized for fast preview encoding.
[profile.dev.package.rustmotion-studio]
# crate, so the studio itself must be optimized for fast preview encoding. The
# studio is a module of `rustmotion` now, so this covers the whole crate.
[profile.dev.package.rustmotion]
opt-level = 3
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ MIT-licensed: no licence key, no telemetry, no per-render billing. See [Non-goal
cargo install rustmotion
```

From this repository, without waiting for a release:

```bash
cargo install --git https://github.com/LeadcodeDev/rustmotion
```

Either command installs the `rustmotion` CLI. Add the `studio` feature to get
the live preview window (`rustmotion-studio`) alongside it:

```bash
cargo install --git https://github.com/LeadcodeDev/rustmotion --features studio
```

The studio is not in the default build because it pulls gpui and a native GUI
toolchain, which do not build everywhere the CLI builds — a headless server
being the obvious case.

**Requirements:** Rust toolchain + C++ compiler (for openh264). **Recommended:** `ffmpeg` CLI for 10-bit H.264 and H.265/VP9/ProRes/WebM/GIF output.

### Shell Completions
Expand Down Expand Up @@ -2155,11 +2172,12 @@ crates/
│ └── *.rs # one file per component (Painter implementation)
└── rustmotion/src/
├── cli/ # the `rustmotion` binary (clap subcommands)
├── studio/ # the `rustmotion-studio` binary (feature `studio`)
├── encode/ # video/audio encoders and muxing
└── loader.rs # JSON/HTML → ResolvedScenario
```

The `rustmotion` crate is where the binary lives — a crate with only a `[lib]` target installs nothing executable via `cargo install`.
The `rustmotion` crate is where both binaries live — a crate with only a `[lib]` target installs nothing executable via `cargo install`, and `cargo install --git <url>` refuses a repository in which more than one package declares a `[[bin]]`. That second constraint is why the studio is a module of this crate (`src/studio/`, behind the `studio` feature) rather than a package of its own.

## License

Expand Down
2 changes: 1 addition & 1 deletion crates/rustmotion-components/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
// never lints here regardless of this attribute. This single crate-root
// allow silences only that internal noise; it does not extend to any other
// crate, so a hand-written construction in `rustmotion-html`,
// `rustmotion-studio`, or this crate's own `tests/` integration suite (each
// the studio, or this crate's own `tests/` integration suite (each
// a separate compilation unit) still warns. Verified empirically before
// relying on it: see the phase-B report for issue #333.
#![allow(deprecated)]
Expand Down
2 changes: 1 addition & 1 deletion crates/rustmotion-core/tests/audit_ws_k.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ const KNOWN_INERT_FIELDS: &[(&str, &str)] = &[
"target",
"Not one of workstream K's 9 named findings — surfaced by this guard test itself, with \
a caveat this test can't resolve on its own: Annotation.target is written by \
rustmotion-studio (crates/rustmotion-studio/src/editor/annotations.rs:94) as raw JSON \
the studio (crates/rustmotion/src/studio/editor/annotations.rs:94) as raw JSON \
(a `\"target\": {...}` object literal, not a `.target` field access — this grep-based \
check only matches Rust member access), so it may be consumed by the `apply-annotations` \
Claude Code skill reading the scenario file's raw JSON rather than by any Rust code path. \
Expand Down
28 changes: 0 additions & 28 deletions crates/rustmotion-studio/Cargo.toml

This file was deleted.

34 changes: 26 additions & 8 deletions crates/rustmotion/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,20 +201,38 @@ crates/
└── rustmotion/src/
├── cli/ # Le binaire `rustmotion` (clap + sous-commandes)
│ └── commands/ # validate, render, schema, info
├── studio/ # Le binaire `rustmotion-studio` (feature `studio`)
├── encode/ # Encodeurs vidéo/audio, mux
└── loader.rs # Chargement JSON/HTML → ResolvedScenario
```

> Le binaire vit dans la crate publiée `rustmotion` : une crate qui n'a qu'une
> `[lib]` n'installe rien d'exécutable via `cargo install`. Il n'y a pas de
> sous-commande `studio` — `rustmotion-studio` dépend de `rustmotion`, donc la
> dépendance inverse serait un cycle. Le studio s'ouvre par son propre binaire.

### `rustmotion-studio` : aucun commentaire
> Les deux binaires vivent dans la crate publiée `rustmotion` : une crate qui
> n'a qu'une `[lib]` n'installe rien d'exécutable via `cargo install`, et
> `cargo install --git <url>` **refuse** un dépôt où plus d'un paquet déclare un
> `[[bin]]` (« multiple packages with binaries found » — ni `default-members` ni
> `required-features` ne changent ce décompte). C'est pour ça que le studio est
> un module de cette crate et non un paquet à part : en paquet, il dépendait de
> `loader`/`encode`, donc en faire une dépendance de `rustmotion` était un cycle,
> que cargo refuse même optionnel.
>
> ```bash
> cargo install --git https://github.com/LeadcodeDev/rustmotion # CLI
> cargo install --git https://github.com/LeadcodeDev/rustmotion --features studio # CLI + studio
> ```
>
> `studio` est hors du build par défaut : il tire gpui et une toolchain GUI, qui
> ne compilent pas partout où le CLI compile. Il n'y a pas de **sous-commande**
> `studio` non plus — elle devrait disparaître du `--help` selon le feature.
> `--workspace` seul ne compile plus le studio : CI passe
> `--features rustmotion/studio` à clippy et aux tests, sans quoi 11 600 lignes
> cessent d'être vérifiées en restant vertes.

### `src/studio/` : aucun commentaire

Le code du studio ne porte **aucun commentaire** — ni `//`, ni `///`, ni `//!`.
La règle ne vaut que pour cette crate : les quatre autres sont publiées, et
vider leurs doc comments viderait leurs pages docs.rs.
La règle suivait la crate ; elle suit maintenant le dossier
`crates/rustmotion/src/studio/`, la fusion du paquet n'ayant rien changé à son
bien-fondé.

Quand un commentaire semble nécessaire, c'est le signal qu'il faut **renommer
la liaison ou extraire une fonction nommée** : l'explication va dans un
Expand Down
57 changes: 53 additions & 4 deletions crates/rustmotion/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,30 @@ path = "src/lib.rs"

# Le binaire vit ici, et pas dans une crate à part, parce que c'est la seule
# façon que `cargo install rustmotion` livre une commande : une crate qui n'a
# qu'une `[lib]` installe une bibliothèque et rien d'exécutable. La
# sous-commande `studio` ne peut pas y être : `rustmotion-studio` dépend de
# `rustmotion`, donc la dépendance inverse — même optionnelle — est un cycle
# que cargo refuse. Le studio garde son propre binaire `rustmotion-studio`.
# qu'une `[lib]` installe une bibliothèque et rien d'exécutable.
#
# Le studio a rejoint cette crate (`src/studio/`) pour la même raison, poussée
# d'un cran : `cargo install --git <url>` sans nom de paquet refuse un dépôt où
# plus d'un paquet déclare un `[[bin]]` (« multiple packages with binaries
# found »), et ni `default-members` ni `required-features` ne changent ce
# décompte — vérifié, pas supposé. Un seul paquet peut donc porter des
# binaires. Il était un paquet à part, qui dépendait de `rustmotion` pour
# `loader` et `encode` : en faire une dépendance de `rustmotion` aurait été un
# cycle, que cargo refuse même optionnel. Devenu un module, il n'y a plus deux
# paquets, donc plus de cycle, et ses chemins `rustmotion::…` restent valides
# tels quels grâce au `extern crate self as rustmotion` de `lib.rs`.
[[bin]]
name = "rustmotion"
path = "src/bin/main.rs"

# Derrière `required-features`, hors du build par défaut : gpui et sa toolchain
# GUI ne doivent pas entrer dans un `cargo install rustmotion` fait sur une
# machine sans affichage, où elles peuvent ne pas compiler du tout.
[[bin]]
name = "rustmotion-studio"
path = "src/bin/studio.rs"
required-features = ["studio"]

[dependencies]
rustmotion-core.workspace = true
rustmotion-components = { workspace = true, features = ["lottie-native"] }
Expand Down Expand Up @@ -50,7 +66,40 @@ tiny-skia = "0.11"
ureq = "3"
rustfft = "6"

# Dépendances du studio (`src/studio/`), toutes optionnelles : le feature
# `studio` les active, le build par défaut ne les voit pas.
gpui-kit = { version = "=0.6.6", optional = true }
gpui-component = { version = "0.6.6", optional = true }
smallvec = { version = "1", optional = true }
tokio = { version = "1", features = ["time"], optional = true }
rfd = { version = "0.17", optional = true }
rodio = { version = "0.22", default-features = false, features = [
"playback",
], optional = true }
palette = { version = "0.7.6", default-features = false, features = [
"std",
], optional = true }

[features]
## Compile et installe aussi le binaire `rustmotion-studio` :
## `cargo install --git https://github.com/LeadcodeDev/rustmotion --features studio`.
## Hors du build par défaut parce qu'il tire gpui et sa toolchain GUI, qui ne
## compilent pas partout où le CLI compile.
##
## `serde_json/preserve_order` en fait partie parce que le studio réécrit les
## fichiers de scénario de l'utilisateur et ne doit pas en réordonner les clés.
## C'est aussi ce que voudrait `validate --fix`, mais l'activer par défaut
## changerait l'ordre de ses réécritures : à décider ailleurs qu'ici.
studio = [
"dep:gpui-kit",
"dep:gpui-component",
"dep:smallvec",
"dep:tokio",
"dep:rfd",
"dep:rodio",
"dep:palette",
"serde_json/preserve_order",
]
# Opt-in: integration tests that shell out to a real ffmpeg binary.
ffmpeg_integration = []
## Re-expose native Lottie decoding (default-on). Activating this feature here
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
fn main() {
if let Err(e) = rustmotion_studio::run() {
if let Err(e) = rustmotion::studio::run() {
eprintln!("Error: {}", e);
std::process::exit(1);
}
Expand Down
12 changes: 7 additions & 5 deletions crates/rustmotion/src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
//! Le binaire `rustmotion` : analyse des arguments et aiguillage vers
//! `commands`.
//!
//! Il n'y a pas de sous-commande `studio` ici. Le studio est une app Dioxus
//! qui dépend de cette crate ; l'appeler depuis ce module ferait dépendre
//! `rustmotion` de `rustmotion-studio`, donc d'elle-même, et cargo refuse le
//! cycle. Le studio s'ouvre par son propre binaire, `rustmotion-studio -f
//! scenario.json`.
//! Il n'y a pas de sous-commande `studio` ici, et c'est délibéré : le studio
//! n'entre dans le build que derrière le feature `studio`, donc une
//! sous-commande devrait soit disparaître du `--help` selon le feature, soit
//! échouer à l'exécution en expliquant qu'il faut réinstaller. Il garde son
//! binaire, `rustmotion-studio -f scenario.json`, que `cargo install
//! --features studio` livre à côté de celui-ci. Le code, lui, vit dans cette
//! crate depuis la fusion du paquet — voir `crate::studio`.

mod claude_md;
mod commands;
Expand Down
Loading
Loading