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.
Files changed (51) hide show
  1. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/obs.py +3 -3
  2. gamesentenceminer-2.7.9/GameSentenceMiner/ocr/gsm_ocr_config.py +42 -0
  3. gamesentenceminer-2.7.9/GameSentenceMiner/ocr/owocr_area_selector.py +294 -0
  4. gamesentenceminer-2.7.9/GameSentenceMiner/ocr/owocr_helper.py +336 -0
  5. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/owocr/owocr/run.py +12 -5
  6. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +16 -8
  7. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner.egg-info/PKG-INFO +1 -1
  8. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner.egg-info/SOURCES.txt +1 -0
  9. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/PKG-INFO +1 -1
  10. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/pyproject.toml +1 -1
  11. gamesentenceminer-2.7.7/GameSentenceMiner/ocr/owocr_area_selector.py +0 -275
  12. gamesentenceminer-2.7.7/GameSentenceMiner/ocr/owocr_helper.py +0 -310
  13. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/__init__.py +0 -0
  14. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/ai/__init__.py +0 -0
  15. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/ai/gemini.py +0 -0
  16. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/anki.py +0 -0
  17. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/communication/__init__.py +0 -0
  18. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/communication/send.py +0 -0
  19. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/communication/websocket.py +0 -0
  20. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/config_gui.py +0 -0
  21. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/configuration.py +0 -0
  22. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/downloader/Untitled_json.py +0 -0
  23. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/downloader/__init__.py +0 -0
  24. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/downloader/download_tools.py +0 -0
  25. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/electron_config.py +0 -0
  26. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/ffmpeg.py +0 -0
  27. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/gametext.py +0 -0
  28. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/gsm.py +0 -0
  29. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/model.py +0 -0
  30. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/notification.py +0 -0
  31. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/ocr/__init__.py +0 -0
  32. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/ocr/ocrconfig.py +0 -0
  33. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/owocr/owocr/__init__.py +0 -0
  34. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/owocr/owocr/__main__.py +0 -0
  35. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/owocr/owocr/config.py +0 -0
  36. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -0
  37. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/owocr/owocr/ocr.py +0 -0
  38. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/package.py +0 -0
  39. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/util.py +0 -0
  40. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/utility_gui.py +0 -0
  41. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/vad/__init__.py +0 -0
  42. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/vad/silero_trim.py +0 -0
  43. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/vad/vosk_helper.py +0 -0
  44. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner/vad/whisper_helper.py +0 -0
  45. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner.egg-info/dependency_links.txt +0 -0
  46. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner.egg-info/entry_points.txt +0 -0
  47. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner.egg-info/requires.txt +0 -0
  48. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/GameSentenceMiner.egg-info/top_level.txt +0 -0
  49. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/LICENSE +0 -0
  50. {gamesentenceminer-2.7.7 → gamesentenceminer-2.7.9}/README.md +0 -0
  51. {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", 4455) # Default to 4455 if not set
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.obs.port = server_port
77
- config.obs.password = server_password
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.")