GameSentenceMiner 2.16.12__tar.gz → 2.17.0__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 (103) hide show
  1. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/anki.py +1 -0
  2. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/gametext.py +2 -1
  3. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/obs.py +29 -2
  4. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/ocr/owocr_area_selector.py +1 -0
  5. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/get_overlay_coords.py +78 -11
  6. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/database_api.py +4 -4
  7. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/js/shared.js +4 -4
  8. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner.egg-info/PKG-INFO +1 -1
  9. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/PKG-INFO +1 -1
  10. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/pyproject.toml +2 -2
  11. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/__init__.py +0 -0
  12. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/ai/__init__.py +0 -0
  13. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/ai/ai_prompting.py +0 -0
  14. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/assets/__init__.py +0 -0
  15. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/assets/icon.png +0 -0
  16. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/assets/icon128.png +0 -0
  17. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/assets/icon256.png +0 -0
  18. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/assets/icon32.png +0 -0
  19. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/assets/icon512.png +0 -0
  20. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/assets/icon64.png +0 -0
  21. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/assets/pickaxe.png +0 -0
  22. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/config_gui.py +1 -1
  23. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/gsm.py +0 -0
  24. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/locales/en_us.json +0 -0
  25. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/locales/ja_jp.json +0 -0
  26. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/locales/zh_cn.json +0 -0
  27. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/ocr/__init__.py +0 -0
  28. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/ocr/gsm_ocr_config.py +0 -0
  29. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/ocr/ocrconfig.py +0 -0
  30. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/ocr/owocr_helper.py +0 -0
  31. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/ocr/ss_picker.py +0 -0
  32. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/owocr/owocr/__init__.py +0 -0
  33. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/owocr/owocr/__main__.py +0 -0
  34. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/owocr/owocr/config.py +0 -0
  35. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -0
  36. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/owocr/owocr/ocr.py +0 -0
  37. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/owocr/owocr/run.py +0 -0
  38. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -0
  39. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/tools/__init__.py +0 -0
  40. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/tools/audio_offset_selector.py +0 -0
  41. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/tools/furigana_filter_preview.py +0 -0
  42. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/tools/ss_selector.py +0 -0
  43. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/tools/window_transparency.py +0 -0
  44. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/__init__.py +0 -0
  45. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/communication/__init__.py +0 -0
  46. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/communication/send.py +0 -0
  47. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/communication/websocket.py +0 -0
  48. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/configuration.py +0 -0
  49. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/db.py +0 -0
  50. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/downloader/Untitled_json.py +0 -0
  51. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/downloader/__init__.py +0 -0
  52. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/downloader/download_tools.py +0 -0
  53. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/downloader/oneocr_dl.py +0 -0
  54. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/electron_config.py +0 -0
  55. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/ffmpeg.py +0 -0
  56. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/gsm_utils.py +0 -0
  57. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/model.py +0 -0
  58. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/notification.py +0 -0
  59. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/text_log.py +0 -0
  60. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/win10toast/__init__.py +0 -0
  61. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/util/win10toast/__main__.py +0 -0
  62. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/vad.py +0 -0
  63. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/__init__.py +0 -0
  64. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/events.py +0 -0
  65. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/gsm_websocket.py +0 -0
  66. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/service.py +0 -0
  67. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/__init__.py +0 -0
  68. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
  69. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/css/kanji-grid.css +0 -0
  70. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/css/search.css +0 -0
  71. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/css/shared.css +0 -0
  72. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/css/stats.css +0 -0
  73. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/favicon-96x96.png +0 -0
  74. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/favicon.ico +0 -0
  75. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/favicon.svg +0 -0
  76. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/js/anki_stats.js +0 -0
  77. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/js/database.js +0 -0
  78. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/js/kanji-grid.js +0 -0
  79. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/js/search.js +0 -0
  80. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/js/stats.js +0 -0
  81. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/site.webmanifest +0 -0
  82. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/style.css +0 -0
  83. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
  84. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
  85. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/stats.py +0 -0
  86. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/templates/anki_stats.html +0 -0
  87. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/templates/components/navigation.html +0 -0
  88. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/templates/components/theme-styles.html +0 -0
  89. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/templates/database.html +0 -0
  90. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/templates/index.html +0 -0
  91. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/templates/search.html +0 -0
  92. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/templates/stats.html +0 -0
  93. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/templates/utility.html +0 -0
  94. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/web/texthooking_page.py +0 -0
  95. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner/wip/__init___.py +0 -0
  96. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner.egg-info/SOURCES.txt +0 -0
  97. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner.egg-info/dependency_links.txt +0 -0
  98. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner.egg-info/entry_points.txt +0 -0
  99. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner.egg-info/requires.txt +0 -0
  100. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/GameSentenceMiner.egg-info/top_level.txt +0 -0
  101. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/LICENSE +0 -0
  102. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/README.md +0 -0
  103. {gamesentenceminer-2.16.12 → gamesentenceminer-2.17.0}/setup.cfg +0 -0
@@ -490,6 +490,7 @@ def update_card_from_same_sentence(last_card, lines, game_line):
490
490
  card_queue.pop(0)
491
491
  logger.error(f"Error saving replay buffer: {e}")
492
492
  return
493
+ return
493
494
  anki_result = anki_results[game_line.id]
494
495
  if anki_result.success:
495
496
  note, last_card = get_initial_card_info(last_card, lines)
@@ -83,6 +83,7 @@ async def listen_websockets():
83
83
  line_time = None
84
84
  while True:
85
85
  message = await websocket.recv()
86
+ message_received_time = datetime.now()
86
87
  if not message:
87
88
  continue
88
89
  logger.debug(message)
@@ -97,7 +98,7 @@ async def listen_websockets():
97
98
  logger.info
98
99
  if current_clipboard != current_line:
99
100
  try:
100
- await handle_new_text_event(current_clipboard, line_time if line_time else None)
101
+ await handle_new_text_event(current_clipboard, line_time if line_time else message_received_time)
101
102
  except Exception as e:
102
103
  logger.error(f"Error handling new text event: {e}", exc_info=True)
103
104
  except (websockets.ConnectionClosed, ConnectionError, InvalidStatus, ConnectionResetError, Exception) as e:
@@ -6,6 +6,7 @@ import threading
6
6
  import time
7
7
  import logging
8
8
  import contextlib
9
+ import shutil
9
10
 
10
11
  import psutil
11
12
 
@@ -189,13 +190,24 @@ def start_obs():
189
190
  except ValueError:
190
191
  print("Invalid PID found in file. Launching new OBS instance.")
191
192
  except OSError:
192
- print("No process found with the stored PID. Launching new OBS instance.")
193
+ print("No process found with the stored PID. Launching new OBS instance.")
193
194
 
194
195
  obs_path = get_obs_path()
195
196
  if not os.path.exists(obs_path):
196
197
  print(f"OBS not found at {obs_path}. Please install OBS.")
197
198
  return None
198
199
  try:
200
+ sentinel_folder = os.path.join(configuration.get_app_directory(), 'obs-studio', 'config', 'obs-studio', '.sentinel')
201
+ if os.path.exists(sentinel_folder):
202
+ try:
203
+ if os.path.isdir(sentinel_folder):
204
+ shutil.rmtree(sentinel_folder)
205
+ else:
206
+ os.remove(sentinel_folder)
207
+ print(f"Deleted sentinel folder: {sentinel_folder}")
208
+ except Exception as e:
209
+ print(f"Failed to delete sentinel folder: {e}")
210
+
199
211
  obs_process = subprocess.Popen([obs_path, '--disable-shutdown-check', '--portable', '--startreplaybuffer', ], cwd=os.path.dirname(obs_path))
200
212
  obs_process_pid = obs_process.pid
201
213
  with open(OBS_PID_FILE, "w") as f:
@@ -663,8 +675,23 @@ def main():
663
675
  update_current_game()
664
676
  print("Testing `get_current_game`:", get_current_game())
665
677
  disconnect_from_obs()
678
+
679
+ def create_scene():
680
+ with connection_pool.get_client() as client:
681
+ # Extract fields from request_json
682
+ request_json = r'{"sceneName":"SILENT HILL f","inputName":"SILENT HILL f - Capture","inputKind":"window_capture","inputSettings":{"mode":"window","window":"SILENT HILL f :UnrealWindow:SHf-Win64-Shipping.exe","capture_audio":true,"cursor":false,"method":"2"}}'
683
+ request_dict = json.loads(request_json)
684
+ scene_name = request_dict.get('sceneName')
685
+ input_name = request_dict.get('inputName')
686
+ input_kind = request_dict.get('inputKind')
687
+ input_settings = request_dict.get('inputSettings')
688
+ input_settings['method'] = 2
689
+ # Remove sceneName from request_dict if needed for create_input
690
+ request_dict.pop('sceneName', None)
691
+ response = client.create_input(inputName=input_name, inputKind=input_kind, sceneName=scene_name, inputSettings=input_settings, sceneItemEnabled=True)
666
692
 
667
693
  if __name__ == '__main__':
668
694
  logging.basicConfig(level=logging.INFO)
669
695
  connect_to_obs_sync()
670
- set_fit_to_screen_for_scene_items(get_current_scene())
696
+ # set_fit_to_screen_for_scene_items(get_current_scene())
697
+ create_scene()
@@ -328,6 +328,7 @@ class ScreenSelector:
328
328
  "How to Use:\n"
329
329
  " • Left Click + Drag: Create a capture area (green).\n"
330
330
  " • Shift + Left Click + Drag: Create an exclusion area (orange).\n"
331
+ " • Ctrl + Left Click + Drag: Create a secondary (menu) area (purple).\n"
331
332
  " • Right-Click on a box: Delete it.\n\n"
332
333
  "Hotkeys:\n"
333
334
  " • Ctrl + S: Save and Quit\n"
@@ -9,11 +9,11 @@ import time
9
9
  from PIL import Image
10
10
  from typing import Dict, Any, List, Tuple
11
11
  import json
12
- from rapidfuzz.distance import Levenshtein
12
+ from rapidfuzz import fuzz
13
13
 
14
14
  # Local application imports
15
15
  from GameSentenceMiner.ocr.gsm_ocr_config import set_dpi_awareness
16
- from GameSentenceMiner.util.configuration import OverlayEngine, get_config, is_windows, is_beangate, logger
16
+ from GameSentenceMiner.util.configuration import OverlayEngine, get_config, get_temporary_directory, is_windows, is_beangate, logger
17
17
  from GameSentenceMiner.util.electron_config import get_ocr_language
18
18
  from GameSentenceMiner.obs import get_screenshot_PIL
19
19
  from GameSentenceMiner.web.texthooking_page import send_word_coordinates_to_overlay
@@ -93,7 +93,8 @@ class OverlayThread(threading.Thread):
93
93
  super().__init__()
94
94
  self.overlay_processor = OverlayProcessor()
95
95
  self.loop = asyncio.new_event_loop()
96
- self.daemon = True # Ensure thread exits when main program exits
96
+ self.daemon = True
97
+ self.first_time_run = True
97
98
 
98
99
  def run(self):
99
100
  """Runs the overlay processing loop."""
@@ -103,11 +104,18 @@ class OverlayThread(threading.Thread):
103
104
  async def overlay_loop(self):
104
105
  """Main loop to periodically process and send overlay data."""
105
106
  while True:
106
- if get_config().overlay.periodic and overlay_server_thread.has_clients():
107
- await self.overlay_processor.find_box_and_send_to_overlay('')
108
- await asyncio.sleep(get_config().overlay.periodic_interval) # Adjust the interval as needed
107
+ if overlay_server_thread.has_clients():
108
+ if get_config().overlay.periodic:
109
+ await self.overlay_processor.find_box_and_send_to_overlay('')
110
+ await asyncio.sleep(get_config().overlay.periodic_interval)
111
+ elif self.first_time_run:
112
+ await self.overlay_processor.find_box_and_send_to_overlay('')
113
+ self.first_time_run = False
114
+ else:
115
+ await asyncio.sleep(3)
109
116
  else:
110
- await asyncio.sleep(3) # Sleep briefly when not active
117
+ self.first_time_run = True
118
+ await asyncio.sleep(3)
111
119
 
112
120
  class OverlayProcessor:
113
121
  """
@@ -125,6 +133,8 @@ class OverlayProcessor:
125
133
  self.lens = None
126
134
  self.regex = None
127
135
  self.ready = False
136
+ self.last_oneocr_result = None
137
+ self.last_lens_result = None
128
138
 
129
139
  try:
130
140
  if self.config.overlay.websocket_port and all([GoogleLens, get_regex]):
@@ -221,6 +231,8 @@ class OverlayProcessor:
221
231
  monitor = self.get_monitor_workarea(0) # Get primary monitor work area
222
232
  sct_img = sct.grab(monitor)
223
233
  img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
234
+
235
+ img.save(os.path.join(get_temporary_directory(), "latest_overlay_screenshot.png"))
224
236
 
225
237
  return img, monitor['width'], monitor['height']
226
238
 
@@ -261,6 +273,8 @@ class OverlayProcessor:
261
273
  paste_x = math.floor(x1)
262
274
  paste_y = math.floor(y1)
263
275
  composite_img.paste(cropped_image, (paste_x, paste_y))
276
+
277
+ composite_img.save(os.path.join(get_temporary_directory(), "latest_overlay_screenshot_trimmed.png"))
264
278
 
265
279
  return composite_img
266
280
 
@@ -277,7 +291,7 @@ class OverlayProcessor:
277
291
  return []
278
292
  if self.oneocr:
279
293
  # 2. Use OneOCR to find general text areas (fast)
280
- _, _, oneocr_results, crop_coords_list = self.oneocr(
294
+ res, text, oneocr_results, crop_coords_list = self.oneocr(
281
295
  full_screenshot,
282
296
  return_coords=True,
283
297
  multiple_crop_coords=True,
@@ -285,8 +299,19 @@ class OverlayProcessor:
285
299
  furigana_filter_sensitivity=None, # Disable furigana filtering
286
300
  )
287
301
 
302
+ text_str = "".join([text for text in text if self.regex.match(text)])
303
+
304
+ # RapidFuzz fuzzy match 90% to not send the same results repeatedly
305
+ if self.last_oneocr_result:
306
+
307
+ score = fuzz.ratio(text_str, self.last_oneocr_result)
308
+ if score >= 80:
309
+ logger.info("OneOCR results are similar to the last results (score: %d). Skipping overlay update.", score)
310
+ return
311
+ self.last_oneocr_result = text_str
312
+
288
313
  logger.info("Sending OneOCR results to overlay.")
289
- await send_word_coordinates_to_overlay(oneocr_results)
314
+ await send_word_coordinates_to_overlay(self._convert_oneocr_results_to_percentages(oneocr_results, monitor_width, monitor_height))
290
315
 
291
316
  # If User Home is beangate
292
317
  if is_beangate:
@@ -317,9 +342,19 @@ class OverlayProcessor:
317
342
  if len(res) != 3:
318
343
  return
319
344
 
320
- _, _, coords = res
345
+ success, text_list, coords = res
346
+
347
+ text_str = "".join([text for text in text_list if self.regex.match(text)])
348
+
349
+ # RapidFuzz fuzzy match 90% to not send the same results repeatedly
350
+ if self.last_lens_result:
351
+ score = fuzz.ratio(text_str, self.last_lens_result)
352
+ if score >= 80:
353
+ logger.info("Google Lens results are similar to the last results (score: %d). Skipping overlay update.", score)
354
+ return
355
+ self.last_lens_result = text_str
321
356
 
322
- if not res or not coords:
357
+ if not success or not coords:
323
358
  return
324
359
 
325
360
  # 5. Process the high-accuracy results into the desired format
@@ -433,6 +468,38 @@ class OverlayProcessor:
433
468
  "x3": center_x + half_w, "y3": center_y + half_h,
434
469
  "x4": center_x - half_w, "y4": center_y + half_h,
435
470
  }
471
+
472
+ def _convert_oneocr_results_to_percentages(
473
+ self,
474
+ oneocr_results: List[Dict[str, Any]],
475
+ monitor_width: int,
476
+ monitor_height: int
477
+ ) -> List[Dict[str, Any]]:
478
+ """
479
+ Converts OneOCR results with pixel coordinates to percentages relative to the monitor size.
480
+ """
481
+ converted_results = []
482
+ for item in oneocr_results:
483
+ bbox = item.get("bounding_rect", {})
484
+ if not bbox:
485
+ continue
486
+ # Convert each coordinate to a percentage of the monitor dimensions
487
+ converted_bbox = {
488
+ key: (value / monitor_width if "x" in key else value / monitor_height)
489
+ for key, value in bbox.items()
490
+ }
491
+ converted_item = item.copy()
492
+ converted_item["bounding_rect"] = converted_bbox
493
+ converted_results.append(converted_item)
494
+ for word in converted_item.get("words", []):
495
+ word_bbox = word.get("bounding_rect", {})
496
+ if word_bbox:
497
+ word["bounding_rect"] = {
498
+ key: (value / monitor_width if "x" in key else value / monitor_height)
499
+ for key, value in word_bbox.items()
500
+ }
501
+ # logger.info(f"Converted OneOCR results to percentages: {converted_results}")
502
+ return converted_results
436
503
 
437
504
  async def main_test_screenshot():
438
505
  """
@@ -335,8 +335,8 @@ def register_database_api_routes(app):
335
335
  if afk_timer is not None:
336
336
  try:
337
337
  afk_timer = int(afk_timer)
338
- if afk_timer < 30 or afk_timer > 600:
339
- return jsonify({'error': 'AFK timer must be between 30 and 600 seconds'}), 400
338
+ if afk_timer < 0 or afk_timer > 600:
339
+ return jsonify({'error': 'AFK timer must be between 0 and 600 seconds'}), 400
340
340
  settings_to_update['afk_timer_seconds'] = afk_timer
341
341
  except (ValueError, TypeError):
342
342
  return jsonify({'error': 'AFK timer must be a valid integer'}), 400
@@ -344,8 +344,8 @@ def register_database_api_routes(app):
344
344
  if session_gap is not None:
345
345
  try:
346
346
  session_gap = int(session_gap)
347
- if session_gap < 300 or session_gap > 7200:
348
- return jsonify({'error': 'Session gap must be between 300 and 7200 seconds (5 minutes to 2 hours)'}), 400
347
+ if session_gap < 0 or session_gap > 7200:
348
+ return jsonify({'error': 'Session gap must be between 0 and 7200 seconds (0 to 2 hours)'}), 400
349
349
  settings_to_update['session_gap_seconds'] = session_gap
350
350
  except (ValueError, TypeError):
351
351
  return jsonify({'error': 'Session gap must be a valid integer'}), 400
@@ -392,8 +392,8 @@ class SettingsManager {
392
392
 
393
393
  if (this.afkTimerInput) {
394
394
  const afkTimer = parseInt(this.afkTimerInput.value);
395
- if (isNaN(afkTimer) || afkTimer < 30 || afkTimer > 600) {
396
- this.showError('AFK timer must be between 30 and 600 seconds');
395
+ if (isNaN(afkTimer) || afkTimer < 0 || afkTimer > 600) {
396
+ this.showError('AFK timer must be between 0 and 600 seconds');
397
397
  return;
398
398
  }
399
399
  settings.afk_timer_seconds = afkTimer;
@@ -401,8 +401,8 @@ class SettingsManager {
401
401
 
402
402
  if (this.sessionGapInput) {
403
403
  const sessionGap = parseInt(this.sessionGapInput.value);
404
- if (isNaN(sessionGap) || sessionGap < 300 || sessionGap > 7200) {
405
- this.showError('Session gap must be between 300 and 7200 seconds');
404
+ if (isNaN(sessionGap) || sessionGap < 0 || sessionGap > 7200) {
405
+ this.showError('Session gap must be between 0 and 7200 seconds (0 to 2 hours)');
406
406
  return;
407
407
  }
408
408
  settings.session_gap_seconds = sessionGap;
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.16.12
3
+ Version: 2.17.0
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.16.12
3
+ Version: 2.17.0
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
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "GameSentenceMiner"
10
- version = "2.16.12"
10
+ version = "2.17.0"
11
11
  description = "A tool for mining sentences from games. Update: Overlay?"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.10"
@@ -57,7 +57,7 @@ dependencies = [
57
57
  "obsws-python~=1.7.2",
58
58
  "numpy==2.2.6",
59
59
  "regex",
60
- "faster-whisper~=1.2.0"
60
+ "faster-whisper~=1.2.0",
61
61
  ]
62
62
 
63
63
  # This creates a command-line script named `gamesentenceminer` that will
@@ -417,10 +417,10 @@ class ConfigApp:
417
417
  self.create_screenshot_tab()
418
418
  self.create_audio_tab()
419
419
  self.create_obs_tab()
420
- self.create_profiles_tab()
421
420
  self.create_ai_tab()
422
421
  self.create_advanced_tab()
423
422
  self.create_overlay_tab()
423
+ self.create_profiles_tab()
424
424
  # self.create_wip_tab()
425
425
 
426
426
  def add_reset_button(self, frame, category, row, column=0, recreate_tab=None):