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>
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 17 KiB |
|
|
@ -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.
|
||||
|
|
@ -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 |
|
|
@ -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.
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)");
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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`.
|
||||
|
After Width: | Height: | Size: 391 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 122 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 444 KiB |
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 89 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 1017 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 17 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 685 B |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 57 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 160 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
|
|
@ -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.
|
||||
|
After Width: | Height: | Size: 444 KiB |
|
|
@ -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.
|
||||
|
|
@ -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"
|
||||
|
|
@ -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;
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||