GameSentenceMiner 2.8.35__tar.gz → 2.8.37__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.
Files changed (67) hide show
  1. gamesentenceminer-2.8.37/GameSentenceMiner/communication/send.py +7 -0
  2. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/config_gui.py +59 -8
  3. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/configuration.py +3 -4
  4. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/gametext.py +13 -9
  5. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/gsm.py +55 -28
  6. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/model.py +18 -0
  7. gamesentenceminer-2.8.37/GameSentenceMiner/obs.py +405 -0
  8. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/ocr/gsm_ocr_config.py +9 -1
  9. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/ocr/owocr_area_selector.py +3 -2
  10. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/ocr/owocr_helper.py +28 -10
  11. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/owocr/owocr/run.py +28 -4
  12. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner.egg-info/PKG-INFO +1 -1
  13. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/PKG-INFO +1 -1
  14. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/pyproject.toml +1 -1
  15. gamesentenceminer-2.8.35/GameSentenceMiner/communication/send.py +0 -7
  16. gamesentenceminer-2.8.35/GameSentenceMiner/obs.py +0 -290
  17. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/__init__.py +0 -0
  18. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/ai/__init__.py +0 -0
  19. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/ai/ai_prompting.py +0 -0
  20. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/anki.py +0 -0
  21. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/communication/__init__.py +0 -0
  22. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/communication/websocket.py +0 -0
  23. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/downloader/Untitled_json.py +0 -0
  24. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/downloader/__init__.py +0 -0
  25. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/downloader/download_tools.py +0 -0
  26. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/downloader/oneocr_dl.py +0 -0
  27. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/electron_config.py +0 -0
  28. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/ffmpeg.py +0 -0
  29. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/notification.py +0 -0
  30. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/ocr/__init__.py +0 -0
  31. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/ocr/ocrconfig.py +0 -0
  32. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/owocr/owocr/__init__.py +0 -0
  33. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/owocr/owocr/__main__.py +0 -0
  34. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/owocr/owocr/config.py +0 -0
  35. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -0
  36. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/owocr/owocr/ocr.py +0 -0
  37. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -0
  38. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/package.py +0 -0
  39. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/text_log.py +0 -0
  40. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/util.py +0 -0
  41. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/vad/__init__.py +0 -0
  42. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/vad/result.py +0 -0
  43. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/vad/silero_trim.py +0 -0
  44. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/vad/vosk_helper.py +0 -0
  45. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/vad/whisper_helper.py +0 -0
  46. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/web/__init__.py +0 -0
  47. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/web/static/__init__.py +0 -0
  48. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
  49. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/web/static/favicon-96x96.png +0 -0
  50. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/web/static/favicon.ico +0 -0
  51. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/web/static/favicon.svg +0 -0
  52. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/web/static/site.webmanifest +0 -0
  53. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/web/static/style.css +0 -0
  54. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
  55. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
  56. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/web/templates/__init__.py +0 -0
  57. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/web/templates/text_replacements.html +0 -0
  58. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/web/templates/utility.html +0 -0
  59. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner/web/texthooking_page.py +0 -0
  60. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner.egg-info/SOURCES.txt +0 -0
  61. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner.egg-info/dependency_links.txt +0 -0
  62. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner.egg-info/entry_points.txt +0 -0
  63. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner.egg-info/requires.txt +0 -0
  64. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/GameSentenceMiner.egg-info/top_level.txt +0 -0
  65. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/LICENSE +0 -0
  66. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/README.md +0 -0
  67. {gamesentenceminer-2.8.35 → gamesentenceminer-2.8.37}/setup.cfg +0 -0
@@ -0,0 +1,7 @@
1
+ import json
2
+
3
+ from GameSentenceMiner.communication.websocket import websocket, Message
4
+
5
+ async def send_restart_signal():
6
+ if websocket:
7
+ await websocket.send(json.dumps(Message(function="restart").to_json()))
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import tkinter as tk
2
3
  from tkinter import filedialog, messagebox, simpledialog, scrolledtext
3
4
 
@@ -7,6 +8,7 @@ from GameSentenceMiner import obs, configuration
7
8
  from GameSentenceMiner.communication.send import send_restart_signal
8
9
  from GameSentenceMiner.configuration import *
9
10
  from GameSentenceMiner.downloader.download_tools import download_ocenaudio_if_needed
11
+ from GameSentenceMiner.model import SceneItem, SceneInfo
10
12
  from GameSentenceMiner.package import get_current_version, get_latest_version
11
13
 
12
14
  settings_saved = False
@@ -79,10 +81,27 @@ class ConfigApp:
79
81
  self.create_advanced_tab()
80
82
  self.create_ai_tab()
81
83
 
84
+ self.notebook.bind("<<NotebookTabChanged>>", self.on_profiles_tab_selected)
85
+
82
86
  ttk.Button(self.window, text="Save Settings", command=self.save_settings).pack(pady=20)
83
87
 
84
88
  self.window.withdraw()
85
89
 
90
+ def show_scene_selection(self, matched_configs):
91
+ selected_scene = None
92
+ if matched_configs:
93
+ selection_window = tk.Toplevel(self.window)
94
+ selection_window.title("Select Profile")
95
+ ttk.Label(selection_window, text="Multiple profiles match the current scene. Please select the profile:").pack(pady=10)
96
+ profile_var = tk.StringVar(value=matched_configs[0])
97
+ profile_dropdown = ttk.Combobox(selection_window, textvariable=profile_var, values=matched_configs, state="readonly")
98
+ profile_dropdown.pack(pady=5)
99
+ ttk.Button(selection_window, text="OK", command=lambda: [selection_window.destroy(), setattr(self, 'selected_scene', profile_var.get())]).pack(pady=10)
100
+ self.window.wait_window(selection_window)
101
+ selected_scene = self.selected_scene
102
+ return selected_scene
103
+
104
+
86
105
  def add_save_hook(self, func):
87
106
  on_save.append(func)
88
107
 
@@ -104,6 +123,7 @@ class ConfigApp:
104
123
 
105
124
  # Create a new Config instance
106
125
  config = ProfileConfig(
126
+ scenes=[self.obs_scene_listbox.get(i) for i in self.obs_scene_listbox.curselection()],
107
127
  general=General(
108
128
  use_websocket=self.websocket_enabled.get(),
109
129
  use_clipboard=self.clipboard_enabled.get(),
@@ -234,12 +254,15 @@ class ConfigApp:
234
254
 
235
255
  current_profile = self.profile_combobox.get()
236
256
  prev_config = self.master_config.get_config()
257
+ self.master_config.switch_to_default_if_not_found=self.switch_to_default_if_not_found.get()
237
258
  if profile_change:
238
259
  self.master_config.current_profile = current_profile
239
260
  else:
240
261
  self.master_config.current_profile = current_profile
241
262
  self.master_config.set_config_for_profile(current_profile, config)
242
263
 
264
+
265
+
243
266
  self.master_config = self.master_config.sync_shared_fields()
244
267
 
245
268
  # Serialize the config instance to JSON
@@ -250,7 +273,7 @@ class ConfigApp:
250
273
 
251
274
  if self.master_config.get_config().restart_required(prev_config):
252
275
  logger.info("Restart Required for some settings to take affect!")
253
- send_restart_signal()
276
+ asyncio.run(send_restart_signal())
254
277
 
255
278
  settings_saved = True
256
279
  configuration.reload_config()
@@ -684,6 +707,13 @@ class ConfigApp:
684
707
  # ve.grid_configure(row=ve.grid_info()['row'] - 1)
685
708
  # db.grid_configure(row=db.grid_info()['row'] - 1)
686
709
 
710
+ def on_profiles_tab_selected(self, event):
711
+ try:
712
+ if self.window.state() != "withdrawn" and self.notebook.tab(self.notebook.select(), "text") == "Profiles":
713
+ self.refresh_obs_scenes()
714
+ except Exception as e:
715
+ logger.debug(e)
716
+
687
717
  @new_tab
688
718
  def create_features_tab(self):
689
719
  features_frame = ttk.Frame(self.notebook)
@@ -967,16 +997,11 @@ class ConfigApp:
967
997
  self.take_screenshot_hotkey.grid(row=self.current_row, column=1)
968
998
  self.add_label_and_increment_row(hotkeys_frame, "Hotkey to take a screenshot.", row=self.current_row, column=2)
969
999
 
970
- ttk.Label(hotkeys_frame, text="Open Utility Hotkey:").grid(row=self.current_row, column=0, sticky='W')
971
- self.open_utility_hotkey = ttk.Entry(hotkeys_frame)
972
- self.open_utility_hotkey.insert(0, self.settings.hotkeys.open_utility)
973
- self.open_utility_hotkey.grid(row=self.current_row, column=1)
974
- self.add_label_and_increment_row(hotkeys_frame, "Hotkey to open the text utility.", row=self.current_row, column=2)
975
-
976
1000
 
977
1001
  @new_tab
978
1002
  def create_profiles_tab(self):
979
1003
  profiles_frame = ttk.Frame(self.notebook)
1004
+
980
1005
  self.notebook.add(profiles_frame, text='Profiles')
981
1006
 
982
1007
  ttk.Label(profiles_frame, text="Select Profile:").grid(row=self.current_row, column=0, sticky='W')
@@ -990,6 +1015,32 @@ class ConfigApp:
990
1015
  ttk.Button(profiles_frame, text="Copy Profile", command=self.copy_profile).grid(row=self.current_row, column=1, pady=5)
991
1016
  if self.master_config.current_profile != DEFAULT_CONFIG:
992
1017
  ttk.Button(profiles_frame, text="Delete Config", command=self.delete_profile).grid(row=self.current_row, column=2, pady=5)
1018
+ self.current_row += 1
1019
+
1020
+ ttk.Label(profiles_frame, text="OBS Scene:").grid(row=self.current_row, column=0, sticky='W')
1021
+ self.obs_scene_var = tk.StringVar(value="")
1022
+ self.obs_scene_listbox = tk.Listbox(profiles_frame, listvariable=self.obs_scene_var, selectmode=tk.MULTIPLE, height=10)
1023
+ self.obs_scene_listbox.grid(row=self.current_row, column=1)
1024
+ ttk.Button(profiles_frame, text="Refresh Scenes", command=self.refresh_obs_scenes).grid(row=self.current_row, column=2, pady=5)
1025
+ self.add_label_and_increment_row(profiles_frame, "Select an OBS scene to associate with this profile. (Optional)", row=self.current_row, column=3)
1026
+
1027
+ ttk.Label(profiles_frame, text="Switch To Default If Not Found:").grid(row=self.current_row, column=0, sticky='W')
1028
+ self.switch_to_default_if_not_found = tk.BooleanVar(value=self.master_config.switch_to_default_if_not_found)
1029
+ ttk.Checkbutton(profiles_frame, variable=self.switch_to_default_if_not_found).grid(row=self.current_row, column=1, sticky='W')
1030
+ self.add_label_and_increment_row(profiles_frame, "Enable to switch to the default profile if the selected OBS scene is not found.", row=self.current_row, column=2)
1031
+
1032
+
1033
+ def refresh_obs_scenes(self):
1034
+ scenes = obs.get_obs_scenes()
1035
+ obs_scene_names = [scene['sceneName'] for scene in scenes]
1036
+ self.obs_scene_listbox.delete(0, tk.END) # Clear existing items
1037
+ for scene_name in obs_scene_names:
1038
+ self.obs_scene_listbox.insert(tk.END, scene_name) # Add each scene to the Listbox
1039
+ for i, scene in enumerate(eval(self.obs_scene_var.get())): # Parse the string as a tuple
1040
+ if scene.strip() in self.settings.scenes: # Use strip() to remove extra spaces
1041
+ self.obs_scene_listbox.select_set(i) # Select the item in the Listbox
1042
+ self.obs_scene_listbox.activate(i)
1043
+ self.obs_scene_listbox.update_idletasks() # Ensure the GUI reflects the changes
993
1044
 
994
1045
  @new_tab
995
1046
  def create_advanced_tab(self):
@@ -1013,7 +1064,6 @@ class ConfigApp:
1013
1064
  ttk.Button(advanced_frame, text="Browse", command=lambda: self.browse_file(self.video_player_path)).grid(row=self.current_row, column=2)
1014
1065
  self.add_label_and_increment_row(advanced_frame, "Path to the video player executable. Will seek to the location of the line in the replay", row=self.current_row, column=3)
1015
1066
 
1016
-
1017
1067
  ttk.Label(advanced_frame, text="Play Latest Video/Audio Hotkey:").grid(row=self.current_row, column=0, sticky='W')
1018
1068
  self.play_latest_audio_hotkey = ttk.Entry(advanced_frame)
1019
1069
  self.play_latest_audio_hotkey.insert(0, self.settings.hotkeys.play_latest_audio)
@@ -1132,6 +1182,7 @@ class ConfigApp:
1132
1182
  print("profile Changed!")
1133
1183
  self.save_settings(profile_change=True)
1134
1184
  self.reload_settings()
1185
+ self.refresh_obs_scenes()
1135
1186
 
1136
1187
  def add_profile(self):
1137
1188
  new_profile_name = simpledialog.askstring("Input", "Enter new profile name:")
@@ -222,6 +222,7 @@ class Ai:
222
222
  @dataclass
223
223
  class ProfileConfig:
224
224
  name: str = 'Default'
225
+ scenes: List[str] = field(default_factory=list)
225
226
  general: General = field(default_factory=General)
226
227
  paths: Paths = field(default_factory=Paths)
227
228
  anki: Anki = field(default_factory=Anki)
@@ -304,10 +305,7 @@ class ProfileConfig:
304
305
 
305
306
  def restart_required(self, previous):
306
307
  previous: ProfileConfig
307
- if any([previous.general.use_websocket != self.general.use_websocket,
308
- previous.general.use_clipboard != self.general.use_clipboard,
309
- previous.general.websocket_uri != self.general.websocket_uri,
310
- previous.paths.folder_to_watch != self.paths.folder_to_watch,
308
+ if any([previous.paths.folder_to_watch != self.paths.folder_to_watch,
311
309
  previous.obs.open_obs != self.obs.open_obs,
312
310
  previous.obs.host != self.obs.host,
313
311
  previous.obs.port != self.obs.port
@@ -325,6 +323,7 @@ class ProfileConfig:
325
323
  class Config:
326
324
  configs: Dict[str, ProfileConfig] = field(default_factory=dict)
327
325
  current_profile: str = DEFAULT_CONFIG
326
+ switch_to_default_if_not_found: bool = True
328
327
 
329
328
  @classmethod
330
329
  def new(cls):
@@ -28,6 +28,9 @@ async def monitor_clipboard():
28
28
 
29
29
  skip_next_clipboard = False
30
30
  while True:
31
+ if not get_config().general.use_clipboard:
32
+ await asyncio.sleep(1)
33
+ continue
31
34
  if not get_config().general.use_both_clipboard_and_websocket and websocket_connected:
32
35
  await asyncio.sleep(1)
33
36
  skip_next_clipboard = True
@@ -44,8 +47,11 @@ async def monitor_clipboard():
44
47
  async def listen_websocket():
45
48
  global current_line, current_line_time, reconnecting, websocket_connected
46
49
  try_other = False
47
- websocket_url = f'ws://{get_config().general.websocket_uri}/gsm'
48
50
  while True:
51
+ if not get_config().general.use_websocket:
52
+ await asyncio.sleep(1)
53
+ continue
54
+ websocket_url = f'ws://{get_config().general.websocket_uri}/gsm'
49
55
  if try_other:
50
56
  websocket_url = f'ws://{get_config().general.websocket_uri}/api/ws/text/origin'
51
57
  try:
@@ -114,13 +120,11 @@ def run_websocket_listener():
114
120
 
115
121
 
116
122
  async def start_text_monitor():
123
+ util.run_new_thread(run_websocket_listener)
117
124
  if get_config().general.use_websocket:
118
- util.run_new_thread(run_websocket_listener)
119
- if get_config().general.use_clipboard:
120
- if get_config().general.use_websocket:
121
- if get_config().general.use_both_clipboard_and_websocket:
122
- logger.info("Listening for Text on both WebSocket and Clipboard.")
123
- else:
124
- logger.info("Both WebSocket and Clipboard monitoring are enabled. WebSocket will take precedence if connected.")
125
- await monitor_clipboard()
125
+ if get_config().general.use_both_clipboard_and_websocket:
126
+ logger.info("Listening for Text on both WebSocket and Clipboard.")
127
+ else:
128
+ logger.info("Both WebSocket and Clipboard monitoring are enabled. WebSocket will take precedence if connected.")
129
+ await monitor_clipboard()
126
130
  await asyncio.sleep(1)
@@ -67,6 +67,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
67
67
 
68
68
  @staticmethod
69
69
  def convert_to_audio(video_path):
70
+ vad_trimmed_audio = ''
70
71
  try:
71
72
  if texthooking_page.event_manager.line_for_audio:
72
73
  line: GameLine = texthooking_page.event_manager.line_for_audio
@@ -174,9 +175,9 @@ class VideoToAudioHandler(FileSystemEventHandler):
174
175
  logger.debug(f"Some error was hit catching to allow further work to be done: {e}", exc_info=True)
175
176
  notification.send_error_no_anki_update()
176
177
  finally:
177
- if get_config().paths.remove_video and os.path.exists(video_path):
178
+ if video_path and get_config().paths.remove_video and os.path.exists(video_path):
178
179
  os.remove(video_path) # Optionally remove the video after conversion
179
- if get_config().paths.remove_audio and os.path.exists(vad_trimmed_audio):
180
+ if vad_trimmed_audio and get_config().paths.remove_audio and os.path.exists(vad_trimmed_audio):
180
181
  os.remove(vad_trimmed_audio) # Optionally remove the screenshot after conversion
181
182
 
182
183
 
@@ -289,8 +290,6 @@ def register_hotkeys():
289
290
  keyboard.add_hotkey(get_config().hotkeys.take_screenshot, get_screenshot)
290
291
  if get_config().hotkeys.play_latest_audio:
291
292
  keyboard.add_hotkey(get_config().hotkeys.play_latest_audio, play_most_recent_audio)
292
- if get_config().hotkeys.open_utility:
293
- keyboard.add_hotkey(get_config().hotkeys.open_utility, texthooking_page.open_texthooker)
294
293
 
295
294
 
296
295
  def get_screenshot():
@@ -386,7 +385,7 @@ def open_multimine(icon, item):
386
385
  texthooking_page.open_texthooker()
387
386
 
388
387
 
389
- def update_icon():
388
+ def update_icon(profile=None):
390
389
  global menu, icon
391
390
  # Recreate the menu with the updated button text
392
391
  profile_menu = Menu(
@@ -483,7 +482,6 @@ def restart_obs():
483
482
  close_obs()
484
483
  time.sleep(1)
485
484
  obs.start_obs()
486
- obs.connect_to_obs()
487
485
 
488
486
 
489
487
  def cleanup():
@@ -558,25 +556,6 @@ def initialize_async():
558
556
  threads.append(util.run_new_thread(task))
559
557
  return threads
560
558
 
561
-
562
- def post_init():
563
- def do_post_init():
564
- global silero_trim, whisper_helper, vosk_helper
565
- logger.info("Post-Initialization started.")
566
- if get_config().obs.enabled:
567
- obs.connect_to_obs()
568
- check_obs_folder_is_correct()
569
- from GameSentenceMiner.vad import vosk_helper
570
- from GameSentenceMiner.vad import whisper_helper
571
- if get_config().vad.is_vosk():
572
- vosk_helper.get_vosk_model()
573
- if get_config().vad.is_whisper():
574
- whisper_helper.initialize_whisper_model()
575
- if get_config().vad.is_silero():
576
- from GameSentenceMiner.vad import silero_trim
577
-
578
- util.run_new_thread(do_post_init)
579
-
580
559
  def handle_websocket_message(message: Message):
581
560
  match FunctionName(message.function):
582
561
  case FunctionName.QUIT:
@@ -586,19 +565,67 @@ def handle_websocket_message(message: Message):
586
565
  close_obs()
587
566
  case FunctionName.START_OBS:
588
567
  obs.start_obs()
589
- obs.connect_to_obs()
590
568
  case _:
591
569
  logger.debug(f"unknown message from electron websocket: {message.to_json()}")
592
570
 
593
571
  def post_init2():
594
572
  asyncio.run(gametext.start_text_monitor())
595
573
 
574
+
575
+ def async_loop():
576
+ async def loop():
577
+ await obs.connect_to_obs()
578
+ if get_config().obs.enabled:
579
+ await register_scene_switcher_callback()
580
+ await check_obs_folder_is_correct()
581
+ logger.info("Post-Initialization started.")
582
+ if get_config().vad.is_vosk():
583
+ from GameSentenceMiner.vad import vosk_helper
584
+ vosk_helper.get_vosk_model()
585
+ if get_config().vad.is_whisper():
586
+ from GameSentenceMiner.vad import whisper_helper
587
+ whisper_helper.initialize_whisper_model()
588
+ if get_config().vad.is_silero():
589
+ from GameSentenceMiner.vad import silero_trim
590
+
591
+ asyncio.run(loop())
592
+
593
+
594
+ async def register_scene_switcher_callback():
595
+ def scene_switcher_callback(scene):
596
+ logger.info(f"Scene changed to: {scene}")
597
+ all_configured_scenes = [config.scenes for config in get_master_config().configs.values()]
598
+ print(all_configured_scenes)
599
+ matching_configs = [name.strip() for name, config in config_instance.configs.items() if scene.strip() in config.scenes]
600
+ switch_to = None
601
+
602
+ if len(matching_configs) > 1:
603
+ selected_scene = settings_window.show_scene_selection(matched_configs=matching_configs)
604
+ if selected_scene:
605
+ switch_to = selected_scene
606
+ else:
607
+ return
608
+ elif matching_configs:
609
+ switch_to = matching_configs[0]
610
+ elif get_master_config().switch_to_default_if_not_found:
611
+ switch_to = configuration.DEFAULT_CONFIG
612
+
613
+ if switch_to and switch_to != get_master_config().current_profile:
614
+ logger.info(f"Switching to profile: {switch_to}")
615
+ get_master_config().current_profile = switch_to
616
+ switch_profile_and_save(switch_to)
617
+ settings_window.reload_settings()
618
+ update_icon()
619
+
620
+ logger.info("Registering scene switcher callback")
621
+ await obs.register_scene_change_callback(scene_switcher_callback)
622
+
596
623
  async def main(reloading=False):
597
624
  global root, settings_window
625
+ initialize(reloading)
598
626
  logger.info("Script started.")
599
627
  root = ttk.Window(themename='darkly')
600
628
  settings_window = config_gui.ConfigApp(root)
601
- initialize(reloading)
602
629
  initialize_async()
603
630
  observer = Observer()
604
631
  observer.schedule(VideoToAudioHandler(), get_config().paths.folder_to_watch, recursive=False)
@@ -608,6 +635,7 @@ async def main(reloading=False):
608
635
 
609
636
  util.run_new_thread(post_init2)
610
637
  util.run_new_thread(run_text_hooker_page)
638
+ util.run_new_thread(async_loop)
611
639
 
612
640
  # Register signal handlers for graceful shutdown
613
641
  signal.signal(signal.SIGTERM, handle_exit()) # Handle `kill` commands
@@ -619,7 +647,6 @@ async def main(reloading=False):
619
647
  try:
620
648
  # if get_config().general.open_config_on_startup:
621
649
  # root.after(0, settings_window.show)
622
- root.after(50, post_init)
623
650
  settings_window.add_save_hook(update_icon)
624
651
  settings_window.on_exit = exit_program
625
652
  root.mainloop()
@@ -83,6 +83,24 @@ class SceneItemsResponse:
83
83
  class RecordDirectory:
84
84
  recordDirectory: str
85
85
 
86
+
87
+ @dataclass_json
88
+ @dataclass
89
+ class SceneItemInfo:
90
+ sceneIndex: int
91
+ sceneName: str
92
+ sceneUuid: str
93
+
94
+
95
+ @dataclass_json
96
+ @dataclass
97
+ class SceneListResponse:
98
+ scenes: List[SceneItemInfo]
99
+ currentProgramSceneName: Optional[str] = None
100
+ currentProgramSceneUuid: Optional[str] = None
101
+ currentPreviewSceneName: Optional[str] = None
102
+ currentPreviewSceneUuid: Optional[str] = None
103
+
86
104
  #
87
105
  # @dataclass_json
88
106
  # @dataclass