GameSentenceMiner 2.7.7__tar.gz → 2.7.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/obs.py +3 -3
- gamesentenceminer-2.7.9/GameSentenceMiner/ocr/gsm_ocr_config.py +42 -0
- gamesentenceminer-2.7.9/GameSentenceMiner/ocr/owocr_area_selector.py +294 -0
- gamesentenceminer-2.7.9/GameSentenceMiner/ocr/owocr_helper.py +336 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/owocr/owocr/run.py +12 -5
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +16 -8
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner.egg-info/PKG-INFO +1 -1
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner.egg-info/SOURCES.txt +1 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/PKG-INFO +1 -1
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/pyproject.toml +1 -1
- gamesentenceminer-2.7.7/GameSentenceMiner/ocr/owocr_area_selector.py +0 -275
- gamesentenceminer-2.7.7/GameSentenceMiner/ocr/owocr_helper.py +0 -310
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/__init__.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/ai/__init__.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/ai/gemini.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/anki.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/communication/__init__.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/communication/send.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/communication/websocket.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/config_gui.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/configuration.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/downloader/Untitled_json.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/downloader/__init__.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/downloader/download_tools.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/electron_config.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/ffmpeg.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/gametext.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/gsm.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/model.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/notification.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/ocr/__init__.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/ocr/ocrconfig.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/owocr/owocr/__init__.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/owocr/owocr/__main__.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/owocr/owocr/config.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/owocr/owocr/ocr.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/package.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/util.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/utility_gui.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/vad/__init__.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/vad/silero_trim.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/vad/vosk_helper.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/vad/whisper_helper.py +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner.egg-info/dependency_links.txt +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner.egg-info/entry_points.txt +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner.egg-info/requires.txt +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner.egg-info/top_level.txt +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/LICENSE +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/README.md +0 -0
- {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/setup.cfg +0 -0
@@ -60,7 +60,7 @@ def get_obs_websocket_config_values():
|
|
60
60
|
|
61
61
|
# Extract values
|
62
62
|
server_enabled = config.get("server_enabled", False)
|
63
|
-
server_port = config.get("server_port",
|
63
|
+
server_port = config.get("server_port", 7274) # Default to 4455 if not set
|
64
64
|
server_password = config.get("server_password", None)
|
65
65
|
|
66
66
|
if not server_enabled:
|
@@ -73,8 +73,8 @@ def get_obs_websocket_config_values():
|
|
73
73
|
if get_config().obs.password == 'your_password':
|
74
74
|
logger.info("OBS WebSocket password is not set. Setting it now...")
|
75
75
|
config = get_master_config()
|
76
|
-
config.
|
77
|
-
config.
|
76
|
+
config.get_config().port = server_port
|
77
|
+
config.get_config().password = server_password
|
78
78
|
with open(get_config_path(), 'w') as file:
|
79
79
|
json.dump(config.to_dict(), file, indent=4)
|
80
80
|
reload_config()
|
@@ -0,0 +1,42 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from dataclasses_json import dataclass_json
|
3
|
+
from typing import List, Optional
|
4
|
+
|
5
|
+
@dataclass_json
|
6
|
+
@dataclass
|
7
|
+
class Monitor:
|
8
|
+
left: int
|
9
|
+
top: int
|
10
|
+
width: int
|
11
|
+
height: int
|
12
|
+
index: int
|
13
|
+
|
14
|
+
@dataclass_json
|
15
|
+
@dataclass
|
16
|
+
class Rectangle:
|
17
|
+
monitor: Monitor
|
18
|
+
coordinates: List[int]
|
19
|
+
is_excluded: bool
|
20
|
+
|
21
|
+
@dataclass_json
|
22
|
+
@dataclass
|
23
|
+
class OCRConfig:
|
24
|
+
scene: str
|
25
|
+
rectangles: List[Rectangle]
|
26
|
+
window: Optional[str] = None
|
27
|
+
|
28
|
+
# Example of how you might use from_dict (assuming you have a dictionary called 'data')
|
29
|
+
data = {
|
30
|
+
"scene": "CODEVEIN",
|
31
|
+
"window": "CODE VEIN",
|
32
|
+
"rectangles": [
|
33
|
+
{
|
34
|
+
"monitor": {"left": 0, "top": 0, "width": 2560, "height": 1440, "index": 0},
|
35
|
+
"coordinates": [749, 1178, 1100, 147],
|
36
|
+
"is_excluded": False,
|
37
|
+
}
|
38
|
+
],
|
39
|
+
}
|
40
|
+
|
41
|
+
config = OCRConfig.from_dict(data)
|
42
|
+
print(config)
|
@@ -0,0 +1,294 @@
|
|
1
|
+
import sys
|
2
|
+
from multiprocessing import Process, Manager
|
3
|
+
import mss
|
4
|
+
from GameSentenceMiner import obs
|
5
|
+
|
6
|
+
from GameSentenceMiner.util import sanitize_filename
|
7
|
+
from PIL import Image, ImageTk
|
8
|
+
import tkinter as tk
|
9
|
+
import json
|
10
|
+
from pathlib import Path
|
11
|
+
import re
|
12
|
+
|
13
|
+
try:
|
14
|
+
import tkinter as tk
|
15
|
+
selector_available = True
|
16
|
+
except:
|
17
|
+
selector_available = False
|
18
|
+
|
19
|
+
MIN_RECT_WIDTH = 25 # Minimum width in pixels
|
20
|
+
MIN_RECT_HEIGHT = 25 # Minimum height in pixels
|
21
|
+
|
22
|
+
class ScreenSelector:
|
23
|
+
def __init__(self, result, window_name):
|
24
|
+
obs.connect_to_obs()
|
25
|
+
self.window_name = window_name
|
26
|
+
print(window_name)
|
27
|
+
self.sct = mss.mss()
|
28
|
+
self.monitors = self.sct.monitors[1:]
|
29
|
+
self.root = None
|
30
|
+
self.result = result
|
31
|
+
self.rectangles = [] # List to store (monitor, coordinates, is_excluded) tuples
|
32
|
+
self.drawn_rect_ids = [] # List to store canvas rectangle IDs
|
33
|
+
self.current_rect_id = None
|
34
|
+
self.start_x = None
|
35
|
+
self.start_y = None
|
36
|
+
self.canvas_windows = {} # Dictionary to store canvas per monitor
|
37
|
+
self.load_existing_rectangles()
|
38
|
+
self.image_mode = True
|
39
|
+
self.monitor_windows = {}
|
40
|
+
self.redo_stack = []
|
41
|
+
|
42
|
+
def get_scene_ocr_config(self):
|
43
|
+
"""Return the path to the OCR config file in GameSentenceMiner/ocr_config."""
|
44
|
+
app_dir = Path.home() / "AppData" / "Roaming" / "GameSentenceMiner"
|
45
|
+
ocr_config_dir = app_dir / "ocr_config"
|
46
|
+
ocr_config_dir.mkdir(parents=True, exist_ok=True)
|
47
|
+
scene = sanitize_filename(obs.get_current_scene())
|
48
|
+
config_path = ocr_config_dir / f"{scene}.json"
|
49
|
+
return config_path
|
50
|
+
|
51
|
+
def load_existing_rectangles(self):
|
52
|
+
config_path = self.get_scene_ocr_config()
|
53
|
+
try:
|
54
|
+
with open(config_path, 'r') as f:
|
55
|
+
config_data = json.load(f)
|
56
|
+
if "rectangles" in config_data:
|
57
|
+
self.rectangles = []
|
58
|
+
for rect_data in config_data["rectangles"]:
|
59
|
+
monitor_data = rect_data.get("monitor")
|
60
|
+
coords = rect_data.get("coordinates")
|
61
|
+
is_excluded = rect_data.get("is_excluded", False)
|
62
|
+
if monitor_data and isinstance(coords, list) and len(coords) == 4:
|
63
|
+
x, y, w, h = coords
|
64
|
+
if w >= MIN_RECT_WIDTH and h >= MIN_RECT_HEIGHT:
|
65
|
+
self.rectangles.append((monitor_data, tuple(coords), is_excluded))
|
66
|
+
else:
|
67
|
+
print(f"Skipping small rectangle from config: {coords}")
|
68
|
+
print(f"Loaded existing rectangles from {config_path}")
|
69
|
+
except FileNotFoundError:
|
70
|
+
print(f"No existing config found at {config_path}")
|
71
|
+
except json.JSONDecodeError:
|
72
|
+
print(f"Error decoding JSON from {config_path}")
|
73
|
+
except Exception as e:
|
74
|
+
print(f"An error occurred while loading rectangles: {e}")
|
75
|
+
|
76
|
+
def save_rects(self, event=None):
|
77
|
+
try:
|
78
|
+
print("Saving rectangles...")
|
79
|
+
config_path = self.get_scene_ocr_config()
|
80
|
+
print(config_path)
|
81
|
+
serializable_rects = []
|
82
|
+
for monitor, coords, is_excluded in self.rectangles:
|
83
|
+
rect_data = {
|
84
|
+
"monitor": monitor,
|
85
|
+
"coordinates": list(coords), # Convert tuple to list for JSON
|
86
|
+
"is_excluded": is_excluded
|
87
|
+
}
|
88
|
+
serializable_rects.append(rect_data)
|
89
|
+
|
90
|
+
print(serializable_rects)
|
91
|
+
with open(config_path, 'w') as f:
|
92
|
+
json.dump({"scene": sanitize_filename(obs.get_current_scene()), "window": self.window_name, "rectangles": serializable_rects}, f, indent=4)
|
93
|
+
print("Rectangles saved.")
|
94
|
+
self.result['rectangles'] = self.rectangles.copy()
|
95
|
+
self.root.destroy()
|
96
|
+
except Exception as e:
|
97
|
+
print(f"Failed to save rectangles: {e}")
|
98
|
+
|
99
|
+
def undo_last_rect(self, event=None):
|
100
|
+
if self.rectangles and self.drawn_rect_ids:
|
101
|
+
last_rect = self.rectangles.pop()
|
102
|
+
last_rect_id = self.drawn_rect_ids.pop()
|
103
|
+
|
104
|
+
monitor, coords, is_excluded = last_rect
|
105
|
+
self.redo_stack.append((monitor, coords, is_excluded, last_rect_id))
|
106
|
+
|
107
|
+
for canvas in self.canvas_windows.values():
|
108
|
+
try:
|
109
|
+
canvas.delete(last_rect_id)
|
110
|
+
except tk.TclError:
|
111
|
+
pass
|
112
|
+
elif self.current_rect_id is not None:
|
113
|
+
for canvas in self.canvas_windows.values():
|
114
|
+
try:
|
115
|
+
canvas.delete(self.current_rect_id)
|
116
|
+
except tk.TclError:
|
117
|
+
pass
|
118
|
+
self.current_rect_id = None
|
119
|
+
self.start_x = None
|
120
|
+
self.start_y = None
|
121
|
+
|
122
|
+
|
123
|
+
def redo_last_rect(self, event=None):
|
124
|
+
if not self.redo_stack:
|
125
|
+
return
|
126
|
+
|
127
|
+
monitor, coords, is_excluded, rect_id = self.redo_stack.pop()
|
128
|
+
canvas = self.canvas_windows.get(monitor['index'])
|
129
|
+
if canvas:
|
130
|
+
x, y, w, h = coords
|
131
|
+
outline_color = 'green' if not is_excluded else 'orange'
|
132
|
+
new_rect_id = canvas.create_rectangle(x, y, x + w, y + h, outline=outline_color, width=2)
|
133
|
+
self.rectangles.append((monitor, coords, is_excluded))
|
134
|
+
self.drawn_rect_ids.append(new_rect_id)
|
135
|
+
|
136
|
+
|
137
|
+
def create_window(self, monitor):
|
138
|
+
screenshot = self.sct.grab(monitor)
|
139
|
+
img = Image.frombytes('RGB', screenshot.size, screenshot.rgb)
|
140
|
+
|
141
|
+
if img.width != monitor['width']:
|
142
|
+
img = img.resize((monitor['width'], monitor['height']), Image.Resampling.LANCZOS)
|
143
|
+
|
144
|
+
AscendingScale = 1.0 # For semi-transparent background
|
145
|
+
window = tk.Toplevel(self.root)
|
146
|
+
window.geometry(f"{monitor['width']}x{monitor['height']}+{monitor['left']}+{monitor['top']}")
|
147
|
+
window.overrideredirect(1)
|
148
|
+
window.attributes('-topmost', 1)
|
149
|
+
|
150
|
+
img_tk = ImageTk.PhotoImage(img)
|
151
|
+
|
152
|
+
canvas = tk.Canvas(window, cursor='cross', highlightthickness=0)
|
153
|
+
canvas.pack(fill=tk.BOTH, expand=True)
|
154
|
+
canvas.image = img_tk
|
155
|
+
canvas.bg_img_id = canvas.create_image(0, 0, image=img_tk, anchor=tk.NW) # Store image ID
|
156
|
+
self.canvas_windows[monitor['index']] = canvas
|
157
|
+
|
158
|
+
# Save monitor and window references for refreshing
|
159
|
+
self.monitor_windows[monitor['index']] = {
|
160
|
+
'window': window,
|
161
|
+
'canvas': canvas,
|
162
|
+
'bg_img_id': canvas.bg_img_id,
|
163
|
+
}
|
164
|
+
|
165
|
+
# Draw existing rectangles for this monitor
|
166
|
+
for mon, coords, is_excluded in self.rectangles:
|
167
|
+
if mon['index'] == monitor['index']:
|
168
|
+
x, y, w, h = coords
|
169
|
+
outline_color = 'green' if not is_excluded else 'orange'
|
170
|
+
rect_id = canvas.create_rectangle(x, y, x + w, y + h, outline=outline_color, width=2)
|
171
|
+
self.drawn_rect_ids.append(rect_id)
|
172
|
+
|
173
|
+
def on_click(event):
|
174
|
+
if self.current_rect_id is None:
|
175
|
+
self.start_x, self.start_y = event.x, event.y
|
176
|
+
outline_color = 'red' if not event.state & 0x0001 else 'purple' # Shift for exclusion
|
177
|
+
self.current_rect_id = canvas.create_rectangle(
|
178
|
+
self.start_x, self.start_y, self.start_x, self.start_y,
|
179
|
+
outline=outline_color, width=2
|
180
|
+
)
|
181
|
+
|
182
|
+
def on_drag(event):
|
183
|
+
if self.current_rect_id:
|
184
|
+
canvas.coords(self.current_rect_id, self.start_x, self.start_y, event.x, event.y)
|
185
|
+
|
186
|
+
def on_release(event):
|
187
|
+
if self.current_rect_id:
|
188
|
+
end_x, end_y = event.x, event.y
|
189
|
+
x1 = min(self.start_x, end_x)
|
190
|
+
y1 = min(self.start_y, end_y)
|
191
|
+
x2 = max(self.start_x, end_x)
|
192
|
+
y2 = max(self.start_y, end_y)
|
193
|
+
width = abs(x2 - x1)
|
194
|
+
height = abs(y2 - y1)
|
195
|
+
if width >= MIN_RECT_WIDTH and height >= MIN_RECT_HEIGHT:
|
196
|
+
is_excluded = bool(event.state & 0x0001) # Shift key for exclusion
|
197
|
+
canvas.itemconfig(self.current_rect_id, outline='green' if not is_excluded else 'orange')
|
198
|
+
self.rectangles.append((monitor, (x1, y1, width, height), is_excluded))
|
199
|
+
self.drawn_rect_ids.append(self.current_rect_id)
|
200
|
+
else:
|
201
|
+
canvas.delete(self.current_rect_id)
|
202
|
+
print(f"Skipping small rectangle: width={width}, height={height}")
|
203
|
+
self.current_rect_id = None
|
204
|
+
self.start_x = None
|
205
|
+
self.start_y = None
|
206
|
+
|
207
|
+
def on_right_click(event):
|
208
|
+
item = canvas.find_closest(event.x, event.y)
|
209
|
+
if item:
|
210
|
+
for idx, rect_id in enumerate(self.drawn_rect_ids):
|
211
|
+
if rect_id == item[0]:
|
212
|
+
canvas.delete(rect_id)
|
213
|
+
# Need to find the corresponding rectangle in self.rectangles and remove it
|
214
|
+
for i, (mon, coords, excluded) in enumerate(self.rectangles):
|
215
|
+
if mon['index'] == monitor['index']:
|
216
|
+
x_r, y_r, w_r, h_r = coords
|
217
|
+
item_coords = canvas.coords(item[0])
|
218
|
+
if x_r == item_coords[0] and y_r == item_coords[1] and x_r + w_r == item_coords[2] and y_r + h_r == item_coords[3]:
|
219
|
+
del self.rectangles[i]
|
220
|
+
break
|
221
|
+
del self.drawn_rect_ids[idx]
|
222
|
+
break
|
223
|
+
|
224
|
+
def toggle_image_mode(event=None):
|
225
|
+
self.image_mode = not self.image_mode
|
226
|
+
if self.image_mode:
|
227
|
+
window.attributes("-alpha", 1.0)
|
228
|
+
else:
|
229
|
+
window.attributes("-alpha", 0.20)
|
230
|
+
|
231
|
+
|
232
|
+
canvas.bind('<ButtonPress-1>', on_click)
|
233
|
+
canvas.bind('<B1-Motion>', on_drag)
|
234
|
+
canvas.bind('<ButtonRelease-1>', on_release)
|
235
|
+
canvas.bind('<Button-3>', on_right_click)
|
236
|
+
window.bind('<Control-s>', self.save_rects)
|
237
|
+
window.bind('<Control-z>', self.undo_last_rect)
|
238
|
+
window.bind('<s>', self.save_rects)
|
239
|
+
window.bind('<z>', self.undo_last_rect)
|
240
|
+
window.bind("<Escape>", self.quit_app)
|
241
|
+
window.bind("<m>", toggle_image_mode)
|
242
|
+
window.bind('<Control-y>', self.redo_last_rect)
|
243
|
+
window.bind('<y>', self.redo_last_rect)
|
244
|
+
|
245
|
+
def start(self):
|
246
|
+
self.root = tk.Tk()
|
247
|
+
self.root.withdraw()
|
248
|
+
|
249
|
+
for monitor in self.monitors:
|
250
|
+
monitor['index'] = self.monitors.index(monitor)
|
251
|
+
self.create_window(monitor)
|
252
|
+
|
253
|
+
self.root.mainloop()
|
254
|
+
|
255
|
+
def quit_app(self, event=None):
|
256
|
+
print("Escape pressed, closing application.")
|
257
|
+
self.on_close()
|
258
|
+
|
259
|
+
def on_close(self):
|
260
|
+
self.root.destroy()
|
261
|
+
|
262
|
+
|
263
|
+
def run_screen_selector(result, window_name):
|
264
|
+
selector = ScreenSelector(result, window_name)
|
265
|
+
selector.start()
|
266
|
+
|
267
|
+
|
268
|
+
def get_screen_selection(window_name):
|
269
|
+
if not selector_available:
|
270
|
+
raise ValueError('tkinter is not installed, unable to open picker')
|
271
|
+
|
272
|
+
with Manager() as manager:
|
273
|
+
res = manager.dict()
|
274
|
+
process = Process(target=run_screen_selector, args=(res,window_name))
|
275
|
+
|
276
|
+
process.start()
|
277
|
+
process.join()
|
278
|
+
|
279
|
+
if 'rectangles' in res:
|
280
|
+
return res.copy()
|
281
|
+
else:
|
282
|
+
return False
|
283
|
+
|
284
|
+
|
285
|
+
if __name__ == "__main__":
|
286
|
+
args = sys.argv[1:]
|
287
|
+
window_name = args[0] if args else None
|
288
|
+
selection = get_screen_selection(window_name)
|
289
|
+
if selection:
|
290
|
+
print("Selected rectangles:")
|
291
|
+
for monitor, coords, is_excluded in selection['rectangles']:
|
292
|
+
print(f"Monitor: {monitor}, Coordinates: {coords}, Excluded: {is_excluded}")
|
293
|
+
else:
|
294
|
+
print("No selection made or process was interrupted.")
|
@@ -0,0 +1,336 @@
|
|
1
|
+
import asyncio
|
2
|
+
import difflib
|
3
|
+
import json
|
4
|
+
import logging
|
5
|
+
import os
|
6
|
+
import queue
|
7
|
+
import threading
|
8
|
+
import time
|
9
|
+
from datetime import datetime
|
10
|
+
from logging.handlers import RotatingFileHandler
|
11
|
+
from pathlib import Path
|
12
|
+
from tkinter import messagebox
|
13
|
+
|
14
|
+
import mss
|
15
|
+
import websockets
|
16
|
+
from PIL import Image, ImageDraw
|
17
|
+
|
18
|
+
from GameSentenceMiner import obs, util
|
19
|
+
from GameSentenceMiner.configuration import get_config, get_app_directory
|
20
|
+
from GameSentenceMiner.electron_config import get_ocr_scan_rate, get_requires_open_window
|
21
|
+
from GameSentenceMiner.ocr.gsm_ocr_config import OCRConfig, Rectangle
|
22
|
+
from GameSentenceMiner.owocr.owocr import screen_coordinate_picker, run
|
23
|
+
from GameSentenceMiner.owocr.owocr.run import TextFiltering
|
24
|
+
|
25
|
+
from dataclasses import dataclass
|
26
|
+
from typing import List, Optional
|
27
|
+
|
28
|
+
CONFIG_FILE = Path("ocr_config.json")
|
29
|
+
DEFAULT_IMAGE_PATH = r"C:\Users\Beangate\Pictures\msedge_acbl8GL7Ax.jpg" # CHANGE THIS
|
30
|
+
logger = logging.getLogger("GSM_OCR")
|
31
|
+
logger.setLevel(logging.DEBUG)
|
32
|
+
# Create a file handler for logging
|
33
|
+
log_file = os.path.join(get_app_directory(), "logs", "ocr_log.txt")
|
34
|
+
os.makedirs(os.path.join(get_app_directory(), "logs"), exist_ok=True)
|
35
|
+
file_handler = RotatingFileHandler(log_file, maxBytes=1024 * 1024, backupCount=5, encoding='utf-8')
|
36
|
+
file_handler.setLevel(logging.DEBUG)
|
37
|
+
# Create a formatter and set it for the handler
|
38
|
+
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
39
|
+
file_handler.setFormatter(formatter)
|
40
|
+
# Add the handler to the logger
|
41
|
+
logger.addHandler(file_handler)
|
42
|
+
|
43
|
+
console_handler = logging.StreamHandler()
|
44
|
+
console_handler.setLevel(logging.INFO)
|
45
|
+
console_handler.setFormatter(formatter)
|
46
|
+
logger.addHandler(console_handler)
|
47
|
+
|
48
|
+
|
49
|
+
def get_new_game_cords():
|
50
|
+
"""Allows multiple coordinate selections."""
|
51
|
+
coords_list = []
|
52
|
+
with mss.mss() as sct:
|
53
|
+
monitors = sct.monitors
|
54
|
+
monitor_map = {i: mon for i, mon in enumerate(monitors)}
|
55
|
+
while True:
|
56
|
+
selected_monitor_index, cords = screen_coordinate_picker.get_screen_selection_with_monitor(monitor_map)
|
57
|
+
selected_monitor = monitor_map[selected_monitor_index]
|
58
|
+
coords_list.append({"monitor": {"left": selected_monitor["left"], "top": selected_monitor["top"],
|
59
|
+
"width": selected_monitor["width"], "height": selected_monitor["height"],
|
60
|
+
"index": selected_monitor_index}, "coordinates": cords,
|
61
|
+
"is_excluded": False})
|
62
|
+
if messagebox.askyesno("Add Another Region", "Do you want to add another region?"):
|
63
|
+
continue
|
64
|
+
else:
|
65
|
+
break
|
66
|
+
app_dir = Path.home() / "AppData" / "Roaming" / "GameSentenceMiner"
|
67
|
+
ocr_config_dir = app_dir / "ocr_config"
|
68
|
+
ocr_config_dir.mkdir(parents=True, exist_ok=True)
|
69
|
+
obs.connect_to_obs()
|
70
|
+
scene = util.sanitize_filename(obs.get_current_scene())
|
71
|
+
config_path = ocr_config_dir / f"{scene}.json"
|
72
|
+
with open(config_path, 'w') as f:
|
73
|
+
json.dump({"scene": scene, "window": None, "rectangles": coords_list}, f, indent=4)
|
74
|
+
print(f"Saved OCR config to {config_path}")
|
75
|
+
return coords_list
|
76
|
+
|
77
|
+
|
78
|
+
def get_ocr_config():
|
79
|
+
"""Loads and updates screen capture areas from the corresponding JSON file."""
|
80
|
+
app_dir = Path.home() / "AppData" / "Roaming" / "GameSentenceMiner"
|
81
|
+
ocr_config_dir = app_dir / "ocr_config"
|
82
|
+
obs.connect_to_obs()
|
83
|
+
scene = util.sanitize_filename(obs.get_current_scene())
|
84
|
+
config_path = ocr_config_dir / f"{scene}.json"
|
85
|
+
if not config_path.exists():
|
86
|
+
raise Exception(f"No config file found at {config_path}.")
|
87
|
+
try:
|
88
|
+
with open(config_path, 'r') as f:
|
89
|
+
config_data = json.load(f)
|
90
|
+
if "rectangles" in config_data and isinstance(config_data["rectangles"], list) and all(
|
91
|
+
isinstance(item, list) and len(item) == 4 for item in config_data["rectangles"]):
|
92
|
+
# Old config format, convert to new
|
93
|
+
new_rectangles = []
|
94
|
+
with mss.mss() as sct:
|
95
|
+
monitors = sct.monitors
|
96
|
+
default_monitor = monitors[1] if len(monitors) > 1 else monitors[0]
|
97
|
+
for rect in config_data["rectangles"]:
|
98
|
+
new_rectangles.append({
|
99
|
+
"monitor": {
|
100
|
+
"left": default_monitor["left"],
|
101
|
+
"top": default_monitor["top"],
|
102
|
+
"width": default_monitor["width"],
|
103
|
+
"height": default_monitor["height"],
|
104
|
+
"index": 0 # Assuming single monitor for old config
|
105
|
+
},
|
106
|
+
"coordinates": rect,
|
107
|
+
"is_excluded": False
|
108
|
+
})
|
109
|
+
for rect in config_data['excluded_rectangles']:
|
110
|
+
new_rectangles.append({
|
111
|
+
"monitor": {
|
112
|
+
"left": default_monitor["left"],
|
113
|
+
"top": default_monitor["top"],
|
114
|
+
"width": default_monitor["width"],
|
115
|
+
"height": default_monitor["height"],
|
116
|
+
"index": 0 # Assuming single monitor for old config
|
117
|
+
},
|
118
|
+
"coordinates": rect,
|
119
|
+
"is_excluded": True
|
120
|
+
})
|
121
|
+
new_config_data = {"scene": config_data.get("scene", scene), "window": config_data.get("window", None),
|
122
|
+
"rectangles": new_rectangles}
|
123
|
+
with open(config_path, 'w') as f:
|
124
|
+
json.dump(new_config_data, f, indent=4)
|
125
|
+
return OCRConfig.from_dict(new_config_data)
|
126
|
+
elif "rectangles" in config_data and isinstance(config_data["rectangles"], list) and all(
|
127
|
+
isinstance(item, dict) and "coordinates" in item for item in config_data["rectangles"]):
|
128
|
+
logger.info("Loading new OCR config format.")
|
129
|
+
logger.info(config_data)
|
130
|
+
return OCRConfig.from_dict(config_data)
|
131
|
+
else:
|
132
|
+
raise Exception(f"Invalid config format in {config_path}.")
|
133
|
+
except json.JSONDecodeError:
|
134
|
+
print("Error decoding JSON. Please check your config file.")
|
135
|
+
return None
|
136
|
+
except Exception as e:
|
137
|
+
print(f"Error loading config: {e}")
|
138
|
+
return None
|
139
|
+
|
140
|
+
|
141
|
+
websocket_server_thread = None
|
142
|
+
websocket_queue = queue.Queue()
|
143
|
+
paused = False
|
144
|
+
|
145
|
+
|
146
|
+
class WebsocketServerThread(threading.Thread):
|
147
|
+
def __init__(self, read):
|
148
|
+
super().__init__(daemon=True)
|
149
|
+
self._loop = None
|
150
|
+
self.read = read
|
151
|
+
self.clients = set()
|
152
|
+
self._event = threading.Event()
|
153
|
+
|
154
|
+
@property
|
155
|
+
def loop(self):
|
156
|
+
self._event.wait()
|
157
|
+
return self._loop
|
158
|
+
|
159
|
+
async def send_text_coroutine(self, message):
|
160
|
+
for client in self.clients:
|
161
|
+
await client.send(message)
|
162
|
+
|
163
|
+
async def server_handler(self, websocket):
|
164
|
+
self.clients.add(websocket)
|
165
|
+
try:
|
166
|
+
async for message in websocket:
|
167
|
+
if self.read and not paused:
|
168
|
+
websocket_queue.put(message)
|
169
|
+
try:
|
170
|
+
await websocket.send('True')
|
171
|
+
except websockets.exceptions.ConnectionClosedOK:
|
172
|
+
pass
|
173
|
+
else:
|
174
|
+
try:
|
175
|
+
await websocket.send('False')
|
176
|
+
except websockets.exceptions.ConnectionClosedOK:
|
177
|
+
pass
|
178
|
+
except websockets.exceptions.ConnectionClosedError:
|
179
|
+
pass
|
180
|
+
finally:
|
181
|
+
self.clients.remove(websocket)
|
182
|
+
|
183
|
+
def send_text(self, text, line_time: datetime):
|
184
|
+
if text:
|
185
|
+
return asyncio.run_coroutine_threadsafe(
|
186
|
+
self.send_text_coroutine(json.dumps({"sentence": text, "time": line_time.isoformat()})), self.loop)
|
187
|
+
|
188
|
+
def stop_server(self):
|
189
|
+
self.loop.call_soon_threadsafe(self._stop_event.set)
|
190
|
+
|
191
|
+
def run(self):
|
192
|
+
async def main():
|
193
|
+
self._loop = asyncio.get_running_loop()
|
194
|
+
self._stop_event = stop_event = asyncio.Event()
|
195
|
+
self._event.set()
|
196
|
+
self.server = start_server = websockets.serve(self.server_handler,
|
197
|
+
get_config().general.websocket_uri.split(":")[0],
|
198
|
+
get_config().general.websocket_uri.split(":")[1],
|
199
|
+
max_size=1000000000)
|
200
|
+
async with start_server:
|
201
|
+
await stop_event.wait()
|
202
|
+
|
203
|
+
asyncio.run(main())
|
204
|
+
|
205
|
+
|
206
|
+
all_cords = None
|
207
|
+
rectangles = None
|
208
|
+
|
209
|
+
|
210
|
+
def text_callback(text, rectangle_index, time, img=None):
|
211
|
+
global twopassocr, ocr2, last_oneocr_results
|
212
|
+
if not text:
|
213
|
+
return
|
214
|
+
if not twopassocr or not ocr2:
|
215
|
+
websocket_server_thread.send_text(text, time if time else datetime.now())
|
216
|
+
return
|
217
|
+
with mss.mss() as sct:
|
218
|
+
line_time = time if time else datetime.now()
|
219
|
+
logger.info(f"Received message: {text}, ATTEMPTING LENS OCR")
|
220
|
+
if rectangles:
|
221
|
+
rect_data: Rectangle = rectangles[rectangle_index]
|
222
|
+
cords = rect_data.coordinates
|
223
|
+
i = rectangle_index
|
224
|
+
else:
|
225
|
+
i = 0
|
226
|
+
mon = sct.monitors
|
227
|
+
cords = [mon[1]['left'], mon[1]['top'], mon[1]['width'], mon[1]['height']]
|
228
|
+
similarity = difflib.SequenceMatcher(None, last_oneocr_results[i], text).ratio()
|
229
|
+
if similarity > .8:
|
230
|
+
return
|
231
|
+
logger.debug(f"Similarity for region {i}: {similarity}")
|
232
|
+
last_oneocr_results[i] = text
|
233
|
+
last_result = ([], -1)
|
234
|
+
try:
|
235
|
+
# sct_params = {'left': cords[0], 'top': cords[1], 'width': cords[2], 'height': cords[3]}
|
236
|
+
# sct_img = sct.grab(sct_params)
|
237
|
+
# img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
|
238
|
+
# img = img.convert("RGBA")
|
239
|
+
# draw = ImageDraw.Draw(img)
|
240
|
+
# excluded_rects = ocr_config.get("excluded_rectangles", [])
|
241
|
+
# if isinstance(excluded_rects, list) and all(
|
242
|
+
# isinstance(item, list) and len(item) == 4 for item in excluded_rects):
|
243
|
+
# for exclusion in excluded_rects:
|
244
|
+
# left, top, right, bottom = exclusion
|
245
|
+
# draw.rectangle((left, top, right, bottom), fill=(0, 0, 0, 0))
|
246
|
+
# elif isinstance(excluded_rects, list) and all(
|
247
|
+
# isinstance(item, dict) and "coordinates" in item and isinstance(item["coordinates"], list) and len(
|
248
|
+
# item["coordinates"]) == 4 for item in excluded_rects):
|
249
|
+
# for exclusion in excluded_rects:
|
250
|
+
# left, top, right, bottom = exclusion["coordinates"]
|
251
|
+
# draw.rectangle((left, top, right, bottom), fill=(0, 0, 0, 0))
|
252
|
+
orig_text, text = run.process_and_write_results(img, None, None, last_result, TextFiltering(),
|
253
|
+
engine=ocr2)
|
254
|
+
if ":gsm_prefix:" in text:
|
255
|
+
text = text.split(":gsm_prefix:")[1]
|
256
|
+
if get_config().advanced.ocr_sends_to_clipboard:
|
257
|
+
import pyperclip
|
258
|
+
pyperclip.copy(text)
|
259
|
+
websocket_server_thread.send_text(text, line_time)
|
260
|
+
except json.JSONDecodeError:
|
261
|
+
print("Invalid JSON received.")
|
262
|
+
except Exception as e:
|
263
|
+
logger.exception(e)
|
264
|
+
print(f"Error processing message: {e}")
|
265
|
+
|
266
|
+
|
267
|
+
done = False
|
268
|
+
|
269
|
+
|
270
|
+
def run_oneocr(ocr_config: OCRConfig, i):
|
271
|
+
global done
|
272
|
+
rect_config = ocr_config.rectangles[i]
|
273
|
+
coords = rect_config.coordinates
|
274
|
+
monitor_config = rect_config.monitor
|
275
|
+
exclusions = (rect.coordinates for rect in list(filter(lambda x: x.is_excluded, ocr_config.rectangles)))
|
276
|
+
screen_area = ",".join(str(c) for c in coords)
|
277
|
+
run.run(read_from="screencapture", write_to="callback",
|
278
|
+
screen_capture_area=screen_area,
|
279
|
+
# screen_capture_monitor=monitor_config['index'],
|
280
|
+
screen_capture_window=ocr_config.window,
|
281
|
+
screen_capture_only_active_windows=get_requires_open_window(),
|
282
|
+
screen_capture_delay_secs=get_ocr_scan_rate(), engine=ocr1,
|
283
|
+
text_callback=text_callback,
|
284
|
+
screen_capture_exclusions=exclusions,
|
285
|
+
rectangle=i)
|
286
|
+
done = True
|
287
|
+
|
288
|
+
|
289
|
+
if __name__ == "__main__":
|
290
|
+
global ocr1, ocr2, twopassocr
|
291
|
+
import sys
|
292
|
+
|
293
|
+
args = sys.argv[1:]
|
294
|
+
if len(args) == 3:
|
295
|
+
ocr1 = args[0]
|
296
|
+
ocr2 = args[1]
|
297
|
+
twopassocr = bool(int(args[2]))
|
298
|
+
elif len(args) == 2:
|
299
|
+
ocr1 = args[0]
|
300
|
+
ocr2 = args[1]
|
301
|
+
twopassocr = True
|
302
|
+
elif len(args) == 1:
|
303
|
+
ocr1 = args[0]
|
304
|
+
ocr2 = None
|
305
|
+
twopassocr = False
|
306
|
+
else:
|
307
|
+
ocr1 = "oneocr"
|
308
|
+
logger.info(f"Received arguments: ocr1={ocr1}, ocr2={ocr2}, twopassocr={twopassocr}")
|
309
|
+
global ocr_config
|
310
|
+
ocr_config: OCRConfig = get_ocr_config()
|
311
|
+
if ocr_config and ocr_config.rectangles:
|
312
|
+
rectangles = ocr_config.rectangles
|
313
|
+
last_oneocr_results = [""] * len(rectangles) if rectangles else [""]
|
314
|
+
oneocr_threads = []
|
315
|
+
run.init_config(False)
|
316
|
+
if rectangles:
|
317
|
+
for i, rectangle in enumerate(rectangles):
|
318
|
+
thread = threading.Thread(target=run_oneocr, args=(ocr_config, i,), daemon=True)
|
319
|
+
oneocr_threads.append(thread)
|
320
|
+
thread.start()
|
321
|
+
else:
|
322
|
+
single_ocr_thread = threading.Thread(target=run_oneocr, args=(ocr_config, 0,), daemon=True)
|
323
|
+
oneocr_threads.append(single_ocr_thread)
|
324
|
+
single_ocr_thread.start()
|
325
|
+
websocket_server_thread = WebsocketServerThread(read=True)
|
326
|
+
websocket_server_thread.start()
|
327
|
+
try:
|
328
|
+
while not done:
|
329
|
+
time.sleep(1)
|
330
|
+
except KeyboardInterrupt as e:
|
331
|
+
pass
|
332
|
+
for thread in oneocr_threads:
|
333
|
+
thread.join()
|
334
|
+
# asyncio.run(websocket_client())
|
335
|
+
else:
|
336
|
+
print("Failed to load OCR configuration. Please check the logs.")
|