GameSentenceMiner 2.11.2__py3-none-any.whl → 2.11.3__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 +8 -1
- GameSentenceMiner/ocr/gsm_ocr_config.py +30 -4
- GameSentenceMiner/ocr/owocr_area_selector.py +207 -117
- GameSentenceMiner/ocr/owocr_helper.py +25 -12
- GameSentenceMiner/owocr/owocr/ocr.py +0 -1
- GameSentenceMiner/owocr/owocr/run.py +113 -8
- {gamesentenceminer-2.11.2.dist-info → gamesentenceminer-2.11.3.dist-info}/METADATA +1 -1
- {gamesentenceminer-2.11.2.dist-info → gamesentenceminer-2.11.3.dist-info}/RECORD +12 -12
- {gamesentenceminer-2.11.2.dist-info → gamesentenceminer-2.11.3.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.11.2.dist-info → gamesentenceminer-2.11.3.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.11.2.dist-info → gamesentenceminer-2.11.3.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.11.2.dist-info → gamesentenceminer-2.11.3.dist-info}/top_level.txt +0 -0
GameSentenceMiner/obs.py
CHANGED
@@ -203,6 +203,7 @@ def connect_to_obs_sync(retry=2):
|
|
203
203
|
obs_connection_manager = OBSConnectionManager()
|
204
204
|
obs_connection_manager.start()
|
205
205
|
update_current_game()
|
206
|
+
logger.info("Connected to OBS WebSocket.")
|
206
207
|
break # Exit the loop once connected
|
207
208
|
except Exception as e:
|
208
209
|
if retry <= 0:
|
@@ -295,6 +296,12 @@ def get_source_from_scene(scene_name):
|
|
295
296
|
logger.error(f"Error getting source from scene: {e}")
|
296
297
|
return ''
|
297
298
|
|
299
|
+
def get_active_source():
|
300
|
+
current_game = get_current_game()
|
301
|
+
if not current_game:
|
302
|
+
return None
|
303
|
+
return get_source_from_scene(current_game)
|
304
|
+
|
298
305
|
def get_record_directory():
|
299
306
|
try:
|
300
307
|
response = client.get_record_directory()
|
@@ -362,7 +369,7 @@ def get_screenshot_base64(compression=0, width=None, height=None):
|
|
362
369
|
return None
|
363
370
|
response = client.get_source_screenshot(name=current_source_name, img_format='png', quality=compression, width=width, height=height)
|
364
371
|
if response and response.image_data:
|
365
|
-
return response.image_data
|
372
|
+
return response.image_data.split(',', 1)[-1] # Remove data:image/png;base64, prefix if present
|
366
373
|
else:
|
367
374
|
logger.error(f"Error getting base64 screenshot: {response}")
|
368
375
|
return None
|
@@ -1,12 +1,15 @@
|
|
1
|
-
import
|
1
|
+
import os
|
2
2
|
from copy import deepcopy
|
3
3
|
from dataclasses import dataclass
|
4
4
|
from math import floor, ceil
|
5
|
+
from pathlib import Path
|
5
6
|
|
7
|
+
from GameSentenceMiner import obs
|
6
8
|
from dataclasses_json import dataclass_json
|
7
9
|
from typing import List, Optional, Union
|
8
10
|
|
9
|
-
from GameSentenceMiner.util.configuration import logger
|
11
|
+
from GameSentenceMiner.util.configuration import logger, get_app_directory
|
12
|
+
from GameSentenceMiner.util.gsm_utils import sanitize_filename
|
10
13
|
|
11
14
|
|
12
15
|
@dataclass_json
|
@@ -50,8 +53,10 @@ class OCRConfig:
|
|
50
53
|
window: Optional[str] = None
|
51
54
|
language: str = "ja"
|
52
55
|
|
53
|
-
def
|
56
|
+
def __post_init__(self):
|
54
57
|
self.pre_scale_rectangles = deepcopy(self.rectangles)
|
58
|
+
|
59
|
+
def scale_coords(self):
|
55
60
|
if self.coordinate_system and self.coordinate_system == "percentage" and self.window:
|
56
61
|
import pygetwindow as gw
|
57
62
|
try:
|
@@ -116,7 +121,28 @@ def get_window(title):
|
|
116
121
|
return window
|
117
122
|
return ret
|
118
123
|
|
119
|
-
#
|
124
|
+
# if windows, set dpi awareness to per-monitor v2
|
120
125
|
def set_dpi_awareness():
|
126
|
+
import sys
|
127
|
+
if sys.platform != "win32":
|
128
|
+
return
|
129
|
+
import ctypes
|
121
130
|
per_monitor_awareness = 2
|
122
131
|
ctypes.windll.shcore.SetProcessDpiAwareness(per_monitor_awareness)
|
132
|
+
|
133
|
+
def get_scene_ocr_config(use_window_as_config=False, window=""):
|
134
|
+
ocr_config_dir = get_ocr_config_path()
|
135
|
+
try:
|
136
|
+
if use_window_as_config:
|
137
|
+
scene = sanitize_filename(window)
|
138
|
+
else:
|
139
|
+
scene = sanitize_filename(obs.get_current_scene() or "Default")
|
140
|
+
except Exception as e:
|
141
|
+
print(f"Error getting OBS scene: {e}. Using default config name.")
|
142
|
+
scene = "Default"
|
143
|
+
return os.path.join(ocr_config_dir, f"{scene}.json")
|
144
|
+
|
145
|
+
def get_ocr_config_path():
|
146
|
+
ocr_config_dir = os.path.join(get_app_directory(), "ocr_config")
|
147
|
+
os.makedirs(ocr_config_dir, exist_ok=True)
|
148
|
+
return ocr_config_dir
|
@@ -1,23 +1,19 @@
|
|
1
|
+
import argparse
|
2
|
+
import base64
|
1
3
|
import ctypes
|
4
|
+
import io
|
2
5
|
import json
|
3
6
|
import sys
|
4
7
|
from multiprocessing import Process, Manager
|
5
8
|
from pathlib import Path
|
6
9
|
|
7
|
-
import mss
|
8
10
|
from PIL import Image, ImageTk
|
9
11
|
|
10
12
|
# Assuming a mock or real obs module exists in this path
|
11
13
|
from GameSentenceMiner import obs
|
12
|
-
from GameSentenceMiner.ocr.gsm_ocr_config import set_dpi_awareness, get_window
|
14
|
+
from GameSentenceMiner.ocr.gsm_ocr_config import set_dpi_awareness, get_window, get_scene_ocr_config
|
13
15
|
from GameSentenceMiner.util.gsm_utils import sanitize_filename
|
14
16
|
|
15
|
-
try:
|
16
|
-
import pygetwindow as gw
|
17
|
-
except ImportError:
|
18
|
-
print("Error: pygetwindow library not found. Please install it: pip install pygetwindow")
|
19
|
-
gw = None
|
20
|
-
|
21
17
|
try:
|
22
18
|
import tkinter as tk
|
23
19
|
from tkinter import font as tkfont # NEW: Import for better font control
|
@@ -34,30 +30,59 @@ COORD_SYSTEM_PERCENTAGE = "percentage"
|
|
34
30
|
|
35
31
|
|
36
32
|
class ScreenSelector:
|
37
|
-
def __init__(self, result, window_name, use_window_as_config):
|
38
|
-
if not selector_available
|
39
|
-
raise RuntimeError("tkinter
|
33
|
+
def __init__(self, result, window_name, use_window_as_config, use_obs_screenshot=False):
|
34
|
+
if not selector_available:
|
35
|
+
raise RuntimeError("tkinter is not available.")
|
40
36
|
if not window_name:
|
41
|
-
raise ValueError("A target window name is required for
|
37
|
+
raise ValueError("A target window name is required for configuration.")
|
42
38
|
|
43
39
|
obs.connect_to_obs_sync()
|
44
40
|
self.window_name = window_name
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
41
|
+
self.use_obs_screenshot = use_obs_screenshot
|
42
|
+
self.screenshot_img = None
|
43
|
+
try:
|
44
|
+
import mss
|
45
|
+
self.sct = mss.mss()
|
46
|
+
self.monitors = self.sct.monitors[1:]
|
47
|
+
if not self.monitors:
|
48
|
+
raise RuntimeError("No monitors found by mss.")
|
49
|
+
for i, monitor in enumerate(self.monitors):
|
50
|
+
monitor['index'] = i
|
51
|
+
except ImportError:
|
52
|
+
print("Error: mss library not found. Please install it: pip install mss")
|
53
|
+
raise RuntimeError("mss is required for screen selection.")
|
54
|
+
|
55
|
+
if self.use_obs_screenshot:
|
56
|
+
print("Using OBS screenshot as target.")
|
57
|
+
screenshot_base64 = obs.get_screenshot_base64(compression=75)
|
58
|
+
# print(screenshot_base64)
|
59
|
+
if not screenshot_base64:
|
60
|
+
raise RuntimeError("Failed to get OBS screenshot.")
|
61
|
+
try:
|
62
|
+
img_data = base64.b64decode(screenshot_base64)
|
63
|
+
self.screenshot_img = Image.open(io.BytesIO(img_data))
|
64
|
+
# Scale image to 1280x720
|
65
|
+
self.screenshot_img = self.screenshot_img.resize((1280, 720), Image.LANCZOS)
|
66
|
+
except Exception as e:
|
67
|
+
raise RuntimeError(f"Failed to decode or open OBS screenshot: {e}")
|
68
|
+
|
69
|
+
self.target_window = None
|
70
|
+
self.target_window_geometry = {
|
71
|
+
"left": 0, "top": 0,
|
72
|
+
"width": self.screenshot_img.width,
|
73
|
+
"height": self.screenshot_img.height
|
74
|
+
}
|
75
|
+
print(f"OBS Screenshot dimensions: {self.target_window_geometry}")
|
76
|
+
else:
|
77
|
+
import pygetwindow as gw
|
78
|
+
if not gw:
|
79
|
+
raise RuntimeError("pygetwindow is not available for window selection.")
|
80
|
+
print(f"Targeting window: '{window_name}'")
|
81
|
+
self.target_window = self._find_target_window()
|
82
|
+
self.target_window_geometry = self._get_window_geometry(self.target_window)
|
83
|
+
if not self.target_window_geometry:
|
84
|
+
raise RuntimeError(f"Could not find or get geometry for window '{self.window_name}'.")
|
85
|
+
print(f"Found target window at: {self.target_window_geometry}")
|
61
86
|
|
62
87
|
self.root = None
|
63
88
|
self.scene = ''
|
@@ -71,6 +96,11 @@ class ScreenSelector:
|
|
71
96
|
self.redo_stack = []
|
72
97
|
self.bounding_box = {} # Geometry of the single large canvas window
|
73
98
|
|
99
|
+
self.canvas = None
|
100
|
+
self.window = None
|
101
|
+
self.instructions_widget = None
|
102
|
+
self.instructions_window_id = None
|
103
|
+
|
74
104
|
self.load_existing_rectangles()
|
75
105
|
|
76
106
|
def _find_target_window(self):
|
@@ -91,23 +121,9 @@ class ScreenSelector:
|
|
91
121
|
return None
|
92
122
|
return None
|
93
123
|
|
94
|
-
def get_scene_ocr_config(self):
|
95
|
-
app_dir = Path.home() / "AppData" / "Roaming" / "GameSentenceMiner"
|
96
|
-
ocr_config_dir = app_dir / "ocr_config"
|
97
|
-
ocr_config_dir.mkdir(parents=True, exist_ok=True)
|
98
|
-
try:
|
99
|
-
if self.use_window_as_config:
|
100
|
-
self.scene = sanitize_filename(self.window_name)
|
101
|
-
else:
|
102
|
-
self.scene = sanitize_filename(obs.get_current_scene() or "")
|
103
|
-
except Exception as e:
|
104
|
-
print(f"Error getting OBS scene: {e}. Using default config name.")
|
105
|
-
self.scene = ""
|
106
|
-
return ocr_config_dir / f"{self.scene}.json"
|
107
|
-
|
108
124
|
def load_existing_rectangles(self):
|
109
125
|
"""Loads rectangles from config, converting from percentage to absolute pixels for use."""
|
110
|
-
config_path = self.
|
126
|
+
config_path = get_scene_ocr_config(self.use_window_as_config, self.window_name)
|
111
127
|
win_geom = self.target_window_geometry # Use current geometry for conversion
|
112
128
|
win_w, win_h, win_l, win_t = win_geom['width'], win_geom['height'], win_geom['left'], win_geom['top']
|
113
129
|
|
@@ -152,7 +168,7 @@ class ScreenSelector:
|
|
152
168
|
|
153
169
|
def save_rects(self, event=None):
|
154
170
|
"""Saves rectangles to config, converting from absolute pixels to percentages."""
|
155
|
-
config_path = self.
|
171
|
+
config_path = get_scene_ocr_config(self.use_window_as_config, self.window_name)
|
156
172
|
win_geom = self.target_window_geometry
|
157
173
|
win_l, win_t, win_w, win_h = win_geom['left'], win_geom['top'], win_geom['width'], win_geom['height']
|
158
174
|
print(f"Saving rectangles to: {config_path} relative to window: {win_geom}")
|
@@ -200,6 +216,12 @@ class ScreenSelector:
|
|
200
216
|
event.widget.winfo_toplevel().winfo_children()[0].delete(last_rect_id)
|
201
217
|
print("Undo: Removed last rectangle.")
|
202
218
|
|
219
|
+
def toggle_image_mode(self, e=None):
|
220
|
+
self.image_mode = not self.image_mode
|
221
|
+
# Only change alpha of the main window, not the text widget
|
222
|
+
self.window.attributes("-alpha", 1.0 if self.image_mode else 0.25)
|
223
|
+
print("Toggled background visibility.")
|
224
|
+
|
203
225
|
def redo_last_rect(self, event=None):
|
204
226
|
if not self.redo_stack: return
|
205
227
|
monitor, abs_coords, is_excluded, old_rect_id = self.redo_stack.pop()
|
@@ -213,8 +235,63 @@ class ScreenSelector:
|
|
213
235
|
print("Redo: Restored rectangle.")
|
214
236
|
|
215
237
|
# --- NEW METHOD TO DISPLAY INSTRUCTIONS ---
|
216
|
-
def _create_instructions_widget(self,
|
217
|
-
"""Creates a
|
238
|
+
def _create_instructions_widget(self, parent_canvas):
|
239
|
+
"""Creates a separate, persistent window for instructions and control buttons."""
|
240
|
+
if self.instructions_widget and self.instructions_widget.winfo_exists():
|
241
|
+
self.instructions_widget.lift()
|
242
|
+
return
|
243
|
+
|
244
|
+
self.instructions_widget = tk.Toplevel(parent_canvas)
|
245
|
+
self.instructions_widget.title("Controls")
|
246
|
+
|
247
|
+
# --- Position it near the main window ---
|
248
|
+
parent_window = parent_canvas.winfo_toplevel()
|
249
|
+
# Make the instructions window transient to the main window to keep it on top
|
250
|
+
# self.instructions_widget.transient(parent_window)
|
251
|
+
self.instructions_widget.attributes('-topmost', 1)
|
252
|
+
# parent_window.update_idletasks() # Ensure dimensions are up-to-date
|
253
|
+
pos_x = parent_window.winfo_x() + 50
|
254
|
+
pos_y = parent_window.winfo_y() + 50
|
255
|
+
self.instructions_widget.geometry(f"+{pos_x}+{pos_y}")
|
256
|
+
|
257
|
+
main_frame = tk.Frame(self.instructions_widget, padx=10, pady=10)
|
258
|
+
main_frame.pack(fill=tk.BOTH, expand=True)
|
259
|
+
|
260
|
+
instructions_text = (
|
261
|
+
"How to Use:\n"
|
262
|
+
"• Left Click + Drag: Create a capture area (green).\n"
|
263
|
+
"• Shift + Left Click + Drag: Create an exclusion area (orange).\n"
|
264
|
+
"• Right-Click on a box: Delete it."
|
265
|
+
)
|
266
|
+
tk.Label(main_frame, text=instructions_text, justify=tk.LEFT, anchor="w").pack(pady=(0, 10), fill=tk.X)
|
267
|
+
|
268
|
+
button_frame = tk.Frame(main_frame)
|
269
|
+
button_frame.pack(fill=tk.X, pady=5)
|
270
|
+
|
271
|
+
def canvas_event_wrapper(func):
|
272
|
+
class MockEvent:
|
273
|
+
def __init__(self, widget):
|
274
|
+
self.widget = widget
|
275
|
+
return lambda: func(MockEvent(self.canvas))
|
276
|
+
|
277
|
+
def root_event_wrapper(func):
|
278
|
+
return lambda: func(None)
|
279
|
+
|
280
|
+
tk.Button(button_frame, text="Save and Quit (Ctrl+S)", command=root_event_wrapper(self.save_rects)).pack(fill=tk.X, pady=2)
|
281
|
+
tk.Button(button_frame, text="Undo (Ctrl+Z)", command=canvas_event_wrapper(self.undo_last_rect)).pack(fill=tk.X, pady=2)
|
282
|
+
tk.Button(button_frame, text="Redo (Ctrl+Y)", command=canvas_event_wrapper(self.redo_last_rect)).pack(fill=tk.X, pady=2)
|
283
|
+
tk.Button(button_frame, text="Toggle Background (M)", command=root_event_wrapper(self.toggle_image_mode)).pack(fill=tk.X, pady=2)
|
284
|
+
tk.Button(button_frame, text="Quit without Saving (Esc)", command=root_event_wrapper(self.quit_app)).pack(fill=tk.X, pady=2)
|
285
|
+
|
286
|
+
hotkeys_text = "\n• I: Toggle this instruction panel"
|
287
|
+
tk.Label(main_frame, text=hotkeys_text, justify=tk.LEFT, anchor="w").pack(pady=(10, 0), fill=tk.X)
|
288
|
+
|
289
|
+
self.instructions_widget.protocol("WM_DELETE_WINDOW", self.toggle_instructions)
|
290
|
+
|
291
|
+
|
292
|
+
# --- NEW METHOD TO DISPLAY INSTRUCTIONS ---
|
293
|
+
def print_instructions_box(self, canvas):
|
294
|
+
"""Creates a separate, persistent window for instructions and control buttons."""
|
218
295
|
instructions_text = (
|
219
296
|
"How to Use:\n"
|
220
297
|
" • Left Click + Drag: Create a capture area (green).\n"
|
@@ -260,44 +337,51 @@ class ScreenSelector:
|
|
260
337
|
canvas.tag_lower(rect_id, text_id)
|
261
338
|
|
262
339
|
def toggle_instructions(self, event=None):
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
current_state = canvas.itemcget(text_items[0], 'state')
|
270
|
-
new_state = tk.NORMAL if current_state == tk.HIDDEN else tk.HIDDEN
|
271
|
-
for item in text_items + rect_items:
|
272
|
-
canvas.itemconfigure(item, state=new_state)
|
273
|
-
print("Toggled instructions visibility.")
|
340
|
+
if self.instructions_widget and self.instructions_widget.winfo_exists() and self.instructions_widget.state() == "normal":
|
341
|
+
self.instructions_widget.withdraw()
|
342
|
+
print("Toggled instructions visibility: OFF")
|
343
|
+
else:
|
344
|
+
self._create_instructions_widget(self.canvas)
|
345
|
+
print("Toggled instructions visibility: ON")
|
274
346
|
|
275
347
|
def start(self):
|
276
348
|
self.root = tk.Tk()
|
277
349
|
self.root.withdraw()
|
278
350
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
351
|
+
if self.use_obs_screenshot:
|
352
|
+
# Use the pre-loaded OBS screenshot
|
353
|
+
img = self.screenshot_img
|
354
|
+
self.bounding_box = self.target_window_geometry
|
355
|
+
# Center the window on the primary monitor
|
356
|
+
primary_monitor = self.sct.monitors[1] if len(self.sct.monitors) > 1 else self.sct.monitors[0]
|
357
|
+
win_x = primary_monitor['left'] + (primary_monitor['width'] - img.width) // 2
|
358
|
+
win_y = primary_monitor['top'] + (primary_monitor['height'] - img.height) // 2
|
359
|
+
window_geometry = f"{img.width}x{img.height}+{int(win_x)}+{int(win_y)}"
|
360
|
+
else:
|
361
|
+
# Calculate bounding box of all monitors for the overlay
|
362
|
+
left = min(m['left'] for m in self.monitors)
|
363
|
+
top = min(m['top'] for m in self.monitors)
|
364
|
+
right = max(m['left'] + m['width'] for m in self.monitors)
|
365
|
+
bottom = max(m['top'] + m['height'] for m in self.monitors)
|
366
|
+
self.bounding_box = {'left': left, 'top': top, 'width': right - left, 'height': bottom - top}
|
367
|
+
|
368
|
+
# Capture the entire desktop area covered by all monitors
|
369
|
+
sct_img = self.sct.grab(self.bounding_box)
|
370
|
+
img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
|
371
|
+
window_geometry = f"{self.bounding_box['width']}x{self.bounding_box['height']}+{left}+{top}"
|
372
|
+
|
373
|
+
self.window = tk.Toplevel(self.root)
|
374
|
+
self.window.geometry(window_geometry)
|
375
|
+
self.window.overrideredirect(1)
|
376
|
+
self.window.attributes('-topmost', 1)
|
293
377
|
|
294
378
|
self.photo_image = ImageTk.PhotoImage(img)
|
295
|
-
canvas = tk.Canvas(window, cursor='cross', highlightthickness=0)
|
296
|
-
canvas.pack(fill=tk.BOTH, expand=True)
|
297
|
-
canvas.create_image(0, 0, image=self.photo_image, anchor=tk.NW)
|
379
|
+
self.canvas = tk.Canvas(self.window, cursor='cross', highlightthickness=0)
|
380
|
+
self.canvas.pack(fill=tk.BOTH, expand=True)
|
381
|
+
self.canvas.create_image(0, 0, image=self.photo_image, anchor=tk.NW)
|
298
382
|
|
299
383
|
# --- MODIFIED: CALL THE INSTRUCTION WIDGET CREATOR ---
|
300
|
-
self._create_instructions_widget(canvas)
|
384
|
+
# self._create_instructions_widget(self.canvas)
|
301
385
|
# --- END MODIFICATION ---
|
302
386
|
|
303
387
|
# Draw existing rectangles (which were converted to absolute pixels on load)
|
@@ -305,29 +389,29 @@ class ScreenSelector:
|
|
305
389
|
x_abs, y_abs, w_abs, h_abs = abs_coords
|
306
390
|
canvas_x = x_abs - self.bounding_box['left']
|
307
391
|
canvas_y = y_abs - self.bounding_box['top']
|
308
|
-
rect_id = canvas.create_rectangle(canvas_x, canvas_y, canvas_x + w_abs, canvas_y + h_abs,
|
392
|
+
rect_id = self.canvas.create_rectangle(canvas_x, canvas_y, canvas_x + w_abs, canvas_y + h_abs,
|
309
393
|
outline='orange' if is_excluded else 'green', width=2)
|
310
394
|
self.drawn_rect_ids.append(rect_id)
|
311
395
|
|
312
396
|
def on_click(event):
|
313
397
|
self.start_x, self.start_y = event.x, event.y
|
314
398
|
outline = 'purple' if bool(event.state & 0x0001) else 'red'
|
315
|
-
self.current_rect_id = canvas.create_rectangle(self.start_x, self.start_y, self.start_x, self.start_y,
|
399
|
+
self.current_rect_id = self.canvas.create_rectangle(self.start_x, self.start_y, self.start_x, self.start_y,
|
316
400
|
outline=outline, width=2)
|
317
401
|
|
318
402
|
def on_drag(event):
|
319
|
-
if self.current_rect_id: canvas.coords(self.current_rect_id, self.start_x, self.start_y, event.x, event.y)
|
403
|
+
if self.current_rect_id: self.canvas.coords(self.current_rect_id, self.start_x, self.start_y, event.x, event.y)
|
320
404
|
|
321
405
|
def on_release(event):
|
322
406
|
if not self.current_rect_id: return
|
323
|
-
coords = canvas.coords(self.current_rect_id)
|
407
|
+
coords = self.canvas.coords(self.current_rect_id)
|
324
408
|
x_abs = int(min(coords[0], coords[2]) + self.bounding_box['left'])
|
325
409
|
y_abs = int(min(coords[1], coords[3]) + self.bounding_box['top'])
|
326
410
|
w, h = int(abs(coords[2] - coords[0])), int(abs(coords[3] - coords[1]))
|
327
411
|
|
328
412
|
if w >= MIN_RECT_WIDTH and h >= MIN_RECT_HEIGHT:
|
329
413
|
is_excl = bool(event.state & 0x0001)
|
330
|
-
canvas.itemconfig(self.current_rect_id, outline='orange' if is_excl else 'green')
|
414
|
+
self.canvas.itemconfig(self.current_rect_id, outline='orange' if is_excl else 'green')
|
331
415
|
|
332
416
|
center_x, center_y = x_abs + w / 2, y_abs + h / 2
|
333
417
|
target_mon = self.monitors[0]
|
@@ -341,7 +425,7 @@ class ScreenSelector:
|
|
341
425
|
self.drawn_rect_ids.append(self.current_rect_id)
|
342
426
|
self.redo_stack.clear()
|
343
427
|
else:
|
344
|
-
canvas.delete(self.current_rect_id)
|
428
|
+
self.canvas.delete(self.current_rect_id)
|
345
429
|
self.current_rect_id = self.start_x = self.start_y = None
|
346
430
|
|
347
431
|
def on_right_click(event):
|
@@ -366,45 +450,45 @@ class ScreenSelector:
|
|
366
450
|
# Now, perform the deletion
|
367
451
|
del self.rectangles[i]
|
368
452
|
del self.drawn_rect_ids[i]
|
369
|
-
canvas.delete(item_id_to_del)
|
453
|
+
self.canvas.delete(item_id_to_del)
|
370
454
|
print("Deleted rectangle.")
|
371
455
|
|
372
456
|
break # Stop after deleting the topmost one
|
373
457
|
|
374
|
-
def toggle_image_mode(e=None):
|
375
|
-
self.image_mode = not self.image_mode
|
376
|
-
# Only change alpha of the main window, not the text widget
|
377
|
-
window.attributes("-alpha", 1.0 if self.image_mode else 0.25)
|
378
|
-
print("Toggled background visibility.")
|
379
|
-
|
380
458
|
def on_enter(e=None):
|
381
|
-
canvas.focus_set()
|
382
|
-
|
383
|
-
canvas.bind('<Enter>', on_enter)
|
384
|
-
canvas.bind('<ButtonPress-1>', on_click)
|
385
|
-
canvas.bind('<B1-Motion>', on_drag)
|
386
|
-
canvas.bind('<ButtonRelease-1>', on_release)
|
387
|
-
canvas.bind('<Button-3>', on_right_click)
|
388
|
-
canvas.bind('<Control-s>', self.save_rects)
|
389
|
-
canvas.bind('<Control-y>', self.redo_last_rect)
|
390
|
-
canvas.bind('<Control-z>', self.undo_last_rect)
|
391
|
-
canvas.bind("<Escape>", self.quit_app)
|
392
|
-
canvas.bind("<m>", toggle_image_mode)
|
393
|
-
canvas.bind("<i>", self.toggle_instructions)
|
394
|
-
|
395
|
-
canvas.focus_set()
|
459
|
+
self.canvas.focus_set()
|
460
|
+
|
461
|
+
self.canvas.bind('<Enter>', on_enter)
|
462
|
+
self.canvas.bind('<ButtonPress-1>', on_click)
|
463
|
+
self.canvas.bind('<B1-Motion>', on_drag)
|
464
|
+
self.canvas.bind('<ButtonRelease-1>', on_release)
|
465
|
+
self.canvas.bind('<Button-3>', on_right_click)
|
466
|
+
self.canvas.bind('<Control-s>', self.save_rects)
|
467
|
+
self.canvas.bind('<Control-y>', self.redo_last_rect)
|
468
|
+
self.canvas.bind('<Control-z>', self.undo_last_rect)
|
469
|
+
self.canvas.bind("<Escape>", self.quit_app)
|
470
|
+
self.canvas.bind("<m>", self.toggle_image_mode)
|
471
|
+
self.canvas.bind("<i>", self.toggle_instructions)
|
472
|
+
|
473
|
+
self.canvas.focus_set()
|
474
|
+
self._create_instructions_widget(self.window)
|
475
|
+
self.window.winfo_toplevel().update_idletasks()
|
476
|
+
self.print_instructions_box(self.canvas)
|
396
477
|
# The print message is now redundant but kept for console feedback
|
397
478
|
print("Starting UI. See on-screen instructions. Press Esc to quit, Ctrl+S to save.")
|
479
|
+
# self.canvas.update_idletasks()
|
398
480
|
self.root.mainloop()
|
399
481
|
|
400
482
|
def quit_app(self, event=None):
|
483
|
+
if self.instructions_widget and self.instructions_widget.winfo_exists():
|
484
|
+
self.instructions_widget.destroy()
|
401
485
|
if self.root and self.root.winfo_exists(): self.root.destroy()
|
402
486
|
self.root = None
|
403
487
|
|
404
488
|
|
405
|
-
def run_screen_selector(result_dict, window_name, use_window_as_config):
|
489
|
+
def run_screen_selector(result_dict, window_name, use_window_as_config, use_obs_screenshot):
|
406
490
|
try:
|
407
|
-
selector = ScreenSelector(result_dict, window_name, use_window_as_config)
|
491
|
+
selector = ScreenSelector(result_dict, window_name, use_window_as_config, use_obs_screenshot)
|
408
492
|
selector.start()
|
409
493
|
except Exception as e:
|
410
494
|
print(f"Error in selector process: {e}", file=sys.stderr)
|
@@ -413,15 +497,15 @@ def run_screen_selector(result_dict, window_name, use_window_as_config):
|
|
413
497
|
result_dict['error'] = str(e)
|
414
498
|
|
415
499
|
|
416
|
-
def get_screen_selection(window_name, use_window_as_config=False):
|
417
|
-
if not selector_available
|
500
|
+
def get_screen_selection(window_name, use_window_as_config=False, use_obs_screenshot=False):
|
501
|
+
if not selector_available: return None
|
418
502
|
if not window_name:
|
419
503
|
print("Error: A target window name must be provided.", file=sys.stderr)
|
420
504
|
return None
|
421
505
|
|
422
506
|
with Manager() as manager:
|
423
507
|
result_data = manager.dict()
|
424
|
-
process = Process(target=run_screen_selector, args=(result_data, window_name, use_window_as_config))
|
508
|
+
process = Process(target=run_screen_selector, args=(result_data, window_name, use_window_as_config, use_obs_screenshot))
|
425
509
|
print(f"Starting ScreenSelector process...")
|
426
510
|
process.start()
|
427
511
|
process.join()
|
@@ -439,18 +523,24 @@ def get_screen_selection(window_name, use_window_as_config=False):
|
|
439
523
|
|
440
524
|
if __name__ == "__main__":
|
441
525
|
set_dpi_awareness()
|
442
|
-
target_window_title = "YouTube - JP"
|
443
|
-
use_window_as_config = False
|
444
|
-
if len(sys.argv) > 1:
|
445
|
-
target_window_title = sys.argv[1]
|
446
|
-
if len(sys.argv) > 2:
|
447
|
-
use_window_as_config = True
|
448
|
-
target_window_title = sys.argv[1]
|
449
526
|
|
450
|
-
|
527
|
+
parser = argparse.ArgumentParser(description="Screen Selector Arguments")
|
528
|
+
parser.add_argument("window_title", nargs="?", default="", help="Target window title")
|
529
|
+
parser.add_argument("--obs_ocr", action="store_true", help="Use OBS screenshot")
|
530
|
+
parser.add_argument("--use_window_for_config", action="store_true", help="Use window for config")
|
531
|
+
args = parser.parse_args()
|
532
|
+
|
533
|
+
target_window_title = args.window_title
|
534
|
+
use_obs_screenshot = args.obs_ocr
|
535
|
+
use_window_as_config = args.use_window_for_config
|
536
|
+
|
537
|
+
print(f"Arguments: Window Title='{target_window_title}', Use OBS Screenshot={use_obs_screenshot}, Use Window for Config={use_window_as_config}")
|
538
|
+
|
539
|
+
# Example of how to call it
|
540
|
+
selection_result = get_screen_selection(target_window_title, use_window_as_config, use_obs_screenshot)
|
451
541
|
|
452
542
|
if selection_result is None:
|
453
|
-
print("
|
543
|
+
print("--- Screen selection failed. ---")
|
454
544
|
elif not selection_result:
|
455
545
|
print("\n--- Screen selection cancelled. ---")
|
456
546
|
elif 'rectangles' in selection_result:
|
@@ -22,7 +22,7 @@ from GameSentenceMiner.ocr.ss_picker import ScreenCropper
|
|
22
22
|
from GameSentenceMiner.owocr.owocr.run import TextFiltering
|
23
23
|
from GameSentenceMiner.util.configuration import get_config, get_app_directory, get_temporary_directory
|
24
24
|
from GameSentenceMiner.util.electron_config import get_ocr_scan_rate, get_requires_open_window
|
25
|
-
from GameSentenceMiner.ocr.gsm_ocr_config import OCRConfig, set_dpi_awareness, get_window
|
25
|
+
from GameSentenceMiner.ocr.gsm_ocr_config import OCRConfig, set_dpi_awareness, get_window, get_ocr_config_path
|
26
26
|
from GameSentenceMiner.owocr.owocr import screen_coordinate_picker, run
|
27
27
|
from GameSentenceMiner.util.gsm_utils import sanitize_filename, do_text_replacements, OCR_REPLACEMENTS_FILE
|
28
28
|
|
@@ -49,15 +49,13 @@ logger.addHandler(console_handler)
|
|
49
49
|
|
50
50
|
def get_ocr_config(window=None, use_window_for_config=False) -> OCRConfig:
|
51
51
|
"""Loads and updates screen capture areas from the corresponding JSON file."""
|
52
|
-
|
53
|
-
ocr_config_dir = app_dir / "ocr_config"
|
54
|
-
os.makedirs(ocr_config_dir, exist_ok=True)
|
52
|
+
ocr_config_dir = get_ocr_config_path()
|
55
53
|
obs.connect_to_obs_sync(retry=0)
|
56
54
|
if use_window_for_config and window:
|
57
55
|
scene = sanitize_filename(window)
|
58
56
|
else:
|
59
57
|
scene = sanitize_filename(obs.get_current_scene())
|
60
|
-
config_path = ocr_config_dir / f"{scene}.json"
|
58
|
+
config_path = Path(ocr_config_dir) / f"{scene}.json"
|
61
59
|
if not config_path.exists():
|
62
60
|
ocr_config = OCRConfig(scene=scene, window=window, rectangles=[], coordinate_system="percentage")
|
63
61
|
with open(config_path, 'w', encoding="utf-8") as f:
|
@@ -202,7 +200,8 @@ def do_second_ocr(ocr1_text, time, img, filtering, ignore_furigana_filter=False,
|
|
202
200
|
engine=ocr2, furigana_filter_sensitivity=furigana_filter_sensitivity if not ignore_furigana_filter else 0)
|
203
201
|
|
204
202
|
if compare_ocr_results(last_ocr2_result, orig_text):
|
205
|
-
|
203
|
+
if text:
|
204
|
+
logger.info("Seems like Text we already sent, not doing anything.")
|
206
205
|
return
|
207
206
|
save_result_image(img)
|
208
207
|
last_ocr2_result = orig_text
|
@@ -257,7 +256,8 @@ def text_callback(text, orig_text, time, img=None, came_from_ss=False, filtering
|
|
257
256
|
|
258
257
|
if manual or not twopassocr:
|
259
258
|
if compare_ocr_results(previous_orig_text, orig_text_string):
|
260
|
-
|
259
|
+
if text:
|
260
|
+
logger.info("Seems like Text we already sent, not doing anything.")
|
261
261
|
return
|
262
262
|
save_result_image(img)
|
263
263
|
asyncio.run(send_result(text, line_start_time))
|
@@ -275,7 +275,8 @@ def text_callback(text, orig_text, time, img=None, came_from_ss=False, filtering
|
|
275
275
|
stable_time = text_stable_start_time
|
276
276
|
previous_img_local = previous_img
|
277
277
|
if compare_ocr_results(previous_orig_text, orig_text_string):
|
278
|
-
|
278
|
+
if text:
|
279
|
+
logger.info("Seems like Text we already sent, not doing anything.")
|
279
280
|
previous_text = None
|
280
281
|
return
|
281
282
|
previous_orig_text = orig_text_string
|
@@ -292,6 +293,10 @@ def text_callback(text, orig_text, time, img=None, came_from_ss=False, filtering
|
|
292
293
|
previous_text = None
|
293
294
|
return
|
294
295
|
|
296
|
+
# Make sure it's an actual new line before starting the timer
|
297
|
+
if compare_ocr_results(orig_text_string, previous_orig_text):
|
298
|
+
return
|
299
|
+
|
295
300
|
if not text_stable_start_time:
|
296
301
|
text_stable_start_time = line_start_time
|
297
302
|
previous_text = text
|
@@ -327,8 +332,14 @@ def run_oneocr(ocr_config: OCRConfig, rectangles):
|
|
327
332
|
|
328
333
|
run.init_config(False)
|
329
334
|
try:
|
330
|
-
|
331
|
-
|
335
|
+
read_from = ""
|
336
|
+
if obs_ocr:
|
337
|
+
read_from = "obs"
|
338
|
+
elif window:
|
339
|
+
read_from = "screencapture"
|
340
|
+
read_from_secondary = "clipboard" if ss_clipboard else None
|
341
|
+
run.run(read_from=read_from,
|
342
|
+
read_from_secondary=read_from_secondary,
|
332
343
|
write_to="callback",
|
333
344
|
screen_capture_area=screen_area,
|
334
345
|
# screen_capture_monitor=monitor_config['index'],
|
@@ -405,7 +416,7 @@ def set_force_stable_hotkey():
|
|
405
416
|
|
406
417
|
if __name__ == "__main__":
|
407
418
|
try:
|
408
|
-
global ocr1, ocr2, twopassocr, language, ss_clipboard, ss, ocr_config, furigana_filter_sensitivity, area_select_ocr_hotkey, window, optimize_second_scan, use_window_for_config, keep_newline
|
419
|
+
global ocr1, ocr2, twopassocr, language, ss_clipboard, ss, ocr_config, furigana_filter_sensitivity, area_select_ocr_hotkey, window, optimize_second_scan, use_window_for_config, keep_newline, obs_ocr
|
409
420
|
import sys
|
410
421
|
|
411
422
|
import argparse
|
@@ -430,6 +441,7 @@ if __name__ == "__main__":
|
|
430
441
|
parser.add_argument("--use_window_for_config", action="store_true",
|
431
442
|
help="Use the specified window for loading OCR configuration")
|
432
443
|
parser.add_argument("--keep_newline", action="store_true", help="Keep new lines in OCR output")
|
444
|
+
parser.add_argument('--obs_ocr', action='store_true', help='Use OBS for Picture Source (not implemented)')
|
433
445
|
|
434
446
|
args = parser.parse_args()
|
435
447
|
|
@@ -449,12 +461,13 @@ if __name__ == "__main__":
|
|
449
461
|
optimize_second_scan = args.optimize_second_scan
|
450
462
|
use_window_for_config = args.use_window_for_config
|
451
463
|
keep_newline = args.keep_newline
|
464
|
+
obs_ocr = args.obs_ocr
|
452
465
|
|
453
466
|
window = None
|
454
467
|
logger.info(f"Received arguments: {vars(args)}")
|
455
468
|
# set_force_stable_hotkey()
|
456
469
|
ocr_config: OCRConfig = get_ocr_config(window=window_name, use_window_for_config=use_window_for_config)
|
457
|
-
if ocr_config:
|
470
|
+
if ocr_config and not obs_ocr:
|
458
471
|
if ocr_config.window:
|
459
472
|
start_time = time.time()
|
460
473
|
while time.time() - start_time < 30:
|
@@ -326,7 +326,6 @@ class GoogleLens:
|
|
326
326
|
# logger.info(f"Vertical space: {vertical_space}, Average height: {avg_height}")
|
327
327
|
# logger.info(avg_height * 2)
|
328
328
|
if vertical_space > avg_height * 2:
|
329
|
-
logger.info('Adding blank line')
|
330
329
|
res += 'BLANK_LINE'
|
331
330
|
for line in paragraph['lines']:
|
332
331
|
if furigana_filter_sensitivity:
|
@@ -55,7 +55,7 @@ except ImportError:
|
|
55
55
|
pass
|
56
56
|
from .config import Config
|
57
57
|
from .screen_coordinate_picker import get_screen_selection
|
58
|
-
from GameSentenceMiner.util.configuration import get_temporary_directory
|
58
|
+
from GameSentenceMiner.util.configuration import get_temporary_directory, get_config
|
59
59
|
|
60
60
|
config = None
|
61
61
|
|
@@ -763,6 +763,100 @@ class ScreenshotThread(threading.Thread):
|
|
763
763
|
elif self.windows_window_tracker_instance:
|
764
764
|
self.windows_window_tracker_instance.join()
|
765
765
|
|
766
|
+
# Use OBS for Screenshot Source (i.e. Linux)
|
767
|
+
class OBSScreenshotThread(threading.Thread):
|
768
|
+
def __init__(self, ocr_config, screen_capture_on_combo, width=1280, height=720, interval=1):
|
769
|
+
super().__init__(daemon=True)
|
770
|
+
self.ocr_config = ocr_config
|
771
|
+
self.interval = interval
|
772
|
+
self.obs_client = None
|
773
|
+
self.websocket = None
|
774
|
+
self.width = width
|
775
|
+
self.height = height
|
776
|
+
self.use_periodic_queue = not screen_capture_on_combo
|
777
|
+
|
778
|
+
def write_result(self, result):
|
779
|
+
if self.use_periodic_queue:
|
780
|
+
periodic_screenshot_queue.put(result)
|
781
|
+
else:
|
782
|
+
image_queue.put((result, True))
|
783
|
+
|
784
|
+
def connect_obs(self):
|
785
|
+
try:
|
786
|
+
import obsws_python as obs
|
787
|
+
self.obs_client = obs.ReqClient(
|
788
|
+
host=get_config().obs.host,
|
789
|
+
port=get_config().obs.port,
|
790
|
+
password=get_config().obs.password,
|
791
|
+
timeout=10
|
792
|
+
)
|
793
|
+
logger.info("Connected to OBS WebSocket.")
|
794
|
+
except Exception as e:
|
795
|
+
logger.error(f"Failed to connect to OBS: {e}")
|
796
|
+
self.obs_client = None
|
797
|
+
|
798
|
+
def run(self):
|
799
|
+
import base64
|
800
|
+
import io
|
801
|
+
from PIL import Image
|
802
|
+
import GameSentenceMiner.obs as obs
|
803
|
+
|
804
|
+
loop = asyncio.new_event_loop()
|
805
|
+
asyncio.set_event_loop(loop)
|
806
|
+
|
807
|
+
self.connect_obs()
|
808
|
+
self.ocr_config.scale_to_custom_size(self.width, self.height)
|
809
|
+
current_source = obs.get_active_source()
|
810
|
+
current_source_name = current_source.get('sourceName') if isinstance(current_source, dict) else None
|
811
|
+
|
812
|
+
while not terminated:
|
813
|
+
try:
|
814
|
+
response = self.obs_client.get_source_screenshot(
|
815
|
+
name=current_source_name,
|
816
|
+
img_format='png',
|
817
|
+
quality=75,
|
818
|
+
width=self.width,
|
819
|
+
height=self.height,
|
820
|
+
)
|
821
|
+
|
822
|
+
if response.image_data:
|
823
|
+
image_data = base64.b64decode(response.image_data.split(",")[1])
|
824
|
+
img = Image.open(io.BytesIO(image_data)).convert("RGBA")
|
825
|
+
|
826
|
+
for rectangle in self.ocr_config.rectangles:
|
827
|
+
if rectangle.is_excluded:
|
828
|
+
left, top, width, height = rectangle.coordinates
|
829
|
+
draw = ImageDraw.Draw(img)
|
830
|
+
draw.rectangle((left, top, left + width, top + height), fill=(0, 0, 0, 0))
|
831
|
+
|
832
|
+
cropped_sections = []
|
833
|
+
for rectangle in [r for r in self.ocr_config.rectangles if not r.is_excluded]:
|
834
|
+
area = rectangle.coordinates
|
835
|
+
cropped_sections.append(img.crop((area[0], area[1], area[0] + area[2], area[1] + area[3])))
|
836
|
+
|
837
|
+
if len(cropped_sections) > 1:
|
838
|
+
combined_width = max(section.width for section in cropped_sections)
|
839
|
+
combined_height = sum(section.height for section in cropped_sections) + (
|
840
|
+
len(cropped_sections) - 1) * 10
|
841
|
+
combined_img = Image.new("RGBA", (combined_width, combined_height))
|
842
|
+
y_offset = 0
|
843
|
+
for section in cropped_sections:
|
844
|
+
combined_img.paste(section, (0, y_offset))
|
845
|
+
y_offset += section.height + 50
|
846
|
+
img = combined_img
|
847
|
+
elif cropped_sections:
|
848
|
+
img = cropped_sections[0]
|
849
|
+
|
850
|
+
self.write_result(img)
|
851
|
+
else:
|
852
|
+
logger.error("Failed to get screenshot data from OBS.")
|
853
|
+
|
854
|
+
except Exception as e:
|
855
|
+
logger.error(f"An unexpected error occurred with OBS connection: {e}")
|
856
|
+
continue
|
857
|
+
|
858
|
+
time.sleep(self.interval)
|
859
|
+
|
766
860
|
class AutopauseTimer:
|
767
861
|
def __init__(self, timeout):
|
768
862
|
self.stop_event = threading.Event()
|
@@ -1137,7 +1231,7 @@ def run(read_from=None,
|
|
1137
1231
|
prefix_to_use = ""
|
1138
1232
|
delay_secs = config.get_general('delay_secs')
|
1139
1233
|
|
1140
|
-
non_path_inputs = ('screencapture', 'clipboard', 'websocket', 'unixsocket')
|
1234
|
+
non_path_inputs = ('screencapture', 'clipboard', 'websocket', 'unixsocket', 'obs')
|
1141
1235
|
read_from_path = None
|
1142
1236
|
read_from_readable = []
|
1143
1237
|
terminated = False
|
@@ -1176,22 +1270,33 @@ def run(read_from=None,
|
|
1176
1270
|
global txt_callback
|
1177
1271
|
txt_callback = text_callback
|
1178
1272
|
|
1179
|
-
if 'screencapture' in (read_from, read_from_secondary):
|
1180
|
-
global take_screenshot
|
1273
|
+
if 'screencapture' in (read_from, read_from_secondary) or 'obs' in (read_from, read_from_secondary):
|
1181
1274
|
global screenshot_event
|
1182
|
-
|
1183
|
-
last_result = ([], engine_index)
|
1275
|
+
global take_screenshot
|
1184
1276
|
if screen_capture_combo != '':
|
1185
1277
|
screen_capture_on_combo = True
|
1186
1278
|
key_combos[screen_capture_combo] = on_screenshot_combo
|
1187
1279
|
else:
|
1188
1280
|
global periodic_screenshot_queue
|
1189
1281
|
periodic_screenshot_queue = queue.Queue()
|
1282
|
+
|
1283
|
+
if 'screencapture' in (read_from, read_from_secondary):
|
1284
|
+
last_screenshot_time = 0
|
1285
|
+
last_result = ([], engine_index)
|
1286
|
+
|
1190
1287
|
screenshot_event = threading.Event()
|
1191
1288
|
screenshot_thread = ScreenshotThread(screen_capture_area, screen_capture_window, screen_capture_exclusions, screen_capture_only_active_windows, screen_capture_areas, screen_capture_on_combo)
|
1192
1289
|
screenshot_thread.start()
|
1193
1290
|
filtering = TextFiltering()
|
1194
1291
|
read_from_readable.append('screen capture')
|
1292
|
+
if 'obs' in (read_from, read_from_secondary):
|
1293
|
+
last_screenshot_time = 0
|
1294
|
+
last_result = ([], engine_index)
|
1295
|
+
screenshot_event = threading.Event()
|
1296
|
+
obs_screenshot_thread = OBSScreenshotThread(gsm_ocr_config, screen_capture_on_combo, interval=screen_capture_delay_secs)
|
1297
|
+
obs_screenshot_thread.start()
|
1298
|
+
filtering = TextFiltering()
|
1299
|
+
read_from_readable.append('obs')
|
1195
1300
|
if 'websocket' in (read_from, read_from_secondary):
|
1196
1301
|
read_from_readable.append('websocket')
|
1197
1302
|
if 'unixsocket' in (read_from, read_from_secondary):
|
@@ -1231,7 +1336,7 @@ def run(read_from=None,
|
|
1231
1336
|
write_to_readable = f'file {write_to}'
|
1232
1337
|
|
1233
1338
|
process_queue = (any(i in ('clipboard', 'websocket', 'unixsocket') for i in (read_from, read_from_secondary)) or read_from_path or screen_capture_on_combo)
|
1234
|
-
process_screenshots = 'screencapture' in (read_from, read_from_secondary) and not screen_capture_on_combo
|
1339
|
+
process_screenshots = any(x in ('screencapture', 'obs') for x in (read_from, read_from_secondary)) and not screen_capture_on_combo
|
1235
1340
|
if threading.current_thread() == threading.main_thread():
|
1236
1341
|
signal.signal(signal.SIGINT, signal_handler)
|
1237
1342
|
if (not process_screenshots) and auto_pause != 0:
|
@@ -1256,7 +1361,7 @@ def run(read_from=None,
|
|
1256
1361
|
pass
|
1257
1362
|
|
1258
1363
|
if (not img) and process_screenshots:
|
1259
|
-
if (not paused) and screenshot_thread.screencapture_window_active and screenshot_thread.screencapture_window_visible and (time.time() - last_screenshot_time) > screen_capture_delay_secs:
|
1364
|
+
if (not paused) and (not screenshot_thread or (screenshot_thread.screencapture_window_active and screenshot_thread.screencapture_window_visible)) and (time.time() - last_screenshot_time) > screen_capture_delay_secs:
|
1260
1365
|
screenshot_event.set()
|
1261
1366
|
img = periodic_screenshot_queue.get()
|
1262
1367
|
filter_img = True
|
@@ -3,7 +3,7 @@ GameSentenceMiner/anki.py,sha256=3BVFXAM7tpJAxHMbsMpnMHUoDfyqHQ1JSYJThW18QWA,168
|
|
3
3
|
GameSentenceMiner/config_gui.py,sha256=QTK1yBDcfHaIUR_JyekkRQY9CVI_rh3Cae0bi7lviIo,99198
|
4
4
|
GameSentenceMiner/gametext.py,sha256=6VkjmBeiuZfPk8T6PHFdIAElBH2Y_oLVYvmcafqN7RM,6747
|
5
5
|
GameSentenceMiner/gsm.py,sha256=wTERcvG37SeDel51TCFusoQqk5B_b11YY4QZMTF0a6s,24954
|
6
|
-
GameSentenceMiner/obs.py,sha256=
|
6
|
+
GameSentenceMiner/obs.py,sha256=rapxY9PTDczGr7e8_41hVuD5VoRExe3IFFbSWZcYDsQ,15470
|
7
7
|
GameSentenceMiner/vad.py,sha256=A3CvBQ67w3c7L8s7mTMxo6U_9ZQXlCToIpGUbePotfA,18321
|
8
8
|
GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
9
|
GameSentenceMiner/ai/ai_prompting.py,sha256=ojp7i_xg2YB1zALgFbivwtXPMVkThnSbPoUiAs-nz_g,25892
|
@@ -16,17 +16,17 @@ GameSentenceMiner/assets/icon512.png,sha256=HxUj2GHjyQsk8NV433256UxU9phPhtjCY-YB
|
|
16
16
|
GameSentenceMiner/assets/icon64.png,sha256=N8xgdZXvhqVQP9QUK3wX5iqxX9LxHljD7c-Bmgim6tM,9301
|
17
17
|
GameSentenceMiner/assets/pickaxe.png,sha256=VfIGyXyIZdzEnVcc4PmG3wszPMO1W4KCT7Q_nFK6eSE,1403829
|
18
18
|
GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
|
-
GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=
|
19
|
+
GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=76IuoOMsBxNvU8z8lixqz58YSZpenNVugnHjrUXgCf4,4963
|
20
20
|
GameSentenceMiner/ocr/ocrconfig.py,sha256=_tY8mjnzHMJrLS8E5pHqYXZjMuLoGKYgJwdhYgN-ny4,6466
|
21
|
-
GameSentenceMiner/ocr/owocr_area_selector.py,sha256=
|
22
|
-
GameSentenceMiner/ocr/owocr_helper.py,sha256=
|
21
|
+
GameSentenceMiner/ocr/owocr_area_selector.py,sha256=Aj6t-cCePPeYNSF-XxQKo2gVNWmWqK3f3qR-0vxdtuE,25523
|
22
|
+
GameSentenceMiner/ocr/owocr_helper.py,sha256=sxmZcori9_ujldclwQFpmMwTyfJyflAQ3mn_3BvIdQs,22888
|
23
23
|
GameSentenceMiner/ocr/ss_picker.py,sha256=0IhxUdaKruFpZyBL-8SpxWg7bPrlGpy3lhTcMMZ5rwo,5224
|
24
24
|
GameSentenceMiner/owocr/owocr/__init__.py,sha256=87hfN5u_PbL_onLfMACbc0F5j4KyIK9lKnRCj6oZgR0,49
|
25
25
|
GameSentenceMiner/owocr/owocr/__main__.py,sha256=XQaqZY99EKoCpU-gWQjNbTs7Kg17HvBVE7JY8LqIE0o,157
|
26
26
|
GameSentenceMiner/owocr/owocr/config.py,sha256=qM7kISHdUhuygGXOxmgU6Ef2nwBShrZtdqu4InDCViE,8103
|
27
27
|
GameSentenceMiner/owocr/owocr/lens_betterproto.py,sha256=oNoISsPilVVRBBPVDtb4-roJtAhp8ZAuFTci3TGXtMc,39141
|
28
|
-
GameSentenceMiner/owocr/owocr/ocr.py,sha256=
|
29
|
-
GameSentenceMiner/owocr/owocr/run.py,sha256=
|
28
|
+
GameSentenceMiner/owocr/owocr/ocr.py,sha256=xAhqCfVY2xKKvUhskAiAaYiL3yQrAl8oYi5GU46NOgI,59392
|
29
|
+
GameSentenceMiner/owocr/owocr/run.py,sha256=824KFS5v3c4ZLx7RYafBOezvFmnB4Idexf4mJAJhfp8,61100
|
30
30
|
GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=Na6XStbQBtpQUSdbN3QhEswtKuU1JjReFk_K8t5ezQE,3395
|
31
31
|
GameSentenceMiner/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
32
32
|
GameSentenceMiner/util/audio_offset_selector.py,sha256=8Stk3BP-XVIuzRv9nl9Eqd2D-1yD3JrgU-CamBywJmY,8542
|
@@ -62,9 +62,9 @@ GameSentenceMiner/web/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
|
|
62
62
|
GameSentenceMiner/web/templates/index.html,sha256=Gv3CJvNnhAzIVV_QxhNq4OD-pXDt1vKCu9k6WdHSXuA,215343
|
63
63
|
GameSentenceMiner/web/templates/text_replacements.html,sha256=tV5c8mCaWSt_vKuUpbdbLAzXZ3ATZeDvQ9PnnAfqY0M,8598
|
64
64
|
GameSentenceMiner/web/templates/utility.html,sha256=3flZinKNqUJ7pvrZk6xu__v67z44rXnaK7UTZ303R-8,16946
|
65
|
-
gamesentenceminer-2.11.
|
66
|
-
gamesentenceminer-2.11.
|
67
|
-
gamesentenceminer-2.11.
|
68
|
-
gamesentenceminer-2.11.
|
69
|
-
gamesentenceminer-2.11.
|
70
|
-
gamesentenceminer-2.11.
|
65
|
+
gamesentenceminer-2.11.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
66
|
+
gamesentenceminer-2.11.3.dist-info/METADATA,sha256=mn5TDp9ibeCZhzxmIdhs4JZnVHwZPWtY0JYJRShzYGQ,7319
|
67
|
+
gamesentenceminer-2.11.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
68
|
+
gamesentenceminer-2.11.3.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
69
|
+
gamesentenceminer-2.11.3.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
70
|
+
gamesentenceminer-2.11.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|