Files
LlamaCasty/scripts/make-store-logos.py
T
gramps daac7689fd TASK 48 slice 2: the app packs as a Microsoft Store MSIX
The manifest the Store route needs, and a real package built from it.

Packaging/Package.appxmanifest is a full-trust packagedClassicApp: EntryPoint
Windows.FullTrustApplication, uap10:TrustLevel=mediumIL,
uap10:RuntimeBehavior=packagedClassicApp, the rescap runFullTrust capability, and
webcam + microphone DeviceCapabilities. That combination is what lets the packaged
app launch the bundled ffmpeg as a child process and write to the user's profile;
an appContainer package would virtualise the recordings folder instead. x64 only,
en-US only (policy 10.7).

Packaging/Assets/ carries the four tiles the packer requires, generated from the
app's own Assets/llamacasty-logo.jpg by scripts/make-store-logos.py - listing art
and app art cannot drift.

scripts/package-msix.sh publishes Release win-x64 self-contained into
artifacts/msix-stage (a RID-less publish would copy every RID's ONNX native),
stages the manifest as AppxManifest.xml + Assets, and runs the Windows SDK's
makeappx. MSIX_PFX/MSIX_PFX_PASSWORD sign a package for a local test install; the
Store signs submissions. artifacts/ is gitignored.

First real package: artifacts/LlamaCasty.msix, 159 MB, 509 files, 'Package
creation succeeded'.

Schema traps found by packing (makeappx reports one error at a time):
Square310x310Logo and Wide310x150Logo are NOT uap:VisualElements attributes - they
live on uap:DefaultTile.

Guard: StorePackagingTests.The_package_manifest_declares_full_trust_camera_and_
microphone - identity/architecture, the full-trust application element, the three
capabilities, no appContainer VALUE (not the file text: the manifest's own comment
says the word), en-US only, and every referenced logo present.

Full suite 438/438, 0 warnings. Cold-start smoke test after the Velopack removal:
MainWindow loaded, no exceptions.
2026-10-22 09:05:50 -07:00

79 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""Generate the Store logo tiles from the app's own brand artwork.
The MSIX manifest references four images that must exist inside the package
(50x50 StoreLogo, 44x44, 150x150, 310x150 wide). They are derived from
`Assets/llamacasty-logo.jpg` — the same artwork the app shows in the overlay —
so the listing art and the app art cannot drift apart.
The bright mark is located by luminance (the artwork is a dark navy field with a
light mark in the middle), cropped, and re-laid on the artwork's own background
colour so the tiles look like the app.
Run: python3 scripts/make-store-logos.py
"""
import os
from PIL import Image
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SOURCE = os.path.join(ROOT, "Assets", "llamacasty-logo.jpg")
OUT_DIR = os.path.join(ROOT, "Packaging", "Assets")
def background(image: Image.Image) -> tuple:
"""The artwork's own background colour (a corner pixel)."""
return image.getpixel((4, 4))
def mark_crop(image: Image.Image) -> tuple:
"""The bbox of the light mark, found by luminance threshold."""
grey = image.convert("L")
bright = grey.point(lambda p: 255 if p > 190 else 0)
bbox = bright.getbbox()
if bbox is None:
raise SystemExit("no bright mark found in the source artwork")
return bbox
def tile(image: Image.Image, crop: tuple, size: int) -> Image.Image:
"""A square tile: the mark scaled to fit, on the artwork's background."""
canvas = Image.new("RGB", (size, size), background(image))
mark = image.crop(crop)
scale = size / max(mark.size)
mark = mark.resize((max(1, int(mark.width * scale)), max(1, int(mark.height * scale))))
canvas.paste(mark, ((size - mark.width) // 2, (size - mark.height) // 2))
return canvas
def wide(image: Image.Image, crop: tuple, width: int, height: int) -> Image.Image:
"""A wide tile: the mark at the artwork's height, centred on the background."""
canvas = Image.new("RGB", (width, height), background(image))
mark = image.crop(crop)
scale = height / mark.height
mark = mark.resize((max(1, int(mark.width * scale)), height))
canvas.paste(mark, ((width - mark.width) // 2, 0))
return canvas
def main() -> None:
os.makedirs(OUT_DIR, exist_ok=True)
image = Image.open(SOURCE).convert("RGB")
crop = mark_crop(image)
print(f"source {SOURCE} {image.size}, mark at {crop}")
outputs = {
"StoreLogo.png": tile(image, crop, 50),
"Square44x44Logo.png": tile(image, crop, 44),
"Square150x150Logo.png": tile(image, crop, 150),
"Square310x310Logo.png": tile(image, crop, 310),
"Wide310x150Logo.png": wide(image, crop, 310, 150),
}
for name, canvas in outputs.items():
path = os.path.join(OUT_DIR, name)
canvas.save(path, "PNG")
print(f"wrote {os.path.relpath(path, ROOT)} {canvas.size}")
if __name__ == "__main__":
main()