GameSentenceMiner 2.10.8__tar.gz → 2.10.10__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/gsm.py +1 -1
- gamesentenceminer-2.10.10/GameSentenceMiner/ocr/owocr_area_selector.py +374 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner.egg-info/PKG-INFO +1 -1
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/PKG-INFO +1 -1
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/pyproject.toml +1 -1
- gamesentenceminer-2.10.8/GameSentenceMiner/ocr/owocr_area_selector.py +0 -905
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/__init__.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/ai/__init__.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/ai/ai_prompting.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/anki.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/assets/__init__.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/assets/icon.png +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/assets/icon128.png +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/assets/icon256.png +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/assets/icon32.png +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/assets/icon512.png +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/assets/icon64.png +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/assets/pickaxe.png +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/config_gui.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/gametext.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/obs.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/ocr/__init__.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/ocr/gsm_ocr_config.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/ocr/ocrconfig.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/ocr/owocr_helper.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/ocr/ss_picker.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/owocr/owocr/__init__.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/owocr/owocr/__main__.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/owocr/owocr/config.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/owocr/owocr/ocr.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/owocr/owocr/run.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/__init__.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/audio_offset_selector.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/communication/__init__.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/communication/send.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/communication/websocket.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/configuration.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/downloader/Untitled_json.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/downloader/__init__.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/downloader/download_tools.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/downloader/oneocr_dl.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/electron_config.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/ffmpeg.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/gsm_utils.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/model.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/notification.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/package.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/ss_selector.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/util/text_log.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/vad.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/web/__init__.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/web/service.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/web/static/__init__.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/web/static/favicon-96x96.png +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/web/static/favicon.ico +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/web/static/favicon.svg +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/web/static/site.webmanifest +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/web/static/style.css +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/web/templates/__init__.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/web/templates/index.html +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/web/templates/text_replacements.html +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/web/templates/utility.html +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner/web/texthooking_page.py +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner.egg-info/SOURCES.txt +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner.egg-info/dependency_links.txt +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner.egg-info/entry_points.txt +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner.egg-info/requires.txt +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/GameSentenceMiner.egg-info/top_level.txt +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/LICENSE +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/README.md +0 -0
- {gamesentenceminer-2.10.8 → gamesentenceminer-2.10.10}/setup.cfg +0 -0
@@ -200,7 +200,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
200
200
|
return vad_result
|
201
201
|
if vad_result.output_audio:
|
202
202
|
vad_trimmed_audio = vad_result.output_audio
|
203
|
-
if
|
203
|
+
if get_config().audio.ffmpeg_reencode_options_to_use and os.path.exists(vad_trimmed_audio):
|
204
204
|
ffmpeg.reencode_file_with_user_config(vad_trimmed_audio, final_audio_output,
|
205
205
|
get_config().audio.ffmpeg_reencode_options_to_use)
|
206
206
|
elif os.path.exists(vad_trimmed_audio):
|
@@ -0,0 +1,374 @@
|
|
1
|
+
import ctypes
|
2
|
+
import json
|
3
|
+
import sys
|
4
|
+
from multiprocessing import Process, Manager
|
5
|
+
from pathlib import Path
|
6
|
+
|
7
|
+
import mss
|
8
|
+
from PIL import Image, ImageTk
|
9
|
+
|
10
|
+
# Assuming a mock or real obs module exists in this path
|
11
|
+
from GameSentenceMiner import obs
|
12
|
+
from GameSentenceMiner.ocr.gsm_ocr_config import set_dpi_awareness, get_window
|
13
|
+
from GameSentenceMiner.util.gsm_utils import sanitize_filename
|
14
|
+
|
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
|
+
try:
|
22
|
+
import tkinter as tk
|
23
|
+
|
24
|
+
selector_available = True
|
25
|
+
except ImportError:
|
26
|
+
print("Error: tkinter library not found. GUI selection is unavailable.")
|
27
|
+
selector_available = False
|
28
|
+
|
29
|
+
MIN_RECT_WIDTH = 25
|
30
|
+
MIN_RECT_HEIGHT = 25
|
31
|
+
|
32
|
+
COORD_SYSTEM_PERCENTAGE = "percentage"
|
33
|
+
|
34
|
+
|
35
|
+
class ScreenSelector:
|
36
|
+
def __init__(self, result, window_name):
|
37
|
+
if not selector_available or not gw:
|
38
|
+
raise RuntimeError("tkinter or pygetwindow is not available.")
|
39
|
+
if not window_name:
|
40
|
+
raise ValueError("A target window name is required for percentage-based coordinates.")
|
41
|
+
|
42
|
+
obs.connect_to_obs_sync()
|
43
|
+
self.window_name = window_name
|
44
|
+
print(f"Targeting window: '{window_name}'")
|
45
|
+
|
46
|
+
self.sct = mss.mss()
|
47
|
+
self.monitors = self.sct.monitors[1:]
|
48
|
+
if not self.monitors:
|
49
|
+
raise RuntimeError("No monitors found by mss.")
|
50
|
+
for i, monitor in enumerate(self.monitors):
|
51
|
+
monitor['index'] = i
|
52
|
+
|
53
|
+
# --- Window Awareness is now critical ---
|
54
|
+
self.target_window = self._find_target_window()
|
55
|
+
self.target_window_geometry = self._get_window_geometry(self.target_window)
|
56
|
+
if not self.target_window_geometry:
|
57
|
+
raise RuntimeError(f"Could not find or get geometry for window '{self.window_name}'.")
|
58
|
+
print(f"Found target window at: {self.target_window_geometry}")
|
59
|
+
# ---
|
60
|
+
|
61
|
+
self.root = None
|
62
|
+
self.result = result
|
63
|
+
self.rectangles = [] # Internal storage is ALWAYS absolute pixels for drawing
|
64
|
+
self.drawn_rect_ids = []
|
65
|
+
self.current_rect_id = None
|
66
|
+
self.start_x = self.start_y = None
|
67
|
+
self.image_mode = True
|
68
|
+
self.redo_stack = []
|
69
|
+
self.bounding_box = {} # Geometry of the single large canvas window
|
70
|
+
|
71
|
+
self.load_existing_rectangles()
|
72
|
+
|
73
|
+
def _find_target_window(self):
|
74
|
+
try:
|
75
|
+
return get_window(self.window_name)
|
76
|
+
except Exception as e:
|
77
|
+
print(f"Error finding window '{self.window_name}': {e}")
|
78
|
+
return None
|
79
|
+
|
80
|
+
def _get_window_geometry(self, window):
|
81
|
+
if window:
|
82
|
+
try:
|
83
|
+
# Ensure width/height are positive and non-zero
|
84
|
+
width = max(1, window.width)
|
85
|
+
height = max(1, window.height)
|
86
|
+
return {"left": window.left, "top": window.top, "width": width, "height": height}
|
87
|
+
except Exception:
|
88
|
+
return None
|
89
|
+
return None
|
90
|
+
|
91
|
+
def get_scene_ocr_config(self):
|
92
|
+
app_dir = Path.home() / "AppData" / "Roaming" / "GameSentenceMiner"
|
93
|
+
ocr_config_dir = app_dir / "ocr_config"
|
94
|
+
ocr_config_dir.mkdir(parents=True, exist_ok=True)
|
95
|
+
try:
|
96
|
+
scene = sanitize_filename(obs.get_current_scene() or "default_scene")
|
97
|
+
except Exception as e:
|
98
|
+
print(f"Error getting OBS scene: {e}. Using default config name.")
|
99
|
+
scene = "default_scene"
|
100
|
+
return ocr_config_dir / f"{scene}.json"
|
101
|
+
|
102
|
+
def load_existing_rectangles(self):
|
103
|
+
"""Loads rectangles from config, converting from percentage to absolute pixels for use."""
|
104
|
+
config_path = self.get_scene_ocr_config()
|
105
|
+
win_geom = self.target_window_geometry # Use current geometry for conversion
|
106
|
+
win_w, win_h, win_l, win_t = win_geom['width'], win_geom['height'], win_geom['left'], win_geom['top']
|
107
|
+
|
108
|
+
try:
|
109
|
+
with open(config_path, 'r', encoding='utf-8') as f:
|
110
|
+
config_data = json.load(f)
|
111
|
+
|
112
|
+
if config_data.get("coordinate_system") != COORD_SYSTEM_PERCENTAGE:
|
113
|
+
print(
|
114
|
+
f"Warning: Config file '{config_path}' does not use '{COORD_SYSTEM_PERCENTAGE}' system. Please re-create selections.")
|
115
|
+
return
|
116
|
+
|
117
|
+
print(f"Loading rectangles from {config_path}...")
|
118
|
+
self.rectangles = []
|
119
|
+
loaded_count = 0
|
120
|
+
|
121
|
+
for rect_data in config_data.get("rectangles", []):
|
122
|
+
try:
|
123
|
+
coords_pct = rect_data["coordinates"]
|
124
|
+
x_pct, y_pct, w_pct, h_pct = map(float, coords_pct)
|
125
|
+
|
126
|
+
# Convert from percentage to absolute pixel coordinates
|
127
|
+
x_abs = (x_pct * win_w) + win_l
|
128
|
+
y_abs = (y_pct * win_h) + win_t
|
129
|
+
w_abs = w_pct * win_w
|
130
|
+
h_abs = h_pct * win_h
|
131
|
+
abs_coords = (int(x_abs), int(y_abs), int(w_abs), int(h_abs))
|
132
|
+
|
133
|
+
monitor_index = rect_data["monitor"]['index']
|
134
|
+
target_monitor = next((m for m in self.monitors if m['index'] == monitor_index), None)
|
135
|
+
if target_monitor:
|
136
|
+
self.rectangles.append((target_monitor, abs_coords, rect_data["is_excluded"]))
|
137
|
+
loaded_count += 1
|
138
|
+
except (KeyError, ValueError, TypeError) as e:
|
139
|
+
print(f"Skipping malformed rectangle data: {rect_data}, Error: {e}")
|
140
|
+
|
141
|
+
print(f"Loaded {loaded_count} valid rectangles.")
|
142
|
+
except FileNotFoundError:
|
143
|
+
print(f"No config found at {config_path}. Starting fresh.")
|
144
|
+
except Exception as e:
|
145
|
+
print(f"Error loading config: {e}. Starting fresh.")
|
146
|
+
|
147
|
+
def save_rects(self, event=None):
|
148
|
+
"""Saves rectangles to config, converting from absolute pixels to percentages."""
|
149
|
+
config_path = self.get_scene_ocr_config()
|
150
|
+
win_geom = self.target_window_geometry
|
151
|
+
win_l, win_t, win_w, win_h = win_geom['left'], win_geom['top'], win_geom['width'], win_geom['height']
|
152
|
+
print(f"Saving rectangles to: {config_path} relative to window: {win_geom}")
|
153
|
+
|
154
|
+
serializable_rects = []
|
155
|
+
for monitor_dict, abs_coords, is_excluded in self.rectangles:
|
156
|
+
x_abs, y_abs, w_abs, h_abs = abs_coords
|
157
|
+
|
158
|
+
# Convert absolute pixel coordinates to percentages
|
159
|
+
x_pct = (x_abs - win_l) / win_w
|
160
|
+
y_pct = (y_abs - win_t) / win_h
|
161
|
+
w_pct = w_abs / win_w
|
162
|
+
h_pct = h_abs / win_h
|
163
|
+
coords_to_save = [x_pct, y_pct, w_pct, h_pct]
|
164
|
+
|
165
|
+
serializable_rects.append({
|
166
|
+
"monitor": {'index': monitor_dict['index']},
|
167
|
+
"coordinates": coords_to_save,
|
168
|
+
"is_excluded": is_excluded
|
169
|
+
})
|
170
|
+
|
171
|
+
save_data = {
|
172
|
+
"scene": obs.get_current_scene() or "default_scene",
|
173
|
+
"window": self.window_name,
|
174
|
+
"coordinate_system": COORD_SYSTEM_PERCENTAGE, # Always save as percentage
|
175
|
+
"window_geometry": win_geom, # Save the geometry used for conversion
|
176
|
+
"rectangles": serializable_rects
|
177
|
+
}
|
178
|
+
|
179
|
+
with open(config_path, 'w', encoding="utf-8") as f:
|
180
|
+
json.dump(save_data, f, indent=4, ensure_ascii=False)
|
181
|
+
|
182
|
+
print(f"Successfully saved {len(serializable_rects)} rectangles.")
|
183
|
+
# Pass back the internal absolute coords for any immediate post-processing
|
184
|
+
self.result['rectangles'] = [(r[0], list(r[1]), r[2]) for r in self.rectangles]
|
185
|
+
self.result['window_geometry'] = win_geom
|
186
|
+
self.result['coordinate_system'] = COORD_SYSTEM_PERCENTAGE
|
187
|
+
self.quit_app()
|
188
|
+
|
189
|
+
def undo_last_rect(self, event=None):
|
190
|
+
if self.rectangles and self.drawn_rect_ids:
|
191
|
+
last_rect_tuple = self.rectangles.pop()
|
192
|
+
last_rect_id = self.drawn_rect_ids.pop()
|
193
|
+
self.redo_stack.append((*last_rect_tuple, last_rect_id))
|
194
|
+
event.widget.winfo_toplevel().winfo_children()[0].delete(last_rect_id)
|
195
|
+
print("Undo: Removed last rectangle.")
|
196
|
+
|
197
|
+
def redo_last_rect(self, event=None):
|
198
|
+
if not self.redo_stack: return
|
199
|
+
monitor, abs_coords, is_excluded, old_rect_id = self.redo_stack.pop()
|
200
|
+
canvas = event.widget.winfo_toplevel().winfo_children()[0]
|
201
|
+
x_abs, y_abs, w_abs, h_abs = abs_coords
|
202
|
+
canvas_x, canvas_y = x_abs - self.bounding_box['left'], y_abs - self.bounding_box['top']
|
203
|
+
new_rect_id = canvas.create_rectangle(canvas_x, canvas_y, canvas_x + w_abs, canvas_y + h_abs,
|
204
|
+
outline='orange' if is_excluded else 'green', width=2)
|
205
|
+
self.rectangles.append((monitor, abs_coords, is_excluded))
|
206
|
+
self.drawn_rect_ids.append(new_rect_id)
|
207
|
+
print("Redo: Restored rectangle.")
|
208
|
+
|
209
|
+
def start(self):
|
210
|
+
self.root = tk.Tk()
|
211
|
+
self.root.withdraw()
|
212
|
+
|
213
|
+
# Calculate bounding box of all monitors
|
214
|
+
left = min(m['left'] for m in self.monitors)
|
215
|
+
top = min(m['top'] for m in self.monitors)
|
216
|
+
right = max(m['left'] + m['width'] for m in self.monitors)
|
217
|
+
bottom = max(m['top'] + m['height'] for m in self.monitors)
|
218
|
+
self.bounding_box = {'left': left, 'top': top, 'width': right - left, 'height': bottom - top}
|
219
|
+
|
220
|
+
sct_img = self.sct.grab(self.sct.monitors[0])
|
221
|
+
img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
|
222
|
+
|
223
|
+
window = tk.Toplevel(self.root)
|
224
|
+
window.geometry(f"{self.bounding_box['width']}x{self.bounding_box['height']}+{left}+{top}")
|
225
|
+
window.overrideredirect(1)
|
226
|
+
window.attributes('-topmost', 1)
|
227
|
+
|
228
|
+
self.photo_image = ImageTk.PhotoImage(img)
|
229
|
+
canvas = tk.Canvas(window, cursor='cross', highlightthickness=0)
|
230
|
+
canvas.pack(fill=tk.BOTH, expand=True)
|
231
|
+
canvas.create_image(0, 0, image=self.photo_image, anchor=tk.NW)
|
232
|
+
|
233
|
+
# Draw existing rectangles (which were converted to absolute pixels on load)
|
234
|
+
for _, abs_coords, is_excluded in self.rectangles:
|
235
|
+
x_abs, y_abs, w_abs, h_abs = abs_coords
|
236
|
+
canvas_x = x_abs - self.bounding_box['left']
|
237
|
+
canvas_y = y_abs - self.bounding_box['top']
|
238
|
+
rect_id = canvas.create_rectangle(canvas_x, canvas_y, canvas_x + w_abs, canvas_y + h_abs,
|
239
|
+
outline='orange' if is_excluded else 'green', width=2)
|
240
|
+
self.drawn_rect_ids.append(rect_id)
|
241
|
+
|
242
|
+
def on_click(event):
|
243
|
+
self.start_x, self.start_y = event.x, event.y
|
244
|
+
outline = 'purple' if bool(event.state & 0x0001) else 'red'
|
245
|
+
self.current_rect_id = canvas.create_rectangle(self.start_x, self.start_y, self.start_x, self.start_y,
|
246
|
+
outline=outline, width=2)
|
247
|
+
|
248
|
+
def on_drag(event):
|
249
|
+
if self.current_rect_id: canvas.coords(self.current_rect_id, self.start_x, self.start_y, event.x, event.y)
|
250
|
+
|
251
|
+
def on_release(event):
|
252
|
+
if not self.current_rect_id: return
|
253
|
+
coords = canvas.coords(self.current_rect_id)
|
254
|
+
x_abs = int(min(coords[0], coords[2]) + self.bounding_box['left'])
|
255
|
+
y_abs = int(min(coords[1], coords[3]) + self.bounding_box['top'])
|
256
|
+
w, h = int(abs(coords[2] - coords[0])), int(abs(coords[3] - coords[1]))
|
257
|
+
|
258
|
+
if w >= MIN_RECT_WIDTH and h >= MIN_RECT_HEIGHT:
|
259
|
+
is_excl = bool(event.state & 0x0001)
|
260
|
+
canvas.itemconfig(self.current_rect_id, outline='orange' if is_excl else 'green')
|
261
|
+
|
262
|
+
center_x, center_y = x_abs + w / 2, y_abs + h / 2
|
263
|
+
target_mon = self.monitors[0]
|
264
|
+
for mon in self.monitors:
|
265
|
+
if mon['left'] <= center_x < mon['left'] + mon['width'] and mon['top'] <= center_y < mon['top'] + \
|
266
|
+
mon['height']:
|
267
|
+
target_mon = mon
|
268
|
+
break
|
269
|
+
|
270
|
+
self.rectangles.append((target_mon, (x_abs, y_abs, w, h), is_excl))
|
271
|
+
self.drawn_rect_ids.append(self.current_rect_id)
|
272
|
+
self.redo_stack.clear()
|
273
|
+
else:
|
274
|
+
canvas.delete(self.current_rect_id)
|
275
|
+
self.current_rect_id = self.start_x = self.start_y = None
|
276
|
+
|
277
|
+
def on_right_click(event):
|
278
|
+
items = canvas.find_closest(event.x, event.y)
|
279
|
+
if items and items[0] in self.drawn_rect_ids:
|
280
|
+
item_id = items[0]
|
281
|
+
idx_to_del = self.drawn_rect_ids.index(item_id)
|
282
|
+
del self.drawn_rect_ids[idx_to_del]
|
283
|
+
del self.rectangles[idx_to_del]
|
284
|
+
self.redo_stack.clear()
|
285
|
+
canvas.delete(item_id)
|
286
|
+
|
287
|
+
def toggle_image_mode(e=None):
|
288
|
+
self.image_mode = not self.image_mode; window.attributes("-alpha", 1.0 if self.image_mode else 0.25)
|
289
|
+
|
290
|
+
def on_enter(e=None):
|
291
|
+
canvas.focus_set()
|
292
|
+
|
293
|
+
canvas.bind('<Enter>', on_enter)
|
294
|
+
canvas.bind('<ButtonPress-1>', on_click)
|
295
|
+
canvas.bind('<B1-Motion>', on_drag)
|
296
|
+
canvas.bind('<ButtonRelease-1>', on_release)
|
297
|
+
canvas.bind('<Button-3>', on_right_click)
|
298
|
+
canvas.bind('<Control-s>', self.save_rects)
|
299
|
+
canvas.bind('<Control-z>', self.undo_last_rect)
|
300
|
+
canvas.bind('<Control-y>', self.redo_last_rect)
|
301
|
+
canvas.bind("<Escape>", self.quit_app)
|
302
|
+
canvas.bind("<m>", toggle_image_mode)
|
303
|
+
|
304
|
+
canvas.focus_set()
|
305
|
+
print("Starting UI. Press Esc to quit, Ctrl+S to save, M to toggle background.")
|
306
|
+
self.root.mainloop()
|
307
|
+
|
308
|
+
def quit_app(self, event=None):
|
309
|
+
if self.root and self.root.winfo_exists(): self.root.destroy()
|
310
|
+
self.root = None
|
311
|
+
|
312
|
+
|
313
|
+
def run_screen_selector(result_dict, window_name):
|
314
|
+
try:
|
315
|
+
selector = ScreenSelector(result_dict, window_name)
|
316
|
+
selector.start()
|
317
|
+
except Exception as e:
|
318
|
+
print(f"Error in selector process: {e}", file=sys.stderr)
|
319
|
+
import traceback
|
320
|
+
traceback.print_exc()
|
321
|
+
result_dict['error'] = str(e)
|
322
|
+
|
323
|
+
|
324
|
+
def get_screen_selection(window_name):
|
325
|
+
if not selector_available or not gw: return None
|
326
|
+
if not window_name:
|
327
|
+
print("Error: A target window name must be provided.", file=sys.stderr)
|
328
|
+
return None
|
329
|
+
|
330
|
+
with Manager() as manager:
|
331
|
+
result_data = manager.dict()
|
332
|
+
process = Process(target=run_screen_selector, args=(result_data, window_name))
|
333
|
+
print(f"Starting ScreenSelector process...")
|
334
|
+
process.start()
|
335
|
+
process.join()
|
336
|
+
|
337
|
+
if 'error' in result_data:
|
338
|
+
print(f"Selector process failed: {result_data['error']}", file=sys.stderr)
|
339
|
+
return None
|
340
|
+
elif 'rectangles' in result_data:
|
341
|
+
print("Screen selection successful.")
|
342
|
+
return dict(result_data)
|
343
|
+
else:
|
344
|
+
print("Selection was cancelled by the user.")
|
345
|
+
return {}
|
346
|
+
|
347
|
+
|
348
|
+
if __name__ == "__main__":
|
349
|
+
set_dpi_awareness()
|
350
|
+
target_window_title = "Windowed Projector (Preview)" # Default
|
351
|
+
if len(sys.argv) > 1:
|
352
|
+
target_window_title = sys.argv[1]
|
353
|
+
# else:
|
354
|
+
# print("Usage: python your_script_name.py \"Target Window Title\"", file=sys.stderr)
|
355
|
+
# print("Example: python selector.py \"Windowed Projector (Preview)\"", file=sys.stderr)
|
356
|
+
# sys.exit(1)
|
357
|
+
|
358
|
+
selection_result = get_screen_selection(target_window_title)
|
359
|
+
|
360
|
+
if selection_result is None:
|
361
|
+
print("\n--- Screen selection failed. ---")
|
362
|
+
elif not selection_result:
|
363
|
+
print("\n--- Screen selection cancelled. ---")
|
364
|
+
elif 'rectangles' in selection_result:
|
365
|
+
print("\n--- Selection Result ---")
|
366
|
+
rects = selection_result.get('rectangles', [])
|
367
|
+
win_geom = selection_result.get('window_geometry')
|
368
|
+
print(f"Saved relative to window: {win_geom}")
|
369
|
+
print(f"Selected rectangles ({len(rects)}):")
|
370
|
+
# The returned coordinates are absolute pixels for immediate use
|
371
|
+
for i, (monitor, coords, is_excluded) in enumerate(rects):
|
372
|
+
coord_str = f"(X:{coords[0]}, Y:{coords[1]}, W:{coords[2]}, H:{coords[3]})"
|
373
|
+
print(
|
374
|
+
f" Rect {i + 1}: On Monitor Idx:{monitor.get('index', 'N/A')}, Coords={coord_str}, Excluded={is_excluded}")
|
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
|
7
7
|
|
8
8
|
[project]
|
9
9
|
name = "GameSentenceMiner"
|
10
|
-
version = "2.10.
|
10
|
+
version = "2.10.10"
|
11
11
|
description = "A tool for mining sentences from games. Update: Full UI Re-design"
|
12
12
|
readme = "README.md"
|
13
13
|
requires-python = ">=3.10"
|