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 <noreply@anthropic.com>
This commit is contained in:
Brandon ThomasandClaude Opus 5.5 committed 2026-09-30 11:18:48 -04:00
1 parent 498ac6da23
commit ac63aad991
11 files changed
+216 -12

No files matched your search

+62
View File
@@ -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: <file name>
title: <what it is>
author: <creator / copyright holder>
source: <URL, or "original work" for contributor-made assets>
license: <SPDX id or licence name>
license-file: <path to the licence text, if the licence requires shipping it>
added: <YYYY-MM-DD> by <contributor>
notes: <how it was made or modified; for screenshots, what is shown>
```
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.
+28
View File
@@ -0,0 +1,28 @@
# Attribution
Every non-code asset in FilmCraft, with its author, source and licence. Each file listed here also has
a `<file>.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 |
+2 -1
View File
@@ -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 `<file>.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
+3 -7
View File
@@ -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 `<file>.attribution` sidecar.
Rules: [`../AGENTS.md`](../AGENTS.md) §1. No Adobe artwork, open licences only, every asset attributed.
@@ -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).
@@ -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).
@@ -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).
@@ -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).
+8 -2
View File
@@ -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))
}
@@ -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.
+73 -2
View File
@@ -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
//! `<file>.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<Vec<String>, 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 <layers|wasm|ci>".into()),
_ => Err("usage: cargo xtask <layers|assets|wasm|ci>".into()),
};
match r {
Ok(()) => ExitCode::SUCCESS,