"""全屏截帧引擎,后台线程持续截图存为 PNG 帧序列。""" from __future__ import annotations import threading import time from pathlib import Path from typing import Optional import mss import mss.tools class ScreenRecorder: def __init__( self, frames_dir: str, fps: int = 10, monitor_index: Optional[int] = None, ): """ Args: monitor_index: mss 显示器索引;None=monitors[0](所有屏合并区域, 旧行为),1=主屏,2+=第 N 屏。多显示器场景推荐显式传 1。 """ self._frames_dir = Path(frames_dir) self._fps = fps self._monitor_index = monitor_index self._stop_event = threading.Event() self._thread: threading.Thread | None = None self._frame_count = 0 def start(self) -> None: self._frames_dir.mkdir(parents=True, exist_ok=True) self._stop_event.clear() self._frame_count = 0 self._thread = threading.Thread(target=self._capture_loop, daemon=True) self._thread.start() def stop(self) -> None: self._stop_event.set() if self._thread: self._thread.join(timeout=10) @property def frame_count(self) -> int: return self._frame_count def _capture_loop(self) -> None: interval = 1.0 / self._fps with mss.mss() as sct: idx = self._monitor_index if self._monitor_index is not None else 0 if idx < 0 or idx >= len(sct.monitors): idx = 0 # 越界兜底 monitor = sct.monitors[idx] while not self._stop_event.is_set(): t0 = time.perf_counter() img = sct.grab(monitor) path = self._frames_dir / f"frame_{self._frame_count:06d}.png" mss.tools.to_png(img.rgb, img.size, output=str(path)) self._frame_count += 1 elapsed = time.perf_counter() - t0 sleep = max(0.0, interval - elapsed) time.sleep(sleep)