GameSentenceMiner 2.13.9__py3-none-any.whl → 2.13.10__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.
@@ -33,6 +33,7 @@ class Rectangle:
33
33
  monitor: Monitor
34
34
  coordinates: List[Union[float, int]]
35
35
  is_excluded: bool
36
+ is_secondary: bool = False
36
37
 
37
38
  @dataclass_json
38
39
  @dataclass
@@ -41,7 +42,8 @@ class WindowGeometry:
41
42
  top: int
42
43
  width: int
43
44
  height: int
44
-
45
+
46
+
45
47
  @dataclass_json
46
48
  @dataclass
47
49
  class OCRConfig:
@@ -34,7 +34,7 @@ class ScreenSelector:
34
34
  def __init__(self, result, window_name, use_window_as_config, use_obs_screenshot=False):
35
35
  if not selector_available:
36
36
  raise RuntimeError("tkinter is not available.")
37
- if not window_name:
37
+ if not window_name and not use_obs_screenshot:
38
38
  raise ValueError("A target window name is required for configuration.")
39
39
 
40
40
  obs.connect_to_obs_sync()
@@ -185,7 +185,7 @@ class ScreenSelector:
185
185
  monitor_index = rect_data["monitor"]['index']
186
186
  target_monitor = next((m for m in self.monitors if m['index'] == monitor_index), None)
187
187
  if target_monitor:
188
- self.rectangles.append((target_monitor, abs_coords, rect_data["is_excluded"]))
188
+ self.rectangles.append((target_monitor, abs_coords, rect_data["is_excluded"], rect_data.get("is_secondary", False)))
189
189
  loaded_count += 1
190
190
  except (KeyError, ValueError, TypeError) as e:
191
191
  print(f"Skipping malformed rectangle data: {rect_data}, Error: {e}")
@@ -204,7 +204,7 @@ class ScreenSelector:
204
204
  print(f"Saving rectangles to: {config_path} relative to window: {win_geom}")
205
205
 
206
206
  serializable_rects = []
207
- for monitor_dict, abs_coords, is_excluded in self.rectangles:
207
+ for monitor_dict, abs_coords, is_excluded, is_secondary in self.rectangles:
208
208
  x_abs, y_abs, w_abs, h_abs = abs_coords
209
209
 
210
210
  # Convert absolute pixel coordinates to percentages
@@ -217,7 +217,8 @@ class ScreenSelector:
217
217
  serializable_rects.append({
218
218
  "monitor": {'index': monitor_dict['index']},
219
219
  "coordinates": coords_to_save,
220
- "is_excluded": is_excluded
220
+ "is_excluded": is_excluded,
221
+ "is_secondary": is_secondary
221
222
  })
222
223
 
223
224
  save_data = {
@@ -254,13 +255,14 @@ class ScreenSelector:
254
255
 
255
256
  def redo_last_rect(self, event=None):
256
257
  if not self.redo_stack: return
257
- monitor, abs_coords, is_excluded, old_rect_id = self.redo_stack.pop()
258
+ monitor, abs_coords, is_excluded, is_secondary, old_rect_id = self.redo_stack.pop()
258
259
  canvas = event.widget.winfo_toplevel().winfo_children()[0]
259
260
  x_abs, y_abs, w_abs, h_abs = abs_coords
260
261
  canvas_x, canvas_y = x_abs - self.bounding_box['left'], y_abs - self.bounding_box['top']
262
+ outline_color = 'purple' if is_secondary else ('orange' if is_excluded else 'green')
261
263
  new_rect_id = canvas.create_rectangle(canvas_x, canvas_y, canvas_x + w_abs, canvas_y + h_abs,
262
- outline='orange' if is_excluded else 'green', width=2)
263
- self.rectangles.append((monitor, abs_coords, is_excluded))
264
+ outline=outline_color, width=2)
265
+ self.rectangles.append((monitor, abs_coords, is_excluded, is_secondary))
264
266
  self.drawn_rect_ids.append(new_rect_id)
265
267
  print("Redo: Restored rectangle.")
266
268
 
@@ -291,6 +293,7 @@ class ScreenSelector:
291
293
  "How to Use:\n"
292
294
  "• Left Click + Drag: Create a capture area (green).\n"
293
295
  "• Shift + Left Click + Drag: Create an exclusion area (orange).\n"
296
+ "• Ctrl + Left Click + Drag: Create a secondary (menu) area (purple).\n"
294
297
  "• Right-Click on a box: Delete it."
295
298
  )
296
299
  tk.Label(main_frame, text=instructions_text, justify=tk.LEFT, anchor="w").pack(pady=(0, 10), fill=tk.X)
@@ -426,17 +429,25 @@ class ScreenSelector:
426
429
  # --- END MODIFICATION ---
427
430
 
428
431
  # Draw existing rectangles (which were converted to absolute pixels on load)
429
- for _, abs_coords, is_excluded in self.rectangles:
432
+ for _, abs_coords, is_excluded, is_secondary in self.rectangles:
430
433
  x_abs, y_abs, w_abs, h_abs = abs_coords
431
434
  canvas_x = x_abs - self.bounding_box['left']
432
435
  canvas_y = y_abs - self.bounding_box['top']
436
+ outline_color = 'purple' if is_secondary else ('orange' if is_excluded else 'green')
433
437
  rect_id = self.canvas.create_rectangle(canvas_x, canvas_y, canvas_x + w_abs, canvas_y + h_abs,
434
- outline='orange' if is_excluded else 'green', width=2)
438
+ outline=outline_color, width=2)
435
439
  self.drawn_rect_ids.append(rect_id)
436
440
 
437
441
  def on_click(event):
438
442
  self.start_x, self.start_y = event.x, event.y
439
- outline = 'purple' if bool(event.state & 0x0001) else 'red'
443
+ ctrl_held = bool(event.state & 0x0004)
444
+ shift_held = bool(event.state & 0x0001)
445
+ if ctrl_held:
446
+ outline = 'purple'
447
+ elif shift_held:
448
+ outline = 'orange'
449
+ else:
450
+ outline = 'green'
440
451
  self.current_rect_id = self.canvas.create_rectangle(self.start_x, self.start_y, self.start_x, self.start_y,
441
452
  outline=outline, width=2)
442
453
 
@@ -451,8 +462,12 @@ class ScreenSelector:
451
462
  w, h = int(abs(coords[2] - coords[0])), int(abs(coords[3] - coords[1]))
452
463
 
453
464
  if w >= MIN_RECT_WIDTH and h >= MIN_RECT_HEIGHT:
454
- is_excl = bool(event.state & 0x0001)
455
- self.canvas.itemconfig(self.current_rect_id, outline='orange' if is_excl else 'green')
465
+ ctrl_held = bool(event.state & 0x0004)
466
+ shift_held = bool(event.state & 0x0001)
467
+ is_excl = shift_held
468
+ is_secondary = ctrl_held
469
+ outline_color = 'purple' if is_secondary else ('orange' if is_excl else 'green')
470
+ self.canvas.itemconfig(self.current_rect_id, outline=outline_color)
456
471
 
457
472
  center_x, center_y = x_abs + w / 2, y_abs + h / 2
458
473
  target_mon = self.monitors[0]
@@ -462,7 +477,7 @@ class ScreenSelector:
462
477
  target_mon = mon
463
478
  break
464
479
 
465
- self.rectangles.append((target_mon, (x_abs, y_abs, w, h), is_excl))
480
+ self.rectangles.append((target_mon, (x_abs, y_abs, w, h), is_excl, is_secondary))
466
481
  self.drawn_rect_ids.append(self.current_rect_id)
467
482
  self.redo_stack.clear()
468
483
  else:
@@ -472,7 +487,7 @@ class ScreenSelector:
472
487
  def on_right_click(event):
473
488
  # Iterate through our rectangles in reverse to find the topmost one.
474
489
  for i in range(len(self.rectangles) - 1, -1, -1):
475
- _monitor, abs_coords, _is_excluded = self.rectangles[i]
490
+ _monitor, abs_coords, _is_excluded, _is_secondary = self.rectangles[i]
476
491
  x_abs, y_abs, w_abs, h_abs = abs_coords
477
492
  canvas_x1 = x_abs - self.bounding_box['left']
478
493
  canvas_y1 = y_abs - self.bounding_box['top']
@@ -540,7 +555,7 @@ def run_screen_selector(result_dict, window_name, use_window_as_config, use_obs_
540
555
 
541
556
  def get_screen_selection(window_name, use_window_as_config=False, use_obs_screenshot=False):
542
557
  if not selector_available: return None
543
- if not window_name:
558
+ if not window_name and not use_obs_screenshot:
544
559
  print("Error: A target window name must be provided.", file=sys.stderr)
545
560
  return None
546
561
 
@@ -567,7 +582,7 @@ if __name__ == "__main__":
567
582
 
568
583
  parser = argparse.ArgumentParser(description="Screen Selector Arguments")
569
584
  parser.add_argument("window_title", nargs="?", default="", help="Target window title")
570
- parser.add_argument("--obs_ocr", action="store_true", help="Use OBS screenshot")
585
+ parser.add_argument("--obs_ocr", action="store_true", default=True, help="Use OBS screenshot")
571
586
  parser.add_argument("--use_window_for_config", action="store_true", help="Use window for config")
572
587
  args = parser.parse_args()
573
588
 
@@ -52,7 +52,6 @@ logger.addHandler(console_handler)
52
52
  def get_ocr_config(window=None, use_window_for_config=False) -> OCRConfig:
53
53
  """Loads and updates screen capture areas from the corresponding JSON file."""
54
54
  ocr_config_dir = get_ocr_config_path()
55
- obs.connect_to_obs_sync(retry=0)
56
55
  obs.update_current_game()
57
56
  if use_window_for_config and window:
58
57
  scene = sanitize_filename(window)
@@ -202,7 +201,7 @@ def do_second_ocr(ocr1_text, time, img, filtering, pre_crop_image=None, ignore_f
202
201
  try:
203
202
  orig_text, text = run.process_and_write_results(img, None, last_ocr2_result if not ignore_previous_result else None, filtering, None,
204
203
  engine=get_ocr_ocr2(), furigana_filter_sensitivity=furigana_filter_sensitivity if not ignore_furigana_filter else 0)
205
-
204
+
206
205
  if compare_ocr_results(last_sent_result, text, threshold=80):
207
206
  if text:
208
207
  logger.info("Seems like Text we already sent, not doing anything.")
@@ -447,6 +446,17 @@ def run_oneocr(ocr_config: OCRConfig, rectangles, config_check_thread):
447
446
 
448
447
  def add_ss_hotkey(ss_hotkey="ctrl+shift+g"):
449
448
  import keyboard
449
+
450
+ def ocr_secondary_rectangles():
451
+ logger.info("Running secondary OCR rectangles...")
452
+ ocr_config = get_ocr_config()
453
+ img = obs.get_screenshot_PIL(compression=80, img_format="jpg")
454
+ ocr_config.scale_to_custom_size(img.width, img.height)
455
+ img = run.apply_ocr_config_to_image(img, ocr_config, is_secondary=True)
456
+ do_second_ocr("", datetime.now(), img, TextFiltering(lang=get_ocr_language()), ignore_furigana_filter=True, ignore_previous_result=True)
457
+
458
+ if not manual:
459
+ keyboard.add_hotkey(get_ocr_manual_ocr_hotkey().lower(), ocr_secondary_rectangles)
450
460
  secret_ss_hotkey = "F14"
451
461
  filtering = TextFiltering(lang=get_ocr_language())
452
462
  cropper = ScreenCropper()
@@ -544,6 +554,8 @@ if __name__ == "__main__":
544
554
  use_window_for_config = args.use_window_for_config
545
555
  keep_newline = args.keep_newline
546
556
  obs_ocr = args.obs_ocr
557
+
558
+ obs.connect_to_obs_sync(retry=0)
547
559
 
548
560
  # Start config change checker thread
549
561
  config_check_thread = ConfigChangeCheckThread()
@@ -832,7 +832,6 @@ class OBSScreenshotThread(threading.Thread):
832
832
  super().__init__(daemon=True)
833
833
  self.ocr_config = ocr_config
834
834
  self.interval = interval
835
- self.obs_client = None
836
835
  self.websocket = None
837
836
  self.current_source = None
838
837
  self.current_source_name = None
@@ -930,16 +929,6 @@ class OBSScreenshotThread(threading.Thread):
930
929
  init_config()
931
930
  start = time.time()
932
931
  while not terminated:
933
- if time.time() - start > 5:
934
- if not self.obs_client:
935
- self.connect_obs()
936
- else:
937
- try:
938
- self.obs_client.get_version()
939
- except Exception as e:
940
- logger.error(f"Lost connection to OBS: {e}")
941
- self.obs_client = None
942
- self.connect_obs()
943
932
  if not screenshot_event.wait(timeout=0.1):
944
933
  continue
945
934
 
@@ -990,15 +979,14 @@ class OBSScreenshotThread(threading.Thread):
990
979
  continue
991
980
 
992
981
 
993
- def apply_ocr_config_to_image(img, ocr_config):
982
+ def apply_ocr_config_to_image(img, ocr_config, is_secondary=False):
994
983
  for rectangle in ocr_config.rectangles:
995
984
  if rectangle.is_excluded:
996
985
  left, top, width, height = rectangle.coordinates
997
986
  draw = ImageDraw.Draw(img)
998
- draw.rectangle((left, top, left + width, top +
999
- height), fill=(0, 0, 0, 0))
987
+ draw.rectangle((left, top, left + width, top + height), fill=(0, 0, 0, 0))
1000
988
 
1001
- rectangles = [r for r in ocr_config.rectangles if not r.is_excluded]
989
+ rectangles = [r for r in ocr_config.rectangles if not r.is_excluded and r.is_secondary == is_secondary]
1002
990
 
1003
991
  # Sort top to bottom
1004
992
  if rectangles:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.13.9
3
+ Version: 2.13.10
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
@@ -19,17 +19,17 @@ GameSentenceMiner/locales/en_us.json,sha256=9aFJw4vls269_ufnVx03uRLXG8-E-hdG9hJ9
19
19
  GameSentenceMiner/locales/ja_jp.json,sha256=zx5R_DLsdUw9Szg7RrOkRprMcnumM5Bnl4QhsfOj-qM,27190
20
20
  GameSentenceMiner/locales/zh_cn.json,sha256=fwP5GQh0WkftdpXuyyt9IiaEIPK_ZXtEa_3kmoeQMAk,24198
21
21
  GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=Ezj-0k6Wo-una91FvYhMp6KGkRhWYihXzLAoh_Wu2xY,5329
22
+ GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=7h_nfC7TRQQXW-zkYT0YLvupGBCxlIp62NDOwAASxiI,5369
23
23
  GameSentenceMiner/ocr/ocrconfig.py,sha256=_tY8mjnzHMJrLS8E5pHqYXZjMuLoGKYgJwdhYgN-ny4,6466
24
- GameSentenceMiner/ocr/owocr_area_selector.py,sha256=tlzQp0uVT-OdM99IqycA58hBbVb4c8LdTAope2hUXWg,27319
25
- GameSentenceMiner/ocr/owocr_helper.py,sha256=EDKDhmZ0kF1xNmw2obZwAdXj-L-zDK09JGZPTJBja-c,26523
24
+ GameSentenceMiner/ocr/owocr_area_selector.py,sha256=Y505tm7KT18flaIyFUe5DfBcBo99e9P3nQDgF4NTmbI,28209
25
+ GameSentenceMiner/ocr/owocr_helper.py,sha256=_FHwiSNsKvRGWwu9huAepbr2Hpij1ewNxSQC4Gwrd7o,27156
26
26
  GameSentenceMiner/ocr/ss_picker.py,sha256=0IhxUdaKruFpZyBL-8SpxWg7bPrlGpy3lhTcMMZ5rwo,5224
27
27
  GameSentenceMiner/owocr/owocr/__init__.py,sha256=87hfN5u_PbL_onLfMACbc0F5j4KyIK9lKnRCj6oZgR0,49
28
28
  GameSentenceMiner/owocr/owocr/__main__.py,sha256=XQaqZY99EKoCpU-gWQjNbTs7Kg17HvBVE7JY8LqIE0o,157
29
29
  GameSentenceMiner/owocr/owocr/config.py,sha256=qM7kISHdUhuygGXOxmgU6Ef2nwBShrZtdqu4InDCViE,8103
30
30
  GameSentenceMiner/owocr/owocr/lens_betterproto.py,sha256=oNoISsPilVVRBBPVDtb4-roJtAhp8ZAuFTci3TGXtMc,39141
31
31
  GameSentenceMiner/owocr/owocr/ocr.py,sha256=L_Dhy5qoSmVNtzBGVHM8aCJtfFsgqbSdTqXd1Obh1EM,62531
32
- GameSentenceMiner/owocr/owocr/run.py,sha256=asd5RsYRlsN7FhnMgbjDcN_m3QtVCWzysb6P9_rplMY,68523
32
+ GameSentenceMiner/owocr/owocr/run.py,sha256=0rsxeIlUys_bpwKDTfBwjER2FpgXKbfzM6lOxgFzzWU,68086
33
33
  GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=Na6XStbQBtpQUSdbN3QhEswtKuU1JjReFk_K8t5ezQE,3395
34
34
  GameSentenceMiner/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
35
  GameSentenceMiner/util/audio_offset_selector.py,sha256=8Stk3BP-XVIuzRv9nl9Eqd2D-1yD3JrgU-CamBywJmY,8542
@@ -67,9 +67,9 @@ GameSentenceMiner/web/templates/index.html,sha256=Gv3CJvNnhAzIVV_QxhNq4OD-pXDt1v
67
67
  GameSentenceMiner/web/templates/text_replacements.html,sha256=tV5c8mCaWSt_vKuUpbdbLAzXZ3ATZeDvQ9PnnAfqY0M,8598
68
68
  GameSentenceMiner/web/templates/utility.html,sha256=3flZinKNqUJ7pvrZk6xu__v67z44rXnaK7UTZ303R-8,16946
69
69
  GameSentenceMiner/wip/get_overlay_coords.py,sha256=nJRytHJwUBToXeAIkf45HP7Yv42YO-ILbP5h8GVeE2Q,19791
70
- gamesentenceminer-2.13.9.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
71
- gamesentenceminer-2.13.9.dist-info/METADATA,sha256=XDe-B_AOtfP-Kno8LypRFVvCfU80qZZylxnB1N9eWKM,1463
72
- gamesentenceminer-2.13.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
73
- gamesentenceminer-2.13.9.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
74
- gamesentenceminer-2.13.9.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
75
- gamesentenceminer-2.13.9.dist-info/RECORD,,
70
+ gamesentenceminer-2.13.10.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
71
+ gamesentenceminer-2.13.10.dist-info/METADATA,sha256=iKam6S744mbWWZ_s-slGjGGAzhy88vTsVQO3uDZCEGY,1464
72
+ gamesentenceminer-2.13.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
73
+ gamesentenceminer-2.13.10.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
74
+ gamesentenceminer-2.13.10.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
75
+ gamesentenceminer-2.13.10.dist-info/RECORD,,