"""把人工演示录制转换成新手机的初始档案;不把任何像素坐标写进配置。""" from __future__ import annotations import json import hashlib import shutil import struct from datetime import datetime, timezone from pathlib import Path from .profiles import save from .paths import ROOT, WORK def png_size(path: Path) -> tuple[int, int]: with path.open("rb") as handle: header = handle.read(24) if header[:8] != b"\x89PNG\r\n\x1a\n" or header[12:16] != b"IHDR": raise ValueError(f"不是有效 PNG:{path}") return struct.unpack(">II", header[16:24]) def import_recording(recording: Path, device: dict[str, str], label: str = "") -> dict: recording = recording.resolve() session_path = recording / "session.json" timeline_path = recording / "timeline.json" if not session_path.exists() or not timeline_path.exists(): raise ValueError("录制目录必须包含 session.json 和 timeline.json") session = json.loads(session_path.read_text(encoding="utf-8")) recorded_device = session.get("device", {}) if recorded_device.get("serial") != device.get("serial"): raise ValueError("录制设备与当前导入设备不一致") timeline = json.loads(timeline_path.read_text(encoding="utf-8")) destination = WORK / "samples" / hashlib.sha256(device["serial"].encode()).hexdigest()[:10] / datetime.now().strftime("%Y%m%d-%H%M%S") destination.mkdir(parents=True, exist_ok=False) samples: dict[str, dict] = {} for entry in timeline.get("snapshots", []): hint = str(entry.get("page_hint") or "other") folder = (recording / str(entry.get("folder") or "")).resolve() if recording not in folder.parents: continue screenshot = folder / "screen.png" if hint in samples or not screenshot.exists(): continue width, height = png_size(screenshot) copied = destination / f"{hint}.png" shutil.copy2(screenshot, copied) samples[hint] = {"snapshot": str(copied.relative_to(ROOT)), "sha256": hashlib.sha256(copied.read_bytes()).hexdigest(), "screen": {"width": width, "height": height}, "captured_at": entry.get("timestamp", "")} profile = { "schema_version": 1, "label": label or device.get("model") or device["serial"], "status": "draft", "device": device, "sampling": {"source": "android_manual_recording", "recorded_at": session.get("started_at", ""), "samples": samples}, "gestures": {"scroll_up": {"from": [0.5, 0.82], "to": [0.5, 0.25], "duration_ms": 350}}, "created_at": datetime.now(timezone.utc).isoformat(), } save(profile) return profile