Adds tools/screencast/ — a reusable screen-recording pipeline for skill demo videos. Wraps a pytest desktop E2E run with: - ScreenRecorder: mss-based full-screen frame capture (background thread) - SubtitleEngine: stdout keyword → timestamped SRT generation - compose_video: FFmpeg filter_complex merge (video + subtitles + BGM) - run_screencast: main orchestrator wiring all three layers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
49 lines
1.6 KiB
Python
49 lines
1.6 KiB
Python
"""全屏截帧引擎,后台线程持续截图存为 PNG 帧序列。"""
|
||
from __future__ import annotations
|
||
|
||
import threading
|
||
import time
|
||
from pathlib import Path
|
||
|
||
import mss
|
||
import mss.tools
|
||
|
||
|
||
class ScreenRecorder:
|
||
def __init__(self, frames_dir: str, fps: int = 10):
|
||
self._frames_dir = Path(frames_dir)
|
||
self._fps = fps
|
||
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:
|
||
monitor = sct.monitors[0] # 全屏(monitor[0] 是所有显示器合并区域)
|
||
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)
|