GameSentenceMiner 2.12.12.post1__tar.gz → 2.13.1__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 (83) hide show
  1. gamesentenceminer-2.13.1/GameSentenceMiner/config_gui.py +2116 -0
  2. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/gametext.py +2 -0
  3. gamesentenceminer-2.13.1/GameSentenceMiner/locales/en_us.json +592 -0
  4. gamesentenceminer-2.13.1/GameSentenceMiner/locales/ja_jp.json +591 -0
  5. gamesentenceminer-2.13.1/GameSentenceMiner/locales/zh_cn.json +592 -0
  6. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/configuration.py +41 -1
  7. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/wip/get_overlay_coords.py +1 -1
  8. gamesentenceminer-2.13.1/GameSentenceMiner.egg-info/PKG-INFO +42 -0
  9. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner.egg-info/SOURCES.txt +3 -1
  10. gamesentenceminer-2.13.1/PKG-INFO +42 -0
  11. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/pyproject.toml +2 -2
  12. gamesentenceminer-2.12.12.post1/GameSentenceMiner/config_gui.py +0 -1881
  13. gamesentenceminer-2.12.12.post1/GameSentenceMiner.egg-info/PKG-INFO +0 -157
  14. gamesentenceminer-2.12.12.post1/PKG-INFO +0 -157
  15. gamesentenceminer-2.12.12.post1/README.md +0 -114
  16. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/__init__.py +0 -0
  17. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/ai/__init__.py +0 -0
  18. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/ai/ai_prompting.py +0 -0
  19. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/anki.py +0 -0
  20. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/assets/__init__.py +0 -0
  21. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/assets/icon.png +0 -0
  22. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/assets/icon128.png +0 -0
  23. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/assets/icon256.png +0 -0
  24. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/assets/icon32.png +0 -0
  25. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/assets/icon512.png +0 -0
  26. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/assets/icon64.png +0 -0
  27. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/assets/pickaxe.png +0 -0
  28. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/gsm.py +0 -0
  29. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/obs.py +0 -0
  30. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/ocr/__init__.py +0 -0
  31. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/ocr/gsm_ocr_config.py +0 -0
  32. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/ocr/ocrconfig.py +0 -0
  33. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/ocr/owocr_area_selector.py +0 -0
  34. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/ocr/owocr_helper.py +0 -0
  35. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/ocr/ss_picker.py +0 -0
  36. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/owocr/owocr/__init__.py +0 -0
  37. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/owocr/owocr/__main__.py +0 -0
  38. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/owocr/owocr/config.py +0 -0
  39. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -0
  40. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/owocr/owocr/ocr.py +0 -0
  41. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/owocr/owocr/run.py +0 -0
  42. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -0
  43. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/__init__.py +0 -0
  44. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/audio_offset_selector.py +0 -0
  45. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/communication/__init__.py +0 -0
  46. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/communication/send.py +0 -0
  47. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/communication/websocket.py +0 -0
  48. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/downloader/Untitled_json.py +0 -0
  49. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/downloader/__init__.py +0 -0
  50. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/downloader/download_tools.py +0 -0
  51. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/downloader/oneocr_dl.py +0 -0
  52. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/electron_config.py +0 -0
  53. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/ffmpeg.py +0 -0
  54. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/gsm_utils.py +0 -0
  55. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/model.py +0 -0
  56. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/notification.py +0 -0
  57. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/package.py +0 -0
  58. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/ss_selector.py +0 -0
  59. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/text_log.py +0 -0
  60. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/util/window_transparency.py +0 -0
  61. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/vad.py +0 -0
  62. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/web/__init__.py +0 -0
  63. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/web/service.py +0 -0
  64. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/web/static/__init__.py +0 -0
  65. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
  66. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/web/static/favicon-96x96.png +0 -0
  67. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/web/static/favicon.ico +0 -0
  68. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/web/static/favicon.svg +0 -0
  69. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/web/static/site.webmanifest +0 -0
  70. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/web/static/style.css +0 -0
  71. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
  72. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
  73. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/web/templates/__init__.py +0 -0
  74. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/web/templates/index.html +0 -0
  75. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/web/templates/text_replacements.html +0 -0
  76. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/web/templates/utility.html +0 -0
  77. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner/web/texthooking_page.py +0 -0
  78. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner.egg-info/dependency_links.txt +0 -0
  79. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner.egg-info/entry_points.txt +0 -0
  80. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner.egg-info/requires.txt +0 -0
  81. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/GameSentenceMiner.egg-info/top_level.txt +0 -0
  82. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/LICENSE +0 -0
  83. {gamesentenceminer-2.12.12.post1 → gamesentenceminer-2.13.1}/setup.cfg +0 -0
@@ -0,0 +1,2116 @@
1
+ import asyncio
2
+ import json
3
+ import subprocess
4
+ import time
5
+ import tkinter as tk
6
+ from tkinter import filedialog, messagebox, simpledialog, scrolledtext, font
7
+
8
+ import pyperclip
9
+ import ttkbootstrap as ttk
10
+
11
+ from GameSentenceMiner import obs
12
+ from GameSentenceMiner.util import configuration
13
+ from GameSentenceMiner.util.communication.send import send_restart_signal
14
+ from GameSentenceMiner.util.configuration import *
15
+ from GameSentenceMiner.util.downloader.download_tools import download_ocenaudio_if_needed
16
+ from GameSentenceMiner.util.package import get_current_version, get_latest_version
17
+
18
+ settings_saved = False
19
+ on_save = []
20
+ exit_func = None
21
+
22
+
23
+ # It's assumed that a file named 'en_us.json' exists in the same directory
24
+ # or a path that Python can find.
25
+ def load_localization(locale=Locale.English):
26
+ """Loads the localization file."""
27
+ try:
28
+ # Use a path relative to this script file
29
+ script_dir = os.path.dirname(os.path.abspath(__file__))
30
+ lang_file = os.path.join(script_dir, 'locales', f'{locale.value}.json')
31
+ with open(lang_file, 'r', encoding='utf-8') as f:
32
+ return json.load(f)['python']['config']
33
+ except (FileNotFoundError, json.JSONDecodeError) as e:
34
+ print(f"Warning: Could not load localization file '{locale.value}.json'. Error: {e}. Falling back to empty dict.")
35
+ return {}
36
+
37
+
38
+ def new_tab(func):
39
+ def wrapper(self, *args, **kwargs):
40
+ self.current_row = 0 # Resetting row for the new tab
41
+ # Perform any other pre-initialization tasks here if needed
42
+ return func(self, *args, **kwargs)
43
+
44
+ return wrapper
45
+
46
+
47
+ class HoverInfoWidget:
48
+ def __init__(self, parent, text, row, column, padx=5, pady=2):
49
+ self.info_icon = ttk.Label(parent, text="ⓘ", foreground="blue", cursor="hand2")
50
+ self.info_icon.grid(row=row, column=column, padx=padx, pady=pady)
51
+ self.info_icon.bind("<Enter>", lambda e: self.show_info_box(text))
52
+ self.info_icon.bind("<Leave>", lambda e: self.hide_info_box())
53
+ self.tooltip = None
54
+
55
+ def change_text(self, text):
56
+ """
57
+ Change the text of the info icon.
58
+ """
59
+ if self.tooltip:
60
+ self.tooltip.destroy()
61
+ self.tooltip = None
62
+ self.info_icon.config(text=text)
63
+
64
+ def show_info_box(self, text):
65
+ x, y, _, _ = self.info_icon.bbox("insert")
66
+ x += self.info_icon.winfo_rootx() + 25
67
+ y += self.info_icon.winfo_rooty() + 20
68
+ self.tooltip = tk.Toplevel(self.info_icon)
69
+ self.tooltip.wm_overrideredirect(True)
70
+ self.tooltip.wm_geometry(f"+{x}+{y}")
71
+ label = ttk.Label(self.tooltip, text=text, relief="solid", borderwidth=1,
72
+ font=("tahoma", "12", "normal"))
73
+ label.pack(ipadx=1)
74
+
75
+ def hide_info_box(self):
76
+ if self.tooltip:
77
+ self.tooltip.destroy()
78
+ self.tooltip = None
79
+
80
+
81
+ class HoverInfoLabelWidget:
82
+ def __init__(self, parent, text, tooltip, row, column, padx=5, pady=2, foreground="white", sticky='W',
83
+ bootstyle=None, font=("Arial", 10, "normal"), apply_to_parent=False, columnspan=1):
84
+ self.label_text_value = ttk.StringVar(value=text)
85
+ self.label = ttk.Label(parent, textvariable=self.label_text_value, foreground=foreground, cursor="hand2", bootstyle=bootstyle, font=font)
86
+ self.label.grid(row=row, column=column, padx=(0, padx), pady=0, sticky=sticky, columnspan=columnspan)
87
+ self.label.bind("<Enter>", lambda e: self.show_info_box(tooltip))
88
+ self.label.bind("<Leave>", lambda e: self.hide_info_box())
89
+ self.tooltip = None
90
+
91
+ def show_info_box(self, text):
92
+ x, y, _, _ = self.label.bbox("insert")
93
+ x += self.label.winfo_rootx() + 25
94
+ y += self.label.winfo_rooty() + 20
95
+ self.tooltip = tk.Toplevel(self.label)
96
+ self.tooltip.wm_overrideredirect(True)
97
+ self.tooltip.wm_geometry(f"+{x}+{y}")
98
+ label = ttk.Label(self.tooltip, text=text, relief="solid", borderwidth=1,
99
+ font=("tahoma", "12", "normal"))
100
+ label.pack(ipadx=1)
101
+
102
+ def hide_info_box(self):
103
+ if self.tooltip:
104
+ self.tooltip.destroy()
105
+ self.tooltip = None
106
+
107
+
108
+ class ResetToDefaultButton(ttk.Button):
109
+ def __init__(self, parent, command, text="Reset to Default", tooltip_text="Reset settings", bootstyle="danger", **kwargs):
110
+ super().__init__(parent, text=text, command=command, bootstyle=bootstyle, **kwargs)
111
+ self.tooltip_text = tooltip_text
112
+ self.tooltip = None
113
+ self.bind("<Enter>", self.show_tooltip)
114
+ self.bind("<Leave>", self.hide_tooltip)
115
+
116
+ def show_tooltip(self, event):
117
+ if not self.tooltip:
118
+ x = self.winfo_rootx() + 20
119
+ y = self.winfo_rooty() + 20
120
+ self.tooltip = tk.Toplevel(self)
121
+ self.tooltip.wm_overrideredirect(True)
122
+ self.tooltip.wm_geometry(f"+{x}+{y}")
123
+ label = ttk.Label(self.tooltip, text=self.tooltip_text, relief="solid",
124
+ borderwidth=1,
125
+ font=("tahoma", "12", "normal"))
126
+ label.pack(ipadx=1)
127
+
128
+ def hide_tooltip(self, event):
129
+ if self.tooltip:
130
+ self.tooltip.destroy()
131
+ self.tooltip = None
132
+
133
+
134
+ class ConfigApp:
135
+ def __init__(self, root):
136
+ self.window = root
137
+ self.on_exit = None
138
+ self.window.tk.call('tk', 'scaling', 1.5) # Set DPI scaling factor
139
+ self.window.protocol("WM_DELETE_WINDOW", self.hide)
140
+ self.obs_scene_listbox_changed = False
141
+ self.test_func = None
142
+
143
+ self.current_row = 0
144
+
145
+ self.master_config: Config = configuration.load_config()
146
+ self.i18n = load_localization(self.master_config.locale)
147
+
148
+ self.window.title(self.i18n.get('app', {}).get('title', 'GameSentenceMiner Configuration'))
149
+
150
+ self.settings = self.master_config.get_config()
151
+ self.default_master_settings = Config.new()
152
+ self.default_settings = self.default_master_settings.get_config()
153
+
154
+ self.notebook = ttk.Notebook(self.window)
155
+ self.notebook.pack(pady=10, expand=True, fill='both')
156
+
157
+ self.required_settings_frame = None
158
+ self.starter_tab = None
159
+ self.general_tab = None
160
+ self.paths_tab = None
161
+ self.anki_tab = None
162
+ self.vad_tab = None
163
+ self.features_tab = None
164
+ self.screenshot_tab = None
165
+ self.audio_tab = None
166
+ self.obs_tab = None
167
+ self.profiles_tab = None
168
+ self.ai_tab = None
169
+ self.advanced_tab = None
170
+ self.wip_tab = None
171
+ self.monitors = []
172
+
173
+ try:
174
+ import mss as mss
175
+ self.monitors = [f"Monitor {i}: width: {monitor['width']}, height: {monitor['height']}" for i, monitor in enumerate(mss.mss().monitors[1:], start=1)]
176
+ if len(self.monitors) == 0:
177
+ self.monitors = [1]
178
+ except ImportError:
179
+ self.monitors = []
180
+
181
+ self.create_vars()
182
+ self.create_tabs()
183
+ self.notebook.bind("<<NotebookTabChanged>>", self.on_profiles_tab_selected)
184
+
185
+ button_frame = ttk.Frame(self.window)
186
+ button_frame.pack(side="bottom", pady=20, anchor="center")
187
+
188
+ buttons_i18n = self.i18n.get('buttons', {})
189
+ self.save_button = ttk.Button(button_frame, text=buttons_i18n.get('save', 'Save Settings'), command=self.save_settings, bootstyle="success")
190
+ self.save_button.grid(row=0, column=0, padx=10)
191
+
192
+ if len(self.master_config.configs) > 1:
193
+ self.sync_changes_var = tk.BooleanVar(value=False)
194
+ sync_btn_i18n = buttons_i18n.get('sync_changes', {})
195
+ self.sync_changes_checkbutton = ttk.Checkbutton(
196
+ button_frame,
197
+ text=sync_btn_i18n.get('label', 'Sync Changes to Profiles'),
198
+ variable=self.sync_changes_var,
199
+ bootstyle="info"
200
+ )
201
+ self.sync_changes_checkbutton.grid(row=0, column=1, padx=10)
202
+ self.sync_changes_hover_info = HoverInfoWidget(
203
+ button_frame,
204
+ sync_btn_i18n.get('tooltip', 'Syncs CHANGED SETTINGS to all profiles.'),
205
+ row=0,
206
+ column=2
207
+ )
208
+
209
+ self.window.update_idletasks()
210
+ self.window.geometry("")
211
+ self.window.withdraw()
212
+
213
+ def change_locale(self):
214
+ """Change the locale of the application."""
215
+ if self.locale_value.get() == self.master_config.locale.name:
216
+ return
217
+ self.i18n = load_localization(Locale[self.locale_value.get()])
218
+ self.save_settings()
219
+ self.reload_settings(force_refresh=True)
220
+
221
+ self.window.title(self.i18n.get('app', {}).get('title', 'GameSentenceMiner Configuration'))
222
+ self.save_button.config(text=self.i18n.get('buttons', {}).get('save', 'Save Settings'))
223
+ if hasattr(self, 'sync_changes_checkbutton'):
224
+ self.sync_changes_checkbutton.config(text=self.i18n.get('buttons', {}).get('sync_changes', {}).get('label', 'Sync Changes to Profiles'))
225
+
226
+ logger.info(f"Locale changed to {self.locale_value.get()}.")
227
+
228
+ def set_test_func(self, func):
229
+ self.test_func = func
230
+
231
+ def create_vars(self):
232
+ """
233
+ Initializes all the tkinter variables used in the configuration GUI.
234
+ """
235
+ # General Settings
236
+ self.websocket_enabled_value = tk.BooleanVar(value=self.settings.general.use_websocket)
237
+ self.clipboard_enabled_value = tk.BooleanVar(value=self.settings.general.use_clipboard)
238
+ self.use_both_clipboard_and_websocket_value = tk.BooleanVar(value=self.settings.general.use_both_clipboard_and_websocket)
239
+ self.merge_matching_sequential_text_value = tk.BooleanVar(value=self.settings.general.merge_matching_sequential_text)
240
+ self.websocket_uri_value = tk.StringVar(value=self.settings.general.websocket_uri)
241
+ self.texthook_replacement_regex_value = tk.StringVar(value=self.settings.general.texthook_replacement_regex)
242
+ self.open_config_on_startup_value = tk.BooleanVar(value=self.settings.general.open_config_on_startup)
243
+ self.open_multimine_on_startup_value = tk.BooleanVar(value=self.settings.general.open_multimine_on_startup)
244
+ self.texthooker_port_value = tk.StringVar(value=str(self.settings.general.texthooker_port))
245
+ self.native_language_value = tk.StringVar(value=CommonLanguages.from_code(self.settings.general.native_language).name.replace('_', ' ').title())
246
+
247
+ # OBS Settings
248
+ self.obs_enabled_value = tk.BooleanVar(value=self.settings.obs.enabled)
249
+ self.obs_websocket_port_value = tk.StringVar(value=str(self.settings.obs.port))
250
+ self.obs_host_value = tk.StringVar(value=self.settings.obs.host)
251
+ self.obs_port_value = tk.StringVar(value=str(self.settings.obs.port))
252
+ self.obs_password_value = tk.StringVar(value=self.settings.obs.password)
253
+ self.obs_open_obs_value = tk.BooleanVar(value=self.settings.obs.open_obs)
254
+ self.obs_close_obs_value = tk.BooleanVar(value=self.settings.obs.close_obs)
255
+ self.obs_get_game_from_scene_name_value = tk.BooleanVar(value=self.settings.obs.get_game_from_scene)
256
+ self.obs_minimum_replay_size_value = tk.StringVar(value=str(self.settings.obs.minimum_replay_size))
257
+
258
+ # Paths Settings
259
+ self.folder_to_watch_value = tk.StringVar(value=self.settings.paths.folder_to_watch)
260
+ self.audio_destination_value = tk.StringVar(value=self.settings.paths.audio_destination)
261
+ self.screenshot_destination_value = tk.StringVar(value=self.settings.paths.screenshot_destination)
262
+ self.remove_video_value = tk.BooleanVar(value=self.settings.paths.remove_video)
263
+ self.remove_audio_value = tk.BooleanVar(value=self.settings.paths.remove_audio)
264
+ self.remove_screenshot_value = tk.BooleanVar(value=self.settings.paths.remove_screenshot)
265
+
266
+ # Anki Settings
267
+ self.update_anki_value = tk.BooleanVar(value=self.settings.anki.update_anki)
268
+ self.anki_url_value = tk.StringVar(value=self.settings.anki.url)
269
+ self.sentence_field_value = tk.StringVar(value=self.settings.anki.sentence_field)
270
+ self.sentence_audio_field_value = tk.StringVar(value=self.settings.anki.sentence_audio_field)
271
+ self.picture_field_value = tk.StringVar(value=self.settings.anki.picture_field)
272
+ self.word_field_value = tk.StringVar(value=self.settings.anki.word_field)
273
+ self.previous_sentence_field_value = tk.StringVar(value=self.settings.anki.previous_sentence_field)
274
+ self.previous_image_field_value = tk.StringVar(value=self.settings.anki.previous_image_field)
275
+ self.custom_tags_value = tk.StringVar(value=', '.join(self.settings.anki.custom_tags))
276
+ self.tags_to_check_value = tk.StringVar(value=', '.join(self.settings.anki.tags_to_check))
277
+ self.add_game_tag_value = tk.BooleanVar(value=self.settings.anki.add_game_tag)
278
+ self.polling_rate_value = tk.StringVar(value=str(self.settings.anki.polling_rate))
279
+ self.overwrite_audio_value = tk.BooleanVar(value=self.settings.anki.overwrite_audio)
280
+ self.overwrite_picture_value = tk.BooleanVar(value=self.settings.anki.overwrite_picture)
281
+ self.multi_overwrites_sentence_value = tk.BooleanVar(value=self.settings.anki.multi_overwrites_sentence)
282
+ self.parent_tag_value = tk.StringVar(value=self.settings.anki.parent_tag)
283
+
284
+ # Features Settings
285
+ self.full_auto_value = tk.BooleanVar(value=self.settings.features.full_auto)
286
+ self.notify_on_update_value = tk.BooleanVar(value=self.settings.features.notify_on_update)
287
+ self.open_anki_edit_value = tk.BooleanVar(value=self.settings.features.open_anki_edit)
288
+ self.open_anki_browser_value = tk.BooleanVar(value=self.settings.features.open_anki_in_browser)
289
+ self.backfill_audio_value = tk.BooleanVar(value=self.settings.features.backfill_audio)
290
+ self.browser_query_value = tk.StringVar(value=self.settings.features.browser_query)
291
+
292
+ # Screenshot Settings
293
+ self.screenshot_enabled_value = tk.BooleanVar(value=self.settings.screenshot.enabled)
294
+ self.screenshot_width_value = tk.StringVar(value=str(self.settings.screenshot.width))
295
+ self.screenshot_height_value = tk.StringVar(value=str(self.settings.screenshot.height))
296
+ self.screenshot_quality_value = tk.StringVar(value=str(self.settings.screenshot.quality))
297
+ self.screenshot_extension_value = tk.StringVar(value=self.settings.screenshot.extension)
298
+ self.screenshot_custom_ffmpeg_settings_value = tk.StringVar(value=self.settings.screenshot.custom_ffmpeg_settings)
299
+ self.screenshot_hotkey_update_anki_value = tk.BooleanVar(value=self.settings.screenshot.screenshot_hotkey_updates_anki)
300
+ self.seconds_after_line_value = tk.StringVar(value=str(self.settings.screenshot.seconds_after_line))
301
+ self.screenshot_timing_value = tk.StringVar(value=self.settings.screenshot.screenshot_timing_setting)
302
+ self.use_screenshot_selector_value = tk.BooleanVar(value=self.settings.screenshot.use_screenshot_selector)
303
+
304
+ # Audio Settings
305
+ self.audio_enabled_value = tk.BooleanVar(value=self.settings.audio.enabled)
306
+ self.audio_extension_value = tk.StringVar(value=self.settings.audio.extension)
307
+ self.beginning_offset_value = tk.StringVar(value=str(self.settings.audio.beginning_offset))
308
+ self.end_offset_value = tk.StringVar(value=str(self.settings.audio.end_offset))
309
+ self.audio_ffmpeg_reencode_options_value = tk.StringVar(value=self.settings.audio.ffmpeg_reencode_options)
310
+ self.external_tool_value = tk.StringVar(value=self.settings.audio.external_tool)
311
+ self.anki_media_collection_value = tk.StringVar(value=self.settings.audio.anki_media_collection)
312
+ self.external_tool_enabled_value = tk.BooleanVar(value=self.settings.audio.external_tool_enabled)
313
+ self.pre_vad_audio_offset_value = tk.StringVar(value=str(self.settings.audio.pre_vad_end_offset))
314
+
315
+ # Hotkeys Settings
316
+ self.reset_line_hotkey_value = tk.StringVar(value=self.settings.hotkeys.reset_line)
317
+ self.take_screenshot_hotkey_value = tk.StringVar(value=self.settings.hotkeys.take_screenshot)
318
+ self.play_latest_audio_hotkey_value = tk.StringVar(value=self.settings.hotkeys.play_latest_audio)
319
+
320
+ # VAD Settings
321
+ self.whisper_model_value = tk.StringVar(value=self.settings.vad.whisper_model)
322
+ self.do_vad_postprocessing_value = tk.BooleanVar(value=self.settings.vad.do_vad_postprocessing)
323
+ self.selected_vad_model_value = tk.StringVar(value=self.settings.vad.selected_vad_model)
324
+ self.backup_vad_model_value = tk.StringVar(value=self.settings.vad.backup_vad_model)
325
+ self.vad_trim_beginning_value = tk.BooleanVar(value=self.settings.vad.trim_beginning)
326
+ self.vad_beginning_offset_value = tk.StringVar(value=str(self.settings.vad.beginning_offset))
327
+ self.add_audio_on_no_results_value = tk.BooleanVar(value=self.settings.vad.add_audio_on_no_results)
328
+ self.language_value = tk.StringVar(value=self.settings.vad.language)
329
+ self.cut_and_splice_segments_value = tk.BooleanVar(value=self.settings.vad.cut_and_splice_segments)
330
+ self.splice_padding_value = tk.StringVar(value=str(self.settings.vad.splice_padding) if self.settings.vad.splice_padding else "")
331
+
332
+ # Advanced Settings
333
+ self.audio_player_path_value = tk.StringVar(value=self.settings.advanced.audio_player_path)
334
+ self.video_player_path_value = tk.StringVar(value=self.settings.advanced.video_player_path)
335
+ self.multi_line_line_break_value = tk.StringVar(value=self.settings.advanced.multi_line_line_break)
336
+ self.multi_line_sentence_storage_field_value = tk.StringVar(value=self.settings.advanced.multi_line_sentence_storage_field)
337
+ self.ocr_websocket_port_value = tk.StringVar(value=str(self.settings.advanced.ocr_websocket_port))
338
+ self.texthooker_communication_websocket_port_value = tk.StringVar(value=str(self.settings.advanced.texthooker_communication_websocket_port))
339
+ self.plaintext_websocket_export_port_value = tk.StringVar(value=str(self.settings.advanced.plaintext_websocket_port))
340
+
341
+ # AI Settings
342
+ self.ai_enabled_value = tk.BooleanVar(value=self.settings.ai.enabled)
343
+ self.ai_provider_value = tk.StringVar(value=self.settings.ai.provider)
344
+ self.gemini_model_value = tk.StringVar(value=self.settings.ai.gemini_model)
345
+ self.groq_model_value = tk.StringVar(value=self.settings.ai.groq_model)
346
+ self.gemini_api_key_value = tk.StringVar(value=self.settings.ai.gemini_api_key)
347
+ self.groq_api_key_value = tk.StringVar(value=self.settings.ai.groq_api_key)
348
+ self.local_ai_model_value = tk.StringVar(value=self.settings.ai.local_model)
349
+ self.ai_anki_field_value = tk.StringVar(value=self.settings.ai.anki_field)
350
+ self.use_canned_translation_prompt_value = tk.BooleanVar(value=self.settings.ai.use_canned_translation_prompt)
351
+ self.use_canned_context_prompt_value = tk.BooleanVar(value=self.settings.ai.use_canned_context_prompt)
352
+ self.ai_dialogue_context_length_value = tk.StringVar(value=str(self.settings.ai.dialogue_context_length))
353
+
354
+ # WIP Settings
355
+ self.overlay_websocket_port_value = tk.StringVar(value=str(self.settings.wip.overlay_websocket_port))
356
+ self.overlay_websocket_send_value = tk.BooleanVar(value=self.settings.wip.overlay_websocket_send)
357
+
358
+ # Master Config Settings
359
+ self.switch_to_default_if_not_found_value = tk.BooleanVar(value=self.master_config.switch_to_default_if_not_found)
360
+ self.locale_value = tk.StringVar(value=self.master_config.locale.name)
361
+
362
+
363
+ def create_tabs(self):
364
+ self.create_required_settings_tab()
365
+ self.create_general_tab()
366
+ self.create_paths_tab()
367
+ self.create_anki_tab()
368
+ self.create_vad_tab()
369
+ self.create_features_tab()
370
+ self.create_screenshot_tab()
371
+ self.create_audio_tab()
372
+ self.create_obs_tab()
373
+ self.create_profiles_tab()
374
+ self.create_ai_tab()
375
+ self.create_advanced_tab()
376
+ self.create_wip_tab()
377
+
378
+ def add_reset_button(self, frame, category, row, column=0, recreate_tab=None):
379
+ """
380
+ Adds a reset button to the given frame that resets the settings in the frame to default values.
381
+ """
382
+ reset_btn_i18n = self.i18n.get('buttons', {}).get('reset_to_default', {})
383
+ reset_button = ResetToDefaultButton(frame, command=lambda: self.reset_to_default(category, recreate_tab),
384
+ text=reset_btn_i18n.get('text', 'Reset to Default'),
385
+ tooltip_text=reset_btn_i18n.get('tooltip', 'Reset current tab to default.'))
386
+ reset_button.grid(row=row, column=column, sticky='W', padx=5, pady=5)
387
+ return reset_button
388
+
389
+ def reset_to_default(self, category, recreate_tab):
390
+ """
391
+ Resets the settings in the current tab to default values.
392
+ """
393
+ dialog_i18n = self.i18n.get('dialogs', {}).get('reset_to_default', {})
394
+ if not messagebox.askyesno(dialog_i18n.get('title', 'Reset to Default'),
395
+ dialog_i18n.get('message', 'Are you sure you want to reset all settings in this tab to default?')):
396
+ return
397
+
398
+ default_category_config = getattr(self.default_settings, category)
399
+
400
+ setattr(self.settings, category, default_category_config)
401
+ recreate_tab()
402
+ self.save_settings(profile_change=False)
403
+ self.reload_settings()
404
+
405
+ def show_scene_selection(self, matched_configs):
406
+ selected_scene = None
407
+ if matched_configs:
408
+ dialog_i18n = self.i18n.get('dialogs', {}).get('select_profile', {})
409
+ buttons_i18n = self.i18n.get('buttons', {})
410
+
411
+ selection_window = tk.Toplevel(self.window)
412
+ selection_window.title(dialog_i18n.get('title', 'Select Profile'))
413
+ selection_window.transient(self.window)
414
+ selection_window.grab_set()
415
+
416
+ ttk.Label(selection_window,
417
+ text=dialog_i18n.get('message', 'Multiple profiles match... Please select:')).pack(pady=10)
418
+ profile_var = tk.StringVar(value=matched_configs[0])
419
+ profile_dropdown = ttk.Combobox(selection_window, textvariable=profile_var, values=matched_configs,
420
+ state="readonly")
421
+ profile_dropdown.pack(pady=5)
422
+ ttk.Button(selection_window, text=buttons_i18n.get('ok', 'OK'),
423
+ command=lambda: [selection_window.destroy(), setattr(self, 'selected_scene', profile_var.get())],
424
+ bootstyle="primary").pack(pady=10)
425
+
426
+ self.window.wait_window(selection_window)
427
+ selected_scene = self.selected_scene
428
+ return selected_scene
429
+
430
+ def add_save_hook(self, func):
431
+ on_save.append(func)
432
+
433
+ def show(self):
434
+ logger.info("Showing Configuration Window")
435
+ obs.update_current_game()
436
+ self.reload_settings()
437
+ if self.window is not None:
438
+ self.window.deiconify()
439
+ self.window.lift()
440
+ self.window.update_idletasks()
441
+ return
442
+
443
+ def hide(self):
444
+ if self.window is not None:
445
+ self.window.withdraw()
446
+
447
+ def save_settings(self, profile_change=False, sync_changes=False):
448
+ global settings_saved
449
+
450
+ sync_changes = self.sync_changes_checkbutton.instate(['selected']) if hasattr(self, 'sync_changes_checkbutton') else sync_changes
451
+ # reset checkbox
452
+ if hasattr(self, 'sync_changes_checkbutton'):
453
+ self.sync_changes_checkbutton.state(['!selected'])
454
+
455
+ # Create a new Config instance
456
+ config = ProfileConfig(
457
+ scenes=self.settings.scenes,
458
+ general=General(
459
+ use_websocket=self.websocket_enabled_value.get(),
460
+ use_clipboard=self.clipboard_enabled_value.get(),
461
+ websocket_uri=self.websocket_uri_value.get(),
462
+ merge_matching_sequential_text= self.merge_matching_sequential_text_value.get(),
463
+ open_config_on_startup=self.open_config_on_startup_value.get(),
464
+ open_multimine_on_startup=self.open_multimine_on_startup_value.get(),
465
+ texthook_replacement_regex=self.texthook_replacement_regex_value.get(),
466
+ use_both_clipboard_and_websocket=self.use_both_clipboard_and_websocket_value.get(),
467
+ texthooker_port=int(self.texthooker_port_value.get()),
468
+ native_language=CommonLanguages.from_name(self.native_language_value.get()) if self.native_language_value.get() else CommonLanguages.ENGLISH.value,
469
+ ),
470
+ paths=Paths(
471
+ folder_to_watch=self.folder_to_watch_value.get(),
472
+ audio_destination=self.audio_destination_value.get(),
473
+ screenshot_destination=self.screenshot_destination_value.get(),
474
+ remove_video=self.remove_video_value.get(),
475
+ remove_audio=self.remove_audio_value.get(),
476
+ remove_screenshot=self.remove_screenshot_value.get()
477
+ ),
478
+ anki=Anki(
479
+ update_anki=self.update_anki_value.get(),
480
+ url=self.anki_url_value.get(),
481
+ sentence_field=self.sentence_field_value.get(),
482
+ sentence_audio_field=self.sentence_audio_field_value.get(),
483
+ picture_field=self.picture_field_value.get(),
484
+ word_field=self.word_field_value.get(),
485
+ previous_sentence_field=self.previous_sentence_field_value.get(),
486
+ previous_image_field=self.previous_image_field_value.get(),
487
+ custom_tags=[tag.strip() for tag in self.custom_tags_value.get().split(',') if tag.strip()],
488
+ tags_to_check=[tag.strip().lower() for tag in self.tags_to_check_value.get().split(',') if tag.strip()],
489
+ add_game_tag=self.add_game_tag_value.get(),
490
+ polling_rate=int(self.polling_rate_value.get()),
491
+ overwrite_audio=self.overwrite_audio_value.get(),
492
+ overwrite_picture=self.overwrite_picture_value.get(),
493
+ multi_overwrites_sentence=self.multi_overwrites_sentence_value.get(),
494
+ parent_tag=self.parent_tag_value.get(),
495
+ ),
496
+ features=Features(
497
+ full_auto=self.full_auto_value.get(),
498
+ notify_on_update=self.notify_on_update_value.get(),
499
+ open_anki_edit=self.open_anki_edit_value.get(),
500
+ open_anki_in_browser=self.open_anki_browser_value.get(),
501
+ backfill_audio=self.backfill_audio_value.get(),
502
+ browser_query=self.browser_query_value.get(),
503
+ ),
504
+ screenshot=Screenshot(
505
+ enabled=self.screenshot_enabled_value.get(),
506
+ width=self.screenshot_width_value.get(),
507
+ height=self.screenshot_height_value.get(),
508
+ quality=self.screenshot_quality_value.get(),
509
+ extension=self.screenshot_extension_value.get(),
510
+ custom_ffmpeg_settings=self.screenshot_custom_ffmpeg_settings_value.get(),
511
+ screenshot_hotkey_updates_anki=self.screenshot_hotkey_update_anki_value.get(),
512
+ seconds_after_line=float(self.seconds_after_line_value.get()) if self.seconds_after_line_value.get() else 0.0,
513
+ screenshot_timing_setting=self.screenshot_timing_value.get(),
514
+ use_screenshot_selector=self.use_screenshot_selector_value.get(),
515
+ ),
516
+ audio=Audio(
517
+ enabled=self.audio_enabled_value.get(),
518
+ extension=self.audio_extension_value.get(),
519
+ beginning_offset=float(self.beginning_offset_value.get()),
520
+ end_offset=float(self.end_offset_value.get()),
521
+ ffmpeg_reencode_options=self.audio_ffmpeg_reencode_options_value.get(),
522
+ external_tool=self.external_tool_value.get(),
523
+ anki_media_collection=self.anki_media_collection_value.get(),
524
+ external_tool_enabled=self.external_tool_enabled_value.get(),
525
+ pre_vad_end_offset=float(self.pre_vad_audio_offset_value.get()),
526
+ ),
527
+ obs=OBS(
528
+ enabled=self.obs_enabled_value.get(),
529
+ open_obs=self.obs_open_obs_value.get(),
530
+ close_obs=self.obs_close_obs_value.get(),
531
+ host=self.obs_host_value.get(),
532
+ port=int(self.obs_port_value.get()),
533
+ password=self.obs_password_value.get(),
534
+ get_game_from_scene=self.obs_get_game_from_scene_name_value.get(),
535
+ minimum_replay_size=int(self.obs_minimum_replay_size_value.get())
536
+ ),
537
+ hotkeys=Hotkeys(
538
+ reset_line=self.reset_line_hotkey_value.get(),
539
+ take_screenshot=self.take_screenshot_hotkey_value.get(),
540
+ play_latest_audio=self.play_latest_audio_hotkey_value.get()
541
+ ),
542
+ vad=VAD(
543
+ whisper_model=self.whisper_model_value.get(),
544
+ do_vad_postprocessing=self.do_vad_postprocessing_value.get(),
545
+ selected_vad_model=self.selected_vad_model_value.get(),
546
+ backup_vad_model=self.backup_vad_model_value.get(),
547
+ trim_beginning=self.vad_trim_beginning_value.get(),
548
+ beginning_offset=float(self.vad_beginning_offset_value.get()),
549
+ add_audio_on_no_results=self.add_audio_on_no_results_value.get(),
550
+ language=self.language_value.get(),
551
+ cut_and_splice_segments=self.cut_and_splice_segments_value.get(),
552
+ splice_padding=float(self.splice_padding_value.get()) if self.splice_padding_value.get() else 0.0,
553
+ ),
554
+ advanced=Advanced(
555
+ audio_player_path=self.audio_player_path_value.get(),
556
+ video_player_path=self.video_player_path_value.get(),
557
+ multi_line_line_break=self.multi_line_line_break_value.get(),
558
+ multi_line_sentence_storage_field=self.multi_line_sentence_storage_field_value.get(),
559
+ ocr_websocket_port=int(self.ocr_websocket_port_value.get()),
560
+ texthooker_communication_websocket_port=int(self.texthooker_communication_websocket_port_value.get()),
561
+ plaintext_websocket_port=int(self.plaintext_websocket_export_port_value.get()),
562
+ ),
563
+ ai=Ai(
564
+ enabled=self.ai_enabled_value.get(),
565
+ provider=self.ai_provider_value.get(),
566
+ gemini_model=self.gemini_model_value.get(),
567
+ groq_model=self.groq_model_value.get(),
568
+ gemini_api_key=self.gemini_api_key_value.get(),
569
+ api_key=self.gemini_api_key_value.get(),
570
+ groq_api_key=self.groq_api_key_value.get(),
571
+ local_model=self.local_ai_model_value.get(),
572
+ anki_field=self.ai_anki_field_value.get(),
573
+ use_canned_translation_prompt=self.use_canned_translation_prompt_value.get(),
574
+ use_canned_context_prompt=self.use_canned_context_prompt_value.get(),
575
+ custom_prompt=self.custom_prompt.get("1.0", tk.END).strip(),
576
+ dialogue_context_length=int(self.ai_dialogue_context_length_value.get()),
577
+ ),
578
+ wip=WIP(
579
+ overlay_websocket_port=int(self.overlay_websocket_port_value.get()),
580
+ overlay_websocket_send=self.overlay_websocket_send_value.get(),
581
+ monitor_to_capture=self.monitor_to_capture.current() if self.monitors else 0
582
+ )
583
+ )
584
+
585
+ # Find the display name for "Custom" to check against
586
+ audio_i18n = self.i18n.get('tabs', {}).get('audio', {})
587
+ ffmpeg_preset_i18n = audio_i18n.get('ffmpeg_preset', {}).get('options', {})
588
+ custom_display_name = ffmpeg_preset_i18n.get('custom', 'Custom')
589
+
590
+ if self.ffmpeg_audio_preset_options.get() == custom_display_name:
591
+ config.audio.custom_encode_settings = self.audio_ffmpeg_reencode_options_value.get()
592
+
593
+ dialog_i18n = self.i18n.get('dialogs', {}).get('config_error', {})
594
+ error_title = dialog_i18n.get('title', 'Configuration Error')
595
+
596
+ if config.features.backfill_audio and config.features.full_auto:
597
+ messagebox.showerror(error_title,
598
+ dialog_i18n.get('full_auto_and_backfill', 'Cannot have Full Auto and Backfill...'))
599
+ return
600
+
601
+ if not config.general.use_websocket and not config.general.use_clipboard:
602
+ messagebox.showerror(error_title, dialog_i18n.get('no_input_method', 'Cannot have both...'))
603
+ return
604
+
605
+ current_profile = self.profile_combobox.get()
606
+ prev_config = self.master_config.get_config()
607
+ self.master_config.switch_to_default_if_not_found = self.switch_to_default_if_not_found_value.get()
608
+ if profile_change:
609
+ self.master_config.current_profile = current_profile
610
+ else:
611
+ self.master_config.current_profile = current_profile
612
+ self.master_config.set_config_for_profile(current_profile, config)
613
+
614
+ self.master_config.locale = Locale[self.locale_value.get()].value
615
+
616
+
617
+ config_backup_folder = os.path.join(get_app_directory(), "backup", "config")
618
+ os.makedirs(config_backup_folder, exist_ok=True)
619
+ timestamp = time.strftime("%Y-%m-%d_%H-%M-%S")
620
+ with open(os.path.join(config_backup_folder, f"config_backup_{timestamp}.json"), 'w') as backup_file:
621
+ backup_file.write(self.master_config.to_json(indent=4))
622
+
623
+ self.master_config = self.master_config.sync_shared_fields()
624
+
625
+ if sync_changes:
626
+ self.master_config.sync_changed_fields(prev_config)
627
+
628
+ with open(get_config_path(), 'w') as file:
629
+ file.write(self.master_config.to_json(indent=4))
630
+
631
+ logger.info("Settings saved successfully!")
632
+
633
+ if self.master_config.get_config().restart_required(prev_config):
634
+ logger.info("Restart Required for some settings to take affect!")
635
+ asyncio.run(send_restart_signal())
636
+
637
+ settings_saved = True
638
+ configuration.reload_config()
639
+ self.settings = get_config()
640
+ for func in on_save:
641
+ func()
642
+
643
+ def reload_settings(self, force_refresh=False):
644
+ new_config = configuration.load_config()
645
+ current_config = new_config.get_config()
646
+
647
+ title_template = self.i18n.get('app', {}).get('title_with_profile', 'GameSentenceMiner Configuration - {profile_name}')
648
+ self.window.title(title_template.format(profile_name=current_config.name))
649
+
650
+ if current_config.name != self.settings.name or self.settings.config_changed(current_config) or force_refresh:
651
+ logger.info("Config changed, reloading settings.")
652
+ self.master_config = new_config
653
+ self.settings = current_config
654
+ for frame in self.notebook.winfo_children():
655
+ frame.destroy()
656
+
657
+ self.required_settings_frame = None
658
+ self.general_tab = None
659
+ self.paths_tab = None
660
+ self.anki_tab = None
661
+ self.vad_tab = None
662
+ self.features_tab = None
663
+ self.screenshot_tab = None
664
+ self.audio_tab = None
665
+ self.obs_tab = None
666
+ self.profiles_tab = None
667
+ self.ai_tab = None
668
+ self.advanced_tab = None
669
+ self.wip_tab = None
670
+
671
+ self.create_vars()
672
+ self.create_tabs()
673
+
674
+ def increment_row(self):
675
+ self.current_row += 1
676
+ return self.current_row
677
+
678
+ @new_tab
679
+ def create_general_tab(self):
680
+ if self.general_tab is None:
681
+ general_i18n = self.i18n.get('tabs', {}).get('general', {})
682
+ self.general_tab = ttk.Frame(self.notebook, padding=15)
683
+ self.notebook.add(self.general_tab, text=general_i18n.get('title', 'General'))
684
+ else:
685
+ for widget in self.general_tab.winfo_children():
686
+ widget.destroy()
687
+
688
+ general_i18n = self.i18n.get('tabs', {}).get('general', {})
689
+
690
+ ws_i18n = general_i18n.get('websocket_enabled', {})
691
+ HoverInfoLabelWidget(self.general_tab, text=ws_i18n.get('label', 'Websocket Enabled:'),
692
+ foreground="dark orange", font=("Helvetica", 10, "bold"),
693
+ tooltip=ws_i18n.get('tooltip', '...'),
694
+ row=self.current_row, column=0)
695
+ ttk.Checkbutton(self.general_tab, variable=self.websocket_enabled_value, bootstyle="round-toggle").grid(
696
+ row=self.current_row, column=1, sticky='W', pady=2)
697
+ self.current_row += 1
698
+
699
+ clip_i18n = general_i18n.get('clipboard_enabled', {})
700
+ HoverInfoLabelWidget(self.general_tab, text=clip_i18n.get('label', 'Clipboard Enabled:'),
701
+ foreground="dark orange", font=("Helvetica", 10, "bold"),
702
+ tooltip=clip_i18n.get('tooltip', '...'), row=self.current_row, column=0)
703
+ ttk.Checkbutton(self.general_tab, variable=self.clipboard_enabled_value, bootstyle="round-toggle").grid(
704
+ row=self.current_row, column=1, sticky='W', pady=2)
705
+ self.current_row += 1
706
+
707
+ both_i18n = general_i18n.get('allow_both_simultaneously', {})
708
+ HoverInfoLabelWidget(self.general_tab, text=both_i18n.get('label', 'Allow Both Simultaneously:'),
709
+ foreground="red", font=("Helvetica", 10, "bold"),
710
+ tooltip=both_i18n.get('tooltip', '...'),
711
+ row=self.current_row, column=0)
712
+ ttk.Checkbutton(self.general_tab, variable=self.use_both_clipboard_and_websocket_value, bootstyle="round-toggle").grid(
713
+ row=self.current_row, column=1, sticky='W', pady=2)
714
+ self.current_row += 1
715
+
716
+ merge_i18n = general_i18n.get('merge_sequential_text', {})
717
+ HoverInfoLabelWidget(self.general_tab, text=merge_i18n.get('label', 'Merge Matching Sequential Text:'),
718
+ foreground="red", font=("Helvetica", 10, "bold"),
719
+ tooltip=merge_i18n.get('tooltip', '...'),
720
+ row=self.current_row, column=0)
721
+ ttk.Checkbutton(self.general_tab, variable=self.merge_matching_sequential_text_value, bootstyle="round-toggle").grid(
722
+ row=self.current_row, column=1, sticky='W', pady=2)
723
+ self.current_row += 1
724
+
725
+ uri_i18n = general_i18n.get('websocket_uri', {})
726
+ HoverInfoLabelWidget(self.general_tab, text=uri_i18n.get('label', 'Websocket URI(s):'),
727
+ tooltip=uri_i18n.get('tooltip', '...'),
728
+ row=self.current_row, column=0)
729
+ ttk.Entry(self.general_tab, width=50, textvariable=self.websocket_uri_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
730
+ self.current_row += 1
731
+
732
+ regex_i18n = general_i18n.get('texthook_regex', {})
733
+ HoverInfoLabelWidget(self.general_tab, text=regex_i18n.get('label', 'TextHook Replacement Regex:'),
734
+ tooltip=regex_i18n.get('tooltip', '...'),
735
+ row=self.current_row, column=0)
736
+ ttk.Entry(self.general_tab, textvariable=self.texthook_replacement_regex_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
737
+ self.current_row += 1
738
+
739
+ open_config_i18n = general_i18n.get('open_config_on_startup', {})
740
+ HoverInfoLabelWidget(self.general_tab, text=open_config_i18n.get('label', 'Open Config on Startup:'),
741
+ tooltip=open_config_i18n.get('tooltip', '...'), row=self.current_row, column=0)
742
+ ttk.Checkbutton(self.general_tab, variable=self.open_config_on_startup_value, bootstyle="round-toggle").grid(
743
+ row=self.current_row, column=1, sticky='W', pady=2)
744
+ self.current_row += 1
745
+
746
+ open_texthooker_i18n = general_i18n.get('open_texthooker_on_startup', {})
747
+ HoverInfoLabelWidget(self.general_tab, text=open_texthooker_i18n.get('label', 'Open GSM Texthooker on Startup:'),
748
+ tooltip=open_texthooker_i18n.get('tooltip', '...'), row=self.current_row, column=0)
749
+ ttk.Checkbutton(self.general_tab, variable=self.open_multimine_on_startup_value, bootstyle="round-toggle").grid(
750
+ row=self.current_row, column=1, sticky='W', pady=2)
751
+ self.current_row += 1
752
+
753
+ port_i18n = general_i18n.get('texthooker_port', {})
754
+ HoverInfoLabelWidget(self.general_tab, text=port_i18n.get('label', 'GSM Texthooker Port:'),
755
+ tooltip=port_i18n.get('tooltip', '...'),
756
+ row=self.current_row, column=0)
757
+ ttk.Entry(self.general_tab, textvariable=self.texthooker_port_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
758
+ self.current_row += 1
759
+
760
+ # locale_i18n = general_i18n.get('locale', {})
761
+ # HoverInfoLabelWidget(self.general_tab, text=locale_i18n.get('label', 'Locale:'),
762
+ # tooltip=locale_i18n.get('tooltip', '...'),
763
+ # row=self.current_row, column=0)
764
+ # locale_combobox = ttk.Combobox(self.general_tab, textvariable=self.locale_value, values=[Locale.English.name, Locale.日本語.name, Locale.中文.name], state="readonly")
765
+ # locale_combobox.grid(row=self.current_row, column=1, sticky='EW', pady=2)
766
+ # locale_combobox.bind("<<ComboboxSelected>>", lambda e: self.change_locale())
767
+ # self.current_row += 1
768
+
769
+ lang_i18n = general_i18n.get('native_language', {})
770
+ HoverInfoLabelWidget(self.general_tab, text=lang_i18n.get('label', 'Native Language:'),
771
+ tooltip=lang_i18n.get('tooltip', '...'),
772
+ row=self.current_row, column=0)
773
+ ttk.Combobox(self.general_tab, textvariable=self.native_language_value, values=CommonLanguages.get_all_names_pretty(), state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
774
+ self.current_row += 1
775
+
776
+ legend_i18n = general_i18n.get('legend', {})
777
+ ttk.Label(self.general_tab, text=legend_i18n.get('important', '...'), foreground="dark orange",
778
+ font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
779
+ self.current_row += 1
780
+ ttk.Label(self.general_tab, text=legend_i18n.get('advanced', '...'), foreground="red",
781
+ font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
782
+ self.current_row += 1
783
+ ttk.Label(self.general_tab, text=legend_i18n.get('recommended', '...'), foreground="green",
784
+ font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
785
+ self.current_row += 1
786
+ ttk.Label(self.general_tab,
787
+ text=legend_i18n.get('tooltip_info', '...'),
788
+ font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
789
+ self.current_row += 1
790
+
791
+ if is_beangate:
792
+ ttk.Button(self.general_tab, text=self.i18n.get('buttons', {}).get('run_function', 'Run Function'), command=self.test_func, bootstyle="info").grid(
793
+ row=self.current_row, column=0, pady=5
794
+ )
795
+ self.current_row += 1
796
+
797
+ self.add_reset_button(self.general_tab, "general", self.current_row, column=0, recreate_tab=self.create_general_tab)
798
+
799
+ self.general_tab.grid_columnconfigure(0, weight=0)
800
+ self.general_tab.grid_columnconfigure(1, weight=0)
801
+ for row in range(self.current_row):
802
+ self.general_tab.grid_rowconfigure(row, minsize=30)
803
+
804
+ return self.general_tab
805
+
806
+ @new_tab
807
+ def create_required_settings_tab(self):
808
+ if self.required_settings_frame is None:
809
+ self.required_settings_frame = ttk.Frame(self.notebook, padding=15)
810
+ self.notebook.add(self.required_settings_frame, text=self.i18n.get('tabs', {}).get('key_settings', {}).get('title', 'Key Settings'))
811
+ else:
812
+ for widget in self.required_settings_frame.winfo_children():
813
+ widget.destroy()
814
+ required_settings_frame = self.required_settings_frame
815
+
816
+ simple_i18n = self.i18n.get('tabs', {}).get('simple', {})
817
+
818
+ # key_settings_i18n = simple_i18n.get('key_settings', {})
819
+ # HoverInfoLabelWidget(required_settings_frame, text=key_settings_i18n.get('label', 'Key Settings'),
820
+ # tooltip=key_settings_i18n.get('tooltip', "These settings are important..."),
821
+ # row=self.current_row, column=0, columnspan=4, font=("Helvetica", 12, "bold"))
822
+ # self.current_row += 1
823
+
824
+ # --- General Settings ---
825
+ general_i18n = self.i18n.get('tabs', {}).get('general', {})
826
+ input_frame = ttk.Frame(required_settings_frame)
827
+ input_frame.grid(row=self.current_row, column=0, columnspan=4, sticky='W', pady=2)
828
+
829
+ ws_i18n = general_i18n.get('websocket_enabled', {})
830
+ HoverInfoLabelWidget(input_frame, text=ws_i18n.get('label', '...'),
831
+ tooltip=ws_i18n.get('tooltip', '...'), row=0, column=0)
832
+ ttk.Checkbutton(input_frame, variable=self.websocket_enabled_value, bootstyle="round-toggle").grid(row=0, column=1, sticky='W', pady=2)
833
+
834
+ clip_i18n = general_i18n.get('clipboard_enabled', {})
835
+ HoverInfoLabelWidget(input_frame, text=clip_i18n.get('label', '...'),
836
+ tooltip=clip_i18n.get('tooltip', '...'), row=0, column=2, padx=5)
837
+ ttk.Checkbutton(input_frame, variable=self.clipboard_enabled_value, bootstyle="round-toggle").grid(row=0, column=3, sticky='W', pady=2)
838
+ self.current_row += 1
839
+
840
+ uri_i18n = general_i18n.get('websocket_uri', {})
841
+ HoverInfoLabelWidget(required_settings_frame, text=uri_i18n.get('label', '...'),
842
+ tooltip=uri_i18n.get('tooltip', '...'), row=self.current_row, column=0)
843
+ ttk.Entry(required_settings_frame, width=50, textvariable=self.websocket_uri_value).grid(row=self.current_row, column=1, columnspan=3, sticky='EW', pady=2)
844
+ self.current_row += 1
845
+
846
+ locale_i18n = general_i18n.get('locale', {})
847
+ HoverInfoLabelWidget(required_settings_frame, text=locale_i18n.get('label', '...'),
848
+ tooltip=locale_i18n.get('tooltip', '...'), row=self.current_row, column=0)
849
+ locale_combobox_simple = ttk.Combobox(required_settings_frame, textvariable=self.locale_value, values=[Locale.English.name, Locale.日本語.name, Locale.中文.name], state="readonly")
850
+ locale_combobox_simple.grid(row=self.current_row, column=1, columnspan=3, sticky='EW', pady=2)
851
+ locale_combobox_simple.bind("<<ComboboxSelected>>", lambda e: self.change_locale())
852
+ self.current_row += 1
853
+
854
+ # --- Paths Settings ---
855
+ paths_i18n = self.i18n.get('tabs', {}).get('paths', {})
856
+ browse_text = self.i18n.get('buttons', {}).get('browse', 'Browse')
857
+ watch_i18n = paths_i18n.get('folder_to_watch', {})
858
+ HoverInfoLabelWidget(required_settings_frame, text=watch_i18n.get('label', '...'),
859
+ tooltip=watch_i18n.get('tooltip', '...'), row=self.current_row, column=0)
860
+ folder_watch_entry = ttk.Entry(required_settings_frame, width=50, textvariable=self.folder_to_watch_value)
861
+ folder_watch_entry.grid(row=self.current_row, column=1, columnspan=2, sticky='EW', pady=2)
862
+ ttk.Button(required_settings_frame, text=browse_text, command=lambda: self.browse_folder(folder_watch_entry),
863
+ bootstyle="outline").grid(row=self.current_row, column=3, padx=5, pady=2)
864
+ self.current_row += 1
865
+
866
+ # --- Anki Settings ---
867
+ anki_i18n = self.i18n.get('tabs', {}).get('anki', {})
868
+ sentence_i18n = anki_i18n.get('sentence_field', {})
869
+ HoverInfoLabelWidget(required_settings_frame, text=sentence_i18n.get('label', '...'),
870
+ tooltip=sentence_i18n.get('tooltip', '...'), row=self.current_row, column=0)
871
+ ttk.Entry(required_settings_frame, textvariable=self.sentence_field_value).grid(row=self.current_row, column=1, columnspan=3, sticky='EW', pady=2)
872
+ self.current_row += 1
873
+
874
+ audio_i18n = anki_i18n.get('sentence_audio_field', {})
875
+ HoverInfoLabelWidget(required_settings_frame, text=audio_i18n.get('label', '...'),
876
+ tooltip=audio_i18n.get('tooltip', '...'), row=self.current_row, column=0)
877
+ ttk.Entry(required_settings_frame, textvariable=self.sentence_audio_field_value).grid(row=self.current_row, column=1, columnspan=3, sticky='EW', pady=2)
878
+ self.current_row += 1
879
+
880
+ pic_i18n = anki_i18n.get('picture_field', {})
881
+ HoverInfoLabelWidget(required_settings_frame, text=pic_i18n.get('label', '...'),
882
+ tooltip=pic_i18n.get('tooltip', '...'), row=self.current_row, column=0)
883
+ ttk.Entry(required_settings_frame, textvariable=self.picture_field_value).grid(row=self.current_row, column=1, columnspan=3, sticky='EW', pady=2)
884
+ self.current_row += 1
885
+
886
+ word_i18n = anki_i18n.get('word_field', {})
887
+ HoverInfoLabelWidget(required_settings_frame, text=word_i18n.get('label', '...'),
888
+ tooltip=word_i18n.get('tooltip', '...'), row=self.current_row, column=0)
889
+ ttk.Entry(required_settings_frame, textvariable=self.word_field_value).grid(row=self.current_row, column=1, columnspan=3, sticky='EW', pady=2)
890
+ self.current_row += 1
891
+
892
+ # --- Audio Settings ---
893
+ audio_tab_i18n = self.i18n.get('tabs', {}).get('audio', {})
894
+ begin_offset_i18n = audio_tab_i18n.get('beginning_offset', {})
895
+ HoverInfoLabelWidget(required_settings_frame, text=begin_offset_i18n.get('label', '...'),
896
+ tooltip=begin_offset_i18n.get('tooltip', '...'), row=self.current_row, column=0)
897
+ ttk.Entry(required_settings_frame, textvariable=self.beginning_offset_value).grid(row=self.current_row, column=1, columnspan=3, sticky='EW', pady=2)
898
+ self.current_row += 1
899
+
900
+ # Vad end offset
901
+
902
+ vad_i18n = self.i18n.get('tabs', {}).get('vad', {})
903
+ vad_end_offset_i18n = vad_i18n.get('audio_end_offset', {})
904
+ HoverInfoLabelWidget(required_settings_frame, text=vad_end_offset_i18n.get('label', '...'),
905
+ tooltip=vad_end_offset_i18n.get('tooltip', '...'), row=self.current_row, column=0)
906
+ ttk.Entry(required_settings_frame, textvariable=self.end_offset_value).grid(row=self.current_row, column=1, columnspan=3, sticky='EW', pady=2)
907
+ self.current_row += 1
908
+
909
+ # --- Features Settings ---
910
+ features_i18n = self.i18n.get('tabs', {}).get('features', {})
911
+ # action_frame = ttk.Frame(required_settings_frame)
912
+ # action_frame.grid(row=self.current_row, column=0, columnspan=4, sticky='W', pady=2)
913
+
914
+ # Feature Toggles, Anki, OBS, Audio, VAD, AI, WIP
915
+ feature_frame = ttk.Frame(required_settings_frame)
916
+ feature_frame.grid(row=self.current_row, column=0, columnspan=5, sticky='W', pady=2)
917
+
918
+ # anki_enabled_i18n = simple_i18n.get('anki_enabled', {})
919
+ # HoverInfoLabelWidget(feature_frame, text=anki_enabled_i18n.get('label', '...'),
920
+ # tooltip=anki_enabled_i18n.get('tooltip', '...'), row=self.current_row, column=0)
921
+ # ttk.Checkbutton(feature_frame, variable=self.update_anki_value, bootstyle="round-toggle").grid(
922
+ # row=self.current_row, column=1, sticky='W', pady=2)
923
+
924
+ open_edit_i18n = features_i18n.get('open_anki_edit', {})
925
+ HoverInfoLabelWidget(feature_frame, text=open_edit_i18n.get('label', '...'),
926
+ tooltip=open_edit_i18n.get('tooltip', '...'), row=self.current_row, column=0)
927
+ ttk.Checkbutton(feature_frame, variable=self.open_anki_edit_value, bootstyle="round-toggle").grid(
928
+ row=self.current_row, column=1, sticky='W', pady=2)
929
+
930
+ open_browser_i18n = features_i18n.get('open_anki_browser', {})
931
+ HoverInfoLabelWidget(feature_frame, text=open_browser_i18n.get('label', '...'),
932
+ tooltip=open_browser_i18n.get('tooltip', '...'), row=self.current_row, column=2, padx=5)
933
+ ttk.Checkbutton(feature_frame, variable=self.open_anki_browser_value, bootstyle="round-toggle").grid(
934
+ row=self.current_row, column=3, sticky='W', pady=2)
935
+ self.current_row += 1
936
+
937
+ # obs_enabled_i18n = simple_i18n.get('obs_enabled', {})
938
+ # HoverInfoLabelWidget(feature_frame, text=obs_enabled_i18n.get('label', '...'),
939
+ # tooltip=obs_enabled_i18n.get('tooltip', '...'), row=self.current_row, column=0)
940
+ # ttk.Checkbutton(feature_frame, variable=self.obs_enabled_value, bootstyle="round-toggle").grid(
941
+ # row=self.current_row, column=1, sticky='W', pady=2)
942
+ # self.current_row += 1
943
+
944
+ # screenshot_i18n = simple_i18n.get('screenshot_enabled', {})
945
+ # HoverInfoLabelWidget(feature_frame, text=screenshot_i18n.get('label', '...'),
946
+ # tooltip=screenshot_i18n.get('tooltip', '...'), row=self.current_row, column=0)
947
+ # ttk.Checkbutton(feature_frame, variable=self.screenshot_enabled_value, bootstyle="round-toggle").grid(
948
+ # row=self.current_row, column=1, sticky='W', pady=2)
949
+ # self.current_row += 1
950
+
951
+ # audio_i18n = self.i18n.get('tabs', {}).get('audio', {})
952
+ # audio_enabled_i18n = simple_i18n.get('audio_enabled', {})
953
+ # HoverInfoLabelWidget(feature_frame, text=audio_enabled_i18n.get('label', '...'),
954
+ # tooltip=audio_enabled_i18n.get('tooltip', '...'), row=self.current_row, column=0)
955
+ # ttk.Checkbutton(feature_frame, variable=self.audio_enabled_value, bootstyle="round-toggle").grid(
956
+ # row=self.current_row, column=1, sticky='W', pady=2)
957
+ # self.current_row += 1
958
+
959
+ # vad_i18n = self.i18n.get('tabs', {}).get('vad', {})
960
+ # vad_enabled_i18n = simple_i18n.get('vad_enabled', {})
961
+ # HoverInfoLabelWidget(feature_frame, text=vad_enabled_i18n.get('label', '...'),
962
+ # tooltip=vad_enabled_i18n.get('tooltip', '...'), row=self.current_row, column=0)
963
+ # ttk.Checkbutton(feature_frame, variable=self.do_vad_postprocessing_value, bootstyle="round-toggle").grid(
964
+ # row=self.current_row, column=1, sticky='W', pady=2)
965
+ # self.current_row += 1
966
+
967
+ # ai_i18n = self.i18n.get('tabs', {}).get('ai', {})
968
+ # ai_enabled_i18n = simple_i18n.get('ai_enabled', {})
969
+ # HoverInfoLabelWidget(feature_frame, text=ai_enabled_i18n.get('label', '...'),
970
+ # tooltip=ai_enabled_i18n.get('tooltip', '...'), row=self.current_row, column=0)
971
+ # ttk.Checkbutton(feature_frame, variable=self.ai_enabled_value, bootstyle="round-toggle").grid(
972
+ # row=self.current_row, column=1, sticky='W', pady=2)
973
+
974
+ required_settings_frame.grid_columnconfigure(1, weight=1)
975
+
976
+ return required_settings_frame
977
+
978
+ @new_tab
979
+ def create_vad_tab(self):
980
+ if self.vad_tab is None:
981
+ vad_i18n = self.i18n.get('tabs', {}).get('vad', {})
982
+ self.vad_tab = ttk.Frame(self.notebook, padding=15)
983
+ self.notebook.add(self.vad_tab, text=vad_i18n.get('title', 'VAD'))
984
+ else:
985
+ for widget in self.vad_tab.winfo_children():
986
+ widget.destroy()
987
+
988
+ vad_frame = self.vad_tab
989
+ vad_i18n = self.i18n.get('tabs', {}).get('vad', {})
990
+
991
+ postproc_i18n = vad_i18n.get('do_postprocessing', {})
992
+ HoverInfoLabelWidget(vad_frame, text=postproc_i18n.get('label', '...'),
993
+ tooltip=postproc_i18n.get('tooltip', '...'),
994
+ row=self.current_row, column=0)
995
+ ttk.Checkbutton(vad_frame, variable=self.do_vad_postprocessing_value, bootstyle="round-toggle").grid(
996
+ row=self.current_row, column=1, sticky='W', pady=2)
997
+ self.current_row += 1
998
+
999
+ lang_i18n = vad_i18n.get('language', {})
1000
+ HoverInfoLabelWidget(vad_frame, text=lang_i18n.get('label', '...'),
1001
+ tooltip=lang_i18n.get('tooltip', '...'),
1002
+ row=self.current_row, column=0)
1003
+ ttk.Combobox(vad_frame, textvariable=self.language_value, values=AVAILABLE_LANGUAGES, state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
1004
+ self.current_row += 1
1005
+
1006
+ whisper_i18n = vad_i18n.get('whisper_model', {})
1007
+ HoverInfoLabelWidget(vad_frame, text=whisper_i18n.get('label', '...'), tooltip=whisper_i18n.get('tooltip', '...'),
1008
+ row=self.current_row, column=0)
1009
+ ttk.Combobox(vad_frame, textvariable=self.whisper_model_value, values=[WHISPER_TINY, WHISPER_BASE, WHISPER_SMALL, WHISPER_MEDIUM,
1010
+ WHSIPER_LARGE, WHISPER_TURBO], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
1011
+ self.current_row += 1
1012
+
1013
+ selected_model_i18n = vad_i18n.get('selected_model', {})
1014
+ HoverInfoLabelWidget(vad_frame, text=selected_model_i18n.get('label', '...'), tooltip=selected_model_i18n.get('tooltip', '...'),
1015
+ foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1016
+ ttk.Combobox(vad_frame, textvariable=self.selected_vad_model_value, values=[SILERO, WHISPER], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
1017
+ self.current_row += 1
1018
+
1019
+ backup_model_i18n = vad_i18n.get('backup_model', {})
1020
+ HoverInfoLabelWidget(vad_frame, text=backup_model_i18n.get('label', '...'),
1021
+ tooltip=backup_model_i18n.get('tooltip', '...'),
1022
+ row=self.current_row, column=0)
1023
+ ttk.Combobox(vad_frame, textvariable=self.backup_vad_model_value, values=[OFF, SILERO, WHISPER], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
1024
+ self.current_row += 1
1025
+
1026
+ no_results_i18n = vad_i18n.get('add_on_no_results', {})
1027
+ HoverInfoLabelWidget(vad_frame, text=no_results_i18n.get('label', '...'),
1028
+ tooltip=no_results_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1029
+ ttk.Checkbutton(vad_frame, variable=self.add_audio_on_no_results_value, bootstyle="round-toggle").grid(
1030
+ row=self.current_row, column=1, sticky='W', pady=2)
1031
+ self.current_row += 1
1032
+
1033
+ end_offset_i18n = vad_i18n.get('audio_end_offset', {})
1034
+ HoverInfoLabelWidget(vad_frame, text=end_offset_i18n.get('label', '...'),
1035
+ tooltip=end_offset_i18n.get('tooltip', '...'), foreground="dark orange",
1036
+ font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1037
+ ttk.Entry(vad_frame, textvariable=self.end_offset_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1038
+ self.current_row += 1
1039
+
1040
+ trim_begin_i18n = vad_i18n.get('trim_beginning', {})
1041
+ HoverInfoLabelWidget(vad_frame, text=trim_begin_i18n.get('label', '...'),
1042
+ tooltip=trim_begin_i18n.get('tooltip', '...'),
1043
+ row=self.current_row, column=0)
1044
+ ttk.Checkbutton(vad_frame, variable=self.vad_trim_beginning_value, bootstyle="round-toggle").grid(
1045
+ row=self.current_row, column=1, sticky='W', pady=2)
1046
+
1047
+ begin_offset_i18n = vad_i18n.get('beginning_offset', {})
1048
+ HoverInfoLabelWidget(vad_frame, text=begin_offset_i18n.get('label', '...'),
1049
+ tooltip=begin_offset_i18n.get('tooltip', '...'),
1050
+ row=self.current_row, column=2)
1051
+ ttk.Entry(vad_frame, textvariable=self.vad_beginning_offset_value).grid(row=self.current_row, column=3, sticky='EW', pady=2)
1052
+ self.current_row += 1
1053
+
1054
+ splice_i18n = vad_i18n.get('cut_and_splice', {})
1055
+ HoverInfoLabelWidget(vad_frame, text=splice_i18n.get('label', '...'),
1056
+ tooltip=splice_i18n.get('tooltip', '...'),
1057
+ row=self.current_row, column=0)
1058
+ ttk.Checkbutton(vad_frame, variable=self.cut_and_splice_segments_value, bootstyle="round-toggle").grid(
1059
+ row=self.current_row, column=1, sticky='W', pady=2)
1060
+
1061
+ padding_i18n = vad_i18n.get('splice_padding', {})
1062
+ HoverInfoLabelWidget(vad_frame, text=padding_i18n.get('label', '...'),
1063
+ tooltip=padding_i18n.get('tooltip', '...'),
1064
+ row=self.current_row, column=2)
1065
+ ttk.Entry(vad_frame, textvariable=self.splice_padding_value).grid(row=self.current_row, column=3, sticky='EW', pady=2)
1066
+ self.current_row += 1
1067
+
1068
+ self.add_reset_button(vad_frame, "vad", self.current_row, 0, self.create_vad_tab)
1069
+
1070
+ for col in range(5): vad_frame.grid_columnconfigure(col, weight=0)
1071
+ for row in range(self.current_row): vad_frame.grid_rowconfigure(row, minsize=30)
1072
+
1073
+ return vad_frame
1074
+
1075
+ @new_tab
1076
+ def create_paths_tab(self):
1077
+ if self.paths_tab is None:
1078
+ paths_i18n = self.i18n.get('tabs', {}).get('paths', {})
1079
+ self.paths_tab = ttk.Frame(self.notebook, padding=15)
1080
+ self.notebook.add(self.paths_tab, text=paths_i18n.get('title', 'Paths'))
1081
+ else:
1082
+ for widget in self.paths_tab.winfo_children():
1083
+ widget.destroy()
1084
+
1085
+ paths_frame = self.paths_tab
1086
+ paths_i18n = self.i18n.get('tabs', {}).get('paths', {})
1087
+ browse_text = self.i18n.get('buttons', {}).get('browse', 'Browse')
1088
+
1089
+ watch_i18n = paths_i18n.get('folder_to_watch', {})
1090
+ HoverInfoLabelWidget(paths_frame, text=watch_i18n.get('label', '...'), tooltip=watch_i18n.get('tooltip', '...'),
1091
+ foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1092
+ folder_watch_entry = ttk.Entry(paths_frame, width=50, textvariable=self.folder_to_watch_value)
1093
+ folder_watch_entry.grid(row=self.current_row, column=1, sticky='W', pady=2)
1094
+ ttk.Button(paths_frame, text=browse_text, command=lambda: self.browse_folder(folder_watch_entry),
1095
+ bootstyle="outline").grid(row=self.current_row, column=2, padx=5, pady=2)
1096
+ self.current_row += 1
1097
+
1098
+ audio_dest_i18n = paths_i18n.get('audio_destination', {})
1099
+ HoverInfoLabelWidget(paths_frame, text=audio_dest_i18n.get('label', '...'), tooltip=audio_dest_i18n.get('tooltip', '...'),
1100
+ foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1101
+ audio_dest_entry = ttk.Entry(paths_frame, width=50, textvariable=self.audio_destination_value)
1102
+ audio_dest_entry.grid(row=self.current_row, column=1, sticky='W', pady=2)
1103
+ ttk.Button(paths_frame, text=browse_text, command=lambda: self.browse_folder(audio_dest_entry),
1104
+ bootstyle="outline").grid(row=self.current_row, column=2, padx=5, pady=2)
1105
+ self.current_row += 1
1106
+
1107
+ ss_dest_i18n = paths_i18n.get('screenshot_destination', {})
1108
+ HoverInfoLabelWidget(paths_frame, text=ss_dest_i18n.get('label', '...'),
1109
+ tooltip=ss_dest_i18n.get('tooltip', '...'), foreground="dark orange",
1110
+ font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1111
+ ss_dest_entry = ttk.Entry(paths_frame, width=50, textvariable=self.screenshot_destination_value)
1112
+ ss_dest_entry.grid(row=self.current_row, column=1, sticky='W', pady=2)
1113
+ ttk.Button(paths_frame, text=browse_text, command=lambda: self.browse_folder(ss_dest_entry),
1114
+ bootstyle="outline").grid(row=self.current_row, column=2, padx=5, pady=2)
1115
+ self.current_row += 1
1116
+
1117
+ rm_vid_i18n = paths_i18n.get('remove_video', {})
1118
+ HoverInfoLabelWidget(paths_frame, text=rm_vid_i18n.get('label', '...'), tooltip=rm_vid_i18n.get('tooltip', '...'),
1119
+ row=self.current_row, column=0)
1120
+ ttk.Checkbutton(paths_frame, variable=self.remove_video_value, bootstyle="round-toggle").grid(row=self.current_row,
1121
+ column=1, sticky='W', pady=2)
1122
+ self.current_row += 1
1123
+
1124
+ rm_audio_i18n = paths_i18n.get('remove_audio', {})
1125
+ HoverInfoLabelWidget(paths_frame, text=rm_audio_i18n.get('label', '...'), tooltip=rm_audio_i18n.get('tooltip', '...'),
1126
+ row=self.current_row, column=0)
1127
+ ttk.Checkbutton(paths_frame, variable=self.remove_audio_value, bootstyle="round-toggle").grid(row=self.current_row,
1128
+ column=1, sticky='W', pady=2)
1129
+ self.current_row += 1
1130
+
1131
+ rm_ss_i18n = paths_i18n.get('remove_screenshot', {})
1132
+ HoverInfoLabelWidget(paths_frame, text=rm_ss_i18n.get('label', '...'), tooltip=rm_ss_i18n.get('tooltip', '...'),
1133
+ row=self.current_row, column=0)
1134
+ ttk.Checkbutton(paths_frame, variable=self.remove_screenshot_value, bootstyle="round-toggle").grid(
1135
+ row=self.current_row, column=1, sticky='W', pady=2)
1136
+ self.current_row += 1
1137
+
1138
+ self.add_reset_button(paths_frame, "paths", self.current_row, 0, self.create_paths_tab)
1139
+
1140
+ for col in range(3): paths_frame.grid_columnconfigure(col, weight=0)
1141
+ for row in range(self.current_row): paths_frame.grid_rowconfigure(row, minsize=30)
1142
+
1143
+ return paths_frame
1144
+
1145
+ def browse_file(self, entry_widget):
1146
+ file_selected = filedialog.askopenfilename()
1147
+ if file_selected:
1148
+ # The entry widget's textvariable will be updated automatically
1149
+ entry_widget.delete(0, tk.END)
1150
+ entry_widget.insert(0, file_selected)
1151
+
1152
+ def browse_folder(self, entry_widget):
1153
+ folder_selected = filedialog.askdirectory()
1154
+ if folder_selected:
1155
+ # The entry widget's textvariable will be updated automatically
1156
+ entry_widget.delete(0, tk.END)
1157
+ entry_widget.insert(0, folder_selected)
1158
+
1159
+ @new_tab
1160
+ def create_anki_tab(self):
1161
+ if self.anki_tab is None:
1162
+ anki_i18n = self.i18n.get('tabs', {}).get('anki', {})
1163
+ self.anki_tab = ttk.Frame(self.notebook, padding=15)
1164
+ self.notebook.add(self.anki_tab, text=anki_i18n.get('title', 'Anki'))
1165
+ else:
1166
+ for widget in self.anki_tab.winfo_children():
1167
+ widget.destroy()
1168
+
1169
+ anki_frame = self.anki_tab
1170
+ anki_i18n = self.i18n.get('tabs', {}).get('anki', {})
1171
+
1172
+ update_i18n = anki_i18n.get('update_anki', {})
1173
+ HoverInfoLabelWidget(anki_frame, text=update_i18n.get('label', '...'), tooltip=update_i18n.get('tooltip', '...'),
1174
+ row=self.current_row, column=0)
1175
+ ttk.Checkbutton(anki_frame, variable=self.update_anki_value, bootstyle="round-toggle").grid(row=self.current_row,
1176
+ column=1, sticky='W', pady=2)
1177
+ self.current_row += 1
1178
+
1179
+ url_i18n = anki_i18n.get('url', {})
1180
+ HoverInfoLabelWidget(anki_frame, text=url_i18n.get('label', '...'), tooltip=url_i18n.get('tooltip', '...'),
1181
+ foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1182
+ ttk.Entry(anki_frame, width=50, textvariable=self.anki_url_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1183
+ self.current_row += 1
1184
+
1185
+ sentence_i18n = anki_i18n.get('sentence_field', {})
1186
+ HoverInfoLabelWidget(anki_frame, text=sentence_i18n.get('label', '...'), tooltip=sentence_i18n.get('tooltip', '...'),
1187
+ foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1188
+ ttk.Entry(anki_frame, textvariable=self.sentence_field_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1189
+ self.current_row += 1
1190
+
1191
+ audio_i18n = anki_i18n.get('sentence_audio_field', {})
1192
+ HoverInfoLabelWidget(anki_frame, text=audio_i18n.get('label', '...'),
1193
+ tooltip=audio_i18n.get('tooltip', '...'),
1194
+ foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1195
+ ttk.Entry(anki_frame, textvariable=self.sentence_audio_field_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1196
+ self.current_row += 1
1197
+
1198
+ pic_i18n = anki_i18n.get('picture_field', {})
1199
+ HoverInfoLabelWidget(anki_frame, text=pic_i18n.get('label', '...'), tooltip=pic_i18n.get('tooltip', '...'),
1200
+ foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1201
+ ttk.Entry(anki_frame, textvariable=self.picture_field_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1202
+ self.current_row += 1
1203
+
1204
+ word_i18n = anki_i18n.get('word_field', {})
1205
+ HoverInfoLabelWidget(anki_frame, text=word_i18n.get('label', '...'), tooltip=word_i18n.get('tooltip', '...'),
1206
+ foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1207
+ ttk.Entry(anki_frame, textvariable=self.word_field_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1208
+ self.current_row += 1
1209
+
1210
+ prev_sent_i18n = anki_i18n.get('previous_sentence_field', {})
1211
+ HoverInfoLabelWidget(anki_frame, text=prev_sent_i18n.get('label', '...'),
1212
+ tooltip=prev_sent_i18n.get('tooltip', '...'),
1213
+ row=self.current_row, column=0)
1214
+ ttk.Entry(anki_frame, textvariable=self.previous_sentence_field_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1215
+ self.current_row += 1
1216
+
1217
+ prev_img_i18n = anki_i18n.get('previous_image_field', {})
1218
+ HoverInfoLabelWidget(anki_frame, text=prev_img_i18n.get('label', '...'),
1219
+ tooltip=prev_img_i18n.get('tooltip', '...'),
1220
+ row=self.current_row, column=0)
1221
+ ttk.Entry(anki_frame, textvariable=self.previous_image_field_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1222
+ self.current_row += 1
1223
+
1224
+ tags_i18n = anki_i18n.get('custom_tags', {})
1225
+ HoverInfoLabelWidget(anki_frame, text=tags_i18n.get('label', '...'), tooltip=tags_i18n.get('tooltip', '...'),
1226
+ row=self.current_row, column=0)
1227
+ ttk.Entry(anki_frame, width=50, textvariable=self.custom_tags_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1228
+ self.current_row += 1
1229
+
1230
+ tags_check_i18n = anki_i18n.get('tags_to_check', {})
1231
+ HoverInfoLabelWidget(anki_frame, text=tags_check_i18n.get('label', '...'),
1232
+ tooltip=tags_check_i18n.get('tooltip', '...'),
1233
+ foreground="green", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1234
+ ttk.Entry(anki_frame, width=50, textvariable=self.tags_to_check_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1235
+ self.current_row += 1
1236
+
1237
+ game_tag_i18n = anki_i18n.get('add_game_tag', {})
1238
+ HoverInfoLabelWidget(anki_frame, text=game_tag_i18n.get('label', '...'),
1239
+ tooltip=game_tag_i18n.get('tooltip', '...'), foreground="green",
1240
+ font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1241
+ ttk.Checkbutton(anki_frame, variable=self.add_game_tag_value, bootstyle="round-toggle").grid(row=self.current_row,
1242
+ column=1, sticky='W', pady=2)
1243
+ self.current_row += 1
1244
+
1245
+ parent_tag_i18n = anki_i18n.get('parent_tag', {})
1246
+ HoverInfoLabelWidget(anki_frame, text=parent_tag_i18n.get('label', '...'),
1247
+ foreground="green", font=("Helvetica", 10, "bold"),
1248
+ tooltip=parent_tag_i18n.get('tooltip', '...'),
1249
+ row=self.current_row, column=0)
1250
+ ttk.Entry(anki_frame, width=50, textvariable=self.parent_tag_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1251
+ self.current_row += 1
1252
+
1253
+ ow_audio_i18n = anki_i18n.get('overwrite_audio', {})
1254
+ HoverInfoLabelWidget(anki_frame, text=ow_audio_i18n.get('label', '...'), tooltip=ow_audio_i18n.get('tooltip', '...'),
1255
+ row=self.current_row, column=0)
1256
+ ttk.Checkbutton(anki_frame, variable=self.overwrite_audio_value, bootstyle="round-toggle").grid(row=self.current_row,
1257
+ column=1, sticky='W', pady=2)
1258
+ self.current_row += 1
1259
+
1260
+ ow_pic_i18n = anki_i18n.get('overwrite_picture', {})
1261
+ HoverInfoLabelWidget(anki_frame, text=ow_pic_i18n.get('label', '...'),
1262
+ tooltip=ow_pic_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1263
+ ttk.Checkbutton(anki_frame, variable=self.overwrite_picture_value, bootstyle="round-toggle").grid(
1264
+ row=self.current_row, column=1, sticky='W', pady=2)
1265
+ self.current_row += 1
1266
+
1267
+ multi_ow_i18n = anki_i18n.get('multi_overwrites_sentence', {})
1268
+ HoverInfoLabelWidget(anki_frame, text=multi_ow_i18n.get('label', '...'),
1269
+ tooltip=multi_ow_i18n.get('tooltip', '...'),
1270
+ row=self.current_row, column=0)
1271
+ ttk.Checkbutton(anki_frame, variable=self.multi_overwrites_sentence_value, bootstyle="round-toggle").grid(
1272
+ row=self.current_row, column=1, sticky='W', pady=2)
1273
+ self.current_row += 1
1274
+
1275
+ self.add_reset_button(anki_frame, "anki", self.current_row, 0, self.create_anki_tab)
1276
+
1277
+ for col in range(2): anki_frame.grid_columnconfigure(col, weight=0)
1278
+ for row in range(self.current_row): anki_frame.grid_rowconfigure(row, minsize=30)
1279
+
1280
+ return anki_frame
1281
+
1282
+ def on_profiles_tab_selected(self, event):
1283
+ try:
1284
+ profiles_i18n = self.i18n.get('tabs', {}).get('profiles', {})
1285
+ if self.window.state() != "withdrawn" and self.notebook.tab(self.notebook.select(), "text") == profiles_i18n.get('title', 'Profiles'):
1286
+ self.refresh_obs_scenes()
1287
+ except Exception as e:
1288
+ logger.debug(e)
1289
+
1290
+ @new_tab
1291
+ def create_features_tab(self):
1292
+ if self.features_tab is None:
1293
+ features_i18n = self.i18n.get('tabs', {}).get('features', {})
1294
+ self.features_tab = ttk.Frame(self.notebook, padding=15)
1295
+ self.notebook.add(self.features_tab, text=features_i18n.get('title', 'Features'))
1296
+ else:
1297
+ for widget in self.features_tab.winfo_children():
1298
+ widget.destroy()
1299
+
1300
+ features_frame = self.features_tab
1301
+ features_i18n = self.i18n.get('tabs', {}).get('features', {})
1302
+
1303
+ notify_i18n = features_i18n.get('notify_on_update', {})
1304
+ HoverInfoLabelWidget(features_frame, text=notify_i18n.get('label', '...'), tooltip=notify_i18n.get('tooltip', '...'),
1305
+ row=self.current_row, column=0)
1306
+ ttk.Checkbutton(features_frame, variable=self.notify_on_update_value, bootstyle="round-toggle").grid(
1307
+ row=self.current_row, column=1, sticky='W', pady=2)
1308
+ self.current_row += 1
1309
+
1310
+ open_edit_i18n = features_i18n.get('open_anki_edit', {})
1311
+ HoverInfoLabelWidget(features_frame, text=open_edit_i18n.get('label', '...'),
1312
+ tooltip=open_edit_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1313
+ ttk.Checkbutton(features_frame, variable=self.open_anki_edit_value, bootstyle="round-toggle").grid(
1314
+ row=self.current_row, column=1, sticky='W', pady=2)
1315
+ self.current_row += 1
1316
+
1317
+ open_browser_i18n = features_i18n.get('open_anki_browser', {})
1318
+ HoverInfoLabelWidget(features_frame, text=open_browser_i18n.get('label', '...'),
1319
+ tooltip=open_browser_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1320
+ ttk.Checkbutton(features_frame, variable=self.open_anki_browser_value, bootstyle="round-toggle").grid(
1321
+ row=self.current_row, column=1, sticky='W', pady=2)
1322
+ self.current_row += 1
1323
+
1324
+ query_i18n = features_i18n.get('browser_query', {})
1325
+ HoverInfoLabelWidget(features_frame, text=query_i18n.get('label', '...'),
1326
+ tooltip=query_i18n.get('tooltip', '...'),
1327
+ row=self.current_row, column=0)
1328
+ ttk.Entry(features_frame, width=50, textvariable=self.browser_query_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1329
+ self.current_row += 1
1330
+
1331
+ backfill_i18n = features_i18n.get('backfill_audio', {})
1332
+ HoverInfoLabelWidget(features_frame, text=backfill_i18n.get('label', '...'), tooltip=backfill_i18n.get('tooltip', '...'),
1333
+ row=self.current_row, column=0)
1334
+ ttk.Checkbutton(features_frame, variable=self.backfill_audio_value, bootstyle="round-toggle").grid(
1335
+ row=self.current_row, column=1, sticky='W', pady=2)
1336
+ self.current_row += 1
1337
+
1338
+ full_auto_i18n = features_i18n.get('full_auto', {})
1339
+ HoverInfoLabelWidget(features_frame, text=full_auto_i18n.get('label', '...'), tooltip=full_auto_i18n.get('tooltip', '...'),
1340
+ row=self.current_row, column=0)
1341
+ ttk.Checkbutton(features_frame, variable=self.full_auto_value, bootstyle="round-toggle").grid(row=self.current_row,
1342
+ column=1, sticky='W', pady=2)
1343
+ self.current_row += 1
1344
+
1345
+ self.add_reset_button(features_frame, "features", self.current_row, 0, self.create_features_tab)
1346
+
1347
+ for col in range(3): features_frame.grid_columnconfigure(col, weight=0)
1348
+ for row in range(self.current_row): features_frame.grid_rowconfigure(row, minsize=30)
1349
+
1350
+ return features_frame
1351
+
1352
+ @new_tab
1353
+ def create_screenshot_tab(self):
1354
+ if self.screenshot_tab is None:
1355
+ ss_i18n = self.i18n.get('tabs', {}).get('screenshot', {})
1356
+ self.screenshot_tab = ttk.Frame(self.notebook, padding=15)
1357
+ self.notebook.add(self.screenshot_tab, text=ss_i18n.get('title', 'Screenshot'))
1358
+ else:
1359
+ for widget in self.screenshot_tab.winfo_children():
1360
+ widget.destroy()
1361
+
1362
+ screenshot_frame = self.screenshot_tab
1363
+ ss_i18n = self.i18n.get('tabs', {}).get('screenshot', {})
1364
+
1365
+ enabled_i18n = ss_i18n.get('enabled', {})
1366
+ HoverInfoLabelWidget(screenshot_frame, text=enabled_i18n.get('label', '...'), tooltip=enabled_i18n.get('tooltip', '...'),
1367
+ row=self.current_row, column=0)
1368
+ ttk.Checkbutton(screenshot_frame, variable=self.screenshot_enabled_value, bootstyle="round-toggle").grid(
1369
+ row=self.current_row, column=1, sticky='W', pady=2)
1370
+ self.current_row += 1
1371
+
1372
+ width_i18n = ss_i18n.get('width', {})
1373
+ HoverInfoLabelWidget(screenshot_frame, text=width_i18n.get('label', '...'), tooltip=width_i18n.get('tooltip', '...'),
1374
+ row=self.current_row, column=0)
1375
+ ttk.Entry(screenshot_frame, textvariable=self.screenshot_width_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1376
+ self.current_row += 1
1377
+
1378
+ height_i18n = ss_i18n.get('height', {})
1379
+ HoverInfoLabelWidget(screenshot_frame, text=height_i18n.get('label', '...'), tooltip=height_i18n.get('tooltip', '...'),
1380
+ row=self.current_row, column=0)
1381
+ ttk.Entry(screenshot_frame, textvariable=self.screenshot_height_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1382
+ self.current_row += 1
1383
+
1384
+ quality_i18n = ss_i18n.get('quality', {})
1385
+ HoverInfoLabelWidget(screenshot_frame, text=quality_i18n.get('label', '...'), tooltip=quality_i18n.get('tooltip', '...'),
1386
+ row=self.current_row, column=0)
1387
+ ttk.Entry(screenshot_frame, textvariable=self.screenshot_quality_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1388
+ self.current_row += 1
1389
+
1390
+ ext_i18n = ss_i18n.get('extension', {})
1391
+ HoverInfoLabelWidget(screenshot_frame, text=ext_i18n.get('label', '...'), tooltip=ext_i18n.get('tooltip', '...'),
1392
+ row=self.current_row, column=0)
1393
+ ttk.Combobox(screenshot_frame, textvariable=self.screenshot_extension_value, values=['webp', 'avif', 'png', 'jpeg'],
1394
+ state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
1395
+ self.current_row += 1
1396
+
1397
+ ffmpeg_i18n = ss_i18n.get('ffmpeg_options', {})
1398
+ HoverInfoLabelWidget(screenshot_frame, text=ffmpeg_i18n.get('label', '...'),
1399
+ tooltip=ffmpeg_i18n.get('tooltip', '...'), foreground="red",
1400
+ font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1401
+ ttk.Entry(screenshot_frame, width=50, textvariable=self.screenshot_custom_ffmpeg_settings_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1402
+ self.current_row += 1
1403
+
1404
+ timing_i18n = ss_i18n.get('timing', {})
1405
+ HoverInfoLabelWidget(screenshot_frame, text=timing_i18n.get('label', '...'),
1406
+ tooltip=timing_i18n.get('tooltip', '...'),
1407
+ row=self.current_row, column=0)
1408
+ ttk.Combobox(screenshot_frame, textvariable=self.screenshot_timing_value, values=['beginning', 'middle', 'end'], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
1409
+ self.current_row += 1
1410
+
1411
+ offset_i18n = ss_i18n.get('offset', {})
1412
+ HoverInfoLabelWidget(screenshot_frame, text=offset_i18n.get('label', '...'),
1413
+ tooltip=offset_i18n.get('tooltip', '...'),
1414
+ foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1415
+ ttk.Entry(screenshot_frame, textvariable=self.seconds_after_line_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1416
+ self.current_row += 1
1417
+
1418
+ selector_i18n = ss_i18n.get('use_selector', {})
1419
+ HoverInfoLabelWidget(screenshot_frame, text=selector_i18n.get('label', '...'),
1420
+ tooltip=selector_i18n.get('tooltip', '...'),
1421
+ row=self.current_row, column=0)
1422
+ ttk.Checkbutton(screenshot_frame, variable=self.use_screenshot_selector_value, bootstyle="round-toggle").grid(
1423
+ row=self.current_row, column=1, sticky='W', pady=2)
1424
+ self.current_row += 1
1425
+
1426
+ hotkey_i18n = ss_i18n.get('hotkey', {})
1427
+ HoverInfoLabelWidget(screenshot_frame, text=hotkey_i18n.get('label', '...'), tooltip=hotkey_i18n.get('tooltip', '...'),
1428
+ row=self.current_row, column=0)
1429
+ ttk.Entry(screenshot_frame, textvariable=self.take_screenshot_hotkey_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1430
+ self.current_row += 1
1431
+
1432
+ hotkey_update_i18n = ss_i18n.get('hotkey_updates_anki', {})
1433
+ HoverInfoLabelWidget(screenshot_frame, text=hotkey_update_i18n.get('label', '...'),
1434
+ tooltip=hotkey_update_i18n.get('tooltip', '...'),
1435
+ row=self.current_row, column=0)
1436
+ ttk.Checkbutton(screenshot_frame, variable=self.screenshot_hotkey_update_anki_value, bootstyle="round-toggle").grid(
1437
+ row=self.current_row, column=1, sticky='W', pady=2)
1438
+ self.current_row += 1
1439
+
1440
+ self.add_reset_button(screenshot_frame, "screenshot", self.current_row, 0, self.create_screenshot_tab)
1441
+
1442
+ for col in range(3): screenshot_frame.grid_columnconfigure(col, weight=0)
1443
+ for row in range(self.current_row): screenshot_frame.grid_rowconfigure(row, minsize=30)
1444
+
1445
+ return screenshot_frame
1446
+
1447
+ def update_audio_ffmpeg_settings(self, event):
1448
+ selected_option = self.ffmpeg_audio_preset_options.get()
1449
+ if selected_option in self.ffmpeg_audio_preset_options_map:
1450
+ self.audio_ffmpeg_reencode_options_value.set(self.ffmpeg_audio_preset_options_map[selected_option])
1451
+ else:
1452
+ self.audio_ffmpeg_reencode_options_value.set("")
1453
+
1454
+ @new_tab
1455
+ def create_audio_tab(self):
1456
+ if self.audio_tab is None:
1457
+ audio_i18n = self.i18n.get('tabs', {}).get('audio', {})
1458
+ self.audio_tab = ttk.Frame(self.notebook, padding=15)
1459
+ self.notebook.add(self.audio_tab, text=audio_i18n.get('title', 'Audio'))
1460
+ else:
1461
+ for widget in self.audio_tab.winfo_children():
1462
+ widget.destroy()
1463
+
1464
+ audio_frame = self.audio_tab
1465
+ audio_i18n = self.i18n.get('tabs', {}).get('audio', {})
1466
+
1467
+ enabled_i18n = audio_i18n.get('enabled', {})
1468
+ HoverInfoLabelWidget(audio_frame, text=enabled_i18n.get('label', '...'), tooltip=enabled_i18n.get('tooltip', '...'),
1469
+ row=self.current_row, column=0)
1470
+ ttk.Checkbutton(audio_frame, variable=self.audio_enabled_value, bootstyle="round-toggle").grid(row=self.current_row,
1471
+ column=1, sticky='W', pady=2)
1472
+ self.current_row += 1
1473
+
1474
+ ext_i18n = audio_i18n.get('extension', {})
1475
+ HoverInfoLabelWidget(audio_frame, text=ext_i18n.get('label', '...'), tooltip=ext_i18n.get('tooltip', '...'),
1476
+ row=self.current_row, column=0)
1477
+ ttk.Combobox(audio_frame, textvariable=self.audio_extension_value, values=['opus', 'mp3', 'ogg', 'aac', 'm4a'], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
1478
+ self.current_row += 1
1479
+
1480
+ begin_offset_i18n = audio_i18n.get('beginning_offset', {})
1481
+ HoverInfoLabelWidget(audio_frame, text=begin_offset_i18n.get('label', '...'),
1482
+ tooltip=begin_offset_i18n.get('tooltip', '...'),
1483
+ foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1484
+ ttk.Entry(audio_frame, textvariable=self.beginning_offset_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1485
+
1486
+ ttk.Button(audio_frame, text=audio_i18n.get('find_offset_button', 'Find Offset (WIP)'), command=self.call_audio_offset_selector,
1487
+ bootstyle="info").grid(row=self.current_row, column=2, sticky='EW', pady=2, padx=5)
1488
+ self.current_row += 1
1489
+
1490
+ end_offset_i18n = audio_i18n.get('end_offset', {})
1491
+ HoverInfoLabelWidget(audio_frame, text=end_offset_i18n.get('label', '...'),
1492
+ tooltip=end_offset_i18n.get('tooltip', '...'),
1493
+ foreground="red", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1494
+ ttk.Entry(audio_frame, textvariable=self.pre_vad_audio_offset_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1495
+ self.current_row += 1
1496
+
1497
+ ffmpeg_preset_i18n = audio_i18n.get('ffmpeg_preset', {})
1498
+ HoverInfoLabelWidget(audio_frame, text=ffmpeg_preset_i18n.get('label', '...'),
1499
+ tooltip=ffmpeg_preset_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1500
+
1501
+ preset_options_i18n = ffmpeg_preset_i18n.get('options', {})
1502
+ self.ffmpeg_audio_preset_options_map = {
1503
+ preset_options_i18n.get('no_reencode', "No Re-encode"): "",
1504
+ preset_options_i18n.get('fade_in', "Simple Fade-in..."): "-c:a {encoder} -f {format} -af \"afade=t=in:d=0.10\"",
1505
+ preset_options_i18n.get('loudness_norm', "Simple loudness..."): "-c:a {encoder} -f {format} -af \"loudnorm=I=-23:TP=-2,afade=t=in:d=0.10\"",
1506
+ preset_options_i18n.get('downmix_norm', "Downmix to mono..."): "-c:a {encoder} -ac 1 -f {format} -af \"loudnorm=I=-23:TP=-2:dual_mono=true,afade=t=in:d=0.10\"",
1507
+ preset_options_i18n.get('downmix_norm_low_bitrate', "Downmix to mono, 30kbps..."): "-c:a {encoder} -b:a 30k -ac 1 -f {format} -af \"loudnorm=I=-23:TP=-2:dual_mono=true,afade=t=in:d=0.10\"",
1508
+ preset_options_i18n.get('custom', "Custom"): get_config().audio.custom_encode_settings,
1509
+ }
1510
+
1511
+ self.ffmpeg_audio_preset_options = ttk.Combobox(audio_frame,
1512
+ values=list(self.ffmpeg_audio_preset_options_map.keys()),
1513
+ width=50, state="readonly")
1514
+ self.ffmpeg_audio_preset_options.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1515
+ self.ffmpeg_audio_preset_options.bind("<<ComboboxSelected>>", self.update_audio_ffmpeg_settings)
1516
+ self.current_row += 1
1517
+
1518
+ ffmpeg_options_i18n = audio_i18n.get('ffmpeg_options', {})
1519
+ HoverInfoLabelWidget(audio_frame, text=ffmpeg_options_i18n.get('label', '...'),
1520
+ tooltip=ffmpeg_options_i18n.get('tooltip', '...'), foreground="red",
1521
+ font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1522
+ ttk.Entry(audio_frame, width=50, textvariable=self.audio_ffmpeg_reencode_options_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1523
+ self.current_row += 1
1524
+
1525
+ anki_media_i18n = audio_i18n.get('anki_media_collection', {})
1526
+ HoverInfoLabelWidget(audio_frame, text=anki_media_i18n.get('label', '...'),
1527
+ tooltip=anki_media_i18n.get('tooltip', '...'),
1528
+ foreground="green", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1529
+ self.anki_media_collection_entry = ttk.Entry(audio_frame, textvariable=self.anki_media_collection_value)
1530
+ self.anki_media_collection_entry.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1531
+ self.current_row += 1
1532
+
1533
+ ext_tool_i18n = audio_i18n.get('external_tool', {})
1534
+ HoverInfoLabelWidget(audio_frame, text=ext_tool_i18n.get('label', '...'),
1535
+ tooltip=ext_tool_i18n.get('tooltip', '...'),
1536
+ foreground="green", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1537
+ self.external_tool_entry = ttk.Entry(audio_frame, textvariable=self.external_tool_value)
1538
+ self.external_tool_entry.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1539
+
1540
+ ext_tool_enabled_i18n = audio_i18n.get('external_tool_enabled', {})
1541
+ HoverInfoLabelWidget(audio_frame, text=ext_tool_enabled_i18n.get('label', '...'), tooltip=ext_tool_enabled_i18n.get('tooltip', '...'),
1542
+ row=self.current_row, column=2, foreground="green", font=("Helvetica", 10, "bold"))
1543
+ ttk.Checkbutton(audio_frame, variable=self.external_tool_enabled_value, bootstyle="round-toggle").grid(
1544
+ row=self.current_row, column=3, sticky='W', padx=10, pady=5)
1545
+ self.current_row += 1
1546
+
1547
+ ttk.Button(audio_frame, text=audio_i18n.get('install_ocenaudio_button', 'Install Ocenaudio'), command=self.download_and_install_ocen,
1548
+ bootstyle="info").grid(row=self.current_row, column=0, pady=5)
1549
+ ttk.Button(audio_frame, text=audio_i18n.get('get_anki_media_button', 'Get Anki Media Collection'),
1550
+ command=self.set_default_anki_media_collection, bootstyle="info").grid(row=self.current_row,
1551
+ column=1, pady=5)
1552
+ self.current_row += 1
1553
+
1554
+ self.add_reset_button(audio_frame, "audio", self.current_row, 0, self.create_audio_tab)
1555
+
1556
+ for col in range(5): audio_frame.grid_columnconfigure(col, weight=0)
1557
+ for row in range(self.current_row): audio_frame.grid_rowconfigure(row, minsize=30)
1558
+
1559
+ return audio_frame
1560
+
1561
+ def call_audio_offset_selector(self):
1562
+ try:
1563
+ path, beginning_offset, end_offset = gsm_state.previous_trim_args
1564
+ current_dir = os.path.dirname(os.path.abspath(__file__))
1565
+ script_path = os.path.join(current_dir, "audio_offset_selector.py")
1566
+
1567
+ logger.info(' '.join([sys.executable, "-m", "GameSentenceMiner.util.audio_offset_selector",
1568
+ "--path", path, "--beginning_offset", str(beginning_offset), "--end_offset", str(end_offset)]))
1569
+
1570
+ result = subprocess.run(
1571
+ [sys.executable, "-m", "GameSentenceMiner.util.audio_offset_selector",
1572
+ "--path", path, "--beginning_offset", str(beginning_offset), "--end_offset", str(end_offset)],
1573
+ capture_output=True, text=True, check=False
1574
+ )
1575
+ if result.returncode != 0:
1576
+ logger.error(f"Script failed with return code: {result.returncode}")
1577
+ return None
1578
+
1579
+ logger.info(result)
1580
+ logger.info(f"Audio offset selector script output: {result.stdout.strip()}")
1581
+ pyperclip.copy(result.stdout.strip())
1582
+
1583
+ dialog_i18n = self.i18n.get('dialogs', {}).get('offset_copied', {})
1584
+ messagebox.showinfo(dialog_i18n.get('title', 'Clipboard'), dialog_i18n.get('message', 'Offset copied!'))
1585
+ return result.stdout.strip()
1586
+
1587
+ except subprocess.CalledProcessError as e:
1588
+ logger.error(f"Error calling script: {e}\nStderr: {e.stderr.strip()}")
1589
+ return None
1590
+ except FileNotFoundError:
1591
+ logger.error(f"Error: Script not found at {script_path}.")
1592
+ return None
1593
+ except Exception as e:
1594
+ logger.error(f"An unexpected error occurred: {e}")
1595
+ return None
1596
+
1597
+ @new_tab
1598
+ def create_obs_tab(self):
1599
+ if self.obs_tab is None:
1600
+ obs_i18n = self.i18n.get('tabs', {}).get('obs', {})
1601
+ self.obs_tab = ttk.Frame(self.notebook, padding=15)
1602
+ self.notebook.add(self.obs_tab, text=obs_i18n.get('title', 'OBS'))
1603
+ else:
1604
+ for widget in self.obs_tab.winfo_children():
1605
+ widget.destroy()
1606
+
1607
+ obs_frame = self.obs_tab
1608
+ obs_i18n = self.i18n.get('tabs', {}).get('obs', {})
1609
+
1610
+ enabled_i18n = obs_i18n.get('enabled', {})
1611
+ HoverInfoLabelWidget(obs_frame, text=enabled_i18n.get('label', '...'), tooltip=enabled_i18n.get('tooltip', '...'),
1612
+ row=self.current_row, column=0)
1613
+ ttk.Checkbutton(obs_frame, variable=self.obs_enabled_value, bootstyle="round-toggle").grid(row=self.current_row,
1614
+ column=1, sticky='W', pady=2)
1615
+ self.current_row += 1
1616
+
1617
+ open_i18n = obs_i18n.get('open_obs', {})
1618
+ HoverInfoLabelWidget(obs_frame, text=open_i18n.get('label', '...'), tooltip=open_i18n.get('tooltip', '...'), row=self.current_row,
1619
+ column=0)
1620
+ ttk.Checkbutton(obs_frame, variable=self.obs_open_obs_value, bootstyle="round-toggle").grid(row=self.current_row,
1621
+ column=1, sticky='W', pady=2)
1622
+ self.current_row += 1
1623
+
1624
+ close_i18n = obs_i18n.get('close_obs', {})
1625
+ HoverInfoLabelWidget(obs_frame, text=close_i18n.get('label', '...'), tooltip=close_i18n.get('tooltip', '...'),
1626
+ row=self.current_row, column=0)
1627
+ ttk.Checkbutton(obs_frame, variable=self.obs_close_obs_value, bootstyle="round-toggle").grid(row=self.current_row,
1628
+ column=1, sticky='W', pady=2)
1629
+ self.current_row += 1
1630
+
1631
+ host_i18n = obs_i18n.get('host', {})
1632
+ HoverInfoLabelWidget(obs_frame, text=host_i18n.get('label', '...'), tooltip=host_i18n.get('tooltip', '...'),
1633
+ row=self.current_row, column=0)
1634
+ ttk.Entry(obs_frame, textvariable=self.obs_host_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1635
+ self.current_row += 1
1636
+
1637
+ port_i18n = obs_i18n.get('port', {})
1638
+ HoverInfoLabelWidget(obs_frame, text=port_i18n.get('label', '...'), tooltip=port_i18n.get('tooltip', '...'),
1639
+ row=self.current_row, column=0)
1640
+ ttk.Entry(obs_frame, textvariable=self.obs_port_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1641
+ self.current_row += 1
1642
+
1643
+ pass_i18n = obs_i18n.get('password', {})
1644
+ HoverInfoLabelWidget(obs_frame, text=pass_i18n.get('label', '...'), tooltip=pass_i18n.get('tooltip', '...'),
1645
+ row=self.current_row, column=0)
1646
+ ttk.Entry(obs_frame, show="*", textvariable=self.obs_password_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1647
+ self.current_row += 1
1648
+
1649
+ game_scene_i18n = obs_i18n.get('game_from_scene', {})
1650
+ HoverInfoLabelWidget(obs_frame, text=game_scene_i18n.get('label', '...'), tooltip=game_scene_i18n.get('tooltip', '...'),
1651
+ row=self.current_row, column=0)
1652
+ ttk.Checkbutton(obs_frame, variable=self.obs_get_game_from_scene_name_value, bootstyle="round-toggle").grid(
1653
+ row=self.current_row, column=1, sticky='W', pady=2)
1654
+ self.current_row += 1
1655
+
1656
+ min_size_i18n = obs_i18n.get('min_replay_size', {})
1657
+ HoverInfoLabelWidget(obs_frame, text=min_size_i18n.get('label', '...'),
1658
+ tooltip=min_size_i18n.get('tooltip', '...'),
1659
+ row=self.current_row, column=0)
1660
+ ttk.Entry(obs_frame, textvariable=self.obs_minimum_replay_size_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1661
+ self.current_row += 1
1662
+
1663
+ self.add_reset_button(obs_frame, "obs", self.current_row, 0, self.create_obs_tab)
1664
+
1665
+ for col in range(3): obs_frame.grid_columnconfigure(col, weight=0)
1666
+ for row in range(self.current_row): obs_frame.grid_rowconfigure(row, minsize=30)
1667
+
1668
+ return obs_frame
1669
+
1670
+ @new_tab
1671
+ def create_profiles_tab(self):
1672
+ if self.profiles_tab is None:
1673
+ profiles_i18n = self.i18n.get('tabs', {}).get('profiles', {})
1674
+ self.profiles_tab = ttk.Frame(self.notebook, padding=15)
1675
+ self.notebook.add(self.profiles_tab, text=profiles_i18n.get('title', 'Profiles'))
1676
+ else:
1677
+ for widget in self.profiles_tab.winfo_children():
1678
+ widget.destroy()
1679
+
1680
+ profiles_frame = self.profiles_tab
1681
+ profiles_i18n = self.i18n.get('tabs', {}).get('profiles', {})
1682
+
1683
+ select_i18n = profiles_i18n.get('select_profile', {})
1684
+ HoverInfoLabelWidget(profiles_frame, text=select_i18n.get('label', '...'), tooltip=select_i18n.get('tooltip', '...'),
1685
+ row=self.current_row, column=0)
1686
+ self.profile_var = tk.StringVar(value=self.settings.name)
1687
+ self.profile_combobox = ttk.Combobox(profiles_frame, textvariable=self.profile_var,
1688
+ values=list(self.master_config.configs.keys()), state="readonly")
1689
+ self.profile_combobox.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1690
+ self.profile_combobox.bind("<<ComboboxSelected>>", self.on_profile_change)
1691
+ self.current_row += 1
1692
+
1693
+ button_row = self.current_row
1694
+ ttk.Button(profiles_frame, text=profiles_i18n.get('add_button', 'Add Profile'), command=self.add_profile, bootstyle="primary").grid(
1695
+ row=button_row, column=0, pady=5)
1696
+ ttk.Button(profiles_frame, text=profiles_i18n.get('copy_button', 'Copy Profile'), command=self.copy_profile, bootstyle="secondary").grid(
1697
+ row=button_row, column=1, pady=5)
1698
+ self.delete_profile_button = ttk.Button(profiles_frame, text=profiles_i18n.get('delete_button', 'Delete Config'), command=self.delete_profile,
1699
+ bootstyle="danger")
1700
+ if self.master_config.current_profile != DEFAULT_CONFIG:
1701
+ self.delete_profile_button.grid(row=button_row, column=2, pady=5)
1702
+ else:
1703
+ self.delete_profile_button.grid_remove()
1704
+ self.current_row += 1
1705
+
1706
+ scene_i18n = profiles_i18n.get('obs_scene', {})
1707
+ HoverInfoLabelWidget(profiles_frame, text=scene_i18n.get('label', '...'),
1708
+ tooltip=scene_i18n.get('tooltip', '...'),
1709
+ row=self.current_row, column=0)
1710
+ self.obs_scene_var = tk.StringVar(value="")
1711
+ self.obs_scene_listbox = tk.Listbox(profiles_frame, listvariable=self.obs_scene_var, selectmode=tk.MULTIPLE,
1712
+ height=10, width=50, selectbackground=ttk.Style().colors.primary)
1713
+ self.obs_scene_listbox.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1714
+ self.obs_scene_listbox.bind("<<ListboxSelect>>", self.on_obs_scene_select)
1715
+ ttk.Button(profiles_frame, text=profiles_i18n.get('refresh_scenes_button', 'Refresh Scenes'), command=self.refresh_obs_scenes, bootstyle="outline").grid(
1716
+ row=self.current_row, column=2, pady=5)
1717
+ self.current_row += 1
1718
+
1719
+ switch_i18n = profiles_i18n.get('switch_to_default', {})
1720
+ HoverInfoLabelWidget(profiles_frame, text=switch_i18n.get('label', '...'),
1721
+ tooltip=switch_i18n.get('tooltip', '...'),
1722
+ row=self.current_row, column=0)
1723
+ ttk.Checkbutton(profiles_frame, variable=self.switch_to_default_if_not_found_value, bootstyle="round-toggle").grid(
1724
+ row=self.current_row, column=1, sticky='W', pady=2)
1725
+ self.current_row += 1
1726
+
1727
+ for col in range(4): profiles_frame.grid_columnconfigure(col, weight=0)
1728
+ for row in range(self.current_row): profiles_frame.grid_rowconfigure(row, minsize=30)
1729
+
1730
+ return profiles_frame
1731
+
1732
+ def on_obs_scene_select(self, event):
1733
+ self.settings.scenes = [self.obs_scene_listbox.get(i) for i in
1734
+ self.obs_scene_listbox.curselection()]
1735
+ self.obs_scene_listbox_changed = True
1736
+
1737
+ def refresh_obs_scenes(self):
1738
+ scenes = obs.get_obs_scenes()
1739
+ obs_scene_names = [scene['sceneName'] for scene in scenes]
1740
+ self.obs_scene_listbox.delete(0, tk.END)
1741
+ for scene_name in obs_scene_names:
1742
+ self.obs_scene_listbox.insert(tk.END, scene_name)
1743
+ for i, scene in enumerate(obs_scene_names):
1744
+ if scene.strip() in self.settings.scenes:
1745
+ self.obs_scene_listbox.select_set(i)
1746
+ self.obs_scene_listbox.activate(i)
1747
+ self.obs_scene_listbox.update_idletasks()
1748
+
1749
+ @new_tab
1750
+ def create_advanced_tab(self):
1751
+ if self.advanced_tab is None:
1752
+ advanced_i18n = self.i18n.get('tabs', {}).get('advanced', {})
1753
+ self.advanced_tab = ttk.Frame(self.notebook, padding=15)
1754
+ self.notebook.add(self.advanced_tab, text=advanced_i18n.get('title', 'Advanced'))
1755
+ else:
1756
+ for widget in self.advanced_tab.winfo_children():
1757
+ widget.destroy()
1758
+
1759
+ advanced_frame = self.advanced_tab
1760
+ advanced_i18n = self.i18n.get('tabs', {}).get('advanced', {})
1761
+ browse_text = self.i18n.get('buttons', {}).get('browse', 'Browse')
1762
+
1763
+ ttk.Label(advanced_frame, text=advanced_i18n.get('player_note', '...'),
1764
+ foreground="red", font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=3,
1765
+ sticky='W', pady=5)
1766
+ self.current_row += 1
1767
+
1768
+ audio_player_i18n = advanced_i18n.get('audio_player_path', {})
1769
+ HoverInfoLabelWidget(advanced_frame, text=audio_player_i18n.get('label', '...'),
1770
+ tooltip=audio_player_i18n.get('tooltip', '...'),
1771
+ row=self.current_row, column=0)
1772
+ audio_player_entry = ttk.Entry(advanced_frame, width=50, textvariable=self.audio_player_path_value)
1773
+ audio_player_entry.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1774
+ ttk.Button(advanced_frame, text=browse_text, command=lambda: self.browse_file(audio_player_entry),
1775
+ bootstyle="outline").grid(row=self.current_row, column=2, padx=5, pady=2)
1776
+ self.current_row += 1
1777
+
1778
+ video_player_i18n = advanced_i18n.get('video_player_path', {})
1779
+ HoverInfoLabelWidget(advanced_frame, text=video_player_i18n.get('label', '...'),
1780
+ tooltip=video_player_i18n.get('tooltip', '...'),
1781
+ row=self.current_row, column=0)
1782
+ video_player_entry = ttk.Entry(advanced_frame, width=50, textvariable=self.video_player_path_value)
1783
+ video_player_entry.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1784
+ ttk.Button(advanced_frame, text=browse_text, command=lambda: self.browse_file(video_player_entry),
1785
+ bootstyle="outline").grid(row=self.current_row, column=2, padx=5, pady=2)
1786
+ self.current_row += 1
1787
+
1788
+ play_hotkey_i18n = advanced_i18n.get('play_latest_hotkey', {})
1789
+ HoverInfoLabelWidget(advanced_frame, text=play_hotkey_i18n.get('label', '...'),
1790
+ tooltip=play_hotkey_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1791
+ ttk.Entry(advanced_frame, textvariable=self.play_latest_audio_hotkey_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1792
+ self.current_row += 1
1793
+
1794
+ linebreak_i18n = advanced_i18n.get('multiline_linebreak', {})
1795
+ HoverInfoLabelWidget(advanced_frame, text=linebreak_i18n.get('label', '...'),
1796
+ tooltip=linebreak_i18n.get('tooltip', '...'),
1797
+ row=self.current_row, column=0)
1798
+ ttk.Entry(advanced_frame, textvariable=self.multi_line_line_break_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1799
+ self.current_row += 1
1800
+
1801
+ storage_field_i18n = advanced_i18n.get('multiline_storage_field', {})
1802
+ HoverInfoLabelWidget(advanced_frame, text=storage_field_i18n.get('label', '...'),
1803
+ tooltip=storage_field_i18n.get('tooltip', '...'),
1804
+ row=self.current_row, column=0)
1805
+ ttk.Entry(advanced_frame, textvariable=self.multi_line_sentence_storage_field_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1806
+ self.current_row += 1
1807
+
1808
+ ocr_port_i18n = advanced_i18n.get('ocr_port', {})
1809
+ HoverInfoLabelWidget(advanced_frame, text=ocr_port_i18n.get('label', '...'),
1810
+ tooltip=ocr_port_i18n.get('tooltip', '...'),
1811
+ row=self.current_row, column=0)
1812
+ ttk.Entry(advanced_frame, textvariable=self.ocr_websocket_port_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1813
+ self.current_row += 1
1814
+
1815
+ comm_port_i18n = advanced_i18n.get('texthooker_comm_port', {})
1816
+ HoverInfoLabelWidget(advanced_frame, text=comm_port_i18n.get('label', '...'),
1817
+ tooltip=comm_port_i18n.get('tooltip', '...'),
1818
+ row=self.current_row, column=0)
1819
+ ttk.Entry(advanced_frame, textvariable=self.texthooker_communication_websocket_port_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1820
+ self.current_row += 1
1821
+
1822
+ export_port_i18n = advanced_i18n.get('plaintext_export_port', {})
1823
+ HoverInfoLabelWidget(advanced_frame, text=export_port_i18n.get('label', '...'),
1824
+ tooltip=export_port_i18n.get('tooltip', '...'),
1825
+ row=self.current_row, column=0)
1826
+ ttk.Entry(advanced_frame, textvariable=self.plaintext_websocket_export_port_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1827
+ self.current_row += 1
1828
+
1829
+ reset_hotkey_i18n = advanced_i18n.get('reset_line_hotkey', {})
1830
+ HoverInfoLabelWidget(advanced_frame, text=reset_hotkey_i18n.get('label', '...'),
1831
+ tooltip=reset_hotkey_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1832
+ ttk.Entry(advanced_frame, textvariable=self.reset_line_hotkey_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1833
+ self.current_row += 1
1834
+
1835
+ polling_i18n = advanced_i18n.get('polling_rate', {})
1836
+ HoverInfoLabelWidget(advanced_frame, text=polling_i18n.get('label', '...'),
1837
+ tooltip=polling_i18n.get('tooltip', '...'),
1838
+ row=self.current_row, column=0)
1839
+ ttk.Entry(advanced_frame, textvariable=self.polling_rate_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1840
+ self.current_row += 1
1841
+
1842
+ current_ver_i18n = advanced_i18n.get('current_version', {})
1843
+ HoverInfoLabelWidget(advanced_frame, text=current_ver_i18n.get('label', 'Current Version:'), bootstyle="secondary",
1844
+ tooltip=current_ver_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1845
+ self.current_version = ttk.Label(advanced_frame, text=get_current_version(), bootstyle="secondary")
1846
+ self.current_version.grid(row=self.current_row, column=1, sticky='W', pady=2)
1847
+ self.current_row += 1
1848
+
1849
+ latest_ver_i18n = advanced_i18n.get('latest_version', {})
1850
+ HoverInfoLabelWidget(advanced_frame, text=latest_ver_i18n.get('label', 'Latest Version:'), bootstyle="secondary",
1851
+ tooltip=latest_ver_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1852
+ self.latest_version = ttk.Label(advanced_frame, text=get_latest_version(), bootstyle="secondary")
1853
+ self.latest_version.grid(row=self.current_row, column=1, sticky='W', pady=2)
1854
+ self.current_row += 1
1855
+
1856
+ self.add_reset_button(advanced_frame, "advanced", self.current_row, 0, self.create_advanced_tab)
1857
+
1858
+ for col in range(4): advanced_frame.grid_columnconfigure(col, weight=0)
1859
+ for row in range(self.current_row): advanced_frame.grid_rowconfigure(row, minsize=30)
1860
+
1861
+ return advanced_frame
1862
+
1863
+ @new_tab
1864
+ def create_ai_tab(self):
1865
+ if self.ai_tab is None:
1866
+ ai_i18n = self.i18n.get('tabs', {}).get('ai', {})
1867
+ self.ai_tab = ttk.Frame(self.notebook, padding=15)
1868
+ self.notebook.add(self.ai_tab, text=ai_i18n.get('title', 'AI'))
1869
+ else:
1870
+ for widget in self.ai_tab.winfo_children():
1871
+ widget.destroy()
1872
+
1873
+ ai_frame = self.ai_tab
1874
+ ai_i18n = self.i18n.get('tabs', {}).get('ai', {})
1875
+
1876
+ enabled_i18n = ai_i18n.get('enabled', {})
1877
+ HoverInfoLabelWidget(ai_frame, text=enabled_i18n.get('label', '...'), tooltip=enabled_i18n.get('tooltip', '...'),
1878
+ row=self.current_row, column=0)
1879
+ ttk.Checkbutton(ai_frame, variable=self.ai_enabled_value, bootstyle="round-toggle").grid(row=self.current_row,
1880
+ column=1, sticky='W', pady=2)
1881
+ self.current_row += 1
1882
+
1883
+ provider_i18n = ai_i18n.get('provider', {})
1884
+ HoverInfoLabelWidget(ai_frame, text=provider_i18n.get('label', '...'), tooltip=provider_i18n.get('tooltip', '...'), row=self.current_row,
1885
+ column=0)
1886
+ ttk.Combobox(ai_frame, textvariable=self.ai_provider_value, values=[AI_GEMINI, AI_GROQ, AI_LOCAL], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
1887
+ self.current_row += 1
1888
+
1889
+ gemini_model_i18n = ai_i18n.get('gemini_model', {})
1890
+ HoverInfoLabelWidget(ai_frame, text=gemini_model_i18n.get('label', '...'), tooltip=gemini_model_i18n.get('tooltip', '...'),
1891
+ row=self.current_row, column=0)
1892
+ ttk.Combobox(ai_frame, textvariable=self.gemini_model_value, values=['gemma-3n-e4b-it', 'gemini-2.5-flash-lite', 'gemini-2.5-flash','gemini-2.0-flash', 'gemini-2.0-flash-lite'], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
1893
+ self.current_row += 1
1894
+
1895
+ gemini_key_i18n = ai_i18n.get('gemini_api_key', {})
1896
+ HoverInfoLabelWidget(ai_frame, text=gemini_key_i18n.get('label', '...'),
1897
+ tooltip=gemini_key_i18n.get('tooltip', '...'),
1898
+ foreground="green", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1899
+ ttk.Entry(ai_frame, show="*", textvariable=self.gemini_api_key_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1900
+ self.current_row += 1
1901
+
1902
+ groq_model_i18n = ai_i18n.get('groq_model', {})
1903
+ HoverInfoLabelWidget(ai_frame, text=groq_model_i18n.get('label', '...'), tooltip=groq_model_i18n.get('tooltip', '...'),
1904
+ row=self.current_row, column=0)
1905
+ ttk.Combobox(ai_frame, textvariable=self.groq_model_value, values=['meta-llama/llama-4-maverick-17b-128e-instruct',
1906
+ 'meta-llama/llama-4-scout-17b-16e-instruct',
1907
+ 'llama-3.1-8b-instant'], state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
1908
+ self.current_row += 1
1909
+
1910
+ groq_key_i18n = ai_i18n.get('groq_api_key', {})
1911
+ HoverInfoLabelWidget(ai_frame, text=groq_key_i18n.get('label', '...'), tooltip=groq_key_i18n.get('tooltip', '...'),
1912
+ row=self.current_row, column=0)
1913
+ ttk.Entry(ai_frame, show="*", textvariable=self.groq_api_key_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1914
+ self.current_row += 1
1915
+
1916
+ local_model_i18n = ai_i18n.get('local_model', {})
1917
+ HoverInfoLabelWidget(ai_frame, text=local_model_i18n.get('label', '...'), tooltip=local_model_i18n.get('tooltip', '...'),
1918
+ foreground="red", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1919
+ ttk.Combobox(ai_frame, textvariable=self.local_ai_model_value, values=[OFF, 'facebook/nllb-200-distilled-600M', 'facebook/nllb-200-1.3B', 'facebook/nllb-200-3.3B']).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1920
+ self.current_row += 1
1921
+
1922
+ anki_field_i18n = ai_i18n.get('anki_field', {})
1923
+ HoverInfoLabelWidget(ai_frame, text=anki_field_i18n.get('label', '...'), tooltip=anki_field_i18n.get('tooltip', '...'),
1924
+ row=self.current_row, column=0)
1925
+ ttk.Entry(ai_frame, textvariable=self.ai_anki_field_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1926
+ self.current_row += 1
1927
+
1928
+ context_i18n = ai_i18n.get('context_length', {})
1929
+ HoverInfoLabelWidget(ai_frame, text=context_i18n.get('label', '...'), tooltip=context_i18n.get('tooltip', '...'),
1930
+ foreground="red", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
1931
+ ttk.Entry(ai_frame, textvariable=self.ai_dialogue_context_length_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1932
+ self.current_row += 1
1933
+
1934
+ canned_trans_i18n = ai_i18n.get('use_canned_translation', {})
1935
+ HoverInfoLabelWidget(ai_frame, text=canned_trans_i18n.get('label', '...'),
1936
+ tooltip=canned_trans_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1937
+ ttk.Checkbutton(ai_frame, variable=self.use_canned_translation_prompt_value, bootstyle="round-toggle").grid(
1938
+ row=self.current_row, column=1, sticky='W', pady=2)
1939
+ self.current_row += 1
1940
+
1941
+ canned_context_i18n = ai_i18n.get('use_canned_context', {})
1942
+ HoverInfoLabelWidget(ai_frame, text=canned_context_i18n.get('label', '...'),
1943
+ tooltip=canned_context_i18n.get('tooltip', '...'), row=self.current_row, column=0)
1944
+ ttk.Checkbutton(ai_frame, variable=self.use_canned_context_prompt_value, bootstyle="round-toggle").grid(
1945
+ row=self.current_row, column=1, sticky='W', pady=2)
1946
+ self.current_row += 1
1947
+
1948
+ custom_prompt_i18n = ai_i18n.get('custom_prompt', {})
1949
+ HoverInfoLabelWidget(ai_frame, text=custom_prompt_i18n.get('label', '...'), tooltip=custom_prompt_i18n.get('tooltip', '...'),
1950
+ row=self.current_row, column=0)
1951
+ self.custom_prompt = scrolledtext.ScrolledText(ai_frame, width=50, height=5, font=("TkDefaultFont", 9),
1952
+ relief="solid", borderwidth=1,
1953
+ highlightbackground=ttk.Style().colors.border)
1954
+ self.custom_prompt.insert(tk.END, self.settings.ai.custom_prompt)
1955
+ self.custom_prompt.grid(row=self.current_row, column=1, sticky='EW', pady=2)
1956
+ self.current_row += 1
1957
+
1958
+ self.add_reset_button(ai_frame, "ai", self.current_row, 0, self.create_ai_tab)
1959
+
1960
+ for col in range(3): ai_frame.grid_columnconfigure(col, weight=0)
1961
+ for row in range(self.current_row): ai_frame.grid_rowconfigure(row, minsize=30)
1962
+
1963
+ return ai_frame
1964
+
1965
+ @new_tab
1966
+ def create_wip_tab(self):
1967
+ if self.wip_tab is None:
1968
+ wip_i18n = self.i18n.get('tabs', {}).get('wip', {})
1969
+ self.wip_tab = ttk.Frame(self.notebook, padding=15)
1970
+ self.notebook.add(self.wip_tab, text=wip_i18n.get('title', 'WIP'))
1971
+ else:
1972
+ for widget in self.wip_tab.winfo_children():
1973
+ widget.destroy()
1974
+
1975
+ wip_frame = self.wip_tab
1976
+ wip_i18n = self.i18n.get('tabs', {}).get('wip', {})
1977
+ try:
1978
+ ttk.Label(wip_frame, text=wip_i18n.get('warning_experimental', '...'),
1979
+ foreground="red", font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2,
1980
+ sticky='W', pady=5)
1981
+ self.current_row += 1
1982
+
1983
+ ttk.Label(wip_frame, text=wip_i18n.get('warning_overlay_deps', '...'),
1984
+ foreground="red", font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2,
1985
+ sticky='W', pady=5)
1986
+ self.current_row += 1
1987
+
1988
+ overlay_port_i18n = wip_i18n.get('overlay_port', {})
1989
+ HoverInfoLabelWidget(wip_frame, text=overlay_port_i18n.get('label', '...'),
1990
+ tooltip=overlay_port_i18n.get('tooltip', '...'),
1991
+ row=self.current_row, column=0)
1992
+ ttk.Entry(wip_frame, textvariable=self.overlay_websocket_port_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
1993
+ self.current_row += 1
1994
+
1995
+ overlay_send_i18n = wip_i18n.get('overlay_send', {})
1996
+ HoverInfoLabelWidget(wip_frame, text=overlay_send_i18n.get('label', '...'),
1997
+ tooltip=overlay_send_i18n.get('tooltip', '...'),
1998
+ row=self.current_row, column=0)
1999
+ ttk.Checkbutton(wip_frame, variable=self.overlay_websocket_send_value, bootstyle="round-toggle").grid(
2000
+ row=self.current_row, column=1, sticky='W', pady=2)
2001
+ self.current_row += 1
2002
+
2003
+ monitor_i18n = wip_i18n.get('monitor_capture', {})
2004
+ HoverInfoLabelWidget(wip_frame, text=monitor_i18n.get('label', '...'),
2005
+ tooltip=monitor_i18n.get('tooltip', '...'),
2006
+ row=self.current_row, column=0)
2007
+ self.monitor_to_capture = ttk.Combobox(wip_frame, values=self.monitors, state="readonly")
2008
+
2009
+ if self.monitors:
2010
+ # Ensure the index is valid
2011
+ monitor_index = self.settings.wip.monitor_to_capture
2012
+ if 0 <= monitor_index < len(self.monitors):
2013
+ self.monitor_to_capture.current(monitor_index)
2014
+ else:
2015
+ self.monitor_to_capture.current(0)
2016
+ else:
2017
+ self.monitor_to_capture.set(monitor_i18n.get('not_detected', "OwOCR Not Detected"))
2018
+ self.monitor_to_capture.grid(row=self.current_row, column=1, sticky='EW', pady=2)
2019
+ self.current_row += 1
2020
+
2021
+ except Exception as e:
2022
+ logger.error(f"Error setting up wip tab to capture: {e}")
2023
+ ttk.Label(wip_frame, text=wip_i18n.get('error_setup', 'Error setting up WIP tab'), foreground="red").grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=5)
2024
+
2025
+ self.add_reset_button(wip_frame, "wip", self.current_row, 0, self.create_wip_tab)
2026
+
2027
+ for col in range(2): wip_frame.grid_columnconfigure(col, weight=0)
2028
+ for row in range(self.current_row): wip_frame.grid_rowconfigure(row, minsize=30)
2029
+
2030
+ return wip_frame
2031
+
2032
+ def on_profile_change(self, event):
2033
+ self.save_settings(profile_change=True)
2034
+ self.reload_settings(force_refresh=True)
2035
+ self.refresh_obs_scenes()
2036
+ if self.master_config.current_profile != DEFAULT_CONFIG:
2037
+ self.delete_profile_button.grid(row=1, column=2, pady=5)
2038
+ else:
2039
+ self.delete_profile_button.grid_remove()
2040
+
2041
+ def add_profile(self):
2042
+ dialog_i18n = self.i18n.get('dialogs', {}).get('add_profile', {})
2043
+ new_profile_name = simpledialog.askstring(dialog_i18n.get('title', 'Input'), dialog_i18n.get('prompt', 'Enter new profile name:'))
2044
+ if new_profile_name:
2045
+ self.master_config.configs[new_profile_name] = self.master_config.get_default_config()
2046
+ self.profile_combobox['values'] = list(self.master_config.configs.keys())
2047
+ self.profile_combobox.set(new_profile_name)
2048
+ self.save_settings()
2049
+ self.reload_settings()
2050
+
2051
+ def copy_profile(self):
2052
+ source_profile = self.profile_combobox.get()
2053
+ dialog_i18n = self.i18n.get('dialogs', {}).get('copy_profile', {})
2054
+ new_profile_name = simpledialog.askstring(dialog_i18n.get('title', 'Input'), dialog_i18n.get('prompt', 'Enter new profile name:'), parent=self.window)
2055
+ if new_profile_name and source_profile in self.master_config.configs:
2056
+ import copy
2057
+ self.master_config.configs[new_profile_name] = copy.deepcopy(self.master_config.configs[source_profile])
2058
+ self.master_config.configs[new_profile_name].name = new_profile_name
2059
+ self.profile_combobox['values'] = list(self.master_config.configs.keys())
2060
+ self.profile_combobox.set(new_profile_name)
2061
+ self.save_settings()
2062
+ self.reload_settings()
2063
+
2064
+ def delete_profile(self):
2065
+ profile_to_delete = self.profile_combobox.get()
2066
+ dialog_i18n = self.i18n.get('dialogs', {}).get('delete_profile', {})
2067
+ error_title = dialog_i18n.get('error_title', 'Error')
2068
+
2069
+ if profile_to_delete == "Default":
2070
+ messagebox.showerror(error_title, dialog_i18n.get('error_cannot_delete_default', 'Cannot delete the Default profile.'))
2071
+ return
2072
+
2073
+ if profile_to_delete and profile_to_delete in self.master_config.configs:
2074
+ confirm = messagebox.askyesno(dialog_i18n.get('title', 'Confirm Delete'),
2075
+ dialog_i18n.get('message', "Are you sure... '{profile_name}'?").format(profile_name=profile_to_delete),
2076
+ parent=self.window, icon='warning')
2077
+ if confirm:
2078
+ del self.master_config.configs[profile_to_delete]
2079
+ self.profile_combobox['values'] = list(self.master_config.configs.keys())
2080
+ self.profile_combobox.set("Default")
2081
+ self.master_config.current_profile = "Default"
2082
+ save_full_config(self.master_config)
2083
+ self.reload_settings()
2084
+
2085
+ def download_and_install_ocen(self):
2086
+ dialog_i18n = self.i18n.get('dialogs', {}).get('install_ocenaudio', {})
2087
+ confirm = messagebox.askyesno(dialog_i18n.get('title', 'Download OcenAudio?'),
2088
+ dialog_i18n.get('message', 'Would you like to download...?'),
2089
+ parent=self.window, icon='question')
2090
+ if confirm:
2091
+ self.external_tool_value.set(dialog_i18n.get('downloading_message', 'Downloading...'))
2092
+ exe_path = download_ocenaudio_if_needed()
2093
+ messagebox.showinfo(dialog_i18n.get('success_title', 'Download Complete'),
2094
+ dialog_i18n.get('success_message', 'Downloaded to {path}').format(path=exe_path),
2095
+ parent=self.window)
2096
+ self.external_tool_value.set(exe_path)
2097
+ self.save_settings()
2098
+
2099
+ def set_default_anki_media_collection(self):
2100
+ dialog_i18n = self.i18n.get('dialogs', {}).get('set_anki_media', {})
2101
+ confirm = messagebox.askyesno(dialog_i18n.get('title', 'Set Default Path?'),
2102
+ dialog_i18n.get('message', 'Would you like to set...?'),
2103
+ parent=self.window, icon='question')
2104
+ if confirm:
2105
+ default_path = get_default_anki_media_collection_path()
2106
+ if default_path != self.anki_media_collection_value.get():
2107
+ self.anki_media_collection_value.set(default_path)
2108
+ self.save_settings()
2109
+
2110
+
2111
+ if __name__ == '__main__':
2112
+ # Ensure 'en_us.json' is in the same directory as this script to run this example
2113
+ root = ttk.Window(themename='darkly')
2114
+ window = ConfigApp(root)
2115
+ window.show()
2116
+ window.window.mainloop()