"""Local screen capture via `mss`. This is the practical "eye": run this on the machine that is in the Discord call viewing the shared screen, and it captures that monitor/region. Swap in a discord-web capture later without touching the pipeline. Requires: pip install mss pillow """ from __future__ import annotations import asyncio import io import time from typing import AsyncIterator from ..interfaces import Frame class MSSFrameSource: def __init__( self, *, monitor: int = 1, region: dict | None = None, interval: float = 1.5, max_width: int = 1280, ) -> None: # `region` overrides `monitor`: {"top":.., "left":.., "width":.., "height":..} self.monitor = monitor self.region = region self.interval = interval self.max_width = max_width self._sct = None def _ensure(self): if self._sct is None: import mss # lazy import so mock mode needs no dependency self._sct = mss.mss() return self._sct def _grab_png(self) -> tuple[bytes, int, int]: from PIL import Image sct = self._ensure() area = self.region or sct.monitors[self.monitor] shot = sct.grab(area) img = Image.frombytes("RGB", shot.size, shot.rgb) if img.width > self.max_width: h = int(img.height * self.max_width / img.width) img = img.resize((self.max_width, h)) buf = io.BytesIO() img.save(buf, format="PNG") return buf.getvalue(), img.width, img.height async def frames(self) -> AsyncIterator[Frame]: while True: # mss is blocking; keep the event loop free. data, w, h = await asyncio.to_thread(self._grab_png) yield Frame(data=data, width=w, height=h, ts=time.monotonic(), mime="image/png") await asyncio.sleep(self.interval) async def aclose(self) -> None: if self._sct is not None: self._sct.close() self._sct = None