app icon: owl with ear tufts (owner's ArtCraft drawing), Dock/taskbar/launcher wiring, palette docs

- assets/app-icon/: canonical SVG + light SVG, 1024 PNG, macOS runtime PNG, .icns, .ico,
  hicolor theme, LICENSE.txt and README (palette, geometry, provenance, regeneration).
- packaging/icons.sh regenerates everything with resvg, iconutil and the new `cargo xtask ico`.
- Native app sets the window icon (with_icon) and the Wayland app id; build.rs embeds the .ico
  and VERSIONINFO into filmcraft.exe on Windows (winresource, Windows-host build-dep only).
- packaging/linux/ai.storyteller.filmcraft.desktop with Icon=ai.storyteller.filmcraft.
- Web app: favicon.svg replaced by favicon.png from the same artwork; sidecars and
  ATTRIBUTION.md updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas 2026-10-02 09:52:46 -04:00
parent 1f54f4a608
commit 4a931a3400
47 changed files with 500 additions and 30 deletions

View File

@ -25,7 +25,22 @@ iconography, images or other Adobe assets; open licences only; every asset attri
| `docs/images/filmcraft-scopes.png` | FilmCraft contributors | Original work: FilmCraft UI crop (scopes of a Charade (1963) frame, public domain) | MIT OR Apache-2.0 |
| `docs/images/filmcraft-effects.png` | FilmCraft contributors | Original work: FilmCraft screenshot crop; NASA Earth Views (US Government work, public domain) | MIT OR Apache-2.0; footage public domain |
| `docs/images/filmcraft-timeline.png` | FilmCraft contributors | Original work: FilmCraft UI crop; thumbnails from Night of the Living Dead (1968) and Carnival of Souls (1962), CC0 Chopin waveform | MIT OR Apache-2.0; film frames public domain |
| `apps/filmcraft-web/web/favicon.svg` | FilmCraft contributors | Original work: web app icon (disc, sprocket ring, play triangle) drawn from basic SVG shapes | MIT OR Apache-2.0 |
| `assets/app-icon/filmcraft.svg` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; canonical vector | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `assets/app-icon/filmcraft-small.svg` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; lighter vector | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `assets/app-icon/filmcraft-1024.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; 1024 px render | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `assets/app-icon/filmcraft-macos-512.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; macOS runtime Dock icon (512 px) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `assets/app-icon/filmcraft.icns` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; macOS .icns | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `assets/app-icon/filmcraft.ico` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Windows .ico (16-256 px) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `assets/app-icon/hicolor/16x16/apps/ai.storyteller.filmcraft.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme 16 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `assets/app-icon/hicolor/24x24/apps/ai.storyteller.filmcraft.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme 24 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `assets/app-icon/hicolor/32x32/apps/ai.storyteller.filmcraft.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme 32 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `assets/app-icon/hicolor/48x48/apps/ai.storyteller.filmcraft.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme 48 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `assets/app-icon/hicolor/64x64/apps/ai.storyteller.filmcraft.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme 64 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `assets/app-icon/hicolor/128x128/apps/ai.storyteller.filmcraft.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme 128 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `assets/app-icon/hicolor/256x256/apps/ai.storyteller.filmcraft.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme 256 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `assets/app-icon/hicolor/512x512/apps/ai.storyteller.filmcraft.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme 512 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `assets/app-icon/hicolor/scalable/apps/ai.storyteller.filmcraft.svg` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; Linux hicolor theme scalable | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
| `apps/filmcraft-web/web/favicon.png` | Brandon Thomas (FilmCraft owner) | Original work: FilmCraft app icon (owl with ear tufts), drawn by the owner in ArtCraft and vectorised; 128 px | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
## Golden test images

19
Cargo.lock generated
View File

@ -1671,6 +1671,7 @@ dependencies = [
"rfd",
"serde_json",
"winit",
"winresource",
]
[[package]]
@ -5777,10 +5778,12 @@ version = "1.1.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a"
dependencies = [
"indexmap",
"serde_core",
"serde_spanned 1.1.1",
"toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
"toml_writer",
"winnow 1.0.4",
]
@ -5847,6 +5850,12 @@ dependencies = [
"winnow 1.0.4",
]
[[package]]
name = "toml_writer"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
[[package]]
name = "tracing"
version = "0.1.44"
@ -6967,6 +6976,16 @@ dependencies = [
"memchr",
]
[[package]]
name = "winresource"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087"
dependencies = [
"toml 1.1.6+spec-1.1.0",
"version_check",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"

View File

@ -7,6 +7,10 @@
</a>
</p>
<p align="center">
<img alt="FilmCraft icon: an engraved owl on FilmCraft violet (#8b5cf6)" src="assets/app-icon/filmcraft-1024.png" width="128">
</p>
<h1 align="center">FilmCraft</h1>
<p align="center">

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,8 @@
asset: favicon.png
title: FilmCraft web app icon (favicon and loading image), 128 px
author: Brandon Thomas (FilmCraft owner), for the FilmCraft project
source: original work (drawn by the owner in ArtCraft, then vectorised)
license: MIT OR Apache-2.0
license-file: assets/app-icon/LICENSE.txt
added: 2026-10-02 by Claude (agent) for Brandon Thomas
notes: Rendered from assets/app-icon/filmcraft.svg with resvg by packaging/icons.sh. Replaces the earlier hand-drawn favicon.svg.

View File

@ -1,16 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<!-- FilmCraft web icon: original work (a film reel ring with a play triangle). -->
<circle cx="32" cy="32" r="30" fill="#14343a"/>
<circle cx="32" cy="32" r="22" fill="none" stroke="#f0a24a" stroke-width="5"/>
<g fill="#14343a">
<circle cx="32" cy="10" r="2.2"/>
<circle cx="32" cy="54" r="2.2"/>
<circle cx="10" cy="32" r="2.2"/>
<circle cx="54" cy="32" r="2.2"/>
<circle cx="16.4" cy="16.4" r="2.2"/>
<circle cx="47.6" cy="47.6" r="2.2"/>
<circle cx="16.4" cy="47.6" r="2.2"/>
<circle cx="47.6" cy="16.4" r="2.2"/>
</g>
<path d="M26 21.5 L44 32 L26 42.5 Z" fill="#f4f1ea"/>
</svg>

Before

Width:  |  Height:  |  Size: 689 B

View File

@ -1,9 +0,0 @@
asset: favicon.svg
title: FilmCraft web app icon (favicon / install icon)
author: FilmCraft contributors
source: original work
license: MIT OR Apache-2.0
added: 2026-10-01 by FilmCraft contributors (Claude agent, M13 web app)
notes: Hand-written SVG: a dark teal disc, an orange ring with eight sprocket dots and a play
triangle, drawn from scratch from basic shapes. Not derived from any other product's
icon.

View File

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<title>FilmCraft</title>
<link rel="icon" href="favicon.svg" type="image/svg+xml">
<link rel="icon" href="favicon.png" type="image/png">
<style>
html, body { margin: 0; height: 100%; overflow: hidden; background: #1b1c1f; }
canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
@ -20,7 +20,7 @@
</head>
<body>
<canvas id="filmcraft_canvas"></canvas>
<div id="loading"><img src="favicon.svg" alt=""><div id="status">Loading FilmCraft…</div></div>
<div id="loading"><img src="favicon.png" alt=""><div id="status">Loading FilmCraft…</div></div>
<script type="module">
import init, { start } from "./filmcraft_web.js";
const t0 = performance.now();

View File

@ -5,6 +5,7 @@ version.workspace = true
edition.workspace = true
license.workspace = true
default-run = "filmcraft"
build = "build.rs"
[lints]
workspace = true
@ -32,3 +33,7 @@ objc2-app-kit = { version = "0.3", default-features = false, features = ["std",
objc2 = "0.6"
# Same winit as eframe: macOS event-loop options (no app activation when driven by an agent).
winit = { version = "0.30", default-features = false }
# Icon + VERSIONINFO for filmcraft.exe (build.rs); only needed when building on Windows.
[target.'cfg(windows)'.build-dependencies]
winresource = "0.1"

43
apps/filmcraft/build.rs Normal file
View File

@ -0,0 +1,43 @@
//! Windows only: embed the app icon and version info (VERSIONINFO) into `filmcraft.exe`, so it
//! shows in Explorer, the taskbar, the Start menu and Alt-Tab.
//!
//! On every other target this does nothing (`winresource` is only a build-dependency on Windows
//! hosts). A missing resource compiler is a warning, unless `FILMCRAFT_REQUIRE_WINRES=1` (for
//! release builds) turns it into an error.
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=../../assets/app-icon/filmcraft.ico");
println!("cargo:rerun-if-env-changed=FILMCRAFT_REQUIRE_WINRES");
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") {
return;
}
windows::embed_resources();
}
#[cfg(windows)]
mod windows {
pub fn embed_resources() {
let mut res = winresource::WindowsResource::new();
res.set_icon("../../assets/app-icon/filmcraft.ico")
.set("ProductName", "FilmCraft")
.set("FileDescription", "FilmCraft video editor")
.set("LegalCopyright", "Copyright (c) the FilmCraft contributors. MIT OR Apache-2.0.")
.set("OriginalFilename", "filmcraft.exe")
.set("InternalName", "filmcraft");
if let Err(e) = res.compile() {
if std::env::var_os("FILMCRAFT_REQUIRE_WINRES").is_some() {
panic!("embedding Windows resources failed: {e}");
}
println!("cargo:warning=filmcraft.exe built without icon/version resources: {e}");
}
}
}
/// Cross-compiling for Windows from macOS or Linux: `winresource` is not available on this host.
#[cfg(not(windows))]
mod windows {
pub fn embed_resources() {
println!("cargo:warning=filmcraft.exe built without icon/version resources (cross-compiled from a non-Windows host)");
}
}

View File

@ -25,6 +25,20 @@ use filmcraft_engine::autosave::{AutosaveConfig, default_data_dir};
use filmcraft_ui_egui::FilmcraftApp;
use serde_json::json;
/// Reverse-DNS app id: Linux `.desktop` file, hicolor icon name and Wayland app id.
const APP_ID: &str = "ai.storyteller.filmcraft";
/// The window, Dock, taskbar and app-switcher icon (`assets/app-icon/`, regenerated by
/// `packaging/icons.sh`). macOS gets the tile inside Apple's transparent margin so it matches the
/// size of other Dock icons; Windows and Linux get the full-bleed tile.
fn app_icon() -> egui::IconData {
#[cfg(target_os = "macos")]
let png: &[u8] = include_bytes!("../../../assets/app-icon/filmcraft-macos-512.png");
#[cfg(not(target_os = "macos"))]
let png: &[u8] = include_bytes!("../../../assets/app-icon/hicolor/256x256/apps/ai.storyteller.filmcraft.png");
eframe::icon_data::from_png_bytes(png).unwrap_or_default()
}
fn main() -> eframe::Result {
app_nap::disable();
let mut control_port: Option<u16> = std::env::var("FILMCRAFT_CONTROL_PORT").ok().and_then(|p| p.parse().ok());
@ -58,7 +72,10 @@ fn main() -> eframe::Result {
.with_titlebar_shown(false)
.with_title_shown(false)
// Started for an agent (`--control`): open without taking the user's keyboard focus.
.with_active(control_port.is_none()),
.with_active(control_port.is_none())
.with_icon(app_icon())
// Wayland matches the window to `packaging/linux/ai.storyteller.filmcraft.desktop`.
.with_app_id(APP_ID),
event_loop_builder: agent_event_loop(control_port.is_some()),
..Default::default()
};

View File

@ -0,0 +1,13 @@
FilmCraft app icon (filmcraft.svg and every file derived from it in this directory:
filmcraft-small.svg, filmcraft-1024.png, filmcraft-macos-512.png, filmcraft.icns, filmcraft.ico,
hicolor/, and apps/filmcraft-web/web/favicon.png).
Original artwork made for this project by the FilmCraft owner (Brandon Thomas) in ArtCraft, then
vectorised. No third-party material. Licensed like the rest of FilmCraft, under either of
* Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
* MIT license (https://opensource.org/licenses/MIT)
at your option.
Regenerate the derived files with packaging/icons.sh.

60
assets/app-icon/README.md Normal file
View File

@ -0,0 +1,60 @@
# FilmCraft app icon
**Creature:** an owl with ear tufts, in a frontal head-and-shoulders portrait. It is the template the
other Crafting App icons follow.
## Palette
Exactly three colours:
| Colour | Hex | Used for |
|---|---|---|
| Ink | `#0b0b0c` | line work and the figure's contour |
| Paper | `#efe9dc` | the figure (the owl) |
| FilmCraft violet (app colour) | `#8b5cf6` | the full-bleed field behind the figure |
## Geometry and style
- A 512-unit viewBox tile, rounded square `rx=112`, with the app colour full bleed (no frame or border).
- An engraving-style portrait in the owl-template framing: the top of the head near y 80–100, the
eyes at about 40 % of the height, and the shoulders running off the bottom edge.
- macOS renders (`filmcraft.icns`, `filmcraft-macos-512.png`) put the tile in Apple's grid (824 of
1024 px, transparent margin). Windows, Linux and the web use the full-bleed tile.
## Provenance
The owner drew it in ArtCraft as a 2880 px engraving-style image. The original is kept in craftrules
at `craftrules/assets/app-icons/filmcraft/source.png` (not in this repo). It was vectorised with
craftrules `assets/logo-options/_tools/vectorize_tile.py`, which keys the drawing to the three-colour
palette and traces the ink with potrace. Licence: [LICENSE.txt](LICENSE.txt) (MIT OR Apache-2.0).
## Files
| File | What |
|---|---|
| `filmcraft.svg` | canonical artwork, traced at 2048 px (about 1 MB) |
| `filmcraft-small.svg` | lighter trace at 1024 px (about 450 KB), for when size matters |
| `filmcraft-1024.png` | 1024 px render |
| `filmcraft-macos-512.png` | runtime Dock icon on macOS (embedded by `apps/filmcraft/src/main.rs`) |
| `filmcraft.icns` | macOS bundle icon (`CFBundleIconFile`) |
| `filmcraft.ico` | Windows icon, 16–256 px (embedded in `filmcraft.exe` by `apps/filmcraft/build.rs`) |
| `hicolor/<size>/apps/ai.storyteller.filmcraft.png`, `hicolor/scalable/…svg` | Linux icon theme, used by `packaging/linux/ai.storyteller.filmcraft.desktop` |
The web app's `apps/filmcraft-web/web/favicon.png` (128 px) comes from the same artwork.
## How the icon reaches each OS
- **macOS:** the window icon set at startup (`ViewportBuilder::with_icon`) is the Dock and app-switcher
icon when the app runs unbundled. A future `.app` bundle points `CFBundleIconFile` at `filmcraft.icns`.
- **Windows:** `build.rs` embeds `filmcraft.ico` as the exe's resource icon (Explorer, Start menu,
pinned taskbar), and the runtime icon covers the window, taskbar and Alt-Tab.
- **Linux:** the runtime icon plus `with_app_id("ai.storyteller.filmcraft")`, which Wayland matches to
the `.desktop` file. Install the `hicolor/` tree under `/usr/share/icons/hicolor/` and the desktop
file under `/usr/share/applications/`.
## Regenerate
`packaging/icons.sh` (needs `resvg`; `iconutil` on macOS for the `.icns`; the `.ico` is packed by
`cargo xtask ico`). To change the artwork, replace `filmcraft.svg` and `filmcraft-small.svg` with new
exports from craftrules, then run the script. Every derived file needs its `.attribution` sidecar
and a row in `ATTRIBUTION.md`.

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

View File

@ -0,0 +1,8 @@
asset: filmcraft-1024.png
title: FilmCraft app icon, 1024 px render
author: Brandon Thomas (FilmCraft owner), for the FilmCraft project
source: original work (drawn by the owner in ArtCraft, then vectorised)
license: MIT OR Apache-2.0
license-file: assets/app-icon/LICENSE.txt
added: 2026-10-02 by Claude (agent) for Brandon Thomas
notes: Rendered from filmcraft.svg with resvg by packaging/icons.sh.

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

@ -0,0 +1,8 @@
asset: filmcraft-macos-512.png
title: FilmCraft app icon, macOS runtime Dock icon (512 px)
author: Brandon Thomas (FilmCraft owner), for the FilmCraft project
source: original work (drawn by the owner in ArtCraft, then vectorised)
license: MIT OR Apache-2.0
license-file: assets/app-icon/LICENSE.txt
added: 2026-10-02 by Claude (agent) for Brandon Thomas
notes: Rendered from filmcraft.svg with resvg by packaging/icons.sh, inside Apple's 824/1024 icon grid (transparent margin). Embedded in the app by apps/filmcraft/src/main.rs.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 444 KiB

View File

@ -0,0 +1,8 @@
asset: filmcraft-small.svg
title: FilmCraft app icon, lighter vector
author: Brandon Thomas (FilmCraft owner), for the FilmCraft project
source: original work (drawn by the owner in ArtCraft, then vectorised)
license: MIT OR Apache-2.0
license-file: assets/app-icon/LICENSE.txt
added: 2026-10-02 by Claude (agent) for Brandon Thomas
notes: The same drawing traced at 1024 px (smaller file). Copied unchanged from craftrules assets/app-icons/filmcraft/icon.svg.

Binary file not shown.

View File

@ -0,0 +1,8 @@
asset: filmcraft.icns
title: FilmCraft app icon, macOS .icns
author: Brandon Thomas (FilmCraft owner), for the FilmCraft project
source: original work (drawn by the owner in ArtCraft, then vectorised)
license: MIT OR Apache-2.0
license-file: assets/app-icon/LICENSE.txt
added: 2026-10-02 by Claude (agent) for Brandon Thomas
notes: Built from resvg renders of filmcraft.svg (Apple icon grid) with iconutil by packaging/icons.sh.

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@ -0,0 +1,8 @@
asset: filmcraft.ico
title: FilmCraft app icon, Windows .ico (16-256 px)
author: Brandon Thomas (FilmCraft owner), for the FilmCraft project
source: original work (drawn by the owner in ArtCraft, then vectorised)
license: MIT OR Apache-2.0
license-file: assets/app-icon/LICENSE.txt
added: 2026-10-02 by Claude (agent) for Brandon Thomas
notes: PNG renders of filmcraft.svg (resvg) packed by cargo xtask ico in packaging/icons.sh. Embedded in filmcraft.exe by apps/filmcraft/build.rs.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1017 KiB

View File

@ -0,0 +1,8 @@
asset: filmcraft.svg
title: FilmCraft app icon, canonical vector (owl with ear tufts)
author: Brandon Thomas (FilmCraft owner), for the FilmCraft project
source: original work (drawn by the owner in ArtCraft, then vectorised)
license: MIT OR Apache-2.0
license-file: assets/app-icon/LICENSE.txt
added: 2026-10-02 by Claude (agent) for Brandon Thomas
notes: SVG traced at 2048 px with craftrules assets/logo-options/_tools/vectorize_tile.py from the owner's ArtCraft drawing (craftrules assets/app-icons/filmcraft/source.png). Three colours: ink #0b0b0c, paper #efe9dc, app colour #8b5cf6; 512 viewBox, rx=112. See assets/app-icon/README.md.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,8 @@
asset: ai.storyteller.filmcraft.png
title: FilmCraft app icon, Linux hicolor theme 128 px
author: Brandon Thomas (FilmCraft owner), for the FilmCraft project
source: original work (drawn by the owner in ArtCraft, then vectorised)
license: MIT OR Apache-2.0
license-file: assets/app-icon/LICENSE.txt
added: 2026-10-02 by Claude (agent) for Brandon Thomas
notes: Rendered from filmcraft.svg with resvg by packaging/icons.sh.

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

View File

@ -0,0 +1,8 @@
asset: ai.storyteller.filmcraft.png
title: FilmCraft app icon, Linux hicolor theme 16 px
author: Brandon Thomas (FilmCraft owner), for the FilmCraft project
source: original work (drawn by the owner in ArtCraft, then vectorised)
license: MIT OR Apache-2.0
license-file: assets/app-icon/LICENSE.txt
added: 2026-10-02 by Claude (agent) for Brandon Thomas
notes: Rendered from filmcraft.svg with resvg by packaging/icons.sh.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,8 @@
asset: ai.storyteller.filmcraft.png
title: FilmCraft app icon, Linux hicolor theme 24 px
author: Brandon Thomas (FilmCraft owner), for the FilmCraft project
source: original work (drawn by the owner in ArtCraft, then vectorised)
license: MIT OR Apache-2.0
license-file: assets/app-icon/LICENSE.txt
added: 2026-10-02 by Claude (agent) for Brandon Thomas
notes: Rendered from filmcraft.svg with resvg by packaging/icons.sh.

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -0,0 +1,8 @@
asset: ai.storyteller.filmcraft.png
title: FilmCraft app icon, Linux hicolor theme 256 px
author: Brandon Thomas (FilmCraft owner), for the FilmCraft project
source: original work (drawn by the owner in ArtCraft, then vectorised)
license: MIT OR Apache-2.0
license-file: assets/app-icon/LICENSE.txt
added: 2026-10-02 by Claude (agent) for Brandon Thomas
notes: Rendered from filmcraft.svg with resvg by packaging/icons.sh.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,8 @@
asset: ai.storyteller.filmcraft.png
title: FilmCraft app icon, Linux hicolor theme 32 px
author: Brandon Thomas (FilmCraft owner), for the FilmCraft project
source: original work (drawn by the owner in ArtCraft, then vectorised)
license: MIT OR Apache-2.0
license-file: assets/app-icon/LICENSE.txt
added: 2026-10-02 by Claude (agent) for Brandon Thomas
notes: Rendered from filmcraft.svg with resvg by packaging/icons.sh.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,8 @@
asset: ai.storyteller.filmcraft.png
title: FilmCraft app icon, Linux hicolor theme 48 px
author: Brandon Thomas (FilmCraft owner), for the FilmCraft project
source: original work (drawn by the owner in ArtCraft, then vectorised)
license: MIT OR Apache-2.0
license-file: assets/app-icon/LICENSE.txt
added: 2026-10-02 by Claude (agent) for Brandon Thomas
notes: Rendered from filmcraft.svg with resvg by packaging/icons.sh.

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

View File

@ -0,0 +1,8 @@
asset: ai.storyteller.filmcraft.png
title: FilmCraft app icon, Linux hicolor theme 512 px
author: Brandon Thomas (FilmCraft owner), for the FilmCraft project
source: original work (drawn by the owner in ArtCraft, then vectorised)
license: MIT OR Apache-2.0
license-file: assets/app-icon/LICENSE.txt
added: 2026-10-02 by Claude (agent) for Brandon Thomas
notes: Rendered from filmcraft.svg with resvg by packaging/icons.sh.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -0,0 +1,8 @@
asset: ai.storyteller.filmcraft.png
title: FilmCraft app icon, Linux hicolor theme 64 px
author: Brandon Thomas (FilmCraft owner), for the FilmCraft project
source: original work (drawn by the owner in ArtCraft, then vectorised)
license: MIT OR Apache-2.0
license-file: assets/app-icon/LICENSE.txt
added: 2026-10-02 by Claude (agent) for Brandon Thomas
notes: Rendered from filmcraft.svg with resvg by packaging/icons.sh.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 444 KiB

View File

@ -0,0 +1,8 @@
asset: ai.storyteller.filmcraft.svg
title: FilmCraft app icon, Linux hicolor theme scalable
author: Brandon Thomas (FilmCraft owner), for the FilmCraft project
source: original work (drawn by the owner in ArtCraft, then vectorised)
license: MIT OR Apache-2.0
license-file: assets/app-icon/LICENSE.txt
added: 2026-10-02 by Claude (agent) for Brandon Thomas
notes: Copy of filmcraft-small.svg, made by packaging/icons.sh.

63
packaging/icons.sh Executable file
View File

@ -0,0 +1,63 @@
#!/usr/bin/env bash
# Regenerate every derived app icon from assets/app-icon/filmcraft.svg (the canonical artwork; a
# 512-unit full-bleed tile with rx=112). filmcraft-small.svg is a lighter trace of the same drawing
# and is copied, not generated.
#
# Needs: resvg (brew install resvg / cargo install resvg). On macOS, iconutil also writes the
# .icns. The outputs are committed, so builds and packaging never need these tools.
#
# packaging/icons.sh
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DIR="$ROOT/assets/app-icon"
SVG="$DIR/filmcraft.svg"
APP_ID="ai.storyteller.filmcraft"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
command -v resvg >/dev/null || { echo "error: resvg not found (brew install resvg)" >&2; exit 1; }
# macOS icons follow Apple's grid: the tile body is 824 of 1024 px with a transparent margin, so
# the Dock shows it at the same size as other apps. 512 * 1024 / 824 = 636.27 units, margin 62.14.
MAC="$TMP/mac.svg"
sed 's/viewBox="0 0 512 512"/viewBox="-62.14 -62.14 636.27 636.27"/' "$SVG" >"$MAC"
grep -q 'viewBox="-62.14' "$MAC" || { echo "error: unexpected viewBox in $SVG" >&2; exit 1; }
render() { resvg -w "$2" -h "$2" "$1" "$3" </dev/null; }
render "$SVG" 1024 "$DIR/filmcraft-1024.png"
# Runtime window/Dock icon on macOS (apps/filmcraft/src/main.rs embeds it).
render "$MAC" 512 "$DIR/filmcraft-macos-512.png"
# Linux hicolor theme. Windows and Linux use the full-bleed tile.
for s in 16 24 32 48 64 128 256 512; do
mkdir -p "$DIR/hicolor/${s}x${s}/apps"
render "$SVG" "$s" "$DIR/hicolor/${s}x${s}/apps/$APP_ID.png"
done
mkdir -p "$DIR/hicolor/scalable/apps"
cp "$DIR/filmcraft-small.svg" "$DIR/hicolor/scalable/apps/$APP_ID.svg"
# Web app favicon and loading image.
render "$SVG" 128 "$ROOT/apps/filmcraft-web/web/favicon.png"
# Windows .ico.
ICO_PNGS=()
for s in 16 20 24 32 40 48 64 128 256; do
render "$SVG" "$s" "$TMP/ico-$s.png"
ICO_PNGS+=("$TMP/ico-$s.png")
done
(cd "$ROOT" && cargo run -q -p xtask -- ico "$DIR/filmcraft.ico" "${ICO_PNGS[@]}")
# macOS .icns.
if command -v iconutil >/dev/null; then
SET="$TMP/filmcraft.iconset"
mkdir -p "$SET"
for s in 16 32 128 256 512; do
render "$MAC" "$s" "$SET/icon_${s}x${s}.png"
render "$MAC" $((s * 2)) "$SET/icon_${s}x${s}@2x.png"
done
iconutil -c icns -o "$DIR/filmcraft.icns" "$SET"
else
echo "warning: iconutil not found (macOS only); filmcraft.icns not regenerated" >&2
fi
echo "icons written to $DIR"

View File

@ -0,0 +1,13 @@
[Desktop Entry]
Type=Application
Name=FilmCraft
GenericName=Video Editor
Comment=Edit video, color and sound
Exec=filmcraft %F
TryExec=filmcraft
Icon=ai.storyteller.filmcraft
Terminal=false
StartupNotify=true
StartupWMClass=ai.storyteller.filmcraft
Categories=AudioVideo;Video;AudioVideoEditing;
Keywords=video;editor;film;timeline;color;grading;nle;

104
xtask/src/ico.rs Normal file
View File

@ -0,0 +1,104 @@
//! `cargo xtask ico <out.ico> <in.png>…`: pack PNG images into a Windows `.ico`.
//!
//! Vista and later read PNG-compressed icon entries, so each PNG is stored as is. The size comes
//! from the PNG's IHDR chunk; 256 px is written as 0, per the ICO format.
use std::path::Path;
/// Width and height from a PNG's IHDR chunk.
pub fn png_size(png: &[u8]) -> Result<(u32, u32), String> {
const SIG: &[u8] = b"\x89PNG\r\n\x1a\n";
if png.len() < 24 || &png[..8] != SIG || &png[12..16] != b"IHDR" {
return Err("not a PNG file".into());
}
let w = u32::from_be_bytes([png[16], png[17], png[18], png[19]]);
let h = u32::from_be_bytes([png[20], png[21], png[22], png[23]]);
Ok((w, h))
}
/// An ICO file holding `pngs`, which must be square and at most 256 px.
pub fn pack(pngs: &[Vec<u8>]) -> Result<Vec<u8>, String> {
if pngs.is_empty() || pngs.len() > u16::MAX as usize {
return Err("need at least one PNG".into());
}
let mut out = Vec::new();
out.extend_from_slice(&0u16.to_le_bytes()); // reserved
out.extend_from_slice(&1u16.to_le_bytes()); // type: icon
out.extend_from_slice(&(pngs.len() as u16).to_le_bytes());
let mut offset = 6 + 16 * pngs.len();
for png in pngs {
let (w, h) = png_size(png)?;
if w != h || w == 0 || w > 256 {
return Err(format!("icon images must be square and at most 256 px, got {w}x{h}"));
}
let dim = if w == 256 { 0 } else { w as u8 };
out.extend_from_slice(&[dim, dim, 0, 0]); // width, height, palette size, reserved
out.extend_from_slice(&1u16.to_le_bytes()); // colour planes
out.extend_from_slice(&32u16.to_le_bytes()); // bits per pixel
out.extend_from_slice(&(png.len() as u32).to_le_bytes());
out.extend_from_slice(&(offset as u32).to_le_bytes());
offset += png.len();
}
for png in pngs {
out.extend_from_slice(png);
}
Ok(out)
}
pub fn run(args: &[&str]) -> Result<(), String> {
let [out, inputs @ ..] = args else {
return Err("usage: cargo xtask ico <out.ico> <in.png>…".into());
};
let pngs = inputs.iter().map(|p| std::fs::read(p).map_err(|e| format!("read {p}: {e}"))).collect::<Result<Vec<_>, _>>()?;
let ico = pack(&pngs)?;
std::fs::write(Path::new(out), &ico).map_err(|e| format!("write {out}: {e}"))?;
println!("{out}: {} images, {} bytes", pngs.len(), ico.len());
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn fake_png(size: u32) -> Vec<u8> {
let mut v = b"\x89PNG\r\n\x1a\n\0\0\0\x0dIHDR".to_vec();
v.extend_from_slice(&size.to_be_bytes());
v.extend_from_slice(&size.to_be_bytes());
v.extend_from_slice(&[8, 6, 0, 0, 0, 1, 2, 3, 4]);
v
}
#[test]
fn packs_directory_and_payloads() {
let a = fake_png(16);
let b = fake_png(256);
let ico = pack(&[a.clone(), b.clone()]).unwrap();
assert_eq!(&ico[..6], &[0, 0, 1, 0, 2, 0]);
// Entry 0: 16 px at offset 6 + 32.
assert_eq!(&ico[6..8], &[16, 16]);
assert_eq!(u32::from_le_bytes(ico[14..18].try_into().unwrap()), a.len() as u32);
assert_eq!(u32::from_le_bytes(ico[18..22].try_into().unwrap()), 38);
// Entry 1: 256 px stored as 0, right after the first payload.
assert_eq!(&ico[22..24], &[0, 0]);
assert_eq!(u32::from_le_bytes(ico[34..38].try_into().unwrap()), 38 + a.len() as u32);
assert_eq!(&ico[38..38 + a.len()], &a[..]);
assert_eq!(&ico[38 + a.len()..], &b[..]);
}
#[test]
fn rejects_bad_input() {
assert!(pack(&[]).is_err());
assert!(pack(&[fake_png(512)]).is_err());
assert!(pack(&[b"GIF89a".to_vec()]).is_err());
assert_eq!(png_size(&fake_png(48)).unwrap(), (48, 48));
}
#[test]
fn committed_icon_is_valid() {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../assets/app-icon/filmcraft.ico");
let Ok(ico) = std::fs::read(&path) else { return };
assert_eq!(&ico[..4], &[0, 0, 1, 0]);
let n = u16::from_le_bytes([ico[4], ico[5]]) as usize;
assert!(n >= 4, "expected several sizes in {}", path.display());
}
}

View File

@ -11,8 +11,11 @@
//! - `fixtures [crate…]`: pre-generate the ffmpeg fixture matrix of the oracle tests (runs each
//! crate's ignored `generate_fixtures` test, i.e. the same generators the tests use) and print
//! what was made, reused or skipped.
//! - `ico <out.ico> <in.png>…`: pack PNGs into a Windows `.ico` (used by `packaging/icons.sh`).
//! - `ci`: fmt check, clippy -D warnings, tests, layers, assets, wasm.
mod ico;
use std::process::{Command, ExitCode};
use serde_json::Value;
@ -346,6 +349,7 @@ fn serve_dir(dir: &std::path::Path, port: u16) -> Result<(), String> {
"js" => "text/javascript",
"wasm" => "application/wasm",
"svg" => "image/svg+xml",
"png" => "image/png",
"json" => "application/json",
"mp4" => "video/mp4",
"webm" => "video/webm",
@ -435,7 +439,11 @@ fn main() -> ExitCode {
"ci" => ci(),
"bench-playback" => bench_playback(),
"web" => web(&std::env::args().skip(2).collect::<Vec<_>>()),
_ => Err("usage: cargo xtask <layers|assets|wasm|web [--dev] [--serve PORT]|fixtures [crate…]|ci|bench-playback [args]>".into()),
"ico" => {
let rest: Vec<String> = std::env::args().skip(2).collect();
ico::run(&rest.iter().map(String::as_str).collect::<Vec<_>>())
}
_ => Err("usage: cargo xtask <layers|assets|wasm|web [--dev] [--serve PORT]|fixtures [crate…]|ico OUT IN…|ci|bench-playback [args]>".into()),
};
match r {
Ok(()) => ExitCode::SUCCESS,