Add Android animation library browsing

This commit is contained in:
2026-10-10 14:51:16 +08:00
parent 5db283c05a
commit c9dd03c23b
39 changed files with 1015 additions and 46 deletions
+33 -3
View File
@@ -1,6 +1,7 @@
"""Explicit mobile capability surface, sharing existing device business operations."""
from __future__ import annotations
import hashlib
import base64
import json
import time
@@ -51,8 +52,8 @@ class MobileControl:
def identity(self):
record = self.identity_record
return dict(device_id=record["device_id"], device_name=record["name"],
short_id=record["device_id"].replace("-", "")[:8], protocol_major=1, protocol_minor=2,
capabilities=["status", "settings", "library", "library_progressive", "playback", "frame", "device_name", "wifi", "wifi_scan", "wifi_transport"],
short_id=record["device_id"].replace("-", "")[:8], protocol_major=1, protocol_minor=3,
capabilities=["status", "settings", "library", "library_progressive", "library_animation", "playback", "frame", "device_name", "wifi", "wifi_scan", "wifi_transport"],
limits=dict(max_message_bytes=65536, library_page_size=50, preview_interval_ms=2000))
def settings(self):
@@ -97,7 +98,7 @@ class MobileControl:
("animation", [demo_animation()] + self.animations.list()["animations"])):
for item in records:
items.append(dict(type=kind, id=item["id"], name=item["name"], revision=item["revision"],
is_demo=bool(item.get("is_demo", False)), playable=kind == "template" or item.get("frame_count", 0) > 0,
frame_count=item.get("frame_count", 0), is_demo=bool(item.get("is_demo", False)), playable=kind == "template" or item.get("frame_count", 0) > 0,
thumbnail_revision=item["revision"]))
if self.library_order is not None:
user_items = [item for item in items if not item["is_demo"]]
@@ -205,6 +206,35 @@ class MobileControl:
next_offset = offset + limit
return dict(library_revision=revision, items=items[offset:next_offset],
next_cursor=f"{revision}:{next_offset}" if next_offset < len(items) else None)
if method in ("library.animation.info", "library.animation.chunk"):
content = self._content(params)
if content["type"] != "animation" or not content["frames"]:
raise RpcError("NOT_FOUND", "内容没有动画帧")
frames = content["frames"]
if method.endswith(".info"):
offset, limit = params.get("offset", 0), params.get("limit", 50)
if type(offset) is not int or not 0 <= offset < len(frames) or type(limit) is not int or not 1 <= limit <= 50:
raise RpcError("BAD_REQUEST", "帧分页参数无效")
end = min(offset + limit, len(frames))
return dict(revision=content["revision"], frame_count=len(frames),
frames=[dict(index=i, duration_ms=frames[i]["duration_ms"]) for i in range(offset, end)],
next_offset=end if end < len(frames) else None)
index, offset, tier = params.get("frame_index"), params.get("offset", 0), params.get("tier")
if type(index) is not int or not 0 <= index < len(frames) or type(offset) is not int or offset < 0 or tier not in ("preview", "full"):
raise RpcError("BAD_REQUEST", "帧读取参数无效")
image = self._preview_image(dict(type="animation", frames=[frames[index]]))
if tier == "preview":
image = image.resize((16, 16), Image.Resampling.LANCZOS)
output = BytesIO()
image.save(output, format="PNG", optimize=True)
data = output.getvalue()
if offset >= len(data):
raise RpcError("BAD_REQUEST", "帧字节偏移无效")
end = min(offset + 4096, len(data))
return dict(revision=content["revision"], tier=tier, frame_index=index, offset=offset,
total_bytes=len(data), sha256=hashlib.sha256(data).hexdigest(),
data_base64=base64.b64encode(data[offset:end]).decode("ascii"),
next_offset=end if end < len(data) else None)
if method == "library.thumbnail":
content = self._content(params)
return self._png(self._preview_image(content))