From ac63aad991ade1c95d150497ac44d6798249280c Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 30 Sep 2026 11:18:48 -0400 Subject: [PATCH] AGENTS.md asset rules (no Adobe iconography/images; open licences only; per-asset .attribution sidecars + ATTRIBUTION.md), cargo xtask assets gate in ci; hevc test fixture temp names unique per thread Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 62 +++++++++++++++ ATTRIBUTION.md | 28 +++++++ CLAUDE.md | 3 +- assets/README.md | 10 +-- assets/fonts/Inter-Medium.ttf.attribution | 8 ++ assets/fonts/Inter-Regular.ttf.attribution | 8 ++ assets/fonts/Inter-SemiBold.ttf.attribution | 8 ++ .../JetBrainsMono-Regular.ttf.attribution | 8 ++ crates/hevc/tests/common/mod.rs | 10 ++- docs/images/filmcraft-hero.png.attribution | 8 ++ xtask/src/main.rs | 75 ++++++++++++++++++- 11 files changed, 216 insertions(+), 12 deletions(-) create mode 100644 AGENTS.md create mode 100644 ATTRIBUTION.md create mode 100644 assets/fonts/Inter-Medium.ttf.attribution create mode 100644 assets/fonts/Inter-Regular.ttf.attribution create mode 100644 assets/fonts/Inter-SemiBold.ttf.attribution create mode 100644 assets/fonts/JetBrainsMono-Regular.ttf.attribution create mode 100644 docs/images/filmcraft-hero.png.attribution diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..49762507 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,62 @@ +# FilmCraft — rules for agents and contributors + +These rules apply to every human and AI contributor. `CLAUDE.md` holds the working instructions; this +file holds the rules that must never be broken. When the two disagree, this file wins. + +## 1. Assets: no Adobe artwork, every asset licensed and attributed + +This rule is absolute. Breaking it is the most serious mistake a contributor can make on this project. + +**What counts as an asset:** any image, icon, cursor, logo, illustration, screenshot, font, colour LUT, +preset, template, sound, music, video, 3D model, or other non-code media. This covers files in the +repository, bytes embedded in code (`include_bytes!`, base64, data URIs), and data hard-coded to +reproduce an image (for example, point lists traced from someone else's icon). + +1. **Never use Adobe iconography, images or other Adobe assets**, in any form: + - no icons, cursors, logos, splash screens, UI artwork or screenshots from any Adobe product; + - no Adobe fonts, LUTs, presets, templates, sound effects, stock media or sample projects; + - no traced, redrawn, recoloured or "inspired-by" copies of Adobe icons. Our icons may follow generic + conventions (a play triangle, a razor blade, a stopwatch), but each must be drawn from scratch + without reference to Adobe's artwork. +2. **Every asset must be open**: open-source licensed (MIT, Apache-2.0, BSD, ISC, zlib, SIL OFL…), + public domain / CC0, or Creative Commons (CC BY or CC BY-SA; no NC/ND licences), **or** created by a + contributor who owns it and licenses it to the project under the project licence (MIT OR Apache-2.0). + If the licence is unknown or unclear, the asset does not go in. +3. **Every asset needs an attribution sidecar.** Next to each asset file `X`, add `X.attribution` with: + ```text + asset: + title: + author: + source: + license: + license-file: + added: by + notes: + ``` + Also add one line for the asset to [`ATTRIBUTION.md`](ATTRIBUTION.md). `cargo xtask assets` (part of + `cargo xtask ci`) fails if any asset lacks a sidecar or index entry. +4. **Screenshots** in the repo may show only FilmCraft (or other open projects), with media we generated + or media that is itself openly licensed. Never commit screenshots of Adobe products. +5. **Local reference material stays local.** Premiere reference screenshots and notes live only in + `plan/premiere/` (gitignored). They must never be committed, bundled, embedded, traced or shipped. +6. **Code-drawn assets** (icons in `crates/ui-egui/src/icons.rs`, procedural demo footage in + `crates/media`, procedural looks in `crates/render`) are original work under the project licence + and are listed in `ATTRIBUTION.md`. +7. **When in doubt, leave it out** and draw or generate it yourself. + +## 2. Clean-room code + +- Never read, disassemble or copy anything inside Adobe application bundles; file names and listings only. + Observe behaviour by using the app; never capture its Home screen, recent projects, account info or + file browsers. +- Never copy GPL/LGPL/AGPL code (FFmpeg, x264, x265, libvpx, MLT, Kdenlive, Shotcut, Olive, LAME…). + Implement codecs and formats from public specifications. +- ffmpeg/ffprobe may be used only as external test oracles and fixture generators. They are never + linked, bundled or shipped. +- Dependencies must use permissive licences: MIT/Apache-2.0/BSD/ISC/Zlib/Unicode/CC0/BSL-1.0, or + MPL-2.0 used unmodified. + +## 3. Engineering rules + +See `CLAUDE.md`: pure Rust, dependency layering (`cargo xtask layers`), exact `Tick` time, everything is +a command, everything is agent-drivable, and the quality gates (`cargo xtask ci`) before every commit. diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md new file mode 100644 index 00000000..00ac8617 --- /dev/null +++ b/ATTRIBUTION.md @@ -0,0 +1,28 @@ +# Attribution + +Every non-code asset in FilmCraft, with its author, source and licence. Each file listed here also has +a `.attribution` sidecar with full details. The rules are in [AGENTS.md](AGENTS.md) §1: no Adobe +iconography, images or other Adobe assets; open licences only; every asset attributed. +`cargo xtask assets` checks that this index and the sidecars cover every asset in the repository. + +## Asset files + +| Asset | Author | Source | Licence | +|---|---|---|---| +| `assets/fonts/Inter-Regular.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) | +| `assets/fonts/Inter-Medium.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) | +| `assets/fonts/Inter-SemiBold.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) | +| `assets/fonts/JetBrainsMono-Regular.ttf` | The JetBrains Mono Project Authors | https://github.com/JetBrains/JetBrainsMono | OFL-1.1 (`assets/fonts/OFL-JetBrainsMono.txt`) | +| `docs/images/filmcraft-hero.png` | FilmCraft contributors | Original work: screenshot of FilmCraft showing procedurally generated demo footage | MIT OR Apache-2.0 | + +## Assets drawn or generated in code + +These are original work by FilmCraft contributors under the project licence (MIT OR Apache-2.0). None +is traced or derived from Adobe artwork. + +| Asset | Where | +|---|---| +| UI icons (tools, transport, panels, header) | `crates/ui-egui/src/icons.rs`: vector paths on a 16×16 grid | +| Demo footage (ocean sunset, aurora, city night, dunes, forest, plasma), bars and tone, counting leader, colour matte | `crates/media/src/generators.rs` | +| Lumetri "Look" presets (Teal & Orange, Warm Film, …) | `crates/render/src/effects.rs` (`apply_look`): procedural colour transforms, not LUT files | +| Colour palette and layout metrics | `crates/ui-egui/src/theme.rs`: colour values and sizes only; no artwork | diff --git a/CLAUDE.md b/CLAUDE.md index 6d0d5cfc..099cc74b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,13 +10,14 @@ FilmCraft is a clean-room, open-source, pure-Rust non-linear video editor target `plan/` is gitignored (local-only). ## Non-negotiables +**Read [`AGENTS.md`](AGENTS.md) first; its rules override everything here.** In particular (§1): never use Adobe iconography, images or any other Adobe asset. Every asset must be openly licensed (OSS / public domain / Creative Commons, or original work by a contributor) and must have a `.attribution` sidecar plus an entry in `ATTRIBUTION.md`. `cargo xtask assets` enforces this. - **Clean-room.** Never read/disassemble anything inside Adobe app bundles (names/listings only). Never copy Adobe icons, presets, LUTs, fonts. Never copy GPL/LGPL/AGPL code (FFmpeg, x264, x265, MLT, Kdenlive, Shotcut, Olive, LAME…). ffmpeg/ffprobe run only as external test oracles / fixture generators — never linked or shipped. - **Pure Rust** in the product. OS media APIs only via Rust bindings in `crates/platform`, optional, behind traits. - **Layering** (`plan/architecture.md` §3, enforced by `cargo xtask layers`): nothing below L5 depends on egui/eframe/winit/rfd/cpal. L0 codec/container crates depend only on `filmcraft-bitstream`. - **Exact time:** all time is `filmcraft_time::Tick` (254 016 000 000/s). Never use f64 seconds for edit math. - **Everything is a command** (`crates/engine`): id, label, menu path, shortcut, params, enabled(), run(). UI, CLI, control channel and MCP all dispatch by id. - **Everything is agent-drivable:** every interactive widget registers an automation id; UI state is serde so the control channel can read/set it. -- **Quality gates** before every commit: `cargo fmt --check`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test --workspace`, `cargo xtask layers`, `cargo xtask wasm` (L0–L5 changes). +- **Quality gates** before every commit: `cargo fmt --check`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test --workspace`, `cargo xtask layers`, `cargo xtask assets`, `cargo xtask wasm` (L0–L5 changes). `cargo xtask ci` runs them all. - **Commits:** one task id per commit (`M2.4: CABAC residual decoding`). Only green states. End messages with the attribution line required by the environment. ## Running and looking at the app diff --git a/assets/README.md b/assets/README.md index b7984e03..a423a09a 100644 --- a/assets/README.md +++ b/assets/README.md @@ -1,8 +1,4 @@ -# Third-party assets +# Assets -| File | Licence | Source | -|---|---|---| -| `fonts/Inter-*.ttf` | SIL OFL 1.1 (`fonts/OFL-Inter.txt`) | https://rsms.me/inter | -| `fonts/JetBrainsMono-Regular.ttf` | SIL OFL 1.1 (`fonts/OFL-JetBrainsMono.txt`) | https://www.jetbrains.com/lp/mono/ | - -All icons are drawn in code (`crates/ui-egui/src/icons.rs`); no Adobe artwork is used. +Every file here is listed in [`../ATTRIBUTION.md`](../ATTRIBUTION.md) and has a `.attribution` sidecar. +Rules: [`../AGENTS.md`](../AGENTS.md) §1. No Adobe artwork, open licences only, every asset attributed. diff --git a/assets/fonts/Inter-Medium.ttf.attribution b/assets/fonts/Inter-Medium.ttf.attribution new file mode 100644 index 00000000..20dc0cf4 --- /dev/null +++ b/assets/fonts/Inter-Medium.ttf.attribution @@ -0,0 +1,8 @@ +asset: Inter-Medium.ttf +title: Inter Medium (UI typeface) +author: The Inter Project Authors (Rasmus Andersson et al.) +source: https://github.com/rsms/inter +license: OFL-1.1 +license-file: assets/fonts/OFL-Inter.txt +added: 2026-09-30 by Claude (agent) for Brandon Thomas +notes: Unmodified font file, embedded in the UI via include_bytes! (crates/ui-egui/src/theme.rs). diff --git a/assets/fonts/Inter-Regular.ttf.attribution b/assets/fonts/Inter-Regular.ttf.attribution new file mode 100644 index 00000000..c4f496fb --- /dev/null +++ b/assets/fonts/Inter-Regular.ttf.attribution @@ -0,0 +1,8 @@ +asset: Inter-Regular.ttf +title: Inter Regular (UI typeface) +author: The Inter Project Authors (Rasmus Andersson et al.) +source: https://github.com/rsms/inter +license: OFL-1.1 +license-file: assets/fonts/OFL-Inter.txt +added: 2026-09-30 by Claude (agent) for Brandon Thomas +notes: Unmodified font file, embedded in the UI via include_bytes! (crates/ui-egui/src/theme.rs). diff --git a/assets/fonts/Inter-SemiBold.ttf.attribution b/assets/fonts/Inter-SemiBold.ttf.attribution new file mode 100644 index 00000000..20476e00 --- /dev/null +++ b/assets/fonts/Inter-SemiBold.ttf.attribution @@ -0,0 +1,8 @@ +asset: Inter-SemiBold.ttf +title: Inter SemiBold (UI typeface) +author: The Inter Project Authors (Rasmus Andersson et al.) +source: https://github.com/rsms/inter +license: OFL-1.1 +license-file: assets/fonts/OFL-Inter.txt +added: 2026-09-30 by Claude (agent) for Brandon Thomas +notes: Unmodified font file, embedded in the UI via include_bytes! (crates/ui-egui/src/theme.rs). diff --git a/assets/fonts/JetBrainsMono-Regular.ttf.attribution b/assets/fonts/JetBrainsMono-Regular.ttf.attribution new file mode 100644 index 00000000..ab720f6c --- /dev/null +++ b/assets/fonts/JetBrainsMono-Regular.ttf.attribution @@ -0,0 +1,8 @@ +asset: JetBrainsMono-Regular.ttf +title: JetBrains Mono Regular (monospace for timecode) +author: The JetBrains Mono Project Authors +source: https://github.com/JetBrains/JetBrainsMono +license: OFL-1.1 +license-file: assets/fonts/OFL-JetBrainsMono.txt +added: 2026-09-30 by Claude (agent) for Brandon Thomas +notes: Unmodified font file, embedded in the UI via include_bytes! (crates/ui-egui/src/theme.rs). diff --git a/crates/hevc/tests/common/mod.rs b/crates/hevc/tests/common/mod.rs index 2957b98e..2da4dabe 100644 --- a/crates/hevc/tests/common/mod.rs +++ b/crates/hevc/tests/common/mod.rs @@ -129,7 +129,7 @@ pub fn ensure(f: &Fixture) -> Option<(PathBuf, PathBuf)> { let hevc = dir.join(format!("{}.hevc", f.name)); let yuv = dir.join(format!("{}.yuv", f.name)); if !hevc.exists() { - let tmp = dir.join(format!("{}.tmp.{}.hevc", f.name, std::process::id())); + let tmp = dir.join(format!("{}.tmp.{}.hevc", f.name, unique())); let mut vf = format!("{}=size={}x{}:rate=25,format={}", f.source, f.width, f.height, pix_fmt(f.bit_depth)); if !f.filter.is_empty() { vf.push(','); @@ -154,7 +154,7 @@ pub fn ensure(f: &Fixture) -> Option<(PathBuf, PathBuf)> { std::fs::rename(&tmp, &hevc).unwrap(); } if !yuv.exists() { - let tmp = dir.join(format!("{}.tmp.{}.yuv", f.name, std::process::id())); + let tmp = dir.join(format!("{}.tmp.{}.yuv", f.name, unique())); let mut c = Command::new(&ff); c.args(["-hide_banner", "-loglevel", "error", "-y", "-i"]).arg(&hevc); c.args(["-fps_mode", "passthrough", "-f", "rawvideo", "-pix_fmt", pix_fmt(f.bit_depth)]).arg(&tmp); @@ -316,3 +316,9 @@ pub fn check_pts(pics: &[Picture]) -> Result<(), String> { } Ok(()) } + +/// Unique temp-file suffix: process id plus a per-process counter (tests run on parallel threads). +fn unique() -> String { + static N: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); + format!("{}-{}", std::process::id(), N.fetch_add(1, std::sync::atomic::Ordering::Relaxed)) +} diff --git a/docs/images/filmcraft-hero.png.attribution b/docs/images/filmcraft-hero.png.attribution new file mode 100644 index 00000000..d6ea1662 --- /dev/null +++ b/docs/images/filmcraft-hero.png.attribution @@ -0,0 +1,8 @@ +asset: filmcraft-hero.png +title: FilmCraft README hero screenshot +author: FilmCraft contributors +source: original work +license: MIT OR Apache-2.0 +license-file: +added: 2026-09-30 by Claude (agent) for Brandon Thomas +notes: Screenshot of the FilmCraft app itself (Editing workspace, demo project) captured with the app's own ui.screenshot control command; status strip cropped, downscaled to 2400 px. All footage shown is procedurally generated by crates/media (demo scenes); all icons are drawn in code; fonts are Inter and JetBrains Mono (OFL-1.1). Contains no Adobe artwork. diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 9adee93f..05343e66 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -3,7 +3,9 @@ //! - `layers`: enforces the dependency layering of `plan/architecture.md` §3 (downward-only edges, //! listed same-layer edges, L0 codec crates depend on `bitstream` only, no UI/OS crates below L5). //! - `wasm`: `cargo check --target wasm32-unknown-unknown` for every crate in L0–L4. -//! - `ci`: fmt check, clippy -D warnings, tests, layers, wasm. +//! - `assets`: every asset file (image, icon, font, LUT, audio, video…) has a complete +//! `.attribution` sidecar and an entry in `ATTRIBUTION.md` (AGENTS.md §1). +//! - `ci`: fmt check, clippy -D warnings, tests, layers, assets, wasm. use std::process::{Command, ExitCode}; @@ -141,6 +143,73 @@ fn layers() -> Result<(), String> { } } +/// File extensions that count as assets (AGENTS.md §1). +const ASSET_EXT: &[&str] = &[ + "png", "jpg", "jpeg", "gif", "webp", "svg", "ico", "icns", "bmp", "tif", "tiff", "heic", "avif", "exr", "ttf", "otf", "ttc", "woff", "woff2", "cube", + "3dl", "lut", "look", "wav", "mp3", "aac", "flac", "ogg", "opus", "m4a", "aif", "aiff", "mp4", "mov", "m4v", "mkv", "webm", "avi", "mxf", "psd", "ai", + "eps", "pdf", "prproj", "ffx", "prfpset", "mogrt", "aep", +]; + +/// Sidecar fields that must be present and non-empty. +const REQUIRED_FIELDS: &[&str] = &["asset", "title", "author", "source", "license", "added"]; + +fn repo_files() -> Result, String> { + let out = Command::new("git").args(["ls-files", "--cached", "--others", "--exclude-standard"]).output().map_err(|e| e.to_string())?; + if !out.status.success() { + return Err(String::from_utf8_lossy(&out.stderr).into()); + } + Ok(String::from_utf8_lossy(&out.stdout).lines().map(str::to_string).filter(|f| std::path::Path::new(f).exists()).collect()) +} + +fn is_asset(path: &str) -> bool { + std::path::Path::new(path).extension().and_then(|e| e.to_str()).is_some_and(|e| ASSET_EXT.contains(&e.to_ascii_lowercase().as_str())) +} + +fn assets() -> Result<(), String> { + let files = repo_files()?; + let index = std::fs::read_to_string("ATTRIBUTION.md").map_err(|e| format!("ATTRIBUTION.md: {e}"))?; + let mut errors = Vec::new(); + let mut n = 0; + for f in files.iter().filter(|f| is_asset(f)) { + n += 1; + let lower = f.to_ascii_lowercase(); + if lower.contains("adobe") || lower.contains("premiere") { + errors.push(format!("{f}: asset paths must not reference Adobe/Premiere (AGENTS.md §1)")); + } + let side = format!("{f}.attribution"); + match std::fs::read_to_string(&side) { + Err(_) => errors.push(format!("{f}: missing attribution sidecar {side}")), + Ok(text) => { + for field in REQUIRED_FIELDS { + let ok = text.lines().any(|l| l.split_once(':').is_some_and(|(k, v)| k.trim() == *field && !v.trim().is_empty())); + if !ok { + errors.push(format!("{side}: field `{field}` missing or empty")); + } + } + let lic = text.lines().find_map(|l| l.split_once(':').filter(|(k, _)| k.trim() == "license").map(|(_, v)| v.trim().to_ascii_lowercase())); + if lic.is_some_and(|l| l.contains("-nc") || l.contains("-nd") || l.contains("adobe") || l.contains("proprietary")) { + errors.push(format!("{side}: licence not allowed (no NC/ND, Adobe or proprietary licences)")); + } + } + } + if !index.contains(&format!("`{f}`")) { + errors.push(format!("{f}: not listed in ATTRIBUTION.md")); + } + } + for f in files.iter().filter(|f| f.ends_with(".attribution")) { + let asset = f.trim_end_matches(".attribution"); + if !std::path::Path::new(asset).exists() { + errors.push(format!("{f}: sidecar for a file that does not exist")); + } + } + if errors.is_empty() { + println!("assets: ok ({n} assets attributed)"); + Ok(()) + } else { + Err(errors.join("\n")) + } +} + fn run(cmd: &mut Command) -> Result<(), String> { eprintln!("$ {cmd:?}"); let st = cmd.status().map_err(|e| e.to_string())?; @@ -172,6 +241,7 @@ fn ci() -> Result<(), String> { run(Command::new(cargo).args(["clippy", "--workspace", "--all-targets", "--release", "--", "-D", "warnings"]))?; run(Command::new(cargo).args(["test", "--workspace", "--release"]))?; layers()?; + assets()?; wasm() } @@ -180,8 +250,9 @@ fn main() -> ExitCode { let r = match task.as_str() { "layers" => layers(), "wasm" => wasm(), + "assets" => assets(), "ci" => ci(), - _ => Err("usage: cargo xtask ".into()), + _ => Err("usage: cargo xtask ".into()), }; match r { Ok(()) => ExitCode::SUCCESS,