GameSentenceMiner 2.7.7__py3-none-any.whl → 2.7.9__py3-none-any.whl
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/obs.py +3 -3
- GameSentenceMiner/ocr/gsm_ocr_config.py +42 -0
- GameSentenceMiner/ocr/owocr_area_selector.py +260 -241
- GameSentenceMiner/ocr/owocr_helper.py +149 -123
- GameSentenceMiner/owocr/owocr/run.py +12 -5
- GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +16 -8
- {gamesentenceminer-2.7.7.dist-info → gamesentenceminer-2.7.9.dist-info}/METADATA +1 -1
- {gamesentenceminer-2.7.7.dist-info → gamesentenceminer-2.7.9.dist-info}/RECORD +12 -11
- {gamesentenceminer-2.7.7.dist-info → gamesentenceminer-2.7.9.dist-info}/WHEEL +1 -1
- {gamesentenceminer-2.7.7.dist-info → gamesentenceminer-2.7.9.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.7.7.dist-info → gamesentenceminer-2.7.9.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.7.7.dist-info → gamesentenceminer-2.7.9.dist-info}/top_level.txt +0 -0
GameSentenceMiner/obs.py
CHANGED
@@ -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)
|
@@ -1,275 +1,294 @@
|
|
1
|
-
import ctypes
|
2
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
|
3
8
|
import tkinter as tk
|
4
|
-
from tkinter import messagebox, simpledialog
|
5
9
|
import json
|
6
10
|
from pathlib import Path
|
7
|
-
import
|
8
|
-
|
9
|
-
|
10
|
-
import
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
self.
|
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
|
24
34
|
self.start_x = None
|
25
35
|
self.start_y = None
|
26
|
-
self.
|
27
|
-
self.
|
28
|
-
self.
|
29
|
-
self.
|
30
|
-
self.
|
31
|
-
self.image_mode = False
|
32
|
-
self.image_item = None
|
33
|
-
self.image_tk = None
|
34
|
-
self.canvas = None
|
35
|
-
|
36
|
-
if not self.initialize_ui():
|
37
|
-
self.after(0, self.destroy)
|
38
|
-
return
|
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 = []
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
self.protocol("WM_DELETE_WINDOW", self.on_close)
|
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
50
|
|
51
|
-
def
|
51
|
+
def load_existing_rectangles(self):
|
52
|
+
config_path = self.get_scene_ocr_config()
|
52
53
|
try:
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
if self.get_scene_ocr_config().exists():
|
66
|
-
try:
|
67
|
-
with open(self.get_scene_ocr_config(), 'r') as f:
|
68
|
-
config_data = json.load(f)
|
69
|
-
loaded_rects = []
|
70
|
-
loaded_excluded_rects = []
|
71
|
-
for r in config_data.get("rectangles", []):
|
72
|
-
try:
|
73
|
-
coords = tuple(map(float, r))
|
74
|
-
if len(coords) == 4:
|
75
|
-
loaded_rects.append(coords)
|
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))
|
76
66
|
else:
|
77
|
-
print(f"Skipping
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
loaded_excluded_rects.append(coords)
|
86
|
-
else:
|
87
|
-
print(f"Skipping invalid excluded rectangle data: {r}")
|
88
|
-
except (ValueError, TypeError) as coord_err:
|
89
|
-
print(f"Skipping invalid coordinate data in excluded rectangle {r}: {coord_err}")
|
90
|
-
|
91
|
-
self.saved_rect_coords = loaded_rects
|
92
|
-
self.excluded_rect_coords = loaded_excluded_rects
|
93
|
-
for rect_id in self.drawn_rect_ids:
|
94
|
-
self.canvas.delete(rect_id)
|
95
|
-
for rect_id in self.drawn_excluded_rect_ids:
|
96
|
-
self.canvas.delete(rect_id)
|
97
|
-
self.drawn_rect_ids = []
|
98
|
-
self.drawn_excluded_rect_ids = []
|
99
|
-
|
100
|
-
for rect in self.saved_rect_coords:
|
101
|
-
x1, y1, x2, y2 = rect
|
102
|
-
rect_id = self.canvas.create_rectangle(x1, y1, x2, y2, outline='blue', width=2)
|
103
|
-
self.drawn_rect_ids.append(rect_id)
|
104
|
-
|
105
|
-
for rect in self.excluded_rect_coords:
|
106
|
-
x1, y1, x2, y2 = rect
|
107
|
-
rect_id = self.canvas.create_rectangle(x1, y1, x2, y2, outline='orange', width=2)
|
108
|
-
self.drawn_excluded_rect_ids.append(rect_id)
|
109
|
-
|
110
|
-
except json.JSONDecodeError:
|
111
|
-
messagebox.showwarning("Config Load Warning", f"Could not parse {self.get_scene_ocr_config()}. Starting with no rectangles.")
|
112
|
-
self.saved_rect_coords = []
|
113
|
-
self.excluded_rect_coords = []
|
114
|
-
except FileNotFoundError:
|
115
|
-
self.saved_rect_coords = []
|
116
|
-
self.excluded_rect_coords = []
|
117
|
-
except Exception as e:
|
118
|
-
messagebox.showerror("Config Load Error", f"Error loading rectangles: {e}")
|
119
|
-
self.saved_rect_coords = []
|
120
|
-
self.excluded_rect_coords = []
|
121
|
-
|
122
|
-
def on_press(self, event):
|
123
|
-
if self.current_rect_id is None:
|
124
|
-
self.start_x = self.canvas.canvasx(event.x)
|
125
|
-
self.start_y = self.canvas.canvasy(event.y)
|
126
|
-
outline_color = 'red' if not event.state & 0x0001 else 'purple' # check if shift is pressed
|
127
|
-
self.current_rect_id = self.canvas.create_rectangle(
|
128
|
-
self.start_x, self.start_y, self.start_x, self.start_y,
|
129
|
-
outline=outline_color, width=2
|
130
|
-
)
|
131
|
-
|
132
|
-
def on_drag(self, event):
|
133
|
-
if self.current_rect_id is not None:
|
134
|
-
cur_x = self.canvas.canvasx(event.x)
|
135
|
-
cur_y = self.canvas.canvasy(event.y)
|
136
|
-
self.canvas.coords(self.current_rect_id, self.start_x, self.start_y, cur_x, cur_y)
|
137
|
-
|
138
|
-
def on_release(self, event):
|
139
|
-
if self.current_rect_id is not None:
|
140
|
-
end_x = self.canvas.canvasx(event.x)
|
141
|
-
end_y = self.canvas.canvasy(event.y)
|
142
|
-
x1 = min(self.start_x, end_x)
|
143
|
-
y1 = min(self.start_y, end_y)
|
144
|
-
x2 = max(self.start_x, end_x)
|
145
|
-
y2 = max(self.start_y, end_y)
|
146
|
-
|
147
|
-
self.canvas.coords(self.current_rect_id, x1, y1, x2, y2)
|
148
|
-
if event.state & 0x0001: # shift is pressed
|
149
|
-
self.canvas.itemconfig(self.current_rect_id, outline='orange')
|
150
|
-
self.excluded_rect_coords.append((x1, y1, x2, y2))
|
151
|
-
self.drawn_excluded_rect_ids.append(self.current_rect_id)
|
152
|
-
else:
|
153
|
-
self.canvas.itemconfig(self.current_rect_id, outline='green')
|
154
|
-
self.saved_rect_coords.append((x1, y1, x2, y2))
|
155
|
-
self.drawn_rect_ids.append(self.current_rect_id)
|
156
|
-
|
157
|
-
self.current_rect_id = None
|
158
|
-
self.start_x = None
|
159
|
-
self.start_y = None
|
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}")
|
160
75
|
|
161
|
-
def save_rects(self):
|
76
|
+
def save_rects(self, event=None):
|
162
77
|
try:
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
if self.canvas.winfo_exists():
|
179
|
-
try:
|
180
|
-
self.canvas.itemconfig(rect_id, outline='orange')
|
181
|
-
except tk.TclError:
|
182
|
-
pass
|
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)
|
183
93
|
print("Rectangles saved.")
|
184
|
-
self.
|
94
|
+
self.result['rectangles'] = self.rectangles.copy()
|
95
|
+
self.root.destroy()
|
185
96
|
except Exception as e:
|
186
|
-
|
187
|
-
|
188
|
-
def save_rects_event(self, event=None):
|
189
|
-
self.save_rects()
|
97
|
+
print(f"Failed to save rectangles: {e}")
|
190
98
|
|
191
99
|
def undo_last_rect(self, event=None):
|
192
|
-
if self.
|
193
|
-
self.
|
100
|
+
if self.rectangles and self.drawn_rect_ids:
|
101
|
+
last_rect = self.rectangles.pop()
|
194
102
|
last_rect_id = self.drawn_rect_ids.pop()
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
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
|
202
112
|
elif self.current_rect_id is not None:
|
203
|
-
|
204
|
-
|
113
|
+
for canvas in self.canvas_windows.values():
|
114
|
+
try:
|
115
|
+
canvas.delete(self.current_rect_id)
|
116
|
+
except tk.TclError:
|
117
|
+
pass
|
205
118
|
self.current_rect_id = None
|
206
119
|
self.start_x = None
|
207
120
|
self.start_y = None
|
208
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
|
+
|
209
255
|
def quit_app(self, event=None):
|
210
256
|
print("Escape pressed, closing application.")
|
211
257
|
self.on_close()
|
212
258
|
|
213
259
|
def on_close(self):
|
214
|
-
self.destroy()
|
260
|
+
self.root.destroy()
|
215
261
|
|
216
|
-
def get_scene_ocr_config(self):
|
217
|
-
app_dir = Path.home() / "AppData" / "Roaming" / "GameSentenceMiner"
|
218
|
-
ocr_config_dir = app_dir / "ocr_config"
|
219
|
-
ocr_config_dir.mkdir(parents=True, exist_ok=True)
|
220
|
-
scene = util.sanitize_filename(obs.get_current_scene())
|
221
|
-
config_path = ocr_config_dir / f"{scene}.json"
|
222
|
-
return config_path
|
223
262
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
if item:
|
229
|
-
if item[0] in self.drawn_rect_ids: # Check if it's a saved rectangle
|
230
|
-
index = self.drawn_rect_ids.index(item[0])
|
231
|
-
self.canvas.delete(item[0])
|
232
|
-
del self.drawn_rect_ids[index]
|
233
|
-
del self.saved_rect_coords[index]
|
234
|
-
elif item[0] in self.drawn_excluded_rect_ids:
|
235
|
-
index = self.drawn_excluded_rect_ids.index(item[0])
|
236
|
-
self.canvas.delete(item[0])
|
237
|
-
del self.drawn_excluded_rect_ids[index]
|
238
|
-
del self.excluded_rect_coords[index]
|
239
|
-
|
240
|
-
def setup_hotkey(self):
|
241
|
-
keyboard.add_hotkey('F13', self.lift_window) # Example hotkey
|
242
|
-
|
243
|
-
def lift_window(self):
|
244
|
-
self.lift() # Bring the window to the front
|
245
|
-
|
246
|
-
def toggle_image_mode(self, event=None):
|
247
|
-
self.image_mode = not self.image_mode
|
248
|
-
if self.image_mode:
|
249
|
-
self.attributes("-alpha", 1.0)
|
250
|
-
self.load_image_from_obs()
|
251
|
-
else:
|
252
|
-
self.attributes("-alpha", 0.20)
|
253
|
-
if self.image_item:
|
254
|
-
self.canvas.delete(self.image_item)
|
255
|
-
self.image_item = None
|
256
|
-
self.image_tk = None
|
263
|
+
def run_screen_selector(result, window_name):
|
264
|
+
selector = ScreenSelector(result, window_name)
|
265
|
+
selector.start()
|
257
266
|
|
258
|
-
def load_image_from_obs(self):
|
259
|
-
try:
|
260
|
-
image_path = obs.get_screenshot()
|
261
|
-
image = Image.open(image_path)
|
262
|
-
self.image_tk = ImageTk.PhotoImage(image)
|
263
|
-
self.image_item = self.canvas.create_image(0, 0, anchor=tk.NW, image=self.image_tk)
|
264
|
-
self.canvas.tag_lower(self.image_item)
|
265
|
-
except Exception as e:
|
266
|
-
messagebox.showerror("Image Load Error", f"Failed to load image from OBS: {e}")
|
267
267
|
|
268
|
-
def
|
269
|
-
|
270
|
-
|
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
|
+
|
271
284
|
|
272
285
|
if __name__ == "__main__":
|
273
286
|
args = sys.argv[1:]
|
274
|
-
|
275
|
-
|
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.")
|
@@ -1,7 +1,6 @@
|
|
1
|
-
# area_selector.py
|
2
1
|
import asyncio
|
3
|
-
import base64
|
4
2
|
import difflib
|
3
|
+
import json
|
5
4
|
import logging
|
6
5
|
import os
|
7
6
|
import queue
|
@@ -9,52 +8,61 @@ import threading
|
|
9
8
|
import time
|
10
9
|
from datetime import datetime
|
11
10
|
from logging.handlers import RotatingFileHandler
|
11
|
+
from pathlib import Path
|
12
12
|
from tkinter import messagebox
|
13
13
|
|
14
14
|
import mss
|
15
15
|
import websockets
|
16
16
|
from PIL import Image, ImageDraw
|
17
|
-
import json
|
18
|
-
from pathlib import Path
|
19
17
|
|
20
18
|
from GameSentenceMiner import obs, util
|
21
19
|
from GameSentenceMiner.configuration import get_config, get_app_directory
|
22
|
-
from GameSentenceMiner.
|
20
|
+
from GameSentenceMiner.electron_config import get_ocr_scan_rate, get_requires_open_window
|
21
|
+
from GameSentenceMiner.ocr.gsm_ocr_config import OCRConfig, Rectangle
|
23
22
|
from GameSentenceMiner.owocr.owocr import screen_coordinate_picker, run
|
24
23
|
from GameSentenceMiner.owocr.owocr.run import TextFiltering
|
25
|
-
|
24
|
+
|
25
|
+
from dataclasses import dataclass
|
26
|
+
from typing import List, Optional
|
26
27
|
|
27
28
|
CONFIG_FILE = Path("ocr_config.json")
|
28
29
|
DEFAULT_IMAGE_PATH = r"C:\Users\Beangate\Pictures\msedge_acbl8GL7Ax.jpg" # CHANGE THIS
|
29
|
-
|
30
30
|
logger = logging.getLogger("GSM_OCR")
|
31
31
|
logger.setLevel(logging.DEBUG)
|
32
|
-
|
33
32
|
# Create a file handler for logging
|
34
33
|
log_file = os.path.join(get_app_directory(), "logs", "ocr_log.txt")
|
35
34
|
os.makedirs(os.path.join(get_app_directory(), "logs"), exist_ok=True)
|
36
|
-
|
37
35
|
file_handler = RotatingFileHandler(log_file, maxBytes=1024 * 1024, backupCount=5, encoding='utf-8')
|
38
36
|
file_handler.setLevel(logging.DEBUG)
|
39
|
-
|
40
37
|
# Create a formatter and set it for the handler
|
41
38
|
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
42
39
|
file_handler.setFormatter(formatter)
|
43
|
-
|
44
40
|
# Add the handler to the logger
|
45
41
|
logger.addHandler(file_handler)
|
46
42
|
|
43
|
+
console_handler = logging.StreamHandler()
|
44
|
+
console_handler.setLevel(logging.INFO)
|
45
|
+
console_handler.setFormatter(formatter)
|
46
|
+
logger.addHandler(console_handler)
|
47
|
+
|
48
|
+
|
47
49
|
def get_new_game_cords():
|
48
50
|
"""Allows multiple coordinate selections."""
|
49
51
|
coords_list = []
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
58
66
|
app_dir = Path.home() / "AppData" / "Roaming" / "GameSentenceMiner"
|
59
67
|
ocr_config_dir = app_dir / "ocr_config"
|
60
68
|
ocr_config_dir.mkdir(parents=True, exist_ok=True)
|
@@ -62,13 +70,13 @@ def get_new_game_cords():
|
|
62
70
|
scene = util.sanitize_filename(obs.get_current_scene())
|
63
71
|
config_path = ocr_config_dir / f"{scene}.json"
|
64
72
|
with open(config_path, 'w') as f:
|
65
|
-
json.dump(coords_list, f, indent=4)
|
73
|
+
json.dump({"scene": scene, "window": None, "rectangles": coords_list}, f, indent=4)
|
66
74
|
print(f"Saved OCR config to {config_path}")
|
67
75
|
return coords_list
|
68
76
|
|
69
77
|
|
70
78
|
def get_ocr_config():
|
71
|
-
"""Loads
|
79
|
+
"""Loads and updates screen capture areas from the corresponding JSON file."""
|
72
80
|
app_dir = Path.home() / "AppData" / "Roaming" / "GameSentenceMiner"
|
73
81
|
ocr_config_dir = app_dir / "ocr_config"
|
74
82
|
obs.connect_to_obs()
|
@@ -76,14 +84,58 @@ def get_ocr_config():
|
|
76
84
|
config_path = ocr_config_dir / f"{scene}.json"
|
77
85
|
if not config_path.exists():
|
78
86
|
raise Exception(f"No config file found at {config_path}.")
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
87
139
|
|
88
140
|
|
89
141
|
websocket_server_thread = None
|
@@ -130,7 +182,8 @@ class WebsocketServerThread(threading.Thread):
|
|
130
182
|
|
131
183
|
def send_text(self, text, line_time: datetime):
|
132
184
|
if text:
|
133
|
-
return asyncio.run_coroutine_threadsafe(
|
185
|
+
return asyncio.run_coroutine_threadsafe(
|
186
|
+
self.send_text_coroutine(json.dumps({"sentence": text, "time": line_time.isoformat()})), self.loop)
|
134
187
|
|
135
188
|
def stop_server(self):
|
136
189
|
self.loop.call_soon_threadsafe(self._stop_event.set)
|
@@ -140,15 +193,21 @@ class WebsocketServerThread(threading.Thread):
|
|
140
193
|
self._loop = asyncio.get_running_loop()
|
141
194
|
self._stop_event = stop_event = asyncio.Event()
|
142
195
|
self._event.set()
|
143
|
-
self.server = start_server = websockets.serve(self.server_handler,
|
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)
|
144
200
|
async with start_server:
|
145
201
|
await stop_event.wait()
|
202
|
+
|
146
203
|
asyncio.run(main())
|
147
204
|
|
205
|
+
|
148
206
|
all_cords = None
|
149
207
|
rectangles = None
|
150
208
|
|
151
|
-
|
209
|
+
|
210
|
+
def text_callback(text, rectangle_index, time, img=None):
|
152
211
|
global twopassocr, ocr2, last_oneocr_results
|
153
212
|
if not text:
|
154
213
|
return
|
@@ -159,8 +218,9 @@ def text_callback(text, rectangle, time):
|
|
159
218
|
line_time = time if time else datetime.now()
|
160
219
|
logger.info(f"Received message: {text}, ATTEMPTING LENS OCR")
|
161
220
|
if rectangles:
|
162
|
-
|
163
|
-
|
221
|
+
rect_data: Rectangle = rectangles[rectangle_index]
|
222
|
+
cords = rect_data.coordinates
|
223
|
+
i = rectangle_index
|
164
224
|
else:
|
165
225
|
i = 0
|
166
226
|
mon = sct.monitors
|
@@ -172,15 +232,23 @@ def text_callback(text, rectangle, time):
|
|
172
232
|
last_oneocr_results[i] = text
|
173
233
|
last_result = ([], -1)
|
174
234
|
try:
|
175
|
-
sct_params = {'left': cords[0], 'top': cords[1], 'width': cords[2], 'height': cords[3]}
|
176
|
-
sct_img = sct.grab(sct_params)
|
177
|
-
img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
|
178
|
-
img = img.convert("RGBA")
|
179
|
-
draw = ImageDraw.Draw(img)
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
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))
|
184
252
|
orig_text, text = run.process_and_write_results(img, None, None, last_result, TextFiltering(),
|
185
253
|
engine=ocr2)
|
186
254
|
if ":gsm_prefix:" in text:
|
@@ -195,74 +263,34 @@ def text_callback(text, rectangle, time):
|
|
195
263
|
logger.exception(e)
|
196
264
|
print(f"Error processing message: {e}")
|
197
265
|
|
266
|
+
|
198
267
|
done = False
|
199
268
|
|
200
|
-
|
269
|
+
|
270
|
+
def run_oneocr(ocr_config: OCRConfig, i):
|
201
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)
|
202
277
|
run.run(read_from="screencapture", write_to="callback",
|
203
|
-
screen_capture_area=
|
204
|
-
|
278
|
+
screen_capture_area=screen_area,
|
279
|
+
# screen_capture_monitor=monitor_config['index'],
|
280
|
+
screen_capture_window=ocr_config.window,
|
205
281
|
screen_capture_only_active_windows=get_requires_open_window(),
|
206
282
|
screen_capture_delay_secs=get_ocr_scan_rate(), engine=ocr1,
|
207
283
|
text_callback=text_callback,
|
208
|
-
screen_capture_exclusions=
|
284
|
+
screen_capture_exclusions=exclusions,
|
209
285
|
rectangle=i)
|
210
286
|
done = True
|
211
287
|
|
212
288
|
|
213
|
-
# async def websocket_client():
|
214
|
-
# uri = "ws://localhost:7331" # Replace with your hosted websocket address
|
215
|
-
# print("Connecting to WebSocket...")
|
216
|
-
# async with websockets.connect(uri) as websocket:
|
217
|
-
# print("Connected to WebSocket.")
|
218
|
-
#
|
219
|
-
# try:
|
220
|
-
# while True:
|
221
|
-
# message = await websocket.recv()
|
222
|
-
# if not message:
|
223
|
-
# continue
|
224
|
-
# line_time = datetime.now()
|
225
|
-
# get_line_history().add_secondary_line(message)
|
226
|
-
# print(f"Received message: {message}, ATTEMPTING LENS OCR")
|
227
|
-
# if ":gsm_prefix:" in message:
|
228
|
-
# i = int(message.split(":gsm_prefix:")[0])
|
229
|
-
# cords = all_cords[i] if i else all_cords[0]
|
230
|
-
# similarity = difflib.SequenceMatcher(None, last_oneocr_results[i], message).ratio()
|
231
|
-
# if similarity > .8:
|
232
|
-
# continue
|
233
|
-
# print(f"Similarity for region {i}: {similarity}")
|
234
|
-
# last_oneocr_results[i] = message
|
235
|
-
# last_result = ([], -1)
|
236
|
-
# try:
|
237
|
-
# sct_params = {'top': cords[1], 'left': cords[0], 'width': cords[2], 'height': cords[3]}
|
238
|
-
# with mss.mss() as sct:
|
239
|
-
# sct_img = sct.grab(sct_params)
|
240
|
-
# img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
|
241
|
-
# draw = ImageDraw.Draw(img)
|
242
|
-
# for exclusion in ocr_config.get("excluded_rectangles", []):
|
243
|
-
# exclusion = tuple(exclusion)
|
244
|
-
# draw.rectangle(exclusion, fill="black")
|
245
|
-
# orig_text, text = run.process_and_write_results(img, "results.txt", None, last_result, TextFiltering(), engine="glens")
|
246
|
-
# if ":gsm_prefix:" in text:
|
247
|
-
# text = text.split(":gsm_prefix:")[1]
|
248
|
-
# websocket_server_thread.send_text(text, line_time)
|
249
|
-
# except json.JSONDecodeError:
|
250
|
-
# print("Invalid JSON received.")
|
251
|
-
# except Exception as e:
|
252
|
-
# logger.exception(e)
|
253
|
-
# print(f"Error processing message: {e}")
|
254
|
-
# except websockets.exceptions.ConnectionClosed:
|
255
|
-
# print("WebSocket connection closed.")
|
256
|
-
# except Exception as e:
|
257
|
-
# print(f"WebSocket error: {e}")
|
258
|
-
|
259
|
-
|
260
289
|
if __name__ == "__main__":
|
261
290
|
global ocr1, ocr2, twopassocr
|
262
291
|
import sys
|
263
292
|
|
264
293
|
args = sys.argv[1:]
|
265
|
-
|
266
294
|
if len(args) == 3:
|
267
295
|
ocr1 = args[0]
|
268
296
|
ocr2 = args[1]
|
@@ -277,34 +305,32 @@ if __name__ == "__main__":
|
|
277
305
|
twopassocr = False
|
278
306
|
else:
|
279
307
|
ocr1 = "oneocr"
|
280
|
-
|
281
308
|
logger.info(f"Received arguments: ocr1={ocr1}, ocr2={ocr2}, twopassocr={twopassocr}")
|
282
309
|
global ocr_config
|
283
|
-
ocr_config = get_ocr_config()
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
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())
|
293
335
|
else:
|
294
|
-
|
295
|
-
oneocr_threads.append(single_ocr_thread)
|
296
|
-
single_ocr_thread.start()
|
297
|
-
|
298
|
-
websocket_server_thread = WebsocketServerThread(read=True)
|
299
|
-
websocket_server_thread.start()
|
300
|
-
|
301
|
-
try:
|
302
|
-
while not done:
|
303
|
-
time.sleep(1)
|
304
|
-
except KeyboardInterrupt as e:
|
305
|
-
pass
|
306
|
-
|
307
|
-
for thread in oneocr_threads:
|
308
|
-
thread.join()
|
309
|
-
|
310
|
-
# asyncio.run(websocket_client())
|
336
|
+
print("Failed to load OCR configuration. Please check the logs.")
|
@@ -571,7 +571,7 @@ def process_and_write_results(img_or_path, write_to, notifications, last_result,
|
|
571
571
|
elif write_to == 'clipboard':
|
572
572
|
pyperclipfix.copy(text)
|
573
573
|
elif write_to == "callback":
|
574
|
-
txt_callback(text, rectangle, start_time)
|
574
|
+
txt_callback(text, rectangle, start_time, img_or_path)
|
575
575
|
elif write_to:
|
576
576
|
with Path(write_to).open('a', encoding='utf-8') as f:
|
577
577
|
f.write(text + '\n')
|
@@ -1026,6 +1026,7 @@ def run(read_from=None,
|
|
1026
1026
|
else:
|
1027
1027
|
try:
|
1028
1028
|
coord_left, coord_top, right, bottom = win32gui.GetWindowRect(window_handle)
|
1029
|
+
|
1029
1030
|
coord_width = right - coord_left
|
1030
1031
|
coord_height = bottom - coord_top
|
1031
1032
|
|
@@ -1045,12 +1046,18 @@ def run(read_from=None,
|
|
1045
1046
|
on_window_closed(False)
|
1046
1047
|
break
|
1047
1048
|
|
1048
|
-
rand = random.randint(1, 10)
|
1049
|
+
# rand = random.randint(1, 10)
|
1049
1050
|
|
1050
1051
|
img = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1)
|
1051
1052
|
|
1053
|
+
# if rand == 1:
|
1054
|
+
# img.show()
|
1055
|
+
|
1052
1056
|
if custom_left:
|
1053
|
-
img = img.crop((custom_left, custom_top, custom_width, custom_height))
|
1057
|
+
img = img.crop((custom_left, custom_top, custom_left + custom_width, custom_top + custom_height))
|
1058
|
+
|
1059
|
+
# if rand == 1:
|
1060
|
+
# img.show()
|
1054
1061
|
|
1055
1062
|
win32gui.DeleteObject(save_bitmap.GetHandle())
|
1056
1063
|
save_dc.DeleteDC()
|
@@ -1064,8 +1071,8 @@ def run(read_from=None,
|
|
1064
1071
|
img = img.convert("RGBA")
|
1065
1072
|
draw = ImageDraw.Draw(img)
|
1066
1073
|
for exclusion in screen_capture_exclusions:
|
1067
|
-
left, top,
|
1068
|
-
draw.rectangle((left, top,
|
1074
|
+
left, top, width, height = exclusion
|
1075
|
+
draw.rectangle((left, top, left + width, top + height), fill=(0, 0, 0, 0))
|
1069
1076
|
# draw.rectangle((left, top, right, bottom), fill=(0, 0, 0))
|
1070
1077
|
# img.save(os.path.join(get_temporary_directory(), 'screencapture.png'), 'png')
|
1071
1078
|
res, _ = process_and_write_results(img, write_to, notifications, last_result, filtering, rectangle=rectangle, start_time=start_time)
|
@@ -55,12 +55,12 @@ class ScreenSelector:
|
|
55
55
|
def on_release(event):
|
56
56
|
nonlocal start_x, start_y
|
57
57
|
end_x, end_y = event.x, event.y
|
58
|
-
|
59
|
-
x1 = min(start_x, end_x)
|
60
|
-
y1 = min(start_y, end_y)
|
61
|
-
x2 = max(start_x, end_x)
|
62
|
-
y2 = max(start_y, end_y)
|
63
|
-
|
58
|
+
|
59
|
+
x1 = min(start_x, end_x)
|
60
|
+
y1 = min(start_y, end_y)
|
61
|
+
x2 = max(start_x, end_x)
|
62
|
+
y2 = max(start_y, end_y)
|
63
|
+
|
64
64
|
self.on_select(monitor, (x1, y1, x2 - x1, y2 - y1))
|
65
65
|
|
66
66
|
canvas.bind('<ButtonPress-1>', on_click)
|
@@ -90,11 +90,19 @@ def get_screen_selection():
|
|
90
90
|
with Manager() as manager:
|
91
91
|
res = manager.dict()
|
92
92
|
process = Process(target=run_screen_selector, args=(res,))
|
93
|
-
|
94
|
-
process.start()
|
93
|
+
|
94
|
+
process.start()
|
95
95
|
process.join()
|
96
96
|
|
97
97
|
if 'monitor' in res and 'coordinates' in res:
|
98
98
|
return res.copy()
|
99
99
|
else:
|
100
100
|
return False
|
101
|
+
|
102
|
+
if __name__ == "__main__":
|
103
|
+
selection = get_screen_selection()
|
104
|
+
if selection:
|
105
|
+
print(f"Selected monitor: {selection['monitor']}")
|
106
|
+
print(f"Selected coordinates: {selection['coordinates']}")
|
107
|
+
else:
|
108
|
+
print("No selection made or process was interrupted.")
|
@@ -8,7 +8,7 @@ GameSentenceMiner/gametext.py,sha256=AAke4swwmN16da0IpyL5xMhU23nTz_c6z2kMfXPYv-8
|
|
8
8
|
GameSentenceMiner/gsm.py,sha256=fcczDPDcC0vDLAPZtIY64mU3Di3RZjR40tuLIiwB5lE,24827
|
9
9
|
GameSentenceMiner/model.py,sha256=JdnkT4VoPOXmOpRgFdvERZ09c9wLN6tUJxdrKlGZcqo,5305
|
10
10
|
GameSentenceMiner/notification.py,sha256=FY39ChSRK0Y8TQ6lBGsLnpZUFPtFpSy2tweeXVoV7kc,2809
|
11
|
-
GameSentenceMiner/obs.py,sha256=
|
11
|
+
GameSentenceMiner/obs.py,sha256=3h1hh868zdXQFGXJ7_mQZs5kcudEMa3yBOrbCVckhCs,9139
|
12
12
|
GameSentenceMiner/package.py,sha256=YlS6QRMuVlm6mdXx0rlXv9_3erTGS21jaP3PNNWfAH0,1250
|
13
13
|
GameSentenceMiner/util.py,sha256=W49gqYlhbzZe-17zHMFcAjNeeteXrv_USHT7aBDKSqM,6825
|
14
14
|
GameSentenceMiner/utility_gui.py,sha256=H4aOddlsrVR768RwbMzYScCziuOz1JeySUigNrPlaac,7692
|
@@ -21,23 +21,24 @@ GameSentenceMiner/downloader/Untitled_json.py,sha256=RUUl2bbbCpUDUUS0fP0tdvf5Fng
|
|
21
21
|
GameSentenceMiner/downloader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
22
|
GameSentenceMiner/downloader/download_tools.py,sha256=mI1u_FGBmBqDIpCH3jOv8DOoZ3obgP5pIf9o9SVfX2Q,8131
|
23
23
|
GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
|
+
GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=UnkAqn_9EoJlzElHLjKYjNsDBM_MnTlsSshqbRAlXIg,890
|
24
25
|
GameSentenceMiner/ocr/ocrconfig.py,sha256=hTROOZ3On2HngXKxwQFZvnr5AxlmlMV0mPxv-F3NbMg,6476
|
25
|
-
GameSentenceMiner/ocr/owocr_area_selector.py,sha256=
|
26
|
-
GameSentenceMiner/ocr/owocr_helper.py,sha256=
|
26
|
+
GameSentenceMiner/ocr/owocr_area_selector.py,sha256=fpPE6FOFSSfgc2feur8Czve91Jl1zBG_nwUSRY6lTbg,11934
|
27
|
+
GameSentenceMiner/ocr/owocr_helper.py,sha256=_mcjF9TA9GrselOzJQdtXhImj8nlLGih9TIiuXsaEdo,14089
|
27
28
|
GameSentenceMiner/owocr/owocr/__init__.py,sha256=opjBOyGGyEqZCE6YdZPnyt7nVfiwyELHsXA0jAsjm14,25
|
28
29
|
GameSentenceMiner/owocr/owocr/__main__.py,sha256=r8MI6RAmbkTWqOJ59uvXoDS7CSw5jX5war9ULGWELrA,128
|
29
30
|
GameSentenceMiner/owocr/owocr/config.py,sha256=738QCJHEWpFhMh966plOcXYWwcshSiRsxjjIwldeTtI,7461
|
30
31
|
GameSentenceMiner/owocr/owocr/lens_betterproto.py,sha256=oNoISsPilVVRBBPVDtb4-roJtAhp8ZAuFTci3TGXtMc,39141
|
31
32
|
GameSentenceMiner/owocr/owocr/ocr.py,sha256=t0kU2GQyW0gf0NGqaYiOO7SjYgX8mQXLaNKJ8Eup6mg,39704
|
32
|
-
GameSentenceMiner/owocr/owocr/run.py,sha256=
|
33
|
-
GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=
|
33
|
+
GameSentenceMiner/owocr/owocr/run.py,sha256=0MGrhO6HoLNF1JpYcl-tS6SnXNM5zu9y0oSZDrAcw5k,47499
|
34
|
+
GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=fjJ3CSXLti3WboGPpmsa7MWOwIXsfpHC8N4zKahGGY0,3346
|
34
35
|
GameSentenceMiner/vad/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
35
36
|
GameSentenceMiner/vad/silero_trim.py,sha256=ULf3zwS-JMsY82cKF7gZxREHw8L6lgpWF2U1YqgE9Oc,1681
|
36
37
|
GameSentenceMiner/vad/vosk_helper.py,sha256=125X8C9NxFPlWWpoNsbOnEqKx8RCjXN109zNx_QXhyg,6070
|
37
38
|
GameSentenceMiner/vad/whisper_helper.py,sha256=JJ-iltCh813XdjyEw0Wn5DaErf6PDqfH0Efu1Md8cIY,3543
|
38
|
-
gamesentenceminer-2.7.
|
39
|
-
gamesentenceminer-2.7.
|
40
|
-
gamesentenceminer-2.7.
|
41
|
-
gamesentenceminer-2.7.
|
42
|
-
gamesentenceminer-2.7.
|
43
|
-
gamesentenceminer-2.7.
|
39
|
+
gamesentenceminer-2.7.9.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
40
|
+
gamesentenceminer-2.7.9.dist-info/METADATA,sha256=hFDJEaZNflRL93x_WRMZwDxjGSnO3ylogfdij2ZwVhs,5839
|
41
|
+
gamesentenceminer-2.7.9.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
|
42
|
+
gamesentenceminer-2.7.9.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
43
|
+
gamesentenceminer-2.7.9.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
44
|
+
gamesentenceminer-2.7.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|