mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-14 11:17:01 -04:00
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 <[email protected]>
This commit is contained in:
1 parent
498ac6da23
commit
ac63aad991
11 files changed
+216
-12
No files matched your search
@@ -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.
|
||||
@@ -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 |
|
||||
@@ -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
@@ -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).
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user