Files
matrix-screen-controller/核桃派软件源代码/app/demo_library.py
T

130 lines
4.2 KiB
Python

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)