GameSentenceMiner 2.17.6__py3-none-any.whl → 2.18.0__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.
Files changed (51) hide show
  1. GameSentenceMiner/ai/ai_prompting.py +51 -51
  2. GameSentenceMiner/anki.py +236 -152
  3. GameSentenceMiner/gametext.py +7 -4
  4. GameSentenceMiner/gsm.py +49 -10
  5. GameSentenceMiner/locales/en_us.json +7 -3
  6. GameSentenceMiner/locales/ja_jp.json +8 -4
  7. GameSentenceMiner/locales/zh_cn.json +8 -4
  8. GameSentenceMiner/obs.py +238 -59
  9. GameSentenceMiner/ocr/owocr_helper.py +1 -1
  10. GameSentenceMiner/tools/ss_selector.py +7 -8
  11. GameSentenceMiner/ui/__init__.py +0 -0
  12. GameSentenceMiner/ui/anki_confirmation.py +187 -0
  13. GameSentenceMiner/{config_gui.py → ui/config_gui.py} +102 -37
  14. GameSentenceMiner/ui/screenshot_selector.py +215 -0
  15. GameSentenceMiner/util/configuration.py +124 -22
  16. GameSentenceMiner/util/db.py +22 -13
  17. GameSentenceMiner/util/downloader/download_tools.py +2 -2
  18. GameSentenceMiner/util/ffmpeg.py +24 -30
  19. GameSentenceMiner/util/get_overlay_coords.py +34 -34
  20. GameSentenceMiner/util/gsm_utils.py +31 -1
  21. GameSentenceMiner/util/text_log.py +11 -9
  22. GameSentenceMiner/vad.py +31 -12
  23. GameSentenceMiner/web/database_api.py +742 -123
  24. GameSentenceMiner/web/static/css/dashboard-shared.css +241 -0
  25. GameSentenceMiner/web/static/css/kanji-grid.css +94 -2
  26. GameSentenceMiner/web/static/css/overview.css +850 -0
  27. GameSentenceMiner/web/static/css/popups-shared.css +126 -0
  28. GameSentenceMiner/web/static/css/shared.css +97 -0
  29. GameSentenceMiner/web/static/css/stats.css +192 -597
  30. GameSentenceMiner/web/static/js/anki_stats.js +6 -4
  31. GameSentenceMiner/web/static/js/database.js +209 -5
  32. GameSentenceMiner/web/static/js/goals.js +610 -0
  33. GameSentenceMiner/web/static/js/kanji-grid.js +267 -4
  34. GameSentenceMiner/web/static/js/overview.js +1176 -0
  35. GameSentenceMiner/web/static/js/shared.js +25 -0
  36. GameSentenceMiner/web/static/js/stats.js +154 -1459
  37. GameSentenceMiner/web/stats.py +2 -2
  38. GameSentenceMiner/web/templates/anki_stats.html +5 -0
  39. GameSentenceMiner/web/templates/components/navigation.html +3 -1
  40. GameSentenceMiner/web/templates/database.html +73 -1
  41. GameSentenceMiner/web/templates/goals.html +376 -0
  42. GameSentenceMiner/web/templates/index.html +13 -11
  43. GameSentenceMiner/web/templates/overview.html +416 -0
  44. GameSentenceMiner/web/templates/stats.html +46 -251
  45. GameSentenceMiner/web/texthooking_page.py +18 -0
  46. {gamesentenceminer-2.17.6.dist-info → gamesentenceminer-2.18.0.dist-info}/METADATA +5 -1
  47. {gamesentenceminer-2.17.6.dist-info → gamesentenceminer-2.18.0.dist-info}/RECORD +51 -41
  48. {gamesentenceminer-2.17.6.dist-info → gamesentenceminer-2.18.0.dist-info}/WHEEL +0 -0
  49. {gamesentenceminer-2.17.6.dist-info → gamesentenceminer-2.18.0.dist-info}/entry_points.txt +0 -0
  50. {gamesentenceminer-2.17.6.dist-info → gamesentenceminer-2.18.0.dist-info}/licenses/LICENSE +0 -0
  51. {gamesentenceminer-2.17.6.dist-info → gamesentenceminer-2.18.0.dist-info}/top_level.txt +0 -0
@@ -9,10 +9,11 @@ import websockets
9
9
  from websockets import InvalidStatus
10
10
  from rapidfuzz import fuzz
11
11
 
12
- from GameSentenceMiner.util.configuration import get_config, gsm_status, logger, gsm_state
12
+ from GameSentenceMiner.util.configuration import get_config, gsm_status, logger, gsm_state, is_dev
13
13
  from GameSentenceMiner.util.db import GameLinesTable
14
14
  from GameSentenceMiner.util.gsm_utils import do_text_replacements, TEXT_REPLACEMENTS_FILE, run_new_thread
15
15
  from GameSentenceMiner import obs
16
+ from GameSentenceMiner.util.gsm_utils import add_srt_line
16
17
  from GameSentenceMiner.util.text_log import add_line, get_text_log
17
18
  from GameSentenceMiner.web.texthooking_page import add_event_to_texthooker, overlay_server_thread
18
19
 
@@ -87,7 +88,8 @@ async def listen_websockets():
87
88
  message_received_time = datetime.now()
88
89
  if not message:
89
90
  continue
90
- logger.debug(message)
91
+ if is_dev:
92
+ logger.debug(message)
91
93
  try:
92
94
  data = json.loads(message)
93
95
  if "sentence" in data:
@@ -183,6 +185,7 @@ async def handle_new_text_event(current_clipboard, line_time=None):
183
185
 
184
186
  async def add_line_to_text_log(line, line_time=None):
185
187
  global overlay_processor
188
+
186
189
  if get_config().general.texthook_replacement_regex:
187
190
  current_line_after_regex = re.sub(get_config().general.texthook_replacement_regex, '', line)
188
191
  else:
@@ -191,7 +194,7 @@ async def add_line_to_text_log(line, line_time=None):
191
194
  logger.info(f"Line Received: {current_line_after_regex}")
192
195
  current_line_time = line_time if line_time else datetime.now()
193
196
  gsm_status.last_line_received = current_line_time.strftime("%Y-%m-%d %H:%M:%S")
194
- add_line(current_line_after_regex, line_time if line_time else datetime.now())
197
+ new_line = add_line(current_line_after_regex, line_time if line_time else datetime.now())
195
198
  if len(get_text_log().values) > 0:
196
199
  await add_event_to_texthooker(get_text_log()[-1])
197
200
  if get_config().overlay.websocket_port and overlay_server_thread.has_clients():
@@ -199,9 +202,9 @@ async def add_line_to_text_log(line, line_time=None):
199
202
  overlay_processor = OverlayProcessor()
200
203
  if overlay_processor.ready:
201
204
  await overlay_processor.find_box_and_send_to_overlay(current_line_after_regex)
205
+ add_srt_line(line_time, new_line)
202
206
  GameLinesTable.add_line(get_text_log()[-1])
203
207
 
204
-
205
208
  def reset_line_hotkey_pressed():
206
209
  global current_line_time
207
210
  logger.info("LINE RESET HOTKEY PRESSED")
GameSentenceMiner/gsm.py CHANGED
@@ -1,3 +1,7 @@
1
+ # There should be no imports here, as any error will crash the program.
2
+ # All imports should be done in the try/except block below.
3
+
4
+
1
5
  def handle_error_in_initialization(e):
2
6
  """Handle errors that occur during initialization."""
3
7
  logger.exception(e, exc_info=True)
@@ -24,6 +28,7 @@ try:
24
28
  import requests
25
29
  import os.path
26
30
  import signal
31
+ import datetime
27
32
  from subprocess import Popen
28
33
  os.environ.pop('TCL_LIBRARY', None)
29
34
 
@@ -36,13 +41,17 @@ try:
36
41
  import psutil
37
42
 
38
43
  start_time = time.time()
44
+ import GameSentenceMiner.util.configuration
39
45
  from GameSentenceMiner.util.configuration import logger, gsm_state, get_config, anki_results, AnkiUpdateResult, \
40
46
  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
47
+ is_windows, is_linux, get_ffmpeg_path
44
48
 
45
49
  logger.debug(f"[Import] configuration: {time.time() - start_time:.3f}s")
50
+
51
+ start_time = time.time()
52
+ from GameSentenceMiner.util.get_overlay_coords import OverlayThread
53
+ from GameSentenceMiner.util.gsm_utils import remove_html_and_cloze_tags, add_srt_line
54
+ logger.debug(f"[Import] get_overlay_coords (OverlayThread, remove_html_and_cloze_tags): {time.time() - start_time:.3f}s")
46
55
 
47
56
  start_time = time.time()
48
57
  from GameSentenceMiner.util.model import VADResult
@@ -72,7 +81,7 @@ try:
72
81
  logger.debug(f"[Import] anki: {time.time() - start_time:.3f}s")
73
82
 
74
83
  start_time = time.time()
75
- from GameSentenceMiner import config_gui
84
+ from GameSentenceMiner.ui import config_gui
76
85
  logger.debug(f"[Import] config_gui: {time.time() - start_time:.3f}s")
77
86
 
78
87
  start_time = time.time()
@@ -98,7 +107,7 @@ try:
98
107
  f"[Import] websocket (connect_websocket, register_websocket_message_handler, FunctionName): {time.time() - start_time:.3f}s")
99
108
 
100
109
  start_time = time.time()
101
- from GameSentenceMiner.util.ffmpeg import get_audio_and_trim, get_video_timings, get_ffmpeg_path
110
+ from GameSentenceMiner.util.ffmpeg import get_audio_and_trim, get_video_timings
102
111
  logger.debug(
103
112
  f"[Import] util.ffmpeg (get_audio_and_trim, get_video_timings, get_ffmpeg_path): {time.time() - start_time:.3f}s")
104
113
 
@@ -145,15 +154,28 @@ class VideoToAudioHandler(FileSystemEventHandler):
145
154
  super().__init__()
146
155
 
147
156
  def on_created(self, event):
148
- if event.is_directory or ("Replay" not in event.src_path and "GSM" not in event.src_path):
157
+ file_name = os.path.basename(event.src_path)
158
+ if event.is_directory or ("Replay" not in file_name and "GSM" not in file_name):
159
+ # This shows up as soon as recording starts, so it's kinda hard to use...
160
+ # if get_config().features.generate_longplay and event.src_path.endswith(".mkv") or event.src_path.endswith(".mp4"):
161
+ # add_srt_line(datetime.datetime.now(), get_all_lines()[-1])
162
+ # logger.info(f"Recording {event.src_path} FOUND, RUNNING LOGIC")
163
+ # wait_for_stable_file(event.src_path)
164
+ # current_srt = gsm_state.current_srt
165
+ # srt_name = os.path.splitext(os.path.basename(event.src_path))[0] + ".srt"
166
+ # srt_path = os.path.join(os.path.dirname(event.src_path), srt_name)
167
+ # shutil.move(current_srt, srt_path)
168
+ # gsm_state.current_srt = None
169
+ # # self.process_replay(event.src_path)
149
170
  return
150
171
  # Adjust based on your OBS output format
151
- if event.src_path.endswith(".mkv") or event.src_path.endswith(".mp4"):
172
+ if file_name.endswith(".mkv") or file_name.endswith(".mp4"):
152
173
  logger.info(f"MKV {event.src_path} FOUND, RUNNING LOGIC")
153
174
  wait_for_stable_file(event.src_path)
154
175
  self.process_replay(event.src_path)
155
176
 
156
177
  def process_replay(self, video_path):
178
+ gsm_state.current_replay = video_path
157
179
  vad_trimmed_audio = ''
158
180
  final_audio_output = ''
159
181
  skip_delete = False
@@ -194,6 +216,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
194
216
  # Get Info of line mined
195
217
  line_cutoff = None
196
218
  start_line = None
219
+ full_text = ''
197
220
  if selected_lines:
198
221
  start_line = selected_lines[0]
199
222
  mined_line = get_mined_line(last_note, selected_lines)
@@ -300,7 +323,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
300
323
  f"{obs.get_current_game(sanitize=True)}.{get_config().audio.extension}"))
301
324
 
302
325
  vad_result = vad_processor.trim_audio_with_vad(
303
- trimmed_audio, vad_trimmed_audio, game_line)
326
+ trimmed_audio, vad_trimmed_audio, game_line, full_text)
304
327
  if timing_only:
305
328
  return vad_result
306
329
 
@@ -337,7 +360,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
337
360
 
338
361
  def initial_checks():
339
362
  try:
340
- subprocess.run(ffmpeg.ffmpeg_base_command_list)
363
+ subprocess.run(GameSentenceMiner.util.configuration.ffmpeg_base_command_list)
341
364
  logger.debug("FFMPEG is installed and accessible.")
342
365
  except FileNotFoundError:
343
366
  logger.error(
@@ -465,6 +488,13 @@ class GSMTray(threading.Thread):
465
488
  self.run_tray()
466
489
 
467
490
  def run_tray(self):
491
+ def run_anki_confirmation_window():
492
+ settings_window.show_anki_confirmation_dialog(expression="こんにちは",
493
+ sentence="こんにちは、世界!元気ですか?",
494
+ screenshot_path="test_image.png",
495
+ audio_path="C:/path/to/my/audio.mp3",
496
+ translation="Hello world! How are you?")
497
+
468
498
  self.profile_menu = Menu(
469
499
  *[MenuItem(("Active: " if profile == get_master_config().current_profile else "") + profile, self.switch_profile) for
470
500
  profile in
@@ -478,9 +508,10 @@ class GSMTray(threading.Thread):
478
508
  MenuItem("Toggle Replay Buffer", self.play_pause),
479
509
  MenuItem("Restart OBS", restart_obs),
480
510
  MenuItem("Switch Profile", self.profile_menu),
511
+ MenuItem("Run Test Code", run_anki_confirmation_window),
481
512
  MenuItem("Exit", exit_program)
482
513
  )
483
-
514
+
484
515
  self.icon = Icon("TrayApp", create_image(), "GameSentenceMiner", menu)
485
516
  self.icon.run()
486
517
 
@@ -574,6 +605,8 @@ def restart_obs():
574
605
 
575
606
  def cleanup():
576
607
  try:
608
+ if gsm_state.current_srt and len(get_all_lines()) > 0:
609
+ add_srt_line(datetime.datetime.now(), get_all_lines()[-1])
577
610
  logger.info("Performing cleanup...")
578
611
  gsm_state.keep_running = False
579
612
 
@@ -794,6 +827,7 @@ async def async_main(reloading=False):
794
827
  root = ttk.Window(themename='darkly')
795
828
  start_time = time.time()
796
829
  settings_window = config_gui.ConfigApp(root)
830
+ gsm_state.config_app = settings_window
797
831
  initialize_async()
798
832
  observer = Observer()
799
833
  observer.schedule(VideoToAudioHandler(),
@@ -823,6 +857,11 @@ async def async_main(reloading=False):
823
857
  if get_config().general.open_config_on_startup:
824
858
  root.after(50, settings_window.show)
825
859
  root.after(50, gsm_tray.start)
860
+ # root.after(100, settings_window.show_anki_confirmation_dialog(expression="こんにちは",
861
+ # sentence="こんにちは、世界!元気ですか?",
862
+ # screenshot_path="test_image.png",
863
+ # audio_path="C:/path/to/my/audio.mp3",
864
+ # translation="Hello world! How are you?"))
826
865
  settings_window.add_save_hook(gsm_tray.update_icon)
827
866
  settings_window.on_exit = exit_program
828
867
  root.mainloop()
@@ -154,6 +154,10 @@
154
154
  "label": "Update Anki:",
155
155
  "tooltip": "Automatically update Anki with new data."
156
156
  },
157
+ "show_update_confirmation_dialog": {
158
+ "label": "Show Update Confirmation Dialog:",
159
+ "tooltip": "Show a confirmation dialog before updating Anki cards."
160
+ },
157
161
  "url": {
158
162
  "label": "Anki URL:",
159
163
  "tooltip": "The URL to connect to your Anki instance."
@@ -440,9 +444,9 @@
440
444
  "label": "Minimum Replay Size (KB):",
441
445
  "tooltip": "Minimum Replay Size for OBS Replays in KB. If Replay is Under this, Audio/Screenshot Will not be grabbed."
442
446
  },
443
- "turn_off_output_check": {
444
- "label": "Turn Off Output Check:",
445
- "tooltip": "Disable the video output Replay Buffer Check."
447
+ "turn_off_replay_buffer_management": {
448
+ "label": "Turn off Replay Buffer Management:",
449
+ "tooltip": "Disable OBS Replay Buffer Management. This is useful if you want to manually manage the replay buffer in OBS."
446
450
  }
447
451
  },
448
452
  "profiles": {
@@ -153,6 +153,10 @@
153
153
  "label": "Ankiを更新:",
154
154
  "tooltip": "自動的にAnkiを更新します。"
155
155
  },
156
+ "show_update_confirmation_dialog": {
157
+ "label": "更新確認ダイアログを表示:",
158
+ "tooltip": "Ankiカードを更新する前に確認ダイアログを表示します。"
159
+ },
156
160
  "url": {
157
161
  "label": "Anki URL:",
158
162
  "tooltip": "AnkiConnectの接続URL。"
@@ -439,10 +443,10 @@
439
443
  "label": "最小リプレイサイズ (KB):",
440
444
  "tooltip": "このサイズ未満のリプレイは処理しません。"
441
445
  },
442
- "turn_off_output_check": {
443
- "label": "出力チェックを無効化:",
444
- "tooltip": "動画出力のリプレイバッファチェックを無効化します。"
445
- }
446
+ "turn_off_replay_buffer_management": {
447
+ "label": "リプレイバッファ管理を無効化:",
448
+ "tooltip": "OBSリプレイバッファの管理を無効化します。OBSでリプレイバッファを手動で管理したい場合に便利です。"
449
+ }
446
450
  },
447
451
  "profiles": {
448
452
  "title": "プロファイル",
@@ -154,6 +154,10 @@
154
154
  "label": "更新 Anki:",
155
155
  "tooltip": "使用新数据自动更新 Anki。"
156
156
  },
157
+ "show_update_confirmation_dialog": {
158
+ "label": "显示更新确认对话框:",
159
+ "tooltip": "在更新 Anki 卡片之前显示确认对话框。"
160
+ },
157
161
  "url": {
158
162
  "label": "Anki URL:",
159
163
  "tooltip": "连接到您的 Anki 实例的 URL。"
@@ -440,10 +444,10 @@
440
444
  "label": "最小回放大小 (KB):",
441
445
  "tooltip": "OBS 录像回放的最小大小(KB)。如果回放小于此大小,将不抓取音频/截图。"
442
446
  },
443
- "turn_off_output_check": {
444
- "label": "关闭输出检查:",
445
- "tooltip": "禁用视频输出的回放缓冲区检查。"
446
- }
447
+ "turn_off_replay_buffer_management": {
448
+ "label": "关闭回放缓冲区管理:",
449
+ "tooltip": "禁用 OBS 回放缓冲区管理。如果您想在 OBS 中手动管理回放缓冲区,这将很有用。"
450
+ }
447
451
  },
448
452
  "profiles": {
449
453
  "title": "配置文件",