168 lines
4.3 KiB
Python
168 lines
4.3 KiB
Python
import pyautogui
|
|
import pygetwindow as gw
|
|
|
|
from common.config_loader import loadOcrConfig
|
|
from common.project_paths import RUNTIME_DIR
|
|
|
|
SCREENSHOT_PATH = RUNTIME_DIR / "latest_screenshot.png"
|
|
OCR_SCREENSHOT_PATH = RUNTIME_DIR / "latest_ocr.png"
|
|
|
|
EXCLUDED_WINDOW_KEYWORDS = [
|
|
"Google Chrome",
|
|
"Microsoft Edge",
|
|
"Mozilla Firefox",
|
|
"Visual Studio Code",
|
|
"PowerShell",
|
|
"Windows Terminal",
|
|
"ChatGPT",
|
|
]
|
|
|
|
|
|
def log(level, message):
|
|
from datetime import datetime
|
|
|
|
now = datetime.now().strftime("%H:%M:%S")
|
|
print(f"[{level}] {now} {message}", flush=True)
|
|
|
|
|
|
def findVrchatWindow():
|
|
windows = gw.getAllWindows()
|
|
candidates = []
|
|
|
|
for window in windows:
|
|
title = window.title or ""
|
|
normalized = title.strip().lower()
|
|
if not normalized:
|
|
continue
|
|
if any(keyword.lower() in normalized for keyword in EXCLUDED_WINDOW_KEYWORDS):
|
|
continue
|
|
|
|
if normalized == "vrchat":
|
|
candidates.append((0, window))
|
|
continue
|
|
if normalized.startswith("vrchat "):
|
|
candidates.append((1, window))
|
|
continue
|
|
if normalized.startswith("vrchat"):
|
|
candidates.append((2, window))
|
|
|
|
if not candidates:
|
|
visible_titles = []
|
|
for window in windows:
|
|
title = (window.title or "").strip()
|
|
if title:
|
|
visible_titles.append(title)
|
|
|
|
log("ERROR", "VRChatウィンドウが見つかりません")
|
|
log("CHECK", "現在検出できるウィンドウ")
|
|
for title in visible_titles[:30]:
|
|
print(title, flush=True)
|
|
|
|
raise RuntimeError("VRChatウィンドウが見つかりません")
|
|
|
|
candidates = sorted(
|
|
candidates,
|
|
key=lambda item: (item[0], -(item[1].width * item[1].height)),
|
|
)
|
|
window = candidates[0][1]
|
|
|
|
if window.width <= 0 or window.height <= 0:
|
|
raise RuntimeError("VRChatウィンドウサイズが不正です")
|
|
|
|
return window
|
|
|
|
|
|
def getVrchatRegion():
|
|
window = findVrchatWindow()
|
|
|
|
try:
|
|
if window.isMinimized:
|
|
window.restore()
|
|
except Exception:
|
|
pass
|
|
|
|
left = int(window.left)
|
|
top = int(window.top)
|
|
width = int(window.width)
|
|
height = int(window.height)
|
|
|
|
if width <= 0 or height <= 0:
|
|
raise RuntimeError("VRChatウィンドウ範囲が不正です")
|
|
|
|
log("INFO", f"VRChat window title={window.title}")
|
|
log("INFO", f"VRChat region left={left} top={top} width={width} height={height}")
|
|
return left, top, width, height
|
|
|
|
|
|
def clampRatio(value):
|
|
return max(0.0, min(1.0, value))
|
|
|
|
|
|
def getOcrRegion():
|
|
left, top, width, height = getVrchatRegion()
|
|
config = loadOcrConfig()
|
|
|
|
# Default to the lower central area. It is much faster than OCRing the
|
|
# whole VRChat window and usually contains chat/subtitle text.
|
|
crop_left = clampRatio(config["crop_left"])
|
|
crop_top = clampRatio(config["crop_top"])
|
|
crop_right = clampRatio(config["crop_right"])
|
|
crop_bottom = clampRatio(config["crop_bottom"])
|
|
|
|
if crop_right <= crop_left:
|
|
crop_left = 0.0
|
|
crop_right = 1.0
|
|
|
|
if crop_bottom <= crop_top:
|
|
crop_top = 0.0
|
|
crop_bottom = 1.0
|
|
|
|
region_left = left + int(width * crop_left)
|
|
region_top = top + int(height * crop_top)
|
|
region_width = max(1, int(width * (crop_right - crop_left)))
|
|
region_height = max(1, int(height * (crop_bottom - crop_top)))
|
|
|
|
log(
|
|
"INFO",
|
|
f"OCR crop left={region_left} top={region_top} width={region_width} height={region_height}",
|
|
)
|
|
return region_left, region_top, region_width, region_height
|
|
|
|
|
|
def captureRegionTo(image_path, region):
|
|
image_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
left, top, width, height = region
|
|
|
|
image = pyautogui.screenshot(region=(left, top, width, height))
|
|
image.save(image_path)
|
|
|
|
log("ACTION", f"captureVrchatWindow saved={image_path}")
|
|
return image_path
|
|
|
|
|
|
def captureVrchatWindowTo(image_path):
|
|
return captureRegionTo(
|
|
image_path,
|
|
getVrchatRegion(),
|
|
)
|
|
|
|
|
|
def captureVrchatWindow():
|
|
return captureVrchatWindowTo(SCREENSHOT_PATH)
|
|
|
|
|
|
def captureOcrRegion():
|
|
return captureRegionTo(
|
|
OCR_SCREENSHOT_PATH,
|
|
getOcrRegion(),
|
|
)
|
|
|
|
|
|
def captureScreen():
|
|
return captureVrchatWindow()
|
|
|
|
|
|
def takeScreenshot():
|
|
return captureVrchatWindow()
|