GameSentenceMiner 2.12.7.post1__tar.gz → 2.12.8__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 (77) hide show
  1. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/config_gui.py +14 -0
  2. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/gametext.py +67 -7
  3. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/ocr/owocr_area_selector.py +27 -14
  4. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/owocr/owocr/ocr.py +1 -1
  5. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/configuration.py +5 -0
  6. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/window_transparency.py +26 -8
  7. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner.egg-info/PKG-INFO +2 -2
  8. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner.egg-info/requires.txt +1 -1
  9. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/PKG-INFO +2 -2
  10. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/pyproject.toml +2 -2
  11. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/__init__.py +0 -0
  12. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/ai/__init__.py +0 -0
  13. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/ai/ai_prompting.py +0 -0
  14. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/anki.py +0 -0
  15. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/assets/__init__.py +0 -0
  16. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/assets/icon.png +0 -0
  17. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/assets/icon128.png +0 -0
  18. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/assets/icon256.png +0 -0
  19. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/assets/icon32.png +0 -0
  20. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/assets/icon512.png +0 -0
  21. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/assets/icon64.png +0 -0
  22. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/assets/pickaxe.png +0 -0
  23. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/gsm.py +0 -0
  24. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/obs.py +0 -0
  25. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/ocr/__init__.py +0 -0
  26. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/ocr/gsm_ocr_config.py +0 -0
  27. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/ocr/ocrconfig.py +0 -0
  28. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/ocr/owocr_helper.py +0 -0
  29. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/ocr/ss_picker.py +0 -0
  30. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/owocr/owocr/__init__.py +0 -0
  31. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/owocr/owocr/__main__.py +0 -0
  32. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/owocr/owocr/config.py +0 -0
  33. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -0
  34. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/owocr/owocr/run.py +0 -0
  35. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -0
  36. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/__init__.py +0 -0
  37. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/audio_offset_selector.py +0 -0
  38. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/communication/__init__.py +0 -0
  39. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/communication/send.py +0 -0
  40. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/communication/websocket.py +0 -0
  41. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/downloader/Untitled_json.py +0 -0
  42. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/downloader/__init__.py +0 -0
  43. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/downloader/download_tools.py +0 -0
  44. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/downloader/oneocr_dl.py +0 -0
  45. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/electron_config.py +0 -0
  46. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/ffmpeg.py +0 -0
  47. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/gsm_utils.py +0 -0
  48. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/model.py +0 -0
  49. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/notification.py +0 -0
  50. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/package.py +0 -0
  51. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/ss_selector.py +0 -0
  52. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/util/text_log.py +0 -0
  53. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/vad.py +0 -0
  54. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/web/__init__.py +0 -0
  55. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/web/service.py +0 -0
  56. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/web/static/__init__.py +0 -0
  57. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
  58. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/web/static/favicon-96x96.png +0 -0
  59. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/web/static/favicon.ico +0 -0
  60. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/web/static/favicon.svg +0 -0
  61. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/web/static/site.webmanifest +0 -0
  62. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/web/static/style.css +0 -0
  63. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
  64. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
  65. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/web/templates/__init__.py +0 -0
  66. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/web/templates/index.html +0 -0
  67. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/web/templates/text_replacements.html +0 -0
  68. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/web/templates/utility.html +0 -0
  69. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/web/texthooking_page.py +0 -0
  70. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner/wip/get_overlay_coords.py +0 -0
  71. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner.egg-info/SOURCES.txt +0 -0
  72. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner.egg-info/dependency_links.txt +0 -0
  73. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner.egg-info/entry_points.txt +0 -0
  74. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/GameSentenceMiner.egg-info/top_level.txt +0 -0
  75. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/LICENSE +0 -0
  76. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/README.md +0 -0
  77. {gamesentenceminer-2.12.7.post1 → gamesentenceminer-2.12.8}/setup.cfg +0 -0
@@ -265,6 +265,7 @@ class ConfigApp:
265
265
  use_websocket=self.websocket_enabled.get(),
266
266
  use_clipboard=self.clipboard_enabled.get(),
267
267
  websocket_uri=self.websocket_uri.get(),
268
+ merge_matching_sequential_text= self.merge_matching_sequential_text.get(),
268
269
  open_config_on_startup=self.open_config_on_startup.get(),
269
270
  open_multimine_on_startup=self.open_multimine_on_startup.get(),
270
271
  texthook_replacement_regex=self.texthook_replacement_regex.get(),
@@ -519,6 +520,19 @@ class ConfigApp:
519
520
  row=self.current_row, column=1,
520
521
  sticky='W', pady=2)
521
522
  self.current_row += 1
523
+
524
+ HoverInfoLabelWidget(self.general_tab, text="Merge Matching Sequential Text:",
525
+ foreground="red", font=("Helvetica", 10, "bold"),
526
+ tooltip="Enable to merge matching sequential text into a single entry. Designed for Luna's Speech Recognition feature. Very niche.",
527
+ row=self.current_row, column=0)
528
+
529
+ self.merge_matching_sequential_text = tk.BooleanVar(
530
+ value=self.settings.general.merge_matching_sequential_text)
531
+ ttk.Checkbutton(self.general_tab, variable=self.merge_matching_sequential_text,
532
+ bootstyle="round-toggle").grid(
533
+ row=self.current_row, column=1,
534
+ sticky='W', pady=2)
535
+ self.current_row += 1
522
536
 
523
537
  HoverInfoLabelWidget(self.general_tab, text="Websocket URI(s):",
524
538
  tooltip="WebSocket URI for connecting. Allows Comma Separated Values for Connecting Multiple.",
@@ -5,6 +5,7 @@ import pyperclip
5
5
  import requests
6
6
  import websockets
7
7
  from websockets import InvalidStatus
8
+ from rapidfuzz import fuzz
8
9
 
9
10
  from GameSentenceMiner.util.gsm_utils import do_text_replacements, TEXT_REPLACEMENTS_FILE, run_new_thread
10
11
  from GameSentenceMiner.util.configuration import *
@@ -14,9 +15,15 @@ from GameSentenceMiner.web.texthooking_page import add_event_to_texthooker, send
14
15
  if get_config().wip.overlay_websocket_send:
15
16
  import GameSentenceMiner.wip.get_overlay_coords as get_overlay_coords
16
17
 
18
+
17
19
  current_line = ''
18
20
  current_line_after_regex = ''
19
21
  current_line_time = datetime.now()
22
+ # Track the start time for the current sequence
23
+ current_sequence_start_time = None
24
+ # Track the last raw clipboard text for prefix comparison
25
+ last_raw_clipboard = ''
26
+ timer = None
20
27
 
21
28
  last_clipboard = ''
22
29
 
@@ -84,8 +91,12 @@ async def listen_websockets():
84
91
  line_time = datetime.fromisoformat(data["time"])
85
92
  except json.JSONDecodeError or TypeError:
86
93
  current_clipboard = message
94
+ logger.info
87
95
  if current_clipboard != current_line:
88
- await handle_new_text_event(current_clipboard, line_time if line_time else None)
96
+ try:
97
+ await handle_new_text_event(current_clipboard, line_time if line_time else None)
98
+ except Exception as e:
99
+ logger.error(f"Error handling new text event: {e}", exc_info=True)
89
100
  except (websockets.ConnectionClosed, ConnectionError, InvalidStatus, ConnectionResetError, Exception) as e:
90
101
  if websocket_url in gsm_status.websockets_connected:
91
102
  gsm_status.websockets_connected.remove(websocket_url)
@@ -112,26 +123,75 @@ async def listen_websockets():
112
123
  websocket_tasks.append(listen_on_websocket(f"localhost:{get_config().advanced.ocr_websocket_port}"))
113
124
 
114
125
  await asyncio.gather(*websocket_tasks)
126
+
127
+
128
+ async def merge_sequential_lines(line, start_time=None):
129
+ if not get_config().general.merge_matching_sequential_text:
130
+ return
131
+ logger.info(f"Merging Sequential Lines: {line}")
132
+ # Use the sequence start time for the merged line
133
+ await add_line_to_text_log(line, start_time if start_time else datetime.now())
134
+ timer = None
135
+ # Reset sequence tracking
136
+ current_sequence_start_time = None
137
+ last_raw_clipboard = ''
138
+
139
+ def schedule_merge(wait, coro, args):
140
+ async def wrapper():
141
+ await asyncio.sleep(wait)
142
+ await coro(*args)
143
+ task = asyncio.create_task(wrapper())
144
+ return task
145
+
115
146
 
116
147
  async def handle_new_text_event(current_clipboard, line_time=None):
117
- global current_line, current_line_time, current_line_after_regex
148
+ global current_line, current_line_time, current_line_after_regex, timer, current_sequence_start_time, last_raw_clipboard
118
149
  current_line = current_clipboard
150
+ logger.info(f"Current Line: {current_line} last raw clipboard: {last_raw_clipboard}")
151
+ # Only apply this logic if merging is enabled
152
+ if get_config().general.merge_matching_sequential_text:
153
+ logger.info(f"Handling new text event: {current_line}")
154
+ # If no timer is active, this is the start of a new sequence
155
+ if not timer:
156
+ logger.info("Starting a new sequence of text lines.")
157
+ current_sequence_start_time = line_time if line_time else datetime.now()
158
+ last_raw_clipboard = current_line
159
+ # Start the timer
160
+ timer = schedule_merge(2, merge_sequential_lines, [current_line[:], current_sequence_start_time])
161
+ else:
162
+ # If the new text starts with the previous, reset the timer (do not update start time)
163
+ if current_line.startswith(last_raw_clipboard) or fuzz.ratio(current_line, last_raw_clipboard) > 50:
164
+ logger.info(f"Current line starts with last raw clipboard: {current_line} starts with {last_raw_clipboard}")
165
+ last_raw_clipboard = current_line
166
+ timer.cancel()
167
+ timer = schedule_merge(2, merge_sequential_lines, [current_line[:], current_sequence_start_time])
168
+ else:
169
+ logger.info(f"Current line does not start with last raw clipboard: {current_line} does not start with {last_raw_clipboard}")
170
+ # If not a prefix, treat as a new sequence
171
+ # timer.cancel()
172
+ current_sequence_start_time = line_time if line_time else datetime.now()
173
+ last_raw_clipboard = current_line
174
+ timer = schedule_merge(2, merge_sequential_lines, [current_line[:], current_sequence_start_time])
175
+ else:
176
+ await add_line_to_text_log(current_line, line_time)
177
+
178
+
179
+ async def add_line_to_text_log(line, line_time=None):
119
180
  if get_config().general.texthook_replacement_regex:
120
- current_line_after_regex = re.sub(get_config().general.texthook_replacement_regex, '', current_line)
181
+ current_line_after_regex = re.sub(get_config().general.texthook_replacement_regex, '', line)
121
182
  else:
122
- current_line_after_regex = current_line
123
- current_line_after_regex = do_text_replacements(current_line, TEXT_REPLACEMENTS_FILE)
183
+ current_line_after_regex = line
184
+ current_line_after_regex = do_text_replacements(current_line_after_regex, TEXT_REPLACEMENTS_FILE)
124
185
  logger.info(f"Line Received: {current_line_after_regex}")
125
186
  current_line_time = line_time if line_time else datetime.now()
126
187
  gsm_status.last_line_received = current_line_time.strftime("%Y-%m-%d %H:%M:%S")
127
- add_line(current_line_after_regex, line_time)
188
+ add_line(current_line_after_regex, line_time if line_time else datetime.now())
128
189
  if len(get_text_log().values) > 0:
129
190
  await add_event_to_texthooker(get_text_log()[-1])
130
191
  if get_config().wip.overlay_websocket_port and get_config().wip.overlay_websocket_send and overlay_server_thread.has_clients():
131
192
  boxes = await find_box_for_sentence(current_line_after_regex)
132
193
  if boxes:
133
194
  await send_word_coordinates_to_overlay(boxes)
134
-
135
195
 
136
196
  async def find_box_for_sentence(sentence):
137
197
  boxes = []
@@ -13,6 +13,7 @@ from PIL import Image, ImageTk
13
13
  from GameSentenceMiner import obs
14
14
  from GameSentenceMiner.ocr.gsm_ocr_config import set_dpi_awareness, get_window, get_scene_ocr_config_path
15
15
  from GameSentenceMiner.util.gsm_utils import sanitize_filename
16
+ from GameSentenceMiner.util.configuration import logger
16
17
 
17
18
  try:
18
19
  import tkinter as tk
@@ -95,6 +96,7 @@ class ScreenSelector:
95
96
  self.image_mode = True
96
97
  self.redo_stack = []
97
98
  self.bounding_box = {} # Geometry of the single large canvas window
99
+ self.instructions_showing = True
98
100
 
99
101
  self.canvas = None
100
102
  self.window = None
@@ -282,11 +284,10 @@ class ScreenSelector:
282
284
  tk.Button(button_frame, text="Redo (Ctrl+Y)", command=canvas_event_wrapper(self.redo_last_rect)).pack(fill=tk.X, pady=2)
283
285
  tk.Button(button_frame, text="Toggle Background (M)", command=root_event_wrapper(self.toggle_image_mode)).pack(fill=tk.X, pady=2)
284
286
  tk.Button(button_frame, text="Quit without Saving (Esc)", command=root_event_wrapper(self.quit_app)).pack(fill=tk.X, pady=2)
287
+ tk.Button(button_frame, text="Toggle Instructions (I)", command=canvas_event_wrapper(self.toggle_instructions)).pack(fill=tk.X, pady=2)
285
288
 
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)
289
+ # hotkeys_text = "\n• I: Toggle this instruction panel"
290
+ # tk.Label(main_frame, text=hotkeys_text, justify=tk.LEFT, anchor="w").pack(pady=(10, 0), fill=tk.X)
290
291
 
291
292
 
292
293
  # --- NEW METHOD TO DISPLAY INSTRUCTIONS ---
@@ -310,7 +311,7 @@ class ScreenSelector:
310
311
  instruction_font = tkfont.Font(family="Segoe UI", size=10, weight="normal")
311
312
 
312
313
  # Create the text item first to get its size
313
- text_id = canvas.create_text(
314
+ self.instructions_overlay = canvas.create_text(
314
315
  20, 20, # Position with a small margin
315
316
  text=instructions_text,
316
317
  anchor=tk.NW,
@@ -320,10 +321,10 @@ class ScreenSelector:
320
321
  )
321
322
 
322
323
  # Get the bounding box of the text to draw a background
323
- text_bbox = canvas.bbox(text_id)
324
+ text_bbox = canvas.bbox(self.instructions_overlay)
324
325
 
325
326
  # Create a background rectangle with padding
326
- rect_id = canvas.create_rectangle(
327
+ self.instructions_rect = canvas.create_rectangle(
327
328
  text_bbox[0] - 10, # left
328
329
  text_bbox[1] - 10, # top
329
330
  text_bbox[2] + 10, # right
@@ -334,15 +335,27 @@ class ScreenSelector:
334
335
  )
335
336
 
336
337
  # Lower the rectangle so it's behind the text
337
- canvas.tag_lower(rect_id, text_id)
338
+ canvas.tag_lower(self.instructions_rect, self.instructions_overlay)
339
+
338
340
 
339
341
  def toggle_instructions(self, event=None):
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")
342
+ canvas = event.widget.winfo_toplevel().winfo_children()[0]
343
+ for element in [self.instructions_overlay, self.instructions_rect]:
344
+ current_state = canvas.itemcget(element, 'state')
345
+ new_state = tk.NORMAL if current_state == tk.HIDDEN else tk.HIDDEN
346
+ canvas.itemconfigure(element, state=new_state)
347
+
348
+ # if self.instructions_showing:
349
+ # self.instructions_widget.withdraw()
350
+ # logger.info(f"Toggled instructions visibility: OFF")
351
+ # self.instructions_showing = False
352
+ # else:
353
+ # self.instructions_widget.deiconify()
354
+ # self.instructions_widget.lift()
355
+ # self.canvas.focus_set()
356
+ # self.instructions_widget.update_idletasks() # Ensure it is fully rendered
357
+ # logger.info("Toggled instructions visibility: ON")
358
+ # self.instructions_showing = True
346
359
 
347
360
  def start(self):
348
361
  self.root = tk.Tk()
@@ -11,7 +11,6 @@ import json
11
11
  import base64
12
12
  from urllib.parse import urlparse, parse_qs
13
13
 
14
- import jaconv
15
14
  import numpy as np
16
15
  import rapidfuzz.fuzz
17
16
  from PIL import Image
@@ -95,6 +94,7 @@ def empty_post_process(text):
95
94
 
96
95
 
97
96
  def post_process(text, keep_blank_lines=False):
97
+ import jaconv
98
98
  if keep_blank_lines:
99
99
  text = '\n'.join([''.join(i.split()) for i in text.splitlines()])
100
100
  else:
@@ -312,6 +312,7 @@ class General:
312
312
  use_websocket: bool = True
313
313
  use_clipboard: bool = True
314
314
  use_both_clipboard_and_websocket: bool = False
315
+ merge_matching_sequential_text: bool = False
315
316
  websocket_uri: str = 'localhost:6677,localhost:9001,localhost:2333'
316
317
  open_config_on_startup: bool = False
317
318
  open_multimine_on_startup: bool = True
@@ -530,6 +531,10 @@ class WIP:
530
531
  overlay_websocket_port: int = 55499
531
532
  overlay_websocket_send: bool = False
532
533
  monitor_to_capture: int = 0
534
+
535
+ def __post_init__(self):
536
+ if self.monitor_to_capture == -1:
537
+ self.monitor_to_capture = 0 # Default to the first monitor if not set
533
538
 
534
539
 
535
540
 
@@ -65,18 +65,21 @@ def reset_window_state(hwnd):
65
65
 
66
66
  # --- Hotkey Callback (equivalent to AHK ^!y::) ---
67
67
 
68
- def toggle_functionality():
68
+ def toggle_functionality(window_hwnd=None):
69
69
  """
70
70
  This function is called when the hotkey is pressed.
71
71
  It manages the toggling logic.
72
72
  """
73
73
  global is_toggled, target_hwnd
74
-
75
- # Get the currently focused window (equivalent to WinGetID("A"))
76
- current_hwnd = win32gui.GetForegroundWindow()
77
- if not current_hwnd:
78
- logger.info("No window is currently active!")
79
- return
74
+
75
+ if window_hwnd:
76
+ current_hwnd = window_hwnd
77
+ else:
78
+ # Get the currently focused window (equivalent to WinGetID("A"))
79
+ current_hwnd = win32gui.GetForegroundWindow()
80
+ if not current_hwnd:
81
+ logger.info("No window is currently active!")
82
+ return
80
83
 
81
84
  with state_lock:
82
85
  # Case 1: The hotkey is pressed on the currently toggled window to disable it.
@@ -167,8 +170,23 @@ if __name__ == "__main__":
167
170
  # get hotkey from args
168
171
  parser = argparse.ArgumentParser(description="Window Transparency Toggle Script")
169
172
  parser.add_argument('--hotkey', type=str, default=HOTKEY, help='Hotkey to toggle transparency (default: ctrl+alt+y)')
173
+ parser.add_argument('--window', type=str, help='Window title to target (optional)')
170
174
 
171
- hotkey = parser.parse_args().hotkey.lower()
175
+ args = parser.parse_args()
176
+ hotkey = args.hotkey.lower()
177
+ target_window_title = args.window
178
+
179
+ if target_window_title:
180
+ # Find the window by title if specified
181
+ target_hwnd = win32gui.FindWindow(None, target_window_title)
182
+ logger.info(f"Searching for window with title: {target_window_title}")
183
+ logger.info(f"Target HWND: {target_hwnd}")
184
+ if not target_hwnd:
185
+ logger.error(f"Window with title '{target_window_title}' not found.")
186
+ sys.exit(1)
187
+ else:
188
+ logger.info(f"Target window found: {target_window_title}")
189
+ toggle_functionality(target_hwnd) # Enable functionality for the specified window
172
190
 
173
191
  # Register the global hotkey
174
192
  keyboard.add_hotkey(hotkey, toggle_functionality)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.12.7.post1
3
+ Version: 2.12.8
4
4
  Summary: A tool for mining sentences from games. Update: Overlay?
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
@@ -29,7 +29,7 @@ Requires-Dist: silero-vad~=5.1.2
29
29
  Requires-Dist: ttkbootstrap~=1.10.1
30
30
  Requires-Dist: dataclasses_json~=0.6.7
31
31
  Requires-Dist: win10toast; sys_platform == "win32"
32
- Requires-Dist: numpy
32
+ Requires-Dist: numpy==2.2.6
33
33
  Requires-Dist: pystray
34
34
  Requires-Dist: pywin32; sys_platform == "win32"
35
35
  Requires-Dist: pygetwindow; sys_platform == "win32"
@@ -14,7 +14,7 @@ stable-ts-whisperless
14
14
  silero-vad~=5.1.2
15
15
  ttkbootstrap~=1.10.1
16
16
  dataclasses_json~=0.6.7
17
- numpy
17
+ numpy==2.2.6
18
18
  pystray
19
19
  flask
20
20
  groq
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.12.7.post1
3
+ Version: 2.12.8
4
4
  Summary: A tool for mining sentences from games. Update: Overlay?
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
@@ -29,7 +29,7 @@ Requires-Dist: silero-vad~=5.1.2
29
29
  Requires-Dist: ttkbootstrap~=1.10.1
30
30
  Requires-Dist: dataclasses_json~=0.6.7
31
31
  Requires-Dist: win10toast; sys_platform == "win32"
32
- Requires-Dist: numpy
32
+ Requires-Dist: numpy==2.2.6
33
33
  Requires-Dist: pystray
34
34
  Requires-Dist: pywin32; sys_platform == "win32"
35
35
  Requires-Dist: pygetwindow; sys_platform == "win32"
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "GameSentenceMiner"
10
- version = "2.12.7.post1"
10
+ version = "2.12.8"
11
11
  description = "A tool for mining sentences from games. Update: Overlay?"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.10"
@@ -39,7 +39,7 @@ dependencies = [
39
39
  "ttkbootstrap~=1.10.1",
40
40
  "dataclasses_json~=0.6.7",
41
41
  "win10toast; sys_platform == 'win32'",
42
- "numpy",
42
+ "numpy==2.2.6",
43
43
  "pystray",
44
44
  "pywin32; sys_platform == 'win32'",
45
45
  "pygetwindow; sys_platform == 'win32'",