262 lines
8.9 KiB
Python
262 lines
8.9 KiB
Python
"""
|
|
EU-Utility - OCR Scanner Plugin
|
|
|
|
Reads text from in-game menus using OCR.
|
|
"""
|
|
|
|
import subprocess
|
|
import platform
|
|
import tempfile
|
|
from pathlib import Path
|
|
from PyQt6.QtWidgets import (
|
|
QWidget, QVBoxLayout, QHBoxLayout,
|
|
QLabel, QPushButton, QTextEdit, QComboBox,
|
|
QFrame, QScrollArea, QGroupBox
|
|
)
|
|
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QTimer
|
|
from PyQt6.QtGui import QPixmap, QImage
|
|
|
|
from plugins.base_plugin import BasePlugin
|
|
|
|
|
|
class OCRScannerThread(QThread):
|
|
"""Background thread for OCR scanning."""
|
|
result_ready = pyqtSignal(str)
|
|
error_occurred = pyqtSignal(str)
|
|
|
|
def __init__(self, region=None):
|
|
super().__init__()
|
|
self.region = region # (x, y, width, height)
|
|
|
|
def run(self):
|
|
"""Capture screen and perform OCR."""
|
|
try:
|
|
system = platform.system()
|
|
|
|
# Create temp file for screenshot
|
|
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp:
|
|
screenshot_path = tmp.name
|
|
|
|
# Capture screenshot
|
|
if system == "Windows":
|
|
# Use PowerShell to capture screen
|
|
ps_cmd = f'''
|
|
Add-Type -AssemblyName System.Windows.Forms
|
|
Add-Type -AssemblyName System.Drawing
|
|
$screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
|
|
$bitmap = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height)
|
|
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
|
$graphics.CopyFromScreen($screen.Location, [System.Drawing.Point]::Empty, $screen.Size)
|
|
$bitmap.Save("{screenshot_path}")
|
|
$graphics.Dispose()
|
|
$bitmap.Dispose()
|
|
'''
|
|
subprocess.run(['powershell', '-Command', ps_cmd], capture_output=True, timeout=10)
|
|
|
|
elif system == "Linux":
|
|
# Use gnome-screenshot or import
|
|
try:
|
|
subprocess.run(['gnome-screenshot', '-f', screenshot_path],
|
|
capture_output=True, timeout=10)
|
|
except:
|
|
subprocess.run(['import', '-window', 'root', screenshot_path],
|
|
capture_output=True, timeout=10)
|
|
|
|
# Perform OCR
|
|
text = self._perform_ocr(screenshot_path)
|
|
|
|
# Clean up
|
|
Path(screenshot_path).unlink(missing_ok=True)
|
|
|
|
self.result_ready.emit(text)
|
|
|
|
except Exception as e:
|
|
self.error_occurred.emit(str(e))
|
|
|
|
def _perform_ocr(self, image_path):
|
|
"""Perform OCR on image."""
|
|
try:
|
|
# Try easyocr first
|
|
import easyocr
|
|
reader = easyocr.Reader(['en'])
|
|
results = reader.readtext(image_path)
|
|
text = '\n'.join([result[1] for result in results])
|
|
return text if text else "No text detected"
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
# Try pytesseract
|
|
import pytesseract
|
|
from PIL import Image
|
|
image = Image.open(image_path)
|
|
text = pytesseract.image_to_string(image)
|
|
return text if text.strip() else "No text detected"
|
|
except:
|
|
pass
|
|
|
|
return "OCR not available. Install: pip install easyocr or pytesseract"
|
|
|
|
|
|
class GameReaderPlugin(BasePlugin):
|
|
"""Read in-game menus and text using OCR."""
|
|
|
|
name = "Game Reader"
|
|
version = "1.0.0"
|
|
author = "ImpulsiveFPS"
|
|
description = "OCR scanner for in-game menus and text"
|
|
hotkey = "ctrl+shift+r" # R for Read
|
|
|
|
def initialize(self):
|
|
"""Setup game reader."""
|
|
self.scan_thread = None
|
|
self.last_result = ""
|
|
|
|
def get_ui(self):
|
|
"""Create game reader UI."""
|
|
widget = QWidget()
|
|
widget.setStyleSheet("background: transparent;")
|
|
layout = QVBoxLayout(widget)
|
|
layout.setSpacing(15)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
# Title
|
|
title = QLabel("📷 Game Reader (OCR)")
|
|
title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;")
|
|
layout.addWidget(title)
|
|
|
|
# Info
|
|
info = QLabel("Capture in-game menus and read the text")
|
|
info.setStyleSheet("color: rgba(255, 255, 255, 150); font-size: 12px;")
|
|
layout.addWidget(info)
|
|
|
|
# Scan button
|
|
scan_btn = QPushButton("Capture Screen")
|
|
scan_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #4a9eff;
|
|
color: white;
|
|
padding: 15px;
|
|
border: none;
|
|
border-radius: 10px;
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #5aafff;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #3a8eef;
|
|
}
|
|
""")
|
|
scan_btn.clicked.connect(self._capture_screen)
|
|
layout.addWidget(scan_btn)
|
|
|
|
# Status
|
|
self.status_label = QLabel("Ready to capture")
|
|
self.status_label.setStyleSheet("color: #666; font-size: 11px;")
|
|
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(self.status_label)
|
|
|
|
# Results area
|
|
results_frame = QFrame()
|
|
results_frame.setStyleSheet("""
|
|
QFrame {
|
|
background-color: rgba(0, 0, 0, 50);
|
|
border-radius: 10px;
|
|
border: 1px solid rgba(255, 255, 255, 20);
|
|
}
|
|
""")
|
|
results_layout = QVBoxLayout(results_frame)
|
|
results_layout.setContentsMargins(10, 10, 10, 10)
|
|
|
|
results_label = QLabel("Captured Text:")
|
|
results_label.setStyleSheet("color: rgba(255, 255, 255, 150); font-size: 12px;")
|
|
results_layout.addWidget(results_label)
|
|
|
|
self.result_text = QTextEdit()
|
|
self.result_text.setPlaceholderText("Captured text will appear here...")
|
|
self.result_text.setStyleSheet("""
|
|
QTextEdit {
|
|
background-color: rgba(30, 30, 30, 100);
|
|
color: white;
|
|
border: none;
|
|
border-radius: 6px;
|
|
padding: 8px;
|
|
font-size: 13px;
|
|
}
|
|
""")
|
|
self.result_text.setMaximumHeight(150)
|
|
results_layout.addWidget(self.result_text)
|
|
|
|
# Copy button
|
|
copy_btn = QPushButton("Copy Text")
|
|
copy_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: rgba(255, 255, 255, 20);
|
|
color: white;
|
|
padding: 8px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: rgba(255, 255, 255, 30);
|
|
}
|
|
""")
|
|
copy_btn.clicked.connect(self._copy_text)
|
|
results_layout.addWidget(copy_btn)
|
|
|
|
layout.addWidget(results_frame)
|
|
|
|
# Common uses
|
|
uses_label = QLabel("Common Uses:")
|
|
uses_label.setStyleSheet("color: rgba(255, 255, 255, 150); font-size: 11px; margin-top: 10px;")
|
|
layout.addWidget(uses_label)
|
|
|
|
uses_text = QLabel(
|
|
"• Read NPC dialogue\n"
|
|
"• Capture mission text\n"
|
|
"• Extract item stats\n"
|
|
"• Read shop prices"
|
|
)
|
|
uses_text.setStyleSheet("color: rgba(255, 255, 255, 100); font-size: 11px;")
|
|
layout.addWidget(uses_text)
|
|
|
|
layout.addStretch()
|
|
|
|
return widget
|
|
|
|
def _capture_screen(self):
|
|
"""Capture screen and perform OCR."""
|
|
self.status_label.setText("Capturing...")
|
|
self.status_label.setStyleSheet("color: #4a9eff;")
|
|
|
|
# Start scan thread
|
|
self.scan_thread = OCRScannerThread()
|
|
self.scan_thread.result_ready.connect(self._on_result)
|
|
self.scan_thread.error_occurred.connect(self._on_error)
|
|
self.scan_thread.start()
|
|
|
|
def _on_result(self, text):
|
|
"""Handle OCR result."""
|
|
self.result_text.setText(text)
|
|
self.last_result = text
|
|
self.status_label.setText(f"✅ Captured {len(text)} characters")
|
|
self.status_label.setStyleSheet("color: #4caf50;")
|
|
|
|
def _on_error(self, error):
|
|
"""Handle OCR error."""
|
|
self.status_label.setText(f"❌ Error: {error}")
|
|
self.status_label.setStyleSheet("color: #f44336;")
|
|
|
|
def _copy_text(self):
|
|
"""Copy text to clipboard."""
|
|
from PyQt6.QtWidgets import QApplication
|
|
clipboard = QApplication.clipboard()
|
|
clipboard.setText(self.result_text.toPlainText())
|
|
self.status_label.setText("📋 Copied to clipboard!")
|
|
|
|
def on_hotkey(self):
|
|
"""Capture on hotkey."""
|
|
self._capture_screen()
|