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

125 lines
4.3 KiB
Python

from __future__ import annotations
import argparse
import base64
import json
from pathlib import Path
from typing import Sequence
from app.animations.store import AnimationStore
PRODUCTION_DATA_ROOT = Path("/var/lib/matrix-screen-controller")
REQUIRED_BASENAME_PREFIX = "matrix-screen-controller-preview-perf-"
class FixtureError(RuntimeError):
pass
def validate_empty_test_root(value: str | Path) -> Path:
path = Path(value).expanduser().resolve(strict=True)
if not path.is_dir():
raise FixtureError("pressure-test data root must be an existing directory")
if path == Path(path.anchor) or path == Path.home().resolve():
raise FixtureError("pressure-test data root is too broad")
if path == PRODUCTION_DATA_ROOT or PRODUCTION_DATA_ROOT in path.parents:
raise FixtureError("refusing to use the production data root")
if not path.name.startswith(REQUIRED_BASENAME_PREFIX):
raise FixtureError(
f"pressure-test directory name must start with {REQUIRED_BASENAME_PREFIX!r}"
)
if any(path.iterdir()):
raise FixtureError("pressure-test data root must be empty")
return path
def solid_scene(sequence: int) -> dict[str, object]:
if not 0 <= sequence <= 0xFFFFFF:
raise FixtureError("fixture sequence exceeds unique RGB color space")
color = bytes(((sequence >> 16) & 0xFF, (sequence >> 8) & 0xFF, sequence & 0xFF))
return {
"version": 1,
"width": 64,
"height": 64,
"pixelRgb": base64.b64encode(color * (64 * 64)).decode("ascii"),
"elements": [],
}
def fixture_name(index: int) -> str:
if index % 3 == 0:
return f"压力动图_{index:03d}_" + "很长的中文名称" * 7
if index % 3 == 1:
return f"stress_{index:03d}_" + "abcdefghijklmnopqrstuvwxyz" * 2
return f"混合Stress_{index:03d}_" + "无空格LongName" * 5
def generate_fixture(
data_root: Path,
*,
animation_count: int = 100,
frames_per_animation: int = 100,
duration_ms: int = 50,
) -> dict[str, int | str]:
if not 1 <= animation_count <= 1000:
raise FixtureError("animation count must be within 1..1000")
if not 1 <= frames_per_animation <= 30_000:
raise FixtureError("frames per animation must be within 1..30000")
if not 50 <= duration_ms <= 604_800_000:
raise FixtureError("frame duration must be within 50..604800000ms")
if animation_count * frames_per_animation > 0x1000000:
raise FixtureError("fixture needs more than 24-bit unique solid colors")
store = AnimationStore(data_root)
sequence = 0
for animation_index in range(animation_count):
def frames():
nonlocal sequence
for _frame_index in range(frames_per_animation):
scene = solid_scene(sequence)
sequence += 1
yield {"scene": scene, "name": None, "duration_ms": duration_ms}
store.create_from_frames(fixture_name(animation_index), frames())
return {
"data_root": str(data_root),
"animations": animation_count,
"frames_per_animation": frames_per_animation,
"total_frames": animation_count * frames_per_animation,
"duration_ms": duration_ms,
}
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Generate isolated 64x64 animations for thumbnail performance tests."
)
parser.add_argument("--data-root", required=True)
parser.add_argument("--animations", type=int, default=100)
parser.add_argument("--frames", type=int, default=100)
parser.add_argument("--duration-ms", type=int, default=50)
return parser
def main(argv: Sequence[str] | None = None) -> int:
args = build_parser().parse_args(argv)
try:
root = validate_empty_test_root(args.data_root)
result = generate_fixture(
root,
animation_count=args.animations,
frames_per_animation=args.frames,
duration_ms=args.duration_ms,
)
except (FixtureError, OSError, ValueError) as exc:
print(json.dumps({"ok": False, "error": str(exc)}, ensure_ascii=False))
return 1
print(json.dumps({"ok": True, **result}, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())