130 lines
4.2 KiB
Python
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)
|