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.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
#!/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()
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build the Microsoft Store MSIX package (TASK 48 item 2).
|
||||
#
|
||||
# scripts/package-msix.sh
|
||||
#
|
||||
# Publishes the app self-contained for win-x64 (so the package carries its own .NET
|
||||
# runtime and the ONLY native folders are win-x64 — a RID-less publish copies every
|
||||
# ONNX Runtime native folder), stages the manifest + logos from Packaging/, and packs
|
||||
# with the Windows SDK's makeappx.
|
||||
#
|
||||
# The result is UNSIGNED (the Store signs the submitted package). To produce a package
|
||||
# you can install locally for testing, point MSIX_PFX at a self-signed .pfx:
|
||||
#
|
||||
# MSIX_PFX=/path/to/dev.pfx MSIX_PFX_PASSWORD=... scripts/package-msix.sh
|
||||
#
|
||||
# ⚠️ Before a real submission: set Identity/@Publisher and Properties/P
|
||||
# PublisherDisplayName in Packaging/Package.appxmanifest to the exact values Partner
|
||||
# Center shows, and bump Identity/@Version. A mismatch is a certification rejection.
|
||||
set -euo pipefail
|
||||
|
||||
REPO_WSL="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
REPO_WIN="$(wslpath -w "$REPO_WSL")"
|
||||
DOTNET="/mnt/c/Program Files/dotnet/dotnet.exe"
|
||||
STAGE_WSL="$REPO_WSL/artifacts/msix-stage"
|
||||
OUT_WSL="$REPO_WSL/artifacts/LlamaCasty.msix"
|
||||
|
||||
MAKEAPPX="$(ls -d "/mnt/c/Program Files (x86)/Windows Kits/10/bin/"*/x64/makeappx.exe 2>/dev/null \
|
||||
| sort -V | tail -1 || true)"
|
||||
SIGNTOOL="$(ls -d "/mnt/c/Program Files (x86)/Windows Kits/10/bin/"*/x64/signtool.exe 2>/dev/null \
|
||||
| sort -V | tail -1 || true)"
|
||||
|
||||
[ -x "$MAKEAPPX" ] || { echo "makeappx.exe not found — install the Windows 10/11 SDK." >&2; exit 1; }
|
||||
|
||||
echo "== publish (Release, win-x64, self-contained) =="
|
||||
rm -rf "$STAGE_WSL"
|
||||
mkdir -p "$STAGE_WSL"
|
||||
"$DOTNET" publish "$REPO_WIN\\ytLive.csproj" \
|
||||
-c Release -r win-x64 --self-contained true \
|
||||
--nologo -v minimal \
|
||||
-o "$(wslpath -w "$STAGE_WSL")"
|
||||
|
||||
[ -f "$STAGE_WSL/ytLive.exe" ] || { echo "publish produced no ytLive.exe" >&2; exit 1; }
|
||||
[ -d "$STAGE_WSL/ffmpeg" ] || {
|
||||
echo "ffmpeg/ is missing from the package — it must ship bundled (Store policy 10.2.2)." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "== stage the manifest + logos =="
|
||||
cp "$REPO_WSL/Packaging/Package.appxmanifest" "$STAGE_WSL/AppxManifest.xml"
|
||||
cp -r "$REPO_WSL/Packaging/Assets" "$STAGE_WSL/Assets"
|
||||
|
||||
echo "== pack =="
|
||||
"$MAKEAPPX" pack /d "$(wslpath -w "$STAGE_WSL")" /p "$(wslpath -w "$OUT_WSL")" /o
|
||||
|
||||
if [ -n "${MSIX_PFX:-}" ]; then
|
||||
[ -x "$SIGNTOOL" ] || { echo "signtool.exe not found — install the Windows 10/11 SDK." >&2; exit 1; }
|
||||
echo "== sign =="
|
||||
if [ -n "${MSIX_PFX_PASSWORD:-}" ]; then
|
||||
"$SIGNTOOL" sign /fd SHA256 /a /f "$(wslpath -w "$MSIX_PFX")" /p "$MSIX_PFX_PASSWORD" \
|
||||
"$(wslpath -w "$OUT_WSL")"
|
||||
else
|
||||
"$SIGNTOOL" sign /fd SHA256 /a /f "$(wslpath -w "$MSIX_PFX")" "$(wslpath -w "$OUT_WSL")"
|
||||
fi
|
||||
else
|
||||
echo "note: unsigned package (the Store signs submissions); set MSIX_PFX to sign locally."
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "MSIX: $OUT_WSL ($(du -h "$OUT_WSL" | cut -f1))"
|
||||
Reference in New Issue
Block a user