GameSentenceMiner 2.9.29__py3-none-any.whl → 2.10.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- GameSentenceMiner/anki.py +16 -14
- GameSentenceMiner/config_gui.py +826 -628
- GameSentenceMiner/gametext.py +5 -2
- GameSentenceMiner/gsm.py +19 -12
- GameSentenceMiner/ocr/gsm_ocr_config.py +16 -0
- GameSentenceMiner/ocr/owocr_area_selector.py +2 -0
- GameSentenceMiner/ocr/owocr_helper.py +18 -33
- GameSentenceMiner/ocr/ss_picker.py +17 -1
- GameSentenceMiner/util/audio_offset_selector.py +205 -0
- GameSentenceMiner/util/configuration.py +45 -16
- GameSentenceMiner/util/ffmpeg.py +23 -95
- GameSentenceMiner/util/gsm_utils.py +64 -5
- GameSentenceMiner/util/text_log.py +2 -2
- GameSentenceMiner/vad.py +3 -14
- GameSentenceMiner/web/service.py +10 -7
- GameSentenceMiner/web/texthooking_page.py +2 -2
- {gamesentenceminer-2.9.29.dist-info → gamesentenceminer-2.10.1.dist-info}/METADATA +4 -2
- {gamesentenceminer-2.9.29.dist-info → gamesentenceminer-2.10.1.dist-info}/RECORD +22 -21
- {gamesentenceminer-2.9.29.dist-info → gamesentenceminer-2.10.1.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.9.29.dist-info → gamesentenceminer-2.10.1.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.9.29.dist-info → gamesentenceminer-2.10.1.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.9.29.dist-info → gamesentenceminer-2.10.1.dist-info}/top_level.txt +0 -0
GameSentenceMiner/config_gui.py
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
import asyncio
|
2
|
+
import subprocess
|
2
3
|
import tkinter as tk
|
3
|
-
from tkinter import filedialog, messagebox, simpledialog, scrolledtext
|
4
|
+
from tkinter import filedialog, messagebox, simpledialog, scrolledtext, font
|
4
5
|
|
6
|
+
import pyperclip
|
5
7
|
import ttkbootstrap as ttk
|
6
8
|
|
7
9
|
from GameSentenceMiner import obs
|
@@ -40,8 +42,8 @@ class HoverInfoWidget:
|
|
40
42
|
self.tooltip = tk.Toplevel(self.info_icon)
|
41
43
|
self.tooltip.wm_overrideredirect(True)
|
42
44
|
self.tooltip.wm_geometry(f"+{x}+{y}")
|
43
|
-
label =
|
44
|
-
|
45
|
+
label = ttk.Label(self.tooltip, text=text, relief="solid", borderwidth=1,
|
46
|
+
font=("tahoma", "12", "normal"))
|
45
47
|
label.pack(ipadx=1)
|
46
48
|
|
47
49
|
def hide_info_box(self):
|
@@ -49,6 +51,29 @@ class HoverInfoWidget:
|
|
49
51
|
self.tooltip.destroy()
|
50
52
|
self.tooltip = None
|
51
53
|
|
54
|
+
class HoverInfoLabelWidget:
|
55
|
+
def __init__(self, parent, text, tooltip, row, column, padx=5, pady=2, foreground="white", sticky='W', bootstyle=None, font=("Arial", 10, "normal")):
|
56
|
+
self.label = ttk.Label(parent, text=text, foreground=foreground, cursor="hand2", bootstyle=bootstyle, font=font)
|
57
|
+
self.label.grid(row=row, column=column, padx=(0, padx), pady=0, sticky=sticky)
|
58
|
+
self.label.bind("<Enter>", lambda e: self.show_info_box(tooltip))
|
59
|
+
self.label.bind("<Leave>", lambda e: self.hide_info_box())
|
60
|
+
self.tooltip = None
|
61
|
+
|
62
|
+
def show_info_box(self, text):
|
63
|
+
x, y, _, _ = self.label.bbox("insert")
|
64
|
+
x += self.label.winfo_rootx() + 25
|
65
|
+
y += self.label.winfo_rooty() + 20
|
66
|
+
self.tooltip = tk.Toplevel(self.label)
|
67
|
+
self.tooltip.wm_overrideredirect(True)
|
68
|
+
self.tooltip.wm_geometry(f"+{x}+{y}")
|
69
|
+
label = ttk.Label(self.tooltip, text=text, relief="solid", borderwidth=1,
|
70
|
+
font=("tahoma", "12", "normal"))
|
71
|
+
label.pack(ipadx=1)
|
72
|
+
|
73
|
+
def hide_info_box(self):
|
74
|
+
if self.tooltip:
|
75
|
+
self.tooltip.destroy()
|
76
|
+
self.tooltip = None
|
52
77
|
|
53
78
|
|
54
79
|
class ConfigApp:
|
@@ -60,6 +85,7 @@ class ConfigApp:
|
|
60
85
|
self.window.protocol("WM_DELETE_WINDOW", self.hide)
|
61
86
|
self.obs_scene_listbox_changed = False
|
62
87
|
|
88
|
+
self.window.geometry("800x600")
|
63
89
|
self.current_row = 0
|
64
90
|
|
65
91
|
self.master_config: Config = configuration.load_config()
|
@@ -67,7 +93,7 @@ class ConfigApp:
|
|
67
93
|
self.settings = self.master_config.get_config()
|
68
94
|
|
69
95
|
self.notebook = ttk.Notebook(self.window)
|
70
|
-
self.notebook.pack(pady=10, expand=True)
|
96
|
+
self.notebook.pack(pady=10, expand=True, fill='both')
|
71
97
|
|
72
98
|
self.general_frame = self.create_general_tab()
|
73
99
|
self.create_paths_tab()
|
@@ -77,18 +103,26 @@ class ConfigApp:
|
|
77
103
|
self.create_screenshot_tab()
|
78
104
|
self.create_audio_tab()
|
79
105
|
self.create_obs_tab()
|
80
|
-
self.create_hotkeys_tab()
|
81
106
|
self.create_profiles_tab()
|
82
|
-
self.create_advanced_tab()
|
83
107
|
self.create_ai_tab()
|
108
|
+
self.create_advanced_tab()
|
109
|
+
# self.create_help_tab()
|
84
110
|
|
85
111
|
self.notebook.bind("<<NotebookTabChanged>>", self.on_profiles_tab_selected)
|
86
112
|
|
87
113
|
button_frame = ttk.Frame(self.window)
|
88
|
-
button_frame.pack(side="
|
89
|
-
|
90
|
-
ttk.Button(button_frame, text="Save Settings", command=self.save_settings).grid(row=0,
|
91
|
-
|
114
|
+
button_frame.pack(side="bottom", pady=20, anchor="center")
|
115
|
+
|
116
|
+
ttk.Button(button_frame, text="Save Settings", command=self.save_settings, bootstyle="success").grid(row=0,
|
117
|
+
column=0,
|
118
|
+
padx=10)
|
119
|
+
# if len(self.master_config.configs) > 1:
|
120
|
+
# ttk.Button(button_frame, text="Save and Sync Changes",
|
121
|
+
# command=lambda: self.save_settings(profile_change=False, sync_changes=True),
|
122
|
+
# bootstyle="info").grid(row=0, column=1, padx=10)
|
123
|
+
# HoverInfoWidget(button_frame,
|
124
|
+
# "Saves Settings and Syncs changed settings to all profiles (if you have them)", row=0,
|
125
|
+
# column=2)
|
92
126
|
|
93
127
|
self.window.withdraw()
|
94
128
|
|
@@ -97,16 +131,23 @@ class ConfigApp:
|
|
97
131
|
if matched_configs:
|
98
132
|
selection_window = tk.Toplevel(self.window)
|
99
133
|
selection_window.title("Select Profile")
|
100
|
-
|
134
|
+
selection_window.transient(self.window) # Make it modal relative to the main window
|
135
|
+
selection_window.grab_set() # Grab all events for this window
|
136
|
+
|
137
|
+
ttk.Label(selection_window,
|
138
|
+
text="Multiple profiles match the current scene. Please select the profile:").pack(pady=10)
|
101
139
|
profile_var = tk.StringVar(value=matched_configs[0])
|
102
|
-
profile_dropdown = ttk.Combobox(selection_window, textvariable=profile_var, values=matched_configs,
|
140
|
+
profile_dropdown = ttk.Combobox(selection_window, textvariable=profile_var, values=matched_configs,
|
141
|
+
state="readonly")
|
103
142
|
profile_dropdown.pack(pady=5)
|
104
|
-
ttk.Button(selection_window, text="OK",
|
105
|
-
|
143
|
+
ttk.Button(selection_window, text="OK",
|
144
|
+
command=lambda: [selection_window.destroy(), setattr(self, 'selected_scene', profile_var.get())],
|
145
|
+
bootstyle="primary").pack(pady=10)
|
146
|
+
|
147
|
+
self.window.wait_window(selection_window) # Wait for selection_window to close
|
106
148
|
selected_scene = self.selected_scene
|
107
149
|
return selected_scene
|
108
150
|
|
109
|
-
|
110
151
|
def add_save_hook(self, func):
|
111
152
|
on_save.append(func)
|
112
153
|
|
@@ -128,7 +169,8 @@ class ConfigApp:
|
|
128
169
|
|
129
170
|
# Create a new Config instance
|
130
171
|
config = ProfileConfig(
|
131
|
-
scenes=[self.obs_scene_listbox.get(i) for i in
|
172
|
+
scenes=[self.obs_scene_listbox.get(i) for i in
|
173
|
+
self.obs_scene_listbox.curselection()] if self.obs_scene_listbox_changed else self.settings.scenes,
|
132
174
|
general=General(
|
133
175
|
use_websocket=self.websocket_enabled.get(),
|
134
176
|
use_clipboard=self.clipboard_enabled.get(),
|
@@ -137,7 +179,7 @@ class ConfigApp:
|
|
137
179
|
open_multimine_on_startup=self.open_multimine_on_startup.get(),
|
138
180
|
texthook_replacement_regex=self.texthook_replacement_regex.get(),
|
139
181
|
use_both_clipboard_and_websocket=self.use_both_clipboard_and_websocket.get(),
|
140
|
-
|
182
|
+
texthooker_port=int(self.texthooker_port.get())
|
141
183
|
),
|
142
184
|
paths=Paths(
|
143
185
|
folder_to_watch=self.folder_to_watch.get(),
|
@@ -163,10 +205,6 @@ class ConfigApp:
|
|
163
205
|
overwrite_audio=self.overwrite_audio.get(),
|
164
206
|
overwrite_picture=self.overwrite_picture.get(),
|
165
207
|
multi_overwrites_sentence=self.multi_overwrites_sentence.get(),
|
166
|
-
anki_custom_fields={
|
167
|
-
key_entry.get(): value_entry.get() for key_entry, value_entry, delete_button in
|
168
|
-
self.custom_field_entries if key_entry.get()
|
169
|
-
}
|
170
208
|
),
|
171
209
|
features=Features(
|
172
210
|
full_auto=self.full_auto.get(),
|
@@ -184,9 +222,7 @@ class ConfigApp:
|
|
184
222
|
extension=self.screenshot_extension.get(),
|
185
223
|
custom_ffmpeg_settings=self.screenshot_custom_ffmpeg_settings.get(),
|
186
224
|
screenshot_hotkey_updates_anki=self.screenshot_hotkey_update_anki.get(),
|
187
|
-
seconds_after_line
|
188
|
-
# use_beginning_of_line_as_screenshot=self.use_beginning_of_line_as_screenshot.get(),
|
189
|
-
# use_new_screenshot_logic=self.use_new_screenshot_logic.get(),
|
225
|
+
seconds_after_line=float(self.seconds_after_line.get()) if self.seconds_after_line.get() else 0.0,
|
190
226
|
screenshot_timing_setting=self.screenshot_timing.get(),
|
191
227
|
use_screenshot_selector=self.use_screenshot_selector.get(),
|
192
228
|
),
|
@@ -196,7 +232,7 @@ class ConfigApp:
|
|
196
232
|
beginning_offset=float(self.beginning_offset.get()),
|
197
233
|
end_offset=float(self.end_offset.get()),
|
198
234
|
ffmpeg_reencode_options=self.audio_ffmpeg_reencode_options.get(),
|
199
|
-
external_tool
|
235
|
+
external_tool=self.external_tool.get(),
|
200
236
|
anki_media_collection=self.anki_media_collection.get(),
|
201
237
|
external_tool_enabled=self.external_tool_enabled.get(),
|
202
238
|
pre_vad_end_offset=float(self.pre_vad_audio_offset.get()),
|
@@ -214,7 +250,7 @@ class ConfigApp:
|
|
214
250
|
hotkeys=Hotkeys(
|
215
251
|
reset_line=self.reset_line_hotkey.get(),
|
216
252
|
take_screenshot=self.take_screenshot_hotkey.get(),
|
217
|
-
#open_utility=self.open_utility_hotkey.get(),
|
253
|
+
# open_utility=self.open_utility_hotkey.get(),
|
218
254
|
play_latest_audio=self.play_latest_audio_hotkey.get()
|
219
255
|
),
|
220
256
|
vad=VAD(
|
@@ -235,7 +271,6 @@ class ConfigApp:
|
|
235
271
|
video_player_path=self.video_player_path.get(),
|
236
272
|
multi_line_line_break=self.multi_line_line_break.get(),
|
237
273
|
multi_line_sentence_storage_field=self.multi_line_sentence_storage_field.get(),
|
238
|
-
ocr_sends_to_clipboard=self.ocr_sends_to_clipboard.get(),
|
239
274
|
use_anki_note_creation_time=self.use_anki_note_creation_time.get(),
|
240
275
|
ocr_websocket_port=int(self.ocr_websocket_port.get()),
|
241
276
|
texthooker_communication_websocket_port=int(self.texthooker_communication_websocket_port.get()),
|
@@ -259,7 +294,8 @@ class ConfigApp:
|
|
259
294
|
config.audio.custom_encode_settings = self.audio_ffmpeg_reencode_options.get()
|
260
295
|
|
261
296
|
if config.features.backfill_audio and config.features.full_auto:
|
262
|
-
messagebox.showerror("Configuration Error",
|
297
|
+
messagebox.showerror("Configuration Error",
|
298
|
+
"Cannot have Full Auto and Backfill mode on at the same time! Note: Backfill is a very niche workflow.")
|
263
299
|
return
|
264
300
|
|
265
301
|
if not config.general.use_websocket and not config.general.use_clipboard:
|
@@ -268,7 +304,7 @@ class ConfigApp:
|
|
268
304
|
|
269
305
|
current_profile = self.profile_combobox.get()
|
270
306
|
prev_config = self.master_config.get_config()
|
271
|
-
self.master_config.switch_to_default_if_not_found=self.switch_to_default_if_not_found.get()
|
307
|
+
self.master_config.switch_to_default_if_not_found = self.switch_to_default_if_not_found.get()
|
272
308
|
if profile_change:
|
273
309
|
self.master_config.current_profile = current_profile
|
274
310
|
else:
|
@@ -296,14 +332,12 @@ class ConfigApp:
|
|
296
332
|
for func in on_save:
|
297
333
|
func()
|
298
334
|
|
299
|
-
|
300
335
|
def reload_settings(self):
|
301
336
|
new_config = configuration.load_config()
|
302
337
|
current_config = new_config.get_config()
|
303
338
|
|
304
339
|
self.window.title("GameSentenceMiner Configuration - " + current_config.name)
|
305
340
|
|
306
|
-
|
307
341
|
if current_config.name != self.settings.name or self.settings.config_changed(current_config):
|
308
342
|
logger.info("Config changed, reloading settings.")
|
309
343
|
self.master_config = new_config
|
@@ -319,11 +353,10 @@ class ConfigApp:
|
|
319
353
|
self.create_screenshot_tab()
|
320
354
|
self.create_audio_tab()
|
321
355
|
self.create_obs_tab()
|
322
|
-
self.create_hotkeys_tab()
|
323
356
|
self.create_profiles_tab()
|
324
357
|
self.create_advanced_tab()
|
325
358
|
self.create_ai_tab()
|
326
|
-
|
359
|
+
self.create_help_tab() # Re-add help tab for modern styling consistent application
|
327
360
|
|
328
361
|
def increment_row(self):
|
329
362
|
"""Increment the current row index and return the new value."""
|
@@ -339,229 +372,281 @@ class ConfigApp:
|
|
339
372
|
|
340
373
|
@new_tab
|
341
374
|
def create_general_tab(self):
|
342
|
-
general_frame = ttk.Frame(self.notebook)
|
375
|
+
general_frame = ttk.Frame(self.notebook, padding=5)
|
343
376
|
self.notebook.add(general_frame, text='General')
|
344
377
|
|
345
|
-
|
378
|
+
HoverInfoLabelWidget(general_frame, text="Websocket Enabled:",
|
379
|
+
foreground="dark orange", font=("Helvetica", 10, "bold"),
|
380
|
+
tooltip="Enable or disable WebSocket communication. Enabling this will disable the clipboard monitor.",
|
381
|
+
row=self.current_row, column=0)
|
346
382
|
self.websocket_enabled = tk.BooleanVar(value=self.settings.general.use_websocket)
|
347
|
-
ttk.Checkbutton(general_frame, variable=self.websocket_enabled).grid(
|
348
|
-
|
349
|
-
|
350
|
-
|
383
|
+
ttk.Checkbutton(general_frame, variable=self.websocket_enabled, bootstyle="round-toggle").grid(
|
384
|
+
row=self.current_row, column=1,
|
385
|
+
sticky='W', pady=2)
|
386
|
+
self.current_row += 1
|
351
387
|
|
352
|
-
|
388
|
+
HoverInfoLabelWidget(general_frame, text="Clipboard Enabled:",
|
389
|
+
foreground="dark orange", font=("Helvetica", 10, "bold"),
|
390
|
+
tooltip="Enable or disable Clipboard monitoring.", row=self.current_row, column=0)
|
353
391
|
self.clipboard_enabled = tk.BooleanVar(value=self.settings.general.use_clipboard)
|
354
|
-
ttk.Checkbutton(general_frame, variable=self.clipboard_enabled).grid(
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
self.
|
364
|
-
|
365
|
-
|
366
|
-
|
392
|
+
ttk.Checkbutton(general_frame, variable=self.clipboard_enabled, bootstyle="round-toggle").grid(
|
393
|
+
row=self.current_row, column=1,
|
394
|
+
sticky='W', pady=2)
|
395
|
+
self.current_row += 1
|
396
|
+
|
397
|
+
HoverInfoLabelWidget(general_frame, text="Allow Both Simultaneously:",
|
398
|
+
foreground="red", font=("Helvetica", 10, "bold"),
|
399
|
+
tooltip="Enable to allow GSM to accept both clipboard and websocket input at the same time.",
|
400
|
+
row=self.current_row, column=0)
|
401
|
+
self.use_both_clipboard_and_websocket = tk.BooleanVar(
|
402
|
+
value=self.settings.general.use_both_clipboard_and_websocket)
|
403
|
+
ttk.Checkbutton(general_frame, variable=self.use_both_clipboard_and_websocket, bootstyle="round-toggle").grid(
|
404
|
+
row=self.current_row, column=1,
|
405
|
+
sticky='W', pady=2)
|
406
|
+
self.current_row += 1
|
407
|
+
|
408
|
+
HoverInfoLabelWidget(general_frame, text="Websocket URI(s):",
|
409
|
+
tooltip="WebSocket URI for connecting. Allows Comma Separated Values for Connecting Multiple.",
|
410
|
+
row=self.current_row, column=0)
|
367
411
|
self.websocket_uri = ttk.Entry(general_frame, width=50)
|
368
412
|
self.websocket_uri.insert(0, self.settings.general.websocket_uri)
|
369
|
-
self.websocket_uri.grid(row=self.current_row, column=1)
|
370
|
-
self.
|
371
|
-
column=2)
|
413
|
+
self.websocket_uri.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
414
|
+
self.current_row += 1
|
372
415
|
|
373
|
-
|
416
|
+
HoverInfoLabelWidget(general_frame, text="TextHook Replacement Regex:",
|
417
|
+
tooltip="Regex to run replacement on texthook input, set this to the same as what you may have in your texthook page.",
|
418
|
+
row=self.current_row, column=0)
|
374
419
|
self.texthook_replacement_regex = ttk.Entry(general_frame)
|
375
420
|
self.texthook_replacement_regex.insert(0, self.settings.general.texthook_replacement_regex)
|
376
|
-
self.texthook_replacement_regex.grid(row=self.current_row, column=1)
|
377
|
-
self.
|
378
|
-
column=2)
|
421
|
+
self.texthook_replacement_regex.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
422
|
+
self.current_row += 1
|
379
423
|
|
380
|
-
|
424
|
+
HoverInfoLabelWidget(general_frame, text="Open Config on Startup:",
|
425
|
+
tooltip="Whether to open config when the script starts.", row=self.current_row, column=0)
|
381
426
|
self.open_config_on_startup = tk.BooleanVar(value=self.settings.general.open_config_on_startup)
|
382
|
-
ttk.Checkbutton(general_frame, variable=self.open_config_on_startup).grid(
|
383
|
-
|
384
|
-
|
385
|
-
|
427
|
+
ttk.Checkbutton(general_frame, variable=self.open_config_on_startup, bootstyle="round-toggle").grid(
|
428
|
+
row=self.current_row, column=1,
|
429
|
+
sticky='W', pady=2)
|
430
|
+
self.current_row += 1
|
386
431
|
|
387
|
-
|
432
|
+
HoverInfoLabelWidget(general_frame, text="Open GSM Texthooker on Startup:",
|
433
|
+
tooltip="Whether to open Texthooking page when the script starts.", row=self.current_row,
|
434
|
+
column=0)
|
388
435
|
self.open_multimine_on_startup = tk.BooleanVar(value=self.settings.general.open_multimine_on_startup)
|
389
|
-
ttk.Checkbutton(general_frame, variable=self.open_multimine_on_startup).grid(
|
390
|
-
|
391
|
-
|
392
|
-
|
436
|
+
ttk.Checkbutton(general_frame, variable=self.open_multimine_on_startup, bootstyle="round-toggle").grid(
|
437
|
+
row=self.current_row, column=1,
|
438
|
+
sticky='W', pady=2)
|
439
|
+
self.current_row += 1
|
393
440
|
|
394
|
-
|
441
|
+
HoverInfoLabelWidget(general_frame, text="GSM Texthooker Port:",
|
442
|
+
tooltip="Port for the Texthooker to run on. Only change if you know what you are doing.",
|
443
|
+
row=self.current_row, column=0)
|
395
444
|
self.texthooker_port = ttk.Entry(general_frame)
|
396
445
|
self.texthooker_port.insert(0, str(self.settings.general.texthooker_port))
|
397
|
-
self.texthooker_port.grid(row=self.current_row, column=1)
|
398
|
-
self.
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
self.
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
self.
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
ttk.Label(general_frame, text="
|
416
|
-
|
417
|
-
self.
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
446
|
+
self.texthooker_port.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
447
|
+
self.current_row += 1
|
448
|
+
|
449
|
+
HoverInfoLabelWidget(general_frame, text="Current Version:", bootstyle="secondary",
|
450
|
+
tooltip="The current version of the application.", row=self.current_row, column=0)
|
451
|
+
self.current_version = ttk.Label(general_frame, text=get_current_version(), bootstyle="secondary")
|
452
|
+
self.current_version.grid(row=self.current_row, column=1, sticky='W', pady=2)
|
453
|
+
self.current_row += 1
|
454
|
+
|
455
|
+
HoverInfoLabelWidget(general_frame, text="Latest Version:", bootstyle="secondary",
|
456
|
+
tooltip="The latest available version of the application.", row=self.current_row, column=0)
|
457
|
+
self.latest_version = ttk.Label(general_frame, text=get_latest_version(), bootstyle="secondary")
|
458
|
+
self.latest_version.grid(row=self.current_row, column=1, sticky='W', pady=2)
|
459
|
+
self.current_row += 1
|
460
|
+
|
461
|
+
ttk.Label(general_frame, text="Indicates important/required settings.", foreground="dark orange",
|
462
|
+
font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
|
463
|
+
self.current_row += 1
|
464
|
+
ttk.Label(general_frame, text="Highlights Advanced Features that may break things.", foreground="red",
|
465
|
+
font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
|
466
|
+
self.current_row += 1
|
467
|
+
ttk.Label(general_frame, text="Indicates Recommended, but completely optional settings.", foreground="green",
|
468
|
+
font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
|
469
|
+
self.current_row += 1
|
470
|
+
ttk.Label(general_frame, text="Every Label in settings has a tooltip with more information if you hover over them.",
|
471
|
+
font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
|
472
|
+
self.current_row += 1
|
473
|
+
|
474
|
+
general_frame.grid_columnconfigure(0, weight=0) # No expansion for the label column
|
475
|
+
general_frame.grid_columnconfigure(1, weight=0) # Entry column gets more space
|
476
|
+
for row in range(self.current_row):
|
477
|
+
general_frame.grid_rowconfigure(row, minsize=30)
|
427
478
|
|
428
479
|
return general_frame
|
429
480
|
|
481
|
+
@new_tab
|
482
|
+
def create_required_settings_tab(self):
|
483
|
+
required_settings_frame = ttk.Frame(self.notebook)
|
484
|
+
self.notebook.add(required_settings_frame, text='Required Settings')
|
485
|
+
return required_settings_frame
|
486
|
+
|
430
487
|
@new_tab
|
431
488
|
def create_vad_tab(self):
|
432
|
-
vad_frame = ttk.Frame(self.notebook)
|
489
|
+
vad_frame = ttk.Frame(self.notebook, padding=15)
|
433
490
|
self.notebook.add(vad_frame, text='VAD')
|
434
491
|
|
435
|
-
|
492
|
+
HoverInfoLabelWidget(vad_frame, text="Voice Detection Postprocessing:",
|
493
|
+
tooltip="Enable post-processing of audio to trim just the voiceline.",
|
494
|
+
row=self.current_row, column=0)
|
436
495
|
self.do_vad_postprocessing = tk.BooleanVar(
|
437
496
|
value=self.settings.vad.do_vad_postprocessing)
|
438
|
-
ttk.Checkbutton(vad_frame, variable=self.do_vad_postprocessing).grid(
|
439
|
-
|
440
|
-
|
497
|
+
ttk.Checkbutton(vad_frame, variable=self.do_vad_postprocessing, bootstyle="round-toggle").grid(
|
498
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
499
|
+
self.current_row += 1
|
441
500
|
|
442
|
-
|
443
|
-
|
501
|
+
HoverInfoLabelWidget(vad_frame, text="Language:",
|
502
|
+
tooltip="Select the language for VAD. This is used for Whisper and Groq (if i implemented it)",
|
503
|
+
row=self.current_row, column=0)
|
504
|
+
self.language = ttk.Combobox(vad_frame, values=AVAILABLE_LANGUAGES, state="readonly")
|
444
505
|
self.language.set(self.settings.vad.language)
|
445
|
-
self.language.grid(row=self.current_row, column=1)
|
446
|
-
self.
|
447
|
-
column=2)
|
506
|
+
self.language.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
507
|
+
self.current_row += 1
|
448
508
|
|
449
|
-
|
509
|
+
HoverInfoLabelWidget(vad_frame, text="Whisper Model:", tooltip="Select the Whisper model size for VAD.",
|
510
|
+
row=self.current_row, column=0)
|
450
511
|
self.whisper_model = ttk.Combobox(vad_frame, values=[WHISPER_TINY, WHISPER_BASE, WHISPER_SMALL, WHISPER_MEDIUM,
|
451
|
-
WHSIPER_LARGE, WHISPER_TURBO])
|
512
|
+
WHSIPER_LARGE, WHISPER_TURBO], state="readonly")
|
452
513
|
self.whisper_model.set(self.settings.vad.whisper_model)
|
453
|
-
self.whisper_model.grid(row=self.current_row, column=1)
|
454
|
-
self.
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
self.
|
459
|
-
self.vosk_url.insert(0,
|
460
|
-
VOSK_BASE if self.settings.vad.vosk_url == 'https://alphacephei.com/vosk/models/vosk-model-ja-0.22.zip' else VOSK_SMALL)
|
461
|
-
self.vosk_url.grid(row=self.current_row, column=1)
|
462
|
-
self.add_label_and_increment_row(vad_frame, "URL for connecting to the Vosk server.", row=self.current_row,
|
463
|
-
column=2)
|
464
|
-
|
465
|
-
ttk.Label(vad_frame, text="Select VAD Model:").grid(row=self.current_row, column=0, sticky='W')
|
466
|
-
self.selected_vad_model = ttk.Combobox(vad_frame, values=[VOSK, SILERO, WHISPER, GROQ])
|
514
|
+
self.whisper_model.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
515
|
+
self.current_row += 1
|
516
|
+
|
517
|
+
HoverInfoLabelWidget(vad_frame, text="Select VAD Model:", tooltip="Select which VAD model to use.",
|
518
|
+
foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
519
|
+
self.selected_vad_model = ttk.Combobox(vad_frame, values=[VOSK, SILERO, WHISPER, GROQ], state="readonly")
|
467
520
|
self.selected_vad_model.set(self.settings.vad.selected_vad_model)
|
468
|
-
self.selected_vad_model.grid(row=self.current_row, column=1)
|
469
|
-
self.
|
521
|
+
self.selected_vad_model.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
522
|
+
self.current_row += 1
|
470
523
|
|
471
|
-
|
472
|
-
|
524
|
+
HoverInfoLabelWidget(vad_frame, text="Backup VAD Model:",
|
525
|
+
tooltip="Select which model to use as a backup if no audio is found.",
|
526
|
+
row=self.current_row, column=0)
|
527
|
+
self.backup_vad_model = ttk.Combobox(vad_frame, values=[OFF, VOSK, SILERO, WHISPER, GROQ], state="readonly")
|
473
528
|
self.backup_vad_model.set(self.settings.vad.backup_vad_model)
|
474
|
-
self.backup_vad_model.grid(row=self.current_row, column=1)
|
475
|
-
self.
|
476
|
-
|
529
|
+
self.backup_vad_model.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
530
|
+
self.current_row += 1
|
531
|
+
|
532
|
+
HoverInfoLabelWidget(vad_frame, text="Add Audio on No Results:",
|
533
|
+
tooltip="Add audio even if no results are found by VAD.", row=self.current_row, column=0)
|
534
|
+
self.add_audio_on_no_results = tk.BooleanVar(value=self.settings.vad.add_audio_on_no_results)
|
535
|
+
ttk.Checkbutton(vad_frame, variable=self.add_audio_on_no_results, bootstyle="round-toggle").grid(
|
536
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
537
|
+
self.current_row += 1
|
477
538
|
|
478
|
-
|
539
|
+
HoverInfoLabelWidget(vad_frame, text="Audio End Offset:",
|
540
|
+
tooltip="Offset in seconds from end of the video to extract.", foreground="dark orange",
|
541
|
+
font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
542
|
+
self.end_offset = ttk.Entry(vad_frame)
|
543
|
+
self.end_offset.insert(0, str(self.settings.audio.end_offset))
|
544
|
+
self.end_offset.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
545
|
+
self.current_row += 1
|
546
|
+
|
547
|
+
HoverInfoLabelWidget(vad_frame, text="Trim Beginning:",
|
548
|
+
tooltip='Beginning offset after VAD Trim, Only active if "Trim Beginning" is ON. Negative values = more time at the beginning',
|
549
|
+
row=self.current_row, column=0)
|
479
550
|
self.vad_trim_beginning = tk.BooleanVar(
|
480
551
|
value=self.settings.vad.trim_beginning)
|
481
|
-
ttk.Checkbutton(vad_frame, variable=self.vad_trim_beginning).grid(
|
482
|
-
|
483
|
-
row=self.current_row, column=2)
|
552
|
+
ttk.Checkbutton(vad_frame, variable=self.vad_trim_beginning, bootstyle="round-toggle").grid(
|
553
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
484
554
|
|
485
|
-
|
555
|
+
HoverInfoLabelWidget(vad_frame, text="Beginning Offset:",
|
556
|
+
tooltip='Beginning offset after VAD Trim, Only active if "Trim Beginning" is ON. Negative values = more time at the beginning',
|
557
|
+
row=self.current_row, column=2)
|
486
558
|
self.vad_beginning_offset = ttk.Entry(vad_frame)
|
487
559
|
self.vad_beginning_offset.insert(0, str(self.settings.vad.beginning_offset))
|
488
|
-
self.vad_beginning_offset.grid(row=self.current_row, column=
|
489
|
-
self.
|
490
|
-
|
491
|
-
ttk.Label(vad_frame, text="Audio End Offset:").grid(row=self.current_row, column=0, sticky='W')
|
492
|
-
self.end_offset = ttk.Entry(vad_frame)
|
493
|
-
self.end_offset.insert(0, str(self.settings.audio.end_offset))
|
494
|
-
self.end_offset.grid(row=self.current_row, column=1)
|
495
|
-
self.add_label_and_increment_row(vad_frame, "Offset in seconds from end of the video to extract.",
|
496
|
-
row=self.current_row, column=2)
|
497
|
-
|
498
|
-
ttk.Label(vad_frame, text="Add Audio on No Results:").grid(row=self.current_row, column=0, sticky='W')
|
499
|
-
self.add_audio_on_no_results = tk.BooleanVar(value=self.settings.vad.add_audio_on_no_results)
|
500
|
-
ttk.Checkbutton(vad_frame, variable=self.add_audio_on_no_results).grid(row=self.current_row, column=1, sticky='W')
|
501
|
-
self.add_label_and_increment_row(vad_frame, "Add audio even if no results are found by VAD.", row=self.current_row, column=2)
|
560
|
+
self.vad_beginning_offset.grid(row=self.current_row, column=3, sticky='EW', pady=2)
|
561
|
+
self.current_row += 1
|
502
562
|
|
503
|
-
|
563
|
+
HoverInfoLabelWidget(vad_frame, text="Cut and Splice Segments:",
|
564
|
+
tooltip="Cut Detected Voice Segments and Paste them back together. More Padding = More Space between voicelines.",
|
565
|
+
row=self.current_row, column=0)
|
504
566
|
self.cut_and_splice_segments = tk.BooleanVar(value=self.settings.vad.cut_and_splice_segments)
|
505
|
-
ttk.Checkbutton(vad_frame, variable=self.cut_and_splice_segments).grid(
|
506
|
-
|
507
|
-
|
508
|
-
|
567
|
+
ttk.Checkbutton(vad_frame, variable=self.cut_and_splice_segments, bootstyle="round-toggle").grid(
|
568
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
569
|
+
HoverInfoLabelWidget(vad_frame, text="Padding:",
|
570
|
+
tooltip="Cut Detected Voice Segments and Paste them back together. More Padding = More Space between voicelines.",
|
571
|
+
row=self.current_row, column=2)
|
509
572
|
self.splice_padding = ttk.Entry(vad_frame)
|
510
573
|
self.splice_padding.insert(0, str(self.settings.vad.splice_padding))
|
511
|
-
self.splice_padding.grid(row=self.current_row, column=
|
512
|
-
self.
|
574
|
+
self.splice_padding.grid(row=self.current_row, column=3, sticky='EW', pady=2)
|
575
|
+
self.current_row += 1
|
513
576
|
|
577
|
+
for col in range(5):
|
578
|
+
vad_frame.grid_columnconfigure(col, weight=0)
|
514
579
|
|
580
|
+
for row in range(self.current_row):
|
581
|
+
vad_frame.grid_rowconfigure(row, minsize=30)
|
515
582
|
|
516
583
|
@new_tab
|
517
584
|
def create_paths_tab(self):
|
518
|
-
paths_frame = ttk.Frame(self.notebook)
|
585
|
+
paths_frame = ttk.Frame(self.notebook, padding=15)
|
519
586
|
self.notebook.add(paths_frame, text='Paths')
|
520
587
|
|
521
|
-
|
588
|
+
HoverInfoLabelWidget(paths_frame, text="Folder to Watch:", tooltip="Path where the OBS Replays will be saved.",
|
589
|
+
foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
522
590
|
self.folder_to_watch = ttk.Entry(paths_frame, width=50)
|
523
591
|
self.folder_to_watch.insert(0, self.settings.paths.folder_to_watch)
|
524
|
-
self.folder_to_watch.grid(row=self.current_row, column=1)
|
525
|
-
ttk.Button(paths_frame, text="Browse", command=lambda: self.browse_folder(self.folder_to_watch)
|
592
|
+
self.folder_to_watch.grid(row=self.current_row, column=1, sticky='W', pady=2)
|
593
|
+
ttk.Button(paths_frame, text="Browse", command=lambda: self.browse_folder(self.folder_to_watch),
|
594
|
+
bootstyle="outline").grid(
|
526
595
|
row=self.current_row,
|
527
|
-
column=2)
|
528
|
-
self.
|
529
|
-
column=3)
|
596
|
+
column=2, padx=5, pady=2)
|
597
|
+
self.current_row += 1
|
530
598
|
|
531
|
-
|
599
|
+
HoverInfoLabelWidget(paths_frame, text="Audio Destination:", tooltip="Path where the cut Audio will be saved.",
|
600
|
+
foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
532
601
|
self.audio_destination = ttk.Entry(paths_frame, width=50)
|
533
602
|
self.audio_destination.insert(0, self.settings.paths.audio_destination)
|
534
|
-
self.audio_destination.grid(row=self.current_row, column=1)
|
535
|
-
ttk.Button(paths_frame, text="Browse", command=lambda: self.browse_folder(self.audio_destination)
|
603
|
+
self.audio_destination.grid(row=self.current_row, column=1, sticky='W', pady=2)
|
604
|
+
ttk.Button(paths_frame, text="Browse", command=lambda: self.browse_folder(self.audio_destination),
|
605
|
+
bootstyle="outline").grid(
|
536
606
|
row=self.current_row,
|
537
|
-
column=2)
|
538
|
-
self.
|
539
|
-
column=3)
|
607
|
+
column=2, padx=5, pady=2)
|
608
|
+
self.current_row += 1
|
540
609
|
|
541
|
-
|
610
|
+
HoverInfoLabelWidget(paths_frame, text="Screenshot Destination:",
|
611
|
+
tooltip="Path where the Screenshot will be saved.", foreground="dark orange",
|
612
|
+
font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
542
613
|
self.screenshot_destination = ttk.Entry(paths_frame, width=50)
|
543
614
|
self.screenshot_destination.insert(0, self.settings.paths.screenshot_destination)
|
544
|
-
self.screenshot_destination.grid(row=self.current_row, column=1)
|
545
|
-
ttk.Button(paths_frame, text="Browse", command=lambda: self.browse_folder(self.screenshot_destination)
|
546
|
-
|
547
|
-
|
548
|
-
|
615
|
+
self.screenshot_destination.grid(row=self.current_row, column=1, sticky='W', pady=2)
|
616
|
+
ttk.Button(paths_frame, text="Browse", command=lambda: self.browse_folder(self.screenshot_destination),
|
617
|
+
bootstyle="outline").grid(
|
618
|
+
row=self.current_row, column=2, padx=5, pady=2)
|
619
|
+
self.current_row += 1
|
549
620
|
|
550
|
-
|
621
|
+
HoverInfoLabelWidget(paths_frame, text="Remove Video:", tooltip="Remove video from the output.",
|
622
|
+
row=self.current_row, column=0)
|
551
623
|
self.remove_video = tk.BooleanVar(value=self.settings.paths.remove_video)
|
552
|
-
ttk.Checkbutton(paths_frame, variable=self.remove_video).grid(row=self.current_row,
|
553
|
-
|
624
|
+
ttk.Checkbutton(paths_frame, variable=self.remove_video, bootstyle="round-toggle").grid(row=self.current_row,
|
625
|
+
column=1, sticky='W',
|
626
|
+
pady=2)
|
627
|
+
self.current_row += 1
|
554
628
|
|
555
|
-
|
629
|
+
HoverInfoLabelWidget(paths_frame, text="Remove Audio:", tooltip="Remove audio from the output.",
|
630
|
+
row=self.current_row, column=0)
|
556
631
|
self.remove_audio = tk.BooleanVar(value=self.settings.paths.remove_audio)
|
557
|
-
ttk.Checkbutton(paths_frame, variable=self.remove_audio).grid(row=self.current_row,
|
558
|
-
|
632
|
+
ttk.Checkbutton(paths_frame, variable=self.remove_audio, bootstyle="round-toggle").grid(row=self.current_row,
|
633
|
+
column=1, sticky='W',
|
634
|
+
pady=2)
|
635
|
+
self.current_row += 1
|
559
636
|
|
560
|
-
|
637
|
+
HoverInfoLabelWidget(paths_frame, text="Remove Screenshot:", tooltip="Remove screenshots after processing.",
|
638
|
+
row=self.current_row, column=0)
|
561
639
|
self.remove_screenshot = tk.BooleanVar(value=self.settings.paths.remove_screenshot)
|
562
|
-
ttk.Checkbutton(paths_frame, variable=self.remove_screenshot).grid(
|
563
|
-
|
564
|
-
|
640
|
+
ttk.Checkbutton(paths_frame, variable=self.remove_screenshot, bootstyle="round-toggle").grid(
|
641
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
642
|
+
self.current_row += 1
|
643
|
+
|
644
|
+
paths_frame.grid_columnconfigure(0, weight=0)
|
645
|
+
paths_frame.grid_columnconfigure(1, weight=0)
|
646
|
+
paths_frame.grid_columnconfigure(2, weight=0)
|
647
|
+
|
648
|
+
for row in range(self.current_row):
|
649
|
+
paths_frame.grid_rowconfigure(row, minsize=30)
|
565
650
|
|
566
651
|
return paths_frame
|
567
652
|
|
@@ -579,182 +664,127 @@ class ConfigApp:
|
|
579
664
|
|
580
665
|
@new_tab
|
581
666
|
def create_anki_tab(self):
|
582
|
-
anki_frame = ttk.Frame(self.notebook)
|
667
|
+
anki_frame = ttk.Frame(self.notebook, padding=15)
|
583
668
|
self.notebook.add(anki_frame, text='Anki')
|
584
669
|
|
585
|
-
|
670
|
+
HoverInfoLabelWidget(anki_frame, text="Update Anki:", tooltip="Automatically update Anki with new data.",
|
671
|
+
row=self.current_row, column=0)
|
586
672
|
self.update_anki = tk.BooleanVar(value=self.settings.anki.update_anki)
|
587
|
-
ttk.Checkbutton(anki_frame, variable=self.update_anki).grid(row=self.current_row,
|
588
|
-
|
589
|
-
|
673
|
+
ttk.Checkbutton(anki_frame, variable=self.update_anki, bootstyle="round-toggle").grid(row=self.current_row,
|
674
|
+
column=1, sticky='W',
|
675
|
+
pady=2)
|
676
|
+
self.current_row += 1
|
590
677
|
|
591
|
-
|
678
|
+
HoverInfoLabelWidget(anki_frame, text="Anki URL:", tooltip="The URL to connect to your Anki instance.",
|
679
|
+
foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
592
680
|
self.anki_url = ttk.Entry(anki_frame, width=50)
|
593
681
|
self.anki_url.insert(0, self.settings.anki.url)
|
594
|
-
self.anki_url.grid(row=self.current_row, column=1)
|
595
|
-
self.
|
596
|
-
column=2)
|
682
|
+
self.anki_url.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
683
|
+
self.current_row += 1
|
597
684
|
|
598
|
-
|
685
|
+
HoverInfoLabelWidget(anki_frame, text="Sentence Field:", tooltip="Field in Anki for the main sentence.",
|
686
|
+
foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
599
687
|
self.sentence_field = ttk.Entry(anki_frame)
|
600
688
|
self.sentence_field.insert(0, self.settings.anki.sentence_field)
|
601
|
-
self.sentence_field.grid(row=self.current_row, column=1)
|
602
|
-
self.
|
603
|
-
column=2)
|
689
|
+
self.sentence_field.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
690
|
+
self.current_row += 1
|
604
691
|
|
605
|
-
|
692
|
+
HoverInfoLabelWidget(anki_frame, text="Sentence Audio Field:",
|
693
|
+
tooltip="Field in Anki for audio associated with the sentence. Leave Blank to Disable Audio Processing.",
|
694
|
+
foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
606
695
|
self.sentence_audio_field = ttk.Entry(anki_frame)
|
607
696
|
self.sentence_audio_field.insert(0, self.settings.anki.sentence_audio_field)
|
608
|
-
self.sentence_audio_field.grid(row=self.current_row, column=1)
|
609
|
-
self.
|
610
|
-
"Field in Anki for audio associated with the sentence. Leave Blank to Disable Audio Processing.",
|
611
|
-
row=self.current_row, column=2)
|
697
|
+
self.sentence_audio_field.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
698
|
+
self.current_row += 1
|
612
699
|
|
613
|
-
|
700
|
+
HoverInfoLabelWidget(anki_frame, text="Picture Field:", tooltip="Field in Anki for associated pictures.",
|
701
|
+
foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
614
702
|
self.picture_field = ttk.Entry(anki_frame)
|
615
703
|
self.picture_field.insert(0, self.settings.anki.picture_field)
|
616
|
-
self.picture_field.grid(row=self.current_row, column=1)
|
617
|
-
self.
|
618
|
-
column=2)
|
704
|
+
self.picture_field.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
705
|
+
self.current_row += 1
|
619
706
|
|
620
|
-
|
707
|
+
HoverInfoLabelWidget(anki_frame, text="Word Field:", tooltip="Field in Anki for individual words.",
|
708
|
+
foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
621
709
|
self.word_field = ttk.Entry(anki_frame)
|
622
710
|
self.word_field.insert(0, self.settings.anki.word_field)
|
623
|
-
self.word_field.grid(row=self.current_row, column=1)
|
624
|
-
self.
|
625
|
-
column=2)
|
711
|
+
self.word_field.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
712
|
+
self.current_row += 1
|
626
713
|
|
627
|
-
|
714
|
+
HoverInfoLabelWidget(anki_frame, text="Previous Sentence Field:",
|
715
|
+
tooltip="Field in Anki for the previous line of dialogue. If Empty, will not populate",
|
716
|
+
row=self.current_row, column=0)
|
628
717
|
self.previous_sentence_field = ttk.Entry(anki_frame)
|
629
718
|
self.previous_sentence_field.insert(0, self.settings.anki.previous_sentence_field)
|
630
|
-
self.previous_sentence_field.grid(row=self.current_row, column=1)
|
631
|
-
self.
|
632
|
-
"Field in Anki for the previous line of dialogue. If Empty, will not populate",
|
633
|
-
row=self.current_row,
|
634
|
-
column=2)
|
719
|
+
self.previous_sentence_field.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
720
|
+
self.current_row += 1
|
635
721
|
|
636
|
-
|
722
|
+
HoverInfoLabelWidget(anki_frame, text="Previous VoiceLine SS Field:",
|
723
|
+
tooltip="Field in Anki for the screenshot of previous line. If Empty, will not populate",
|
724
|
+
row=self.current_row, column=0)
|
637
725
|
self.previous_image_field = ttk.Entry(anki_frame)
|
638
726
|
self.previous_image_field.insert(0, self.settings.anki.previous_image_field)
|
639
|
-
self.previous_image_field.grid(row=self.current_row, column=1)
|
640
|
-
self.
|
641
|
-
"Field in Anki for the screenshot of previous line. If Empty, will not populate",
|
642
|
-
row=self.current_row,
|
643
|
-
column=2)
|
727
|
+
self.previous_image_field.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
728
|
+
self.current_row += 1
|
644
729
|
|
645
|
-
|
730
|
+
HoverInfoLabelWidget(anki_frame, text="Add Tags:", tooltip="Comma-separated custom tags for the Anki cards.",
|
731
|
+
row=self.current_row, column=0)
|
646
732
|
self.custom_tags = ttk.Entry(anki_frame, width=50)
|
647
733
|
self.custom_tags.insert(0, ', '.join(self.settings.anki.custom_tags))
|
648
|
-
self.custom_tags.grid(row=self.current_row, column=1)
|
649
|
-
self.
|
650
|
-
row=self.current_row, column=2)
|
734
|
+
self.custom_tags.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
735
|
+
self.current_row += 1
|
651
736
|
|
652
|
-
|
737
|
+
HoverInfoLabelWidget(anki_frame, text="Tags to work on:",
|
738
|
+
tooltip="Comma-separated Tags, script will only do 1-click on cards with these tags (Recommend keep empty, or use Yomitan Profile to add custom tag from texthooker page)",
|
739
|
+
foreground="green", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
653
740
|
self.tags_to_check = ttk.Entry(anki_frame, width=50)
|
654
741
|
self.tags_to_check.insert(0, ', '.join(self.settings.anki.tags_to_check))
|
655
|
-
self.tags_to_check.grid(row=self.current_row, column=1)
|
656
|
-
self.
|
657
|
-
"Comma-separated Tags, script will only do 1-click on cards with these tags (Recommend keep empty, or use Yomitan Profile to add custom tag from texthooker page)",
|
658
|
-
row=self.current_row, column=2)
|
742
|
+
self.tags_to_check.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
743
|
+
self.current_row += 1
|
659
744
|
|
660
|
-
|
745
|
+
HoverInfoLabelWidget(anki_frame, text="Add Game as Tag:",
|
746
|
+
tooltip="Include a tag for the game on the Anki card.", foreground="green",
|
747
|
+
font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
661
748
|
self.add_game_tag = tk.BooleanVar(value=self.settings.anki.add_game_tag)
|
662
|
-
ttk.Checkbutton(anki_frame, variable=self.add_game_tag).grid(row=self.current_row,
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
ttk.Label(anki_frame, text="Polling Rate:").grid(row=self.current_row, column=0, sticky='W')
|
667
|
-
self.polling_rate = ttk.Entry(anki_frame)
|
668
|
-
self.polling_rate.insert(0, str(self.settings.anki.polling_rate))
|
669
|
-
self.polling_rate.grid(row=self.current_row, column=1)
|
670
|
-
self.add_label_and_increment_row(anki_frame, "Rate at which Anki will check for updates (in milliseconds).",
|
671
|
-
row=self.current_row, column=2)
|
749
|
+
ttk.Checkbutton(anki_frame, variable=self.add_game_tag, bootstyle="round-toggle").grid(row=self.current_row,
|
750
|
+
column=1, sticky='W',
|
751
|
+
pady=2)
|
752
|
+
self.current_row += 1
|
672
753
|
|
673
|
-
|
754
|
+
HoverInfoLabelWidget(anki_frame, text="Overwrite Audio:", tooltip="Overwrite existing audio in Anki cards.",
|
755
|
+
row=self.current_row, column=0)
|
674
756
|
self.overwrite_audio = tk.BooleanVar(
|
675
757
|
value=self.settings.anki.overwrite_audio)
|
676
|
-
ttk.Checkbutton(anki_frame, variable=self.overwrite_audio).grid(row=self.current_row,
|
677
|
-
|
678
|
-
|
758
|
+
ttk.Checkbutton(anki_frame, variable=self.overwrite_audio, bootstyle="round-toggle").grid(row=self.current_row,
|
759
|
+
column=1, sticky='W',
|
760
|
+
pady=2)
|
761
|
+
self.current_row += 1
|
679
762
|
|
680
|
-
|
763
|
+
HoverInfoLabelWidget(anki_frame, text="Overwrite Picture:",
|
764
|
+
tooltip="Overwrite existing pictures in Anki cards.", row=self.current_row, column=0)
|
681
765
|
self.overwrite_picture = tk.BooleanVar(
|
682
766
|
value=self.settings.anki.overwrite_picture)
|
683
|
-
ttk.Checkbutton(anki_frame, variable=self.overwrite_picture).grid(
|
684
|
-
|
685
|
-
|
767
|
+
ttk.Checkbutton(anki_frame, variable=self.overwrite_picture, bootstyle="round-toggle").grid(
|
768
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
769
|
+
self.current_row += 1
|
686
770
|
|
687
|
-
|
771
|
+
HoverInfoLabelWidget(anki_frame, text="Multi-line Mining Overwrite Sentence:",
|
772
|
+
tooltip="When using Multi-line Mining, overwrite the sentence with a concatenation of the lines selected.",
|
773
|
+
row=self.current_row, column=0)
|
688
774
|
self.multi_overwrites_sentence = tk.BooleanVar(
|
689
775
|
value=self.settings.anki.multi_overwrites_sentence)
|
690
|
-
ttk.Checkbutton(anki_frame, variable=self.multi_overwrites_sentence).grid(
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
self.anki_custom_fields = self.settings.anki.anki_custom_fields
|
695
|
-
self.custom_field_entries = []
|
776
|
+
ttk.Checkbutton(anki_frame, variable=self.multi_overwrites_sentence, bootstyle="round-toggle").grid(
|
777
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
778
|
+
self.current_row += 1
|
696
779
|
|
697
|
-
|
780
|
+
anki_frame.grid_columnconfigure(0, weight=0)
|
781
|
+
anki_frame.grid_columnconfigure(1, weight=0)
|
698
782
|
|
699
|
-
|
700
|
-
|
701
|
-
column=0, pady=5)
|
702
|
-
self.add_label_and_increment_row(anki_frame, "Add a new custom field for Anki cards.", row=self.current_row,
|
703
|
-
column=2)
|
704
|
-
self.display_custom_fields(anki_frame, self.current_row)
|
783
|
+
for row in range(self.current_row):
|
784
|
+
anki_frame.grid_rowconfigure(row, minsize=30)
|
705
785
|
|
706
786
|
return anki_frame
|
707
787
|
|
708
|
-
def add_custom_field(self, frame, start_row):
|
709
|
-
row = len(self.custom_field_entries) + 1 + start_row
|
710
|
-
|
711
|
-
key_entry = ttk.Entry(frame)
|
712
|
-
key_entry.grid(row=row, column=0, padx=5, pady=2, sticky='W')
|
713
|
-
value_entry = ttk.Entry(frame)
|
714
|
-
value_entry.grid(row=row, column=1, padx=5, pady=2, sticky='W')
|
715
|
-
|
716
|
-
# Create a delete button for this custom field
|
717
|
-
delete_button = ttk.Button(frame, text="X",
|
718
|
-
command=lambda: self.delete_custom_field(row, key_entry, value_entry, delete_button))
|
719
|
-
delete_button.grid(row=row, column=2, padx=5, pady=2)
|
720
|
-
|
721
|
-
self.custom_field_entries.append((key_entry, value_entry, delete_button))
|
722
|
-
|
723
|
-
def display_custom_fields(self, frame, start_row):
|
724
|
-
for row, (key, value) in enumerate(self.anki_custom_fields.items()):
|
725
|
-
key_entry = ttk.Entry(frame)
|
726
|
-
key_entry.insert(0, key)
|
727
|
-
key_entry.grid(row=row + start_row, column=0, padx=5, pady=2, sticky='W')
|
728
|
-
|
729
|
-
value_entry = ttk.Entry(frame)
|
730
|
-
value_entry.insert(0, value)
|
731
|
-
value_entry.grid(row=row + start_row, column=1, padx=5, pady=2, sticky='W')
|
732
|
-
|
733
|
-
# Create a delete button for each existing custom field
|
734
|
-
delete_button = ttk.Button(frame, text="X",
|
735
|
-
command=lambda: self.delete_custom_field(row + start_row, key_entry, value_entry,
|
736
|
-
delete_button))
|
737
|
-
delete_button.grid(row=row + start_row, column=2, padx=5, pady=2)
|
738
|
-
|
739
|
-
self.custom_field_entries.append((key_entry, value_entry, delete_button))
|
740
|
-
|
741
|
-
def delete_custom_field(self, row, key_entry, value_entry, delete_button):
|
742
|
-
# Remove the entry from the GUI
|
743
|
-
key_entry.destroy()
|
744
|
-
value_entry.destroy()
|
745
|
-
delete_button.destroy()
|
746
|
-
|
747
|
-
# Remove the entry from the custom field entries list
|
748
|
-
self.custom_field_entries.remove((key_entry, value_entry, delete_button))
|
749
|
-
|
750
|
-
# Update the GUI rows below to fill the gap if necessary
|
751
|
-
# for (ke, ve, db) in self.custom_field_entries:
|
752
|
-
# if self.custom_field_entries.index((ke, ve, db)) > self.custom_field_entries.index(
|
753
|
-
# (key_entry, value_entry, delete_button)):
|
754
|
-
# ke.grid_configure(row=ke.grid_info()['row'] - 1)
|
755
|
-
# ve.grid_configure(row=ve.grid_info()['row'] - 1)
|
756
|
-
# db.grid_configure(row=db.grid_info()['row'] - 1)
|
757
|
-
|
758
788
|
def on_profiles_tab_selected(self, event):
|
759
789
|
try:
|
760
790
|
if self.window.state() != "withdrawn" and self.notebook.tab(self.notebook.select(), "text") == "Profiles":
|
@@ -764,129 +794,155 @@ class ConfigApp:
|
|
764
794
|
|
765
795
|
@new_tab
|
766
796
|
def create_features_tab(self):
|
767
|
-
features_frame = ttk.Frame(self.notebook)
|
797
|
+
features_frame = ttk.Frame(self.notebook, padding=15)
|
768
798
|
self.notebook.add(features_frame, text='Features')
|
769
799
|
|
770
|
-
|
800
|
+
HoverInfoLabelWidget(features_frame, text="Notify on Update:", tooltip="Notify the user when an update occurs.",
|
801
|
+
row=self.current_row, column=0)
|
771
802
|
self.notify_on_update = tk.BooleanVar(value=self.settings.features.notify_on_update)
|
772
|
-
ttk.Checkbutton(features_frame, variable=self.notify_on_update).grid(
|
773
|
-
|
774
|
-
|
803
|
+
ttk.Checkbutton(features_frame, variable=self.notify_on_update, bootstyle="round-toggle").grid(
|
804
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
805
|
+
self.current_row += 1
|
775
806
|
|
776
|
-
|
807
|
+
HoverInfoLabelWidget(features_frame, text="Open Anki Edit:",
|
808
|
+
tooltip="Automatically open Anki for editing after updating.", row=self.current_row,
|
809
|
+
column=0)
|
777
810
|
self.open_anki_edit = tk.BooleanVar(value=self.settings.features.open_anki_edit)
|
778
|
-
ttk.Checkbutton(features_frame, variable=self.open_anki_edit).grid(
|
779
|
-
|
780
|
-
|
811
|
+
ttk.Checkbutton(features_frame, variable=self.open_anki_edit, bootstyle="round-toggle").grid(
|
812
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
813
|
+
self.current_row += 1
|
781
814
|
|
782
|
-
|
815
|
+
HoverInfoLabelWidget(features_frame, text="Open Anki Note in Browser:",
|
816
|
+
tooltip="Open Anki note in browser after updating.", row=self.current_row, column=0)
|
783
817
|
self.open_anki_browser = tk.BooleanVar(value=self.settings.features.open_anki_in_browser)
|
784
|
-
ttk.Checkbutton(features_frame, variable=self.open_anki_browser).grid(
|
785
|
-
|
786
|
-
|
818
|
+
ttk.Checkbutton(features_frame, variable=self.open_anki_browser, bootstyle="round-toggle").grid(
|
819
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
820
|
+
self.current_row += 1
|
787
821
|
|
788
|
-
|
822
|
+
HoverInfoLabelWidget(features_frame, text="Browser Query:",
|
823
|
+
tooltip="Query to use when opening Anki notes in the browser. Ex: 'Added:1'",
|
824
|
+
row=self.current_row, column=0)
|
789
825
|
self.browser_query = ttk.Entry(features_frame, width=50)
|
790
826
|
self.browser_query.insert(0, self.settings.features.browser_query)
|
791
|
-
self.browser_query.grid(row=self.current_row, column=1)
|
792
|
-
self.
|
793
|
-
column=2)
|
827
|
+
self.browser_query.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
828
|
+
self.current_row += 1
|
794
829
|
|
795
|
-
|
830
|
+
HoverInfoLabelWidget(features_frame, text="Backfill Audio:", tooltip="Fill in audio data for existing entries.",
|
831
|
+
row=self.current_row, column=0)
|
796
832
|
self.backfill_audio = tk.BooleanVar(value=self.settings.features.backfill_audio)
|
797
|
-
ttk.Checkbutton(features_frame, variable=self.backfill_audio).grid(
|
798
|
-
|
799
|
-
|
833
|
+
ttk.Checkbutton(features_frame, variable=self.backfill_audio, bootstyle="round-toggle").grid(
|
834
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
835
|
+
self.current_row += 1
|
800
836
|
|
801
|
-
|
837
|
+
HoverInfoLabelWidget(features_frame, text="Full Auto Mode:", tooltip="Yomitan 1-click anki card creation.",
|
838
|
+
row=self.current_row, column=0)
|
802
839
|
self.full_auto = tk.BooleanVar(
|
803
840
|
value=self.settings.features.full_auto)
|
804
|
-
ttk.Checkbutton(features_frame, variable=self.full_auto).grid(row=self.current_row,
|
805
|
-
|
806
|
-
|
841
|
+
ttk.Checkbutton(features_frame, variable=self.full_auto, bootstyle="round-toggle").grid(row=self.current_row,
|
842
|
+
column=1, sticky='W',
|
843
|
+
pady=2)
|
844
|
+
self.current_row += 1
|
845
|
+
|
846
|
+
for col in range(3):
|
847
|
+
features_frame.grid_columnconfigure(col, weight=0)
|
848
|
+
|
849
|
+
for row in range(self.current_row):
|
850
|
+
features_frame.grid_rowconfigure(row, minsize=30)
|
807
851
|
|
808
852
|
@new_tab
|
809
853
|
def create_screenshot_tab(self):
|
810
|
-
screenshot_frame = ttk.Frame(self.notebook)
|
854
|
+
screenshot_frame = ttk.Frame(self.notebook, padding=15)
|
811
855
|
self.notebook.add(screenshot_frame, text='Screenshot')
|
812
856
|
|
813
|
-
|
857
|
+
HoverInfoLabelWidget(screenshot_frame, text="Enabled:", tooltip="Enable or disable screenshot processing.",
|
858
|
+
row=self.current_row, column=0)
|
814
859
|
self.screenshot_enabled = tk.BooleanVar(value=self.settings.screenshot.enabled)
|
815
|
-
ttk.Checkbutton(screenshot_frame, variable=self.screenshot_enabled).grid(
|
816
|
-
|
860
|
+
ttk.Checkbutton(screenshot_frame, variable=self.screenshot_enabled, bootstyle="round-toggle").grid(
|
861
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
862
|
+
self.current_row += 1
|
817
863
|
|
818
|
-
|
864
|
+
HoverInfoLabelWidget(screenshot_frame, text="Width:", tooltip="Width of the screenshot in pixels.",
|
865
|
+
row=self.current_row, column=0)
|
819
866
|
self.screenshot_width = ttk.Entry(screenshot_frame)
|
820
867
|
self.screenshot_width.insert(0, str(self.settings.screenshot.width))
|
821
|
-
self.screenshot_width.grid(row=self.current_row, column=1)
|
822
|
-
self.
|
823
|
-
column=2)
|
868
|
+
self.screenshot_width.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
869
|
+
self.current_row += 1
|
824
870
|
|
825
|
-
|
871
|
+
HoverInfoLabelWidget(screenshot_frame, text="Height:", tooltip="Height of the screenshot in pixels.",
|
872
|
+
row=self.current_row, column=0)
|
826
873
|
self.screenshot_height = ttk.Entry(screenshot_frame)
|
827
874
|
self.screenshot_height.insert(0, str(self.settings.screenshot.height))
|
828
|
-
self.screenshot_height.grid(row=self.current_row, column=1)
|
829
|
-
self.
|
830
|
-
column=2)
|
875
|
+
self.screenshot_height.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
876
|
+
self.current_row += 1
|
831
877
|
|
832
|
-
|
878
|
+
HoverInfoLabelWidget(screenshot_frame, text="Quality:", tooltip="Quality of the screenshot (0-100).",
|
879
|
+
row=self.current_row, column=0)
|
833
880
|
self.screenshot_quality = ttk.Entry(screenshot_frame)
|
834
881
|
self.screenshot_quality.insert(0, str(self.settings.screenshot.quality))
|
835
|
-
self.screenshot_quality.grid(row=self.current_row, column=1)
|
836
|
-
self.
|
837
|
-
column=2)
|
838
|
-
|
839
|
-
ttk.Label(screenshot_frame, text="Extension:").grid(row=self.current_row, column=0, sticky='W')
|
840
|
-
self.screenshot_extension = ttk.Combobox(screenshot_frame, values=['webp', 'avif', 'png', 'jpeg'])
|
841
|
-
self.screenshot_extension.insert(0, self.settings.screenshot.extension)
|
842
|
-
self.screenshot_extension.grid(row=self.current_row, column=1)
|
843
|
-
self.add_label_and_increment_row(screenshot_frame, "File extension for the screenshot format.",
|
844
|
-
row=self.current_row, column=2)
|
845
|
-
|
846
|
-
ttk.Label(screenshot_frame, text="FFmpeg Reencode Options:").grid(row=self.current_row, column=0, sticky='W')
|
847
|
-
self.screenshot_custom_ffmpeg_settings = ttk.Entry(screenshot_frame, width=50)
|
848
|
-
self.screenshot_custom_ffmpeg_settings.insert(0, self.settings.screenshot.custom_ffmpeg_settings)
|
849
|
-
self.screenshot_custom_ffmpeg_settings.grid(row=self.current_row, column=1)
|
850
|
-
self.add_label_and_increment_row(screenshot_frame, "Custom FFmpeg options for re-encoding screenshots.",
|
851
|
-
row=self.current_row, column=2)
|
882
|
+
self.screenshot_quality.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
883
|
+
self.current_row += 1
|
852
884
|
|
885
|
+
HoverInfoLabelWidget(screenshot_frame, text="Extension:", tooltip="File extension for the screenshot format.",
|
886
|
+
row=self.current_row, column=0)
|
887
|
+
self.screenshot_extension = ttk.Combobox(screenshot_frame, values=['webp', 'avif', 'png', 'jpeg'],
|
888
|
+
state="readonly")
|
889
|
+
self.screenshot_extension.set(self.settings.screenshot.extension)
|
890
|
+
self.screenshot_extension.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
891
|
+
self.current_row += 1
|
853
892
|
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
self.
|
858
|
-
|
893
|
+
HoverInfoLabelWidget(screenshot_frame, text="FFmpeg Reencode Options:",
|
894
|
+
tooltip="Custom FFmpeg options for re-encoding screenshots.", foreground="red",
|
895
|
+
font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
896
|
+
self.screenshot_custom_ffmpeg_settings = ttk.Entry(screenshot_frame, width=50)
|
897
|
+
self.screenshot_custom_ffmpeg_settings.insert(0, self.settings.screenshot.custom_ffmpeg_settings)
|
898
|
+
self.screenshot_custom_ffmpeg_settings.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
899
|
+
self.current_row += 1
|
859
900
|
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
self.screenshot_timing.
|
864
|
-
self.
|
865
|
-
|
901
|
+
HoverInfoLabelWidget(screenshot_frame, text="Screenshot Timing:",
|
902
|
+
tooltip="Select when to take the screenshot relative to the line: beginning, middle, or end.",
|
903
|
+
row=self.current_row, column=0)
|
904
|
+
self.screenshot_timing = ttk.Combobox(screenshot_frame, values=['beginning', 'middle', 'end'], state="readonly")
|
905
|
+
self.screenshot_timing.set(self.settings.screenshot.screenshot_timing_setting)
|
906
|
+
self.screenshot_timing.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
907
|
+
self.current_row += 1
|
866
908
|
|
867
|
-
|
909
|
+
HoverInfoLabelWidget(screenshot_frame, text="Screenshot Offset:",
|
910
|
+
tooltip="Time in seconds to offset the screenshot based on the Timing setting above (should almost always be positive, can be negative if you use \"middle\")",
|
911
|
+
foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
868
912
|
self.seconds_after_line = ttk.Entry(screenshot_frame)
|
869
913
|
self.seconds_after_line.insert(0, str(self.settings.screenshot.seconds_after_line))
|
870
|
-
self.seconds_after_line.grid(row=self.current_row, column=1)
|
871
|
-
self.
|
872
|
-
column=2)
|
914
|
+
self.seconds_after_line.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
915
|
+
self.current_row += 1
|
873
916
|
|
874
|
-
|
917
|
+
HoverInfoLabelWidget(screenshot_frame, text="Use Screenshot Selector for every card:",
|
918
|
+
tooltip="Enable to use the screenshot selector to choose the screenshot point on every card.",
|
919
|
+
row=self.current_row, column=0)
|
875
920
|
self.use_screenshot_selector = tk.BooleanVar(value=self.settings.screenshot.use_screenshot_selector)
|
876
|
-
ttk.Checkbutton(screenshot_frame, variable=self.use_screenshot_selector).grid(
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
921
|
+
ttk.Checkbutton(screenshot_frame, variable=self.use_screenshot_selector, bootstyle="round-toggle").grid(
|
922
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
923
|
+
self.current_row += 1
|
924
|
+
|
925
|
+
HoverInfoLabelWidget(screenshot_frame, text="Take Screenshot Hotkey:", tooltip="Hotkey to take a screenshot.",
|
926
|
+
row=self.current_row, column=0)
|
927
|
+
self.take_screenshot_hotkey = ttk.Entry(screenshot_frame)
|
928
|
+
self.take_screenshot_hotkey.insert(0, self.settings.hotkeys.take_screenshot)
|
929
|
+
self.take_screenshot_hotkey.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
930
|
+
self.current_row += 1
|
931
|
+
|
932
|
+
HoverInfoLabelWidget(screenshot_frame, text="Screenshot Hotkey Updates Anki:",
|
933
|
+
tooltip="Enable to allow Screenshot hotkey/button to update the latest anki card.",
|
934
|
+
row=self.current_row, column=0)
|
935
|
+
self.screenshot_hotkey_update_anki = tk.BooleanVar(
|
936
|
+
value=self.settings.screenshot.screenshot_hotkey_updates_anki)
|
937
|
+
ttk.Checkbutton(screenshot_frame, variable=self.screenshot_hotkey_update_anki, bootstyle="round-toggle").grid(
|
938
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
939
|
+
self.current_row += 1
|
940
|
+
|
941
|
+
for col in range(3):
|
942
|
+
screenshot_frame.grid_columnconfigure(col, weight=0)
|
943
|
+
|
944
|
+
for row in range(self.current_row):
|
945
|
+
screenshot_frame.grid_rowconfigure(row, minsize=30)
|
890
946
|
|
891
947
|
def update_audio_ffmpeg_settings(self, event):
|
892
948
|
selected_option = self.ffmpeg_audio_preset_options.get()
|
@@ -899,209 +955,281 @@ class ConfigApp:
|
|
899
955
|
|
900
956
|
@new_tab
|
901
957
|
def create_audio_tab(self):
|
902
|
-
audio_frame = ttk.Frame(self.notebook)
|
958
|
+
audio_frame = ttk.Frame(self.notebook, padding=15)
|
903
959
|
self.notebook.add(audio_frame, text='Audio')
|
904
960
|
|
905
|
-
|
961
|
+
HoverInfoLabelWidget(audio_frame, text="Enabled:", tooltip="Enable or disable audio processing.",
|
962
|
+
row=self.current_row, column=0)
|
906
963
|
self.audio_enabled = tk.BooleanVar(value=self.settings.audio.enabled)
|
907
|
-
ttk.Checkbutton(audio_frame, variable=self.audio_enabled).grid(row=self.current_row,
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
self.audio_extension = ttk.Combobox(audio_frame, values=['opus', 'mp3', 'ogg', 'aac', 'm4a'])
|
912
|
-
self.audio_extension.insert(0, self.settings.audio.extension)
|
913
|
-
self.audio_extension.grid(row=self.current_row, column=1)
|
914
|
-
self.add_label_and_increment_row(audio_frame, "File extension for audio files.", row=self.current_row, column=2)
|
964
|
+
ttk.Checkbutton(audio_frame, variable=self.audio_enabled, bootstyle="round-toggle").grid(row=self.current_row,
|
965
|
+
column=1, sticky='W',
|
966
|
+
pady=2)
|
967
|
+
self.current_row += 1
|
915
968
|
|
969
|
+
HoverInfoLabelWidget(audio_frame, text="Audio Extension:", tooltip="File extension for audio files.",
|
970
|
+
row=self.current_row, column=0)
|
971
|
+
self.audio_extension = ttk.Combobox(audio_frame, values=['opus', 'mp3', 'ogg', 'aac', 'm4a'], state="readonly")
|
972
|
+
self.audio_extension.set(self.settings.audio.extension)
|
973
|
+
self.audio_extension.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
974
|
+
self.current_row += 1
|
916
975
|
|
917
|
-
|
976
|
+
HoverInfoLabelWidget(audio_frame, text="Audio Extraction Beginning Offset:",
|
977
|
+
tooltip="Offset in seconds from beginning of the video to extract (Should Usually be negative or 0).",
|
978
|
+
foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
918
979
|
self.beginning_offset = ttk.Entry(audio_frame)
|
919
980
|
self.beginning_offset.insert(0, str(self.settings.audio.beginning_offset))
|
920
|
-
self.beginning_offset.grid(row=self.current_row, column=1)
|
921
|
-
|
922
|
-
|
981
|
+
self.beginning_offset.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
982
|
+
|
983
|
+
ttk.Button(audio_frame, text="Find Offset (WIP)", command=self.call_audio_offset_selector, bootstyle="info").grid(
|
984
|
+
row=self.current_row, column=2, sticky='EW', pady=2, padx=5)
|
985
|
+
|
986
|
+
self.current_row += 1
|
923
987
|
|
924
|
-
|
988
|
+
|
989
|
+
HoverInfoLabelWidget(audio_frame, text="Audio Extraction End Offset:",
|
990
|
+
tooltip="Offset in seconds to trim from the end before VAD processing starts. Warning: May Result in lost audio if negative.",
|
991
|
+
foreground="red", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
925
992
|
self.pre_vad_audio_offset = ttk.Entry(audio_frame)
|
926
993
|
self.pre_vad_audio_offset.insert(0, str(self.settings.audio.pre_vad_end_offset))
|
927
|
-
self.pre_vad_audio_offset.grid(row=self.current_row, column=1)
|
928
|
-
self.
|
929
|
-
row=self.current_row, column=2)
|
930
|
-
|
994
|
+
self.pre_vad_audio_offset.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
995
|
+
self.current_row += 1
|
931
996
|
|
932
|
-
|
997
|
+
HoverInfoLabelWidget(audio_frame, text="FFmpeg Preset Options:",
|
998
|
+
tooltip="Select a preset FFmpeg option for re-encoding screenshots.", row=self.current_row,
|
999
|
+
column=0)
|
933
1000
|
|
934
1001
|
# Define display names and their corresponding values
|
935
1002
|
self.ffmpeg_audio_preset_options_map = {
|
936
|
-
"No Re-encode"
|
937
|
-
"Simple Fade-in, Avoids Audio Clipping (Default)": "-c:a
|
938
|
-
"Simple loudness normalization (Simplest, Start Here)": "-c:a
|
939
|
-
"Downmix to mono with normalization (Recommended(?))": "-c:a
|
940
|
-
"Downmix to mono, 30kbps, normalized (Optimal(?))": "-c:a
|
1003
|
+
"No Re-encode": "",
|
1004
|
+
"Simple Fade-in, Avoids Audio Clipping (Default)": "-c:a {encoder} -f {format} -af \"afade=t=in:d=0.10\"",
|
1005
|
+
"Simple loudness normalization (Simplest, Start Here)": "-c:a {encoder} -f {format} -af \"loudnorm=I=-23:TP=-2,afade=t=in:d=0.10\"",
|
1006
|
+
"Downmix to mono with normalization (Recommended(?))": "-c:a {encoder} -ac 1 -f {format} -af \"loudnorm=I=-23:TP=-2:dual_mono=true,afade=t=in:d=0.10\"",
|
1007
|
+
"Downmix to mono, 30kbps, normalized (Optimal(?))": "-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\"",
|
941
1008
|
"Custom": get_config().audio.custom_encode_settings,
|
942
1009
|
}
|
943
1010
|
|
944
1011
|
# Create a Combobox with display names
|
945
|
-
self.ffmpeg_audio_preset_options = ttk.Combobox(audio_frame,
|
1012
|
+
self.ffmpeg_audio_preset_options = ttk.Combobox(audio_frame,
|
1013
|
+
values=list(self.ffmpeg_audio_preset_options_map.keys()),
|
1014
|
+
width=50, state="readonly")
|
946
1015
|
# self.ffmpeg_preset_options.set("Downmix to mono with normalization") # Set default display name
|
947
|
-
self.ffmpeg_audio_preset_options.grid(row=self.current_row, column=1)
|
1016
|
+
self.ffmpeg_audio_preset_options.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
948
1017
|
|
949
1018
|
# Bind selection to update settings
|
950
1019
|
self.ffmpeg_audio_preset_options.bind("<<ComboboxSelected>>", self.update_audio_ffmpeg_settings)
|
1020
|
+
self.current_row += 1
|
951
1021
|
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
ttk.Label(audio_frame, text="FFmpeg Reencode Options:").grid(row=self.current_row, column=0, sticky='W')
|
1022
|
+
HoverInfoLabelWidget(audio_frame, text="FFmpeg Reencode Options:",
|
1023
|
+
tooltip="Custom FFmpeg options for re-encoding audio files.", foreground="red",
|
1024
|
+
font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
956
1025
|
self.audio_ffmpeg_reencode_options = ttk.Entry(audio_frame, width=50)
|
957
1026
|
self.audio_ffmpeg_reencode_options.insert(0, self.settings.audio.ffmpeg_reencode_options)
|
958
|
-
self.audio_ffmpeg_reencode_options.grid(row=self.current_row, column=1)
|
959
|
-
self.
|
960
|
-
row=self.current_row, column=2)
|
961
|
-
|
1027
|
+
self.audio_ffmpeg_reencode_options.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1028
|
+
self.current_row += 1
|
962
1029
|
|
963
|
-
|
1030
|
+
HoverInfoLabelWidget(audio_frame, text="Anki Media Collection:",
|
1031
|
+
tooltip="Path of the Anki Media Collection, used for external Trimming tool. NO TRAILING SLASH",
|
1032
|
+
foreground="green", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
964
1033
|
self.anki_media_collection = ttk.Entry(audio_frame)
|
965
1034
|
self.anki_media_collection.insert(0, self.settings.audio.anki_media_collection)
|
966
|
-
self.anki_media_collection.grid(row=self.current_row, column=1)
|
967
|
-
self.
|
968
|
-
"Path of the Anki Media Collection, used for external Trimming tool. NO TRAILING SLASH",
|
969
|
-
row=self.current_row,
|
970
|
-
column=2)
|
1035
|
+
self.anki_media_collection.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1036
|
+
self.current_row += 1
|
971
1037
|
|
972
|
-
|
1038
|
+
HoverInfoLabelWidget(audio_frame, text="External Audio Editing Tool:",
|
1039
|
+
tooltip="Path to External tool that opens the audio up for manual trimming. I recommend OcenAudio for in-place Editing.",
|
1040
|
+
foreground="green", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
973
1041
|
self.external_tool = ttk.Entry(audio_frame)
|
974
1042
|
self.external_tool.insert(0, self.settings.audio.external_tool)
|
975
|
-
self.external_tool.grid(row=self.current_row, column=1)
|
976
|
-
ttk.Label(audio_frame, text="Enabled:").grid(row=self.current_row, column=2, sticky='W')
|
1043
|
+
self.external_tool.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
977
1044
|
self.external_tool_enabled = tk.BooleanVar(value=self.settings.audio.external_tool_enabled)
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
1045
|
+
HoverInfoLabelWidget(audio_frame, text="Enabled:", tooltip="Send Audio to External Tool for Editing.",
|
1046
|
+
row=self.current_row, column=2, foreground="green", font=("Helvetica", 10, "bold"))
|
1047
|
+
ttk.Checkbutton(audio_frame, variable=self.external_tool_enabled, bootstyle="round-toggle").grid(
|
1048
|
+
row=self.current_row, column=3, sticky='W', padx=10, pady=5)
|
1049
|
+
self.current_row += 1
|
983
1050
|
|
984
|
-
ttk.Button(audio_frame, text="Install Ocenaudio", command=self.download_and_install_ocen
|
1051
|
+
ttk.Button(audio_frame, text="Install Ocenaudio", command=self.download_and_install_ocen,
|
1052
|
+
bootstyle="info").grid(
|
985
1053
|
row=self.current_row, column=0, pady=5)
|
986
1054
|
ttk.Button(audio_frame, text="Get Anki Media Collection",
|
987
|
-
command=self.set_default_anki_media_collection).grid(row=self.current_row,
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
1055
|
+
command=self.set_default_anki_media_collection, bootstyle="info").grid(row=self.current_row,
|
1056
|
+
column=1, pady=5)
|
1057
|
+
self.current_row += 1
|
1058
|
+
|
1059
|
+
for col in range(5):
|
1060
|
+
audio_frame.grid_columnconfigure(col, weight=0)
|
1061
|
+
|
1062
|
+
for row in range(self.current_row):
|
1063
|
+
audio_frame.grid_rowconfigure(row, minsize=30)
|
1064
|
+
|
1065
|
+
|
1066
|
+
def call_audio_offset_selector(self):
|
1067
|
+
try:
|
1068
|
+
# if is_dev:
|
1069
|
+
# path, beginning_offset, end_offset = r"C:\Users\Beangate\GSM\Electron App\test\tmphd01whan_untrimmed.opus", 500, 0
|
1070
|
+
# else:
|
1071
|
+
path, beginning_offset, end_offset = gsm_state.previous_trim_args
|
1072
|
+
# Get the directory of the current script
|
1073
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
1074
|
+
# Construct the path to the audio offset selector script
|
1075
|
+
script_path = os.path.join(current_dir,
|
1076
|
+
"audio_offset_selector.py") # Replace with the actual script name if different
|
1077
|
+
|
1078
|
+
logger.info(' '.join([sys.executable, "-m", "GameSentenceMiner.util.audio_offset_selector",
|
1079
|
+
"--path", path, "--beginning_offset", str(beginning_offset), "--end_offset",
|
1080
|
+
str(end_offset)]))
|
1081
|
+
|
1082
|
+
# Run the script using subprocess.run()
|
1083
|
+
result = subprocess.run(
|
1084
|
+
[sys.executable, "-m", "GameSentenceMiner.util.audio_offset_selector",
|
1085
|
+
"--path", path, "--beginning_offset", str(beginning_offset), "--end_offset", str(end_offset)],
|
1086
|
+
capture_output=True,
|
1087
|
+
text=True, # Get output as text
|
1088
|
+
check=False # Raise an exception for non-zero exit codes
|
1089
|
+
)
|
1090
|
+
if result.returncode != 0:
|
1091
|
+
logger.error(f"Script failed with return code: {result.returncode}")
|
1092
|
+
return None
|
1093
|
+
logger.info(result)
|
1094
|
+
logger.info(f"Audio offset selector script output: {result.stdout.strip()}")
|
1095
|
+
pyperclip.copy(result.stdout.strip()) # Copy the output to clipboard
|
1096
|
+
messagebox.showinfo("Clipboard", "Offset copied to clipboard!")
|
1097
|
+
return result.stdout.strip() # Return the output
|
1098
|
+
|
1099
|
+
except subprocess.CalledProcessError as e:
|
1100
|
+
logger.error(f"Error calling script: {e}")
|
1101
|
+
logger.error(f"Script output (stderr): {e.stderr.strip()}")
|
1102
|
+
return None
|
1103
|
+
except FileNotFoundError:
|
1104
|
+
logger.error(f"Error: Script not found at {script_path}. Make sure the script name is correct.")
|
1105
|
+
return None
|
1106
|
+
except Exception as e:
|
1107
|
+
logger.error(f"An unexpected error occurred: {e}")
|
1108
|
+
return None
|
1109
|
+
|
992
1110
|
|
993
1111
|
@new_tab
|
994
1112
|
def create_obs_tab(self):
|
995
|
-
obs_frame = ttk.Frame(self.notebook)
|
1113
|
+
obs_frame = ttk.Frame(self.notebook, padding=15)
|
996
1114
|
self.notebook.add(obs_frame, text='OBS')
|
997
1115
|
|
998
|
-
|
1116
|
+
HoverInfoLabelWidget(obs_frame, text="Enabled:", tooltip="Enable or disable OBS integration.",
|
1117
|
+
row=self.current_row, column=0)
|
999
1118
|
self.obs_enabled = tk.BooleanVar(value=self.settings.obs.enabled)
|
1000
|
-
ttk.Checkbutton(obs_frame, variable=self.obs_enabled).grid(row=self.current_row,
|
1001
|
-
|
1002
|
-
|
1119
|
+
ttk.Checkbutton(obs_frame, variable=self.obs_enabled, bootstyle="round-toggle").grid(row=self.current_row,
|
1120
|
+
column=1, sticky='W',
|
1121
|
+
pady=2)
|
1122
|
+
self.current_row += 1
|
1003
1123
|
|
1004
|
-
|
1124
|
+
HoverInfoLabelWidget(obs_frame, text="Open OBS:", tooltip="Open OBS when the GSM starts.", row=self.current_row,
|
1125
|
+
column=0)
|
1005
1126
|
self.open_obs = tk.BooleanVar(value=self.settings.obs.open_obs)
|
1006
|
-
ttk.Checkbutton(obs_frame, variable=self.open_obs).grid(row=self.current_row,
|
1007
|
-
|
1008
|
-
|
1127
|
+
ttk.Checkbutton(obs_frame, variable=self.open_obs, bootstyle="round-toggle").grid(row=self.current_row,
|
1128
|
+
column=1, sticky='W', pady=2)
|
1129
|
+
self.current_row += 1
|
1009
1130
|
|
1010
|
-
|
1131
|
+
HoverInfoLabelWidget(obs_frame, text="Close OBS:", tooltip="Close OBS when the GSM closes.",
|
1132
|
+
row=self.current_row, column=0)
|
1011
1133
|
self.close_obs = tk.BooleanVar(value=self.settings.obs.close_obs)
|
1012
|
-
ttk.Checkbutton(obs_frame, variable=self.close_obs).grid(row=self.current_row,
|
1013
|
-
|
1014
|
-
|
1134
|
+
ttk.Checkbutton(obs_frame, variable=self.close_obs, bootstyle="round-toggle").grid(row=self.current_row,
|
1135
|
+
column=1, sticky='W', pady=2)
|
1136
|
+
self.current_row += 1
|
1015
1137
|
|
1016
|
-
|
1138
|
+
HoverInfoLabelWidget(obs_frame, text="Host:", tooltip="Host address for the OBS WebSocket server.",
|
1139
|
+
row=self.current_row, column=0)
|
1017
1140
|
self.obs_host = ttk.Entry(obs_frame)
|
1018
1141
|
self.obs_host.insert(0, self.settings.obs.host)
|
1019
|
-
self.obs_host.grid(row=self.current_row, column=1)
|
1020
|
-
self.
|
1021
|
-
column=2)
|
1142
|
+
self.obs_host.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1143
|
+
self.current_row += 1
|
1022
1144
|
|
1023
|
-
|
1145
|
+
HoverInfoLabelWidget(obs_frame, text="Port:", tooltip="Port number for the OBS WebSocket server.",
|
1146
|
+
row=self.current_row, column=0)
|
1024
1147
|
self.obs_port = ttk.Entry(obs_frame)
|
1025
1148
|
self.obs_port.insert(0, str(self.settings.obs.port))
|
1026
|
-
self.obs_port.grid(row=self.current_row, column=1)
|
1027
|
-
self.
|
1028
|
-
column=2)
|
1149
|
+
self.obs_port.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1150
|
+
self.current_row += 1
|
1029
1151
|
|
1030
|
-
|
1031
|
-
|
1152
|
+
HoverInfoLabelWidget(obs_frame, text="Password:", tooltip="Password for the OBS WebSocket server.",
|
1153
|
+
row=self.current_row, column=0)
|
1154
|
+
self.obs_password = ttk.Entry(obs_frame, show="*")
|
1032
1155
|
self.obs_password.insert(0, self.settings.obs.password)
|
1033
|
-
self.obs_password.grid(row=self.current_row, column=1)
|
1034
|
-
self.
|
1035
|
-
column=2)
|
1156
|
+
self.obs_password.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1157
|
+
self.current_row += 1
|
1036
1158
|
|
1037
|
-
|
1159
|
+
HoverInfoLabelWidget(obs_frame, text="Get Game From Scene Name:", tooltip="Changes Current Game to Scene Name",
|
1160
|
+
row=self.current_row, column=0)
|
1038
1161
|
self.get_game_from_scene_name = tk.BooleanVar(value=self.settings.obs.get_game_from_scene)
|
1039
|
-
ttk.Checkbutton(obs_frame, variable=self.get_game_from_scene_name).grid(
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1162
|
+
ttk.Checkbutton(obs_frame, variable=self.get_game_from_scene_name, bootstyle="round-toggle").grid(
|
1163
|
+
row=self.current_row, column=1,
|
1164
|
+
sticky='W', pady=2)
|
1165
|
+
self.current_row += 1
|
1043
1166
|
|
1044
|
-
|
1167
|
+
HoverInfoLabelWidget(obs_frame, text="Minimum Replay Size (KB):",
|
1168
|
+
tooltip="Minimum Replay Size for OBS Replays in KB. If Replay is Under this, Audio/Screenshot Will not be grabbed.",
|
1169
|
+
row=self.current_row, column=0)
|
1045
1170
|
self.minimum_replay_size = ttk.Entry(obs_frame)
|
1046
1171
|
self.minimum_replay_size.insert(0, str(self.settings.obs.minimum_replay_size))
|
1047
|
-
self.minimum_replay_size.grid(row=self.current_row, column=1)
|
1048
|
-
self.
|
1049
|
-
"Audio/Screenshot Will not be grabbed.", row=self.current_row,
|
1050
|
-
column=2)
|
1051
|
-
|
1052
|
-
@new_tab
|
1053
|
-
def create_hotkeys_tab(self):
|
1054
|
-
hotkeys_frame = ttk.Frame(self.notebook)
|
1055
|
-
self.notebook.add(hotkeys_frame, text='Hotkeys')
|
1056
|
-
|
1057
|
-
ttk.Label(hotkeys_frame, text="Reset Line Hotkey:").grid(row=self.current_row, column=0, sticky='W')
|
1058
|
-
self.reset_line_hotkey = ttk.Entry(hotkeys_frame)
|
1059
|
-
self.reset_line_hotkey.insert(0, self.settings.hotkeys.reset_line)
|
1060
|
-
self.reset_line_hotkey.grid(row=self.current_row, column=1)
|
1061
|
-
self.add_label_and_increment_row(hotkeys_frame, "Hotkey to reset the current line of dialogue.",
|
1062
|
-
row=self.current_row, column=2)
|
1172
|
+
self.minimum_replay_size.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1173
|
+
self.current_row += 1
|
1063
1174
|
|
1064
|
-
|
1065
|
-
|
1066
|
-
self.take_screenshot_hotkey.insert(0, self.settings.hotkeys.take_screenshot)
|
1067
|
-
self.take_screenshot_hotkey.grid(row=self.current_row, column=1)
|
1068
|
-
self.add_label_and_increment_row(hotkeys_frame, "Hotkey to take a screenshot.", row=self.current_row, column=2)
|
1175
|
+
for col in range(3):
|
1176
|
+
obs_frame.grid_columnconfigure(col, weight=0)
|
1069
1177
|
|
1178
|
+
for row in range(self.current_row):
|
1179
|
+
obs_frame.grid_rowconfigure(row, minsize=30)
|
1070
1180
|
|
1071
1181
|
@new_tab
|
1072
1182
|
def create_profiles_tab(self):
|
1073
|
-
profiles_frame = ttk.Frame(self.notebook)
|
1074
|
-
|
1183
|
+
profiles_frame = ttk.Frame(self.notebook, padding=15)
|
1075
1184
|
self.notebook.add(profiles_frame, text='Profiles')
|
1076
1185
|
|
1077
|
-
|
1186
|
+
HoverInfoLabelWidget(profiles_frame, text="Select Profile:", tooltip="Select a profile to load its settings.",
|
1187
|
+
row=self.current_row, column=0)
|
1078
1188
|
self.profile_var = tk.StringVar(value=self.settings.name)
|
1079
|
-
self.profile_combobox = ttk.Combobox(profiles_frame, textvariable=self.profile_var,
|
1080
|
-
|
1189
|
+
self.profile_combobox = ttk.Combobox(profiles_frame, textvariable=self.profile_var,
|
1190
|
+
values=list(self.master_config.configs.keys()), state="readonly")
|
1191
|
+
self.profile_combobox.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1081
1192
|
self.profile_combobox.bind("<<ComboboxSelected>>", self.on_profile_change)
|
1082
|
-
self.
|
1193
|
+
self.current_row += 1
|
1083
1194
|
|
1084
|
-
|
1085
|
-
ttk.Button(profiles_frame, text="
|
1086
|
-
|
1195
|
+
button_row = self.current_row
|
1196
|
+
ttk.Button(profiles_frame, text="Add Profile", command=self.add_profile, bootstyle="primary").grid(
|
1197
|
+
row=button_row, column=0, pady=5)
|
1198
|
+
ttk.Button(profiles_frame, text="Copy Profile", command=self.copy_profile, bootstyle="secondary").grid(
|
1199
|
+
row=button_row, column=1, pady=5)
|
1200
|
+
self.delete_profile_button = ttk.Button(profiles_frame, text="Delete Config", command=self.delete_profile,
|
1201
|
+
bootstyle="danger")
|
1087
1202
|
if self.master_config.current_profile != DEFAULT_CONFIG:
|
1088
|
-
self.delete_profile_button.grid(row=
|
1203
|
+
self.delete_profile_button.grid(row=button_row, column=2, pady=5)
|
1089
1204
|
else:
|
1090
1205
|
self.delete_profile_button.grid_remove()
|
1091
1206
|
self.current_row += 1
|
1092
1207
|
|
1093
|
-
|
1208
|
+
HoverInfoLabelWidget(profiles_frame, text="OBS Scene (Auto Switch Profile):",
|
1209
|
+
tooltip="Select an OBS scene to associate with this profile. (Optional)",
|
1210
|
+
row=self.current_row, column=0)
|
1094
1211
|
self.obs_scene_var = tk.StringVar(value="")
|
1095
|
-
self.obs_scene_listbox = tk.Listbox(profiles_frame, listvariable=self.obs_scene_var, selectmode=tk.MULTIPLE,
|
1096
|
-
|
1212
|
+
self.obs_scene_listbox = tk.Listbox(profiles_frame, listvariable=self.obs_scene_var, selectmode=tk.MULTIPLE,
|
1213
|
+
height=10, width=50, selectbackground=ttk.Style().colors.primary)
|
1214
|
+
self.obs_scene_listbox.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1097
1215
|
self.obs_scene_listbox.bind("<<ListboxSelect>>", self.on_obs_scene_select)
|
1098
|
-
ttk.Button(profiles_frame, text="Refresh Scenes", command=self.refresh_obs_scenes).grid(
|
1099
|
-
|
1216
|
+
ttk.Button(profiles_frame, text="Refresh Scenes", command=self.refresh_obs_scenes, bootstyle="outline").grid(
|
1217
|
+
row=self.current_row, column=2, pady=5)
|
1218
|
+
self.current_row += 1
|
1100
1219
|
|
1101
|
-
|
1220
|
+
HoverInfoLabelWidget(profiles_frame, text="Switch To Default If Not Found:",
|
1221
|
+
tooltip="Enable to switch to the default profile if the selected OBS scene is not found.",
|
1222
|
+
row=self.current_row, column=0)
|
1102
1223
|
self.switch_to_default_if_not_found = tk.BooleanVar(value=self.master_config.switch_to_default_if_not_found)
|
1103
|
-
ttk.Checkbutton(profiles_frame, variable=self.switch_to_default_if_not_found).grid(
|
1104
|
-
|
1224
|
+
ttk.Checkbutton(profiles_frame, variable=self.switch_to_default_if_not_found, bootstyle="round-toggle").grid(
|
1225
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
1226
|
+
self.current_row += 1
|
1227
|
+
|
1228
|
+
for col in range(4):
|
1229
|
+
profiles_frame.grid_columnconfigure(col, weight=0)
|
1230
|
+
|
1231
|
+
for row in range(self.current_row):
|
1232
|
+
profiles_frame.grid_rowconfigure(row, minsize=30)
|
1105
1233
|
|
1106
1234
|
def on_obs_scene_select(self, event):
|
1107
1235
|
self.obs_scene_listbox_changed = True
|
@@ -1112,158 +1240,218 @@ class ConfigApp:
|
|
1112
1240
|
self.obs_scene_listbox.delete(0, tk.END) # Clear existing items
|
1113
1241
|
for scene_name in obs_scene_names:
|
1114
1242
|
self.obs_scene_listbox.insert(tk.END, scene_name) # Add each scene to the Listbox
|
1115
|
-
for i, scene in enumerate(
|
1116
|
-
if scene.strip() in self.settings.scenes: #
|
1243
|
+
for i, scene in enumerate(obs_scene_names): # Iterate through actual scene names
|
1244
|
+
if scene.strip() in self.settings.scenes: # Check if scene is in current settings
|
1117
1245
|
self.obs_scene_listbox.select_set(i) # Select the item in the Listbox
|
1118
1246
|
self.obs_scene_listbox.activate(i)
|
1119
|
-
|
1247
|
+
self.obs_scene_listbox.update_idletasks() # Ensure the GUI reflects the changes
|
1120
1248
|
|
1121
1249
|
@new_tab
|
1122
1250
|
def create_advanced_tab(self):
|
1123
|
-
advanced_frame = ttk.Frame(self.notebook)
|
1251
|
+
advanced_frame = ttk.Frame(self.notebook, padding=15)
|
1124
1252
|
self.notebook.add(advanced_frame, text='Advanced')
|
1125
1253
|
|
1126
|
-
ttk.Label(advanced_frame, text="Note: Only one of these will take effect, prioritizing audio.",
|
1254
|
+
ttk.Label(advanced_frame, text="Note: Only one of these will take effect, prioritizing audio.",
|
1255
|
+
foreground="red", font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=3,
|
1256
|
+
sticky='W', pady=5)
|
1127
1257
|
self.current_row += 1
|
1128
1258
|
|
1129
|
-
|
1259
|
+
HoverInfoLabelWidget(advanced_frame, text="Audio Player Path:",
|
1260
|
+
tooltip="Path to the audio player executable. Will open the trimmed Audio",
|
1261
|
+
row=self.current_row, column=0)
|
1130
1262
|
self.audio_player_path = ttk.Entry(advanced_frame, width=50)
|
1131
1263
|
self.audio_player_path.insert(0, self.settings.advanced.audio_player_path)
|
1132
|
-
self.audio_player_path.grid(row=self.current_row, column=1)
|
1133
|
-
ttk.Button(advanced_frame, text="Browse", command=lambda: self.browse_file(self.audio_player_path)
|
1134
|
-
|
1264
|
+
self.audio_player_path.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1265
|
+
ttk.Button(advanced_frame, text="Browse", command=lambda: self.browse_file(self.audio_player_path),
|
1266
|
+
bootstyle="outline").grid(row=self.current_row, column=2, padx=5, pady=2)
|
1267
|
+
self.current_row += 1
|
1135
1268
|
|
1136
|
-
|
1269
|
+
HoverInfoLabelWidget(advanced_frame, text="Video Player Path:",
|
1270
|
+
tooltip="Path to the video player executable. Will seek to the location of the line in the replay",
|
1271
|
+
row=self.current_row, column=0)
|
1137
1272
|
self.video_player_path = ttk.Entry(advanced_frame, width=50)
|
1138
1273
|
self.video_player_path.insert(0, self.settings.advanced.video_player_path)
|
1139
|
-
self.video_player_path.grid(row=self.current_row, column=1)
|
1140
|
-
ttk.Button(advanced_frame, text="Browse", command=lambda: self.browse_file(self.video_player_path)
|
1141
|
-
|
1274
|
+
self.video_player_path.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1275
|
+
ttk.Button(advanced_frame, text="Browse", command=lambda: self.browse_file(self.video_player_path),
|
1276
|
+
bootstyle="outline").grid(row=self.current_row, column=2, padx=5, pady=2)
|
1277
|
+
self.current_row += 1
|
1142
1278
|
|
1143
|
-
|
1279
|
+
HoverInfoLabelWidget(advanced_frame, text="Play Latest Video/Audio Hotkey:",
|
1280
|
+
tooltip="Hotkey to trim and play the latest audio.", row=self.current_row, column=0)
|
1144
1281
|
self.play_latest_audio_hotkey = ttk.Entry(advanced_frame)
|
1145
1282
|
self.play_latest_audio_hotkey.insert(0, self.settings.hotkeys.play_latest_audio)
|
1146
|
-
self.play_latest_audio_hotkey.grid(row=self.current_row, column=1)
|
1147
|
-
self.
|
1283
|
+
self.play_latest_audio_hotkey.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1284
|
+
self.current_row += 1
|
1148
1285
|
|
1149
|
-
|
1286
|
+
HoverInfoLabelWidget(advanced_frame, text="Multi-line Line-Break:",
|
1287
|
+
tooltip="Line break for multi-line mining. This goes between each sentence",
|
1288
|
+
row=self.current_row, column=0)
|
1150
1289
|
self.multi_line_line_break = ttk.Entry(advanced_frame)
|
1151
1290
|
self.multi_line_line_break.insert(0, self.settings.advanced.multi_line_line_break)
|
1152
|
-
self.multi_line_line_break.grid(row=self.current_row, column=1)
|
1153
|
-
self.
|
1291
|
+
self.multi_line_line_break.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1292
|
+
self.current_row += 1
|
1154
1293
|
|
1155
|
-
|
1294
|
+
HoverInfoLabelWidget(advanced_frame, text="Multi-Line Sentence Storage Field:",
|
1295
|
+
tooltip="Field in Anki for storing the multi-line sentence temporarily.",
|
1296
|
+
row=self.current_row, column=0)
|
1156
1297
|
self.multi_line_sentence_storage_field = ttk.Entry(advanced_frame)
|
1157
1298
|
self.multi_line_sentence_storage_field.insert(0, self.settings.advanced.multi_line_sentence_storage_field)
|
1158
|
-
self.multi_line_sentence_storage_field.grid(row=self.current_row, column=1)
|
1159
|
-
self.
|
1160
|
-
|
1161
|
-
ttk.Label(advanced_frame, text="OCR Sends to Clipboard:").grid(row=self.current_row, column=0, sticky='W')
|
1162
|
-
self.ocr_sends_to_clipboard = tk.BooleanVar(value=self.settings.advanced.ocr_sends_to_clipboard)
|
1163
|
-
ttk.Checkbutton(advanced_frame, variable=self.ocr_sends_to_clipboard).grid(row=self.current_row, column=1, sticky='W')
|
1164
|
-
self.add_label_and_increment_row(advanced_frame, "Enable to send OCR results to clipboard.", row=self.current_row, column=2)
|
1299
|
+
self.multi_line_sentence_storage_field.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1300
|
+
self.current_row += 1
|
1165
1301
|
|
1302
|
+
HoverInfoLabelWidget(advanced_frame, text="OCR WebSocket Port:",
|
1303
|
+
tooltip="Port for OCR WebSocket communication. GSM will also listen on this port",
|
1304
|
+
row=self.current_row, column=0)
|
1166
1305
|
self.ocr_websocket_port = ttk.Entry(advanced_frame)
|
1167
1306
|
self.ocr_websocket_port.insert(0, str(self.settings.advanced.ocr_websocket_port))
|
1168
|
-
self.ocr_websocket_port.grid(row=self.current_row, column=1)
|
1169
|
-
self.
|
1307
|
+
self.ocr_websocket_port.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1308
|
+
self.current_row += 1
|
1170
1309
|
|
1171
|
-
|
1310
|
+
HoverInfoLabelWidget(advanced_frame, text="Texthooker Communication WebSocket Port:",
|
1311
|
+
tooltip="Port for GSM Texthooker WebSocket communication. Does nothing right now, hardcoded to 55001",
|
1312
|
+
row=self.current_row, column=0)
|
1172
1313
|
self.texthooker_communication_websocket_port = ttk.Entry(advanced_frame)
|
1173
|
-
self.texthooker_communication_websocket_port.insert(0,
|
1174
|
-
|
1175
|
-
self.
|
1176
|
-
|
1314
|
+
self.texthooker_communication_websocket_port.insert(0,
|
1315
|
+
str(self.settings.advanced.texthooker_communication_websocket_port))
|
1316
|
+
self.texthooker_communication_websocket_port.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1317
|
+
self.current_row += 1
|
1177
1318
|
|
1178
|
-
|
1319
|
+
HoverInfoLabelWidget(advanced_frame, text="Use Anki Creation Date for Audio Timing:",
|
1320
|
+
tooltip="Use the Anki note creation date for audio timing instead of the OBS replay time.",
|
1321
|
+
row=self.current_row, column=0)
|
1179
1322
|
self.use_anki_note_creation_time = tk.BooleanVar(value=self.settings.advanced.use_anki_note_creation_time)
|
1180
|
-
ttk.Checkbutton(advanced_frame, variable=self.use_anki_note_creation_time).grid(
|
1181
|
-
|
1323
|
+
ttk.Checkbutton(advanced_frame, variable=self.use_anki_note_creation_time, bootstyle="round-toggle").grid(
|
1324
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
1325
|
+
self.current_row += 1
|
1182
1326
|
|
1327
|
+
HoverInfoLabelWidget(advanced_frame, text="Reset Line Hotkey:",
|
1328
|
+
tooltip="Hotkey to reset the current line of dialogue.", row=self.current_row, column=0)
|
1329
|
+
self.reset_line_hotkey = ttk.Entry(advanced_frame)
|
1330
|
+
self.reset_line_hotkey.insert(0, self.settings.hotkeys.reset_line)
|
1331
|
+
self.reset_line_hotkey.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1332
|
+
self.current_row += 1
|
1333
|
+
|
1334
|
+
HoverInfoLabelWidget(advanced_frame, text="Polling Rate:",
|
1335
|
+
tooltip="Rate at which Anki will check for updates (in milliseconds).",
|
1336
|
+
row=self.current_row, column=0)
|
1337
|
+
self.polling_rate = ttk.Entry(advanced_frame)
|
1338
|
+
self.polling_rate.insert(0, str(self.settings.anki.polling_rate))
|
1339
|
+
self.polling_rate.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1340
|
+
self.current_row += 1
|
1341
|
+
|
1342
|
+
HoverInfoLabelWidget(advanced_frame, text="Vosk URL:", tooltip="URL for connecting to the Vosk server.",
|
1343
|
+
row=self.current_row, column=0)
|
1344
|
+
self.vosk_url = ttk.Combobox(advanced_frame, values=[VOSK_BASE, VOSK_SMALL], state="readonly")
|
1345
|
+
self.vosk_url.set(VOSK_BASE if self.settings.vad.vosk_url == 'https://alphacephei.com/vosk/models/vosk-model-ja-0.22.zip' else VOSK_SMALL)
|
1346
|
+
self.vosk_url.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1347
|
+
self.current_row += 1
|
1183
1348
|
|
1349
|
+
for col in range(4):
|
1350
|
+
advanced_frame.grid_columnconfigure(col, weight=0)
|
1351
|
+
|
1352
|
+
for row in range(self.current_row):
|
1353
|
+
advanced_frame.grid_rowconfigure(row, minsize=30)
|
1184
1354
|
|
1185
1355
|
@new_tab
|
1186
1356
|
def create_ai_tab(self):
|
1187
|
-
ai_frame = ttk.Frame(self.notebook)
|
1357
|
+
ai_frame = ttk.Frame(self.notebook, padding=15)
|
1188
1358
|
self.notebook.add(ai_frame, text='AI')
|
1189
1359
|
|
1190
|
-
|
1360
|
+
HoverInfoLabelWidget(ai_frame, text="Enabled:", tooltip="Enable or disable AI integration.",
|
1361
|
+
row=self.current_row, column=0)
|
1191
1362
|
self.ai_enabled = tk.BooleanVar(value=self.settings.ai.enabled)
|
1192
|
-
ttk.Checkbutton(ai_frame, variable=self.ai_enabled).grid(row=self.current_row,
|
1193
|
-
|
1363
|
+
ttk.Checkbutton(ai_frame, variable=self.ai_enabled, bootstyle="round-toggle").grid(row=self.current_row,
|
1364
|
+
column=1, sticky='W', pady=2)
|
1365
|
+
self.current_row += 1
|
1194
1366
|
|
1195
|
-
|
1196
|
-
|
1367
|
+
HoverInfoLabelWidget(ai_frame, text="Provider:", tooltip="Select the AI provider.", row=self.current_row,
|
1368
|
+
column=0)
|
1369
|
+
self.ai_provider = ttk.Combobox(ai_frame, values=['Gemini', 'Groq'], state="readonly")
|
1197
1370
|
self.ai_provider.set(self.settings.ai.provider)
|
1198
|
-
self.ai_provider.grid(row=self.current_row, column=1)
|
1199
|
-
self.
|
1371
|
+
self.ai_provider.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1372
|
+
self.current_row += 1
|
1200
1373
|
|
1201
|
-
|
1202
|
-
|
1374
|
+
HoverInfoLabelWidget(ai_frame, text="Gemini AI Model:", tooltip="Select the AI model to use.",
|
1375
|
+
row=self.current_row, column=0)
|
1376
|
+
self.gemini_model = ttk.Combobox(ai_frame, values=['gemini-2.0-flash', 'gemini-2.0-flash-lite',
|
1377
|
+
'gemini-2.5-pro-preview-05-06',
|
1378
|
+
'gemini-2.5-flash-preview-05-20'], state="readonly")
|
1203
1379
|
self.gemini_model.set(self.settings.ai.gemini_model)
|
1204
|
-
self.gemini_model.grid(row=self.current_row, column=1)
|
1205
|
-
self.
|
1206
|
-
|
1207
|
-
|
1208
|
-
# ttk.Label(ai_frame, text="Provider:").grid(row=self.current_row, column=0, sticky='W')
|
1209
|
-
# self.provider = ttk.Combobox(ai_frame,
|
1210
|
-
# values=[AI_GEMINI])
|
1211
|
-
# self.provider.set(self.settings.ai.provider)
|
1212
|
-
# self.provider.grid(row=self.current_row, column=1)
|
1213
|
-
# self.add_label_and_increment_row(ai_frame, "Select the AI provider. Currently only Gemini is supported.", row=self.current_row, column=2)
|
1380
|
+
self.gemini_model.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1381
|
+
self.current_row += 1
|
1214
1382
|
|
1215
|
-
|
1383
|
+
HoverInfoLabelWidget(ai_frame, text="Gemini API Key:",
|
1384
|
+
tooltip="API key for the selected AI provider (Gemini only currently).",
|
1385
|
+
foreground="green", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
1216
1386
|
self.gemini_api_key = ttk.Entry(ai_frame, show="*") # Mask the API key for security
|
1217
1387
|
self.gemini_api_key.insert(0, self.settings.ai.gemini_api_key)
|
1218
|
-
self.gemini_api_key.grid(row=self.current_row, column=1)
|
1219
|
-
self.
|
1220
|
-
column=2)
|
1388
|
+
self.gemini_api_key.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1389
|
+
self.current_row += 1
|
1221
1390
|
|
1222
|
-
|
1223
|
-
|
1391
|
+
HoverInfoLabelWidget(ai_frame, text="Groq AI Model:", tooltip="Select the Groq AI model to use.",
|
1392
|
+
row=self.current_row, column=0)
|
1393
|
+
self.groq_model = ttk.Combobox(ai_frame, values=['meta-llama/llama-4-maverick-17b-128e-instruct',
|
1394
|
+
'meta-llama/llama-4-scout-17b-16e-instruct',
|
1395
|
+
'llama-3.1-8b-instant'], state="readonly")
|
1224
1396
|
self.groq_model.set(self.settings.ai.groq_model)
|
1225
|
-
self.groq_model.grid(row=self.current_row, column=1)
|
1226
|
-
self.
|
1227
|
-
|
1397
|
+
self.groq_model.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1398
|
+
self.current_row += 1
|
1228
1399
|
|
1229
|
-
|
1400
|
+
HoverInfoLabelWidget(ai_frame, text="Groq API Key:", tooltip="API key for Groq AI provider.",
|
1401
|
+
row=self.current_row, column=0)
|
1230
1402
|
self.groq_api_key = ttk.Entry(ai_frame, show="*") # Mask the API key for security
|
1231
1403
|
self.groq_api_key.insert(0, self.settings.ai.groq_api_key)
|
1232
|
-
self.groq_api_key.grid(row=self.current_row, column=1)
|
1233
|
-
self.
|
1234
|
-
column=2)
|
1404
|
+
self.groq_api_key.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1405
|
+
self.current_row += 1
|
1235
1406
|
|
1236
|
-
|
1407
|
+
HoverInfoLabelWidget(ai_frame, text="Anki Field:", tooltip="Field in Anki for AI-generated content.",
|
1408
|
+
row=self.current_row, column=0)
|
1237
1409
|
self.ai_anki_field = ttk.Entry(ai_frame)
|
1238
1410
|
self.ai_anki_field.insert(0, self.settings.ai.anki_field)
|
1239
|
-
self.ai_anki_field.grid(row=self.current_row, column=1)
|
1240
|
-
self.
|
1241
|
-
column=2)
|
1411
|
+
self.ai_anki_field.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1412
|
+
self.current_row += 1
|
1242
1413
|
|
1243
|
-
|
1414
|
+
HoverInfoLabelWidget(ai_frame, text="Use Canned Translation Prompt:",
|
1415
|
+
tooltip="Use a pre-defined translation prompt for AI.", row=self.current_row, column=0)
|
1244
1416
|
self.use_canned_translation_prompt = tk.BooleanVar(value=self.settings.ai.use_canned_translation_prompt)
|
1245
|
-
ttk.Checkbutton(ai_frame, variable=self.use_canned_translation_prompt).grid(
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1417
|
+
ttk.Checkbutton(ai_frame, variable=self.use_canned_translation_prompt, bootstyle="round-toggle").grid(
|
1418
|
+
row=self.current_row, column=1,
|
1419
|
+
sticky='W', pady=2)
|
1420
|
+
self.current_row += 1
|
1249
1421
|
|
1250
|
-
|
1422
|
+
HoverInfoLabelWidget(ai_frame, text="Use Canned Context Prompt:",
|
1423
|
+
tooltip="Use a pre-defined context prompt for AI.", row=self.current_row, column=0)
|
1251
1424
|
self.use_canned_context_prompt = tk.BooleanVar(value=self.settings.ai.use_canned_context_prompt)
|
1252
|
-
ttk.Checkbutton(ai_frame, variable=self.use_canned_context_prompt).grid(
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
ttk.Label(ai_frame, text="Custom Prompt:").grid(row=self.current_row, column=0, sticky='W')
|
1425
|
+
ttk.Checkbutton(ai_frame, variable=self.use_canned_context_prompt, bootstyle="round-toggle").grid(
|
1426
|
+
row=self.current_row, column=1,
|
1427
|
+
sticky='W', pady=2)
|
1428
|
+
self.current_row += 1
|
1258
1429
|
|
1259
|
-
|
1430
|
+
HoverInfoLabelWidget(ai_frame, text="Custom Prompt:", tooltip="Custom prompt for AI processing.",
|
1431
|
+
row=self.current_row, column=0)
|
1432
|
+
self.custom_prompt = scrolledtext.ScrolledText(ai_frame, width=50, height=5, font=("TkDefaultFont", 9),
|
1433
|
+
relief="solid", borderwidth=1,
|
1434
|
+
highlightbackground=ttk.Style().colors.border) # Adjust height as needed
|
1260
1435
|
self.custom_prompt.insert(tk.END, self.settings.ai.custom_prompt)
|
1261
|
-
self.custom_prompt.grid(row=self.current_row, column=1)
|
1436
|
+
self.custom_prompt.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1437
|
+
self.current_row += 1
|
1262
1438
|
|
1263
|
-
|
1439
|
+
for col in range(3):
|
1440
|
+
ai_frame.grid_columnconfigure(col, weight=0)
|
1441
|
+
|
1442
|
+
for row in range(self.current_row):
|
1443
|
+
ai_frame.grid_rowconfigure(row, minsize=30)
|
1264
1444
|
|
1265
1445
|
return ai_frame
|
1266
1446
|
|
1447
|
+
# @new_tab
|
1448
|
+
# def create_help_tab(self):
|
1449
|
+
# help_frame = ttk.Frame(self.notebook, padding=15)
|
1450
|
+
# self.notebook.add(help_frame, text='Help')
|
1451
|
+
#
|
1452
|
+
#
|
1453
|
+
#
|
1454
|
+
# help_frame.grid_columnconfigure(0, weight=1)
|
1267
1455
|
|
1268
1456
|
def on_profile_change(self, event):
|
1269
1457
|
self.save_settings(profile_change=True)
|
@@ -1285,9 +1473,12 @@ class ConfigApp:
|
|
1285
1473
|
|
1286
1474
|
def copy_profile(self):
|
1287
1475
|
source_profile = self.profile_combobox.get()
|
1288
|
-
new_profile_name = simpledialog.askstring("Input", "Enter new profile name:")
|
1476
|
+
new_profile_name = simpledialog.askstring("Input", "Enter new profile name:", parent=self.window)
|
1289
1477
|
if new_profile_name and source_profile in self.master_config.configs:
|
1290
|
-
|
1478
|
+
# Deep copy the configuration to avoid shared references
|
1479
|
+
import copy
|
1480
|
+
self.master_config.configs[new_profile_name] = copy.deepcopy(self.master_config.configs[source_profile])
|
1481
|
+
self.master_config.configs[new_profile_name].name = new_profile_name # Update the name in the copied config
|
1291
1482
|
self.profile_combobox['values'] = list(self.master_config.configs.keys())
|
1292
1483
|
self.profile_combobox.set(new_profile_name)
|
1293
1484
|
self.save_settings()
|
@@ -1300,7 +1491,9 @@ class ConfigApp:
|
|
1300
1491
|
return
|
1301
1492
|
|
1302
1493
|
if profile_to_delete and profile_to_delete in self.master_config.configs:
|
1303
|
-
confirm = messagebox.askyesno("Confirm Delete",
|
1494
|
+
confirm = messagebox.askyesno("Confirm Delete",
|
1495
|
+
f"Are you sure you want to delete the profile '{profile_to_delete}'?",
|
1496
|
+
parent=self.window, icon='warning')
|
1304
1497
|
if confirm:
|
1305
1498
|
del self.master_config.configs[profile_to_delete]
|
1306
1499
|
self.profile_combobox['values'] = list(self.master_config.configs.keys())
|
@@ -1309,26 +1502,31 @@ class ConfigApp:
|
|
1309
1502
|
save_full_config(self.master_config)
|
1310
1503
|
self.reload_settings()
|
1311
1504
|
|
1312
|
-
|
1313
1505
|
def show_error_box(self, title, message):
|
1314
1506
|
messagebox.showerror(title, message)
|
1315
1507
|
|
1316
1508
|
def download_and_install_ocen(self):
|
1317
|
-
confirm = messagebox.askyesno("Download OcenAudio?",
|
1509
|
+
confirm = messagebox.askyesno("Download OcenAudio?",
|
1510
|
+
"Would you like to download and install OcenAudio? It is a free audio editing software that works extremely well with GSM.",
|
1511
|
+
parent=self.window, icon='question')
|
1318
1512
|
if confirm:
|
1319
1513
|
self.external_tool.delete(0, tk.END)
|
1320
1514
|
self.external_tool.insert(0, "Downloading OcenAudio...")
|
1321
1515
|
exe_path = download_ocenaudio_if_needed()
|
1322
|
-
messagebox.showinfo("OcenAudio Downloaded",
|
1516
|
+
messagebox.showinfo("OcenAudio Downloaded",
|
1517
|
+
f"OcenAudio has been downloaded and installed. You can find it at {exe_path}.",
|
1518
|
+
parent=self.window)
|
1323
1519
|
self.external_tool.delete(0, tk.END)
|
1324
1520
|
self.external_tool.insert(0, exe_path)
|
1325
1521
|
self.save_settings()
|
1326
1522
|
|
1327
1523
|
def set_default_anki_media_collection(self):
|
1328
|
-
confirm = messagebox.askyesno("Set Default Anki Media Collection?",
|
1524
|
+
confirm = messagebox.askyesno("Set Default Anki Media Collection?",
|
1525
|
+
"Would you like to set the default Anki media collection path? This will help the script find the media collection for external trimming.\n\nDefault: %APPDATA%/Anki2/User 1/collection.media",
|
1526
|
+
parent=self.window, icon='question')
|
1329
1527
|
if confirm:
|
1330
1528
|
default_path = get_default_anki_media_collection_path()
|
1331
|
-
if default_path != self.settings.audio.
|
1529
|
+
if default_path != self.settings.audio.anki_media_collection: # Check against anki_media_collection
|
1332
1530
|
self.anki_media_collection.delete(0, tk.END)
|
1333
1531
|
self.anki_media_collection.insert(0, default_path)
|
1334
1532
|
self.save_settings()
|
@@ -1338,4 +1536,4 @@ if __name__ == '__main__':
|
|
1338
1536
|
root = ttk.Window(themename='darkly')
|
1339
1537
|
window = ConfigApp(root)
|
1340
1538
|
window.show()
|
1341
|
-
window.window.mainloop()
|
1539
|
+
window.window.mainloop()
|