GameSentenceMiner 2.5.11__py3-none-any.whl → 2.5.13__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.
GameSentenceMiner/anki.py CHANGED
@@ -1,22 +1,22 @@
1
+ import time
2
+
1
3
  import base64
2
4
  import subprocess
3
5
  import threading
4
- import time
5
6
  import urllib.request
6
7
  from datetime import datetime, timedelta
8
+ from requests import post
7
9
 
8
- import requests as req
9
-
10
- from GameSentenceMiner import obs, util, notification, ffmpeg, gametext
11
-
10
+ from GameSentenceMiner import obs, util, notification, ffmpeg
12
11
  from GameSentenceMiner.configuration import *
13
12
  from GameSentenceMiner.configuration import get_config
14
13
  from GameSentenceMiner.gametext import get_text_event
15
14
  from GameSentenceMiner.model import AnkiCard
16
- from GameSentenceMiner.utility_gui import utility_window, get_utility_window
15
+ from GameSentenceMiner.utility_gui import get_utility_window
17
16
  from GameSentenceMiner.obs import get_current_game
18
17
  from GameSentenceMiner.util import remove_html_and_cloze_tags, combine_dialogue
19
18
 
19
+
20
20
  audio_in_anki = None
21
21
  screenshot_in_anki = None
22
22
  prev_screenshot_in_anki = None
@@ -32,8 +32,9 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
32
32
  global audio_in_anki, screenshot_in_anki, prev_screenshot_in_anki
33
33
  update_audio = should_update_audio and (get_config().anki.sentence_audio_field and not
34
34
  last_note.get_field(get_config().anki.sentence_audio_field) or get_config().anki.overwrite_audio)
35
- update_picture = (get_config().anki.picture_field and get_config().anki.overwrite_picture) or not \
36
- last_note.get_field(get_config().anki.picture_field)
35
+ update_picture = (get_config().anki.picture_field and get_config().screenshot.enabled
36
+ and (get_config().anki.overwrite_picture or not last_note.get_field(get_config().anki.picture_field)))
37
+
37
38
 
38
39
  if not reuse_audio:
39
40
  if update_audio:
@@ -54,11 +55,13 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
54
55
 
55
56
  # note = {'id': last_note.noteId, 'fields': {}}
56
57
 
57
- if update_audio:
58
+ if update_audio and audio_in_anki:
58
59
  note['fields'][get_config().anki.sentence_audio_field] = audio_html
59
60
 
60
- if update_picture:
61
+ if update_picture and screenshot_in_anki:
61
62
  note['fields'][get_config().anki.picture_field] = image_html
63
+ if not get_config().screenshot.enabled:
64
+ logger.info("Skipping Adding Screenshot to Anki, Screenshot is disabled in settings")
62
65
 
63
66
  if prev_screenshot_in_anki:
64
67
  note['fields'][get_config().anki.previous_image_field] = prev_screenshot_html
@@ -82,7 +85,7 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
82
85
  if get_config().features.open_anki_edit:
83
86
  notification.open_anki_card(last_note.noteId)
84
87
 
85
- if get_config().audio.external_tool:
88
+ if get_config().audio.external_tool and get_config().audio.external_tool_enabled and update_audio:
86
89
  open_audio_in_external(f"{get_config().audio.anki_media_collection}/{audio_in_anki}")
87
90
 
88
91
 
@@ -309,7 +312,7 @@ def monitor_anki():
309
312
 
310
313
  # Fetch recent note IDs from Anki
311
314
  def get_note_ids():
312
- response = req.post(get_config().anki.url, json={
315
+ response = post(get_config().anki.url, json={
313
316
  "action": "findNotes",
314
317
  "version": 6,
315
318
  "params": {"query": "added:1"}
@@ -147,6 +147,7 @@ class ConfigApp:
147
147
  backfill_audio=self.backfill_audio.get()
148
148
  ),
149
149
  screenshot=Screenshot(
150
+ enabled=self.screenshot_enabled.get(),
150
151
  width=self.screenshot_width.get(),
151
152
  height=self.screenshot_height.get(),
152
153
  quality=self.screenshot_quality.get(),
@@ -157,12 +158,14 @@ class ConfigApp:
157
158
  use_beginning_of_line_as_screenshot=self.use_beginning_of_line_as_screenshot.get()
158
159
  ),
159
160
  audio=Audio(
161
+ enabled=self.audio_enabled.get(),
160
162
  extension=self.audio_extension.get(),
161
163
  beginning_offset=float(self.beginning_offset.get()),
162
164
  end_offset=float(self.end_offset.get()),
163
165
  ffmpeg_reencode_options=self.ffmpeg_reencode_options.get(),
164
166
  external_tool = self.external_tool.get(),
165
167
  anki_media_collection=self.anki_media_collection.get(),
168
+ external_tool_enabled=self.external_tool_enabled.get(),
166
169
  ),
167
170
  obs=OBS(
168
171
  enabled=self.obs_enabled.get(),
@@ -171,7 +174,6 @@ class ConfigApp:
171
174
  host=self.obs_host.get(),
172
175
  port=int(self.obs_port.get()),
173
176
  password=self.obs_password.get(),
174
- start_buffer=self.obs_start_buffer.get(),
175
177
  get_game_from_scene=self.get_game_from_scene_name.get(),
176
178
  minimum_replay_size=int(self.minimum_replay_size.get())
177
179
  ),
@@ -679,6 +681,11 @@ class ConfigApp:
679
681
  screenshot_frame = ttk.Frame(self.notebook)
680
682
  self.notebook.add(screenshot_frame, text='Screenshot')
681
683
 
684
+ ttk.Label(screenshot_frame, text="Enabled:").grid(row=self.current_row, column=0, sticky='W')
685
+ self.screenshot_enabled = tk.BooleanVar(value=self.settings.screenshot.enabled)
686
+ ttk.Checkbutton(screenshot_frame, variable=self.screenshot_enabled).grid(row=self.current_row, column=1, sticky='W')
687
+ self.add_label_and_increment_row(screenshot_frame, "Enable or disable screenshot processing.", row=self.current_row, column=2)
688
+
682
689
  ttk.Label(screenshot_frame, text="Width:").grid(row=self.current_row, column=0, sticky='W')
683
690
  self.screenshot_width = ttk.Entry(screenshot_frame)
684
691
  self.screenshot_width.insert(0, str(self.settings.screenshot.width))
@@ -737,6 +744,11 @@ class ConfigApp:
737
744
  audio_frame = ttk.Frame(self.notebook)
738
745
  self.notebook.add(audio_frame, text='Audio')
739
746
 
747
+ ttk.Label(audio_frame, text="Enabled:").grid(row=self.current_row, column=0, sticky='W')
748
+ self.audio_enabled = tk.BooleanVar(value=self.settings.audio.enabled)
749
+ ttk.Checkbutton(audio_frame, variable=self.audio_enabled).grid(row=self.current_row, column=1, sticky='W')
750
+ self.add_label_and_increment_row(audio_frame, "Enable or disable audio processing.", row=self.current_row, column=2)
751
+
740
752
  ttk.Label(audio_frame, text="Audio Extension:").grid(row=self.current_row, column=0, sticky='W')
741
753
  self.audio_extension = ttk.Combobox(audio_frame, values=['opus', 'mp3', 'ogg', 'aac', 'm4a'])
742
754
  self.audio_extension.insert(0, self.settings.audio.extension)
@@ -777,10 +789,13 @@ class ConfigApp:
777
789
  self.external_tool = ttk.Entry(audio_frame)
778
790
  self.external_tool.insert(0, self.settings.audio.external_tool)
779
791
  self.external_tool.grid(row=self.current_row, column=1)
792
+ ttk.Label(audio_frame, text="Enabled:").grid(row=self.current_row, column=2, sticky='W')
793
+ self.external_tool_enabled = tk.BooleanVar(value=self.settings.audio.external_tool_enabled)
794
+ ttk.Checkbutton(audio_frame, variable=self.external_tool_enabled).grid(row=self.current_row, column=3, sticky='W')
780
795
  self.add_label_and_increment_row(audio_frame,
781
796
  "Path to External tool that opens the audio up for manual trimming. I recommend OcenAudio for in-place Editing.",
782
797
  row=self.current_row,
783
- column=2)
798
+ column=4)
784
799
 
785
800
  ttk.Button(audio_frame, text="Install Ocenaudio", command=self.download_and_install_ocen).grid(
786
801
  row=self.current_row, column=0, pady=5)
@@ -835,12 +850,6 @@ class ConfigApp:
835
850
  self.add_label_and_increment_row(obs_frame, "Password for the OBS WebSocket server.", row=self.current_row,
836
851
  column=2)
837
852
 
838
- ttk.Label(obs_frame, text="Start/Stop Buffer:").grid(row=self.current_row, column=0, sticky='W')
839
- self.obs_start_buffer = tk.BooleanVar(value=self.settings.obs.start_buffer)
840
- ttk.Checkbutton(obs_frame, variable=self.obs_start_buffer).grid(row=self.current_row, column=1, sticky='W')
841
- self.add_label_and_increment_row(obs_frame, "Start and Stop the Buffer when Script runs.", row=self.current_row,
842
- column=2)
843
-
844
853
  ttk.Label(obs_frame, text="Get Game From Scene Name:").grid(row=self.current_row, column=0, sticky='W')
845
854
  self.get_game_from_scene_name = tk.BooleanVar(value=self.settings.obs.get_game_from_scene)
846
855
  ttk.Checkbutton(obs_frame, variable=self.get_game_from_scene_name).grid(row=self.current_row, column=1,
@@ -98,6 +98,7 @@ class Features:
98
98
  @dataclass_json
99
99
  @dataclass
100
100
  class Screenshot:
101
+ enabled: bool = True
101
102
  width: str = 0
102
103
  height: str = 0
103
104
  quality: str = 85
@@ -111,12 +112,14 @@ class Screenshot:
111
112
  @dataclass_json
112
113
  @dataclass
113
114
  class Audio:
115
+ enabled: bool = True
114
116
  extension: str = 'opus'
115
117
  beginning_offset: float = 0.0
116
118
  end_offset: float = 0.5
117
119
  ffmpeg_reencode_options: str = ''
118
120
  external_tool: str = ""
119
121
  anki_media_collection: str = ""
122
+ external_tool_enabled: bool = True
120
123
 
121
124
 
122
125
  @dataclass_json
@@ -128,7 +131,6 @@ class OBS:
128
131
  host: str = "localhost"
129
132
  port: int = 4455
130
133
  password: str = "your_password"
131
- start_buffer: bool = True
132
134
  get_game_from_scene: bool = True
133
135
  minimum_replay_size: int = 0
134
136
 
GameSentenceMiner/gsm.py CHANGED
@@ -1,6 +1,6 @@
1
+
1
2
  import os.path
2
3
  import signal
3
- import time
4
4
  from subprocess import Popen
5
5
 
6
6
  import keyboard
@@ -11,6 +11,8 @@ from pystray import Icon, Menu, MenuItem
11
11
  from watchdog.events import FileSystemEventHandler
12
12
  from watchdog.observers import Observer
13
13
 
14
+ import time
15
+
14
16
  from GameSentenceMiner import anki
15
17
  from GameSentenceMiner import config_gui
16
18
  from GameSentenceMiner import configuration
@@ -29,11 +31,11 @@ from GameSentenceMiner.gametext import get_text_event, get_mined_line, GameLine
29
31
  from GameSentenceMiner.obs import check_obs_folder_is_correct
30
32
  from GameSentenceMiner.util import *
31
33
  from GameSentenceMiner.utility_gui import init_utility_window, get_utility_window
32
- from GameSentenceMiner.vad import silero_trim, whisper_helper, vosk_helper
33
34
 
34
35
  if is_windows():
35
36
  import win32api
36
37
 
38
+ silero_trim, whisper_helper, vosk_helper = None, None, None
37
39
  procs_to_close = []
38
40
  settings_window: config_gui.ConfigApp = None
39
41
  obs_paused = False
@@ -42,34 +44,16 @@ menu: Menu
42
44
  root = None
43
45
 
44
46
 
47
+
45
48
  class VideoToAudioHandler(FileSystemEventHandler):
46
49
  def on_created(self, event):
47
50
  if event.is_directory or ("Replay" not in event.src_path and "GSM" not in event.src_path):
48
51
  return
49
52
  if event.src_path.endswith(".mkv") or event.src_path.endswith(".mp4"): # Adjust based on your OBS output format
50
53
  logger.info(f"MKV {event.src_path} FOUND, RUNNING LOGIC")
51
- self.wait_for_stable_file(event.src_path)
54
+ wait_for_stable_file(event.src_path)
52
55
  self.convert_to_audio(event.src_path)
53
56
 
54
- @staticmethod
55
- def wait_for_stable_file(file_path, timeout=10, check_interval=0.5):
56
- elapsed_time = 0
57
- last_size = -1
58
-
59
- while elapsed_time < timeout:
60
- try:
61
- current_size = os.path.getsize(file_path)
62
- if current_size == last_size:
63
- return True
64
- last_size = current_size
65
- time.sleep(check_interval)
66
- elapsed_time += check_interval
67
- except Exception as e:
68
- logger.warning(f"Error checking file size, will still try updating Anki Card!: {e}")
69
- return False
70
- logger.warning("File size did not stabilize within the timeout period. Continuing...")
71
- return False
72
-
73
57
  @staticmethod
74
58
  def convert_to_audio(video_path):
75
59
  try:
@@ -141,7 +125,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
141
125
  tango = last_note.get_field(get_config().anki.word_field) if last_note else ''
142
126
  get_utility_window().reset_checkboxes()
143
127
 
144
- if get_config().anki.sentence_audio_field:
128
+ if get_config().anki.sentence_audio_field and get_config().audio.enabled:
145
129
  logger.debug("Attempting to get audio from video")
146
130
  final_audio_output, should_update_audio, vad_trimmed_audio = VideoToAudioHandler.get_audio(
147
131
  start_line,
@@ -151,7 +135,10 @@ class VideoToAudioHandler(FileSystemEventHandler):
151
135
  final_audio_output = ""
152
136
  should_update_audio = False
153
137
  vad_trimmed_audio = ""
154
- logger.info("No SentenceAudio Field in config, skipping audio processing!")
138
+ if not get_config().audio.enabled:
139
+ logger.info("Audio is disabled in config, skipping audio processing!")
140
+ elif not get_config().anki.sentence_audio_field:
141
+ logger.info("No SentenceAudio Field in config, skipping audio processing!")
155
142
  if get_config().anki.update_anki and last_note:
156
143
  anki.update_anki_card(last_note, note, audio_path=final_audio_output, video_path=video_path,
157
144
  tango=tango,
@@ -183,26 +170,10 @@ class VideoToAudioHandler(FileSystemEventHandler):
183
170
  f"{obs.get_current_game(sanitize=True)}.{get_config().audio.extension}"))
184
171
  should_update_audio = True
185
172
  if get_config().vad.do_vad_postprocessing:
186
- match get_config().vad.selected_vad_model:
187
- case configuration.SILERO:
188
- should_update_audio = silero_trim.process_audio_with_silero(trimmed_audio, vad_trimmed_audio)
189
- case configuration.VOSK:
190
- should_update_audio = vosk_helper.process_audio_with_vosk(trimmed_audio, vad_trimmed_audio)
191
- case configuration.WHISPER:
192
- should_update_audio = whisper_helper.process_audio_with_whisper(trimmed_audio,
193
- vad_trimmed_audio)
173
+ should_update_audio = do_vad_processing(get_config().vad.selected_vad_model, trimmed_audio, vad_trimmed_audio)
194
174
  if not should_update_audio:
195
- match get_config().vad.backup_vad_model:
196
- case configuration.OFF:
197
- pass
198
- case configuration.SILERO:
199
- should_update_audio = silero_trim.process_audio_with_silero(trimmed_audio,
200
- vad_trimmed_audio)
201
- case configuration.VOSK:
202
- should_update_audio = vosk_helper.process_audio_with_vosk(trimmed_audio, vad_trimmed_audio)
203
- case configuration.WHISPER:
204
- should_update_audio = whisper_helper.process_audio_with_whisper(trimmed_audio,
205
- vad_trimmed_audio)
175
+ should_update_audio = do_vad_processing(get_config().vad.selected_vad_model, trimmed_audio,
176
+ vad_trimmed_audio)
206
177
  if not should_update_audio and get_config().vad.add_audio_on_no_results:
207
178
  logger.info("No voice activity detected, using full audio.")
208
179
  vad_trimmed_audio = trimmed_audio
@@ -214,6 +185,22 @@ class VideoToAudioHandler(FileSystemEventHandler):
214
185
  shutil.move(vad_trimmed_audio, final_audio_output)
215
186
  return final_audio_output, should_update_audio, vad_trimmed_audio
216
187
 
188
+
189
+ def do_vad_processing(model, trimmed_audio, vad_trimmed_audio, second_pass=False):
190
+ match model:
191
+ case configuration.OFF:
192
+ pass
193
+ case configuration.SILERO:
194
+ from GameSentenceMiner.vad import silero_trim
195
+ return silero_trim.process_audio_with_silero(trimmed_audio, vad_trimmed_audio)
196
+ case configuration.VOSK:
197
+ from GameSentenceMiner.vad import vosk_helper
198
+ return vosk_helper.process_audio_with_vosk(trimmed_audio, vad_trimmed_audio)
199
+ case configuration.WHISPER:
200
+ from GameSentenceMiner.vad import whisper_helper
201
+ return whisper_helper.process_audio_with_whisper(trimmed_audio, vad_trimmed_audio)
202
+
203
+
217
204
  def play_audio_in_external(filepath):
218
205
  exe = get_config().advanced.audio_player_path
219
206
 
@@ -244,6 +231,8 @@ def play_video_in_external(line, filepath):
244
231
  command.append(convert_to_vlc_seconds(start))
245
232
  command.append(os.path.normpath(filepath))
246
233
 
234
+ logger.info(" ".join(command))
235
+
247
236
  try:
248
237
  proc = subprocess.Popen(command)
249
238
  print(f"Opened {filepath} in {get_config().advanced.video_player_path}.")
@@ -286,7 +275,7 @@ def register_hotkeys():
286
275
  def get_screenshot():
287
276
  try:
288
277
  image = obs.get_screenshot()
289
- time.sleep(2) # Wait for ss to save
278
+ wait_for_stable_file(image, timeout=3)
290
279
  if not image:
291
280
  raise Exception("Failed to get Screenshot from OBS")
292
281
  encoded_image = ffmpeg.process_image(image)
@@ -479,9 +468,8 @@ def cleanup():
479
468
  util.keep_running = False
480
469
 
481
470
  if get_config().obs.enabled:
482
- if get_config().obs.start_buffer:
483
- obs.stop_replay_buffer()
484
- obs.disconnect_from_obs()
471
+ obs.stop_replay_buffer()
472
+ obs.disconnect_from_obs()
485
473
  if get_config().obs.close_obs:
486
474
  close_obs()
487
475
 
@@ -521,9 +509,9 @@ def initialize(reloading=False):
521
509
  if is_windows():
522
510
  download_obs_if_needed()
523
511
  download_ffmpeg_if_needed()
524
- # if get_config().obs.enabled:
525
- # if get_config().obs.open_obs:
526
- # obs_process = obs.start_obs()
512
+ if get_config().obs.enabled:
513
+ if get_config().obs.open_obs:
514
+ obs_process = obs.start_obs()
527
515
  # obs.connect_to_obs(start_replay=True)
528
516
  # anki.start_monitoring_anki()
529
517
  # gametext.start_text_monitor()
@@ -541,27 +529,29 @@ def initialize(reloading=False):
541
529
  def initialize_async():
542
530
  tasks = [gametext.start_text_monitor, connect_websocket, run_tray]
543
531
  threads = []
544
- if get_config().obs.enabled:
545
- if get_config().obs.open_obs:
546
- tasks.append(obs.start_obs)
547
- else:
548
- tasks.append(obs.connect_to_obs)
549
- tasks.append(anki.start_monitoring_anki)
550
- if get_config().vad.do_vad_postprocessing:
551
- if VOSK in (get_config().vad.backup_vad_model, get_config().vad.selected_vad_model):
552
- tasks.append(vosk_helper.get_vosk_model)
553
- if WHISPER in (get_config().vad.backup_vad_model, get_config().vad.selected_vad_model):
554
- tasks.append(whisper_helper.initialize_whisper_model)
532
+ tasks.append(anki.start_monitoring_anki)
555
533
  for task in tasks:
556
534
  threads.append(util.run_new_thread(task))
557
535
  return threads
558
536
 
559
- def check_async_init_done(threads):
560
- while True:
561
- if all(not thread.is_alive() for thread in threads):
562
- break
563
- time.sleep(0.5)
564
- logger.info("Script Fully Initialized. Happy Mining!")
537
+
538
+ def post_init():
539
+ def do_post_init():
540
+ global silero_trim, whisper_helper, vosk_helper
541
+ logger.info("Post-Initialization started.")
542
+ if get_config().obs.enabled:
543
+ obs.connect_to_obs()
544
+ check_obs_folder_is_correct()
545
+ from GameSentenceMiner.vad import vosk_helper
546
+ from GameSentenceMiner.vad import whisper_helper
547
+ if get_config().vad.is_vosk():
548
+ vosk_helper.get_vosk_model()
549
+ if get_config().vad.is_whisper():
550
+ whisper_helper.initialize_whisper_model()
551
+ if get_config().vad.is_silero():
552
+ from GameSentenceMiner.vad import silero_trim
553
+
554
+ util.run_new_thread(do_post_init)
565
555
 
566
556
 
567
557
  def handle_websocket_message(message: Message):
@@ -581,7 +571,6 @@ def main(reloading=False):
581
571
  init_utility_window(root)
582
572
  initialize(reloading)
583
573
  initialize_async()
584
- check_obs_folder_is_correct()
585
574
  observer = Observer()
586
575
  observer.schedule(VideoToAudioHandler(), get_config().paths.folder_to_watch, recursive=False)
587
576
  observer.start()
@@ -599,6 +588,7 @@ def main(reloading=False):
599
588
  root.after(0, settings_window.show)
600
589
  if get_config().general.open_multimine_on_startup:
601
590
  root.after(0, get_utility_window().show)
591
+ root.after(0, post_init)
602
592
  settings_window.add_save_hook(update_icon)
603
593
  settings_window.on_exit = exit_program
604
594
  root.mainloop()
@@ -613,4 +603,5 @@ def main(reloading=False):
613
603
 
614
604
 
615
605
  if __name__ == "__main__":
606
+ logger.info("Starting GSM")
616
607
  main()
GameSentenceMiner/obs.py CHANGED
@@ -28,12 +28,9 @@ def start_obs():
28
28
  return None
29
29
 
30
30
  try:
31
- obs_pid = is_obs_running(obs_path)
32
- if obs_pid:
33
- return obs_pid
34
- obs_process = subprocess.Popen([obs_path, '--disable-shutdown-check', '--portable'], cwd=os.path.dirname(obs_path))
31
+ obs_process = subprocess.Popen([obs_path, '--disable-shutdown-check', '--portable', '--startreplaybuffer'], cwd=os.path.dirname(obs_path))
32
+
35
33
  logger.info("OBS launched")
36
- connect_to_obs()
37
34
  return obs_process.pid
38
35
  except Exception as e:
39
36
  logger.error(f"Error launching OBS: {e}")
@@ -48,15 +45,6 @@ def check_obs_folder_is_correct():
48
45
  get_master_config().sync_shared_fields()
49
46
  save_full_config(get_master_config())
50
47
 
51
- def is_obs_running(obs_path):
52
- obs_path = os.path.abspath(obs_path) # Normalize path
53
- for process in psutil.process_iter(['exe']):
54
- try:
55
- if process.info['exe'] and os.path.abspath(process.info['exe']) == obs_path:
56
- return process.pid
57
- except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
58
- continue
59
- return False
60
48
 
61
49
  def get_obs_websocket_config_values():
62
50
  config_path = os.path.join(get_app_directory(), 'obs-studio', 'config', 'obs-studio', 'plugin_config', 'obs-websocket', 'config.json')
@@ -92,10 +80,8 @@ def get_obs_websocket_config_values():
92
80
 
93
81
 
94
82
  def on_connect(obs):
95
- logger.info("Connected to OBS WebSocket.")
96
- time.sleep(2)
97
- if get_config().obs.start_buffer:
98
- start_replay_buffer()
83
+ logger.info("Reconnected to OBS WebSocket.")
84
+ start_replay_buffer()
99
85
 
100
86
 
101
87
  def on_disconnect(obs):
@@ -111,9 +97,6 @@ def connect_to_obs():
111
97
  password=get_config().obs.password, authreconnect=1, on_connect=on_connect,
112
98
  on_disconnect=on_disconnect)
113
99
  client.connect()
114
-
115
- if get_config().obs.start_buffer:
116
- start_replay_buffer()
117
100
  update_current_game()
118
101
 
119
102
 
@@ -135,6 +118,7 @@ def do_obs_call(request, from_dict = None, retry=10):
135
118
  return from_dict(response.datain)
136
119
  return None
137
120
  except Exception as e:
121
+ logger.error(e)
138
122
  if "socket is already closed" in str(e) or "object has no attribute" in str(e):
139
123
  if retry > 0:
140
124
  time.sleep(1)
GameSentenceMiner/util.py CHANGED
@@ -6,6 +6,7 @@ import string
6
6
  import subprocess
7
7
  import sys
8
8
  import threading
9
+ import time
9
10
  from datetime import datetime
10
11
  from sys import platform
11
12
 
@@ -178,38 +179,31 @@ def combine_dialogue(dialogue_lines, new_lines=None):
178
179
 
179
180
  return new_lines
180
181
 
181
- # def import_vad_models():
182
- # silero_trim, whisper_helper, vosk_helper = None, None, None
183
- #
184
- # def check_and_install(package_name):
185
- # try:
186
- # importlib.import_module(package_name)
187
- # return True
188
- # except ImportError:
189
- # logger.warning(f"{package_name} is not installed. Attempting to install...")
190
- # try:
191
- # python_executable = sys.executable
192
- # subprocess.check_call([python_executable, "-m", "pip", "install", package_name])
193
- # logger.info(f"{package_name} installed successfully.")
194
- # return True
195
- # except subprocess.CalledProcessError as e:
196
- # logger.error(f"Failed to install {package_name}: {e}")
197
- # return False
198
- #
199
- # if get_config().vad.is_silero():
200
- # if check_and_install("silero_vad"):
201
- # from GameSentenceMiner.vad import silero_trim
202
- # else:
203
- # logger.error("Silero VAD is enabled and silero_vad package could not be installed.")
204
- # if get_config().vad.is_whisper():
205
- # if check_and_install("stable-ts"):
206
- # from GameSentenceMiner.vad import whisper_helper
207
- # else:
208
- # logger.error("Whisper is enabled and whisper package could not be installed.")
209
- # if get_config().vad.is_vosk():
210
- # if check_and_install("vosk"):
211
- # from GameSentenceMiner.vad import vosk_helper
212
- # else:
213
- # logger.error("Vosk is enabled and vosk package could not be installed.")
214
- #
215
- # return silero_trim, whisper_helper, vosk_helper
182
+ def wait_for_stable_file(file_path, timeout=10, check_interval=0.1):
183
+ elapsed_time = 0
184
+ last_size = -1
185
+
186
+ while elapsed_time < timeout:
187
+ try:
188
+ current_size = os.path.getsize(file_path)
189
+ if current_size == last_size:
190
+ return True
191
+ last_size = current_size
192
+ time.sleep(check_interval)
193
+ elapsed_time += check_interval
194
+ except Exception as e:
195
+ logger.warning(f"Error checking file size, will still try updating Anki Card!: {e}")
196
+ return False
197
+ logger.warning("File size did not stabilize within the timeout period. Continuing...")
198
+ return False
199
+
200
+
201
+ def import_vad_models():
202
+ silero_trim, whisper_helper, vosk_helper = None, None, None
203
+ if get_config().vad.is_silero():
204
+ from GameSentenceMiner.vad import silero_trim
205
+ if get_config().vad.is_whisper():
206
+ from GameSentenceMiner.vad import whisper_helper
207
+ if get_config().vad.is_vosk():
208
+ from GameSentenceMiner.vad import vosk_helper
209
+ return silero_trim, whisper_helper, vosk_helper
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GameSentenceMiner
3
- Version: 2.5.11
3
+ Version: 2.5.13
4
4
  Summary: A tool for mining sentences from games. Update: Multi-Line Mining! Fixed!
5
5
  Author-email: Beangate <bpwhelan95@gmail.com>
6
6
  License: MIT License
@@ -97,7 +97,7 @@ steps before making an issue:
97
97
 
98
98
  - Try Restarting OBS
99
99
  - Make sure your hook is the best you can find. (Preferably it gives you the text RIGHT when the voice line starts)
100
- - Try Adjusting Offset Configuration in `config.toml` to better match your situation. (i.e. if the hook is late, add a
100
+ - Try Adjusting Offset Configuration in the config to better match your situation. (i.e. if the hook is late, add a
101
101
  negative beginning offset)
102
102
  - Try using "Trim beginning" in `VAD` settings.
103
103
 
@@ -1,15 +1,15 @@
1
1
  GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- GameSentenceMiner/anki.py,sha256=xeWU1jX1ZK6tZVnDPqhBzetkCxdbPUWG1LkUy0sfHVg,13089
3
- GameSentenceMiner/config_gui.py,sha256=v4XRxGqRddqCrdv1UHu4Gnp7aMjL-CCCHBFlTy1Y2v0,61265
4
- GameSentenceMiner/configuration.py,sha256=DXlwEV4A7X3yzVV6UomY2jmmYAZx_jjMRxZeweoCtRE,20149
2
+ GameSentenceMiner/anki.py,sha256=FHm76i7WYEs--szhzZjz0B6DW4JuDQopmjR3RjYBogs,13358
3
+ GameSentenceMiner/config_gui.py,sha256=WVSN239lyVm5GwKzE1HXiCSCu2CNpkcDm8TGKlTJpMc,62093
4
+ GameSentenceMiner/configuration.py,sha256=_lxd1x78NX7tktt6RU7nhO4zkKzSoNKFQ-0r1zwNilU,20208
5
5
  GameSentenceMiner/ffmpeg.py,sha256=fzOxrn-a4KqFdUY2oove164CTDOSsdPQtzqRW5f1P7c,12002
6
6
  GameSentenceMiner/gametext.py,sha256=LORVdE2WEo1CDI8gonc7qxrhbS4KFKXFQVKjhlkpLbc,7368
7
- GameSentenceMiner/gsm.py,sha256=XZLc_HwA8Zzxas0M0oFYbjRwgXa7OLPhT2QXhoiUVzQ,25125
7
+ GameSentenceMiner/gsm.py,sha256=OHOoO0U-YMjLnMvwG9zg63jDTcXLg-rG90Cw27l5yBo,24224
8
8
  GameSentenceMiner/model.py,sha256=JdnkT4VoPOXmOpRgFdvERZ09c9wLN6tUJxdrKlGZcqo,5305
9
9
  GameSentenceMiner/notification.py,sha256=FY39ChSRK0Y8TQ6lBGsLnpZUFPtFpSy2tweeXVoV7kc,2809
10
- GameSentenceMiner/obs.py,sha256=EcgQ0VdeYXGeXCyvk8mBiHBL-HHai037BoCXomH9ge4,8239
10
+ GameSentenceMiner/obs.py,sha256=MlxRToq5wALPI1XrD8rxEU-N8mWII91cNJWY7rUa5uI,7642
11
11
  GameSentenceMiner/package.py,sha256=YlS6QRMuVlm6mdXx0rlXv9_3erTGS21jaP3PNNWfAH0,1250
12
- GameSentenceMiner/util.py,sha256=tkaoU1bj8iPMTNwUCWUzFLAnT44Ot92D1tYwQMEnARw,7336
12
+ GameSentenceMiner/util.py,sha256=Awhy57vX4NgQzygqKaGQn2EJ75T0uiXlhmINFOWlQkU,6825
13
13
  GameSentenceMiner/utility_gui.py,sha256=H4aOddlsrVR768RwbMzYScCziuOz1JeySUigNrPlaac,7692
14
14
  GameSentenceMiner/communication/__init__.py,sha256=_jGn9PJxtOAOPtJ2rI-Qu9hEHVZVpIvWlxKvqk91_zI,638
15
15
  GameSentenceMiner/communication/send.py,sha256=oOJdCS6-LNX90amkRn5FL2xqx6THGm56zHR2ntVIFTE,229
@@ -21,9 +21,9 @@ GameSentenceMiner/vad/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
21
21
  GameSentenceMiner/vad/silero_trim.py,sha256=-thDIZLuTLra3YBj7WR16Z6JeDgSpge2YuahprBvD8I,1585
22
22
  GameSentenceMiner/vad/vosk_helper.py,sha256=BI_mg_qyrjNbuEJjXSUDoV0FWEtQtEOAPmrrNixnZ_8,5974
23
23
  GameSentenceMiner/vad/whisper_helper.py,sha256=OF4J8TPPoKPJR1uFwrWAZ2Q7v0HJkVvNGmF8l1tACX0,3447
24
- gamesentenceminer-2.5.11.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
25
- gamesentenceminer-2.5.11.dist-info/METADATA,sha256=7yFwFF9yNCL_KdU8_aYpg6BeWpiQKBjWD1sm6Scl9UI,5436
26
- gamesentenceminer-2.5.11.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
27
- gamesentenceminer-2.5.11.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
28
- gamesentenceminer-2.5.11.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
29
- gamesentenceminer-2.5.11.dist-info/RECORD,,
24
+ gamesentenceminer-2.5.13.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
25
+ gamesentenceminer-2.5.13.dist-info/METADATA,sha256=WpCjip7e_2FhZIkC9bEgZhknsc9qjoAhZDw5Q8Tq0Uw,5433
26
+ gamesentenceminer-2.5.13.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
27
+ gamesentenceminer-2.5.13.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
28
+ gamesentenceminer-2.5.13.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
29
+ gamesentenceminer-2.5.13.dist-info/RECORD,,