GameSentenceMiner 2.14.20__tar.gz → 2.15.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.
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/config_gui.py +3 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/gsm.py +92 -81
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/obs.py +18 -5
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/ocr/gsm_ocr_config.py +0 -1
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/ocr/owocr_helper.py +0 -1
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/owocr/owocr/ocr.py +24 -22
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/owocr/owocr/run.py +102 -22
- gamesentenceminer-2.15.0/GameSentenceMiner/tools/furigana_filter_preview.py +330 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/get_overlay_coords.py +81 -48
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/vad.py +2 -2
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner.egg-info/PKG-INFO +2 -2
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner.egg-info/SOURCES.txt +1 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/PKG-INFO +2 -2
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/README.md +1 -1
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/pyproject.toml +1 -1
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/__init__.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/ai/__init__.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/ai/ai_prompting.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/anki.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/assets/__init__.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/assets/icon.png +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/assets/icon128.png +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/assets/icon256.png +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/assets/icon32.png +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/assets/icon512.png +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/assets/icon64.png +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/assets/pickaxe.png +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/gametext.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/locales/en_us.json +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/locales/ja_jp.json +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/locales/zh_cn.json +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/ocr/__init__.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/ocr/ocrconfig.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/ocr/owocr_area_selector.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/ocr/ss_picker.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/owocr/owocr/__init__.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/owocr/owocr/__main__.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/owocr/owocr/config.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/tools/__init__.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/tools/audio_offset_selector.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/tools/ss_selector.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/tools/window_transparency.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/__init__.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/communication/__init__.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/communication/send.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/communication/websocket.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/configuration.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/db.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/downloader/Untitled_json.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/downloader/__init__.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/downloader/download_tools.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/downloader/oneocr_dl.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/electron_config.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/ffmpeg.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/gsm_utils.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/model.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/notification.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/text_log.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/win10toast/__init__.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/util/win10toast/__main__.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/web/__init__.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/web/service.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/web/static/__init__.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/web/static/favicon-96x96.png +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/web/static/favicon.ico +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/web/static/favicon.svg +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/web/static/site.webmanifest +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/web/static/style.css +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/web/templates/__init__.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/web/templates/index.html +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/web/templates/text_replacements.html +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/web/templates/utility.html +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/web/texthooking_page.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/wip/__init___.py +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner.egg-info/dependency_links.txt +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner.egg-info/entry_points.txt +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner.egg-info/requires.txt +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner.egg-info/top_level.txt +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/LICENSE +0 -0
- {gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/setup.cfg +0 -0
@@ -2192,6 +2192,9 @@ class ConfigApp:
|
|
2192
2192
|
row=self.current_row, column=0)
|
2193
2193
|
self.overlay_monitor = ttk.Combobox(overlay_frame, values=self.monitors, state="readonly")
|
2194
2194
|
self.overlay_monitor.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
2195
|
+
# disable selection for now, default to value 1
|
2196
|
+
self.overlay_monitor.current(1)
|
2197
|
+
self.overlay_monitor.config(state="disabled")
|
2195
2198
|
self.current_row += 1
|
2196
2199
|
|
2197
2200
|
if self.monitors:
|
@@ -123,8 +123,6 @@ if is_windows():
|
|
123
123
|
procs_to_close = []
|
124
124
|
settings_window: config_gui.ConfigApp = None
|
125
125
|
obs_paused = False
|
126
|
-
icon: Icon
|
127
|
-
menu: Menu
|
128
126
|
root = None
|
129
127
|
warnings.simplefilter("ignore", DeprecationWarning)
|
130
128
|
|
@@ -401,83 +399,94 @@ def open_log():
|
|
401
399
|
logger.info("Log opened.")
|
402
400
|
|
403
401
|
|
404
|
-
def
|
405
|
-
|
406
|
-
if not passed_icon:
|
407
|
-
passed_icon = icon
|
408
|
-
logger.info("Exiting...")
|
409
|
-
passed_icon.stop()
|
410
|
-
cleanup()
|
402
|
+
def open_multimine(icon, item):
|
403
|
+
texthooking_page.open_texthooker()
|
411
404
|
|
412
405
|
|
413
|
-
def
|
414
|
-
|
415
|
-
|
416
|
-
|
406
|
+
def exit_program(passed_icon, item):
|
407
|
+
"""Exit the application."""
|
408
|
+
if not passed_icon:
|
409
|
+
passed_icon = icon
|
410
|
+
logger.info("Exiting...")
|
411
|
+
passed_icon.stop()
|
412
|
+
cleanup()
|
417
413
|
|
414
|
+
class GSMTray(threading.Thread):
|
415
|
+
def __init__(self):
|
416
|
+
super().__init__()
|
417
|
+
self.daemon = True
|
418
|
+
self.menu = None
|
419
|
+
self.icon = None
|
420
|
+
|
421
|
+
def run(self):
|
422
|
+
self.run_tray()
|
423
|
+
|
424
|
+
|
425
|
+
def run_tray(self):
|
426
|
+
self.profile_menu = Menu(
|
427
|
+
*[MenuItem(("Active: " if profile == get_master_config().current_profile else "") + profile, self.switch_profile) for
|
428
|
+
profile in
|
429
|
+
get_master_config().get_all_profile_names()]
|
430
|
+
)
|
431
|
+
|
432
|
+
menu = Menu(
|
433
|
+
MenuItem("Open Settings", open_settings, default=True),
|
434
|
+
MenuItem("Open Texthooker", texthooking_page.open_texthooker),
|
435
|
+
MenuItem("Open Log", open_log),
|
436
|
+
MenuItem("Toggle Replay Buffer", self.play_pause),
|
437
|
+
MenuItem("Restart OBS", restart_obs),
|
438
|
+
MenuItem("Switch Profile", self.profile_menu),
|
439
|
+
MenuItem("Exit", exit_program)
|
440
|
+
)
|
441
|
+
|
442
|
+
self.icon = Icon("TrayApp", create_image(), "GameSentenceMiner", menu)
|
443
|
+
self.icon.run()
|
444
|
+
|
445
|
+
def update_icon(self, profile=None):
|
446
|
+
global menu, icon
|
447
|
+
# Recreate the menu with the updated button text
|
448
|
+
profile_menu = Menu(
|
449
|
+
*[MenuItem(("Active: " if profile == get_master_config().current_profile else "") + profile, self.switch_profile) for
|
450
|
+
profile in
|
451
|
+
get_master_config().get_all_profile_names()]
|
452
|
+
)
|
453
|
+
|
454
|
+
menu = Menu(
|
455
|
+
MenuItem("Open Settings", open_settings, default=True),
|
456
|
+
MenuItem("Open Multi-Mine GUI", open_multimine),
|
457
|
+
MenuItem("Open Log", open_log),
|
458
|
+
MenuItem("Toggle Replay Buffer", self.play_pause),
|
459
|
+
MenuItem("Restart OBS", restart_obs),
|
460
|
+
MenuItem("Switch Profile", profile_menu),
|
461
|
+
MenuItem("Exit", exit_program)
|
462
|
+
)
|
463
|
+
|
464
|
+
self.icon.menu = menu
|
465
|
+
self.icon.update_menu()
|
466
|
+
|
467
|
+
def switch_profile(self, icon, item):
|
468
|
+
if "Active:" in item.text:
|
469
|
+
logger.error("You cannot switch to the currently active profile!")
|
470
|
+
return
|
471
|
+
logger.info(f"Switching to profile: {item.text}")
|
472
|
+
prev_config = get_config()
|
473
|
+
get_master_config().current_profile = item.text
|
474
|
+
switch_profile_and_save(item.text)
|
475
|
+
settings_window.reload_settings()
|
476
|
+
self.update_icon()
|
477
|
+
if get_config().restart_required(prev_config):
|
478
|
+
send_restart_signal()
|
418
479
|
|
419
|
-
def
|
420
|
-
|
480
|
+
def play_pause(self, icon, item):
|
481
|
+
global obs_paused, menu
|
482
|
+
obs.toggle_replay_buffer()
|
483
|
+
self.update_icon()
|
421
484
|
|
485
|
+
def stop(self):
|
486
|
+
if self.icon:
|
487
|
+
self.icon.stop()
|
422
488
|
|
423
|
-
|
424
|
-
global menu, icon
|
425
|
-
# Recreate the menu with the updated button text
|
426
|
-
profile_menu = Menu(
|
427
|
-
*[MenuItem(("Active: " if profile == get_master_config().current_profile else "") + profile, switch_profile) for
|
428
|
-
profile in
|
429
|
-
get_master_config().get_all_profile_names()]
|
430
|
-
)
|
431
|
-
|
432
|
-
menu = Menu(
|
433
|
-
MenuItem("Open Settings", open_settings, default=True),
|
434
|
-
MenuItem("Open Multi-Mine GUI", open_multimine),
|
435
|
-
MenuItem("Open Log", open_log),
|
436
|
-
MenuItem("Toggle Replay Buffer", play_pause),
|
437
|
-
MenuItem("Restart OBS", restart_obs),
|
438
|
-
MenuItem("Switch Profile", profile_menu),
|
439
|
-
MenuItem("Exit", exit_program)
|
440
|
-
)
|
441
|
-
|
442
|
-
icon.menu = menu
|
443
|
-
icon.update_menu()
|
444
|
-
|
445
|
-
|
446
|
-
def switch_profile(icon, item):
|
447
|
-
if "Active:" in item.text:
|
448
|
-
logger.error("You cannot switch to the currently active profile!")
|
449
|
-
return
|
450
|
-
logger.info(f"Switching to profile: {item.text}")
|
451
|
-
prev_config = get_config()
|
452
|
-
get_master_config().current_profile = item.text
|
453
|
-
switch_profile_and_save(item.text)
|
454
|
-
settings_window.reload_settings()
|
455
|
-
update_icon()
|
456
|
-
if get_config().restart_required(prev_config):
|
457
|
-
send_restart_signal()
|
458
|
-
|
459
|
-
|
460
|
-
def run_tray():
|
461
|
-
global menu, icon
|
462
|
-
|
463
|
-
profile_menu = Menu(
|
464
|
-
*[MenuItem(("Active: " if profile == get_master_config().current_profile else "") + profile, switch_profile) for
|
465
|
-
profile in
|
466
|
-
get_master_config().get_all_profile_names()]
|
467
|
-
)
|
468
|
-
|
469
|
-
menu = Menu(
|
470
|
-
MenuItem("Open Settings", open_settings, default=True),
|
471
|
-
MenuItem("Open Texthooker", texthooking_page.open_texthooker),
|
472
|
-
MenuItem("Open Log", open_log),
|
473
|
-
MenuItem("Toggle Replay Buffer", play_pause),
|
474
|
-
MenuItem("Restart OBS", restart_obs),
|
475
|
-
MenuItem("Switch Profile", profile_menu),
|
476
|
-
MenuItem("Exit", exit_program)
|
477
|
-
)
|
478
|
-
|
479
|
-
icon = Icon("TrayApp", create_image(), "GameSentenceMiner", menu)
|
480
|
-
icon.run()
|
489
|
+
gsm_tray = GSMTray()
|
481
490
|
|
482
491
|
|
483
492
|
# def close_obs():
|
@@ -551,8 +560,8 @@ def cleanup():
|
|
551
560
|
proc.kill()
|
552
561
|
logger.error(f"Error terminating process {proc}: {e}")
|
553
562
|
|
554
|
-
if
|
555
|
-
|
563
|
+
if gsm_tray:
|
564
|
+
gsm_tray.stop()
|
556
565
|
|
557
566
|
for video in gsm_state.videos_to_remove:
|
558
567
|
try:
|
@@ -608,7 +617,7 @@ def initialize(reloading=False):
|
|
608
617
|
|
609
618
|
|
610
619
|
def initialize_async():
|
611
|
-
tasks = [connect_websocket
|
620
|
+
tasks = [connect_websocket]
|
612
621
|
threads = []
|
613
622
|
tasks.append(anki.start_monitoring_anki)
|
614
623
|
for task in tasks:
|
@@ -633,11 +642,12 @@ def handle_websocket_message(message: Message):
|
|
633
642
|
case FunctionName.OPEN_LOG:
|
634
643
|
open_log()
|
635
644
|
case FunctionName.TOGGLE_REPLAY_BUFFER:
|
636
|
-
|
645
|
+
obs.toggle_replay_buffer()
|
637
646
|
case FunctionName.RESTART_OBS:
|
638
647
|
restart_obs()
|
639
648
|
case FunctionName.EXIT:
|
640
|
-
|
649
|
+
cleanup()
|
650
|
+
sys.exit(0)
|
641
651
|
case FunctionName.CONNECT:
|
642
652
|
logger.debug("Electron WSS connected")
|
643
653
|
case _:
|
@@ -671,7 +681,7 @@ async def register_scene_switcher_callback():
|
|
671
681
|
all_configured_scenes = [
|
672
682
|
config.scenes for config in get_master_config().configs.values()]
|
673
683
|
print(all_configured_scenes)
|
674
|
-
matching_configs = [name.strip() for name, config in
|
684
|
+
matching_configs = [name.strip() for name, config in get_master_config().configs.items(
|
675
685
|
) if scene.strip() in config.scenes]
|
676
686
|
switch_to = None
|
677
687
|
|
@@ -692,7 +702,7 @@ async def register_scene_switcher_callback():
|
|
692
702
|
get_master_config().current_profile = switch_to
|
693
703
|
switch_profile_and_save(switch_to)
|
694
704
|
settings_window.reload_settings()
|
695
|
-
update_icon()
|
705
|
+
gsm_tray.update_icon()
|
696
706
|
|
697
707
|
await obs.register_scene_change_callback(scene_switcher_callback)
|
698
708
|
|
@@ -763,7 +773,8 @@ async def async_main(reloading=False):
|
|
763
773
|
try:
|
764
774
|
if get_config().general.open_config_on_startup:
|
765
775
|
root.after(50, settings_window.show)
|
766
|
-
|
776
|
+
root.after(50, gsm_tray.start)
|
777
|
+
settings_window.add_save_hook(gsm_tray.update_icon)
|
767
778
|
settings_window.on_exit = exit_program
|
768
779
|
root.mainloop()
|
769
780
|
except KeyboardInterrupt:
|
@@ -1,16 +1,17 @@
|
|
1
1
|
import asyncio
|
2
|
+
import json
|
2
3
|
import os.path
|
3
4
|
import subprocess
|
4
5
|
import threading
|
5
6
|
import time
|
6
|
-
|
7
|
+
import logging
|
7
8
|
|
8
9
|
import psutil
|
9
10
|
|
10
11
|
import obsws_python as obs
|
11
12
|
|
12
13
|
from GameSentenceMiner.util import configuration
|
13
|
-
from GameSentenceMiner.util.configuration import
|
14
|
+
from GameSentenceMiner.util.configuration import get_app_directory, get_config, get_master_config, is_windows, save_full_config, reload_config, logger, gsm_status, gsm_state
|
14
15
|
from GameSentenceMiner.util.gsm_utils import sanitize_filename, make_unique_file_name
|
15
16
|
import tkinter as tk
|
16
17
|
from tkinter import messagebox
|
@@ -54,7 +55,7 @@ class OBSConnectionManager(threading.Thread):
|
|
54
55
|
if gsm_status.obs_connected and not replay_buffer_status and not self.said_no_to_replay_buffer:
|
55
56
|
try:
|
56
57
|
self.check_output()
|
57
|
-
except Exception
|
58
|
+
except Exception:
|
58
59
|
pass
|
59
60
|
except Exception as e:
|
60
61
|
logger.error(f"Error when running Extra Utils in OBS Health Check, Keeping ConnectionManager Alive: {e}")
|
@@ -251,7 +252,6 @@ def connect_to_obs_sync(retry=2, check_output=True):
|
|
251
252
|
logger.error(f"Failed to connect to OBS WebSocket: {e}")
|
252
253
|
client = None
|
253
254
|
event_client = None
|
254
|
-
connecting = False
|
255
255
|
break
|
256
256
|
time.sleep(1)
|
257
257
|
retry -= 1
|
@@ -483,6 +483,9 @@ def set_fit_to_screen_for_scene_items(scene_name: str):
|
|
483
483
|
try:
|
484
484
|
# 1. Get the canvas (base) resolution from OBS video settings
|
485
485
|
video_settings = client.get_video_settings()
|
486
|
+
if not hasattr(video_settings, 'base_width') or not hasattr(video_settings, 'base_height'):
|
487
|
+
logger.debug("Video settings do not have base_width or base_height attributes, probably weird websocket error issue? Idk what causes it..")
|
488
|
+
return
|
486
489
|
canvas_width = video_settings.base_width
|
487
490
|
canvas_height = video_settings.base_height
|
488
491
|
|
@@ -499,12 +502,23 @@ def set_fit_to_screen_for_scene_items(scene_name: str):
|
|
499
502
|
item_id = item['sceneItemId']
|
500
503
|
source_name = item['sourceName']
|
501
504
|
|
505
|
+
scene_item_transform = item.get('sceneItemTransform', {})
|
506
|
+
|
507
|
+
source_width = scene_item_transform.get('sourceWidth', None)
|
508
|
+
source_height = scene_item_transform.get('sourceHeight', None)
|
509
|
+
|
502
510
|
# This transform object is the equivalent of "Fit to Screen"
|
503
511
|
fit_to_screen_transform = {
|
504
512
|
'boundsType': 'OBS_BOUNDS_SCALE_INNER',
|
505
513
|
'alignment': 5, # 5 = Center alignment (horizontal and vertical)
|
506
514
|
'boundsWidth': canvas_width,
|
507
515
|
'boundsHeight': canvas_height,
|
516
|
+
'positionX': 0,
|
517
|
+
'positionY': 0,
|
518
|
+
'cropLeft': 0 if canvas_width >= source_width else (source_width - canvas_width) // 2,
|
519
|
+
'cropRight': 0 if canvas_width >= source_width else (source_width - canvas_width) // 2,
|
520
|
+
'cropTop': 0 if canvas_height >= source_height else (source_height - canvas_height) // 2,
|
521
|
+
'cropBottom': 0 if canvas_height >= source_height else (source_height - canvas_height) // 2,
|
508
522
|
}
|
509
523
|
|
510
524
|
try:
|
@@ -559,7 +573,6 @@ def main():
|
|
559
573
|
disconnect_from_obs()
|
560
574
|
|
561
575
|
if __name__ == '__main__':
|
562
|
-
from mss import mss
|
563
576
|
logging.basicConfig(level=logging.INFO)
|
564
577
|
connect_to_obs_sync()
|
565
578
|
set_fit_to_screen_for_scene_items(get_current_scene())
|
{gamesentenceminer-2.14.20 → gamesentenceminer-2.15.0}/GameSentenceMiner/ocr/gsm_ocr_config.py
RENAMED
@@ -83,7 +83,6 @@ class OCRConfig:
|
|
83
83
|
]
|
84
84
|
|
85
85
|
def scale_to_custom_size(self, width, height):
|
86
|
-
print(self.pre_scale_rectangles)
|
87
86
|
self.rectangles = self.pre_scale_rectangles.copy()
|
88
87
|
if self.coordinate_system and self.coordinate_system == "percentage":
|
89
88
|
for rectangle in self.rectangles:
|
@@ -106,6 +106,7 @@ def empty_post_process(text):
|
|
106
106
|
|
107
107
|
def post_process(text, keep_blank_lines=False):
|
108
108
|
import jaconv
|
109
|
+
text = text.replace("\"", "")
|
109
110
|
if keep_blank_lines:
|
110
111
|
text = '\n'.join([''.join(i.split()) for i in text.splitlines()])
|
111
112
|
else:
|
@@ -354,8 +355,8 @@ class GoogleLens:
|
|
354
355
|
response_proto = LensOverlayServerResponse().FromString(res.content)
|
355
356
|
response_dict = response_proto.to_dict(betterproto.Casing.SNAKE)
|
356
357
|
|
357
|
-
if os.path.exists(r"C:\Users\Beangate\GSM\
|
358
|
-
with open(os.path.join(r"C:\Users\Beangate\GSM\
|
358
|
+
if os.path.exists(r"C:\Users\Beangate\GSM\test"):
|
359
|
+
with open(os.path.join(r"C:\Users\Beangate\GSM\test", 'glens_response.json'), 'w', encoding='utf-8') as f:
|
359
360
|
json.dump(response_dict, f, indent=4, ensure_ascii=False)
|
360
361
|
res = ''
|
361
362
|
text = response_dict['objects_response']['text']
|
@@ -377,16 +378,13 @@ class GoogleLens:
|
|
377
378
|
res += 'BLANK_LINE'
|
378
379
|
for line in paragraph['lines']:
|
379
380
|
if furigana_filter_sensitivity:
|
381
|
+
line_width = line['geometry']['bounding_box']['width'] * img.width
|
382
|
+
line_height = line['geometry']['bounding_box']['height'] * img.height
|
380
383
|
for word in line['words']:
|
381
384
|
if self.punctuation_regex.findall(word['plain_text']):
|
382
385
|
res += word['plain_text'] + word['text_separator']
|
383
386
|
continue
|
384
|
-
if
|
385
|
-
res += word['plain_text'] + word['text_separator']
|
386
|
-
continue
|
387
|
-
word_width = word['geometry']['bounding_box']['width'] * img.width
|
388
|
-
word_height = word['geometry']['bounding_box']['height'] * img.height
|
389
|
-
if word_width > furigana_filter_sensitivity and word_height > furigana_filter_sensitivity:
|
387
|
+
if line_width > furigana_filter_sensitivity and line_height > furigana_filter_sensitivity:
|
390
388
|
res += word['plain_text'] + word['text_separator']
|
391
389
|
else:
|
392
390
|
skipped.extend(word['plain_text'])
|
@@ -394,7 +392,8 @@ class GoogleLens:
|
|
394
392
|
else:
|
395
393
|
for word in line['words']:
|
396
394
|
res += word['plain_text'] + word['text_separator']
|
397
|
-
|
395
|
+
res += '\n'
|
396
|
+
|
398
397
|
previous_line = paragraph
|
399
398
|
res += '\n'
|
400
399
|
# logger.info(
|
@@ -920,7 +919,7 @@ class OneOCR:
|
|
920
919
|
self.regex = re.compile(
|
921
920
|
r'[a-zA-Z\u00C0-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u1D00-\u1D7F\u1D80-\u1DBF\u1E00-\u1EFF\u2C60-\u2C7F\uA720-\uA7FF\uAB30-\uAB6F]')
|
922
921
|
|
923
|
-
def __call__(self, img, furigana_filter_sensitivity=0, return_coords=False, multiple_crop_coords=False, return_one_box=True):
|
922
|
+
def __call__(self, img, furigana_filter_sensitivity=0, return_coords=False, multiple_crop_coords=False, return_one_box=True, return_dict=False):
|
924
923
|
lang = get_ocr_language()
|
925
924
|
if furigana_filter_sensitivity != None:
|
926
925
|
furigana_filter_sensitivity = get_furigana_filter_sensitivity()
|
@@ -940,6 +939,7 @@ class OneOCR:
|
|
940
939
|
return (False, 'Invalid image provided')
|
941
940
|
crop_coords = None
|
942
941
|
crop_coords_list = []
|
942
|
+
ocr_resp = ''
|
943
943
|
if sys.platform == 'win32':
|
944
944
|
try:
|
945
945
|
ocr_resp = self.model.recognize_pil(img)
|
@@ -959,17 +959,17 @@ class OneOCR:
|
|
959
959
|
boxes = []
|
960
960
|
if furigana_filter_sensitivity > 0:
|
961
961
|
for line in filtered_lines:
|
962
|
+
line_x1, line_x2, line_x3, line_x4 = line['bounding_rect']['x1'], line['bounding_rect']['x2'], \
|
963
|
+
line['bounding_rect']['x3'], line['bounding_rect']['x4']
|
964
|
+
line_y1, line_y2, line_y3, line_y4 = line['bounding_rect']['y1'], line['bounding_rect']['y2'], \
|
965
|
+
line['bounding_rect']['y3'], line['bounding_rect']['y4']
|
966
|
+
line_width = max(line_x2 - line_x1, line_x3 - line_x4)
|
967
|
+
line_height = max(line_y3 - line_y1, line_y4 - line_y2)
|
962
968
|
for char in line['words']:
|
963
969
|
if self.punctuation_regex.findall(char['text']):
|
964
970
|
res += char['text']
|
965
971
|
continue
|
966
|
-
|
967
|
-
char['bounding_rect']['x3'], char['bounding_rect']['x4']
|
968
|
-
y1, y2, y3, y4 = char['bounding_rect']['y1'], char['bounding_rect']['y2'], \
|
969
|
-
char['bounding_rect']['y3'], char['bounding_rect']['y4']
|
970
|
-
width = max(x2 - x1, x3 - x4)
|
971
|
-
height = max(y3 - y1, y4 - y2)
|
972
|
-
if width > furigana_filter_sensitivity and height > furigana_filter_sensitivity:
|
972
|
+
if line_width > furigana_filter_sensitivity and line_height > furigana_filter_sensitivity:
|
973
973
|
res += char['text']
|
974
974
|
else:
|
975
975
|
skipped.extend(char for char in line['text'])
|
@@ -1042,6 +1042,8 @@ class OneOCR:
|
|
1042
1042
|
x.append(crop_coords_list)
|
1043
1043
|
if return_one_box:
|
1044
1044
|
x.append(crop_coords)
|
1045
|
+
if return_dict:
|
1046
|
+
x.append(ocr_resp)
|
1045
1047
|
if is_path:
|
1046
1048
|
img.close()
|
1047
1049
|
return x
|
@@ -1432,10 +1434,10 @@ class localLLMOCR:
|
|
1432
1434
|
self.keep_warm = config.get('keep_warm', True)
|
1433
1435
|
self.custom_prompt = config.get('prompt', None)
|
1434
1436
|
self.available = True
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1437
|
+
if any(x in self.api_url for x in ['localhost', '127.0.0.1']):
|
1438
|
+
if not self.check_connection(self.api_url):
|
1439
|
+
logger.warning('Local LLM OCR API is not reachable')
|
1440
|
+
return
|
1439
1441
|
self.client = openai.OpenAI(
|
1440
1442
|
base_url=self.api_url.replace('/v1/chat/completions', '/v1'),
|
1441
1443
|
api_key=self.api_key
|
@@ -1492,7 +1494,7 @@ class localLLMOCR:
|
|
1492
1494
|
prompt = self.custom_prompt.strip()
|
1493
1495
|
else:
|
1494
1496
|
prompt = f"""
|
1495
|
-
Extract all {CommonLanguages.from_code(get_ocr_language()).name} Text from Image. Ignore all Furigana. Do not return any commentary, just the text in the image. If there is no text in the image, return "" (Empty String).
|
1497
|
+
Extract all {CommonLanguages.from_code(get_ocr_language()).name} Text from Image. Ignore all Furigana. Do not return any commentary, just the text in the image. Do not Translate. If there is no text in the image, return "" (Empty String).
|
1496
1498
|
"""
|
1497
1499
|
|
1498
1500
|
response = self.client.chat.completions.create(
|
@@ -1,5 +1,6 @@
|
|
1
|
-
from
|
2
|
-
from
|
1
|
+
from GameSentenceMiner.ocr.gsm_ocr_config import set_dpi_awareness, get_scene_ocr_config
|
2
|
+
from GameSentenceMiner.util.electron_config import * # noqa: F403
|
3
|
+
from GameSentenceMiner.util.gsm_utils import do_text_replacements, OCR_REPLACEMENTS_FILE
|
3
4
|
|
4
5
|
try:
|
5
6
|
import win32gui
|
@@ -28,7 +29,6 @@ import signal
|
|
28
29
|
import threading
|
29
30
|
from pathlib import Path
|
30
31
|
import queue
|
31
|
-
import io
|
32
32
|
import re
|
33
33
|
import logging
|
34
34
|
import inspect
|
@@ -39,23 +39,22 @@ import mss
|
|
39
39
|
import asyncio
|
40
40
|
import websockets
|
41
41
|
import socketserver
|
42
|
-
import
|
42
|
+
import cv2
|
43
|
+
import numpy as np
|
43
44
|
|
44
45
|
from datetime import datetime, timedelta
|
45
|
-
from PIL import Image, ImageDraw
|
46
|
+
from PIL import Image, ImageDraw
|
46
47
|
from loguru import logger
|
47
48
|
from desktop_notifier import DesktopNotifierSync
|
48
49
|
import psutil
|
49
50
|
|
50
|
-
import
|
51
|
-
from .ocr import *
|
52
|
-
try:
|
53
|
-
from .secret import *
|
54
|
-
except ImportError:
|
55
|
-
pass
|
51
|
+
from .ocr import * # noqa: F403
|
56
52
|
from .config import Config
|
57
53
|
from .screen_coordinate_picker import get_screen_selection
|
58
|
-
from GameSentenceMiner.util.configuration import get_temporary_directory
|
54
|
+
from GameSentenceMiner.util.configuration import get_temporary_directory
|
55
|
+
|
56
|
+
from skimage.metrics import structural_similarity as ssim
|
57
|
+
from typing import Union
|
59
58
|
|
60
59
|
config = None
|
61
60
|
last_image = None
|
@@ -799,8 +798,6 @@ class ScreenshotThread(threading.Thread):
|
|
799
798
|
self.windows_window_tracker_instance.join()
|
800
799
|
|
801
800
|
|
802
|
-
import cv2
|
803
|
-
import numpy as np
|
804
801
|
|
805
802
|
def apply_adaptive_threshold_filter(img):
|
806
803
|
img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
|
@@ -865,11 +862,6 @@ def are_images_identical(img1, img2, img2_np=None):
|
|
865
862
|
return (img1_np.shape == img2_np.shape) and np.array_equal(img1_np, img2_np)
|
866
863
|
|
867
864
|
|
868
|
-
import cv2
|
869
|
-
import numpy as np
|
870
|
-
from skimage.metrics import structural_similarity as ssim
|
871
|
-
from typing import Union
|
872
|
-
|
873
865
|
ImageType = Union[np.ndarray, Image.Image]
|
874
866
|
|
875
867
|
def _prepare_image(image: ImageType) -> np.ndarray:
|
@@ -1319,6 +1311,10 @@ def on_screenshot_combo():
|
|
1319
1311
|
def on_window_minimized(minimized):
|
1320
1312
|
global screencapture_window_visible
|
1321
1313
|
screencapture_window_visible = not minimized
|
1314
|
+
|
1315
|
+
|
1316
|
+
def do_configured_ocr_replacements(text: str) -> str:
|
1317
|
+
return do_text_replacements(text, OCR_REPLACEMENTS_FILE)
|
1322
1318
|
|
1323
1319
|
|
1324
1320
|
def process_and_write_results(img_or_path, write_to=None, last_result=None, filtering=None, notify=None, engine=None, ocr_start_time=None, furigana_filter_sensitivity=0):
|
@@ -1364,14 +1360,21 @@ def process_and_write_results(img_or_path, write_to=None, last_result=None, filt
|
|
1364
1360
|
# print(engine_index)
|
1365
1361
|
|
1366
1362
|
if res:
|
1363
|
+
text = do_configured_ocr_replacements(text)
|
1367
1364
|
if filtering:
|
1368
1365
|
text, orig_text = filtering(text, last_result)
|
1369
1366
|
if get_ocr_language() == "ja" or get_ocr_language() == "zh":
|
1370
1367
|
text = post_process(text, keep_blank_lines=get_ocr_keep_newline())
|
1371
|
-
logger.opt(ansi=True).info(
|
1372
|
-
f'Text recognized in {end_time - start_time:0.03f}s using <{engine_color}>{engine_instance.readable_name}</{engine_color}>: {text}')
|
1373
1368
|
if notify and config.get_general('notifications'):
|
1374
1369
|
notifier.send(title='owocr', message='Text recognized: ' + text)
|
1370
|
+
|
1371
|
+
if text and write_to is not None:
|
1372
|
+
if check_text_is_all_menu(text, crop_coords):
|
1373
|
+
logger.opt(ansi=True).info('Text is identified as all menu items, skipping further processing.')
|
1374
|
+
return orig_text, ''
|
1375
|
+
|
1376
|
+
logger.opt(ansi=True).info(
|
1377
|
+
f'Text recognized in {end_time - start_time:0.03f}s using <{engine_color}>{engine_instance.readable_name}</{engine_color}>: {text}')
|
1375
1378
|
|
1376
1379
|
if write_to == 'websocket':
|
1377
1380
|
websocket_server_thread.send_text(text)
|
@@ -1395,6 +1398,83 @@ def process_and_write_results(img_or_path, write_to=None, last_result=None, filt
|
|
1395
1398
|
|
1396
1399
|
return orig_text, text
|
1397
1400
|
|
1401
|
+
def check_text_is_all_menu(text: str, crop_coords: tuple) -> bool:
|
1402
|
+
"""
|
1403
|
+
Checks if the recognized text consists entirely of menu items.
|
1404
|
+
This function checks if the detected text area falls entirely within secondary rectangles (menu areas).
|
1405
|
+
|
1406
|
+
:param text: The recognized text from OCR.
|
1407
|
+
:param crop_coords: Tuple containing (x, y, width, height) of the detected text area relative to the cropped image.
|
1408
|
+
:return: True if the text is all menu items (within secondary rectangles), False otherwise.
|
1409
|
+
"""
|
1410
|
+
if not text or not crop_coords:
|
1411
|
+
return False
|
1412
|
+
|
1413
|
+
original_width = obs_screenshot_thread.width
|
1414
|
+
original_height = obs_screenshot_thread.height
|
1415
|
+
crop_x, crop_y, crop_w, crop_h = crop_coords
|
1416
|
+
|
1417
|
+
ocr_config = get_scene_ocr_config()
|
1418
|
+
|
1419
|
+
if not any(rect.is_secondary for rect in ocr_config.rectangles):
|
1420
|
+
return False
|
1421
|
+
|
1422
|
+
ocr_config.scale_to_custom_size(original_width, original_height)
|
1423
|
+
if not ocr_config or not ocr_config.rectangles:
|
1424
|
+
return False
|
1425
|
+
|
1426
|
+
primary_rectangles = [rect for rect in ocr_config.rectangles if not rect.is_excluded and not rect.is_secondary]
|
1427
|
+
menu_rectangles = [rect for rect in ocr_config.rectangles if rect.is_secondary and not rect.is_excluded]
|
1428
|
+
|
1429
|
+
if not menu_rectangles:
|
1430
|
+
return False
|
1431
|
+
|
1432
|
+
if not primary_rectangles:
|
1433
|
+
if crop_x < 0 or crop_y < 0 or crop_x + crop_w > original_width or crop_y + crop_h > original_height:
|
1434
|
+
return False
|
1435
|
+
for menu_rect in menu_rectangles:
|
1436
|
+
rect_left, rect_top, rect_width, rect_height = menu_rect.coordinates
|
1437
|
+
rect_right = rect_left + rect_width
|
1438
|
+
rect_bottom = rect_top + rect_height
|
1439
|
+
if (crop_x >= rect_left and crop_y >= rect_top and
|
1440
|
+
crop_x + crop_w <= rect_right and crop_y + crop_h <= rect_bottom):
|
1441
|
+
return True
|
1442
|
+
return False
|
1443
|
+
|
1444
|
+
primary_rectangles.sort(key=lambda r: r.coordinates[1])
|
1445
|
+
|
1446
|
+
if len(primary_rectangles) == 1:
|
1447
|
+
primary_rect = primary_rectangles[0]
|
1448
|
+
primary_left, primary_top = primary_rect.coordinates[0], primary_rect.coordinates[1]
|
1449
|
+
original_x = crop_x + primary_left
|
1450
|
+
original_y = crop_y + primary_top
|
1451
|
+
else:
|
1452
|
+
current_y_offset = 0
|
1453
|
+
original_x = None
|
1454
|
+
original_y = None
|
1455
|
+
for i, primary_rect in enumerate(primary_rectangles):
|
1456
|
+
primary_left, primary_top, primary_width, primary_height = primary_rect.coordinates
|
1457
|
+
section_height = primary_height
|
1458
|
+
if crop_y >= current_y_offset and crop_y < current_y_offset + section_height:
|
1459
|
+
original_x = crop_x + primary_left
|
1460
|
+
original_y = (crop_y - current_y_offset) + primary_top
|
1461
|
+
break
|
1462
|
+
current_y_offset += section_height + 50
|
1463
|
+
if original_x is None or original_y is None:
|
1464
|
+
return False
|
1465
|
+
|
1466
|
+
if original_x < 0 or original_y < 0 or original_x > original_width or original_y > original_height:
|
1467
|
+
return False
|
1468
|
+
|
1469
|
+
for menu_rect in menu_rectangles:
|
1470
|
+
rect_left, rect_top, rect_width, rect_height = menu_rect.coordinates
|
1471
|
+
rect_right = rect_left + rect_width
|
1472
|
+
rect_bottom = rect_top + rect_height
|
1473
|
+
if (original_x >= rect_left and original_y >= rect_top and
|
1474
|
+
original_x <= rect_right and original_y <= rect_bottom):
|
1475
|
+
return True
|
1476
|
+
|
1477
|
+
return False
|
1398
1478
|
|
1399
1479
|
def get_path_key(path):
|
1400
1480
|
return path, path.lstat().st_mtime
|
@@ -1785,7 +1865,7 @@ def run(read_from=None,
|
|
1785
1865
|
continue
|
1786
1866
|
|
1787
1867
|
res, text = process_and_write_results(img, write_to, last_result, filtering, notify,
|
1788
|
-
ocr_start_time=ocr_start_time, furigana_filter_sensitivity=
|
1868
|
+
ocr_start_time=ocr_start_time, furigana_filter_sensitivity=None if get_ocr_two_pass_ocr() else get_furigana_filter_sensitivity())
|
1789
1869
|
if not text and not previous_text and time.time() - last_result_time > 10:
|
1790
1870
|
sleep_time_to_add += .005
|
1791
1871
|
logger.info(f"No text detected again, sleeping.")
|