GameSentenceMiner 2.12.7.post1__py3-none-any.whl → 2.12.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 = []
GameSentenceMiner/gsm.py CHANGED
@@ -597,7 +597,7 @@ async def async_main(reloading=False):
597
597
  observer = Observer()
598
598
  observer.schedule(VideoToAudioHandler(), get_config().paths.folder_to_watch, recursive=False)
599
599
  observer.start()
600
- if not is_linux():
600
+ if not is_windows():
601
601
  register_hotkeys()
602
602
 
603
603
  run_new_thread(post_init2)
@@ -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
 
@@ -19,6 +19,7 @@ if is_windows():
19
19
  else:
20
20
  notifier = notification
21
21
 
22
+
22
23
  def open_browser_window(note_id, query=None):
23
24
  url = "http://localhost:8765"
24
25
  headers = {'Content-Type': 'application/json'}
@@ -75,17 +76,21 @@ def open_anki_card(note_id):
75
76
  logger.info(f"Error connecting to AnkiConnect: {e}")
76
77
 
77
78
 
78
-
79
79
  def send_notification(title, message, timeout):
80
- if is_windows():
81
- notifier.show_toast(title, message, duration=timeout, threaded=True)
82
- else:
83
- notification.notify(
84
- title=title,
85
- message=message,
86
- app_name="GameSentenceMiner",
87
- timeout=timeout # Notification disappears after 5 seconds
88
- )
80
+ try:
81
+ if is_windows():
82
+ notifier.show_toast(
83
+ title, message, duration=timeout, threaded=True)
84
+ else:
85
+ notification.notify(
86
+ title=title,
87
+ message=message,
88
+ app_name="GameSentenceMiner",
89
+ timeout=timeout # Notification disappears after 5 seconds
90
+ )
91
+ except Exception as e:
92
+ logger.error(f"Failed to send notification: {e}")
93
+
89
94
 
90
95
  def send_note_updated(tango):
91
96
  send_notification(
@@ -94,6 +99,7 @@ def send_note_updated(tango):
94
99
  timeout=5 # Notification disappears after 5 seconds
95
100
  )
96
101
 
102
+
97
103
  def send_screenshot_updated(tango):
98
104
  send_notification(
99
105
  title="Anki Card Updated",
@@ -132,5 +138,3 @@ def send_error_no_anki_update():
132
138
  message=f"Anki Card not updated, Check Console for Reason!",
133
139
  timeout=5 # Notification disappears after 5 seconds
134
140
  )
135
-
136
-
@@ -127,7 +127,7 @@ def get_text_event(last_note) -> GameLine:
127
127
  lines = game_log.values
128
128
 
129
129
  if not lines:
130
- raise Exception("No lines in history. Text is required from either clipboard or websocket for GSM to work. Please check your setup/config.")
130
+ raise Exception("No voicelines in GSM. GSM can only do work on text that has been sent to it since it started. If you are not getting any text into GSM, please check your setup/config.")
131
131
 
132
132
  if not last_note:
133
133
  return lines[-1]
@@ -168,6 +168,8 @@ def get_mined_line(last_note: AnkiCard, lines=None):
168
168
  return lines[-1]
169
169
  if not lines:
170
170
  lines = get_all_lines()
171
+ if not lines:
172
+ raise Exception("No voicelines in GSM. GSM can only do work on text that has been sent to it since it started. If you are not getting any text into GSM, please check your setup/config.")
171
173
 
172
174
  sentence = last_note.get_field(get_config().anki.sentence_field)
173
175
  for line in reversed(lines):
@@ -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)
GameSentenceMiner/vad.py CHANGED
@@ -47,6 +47,8 @@ class VADSystem:
47
47
  else:
48
48
  logger.info(result.trim_successful_string())
49
49
  return result
50
+ else:
51
+ return VADResult(True, 0, get_audio_length(input_audio), "OFF", [], input_audio)
50
52
 
51
53
 
52
54
  def _do_vad_processing(self, model, input_audio, output_audio, game_line):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.12.7.post1
3
+ Version: 2.12.9
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"
@@ -1,10 +1,10 @@
1
1
  GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  GameSentenceMiner/anki.py,sha256=FUwcWO0-arzfQjejQmDKP7pNNakhboo8InQ4s_jv6AY,19099
3
- GameSentenceMiner/config_gui.py,sha256=tRLYj8a7uKgPdO9VZCaAo1eUs0aQZAexpAAfLIyRCZA,104812
4
- GameSentenceMiner/gametext.py,sha256=fIm28ZvRzKvnVHj86TmSYR2QQifo_Lk6cx4UptIltLs,7844
5
- GameSentenceMiner/gsm.py,sha256=GGF0owRrrYJgdfXx-INwfuKbaoY-G5gLllE-sNrwYnI,25341
3
+ GameSentenceMiner/config_gui.py,sha256=UCipZVAVupJeA8Kaa1tqTBsZpErNIIePJabqKh4SI7s,105693
4
+ GameSentenceMiner/gametext.py,sha256=TYlkgM5-J2o8-WCKypSUitmKq_UcjOGpsZBINiR-mWk,10875
5
+ GameSentenceMiner/gsm.py,sha256=XfoxdN31rNUF5xPdCSamBlqIX3MPdOyoYWTPKNhWttE,25343
6
6
  GameSentenceMiner/obs.py,sha256=bMVWAPQ6QLf4celLiOsL9BUO8pTdMn9lpT9fQCNfm7Q,18718
7
- GameSentenceMiner/vad.py,sha256=zo9JpuEOCXczPXM-dq8lbr-zM-MPpfJ8aajggR3mKk4,18710
7
+ GameSentenceMiner/vad.py,sha256=-Q1KtDJnT8zRFeEc4LLyAECf07YOUM15UDRrnWkuDgo,18817
8
8
  GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  GameSentenceMiner/ai/ai_prompting.py,sha256=iHkEx2pQJ-tEyejOgYy4G0DcZc8qvBugVL6-CQpPSME,26089
10
10
  GameSentenceMiner/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -18,28 +18,28 @@ GameSentenceMiner/assets/pickaxe.png,sha256=VfIGyXyIZdzEnVcc4PmG3wszPMO1W4KCT7Q_
18
18
  GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=Ezj-0k6Wo-una91FvYhMp6KGkRhWYihXzLAoh_Wu2xY,5329
20
20
  GameSentenceMiner/ocr/ocrconfig.py,sha256=_tY8mjnzHMJrLS8E5pHqYXZjMuLoGKYgJwdhYgN-ny4,6466
21
- GameSentenceMiner/ocr/owocr_area_selector.py,sha256=O8qKOTDglk-D4N-2_ORLeZacXT-OVOCNxUI8sQHAlx4,25538
21
+ GameSentenceMiner/ocr/owocr_area_selector.py,sha256=seTO8kUCTW445qAUo4c7mvLtiv1SWeElpSUpP9DPDOA,26331
22
22
  GameSentenceMiner/ocr/owocr_helper.py,sha256=To_tIGPgj68QkEGasQuh5vK6Zg-BLqJmkVHusp-q4Bs,25427
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=Zii5r15ZlHFJWSbmXpva6QJVGkU3j2wT5Q0izazLyCQ,63021
28
+ GameSentenceMiner/owocr/owocr/ocr.py,sha256=jjm7TGIOaR-7JfoP_4B24uW7tLsxJL36s2kFndJy_SA,63025
29
29
  GameSentenceMiner/owocr/owocr/run.py,sha256=BvUmJAb1YboDn3FNS6jP92i3gmYjTE_uGJi-_qG_LdY,65962
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
33
- GameSentenceMiner/util/configuration.py,sha256=QIjSN0NfrucaK0ddhGcXqIYemBzPNtpNA3PN2SSs_PM,35991
33
+ GameSentenceMiner/util/configuration.py,sha256=M9pMUykKhwlp5sEfIip9Ikakdl_im6ArItBWeCUcgaY,36199
34
34
  GameSentenceMiner/util/electron_config.py,sha256=8LZwl-T_uF5z_ig-IZcm9QI-VKaD7zaHX9u6MaLYuo4,8648
35
35
  GameSentenceMiner/util/ffmpeg.py,sha256=t0tflxq170n8PZKkdw8fTZIUQfXD0p_qARa9JTdhBTc,21530
36
36
  GameSentenceMiner/util/gsm_utils.py,sha256=iRyLVcodMptRhkCzLf3hyqc6_RCktXnwApi6mLju6oQ,11565
37
37
  GameSentenceMiner/util/model.py,sha256=hmA_seopP2bK40v9T4ulua9TrAeWtbkdCv-sTBPBQDk,6660
38
- GameSentenceMiner/util/notification.py,sha256=0OnEYjn3DUEZ6c6OtPjdVZe-DG-QSoMAl9fetjjCvNU,3874
38
+ GameSentenceMiner/util/notification.py,sha256=uZbaIpyNE2GSiGOT64sKLkHL1AShifqo6vYgZhH6CKo,4021
39
39
  GameSentenceMiner/util/package.py,sha256=u1ym5z869lw5EHvIviC9h9uH97bzUXSXXA8KIn8rUvk,1157
40
40
  GameSentenceMiner/util/ss_selector.py,sha256=cbjMxiKOCuOfbRvLR_PCRlykBrGtm1LXd6u5czPqkmc,4793
41
- GameSentenceMiner/util/text_log.py,sha256=Gbm8H2DHUX4u0QBnbjs3jOfzkS6_5WBczgD3mwant7Q,6031
42
- GameSentenceMiner/util/window_transparency.py,sha256=hmeQYqK3mUEh47hZ8pODldUbxCC5eluMddanXfC_epQ,7325
41
+ GameSentenceMiner/util/text_log.py,sha256=38b5Qrluev2zTxoKZ1BS06DBsoycZQ5HB0YYGeFF1Q4,6290
42
+ GameSentenceMiner/util/window_transparency.py,sha256=GtbxbmZg0-UYPXhfHff-7IKZyY2DKe4B9GdyovfmpeM,8166
43
43
  GameSentenceMiner/util/communication/__init__.py,sha256=xh__yn2MhzXi9eLi89PeZWlJPn-cbBSjskhi1BRraXg,643
44
44
  GameSentenceMiner/util/communication/send.py,sha256=Wki9qIY2CgYnuHbmnyKVIYkcKAN_oYS4up93XMikBaI,222
45
45
  GameSentenceMiner/util/communication/websocket.py,sha256=TbphRGmxVrgEupS7tNdifsmQfWDfIp0Hio2cSiUKgsk,3317
@@ -64,9 +64,9 @@ GameSentenceMiner/web/templates/index.html,sha256=Gv3CJvNnhAzIVV_QxhNq4OD-pXDt1v
64
64
  GameSentenceMiner/web/templates/text_replacements.html,sha256=tV5c8mCaWSt_vKuUpbdbLAzXZ3ATZeDvQ9PnnAfqY0M,8598
65
65
  GameSentenceMiner/web/templates/utility.html,sha256=3flZinKNqUJ7pvrZk6xu__v67z44rXnaK7UTZ303R-8,16946
66
66
  GameSentenceMiner/wip/get_overlay_coords.py,sha256=_re9zfyuFryZAUKbMQ1LAfQBDIRUmq_1kniisN7J7xE,19793
67
- gamesentenceminer-2.12.7.post1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
68
- gamesentenceminer-2.12.7.post1.dist-info/METADATA,sha256=aZ_GdrwU5ScSTkITk8AJmwNA_V20uSb1dAO3oXRQNHg,7067
69
- gamesentenceminer-2.12.7.post1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
70
- gamesentenceminer-2.12.7.post1.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
71
- gamesentenceminer-2.12.7.post1.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
72
- gamesentenceminer-2.12.7.post1.dist-info/RECORD,,
67
+ gamesentenceminer-2.12.9.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
68
+ gamesentenceminer-2.12.9.dist-info/METADATA,sha256=hxe3hqm5pm3PUW6BKpAynDpz6lMOSIBMR3E0ndiIkng,7068
69
+ gamesentenceminer-2.12.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
70
+ gamesentenceminer-2.12.9.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
71
+ gamesentenceminer-2.12.9.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
72
+ gamesentenceminer-2.12.9.dist-info/RECORD,,