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-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-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 |
|
| `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
|
## Golden test images
|
||||||
|
|
||||||
|
|||||||
@@ -1671,6 +1671,7 @@ dependencies = [
|
|||||||
"rfd",
|
"rfd",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"winit",
|
"winit",
|
||||||
|
"winresource",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5777,10 +5778,12 @@ version = "1.1.6+spec-1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a"
|
checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
"serde_spanned 1.1.1",
|
"serde_spanned 1.1.1",
|
||||||
"toml_datetime 1.1.1+spec-1.1.0",
|
"toml_datetime 1.1.1+spec-1.1.0",
|
||||||
"toml_parser",
|
"toml_parser",
|
||||||
|
"toml_writer",
|
||||||
"winnow 1.0.4",
|
"winnow 1.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5847,6 +5850,12 @@ dependencies = [
|
|||||||
"winnow 1.0.4",
|
"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]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
@@ -6967,6 +6976,16 @@ dependencies = [
|
|||||||
"memchr",
|
"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]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen"
|
||||||
version = "0.57.1"
|
version = "0.57.1"
|
||||||
|
|||||||
@@ -7,6 +7,10 @@
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</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>
|
<h1 align="center">FilmCraft</h1>
|
||||||
|
|
||||||
<p align="center">
|
<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="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="color-scheme" content="dark">
|
<meta name="color-scheme" content="dark">
|
||||||
<title>FilmCraft</title>
|
<title>FilmCraft</title>
|
||||||
<link rel="icon" href="favicon.svg" type="image/svg+xml">
|
<link rel="icon" href="favicon.png" type="image/png">
|
||||||
<style>
|
<style>
|
||||||
html, body { margin: 0; height: 100%; overflow: hidden; background: #1b1c1f; }
|
html, body { margin: 0; height: 100%; overflow: hidden; background: #1b1c1f; }
|
||||||
canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
|
canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<canvas id="filmcraft_canvas"></canvas>
|
<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">
|
<script type="module">
|
||||||
import init, { start } from "./filmcraft_web.js";
|
import init, { start } from "./filmcraft_web.js";
|
||||||
const t0 = performance.now();
|
const t0 = performance.now();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ version.workspace = true
|
|||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
default-run = "filmcraft"
|
default-run = "filmcraft"
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
@@ -32,3 +33,7 @@ objc2-app-kit = { version = "0.3", default-features = false, features = ["std",
|
|||||||
objc2 = "0.6"
|
objc2 = "0.6"
|
||||||
# Same winit as eframe: macOS event-loop options (no app activation when driven by an agent).
|
# Same winit as eframe: macOS event-loop options (no app activation when driven by an agent).
|
||||||
winit = { version = "0.30", default-features = false }
|
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 filmcraft_ui_egui::FilmcraftApp;
|
||||||
use serde_json::json;
|
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 {
|
fn main() -> eframe::Result {
|
||||||
app_nap::disable();
|
app_nap::disable();
|
||||||
let mut control_port: Option<u16> = std::env::var("FILMCRAFT_CONTROL_PORT").ok().and_then(|p| p.parse().ok());
|
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_titlebar_shown(false)
|
||||||
.with_title_shown(false)
|
.with_title_shown(false)
|
||||||
// Started for an agent (`--control`): open without taking the user's keyboard focus.
|
// 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()),
|
event_loop_builder: agent_event_loop(control_port.is_some()),
|
||||||
..Default::default()
|
..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
|
//! - `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
|
//! crate's ignored `generate_fixtures` test, i.e. the same generators the tests use) and print
|
||||||
//! what was made, reused or skipped.
|
//! 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.
|
//! - `ci`: fmt check, clippy -D warnings, tests, layers, assets, wasm.
|
||||||
|
|
||||||
|
mod ico;
|
||||||
|
|
||||||
use std::process::{Command, ExitCode};
|
use std::process::{Command, ExitCode};
|
||||||
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
@@ -346,6 +349,7 @@ fn serve_dir(dir: &std::path::Path, port: u16) -> Result<(), String> {
|
|||||||
"js" => "text/javascript",
|
"js" => "text/javascript",
|
||||||
"wasm" => "application/wasm",
|
"wasm" => "application/wasm",
|
||||||
"svg" => "image/svg+xml",
|
"svg" => "image/svg+xml",
|
||||||
|
"png" => "image/png",
|
||||||
"json" => "application/json",
|
"json" => "application/json",
|
||||||
"mp4" => "video/mp4",
|
"mp4" => "video/mp4",
|
||||||
"webm" => "video/webm",
|
"webm" => "video/webm",
|
||||||
@@ -435,7 +439,11 @@ fn main() -> ExitCode {
|
|||||||
"ci" => ci(),
|
"ci" => ci(),
|
||||||
"bench-playback" => bench_playback(),
|
"bench-playback" => bench_playback(),
|
||||||
"web" => web(&std::env::args().skip(2).collect::<Vec<_>>()),
|
"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 {
|
match r {
|
||||||
Ok(()) => ExitCode::SUCCESS,
|
Ok(()) => ExitCode::SUCCESS,
|
||||||
|
|||||||