from __future__ import annotations import base64 import hashlib import json from typing import Any from PIL import Image from app.display.startup_indicator import ( CLOSED_EYE_SECONDS, OPEN_EYE_SECONDS, render_startup_smile, ) from app.templates.store import render_scene_thumbnail, scene_digest DEMO_STATIC_ID = "00000000-0000-4000-8000-000000000101" DEMO_ANIMATION_ID = "00000000-0000-4000-8000-000000000102" DEMO_OPEN_FRAME_ID = "00000000-0000-4000-8000-000000000201" DEMO_CLOSED_FRAME_ID = "00000000-0000-4000-8000-000000000202" DEMO_STATIC_NAME = "演示静态图" DEMO_ANIMATION_NAME = "演示动图" DEMO_RESTRICTED_MESSAGE = "演示案例不得编辑,请复制" DEMO_TIMESTAMP = "1970-01-01T00:00:00+00:00" def _scene(image: Image.Image) -> dict[str, Any]: rgb = image.convert("RGB") return { "version": 1, "width": 64, "height": 64, "pixelRgb": base64.b64encode(rgb.tobytes()).decode("ascii"), "elements": [], } DEMO_OPEN_SCENE = _scene(render_startup_smile(eyes_closed=False)) DEMO_CLOSED_SCENE = _scene(render_startup_smile(eyes_closed=True)) def _revision(value: Any) -> str: canonical = json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) return hashlib.sha256(canonical.encode("utf-8")).hexdigest()[:32] def demo_template(*, include_scene: bool = False) -> dict[str, Any]: digest = scene_digest(DEMO_OPEN_SCENE) item = { "id": DEMO_STATIC_ID, "name": DEMO_STATIC_NAME, "created_at": DEMO_TIMESTAMP, "updated_at": DEMO_TIMESTAMP, "digest": digest, "thumbnail_url": f"/api/templates/{DEMO_STATIC_ID}/thumbnail?v={digest}", "size_bytes": 0, "is_demo": True, "demo_order": 1, "read_only": True, } item["revision"] = _revision({**item, "scene": DEMO_OPEN_SCENE}) if include_scene: item["scene"] = DEMO_OPEN_SCENE return item def _demo_frames(*, include_scenes: bool) -> list[dict[str, Any]]: frames = [] definitions = ( (DEMO_OPEN_FRAME_ID, "睁眼", round(OPEN_EYE_SECONDS * 1000), DEMO_OPEN_SCENE), (DEMO_CLOSED_FRAME_ID, "闭眼", round(CLOSED_EYE_SECONDS * 1000), DEMO_CLOSED_SCENE), ) for position, (frame_id, name, duration_ms, scene) in enumerate(definitions, start=1): digest = scene_digest(scene) frame = { "id": frame_id, "name": name, "position": position, "duration_ms": duration_ms, "created_at": DEMO_TIMESTAMP, "updated_at": DEMO_TIMESTAMP, "digest": digest, "thumbnail_url": ( f"/api/animations/{DEMO_ANIMATION_ID}/frames/{frame_id}/thumbnail?v={digest}" ), } if include_scenes: frame["scene"] = scene frames.append(frame) return frames def demo_animation(*, include_scenes: bool = False) -> dict[str, Any]: frames_with_scenes = _demo_frames(include_scenes=True) revision = _revision({"id": DEMO_ANIMATION_ID, "name": DEMO_ANIMATION_NAME, "frames": frames_with_scenes}) frames = frames_with_scenes if include_scenes else _demo_frames(include_scenes=False) return { "id": DEMO_ANIMATION_ID, "name": DEMO_ANIMATION_NAME, "created_at": DEMO_TIMESTAMP, "updated_at": DEMO_TIMESTAMP, "revision": revision, "frame_count": len(frames), "total_duration_ms": sum(frame["duration_ms"] for frame in frames), "size_bytes": 0, "thumbnail_url": frames[0]["thumbnail_url"], "frames": frames, "is_demo": True, "demo_order": 2, "read_only": True, } def demo_animation_frame(frame_id: str) -> dict[str, Any]: animation = demo_animation(include_scenes=True) for frame in animation["frames"]: if frame["id"] == frame_id: return { "animation_id": DEMO_ANIMATION_ID, "animation_name": DEMO_ANIMATION_NAME, "animation_revision": animation["revision"], **frame, } raise KeyError(frame_id) def demo_thumbnail(scene: dict[str, Any]) -> bytes: return render_scene_thumbnail(scene)