GameSentenceMiner 2.7.14__py3-none-any.whl → 2.7.16__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.
@@ -283,10 +283,10 @@ class ScreenSelector:
283
283
  win_w = window_geom_to_save['width']
284
284
  win_h = window_geom_to_save['height']
285
285
  # Basic check for valid dimensions needed for percentage calculation
286
- if win_w > 0 and win_h > 0 and win_h > 0 and win_t > 0:
286
+ if win_w > 0 and win_h > 0:
287
287
  save_coord_system = COORD_SYSTEM_PERCENTAGE
288
- win_l = max(0, window_geom_to_save['left'])
289
- win_t = max(0, window_geom_to_save['top'])
288
+ win_l = window_geom_to_save['left']
289
+ win_t = window_geom_to_save['top']
290
290
  print(f"Saving using coordinate system: {save_coord_system} relative to {window_geom_to_save}")
291
291
  else:
292
292
  print(
@@ -306,14 +306,14 @@ class ScreenSelector:
306
306
  coords_to_save = []
307
307
 
308
308
  # --- Convert absolute pixels to the chosen system ---
309
- if save_coord_system == COORD_SYSTEM_PERCENTAGE and window_geom_to_save and 0 <= win_l < monitor_dict['left'] and 0 <= win_t < monitor_dict['top']:
309
+ if save_coord_system == COORD_SYSTEM_PERCENTAGE and window_geom_to_save:
310
310
  # Calculate percentages (handle potential float precision issues if necessary)
311
311
  x_pct = (x_abs - win_l) / win_w
312
312
  y_pct = (y_abs - win_t) / win_h
313
313
  w_pct = w_abs / win_w
314
314
  h_pct = h_abs / win_h
315
315
  # Round percentages slightly to avoid overly long floats? Optional.
316
- # precision = 6
316
+ # precision = 6+
317
317
  # coords_to_save = [round(x_pct, precision), round(y_pct, precision), round(w_pct, precision), round(h_pct, precision)]
318
318
  coords_to_save = [x_pct, y_pct, w_pct, h_pct]
319
319
  else:
@@ -861,8 +861,8 @@ if __name__ == "__main__":
861
861
  # Example: uncomment below to target Calculator on Windows by default if no arg given
862
862
  # if sys.platform == "win32": target_window_title = "Calculator"
863
863
 
864
- if not target_window_title:
865
- target_window_title = get_ocr_config().window
864
+ # if not target_window_title:
865
+ # target_window_title = get_ocr_config().window
866
866
 
867
867
  # Get the selection result
868
868
  selection_result = get_screen_selection(target_window_title)
@@ -84,7 +84,8 @@ def get_ocr_config() -> OCRConfig:
84
84
  scene = util.sanitize_filename(obs.get_current_scene())
85
85
  config_path = ocr_config_dir / f"{scene}.json"
86
86
  if not config_path.exists():
87
- raise Exception(f"No config file found at {config_path}.")
87
+ config_path.touch()
88
+ return
88
89
  try:
89
90
  with open(config_path, 'r', encoding="utf-8") as f:
90
91
  config_data = json.load(f)
@@ -107,20 +108,21 @@ def get_ocr_config() -> OCRConfig:
107
108
  "coordinates": rect,
108
109
  "is_excluded": False
109
110
  })
110
- for rect in config_data['excluded_rectangles']:
111
- new_rectangles.append({
112
- "monitor": {
113
- "left": default_monitor["left"],
114
- "top": default_monitor["top"],
115
- "width": default_monitor["width"],
116
- "height": default_monitor["height"],
117
- "index": 0 # Assuming single monitor for old config
118
- },
119
- "coordinates": rect,
120
- "is_excluded": True
121
- })
111
+ if 'excluded_rectangles' in config_data:
112
+ for rect in config_data['excluded_rectangles']:
113
+ new_rectangles.append({
114
+ "monitor": {
115
+ "left": default_monitor["left"],
116
+ "top": default_monitor["top"],
117
+ "width": default_monitor["width"],
118
+ "height": default_monitor["height"],
119
+ "index": 0 # Assuming single monitor for old config
120
+ },
121
+ "coordinates": rect,
122
+ "is_excluded": True
123
+ })
122
124
  new_config_data = {"scene": config_data.get("scene", scene), "window": config_data.get("window", None),
123
- "rectangles": new_rectangles}
125
+ "rectangles": new_rectangles, "coordinate_system": "absolute"}
124
126
  with open(config_path, 'w', encoding="utf-8") as f:
125
127
  json.dump(new_config_data, f, indent=4)
126
128
  return OCRConfig.from_dict(new_config_data)
@@ -231,32 +233,45 @@ def do_second_ocr(ocr1_text, rectangle_index, time, img):
231
233
  last_oneocr_results_to_check = {} # Store last OCR result for each rectangle
232
234
  last_oneocr_times = {} # Store last OCR time for each rectangle
233
235
  text_stable_start_times = {} # Store the start time when text becomes stable for each rectangle
236
+ orig_text_results = {} # Store original text results for each rectangle
234
237
  TEXT_APPEARENCE_DELAY = get_ocr_scan_rate() * 1000 + 500 # Adjust as needed
235
238
 
236
- def text_callback(text, rectangle_index, time, img=None):
237
- global twopassocr, ocr2, last_oneocr_results_to_check, last_oneocr_times, text_stable_start_times
239
+ def text_callback(text, orig_text, rectangle_index, time, img=None):
240
+ global twopassocr, ocr2, last_oneocr_results_to_check, last_oneocr_times, text_stable_start_times, orig_text_results
241
+ orig_text_string = ''.join([item for item in orig_text if item is not None]) if orig_text else ""
238
242
 
239
243
  current_time = time if time else datetime.now()
240
244
 
241
245
  previous_text = last_oneocr_results_to_check.get(rectangle_index, "").strip()
246
+ previous_orig_text = orig_text_results.get(rectangle_index, "").strip()
247
+
248
+ # print(previous_orig_text)
249
+ # if orig_text:
250
+ # print(orig_text_string)
242
251
 
243
252
  if not text:
244
253
  if previous_text:
245
254
  if rectangle_index in text_stable_start_times:
246
255
  stable_time = text_stable_start_times[rectangle_index]
256
+ previous_result = last_ocr1_results[rectangle_index]
257
+ if previous_result and fuzz.ratio(previous_result, previous_text) >= 80:
258
+ logger.info("Seems like the same text, not " + "doing second OCR" if twopassocr else "sending")
259
+ del last_oneocr_results_to_check[rectangle_index]
260
+ return
261
+ if previous_orig_text and fuzz.ratio(orig_text_string, previous_orig_text) >= 80:
262
+ logger.info("Seems like Text we already sent, not doing anything.")
263
+ del last_oneocr_results_to_check[rectangle_index]
264
+ return
265
+ orig_text_results[rectangle_index] = orig_text_string
247
266
  if twopassocr:
248
267
  do_second_ocr(previous_text, rectangle_index, time, img)
249
268
  else:
250
- previous_result = last_ocr1_results[rectangle_index]
251
- if previous_result and fuzz.ratio(previous_result, previous_text) >= 80:
252
- logger.info("Seems like the same text, not sending")
253
- return
254
269
  if get_config().advanced.ocr_sends_to_clipboard:
255
270
  import pyperclip
256
271
  pyperclip.copy(text)
257
272
  websocket_server_thread.send_text(previous_text, stable_time)
258
273
  img.save(os.path.join(get_app_directory(), "temp", "last_successful_ocr.png"))
259
- last_ocr1_results[rectangle_index] = previous_text
274
+ last_ocr1_results[rectangle_index] = previous_text
260
275
  del text_stable_start_times[rectangle_index]
261
276
  del last_oneocr_results_to_check[rectangle_index]
262
277
  return
@@ -285,11 +300,13 @@ done = False
285
300
 
286
301
  def run_oneocr(ocr_config: OCRConfig, i, area=False):
287
302
  global done
288
- rect_config = ocr_config.rectangles[i]
289
- coords = rect_config.coordinates
290
- monitor_config = rect_config.monitor
303
+ screen_area = None
304
+ if ocr_config.rectangles:
305
+ rect_config = ocr_config.rectangles[i]
306
+ coords = rect_config.coordinates
307
+ monitor_config = rect_config.monitor
308
+ screen_area = ",".join(str(c) for c in coords) if area else None
291
309
  exclusions = list(rect.coordinates for rect in list(filter(lambda x: x.is_excluded, ocr_config.rectangles)))
292
- screen_area = ",".join(str(c) for c in coords) if area else None
293
310
  run.run(read_from="screencapture", write_to="callback",
294
311
  screen_capture_area=screen_area,
295
312
  # screen_capture_monitor=monitor_config['index'],
@@ -326,9 +343,9 @@ if __name__ == "__main__":
326
343
  logger.info(f"Received arguments: ocr1={ocr1}, ocr2={ocr2}, twopassocr={twopassocr}")
327
344
  global ocr_config
328
345
  ocr_config: OCRConfig = get_ocr_config()
329
-
346
+ print(ocr_config)
330
347
  logger.info(f"Starting OCR with configuration: Window: {ocr_config.window}, Rectangles: {len(ocr_config.rectangles)}, Engine 1: {ocr1}, Engine 2: {ocr2}, Two-pass OCR: {twopassocr}")
331
- if ocr_config and ocr_config.rectangles:
348
+ if ocr_config:
332
349
  rectangles = list(filter(lambda rect: not rect.is_excluded, ocr_config.rectangles))
333
350
  last_ocr1_results = [""] * len(rectangles) if rectangles else [""]
334
351
  last_ocr2_results = [""] * len(rectangles) if rectangles else [""]
@@ -759,7 +759,9 @@ class OneOCR:
759
759
 
760
760
  if sys.platform == 'win32':
761
761
  try:
762
- res = self.model.recognize_pil(img)['text']
762
+ ocr_resp = self.model.recognize_pil(img)
763
+ # print(json.dumps(ocr_resp))
764
+ res = ocr_resp['text']
763
765
  except RuntimeError as e:
764
766
  return (False, e)
765
767
  else:
@@ -773,6 +775,7 @@ class OneOCR:
773
775
  if res.status_code != 200:
774
776
  return (False, 'Unknown error!')
775
777
 
778
+
776
779
  res = res.json()['text']
777
780
 
778
781
  x = (True, res)
@@ -1007,9 +1010,9 @@ class GeminiOCR:
1007
1010
  try:
1008
1011
  import google.generativeai as genai
1009
1012
  if isinstance(img_or_path, str) or isinstance(img_or_path, Path):
1010
- img = Image.open(img_or_path).convert("RGB")
1013
+ img = Image.open(img_or_path)
1011
1014
  elif isinstance(img_or_path, Image.Image):
1012
- img = img_or_path.convert("RGB")
1015
+ img = img_or_path
1013
1016
  else:
1014
1017
  raise ValueError(f'img_or_path must be a path or PIL.Image, instead got: {img_or_path}')
1015
1018
 
@@ -1023,11 +1026,11 @@ class GeminiOCR:
1023
1026
  {
1024
1027
  'inline_data': {
1025
1028
  'mime_type': 'image/png',
1026
- 'data': base64.b64encode(img_bytes).decode('utf-8')
1029
+ 'data': img_bytes
1027
1030
  }
1028
1031
  },
1029
1032
  {
1030
- 'text': 'As Quick as Possible, Give me the text from this image, no other output. If there is no text, return nothing.'
1033
+ 'text': 'Analyze the image. Extract text *only* from within dialogue boxes (speech bubbles or panels containing character dialogue). From the extracted dialogue text, filter out any furigana. Ignore and do not include any text found outside of dialogue boxes, including character names, speaker labels, or sound effects. Return *only* the filtered dialogue text. If no text is found within dialogue boxes after applying filters, return nothing. Do not include any other output, formatting markers, or commentary.'
1031
1034
  }
1032
1035
  ]
1033
1036
  }
@@ -1044,11 +1047,4 @@ class GeminiOCR:
1044
1047
  return (False, f'Gemini API request failed: {e}')
1045
1048
 
1046
1049
  def _preprocess(self, img):
1047
- try:
1048
- from io import BytesIO
1049
- img_io = BytesIO()
1050
- img.save(img_io, 'PNG') # Save as PNG
1051
- return img_io.getvalue()
1052
- except Exception as e:
1053
- logger.error(f'Error preprocessing image for Gemini: {e}')
1054
- return None
1050
+ return pil_image_to_bytes(img, png_compression=1)
@@ -25,6 +25,10 @@ import psutil
25
25
 
26
26
  import inspect
27
27
  from .ocr import *
28
+ try:
29
+ from .secret import *
30
+ except ImportError:
31
+ pass
28
32
  from .config import Config
29
33
  from .screen_coordinate_picker import get_screen_selection
30
34
  from ...configuration import get_temporary_directory
@@ -571,7 +575,7 @@ def process_and_write_results(img_or_path, write_to, notifications, last_result,
571
575
  elif write_to == 'clipboard':
572
576
  pyperclipfix.copy(text)
573
577
  elif write_to == "callback":
574
- txt_callback(text, rectangle, start_time, img_or_path)
578
+ txt_callback(text, orig_text, rectangle, start_time, img_or_path)
575
579
  elif write_to:
576
580
  with Path(write_to).open('a', encoding='utf-8') as f:
577
581
  f.write(text + '\n')
@@ -670,7 +674,10 @@ def run(read_from=None,
670
674
  for config_engine in config.get_general('engines').split(','):
671
675
  config_engines.append(config_engine.strip().lower())
672
676
 
673
- for _,engine_class in sorted(inspect.getmembers(sys.modules[__name__], lambda x: hasattr(x, '__module__') and x.__module__ and __package__ + '.ocr' in x.__module__ and inspect.isclass(x))):
677
+ for _, engine_class in sorted(inspect.getmembers(sys.modules[__name__],
678
+ lambda x: hasattr(x, '__module__') and x.__module__ and (
679
+ __package__ + '.ocr' in x.__module__ or __package__ + '.secret' in x.__module__) and inspect.isclass(
680
+ x))):
674
681
  if len(config_engines) == 0 or engine_class.name in config_engines:
675
682
  if config.get_engine(engine_class.name) == None:
676
683
  engine_instance = engine_class()
@@ -921,7 +928,7 @@ def run(read_from=None,
921
928
  logger.opt(ansi=True).info(f"Reading from {read_from_readable}, writing to {write_to_readable} using <{engine_color}>{engine_instances[engine_index].readable_name}</{engine_color}>{' (paused)' if paused else ''}")
922
929
 
923
930
  while not terminated and not stop_running_flag:
924
- start_time = datetime.datetime.now()
931
+ start_time = datetime.now()
925
932
  if read_from == 'websocket':
926
933
  while True:
927
934
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.7.14
3
+ Version: 2.7.16
4
4
  Summary: A tool for mining sentences from games. Update: Multi-Line Mining! Fixed!
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
@@ -23,22 +23,22 @@ GameSentenceMiner/downloader/download_tools.py,sha256=mI1u_FGBmBqDIpCH3jOv8DOoZ3
23
23
  GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=zagsB4UD9mmZX_r6dFBCXZqdDa0XGk-RvIqbKoPB9lQ,1932
25
25
  GameSentenceMiner/ocr/ocrconfig.py,sha256=hTROOZ3On2HngXKxwQFZvnr5AxlmlMV0mPxv-F3NbMg,6476
26
- GameSentenceMiner/ocr/owocr_area_selector.py,sha256=bCgusYXe9ibCsf56PlU301aNfDA2PDKasi78ox0IGbk,46856
27
- GameSentenceMiner/ocr/owocr_helper.py,sha256=Rd1wcVtJy0N6ySn1p7-08hLYA9iPkts12zGJESoEewI,15126
26
+ GameSentenceMiner/ocr/owocr_area_selector.py,sha256=bwlvvM_SwRHzwbZ3GSQfxGHT0ASy3rMxB5DQ7RhVZkQ,46742
27
+ GameSentenceMiner/ocr/owocr_helper.py,sha256=wL6EjjFTU6WJu_1UdY0g1dl0JhLweO54YnAY9fOPjaQ,16117
28
28
  GameSentenceMiner/owocr/owocr/__init__.py,sha256=opjBOyGGyEqZCE6YdZPnyt7nVfiwyELHsXA0jAsjm14,25
29
29
  GameSentenceMiner/owocr/owocr/__main__.py,sha256=r8MI6RAmbkTWqOJ59uvXoDS7CSw5jX5war9ULGWELrA,128
30
30
  GameSentenceMiner/owocr/owocr/config.py,sha256=738QCJHEWpFhMh966plOcXYWwcshSiRsxjjIwldeTtI,7461
31
31
  GameSentenceMiner/owocr/owocr/lens_betterproto.py,sha256=oNoISsPilVVRBBPVDtb4-roJtAhp8ZAuFTci3TGXtMc,39141
32
- GameSentenceMiner/owocr/owocr/ocr.py,sha256=t0kU2GQyW0gf0NGqaYiOO7SjYgX8mQXLaNKJ8Eup6mg,39704
33
- GameSentenceMiner/owocr/owocr/run.py,sha256=0MGrhO6HoLNF1JpYcl-tS6SnXNM5zu9y0oSZDrAcw5k,47499
32
+ GameSentenceMiner/owocr/owocr/ocr.py,sha256=n24Xg8Z8dbcgLpq1u4d22z3tLV1evmf0dK3-Xocv3vs,39878
33
+ GameSentenceMiner/owocr/owocr/run.py,sha256=pEHxot24yRPvbeE3fmgWMcIILBQfUiSQ3sXjw0LJOF0,47791
34
34
  GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=fjJ3CSXLti3WboGPpmsa7MWOwIXsfpHC8N4zKahGGY0,3346
35
35
  GameSentenceMiner/vad/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
36
  GameSentenceMiner/vad/silero_trim.py,sha256=ULf3zwS-JMsY82cKF7gZxREHw8L6lgpWF2U1YqgE9Oc,1681
37
37
  GameSentenceMiner/vad/vosk_helper.py,sha256=125X8C9NxFPlWWpoNsbOnEqKx8RCjXN109zNx_QXhyg,6070
38
38
  GameSentenceMiner/vad/whisper_helper.py,sha256=JJ-iltCh813XdjyEw0Wn5DaErf6PDqfH0Efu1Md8cIY,3543
39
- gamesentenceminer-2.7.14.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
40
- gamesentenceminer-2.7.14.dist-info/METADATA,sha256=KUj0VZC1ZOiywqGwMSjz4No-9EKoJ4g-KgVgoeyDRsU,5892
41
- gamesentenceminer-2.7.14.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
42
- gamesentenceminer-2.7.14.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
43
- gamesentenceminer-2.7.14.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
44
- gamesentenceminer-2.7.14.dist-info/RECORD,,
39
+ gamesentenceminer-2.7.16.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
40
+ gamesentenceminer-2.7.16.dist-info/METADATA,sha256=FnLkWirfE9GWkCtHhyegdVXNR9tvpwoIqPoUQBQElAw,5892
41
+ gamesentenceminer-2.7.16.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
42
+ gamesentenceminer-2.7.16.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
43
+ gamesentenceminer-2.7.16.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
44
+ gamesentenceminer-2.7.16.dist-info/RECORD,,