GameSentenceMiner 2.17.0__tar.gz → 2.17.2__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.

Potentially problematic release.


This version of GameSentenceMiner might be problematic. Click here for more details.

Files changed (104) hide show
  1. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/anki.py +31 -3
  2. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/config_gui.py +26 -2
  3. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/gametext.py +4 -3
  4. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/gsm.py +19 -23
  5. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/obs.py +17 -7
  6. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/ocr/owocr_helper.py +11 -8
  7. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/owocr/owocr/run.py +11 -5
  8. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/configuration.py +7 -5
  9. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/db.py +176 -8
  10. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/downloader/download_tools.py +57 -24
  11. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/ffmpeg.py +5 -2
  12. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/get_overlay_coords.py +3 -0
  13. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/gsm_utils.py +0 -54
  14. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/vad.py +5 -2
  15. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/database_api.py +12 -1
  16. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/gsm_websocket.py +1 -1
  17. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/css/shared.css +20 -0
  18. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/css/stats.css +496 -1
  19. gamesentenceminer-2.17.2/GameSentenceMiner/web/static/js/anki_stats.js +168 -0
  20. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/js/shared.js +2 -49
  21. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/js/stats.js +274 -39
  22. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/templates/anki_stats.html +36 -0
  23. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/templates/index.html +1 -1
  24. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/templates/stats.html +35 -15
  25. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/texthooking_page.py +31 -8
  26. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner.egg-info/PKG-INFO +1 -1
  27. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/PKG-INFO +1 -1
  28. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/pyproject.toml +4 -4
  29. gamesentenceminer-2.17.0/GameSentenceMiner/web/static/js/anki_stats.js +0 -84
  30. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/__init__.py +0 -0
  31. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/ai/__init__.py +0 -0
  32. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/ai/ai_prompting.py +0 -0
  33. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/assets/__init__.py +0 -0
  34. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/assets/icon.png +0 -0
  35. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/assets/icon128.png +0 -0
  36. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/assets/icon256.png +0 -0
  37. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/assets/icon32.png +0 -0
  38. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/assets/icon512.png +0 -0
  39. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/assets/icon64.png +0 -0
  40. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/assets/pickaxe.png +0 -0
  41. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/locales/en_us.json +0 -0
  42. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/locales/ja_jp.json +0 -0
  43. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/locales/zh_cn.json +0 -0
  44. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/ocr/__init__.py +0 -0
  45. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/ocr/gsm_ocr_config.py +0 -0
  46. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/ocr/ocrconfig.py +0 -0
  47. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/ocr/owocr_area_selector.py +0 -0
  48. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/ocr/ss_picker.py +0 -0
  49. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/owocr/owocr/__init__.py +0 -0
  50. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/owocr/owocr/__main__.py +0 -0
  51. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/owocr/owocr/config.py +0 -0
  52. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -0
  53. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/owocr/owocr/ocr.py +0 -0
  54. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -0
  55. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/tools/__init__.py +0 -0
  56. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/tools/audio_offset_selector.py +0 -0
  57. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/tools/furigana_filter_preview.py +0 -0
  58. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/tools/ss_selector.py +0 -0
  59. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/tools/window_transparency.py +0 -0
  60. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/__init__.py +0 -0
  61. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/communication/__init__.py +0 -0
  62. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/communication/send.py +0 -0
  63. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/communication/websocket.py +0 -0
  64. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/downloader/Untitled_json.py +0 -0
  65. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/downloader/__init__.py +0 -0
  66. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/downloader/oneocr_dl.py +0 -0
  67. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/electron_config.py +0 -0
  68. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/model.py +0 -0
  69. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/notification.py +0 -0
  70. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/text_log.py +0 -0
  71. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/win10toast/__init__.py +0 -0
  72. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/util/win10toast/__main__.py +0 -0
  73. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/__init__.py +0 -0
  74. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/events.py +0 -0
  75. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/service.py +0 -0
  76. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/__init__.py +0 -0
  77. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
  78. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/css/kanji-grid.css +0 -0
  79. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/css/search.css +0 -0
  80. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/favicon-96x96.png +0 -0
  81. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/favicon.ico +0 -0
  82. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/favicon.svg +0 -0
  83. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/js/database.js +0 -0
  84. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/js/kanji-grid.js +0 -0
  85. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/js/search.js +0 -0
  86. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/site.webmanifest +0 -0
  87. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/style.css +0 -0
  88. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
  89. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
  90. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/stats.py +0 -0
  91. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/templates/components/navigation.html +0 -0
  92. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/templates/components/theme-styles.html +0 -0
  93. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/templates/database.html +0 -0
  94. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/templates/search.html +0 -0
  95. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/web/templates/utility.html +0 -0
  96. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner/wip/__init___.py +0 -0
  97. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner.egg-info/SOURCES.txt +0 -0
  98. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner.egg-info/dependency_links.txt +0 -0
  99. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner.egg-info/entry_points.txt +0 -0
  100. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner.egg-info/requires.txt +0 -0
  101. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/GameSentenceMiner.egg-info/top_level.txt +0 -0
  102. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/LICENSE +0 -0
  103. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/README.md +0 -0
  104. {gamesentenceminer-2.17.0 → gamesentenceminer-2.17.2}/setup.cfg +0 -0
@@ -1,4 +1,8 @@
1
1
  import copy
2
+ import json
3
+ import os
4
+ import shutil
5
+ import threading
2
6
  from pathlib import Path
3
7
  import queue
4
8
  import time
@@ -15,8 +19,8 @@ from GameSentenceMiner.util.db import GameLinesTable
15
19
  from GameSentenceMiner.util.gsm_utils import make_unique, sanitize_filename, wait_for_stable_file, remove_html_and_cloze_tags, combine_dialogue, \
16
20
  run_new_thread, open_audio_in_external
17
21
  from GameSentenceMiner.util import ffmpeg, notification
18
- from GameSentenceMiner.util.configuration import *
19
- from GameSentenceMiner.util.configuration import get_config
22
+ from GameSentenceMiner.util.configuration import get_config, AnkiUpdateResult, logger, anki_results, gsm_status, \
23
+ gsm_state
20
24
  from GameSentenceMiner.util.model import AnkiCard
21
25
  from GameSentenceMiner.util.text_log import get_all_lines, get_text_event, get_mined_line, lines_match
22
26
  from GameSentenceMiner.obs import get_current_game
@@ -554,16 +558,40 @@ def start_monitoring_anki():
554
558
 
555
559
  # --- Anki Stats Kanji Extraction Utilities ---
556
560
 
557
- def get_all_anki_first_field_kanji():
561
+ def get_anki_earliest_date():
562
+ """
563
+ Fetches the earliest Anki card ID.
564
+ """
565
+ try:
566
+ note_ids = invoke("findCards", query="")
567
+ if not note_ids:
568
+ return 0
569
+
570
+ # Return the first card ID as the "earliest"
571
+ # return note_ids[0]
572
+ return min(note_ids)
573
+
574
+ except Exception as e:
575
+ logger.error(f"Failed to fetch kanji from Anki: {e}")
576
+ return 0
577
+
578
+ def get_all_anki_first_field_kanji(start_timestamp = None, end_timestamp = None):
558
579
  """
559
580
  Fetch all notes from Anki and extract unique kanji from the first field of each note.
560
581
  Returns a set of kanji characters.
582
+ Optional filtering by start_timestamp and end_timestamp on note IDs.
561
583
  """
562
584
  from GameSentenceMiner.web.stats import is_kanji
563
585
  try:
564
586
  note_ids = invoke("findNotes", query="")
565
587
  if not note_ids:
566
588
  return set()
589
+
590
+ # Filter note IDs by start and end timestamps if provided
591
+ if (start_timestamp and end_timestamp):
592
+ note_ids = [nid for nid in note_ids if int(start_timestamp) <= nid <= int(end_timestamp)]
593
+ if not note_ids:
594
+ return set()
567
595
  kanji_set = set()
568
596
  batch_size = 1000
569
597
  for i in range(0, len(note_ids), batch_size):
@@ -1,7 +1,9 @@
1
1
  import asyncio
2
2
  import copy
3
3
  import json
4
+ import os
4
5
  import subprocess
6
+ import sys
5
7
  import time
6
8
  import tkinter as tk
7
9
  from tkinter import filedialog, messagebox, simpledialog, scrolledtext, font
@@ -12,7 +14,11 @@ import ttkbootstrap as ttk
12
14
  from GameSentenceMiner import obs
13
15
  from GameSentenceMiner.util import configuration
14
16
  from GameSentenceMiner.util.communication.send import send_restart_signal
15
- from GameSentenceMiner.util.configuration import *
17
+ from GameSentenceMiner.util.configuration import Config, Locale, logger, CommonLanguages, ProfileConfig, General, Paths, \
18
+ Anki, Features, Screenshot, Audio, OBS, Hotkeys, VAD, Overlay, Ai, Advanced, OverlayEngine, get_app_directory, \
19
+ get_config, is_beangate, AVAILABLE_LANGUAGES, WHSIPER_LARGE, WHISPER_TINY, WHISPER_BASE, WHISPER_SMALL, \
20
+ WHISPER_MEDIUM, WHISPER_TURBO, SILERO, WHISPER, OFF, gsm_state, DEFAULT_CONFIG, get_latest_version, \
21
+ get_current_version, AI_GEMINI, AI_GROQ, AI_OPENAI, save_full_config, get_default_anki_media_collection_path
16
22
  from GameSentenceMiner.util.db import AIModelsTable
17
23
  from GameSentenceMiner.util.downloader.download_tools import download_ocenaudio_if_needed
18
24
 
@@ -378,6 +384,7 @@ class ConfigApp:
378
384
  self.ocr_websocket_port_value = tk.StringVar(value=str(self.settings.advanced.ocr_websocket_port))
379
385
  self.texthooker_communication_websocket_port_value = tk.StringVar(value=str(self.settings.advanced.texthooker_communication_websocket_port))
380
386
  self.plaintext_websocket_export_port_value = tk.StringVar(value=str(self.settings.advanced.plaintext_websocket_port))
387
+ self.localhost_bind_address_value = tk.StringVar(value=self.settings.advanced.localhost_bind_address)
381
388
 
382
389
  # AI Settings
383
390
  self.ai_enabled_value = tk.BooleanVar(value=self.settings.ai.enabled)
@@ -401,6 +408,7 @@ class ConfigApp:
401
408
  self.overlay_engine_value = tk.StringVar(value=self.settings.overlay.engine)
402
409
  self.periodic_value = tk.BooleanVar(value=self.settings.overlay.periodic)
403
410
  self.periodic_interval_value = tk.StringVar(value=str(self.settings.overlay.periodic_interval))
411
+ self.scan_delay_value = tk.StringVar(value=str(self.settings.overlay.scan_delay))
404
412
 
405
413
  # Master Config Settings
406
414
  self.switch_to_default_if_not_found_value = tk.BooleanVar(value=self.master_config.switch_to_default_if_not_found)
@@ -615,6 +623,7 @@ class ConfigApp:
615
623
  ocr_websocket_port=int(self.ocr_websocket_port_value.get()),
616
624
  texthooker_communication_websocket_port=int(self.texthooker_communication_websocket_port_value.get()),
617
625
  plaintext_websocket_port=int(self.plaintext_websocket_export_port_value.get()),
626
+ localhost_bind_address=self.localhost_bind_address_value.get(),
618
627
  ),
619
628
  ai=Ai(
620
629
  enabled=self.ai_enabled_value.get(),
@@ -637,6 +646,7 @@ class ConfigApp:
637
646
  websocket_port=int(self.overlay_websocket_port_value.get()),
638
647
  monitor_to_capture=self.overlay_monitor.current() if self.monitors else 0,
639
648
  engine=OverlayEngine(self.overlay_engine_value.get()).value if self.overlay_engine_value.get() else OverlayEngine.LENS.value,
649
+ scan_delay=float(self.scan_delay_value.get()),
640
650
  periodic=self.periodic_value.get(),
641
651
  periodic_interval=self.periodic_interval_value.get(),
642
652
  )
@@ -1992,6 +2002,12 @@ class ConfigApp:
1992
2002
  ttk.Entry(advanced_frame, textvariable=self.polling_rate_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1993
2003
  self.current_row += 1
1994
2004
 
2005
+ localhost_bind_address_i18n = advanced_i18n.get('localhost_bind_address', {})
2006
+ HoverInfoLabelWidget(advanced_frame, text=localhost_bind_address_i18n.get('label', 'LocalHost Bind Address:'),
2007
+ tooltip=localhost_bind_address_i18n.get('tooltip', 'Set this to 0.0.0.0 if you want to connect from another device in your LAN, otherwise leave as is.'), row=self.current_row, column=0)
2008
+ ttk.Entry(advanced_frame, textvariable=self.localhost_bind_address_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
2009
+ self.current_row += 1
2010
+
1995
2011
  current_ver_i18n = advanced_i18n.get('current_version', {})
1996
2012
  HoverInfoLabelWidget(advanced_frame, text=current_ver_i18n.get('label', 'Current Version:'), bootstyle="secondary",
1997
2013
  tooltip=current_ver_i18n.get('tooltip', '...'), row=self.current_row, column=0)
@@ -2279,7 +2295,15 @@ class ConfigApp:
2279
2295
  textvariable=self.overlay_engine_value)
2280
2296
  self.overlay_engine.grid(row=self.current_row, column=1, sticky='EW', pady=2)
2281
2297
  self.current_row += 1
2282
-
2298
+
2299
+ # Scan Delay
2300
+ scan_delay_i18n = overlay_i18n.get('scan_delay', {})
2301
+ HoverInfoLabelWidget(overlay_frame, text=scan_delay_i18n.get('label', 'Scan Delay:'),
2302
+ tooltip=scan_delay_i18n.get('tooltip', 'Delay between GSM Receiving Text, and Scanning for Overlay. Increase this value if your game\'s text appears slowly.'),
2303
+ row=self.current_row, column=0)
2304
+ ttk.Entry(overlay_frame, textvariable=self.scan_delay_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
2305
+ self.current_row += 1
2306
+
2283
2307
  # Periodic Settings
2284
2308
  periodic_i18n = overlay_i18n.get('periodic', {})
2285
2309
  HoverInfoLabelWidget(overlay_frame, text=periodic_i18n.get('label', 'Periodic:'),
@@ -1,5 +1,7 @@
1
1
  import asyncio
2
+ import json
2
3
  import re
4
+ from datetime import datetime
3
5
 
4
6
  import pyperclip
5
7
  import requests
@@ -7,12 +9,11 @@ import websockets
7
9
  from websockets import InvalidStatus
8
10
  from rapidfuzz import fuzz
9
11
 
10
-
12
+ from GameSentenceMiner.util.configuration import get_config, gsm_status, logger, gsm_state
11
13
  from GameSentenceMiner.util.db import GameLinesTable
12
14
  from GameSentenceMiner.util.gsm_utils import do_text_replacements, TEXT_REPLACEMENTS_FILE, run_new_thread
13
- from GameSentenceMiner.util.configuration import *
14
- from GameSentenceMiner.util.text_log import *
15
15
  from GameSentenceMiner import obs
16
+ from GameSentenceMiner.util.text_log import add_line, get_text_log
16
17
  from GameSentenceMiner.web.texthooking_page import add_event_to_texthooker, overlay_server_thread
17
18
 
18
19
  from GameSentenceMiner.util.get_overlay_coords import OverlayProcessor
@@ -1,20 +1,3 @@
1
- import tempfile
2
- import time
3
- import asyncio
4
- import subprocess
5
- import sys
6
-
7
- import os
8
- import warnings
9
-
10
- import requests
11
-
12
- from GameSentenceMiner.util.get_overlay_coords import OverlayThread
13
- from GameSentenceMiner.util.gsm_utils import remove_html_and_cloze_tags
14
-
15
- os.environ.pop('TCL_LIBRARY', None)
16
-
17
-
18
1
  def handle_error_in_initialization(e):
19
2
  """Handle errors that occur during initialization."""
20
3
  logger.exception(e, exc_info=True)
@@ -29,9 +12,20 @@ def handle_error_in_initialization(e):
29
12
 
30
13
 
31
14
  try:
15
+ import asyncio
16
+ import os
17
+ import shutil
18
+ import subprocess
19
+ import sys
20
+ import tempfile
21
+ import threading
22
+ import time
23
+ import warnings
24
+ import requests
32
25
  import os.path
33
26
  import signal
34
27
  from subprocess import Popen
28
+ os.environ.pop('TCL_LIBRARY', None)
35
29
 
36
30
  import keyboard
37
31
  import ttkbootstrap as ttk
@@ -42,7 +36,12 @@ try:
42
36
  import psutil
43
37
 
44
38
  start_time = time.time()
45
- from GameSentenceMiner.util.configuration import *
39
+ from GameSentenceMiner.util.configuration import logger, gsm_state, get_config, anki_results, AnkiUpdateResult, \
40
+ get_temporary_directory, get_log_path, get_master_config, switch_profile_and_save, get_app_directory, gsm_status, \
41
+ is_windows, is_linux
42
+ from GameSentenceMiner.util.get_overlay_coords import OverlayThread
43
+ from GameSentenceMiner.util.gsm_utils import remove_html_and_cloze_tags
44
+
46
45
  logger.debug(f"[Import] configuration: {time.time() - start_time:.3f}s")
47
46
 
48
47
  start_time = time.time()
@@ -113,10 +112,6 @@ try:
113
112
  logger.debug(
114
113
  f"[Import] util.text_log (GameLine, get_text_event, get_mined_line, get_all_lines, game_log): {time.time() - start_time:.3f}s")
115
114
 
116
- start_time = time.time()
117
- from GameSentenceMiner.util import *
118
- logger.debug(f"[Import] util *: {time.time() - start_time:.3f}s")
119
-
120
115
  start_time = time.time()
121
116
  from GameSentenceMiner.web import texthooking_page
122
117
  logger.debug(
@@ -675,6 +670,7 @@ def initialize_async():
675
670
 
676
671
 
677
672
  def handle_websocket_message(message: Message):
673
+ logger.info(f"WebSocket Message Received: {message.to_json()}")
678
674
  try:
679
675
  match FunctionName(message.function):
680
676
  case FunctionName.QUIT:
@@ -683,7 +679,7 @@ def handle_websocket_message(message: Message):
683
679
  case FunctionName.QUIT_OBS:
684
680
  close_obs()
685
681
  case FunctionName.START_OBS:
686
- obs.start_obs()
682
+ obs.start_obs(force_restart=not gsm_status.obs_connected)
687
683
  case FunctionName.OPEN_SETTINGS:
688
684
  open_settings()
689
685
  case FunctionName.OPEN_TEXTHOOKER:
@@ -165,28 +165,36 @@ class OBSConnectionManager(threading.Thread):
165
165
  self.counter = 0
166
166
  return
167
167
  start_replay_buffer()
168
-
168
+
169
169
  def get_obs_path():
170
170
  return os.path.join(configuration.get_app_directory(), 'obs-studio/bin/64bit/obs64.exe')
171
171
 
172
172
  def is_process_running(pid):
173
173
  try:
174
174
  process = psutil.Process(pid)
175
- return 'obs' in process.exe()
175
+ return 'obs' in process.exe().lower()
176
176
  except (psutil.NoSuchProcess, psutil.AccessDenied, OSError):
177
177
  if os.path.exists(OBS_PID_FILE):
178
178
  os.remove(OBS_PID_FILE)
179
179
  return False
180
180
 
181
- def start_obs():
181
+ def start_obs(force_restart=False):
182
182
  global obs_process_pid
183
183
  if os.path.exists(OBS_PID_FILE):
184
184
  with open(OBS_PID_FILE, "r") as f:
185
185
  try:
186
186
  obs_process_pid = int(f.read().strip())
187
187
  if is_process_running(obs_process_pid):
188
- print(f"OBS is already running with PID: {obs_process_pid}")
189
- return obs_process_pid
188
+ if force_restart:
189
+ try:
190
+ process = psutil.Process(obs_process_pid)
191
+ process.terminate()
192
+ process.wait(timeout=10)
193
+ print("OBS process terminated for restart.")
194
+ except Exception as e:
195
+ print(f"Error terminating OBS process: {e}")
196
+ else:
197
+ return obs_process_pid
190
198
  except ValueError:
191
199
  print("Invalid PID found in file. Launching new OBS instance.")
192
200
  except OSError:
@@ -693,5 +701,7 @@ def create_scene():
693
701
  if __name__ == '__main__':
694
702
  logging.basicConfig(level=logging.INFO)
695
703
  connect_to_obs_sync()
696
- # set_fit_to_screen_for_scene_items(get_current_scene())
697
- create_scene()
704
+ img = get_screenshot_PIL(source_name='Display Capture 2', compression=100, img_format='jpg', width=2560, height=1440)
705
+ img.show()
706
+ # # set_fit_to_screen_for_scene_items(get_current_scene())
707
+ # create_scene()
@@ -19,13 +19,15 @@ from PIL import Image
19
19
  from rapidfuzz import fuzz
20
20
 
21
21
  from GameSentenceMiner import obs
22
- from GameSentenceMiner.util.electron_config import *
23
22
  from GameSentenceMiner.ocr.ss_picker import ScreenCropper
24
23
  from GameSentenceMiner.owocr.owocr.run import TextFiltering
25
24
  from GameSentenceMiner.util.configuration import get_config, get_app_directory, get_temporary_directory
26
25
  from GameSentenceMiner.ocr.gsm_ocr_config import OCRConfig, has_config_changed, set_dpi_awareness, get_window, get_ocr_config_path
27
- from GameSentenceMiner.owocr.owocr import screen_coordinate_picker, run
28
- from GameSentenceMiner.util.gsm_utils import sanitize_filename, do_text_replacements, OCR_REPLACEMENTS_FILE
26
+ from GameSentenceMiner.owocr.owocr import run
27
+ from GameSentenceMiner.util.electron_config import get_ocr_ocr2, get_ocr_send_to_clipboard, get_ocr_scan_rate, \
28
+ has_ocr_config_changed, reload_electron_config, get_ocr_two_pass_ocr, get_ocr_optimize_second_scan, \
29
+ get_ocr_language, get_ocr_manual_ocr_hotkey
30
+ from GameSentenceMiner.util.gsm_utils import sanitize_filename
29
31
  import threading
30
32
  import time
31
33
 
@@ -173,7 +175,7 @@ class WebsocketServerThread(threading.Thread):
173
175
  self._stop_event = stop_event = asyncio.Event()
174
176
  self._event.set()
175
177
  self.server = start_server = websockets.serve(self.server_handler,
176
- "0.0.0.0",
178
+ get_config().advanced.localhost_bind_address,
177
179
  get_config().advanced.ocr_websocket_port,
178
180
  max_size=1000000000)
179
181
  async with start_server:
@@ -420,7 +422,7 @@ def get_ocr2_image(crop_coords, og_image: Image.Image, ocr2_engine=None):
420
422
  Logic is unchanged, but code is refactored for clarity and maintainability.
421
423
  """
422
424
  def return_original_image():
423
- logger.info("Returning original image for OCR2 (no cropping or optimization).")
425
+ logger.debug("Returning original image for OCR2 (no cropping or optimization).")
424
426
  if not crop_coords or not get_ocr_optimize_second_scan():
425
427
  return og_image
426
428
  x1, y1, x2, y2 = crop_coords
@@ -546,9 +548,10 @@ def add_ss_hotkey(ss_hotkey="ctrl+shift+g"):
546
548
  ocr_config = get_ocr_config()
547
549
  img = obs.get_screenshot_PIL(compression=80, img_format="jpg")
548
550
  ocr_config.scale_to_custom_size(img.width, img.height)
549
- img = run.apply_ocr_config_to_image(img, ocr_config, is_secondary=True)
550
- do_second_ocr("", datetime.now(), img, TextFiltering(lang=get_ocr_language()), ignore_furigana_filter=True, ignore_previous_result=True)
551
-
551
+ for rectangle in [rectangle for rectangle in ocr_config.rectangles if rectangle.is_secondary]:
552
+ new_img = run.apply_ocr_config_to_image(img, ocr_config, is_secondary=True, rectangles=[rectangle])
553
+ do_second_ocr("", datetime.now(), new_img, TextFiltering(lang=get_ocr_language()), ignore_furigana_filter=True, ignore_previous_result=True)
554
+
552
555
  filtering = TextFiltering(lang=get_ocr_language())
553
556
  cropper = ScreenCropper()
554
557
  def capture():
@@ -1,6 +1,11 @@
1
+ import os
2
+ import sys
3
+
1
4
  from GameSentenceMiner.ocr.gsm_ocr_config import set_dpi_awareness, get_scene_ocr_config
2
- from GameSentenceMiner.util.electron_config import * # noqa: F403
3
5
  from GameSentenceMiner.util.gsm_utils import do_text_replacements, OCR_REPLACEMENTS_FILE
6
+ from GameSentenceMiner.util.electron_config import get_ocr_language, get_ocr_requires_open_window, \
7
+ has_ocr_config_changed, reload_electron_config, get_ocr_scan_rate, get_ocr_two_pass_ocr, get_ocr_keep_newline, \
8
+ get_ocr_ocr1, get_furigana_filter_sensitivity
4
9
 
5
10
  try:
6
11
  import win32gui
@@ -52,7 +57,7 @@ import psutil
52
57
  from .ocr import * # noqa: F403
53
58
  from .config import Config
54
59
  from .screen_coordinate_picker import get_screen_selection
55
- from GameSentenceMiner.util.configuration import get_temporary_directory
60
+ from GameSentenceMiner.util.configuration import get_config, get_temporary_directory
56
61
 
57
62
  from skimage.metrics import structural_similarity as ssim
58
63
  from typing import Union
@@ -286,7 +291,7 @@ class WebsocketServerThread(threading.Thread):
286
291
  self._stop_event = stop_event = asyncio.Event()
287
292
  self._event.set()
288
293
  self.server = start_server = websockets.serve(
289
- self.server_handler, '0.0.0.0', config.get_general('websocket_port'), max_size=1000000000)
294
+ self.server_handler, get_config().advanced.localhost_bind_address, config.get_general('websocket_port'), max_size=1000000000)
290
295
  async with start_server:
291
296
  await stop_event.wait()
292
297
  asyncio.run(main())
@@ -1135,14 +1140,15 @@ def scale_down_width_height(width, height):
1135
1140
  return width, height
1136
1141
 
1137
1142
 
1138
- def apply_ocr_config_to_image(img, ocr_config, is_secondary=False):
1143
+ def apply_ocr_config_to_image(img, ocr_config, is_secondary=False, rectangles=None):
1139
1144
  for rectangle in ocr_config.rectangles:
1140
1145
  if rectangle.is_excluded:
1141
1146
  left, top, width, height = rectangle.coordinates
1142
1147
  draw = ImageDraw.Draw(img)
1143
1148
  draw.rectangle((left, top, left + width, top + height), fill=(0, 0, 0, 0))
1144
1149
 
1145
- rectangles = [r for r in ocr_config.rectangles if not r.is_excluded and r.is_secondary == is_secondary]
1150
+ if not rectangles:
1151
+ rectangles = [r for r in ocr_config.rectangles if not r.is_excluded and r.is_secondary == is_secondary]
1146
1152
 
1147
1153
  # Sort top to bottom
1148
1154
  if rectangles:
@@ -589,9 +589,10 @@ class Advanced:
589
589
  multi_line_sentence_storage_field: str = ''
590
590
  ocr_websocket_port: int = 9002
591
591
  texthooker_communication_websocket_port: int = 55001
592
- afk_timer_seconds: int = 120
593
- session_gap_seconds: int = 3600
594
- streak_requirement_hours: float = 0.01 # 1 second required per day to keep your streak by default
592
+ afk_timer_seconds: int = 120 # LEGACY, not used anymore
593
+ session_gap_seconds: int = 3600 # LEGACY, not used anymore
594
+ streak_requirement_hours: float = 0.01 # LEGACY, not used anymore
595
+ localhost_bind_address: str = '127.0.0.1' # Default 127.0.0.1 for security, set to 0.0.0.0 to allow external connections
595
596
 
596
597
  def __post_init__(self):
597
598
  if self.plaintext_websocket_port == -1:
@@ -646,6 +647,7 @@ class Overlay:
646
647
  monitor_to_capture: int = 0
647
648
  periodic: bool = False
648
649
  periodic_interval: float = 1.0
650
+ scan_delay: float = 0.25
649
651
 
650
652
  def __post_init__(self):
651
653
  if self.monitor_to_capture == -1:
@@ -1248,5 +1250,5 @@ is_dev = is_running_from_source()
1248
1250
 
1249
1251
  is_beangate = os.path.exists("C:/Users/Beangate")
1250
1252
 
1251
- logger.debug(f"Running in development mode: {is_dev}")
1252
- logger.debug(f"Running on Beangate's PC: {is_beangate}")
1253
+ # logger.debug(f"Running in development mode: {is_dev}")
1254
+ # logger.debug(f"Running on Beangate's PC: {is_beangate}")