GameSentenceMiner 2.17.7__py3-none-any.whl → 2.18.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- GameSentenceMiner/ai/ai_prompting.py +6 -6
- GameSentenceMiner/anki.py +236 -152
- GameSentenceMiner/gametext.py +7 -4
- GameSentenceMiner/gsm.py +49 -10
- GameSentenceMiner/locales/en_us.json +7 -3
- GameSentenceMiner/locales/ja_jp.json +8 -4
- GameSentenceMiner/locales/zh_cn.json +8 -4
- GameSentenceMiner/obs.py +238 -59
- GameSentenceMiner/ocr/owocr_helper.py +1 -1
- GameSentenceMiner/tools/ss_selector.py +7 -8
- GameSentenceMiner/ui/__init__.py +0 -0
- GameSentenceMiner/ui/anki_confirmation.py +187 -0
- GameSentenceMiner/{config_gui.py → ui/config_gui.py} +100 -35
- GameSentenceMiner/ui/screenshot_selector.py +215 -0
- GameSentenceMiner/util/configuration.py +124 -22
- GameSentenceMiner/util/db.py +22 -13
- GameSentenceMiner/util/downloader/download_tools.py +2 -2
- GameSentenceMiner/util/ffmpeg.py +24 -30
- GameSentenceMiner/util/get_overlay_coords.py +34 -34
- GameSentenceMiner/util/gsm_utils.py +31 -1
- GameSentenceMiner/util/text_log.py +11 -9
- GameSentenceMiner/vad.py +31 -12
- GameSentenceMiner/web/database_api.py +742 -123
- GameSentenceMiner/web/static/css/dashboard-shared.css +241 -0
- GameSentenceMiner/web/static/css/kanji-grid.css +94 -2
- GameSentenceMiner/web/static/css/overview.css +850 -0
- GameSentenceMiner/web/static/css/popups-shared.css +126 -0
- GameSentenceMiner/web/static/css/shared.css +97 -0
- GameSentenceMiner/web/static/css/stats.css +192 -597
- GameSentenceMiner/web/static/js/anki_stats.js +6 -4
- GameSentenceMiner/web/static/js/database.js +209 -5
- GameSentenceMiner/web/static/js/goals.js +610 -0
- GameSentenceMiner/web/static/js/kanji-grid.js +267 -4
- GameSentenceMiner/web/static/js/overview.js +1176 -0
- GameSentenceMiner/web/static/js/shared.js +25 -0
- GameSentenceMiner/web/static/js/stats.js +154 -1459
- GameSentenceMiner/web/stats.py +2 -2
- GameSentenceMiner/web/templates/anki_stats.html +5 -0
- GameSentenceMiner/web/templates/components/navigation.html +3 -1
- GameSentenceMiner/web/templates/database.html +73 -1
- GameSentenceMiner/web/templates/goals.html +376 -0
- GameSentenceMiner/web/templates/index.html +13 -11
- GameSentenceMiner/web/templates/overview.html +416 -0
- GameSentenceMiner/web/templates/stats.html +46 -251
- GameSentenceMiner/web/texthooking_page.py +18 -0
- {gamesentenceminer-2.17.7.dist-info → gamesentenceminer-2.18.0.dist-info}/METADATA +5 -1
- {gamesentenceminer-2.17.7.dist-info → gamesentenceminer-2.18.0.dist-info}/RECORD +51 -41
- {gamesentenceminer-2.17.7.dist-info → gamesentenceminer-2.18.0.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.17.7.dist-info → gamesentenceminer-2.18.0.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.17.7.dist-info → gamesentenceminer-2.18.0.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.17.7.dist-info → gamesentenceminer-2.18.0.dist-info}/top_level.txt +0 -0
|
@@ -38,7 +38,7 @@ logger.setLevel(logging.DEBUG)
|
|
|
38
38
|
# Create a file handler for logging
|
|
39
39
|
log_file = os.path.join(get_app_directory(), "logs", "ocr_log.txt")
|
|
40
40
|
os.makedirs(os.path.join(get_app_directory(), "logs"), exist_ok=True)
|
|
41
|
-
file_handler = RotatingFileHandler(log_file, maxBytes=1024 * 1024, backupCount=
|
|
41
|
+
file_handler = RotatingFileHandler(log_file, maxBytes=1024 * 1024, backupCount=2, encoding='utf-8')
|
|
42
42
|
file_handler.setLevel(logging.DEBUG)
|
|
43
43
|
# Create a formatter and set it for the handler
|
|
44
44
|
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
# TODO REMOVE THIS, DEPRECATED
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
5
|
import subprocess
|
|
6
6
|
|
|
7
|
+
import tkinter as tk
|
|
8
|
+
from PIL import Image, ImageTk
|
|
9
|
+
from GameSentenceMiner.util.gsm_utils import sanitize_filename
|
|
10
|
+
from GameSentenceMiner.util.configuration import get_temporary_directory, logger, ffmpeg_base_command_list
|
|
11
|
+
from GameSentenceMiner.util import ffmpeg
|
|
12
|
+
|
|
7
13
|
# Suppress stdout and stderr during imports
|
|
8
14
|
sys_stdout = sys.stdout
|
|
9
15
|
sys_stderr = sys.stderr
|
|
10
16
|
sys.stdout = open(os.devnull, 'w')
|
|
11
17
|
sys.stderr = open(os.devnull, 'w')
|
|
12
18
|
|
|
13
|
-
import tkinter as tk
|
|
14
|
-
from PIL import Image, ImageTk
|
|
15
|
-
from GameSentenceMiner.util.gsm_utils import sanitize_filename
|
|
16
|
-
from GameSentenceMiner.util.configuration import get_temporary_directory, logger
|
|
17
|
-
from GameSentenceMiner.util.ffmpeg import ffmpeg_base_command_list
|
|
18
|
-
from GameSentenceMiner.util import ffmpeg
|
|
19
|
-
|
|
20
19
|
def extract_frames(video_path, timestamp, temp_dir, mode):
|
|
21
20
|
frame_paths = []
|
|
22
21
|
timestamp_number = float(timestamp)
|
|
File without changes
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
from tkinter import scrolledtext
|
|
3
|
+
from PIL import Image, ImageTk
|
|
4
|
+
|
|
5
|
+
import ttkbootstrap as ttk
|
|
6
|
+
from GameSentenceMiner.util.configuration import get_config, logger, gsm_state
|
|
7
|
+
|
|
8
|
+
import platform
|
|
9
|
+
import subprocess
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
class AnkiConfirmationDialog(tk.Toplevel):
|
|
13
|
+
"""
|
|
14
|
+
A modal dialog to confirm Anki card details and choose an audio option.
|
|
15
|
+
"""
|
|
16
|
+
def __init__(self, parent, config_app, expression, sentence, screenshot_path, audio_path, translation, screenshot_timestamp):
|
|
17
|
+
super().__init__(parent)
|
|
18
|
+
self.config_app = config_app
|
|
19
|
+
self.screenshot_timestamp = screenshot_timestamp
|
|
20
|
+
|
|
21
|
+
# Initialize screenshot_path here, will be updated by button if needed
|
|
22
|
+
self.screenshot_path = screenshot_path
|
|
23
|
+
|
|
24
|
+
self.title("Confirm Anki Card Details")
|
|
25
|
+
self.result = None # This will store the user's choice
|
|
26
|
+
|
|
27
|
+
# This makes the dialog block interaction with other windows.
|
|
28
|
+
self.grab_set()
|
|
29
|
+
|
|
30
|
+
# --- Create and lay out widgets ---
|
|
31
|
+
self._create_widgets(expression, sentence, screenshot_path, audio_path, translation)
|
|
32
|
+
|
|
33
|
+
# --- Smarter Centering Logic ---
|
|
34
|
+
self.update_idletasks()
|
|
35
|
+
|
|
36
|
+
if parent.state() == 'withdrawn':
|
|
37
|
+
screen_width = self.winfo_screenwidth()
|
|
38
|
+
screen_height = self.winfo_screenheight()
|
|
39
|
+
dialog_width = self.winfo_width()
|
|
40
|
+
dialog_height = self.winfo_height()
|
|
41
|
+
x = (screen_width // 2) - (dialog_width // 2)
|
|
42
|
+
y = (screen_height // 2) - (dialog_height // 2)
|
|
43
|
+
self.geometry(f'+{x}+{y}')
|
|
44
|
+
else:
|
|
45
|
+
self.transient(parent)
|
|
46
|
+
parent_x = parent.winfo_x()
|
|
47
|
+
parent_y = parent.winfo_y()
|
|
48
|
+
parent_width = parent.winfo_width()
|
|
49
|
+
parent_height = parent.winfo_height()
|
|
50
|
+
dialog_width = self.winfo_width()
|
|
51
|
+
dialog_height = self.winfo_height()
|
|
52
|
+
x = parent_x + (parent_width // 2) - (dialog_width // 2)
|
|
53
|
+
y = parent_y + (parent_height // 2) - (dialog_height // 2)
|
|
54
|
+
self.geometry(f'+{x}+{y}')
|
|
55
|
+
|
|
56
|
+
self.protocol("WM_DELETE_WINDOW", self._on_cancel)
|
|
57
|
+
self.attributes('-topmost', True)
|
|
58
|
+
self.wait_window(self)
|
|
59
|
+
|
|
60
|
+
def _create_widgets(self, expression, sentence, screenshot_path, audio_path, translation):
|
|
61
|
+
main_frame = ttk.Frame(self, padding=20)
|
|
62
|
+
main_frame.pack(expand=True, fill="both")
|
|
63
|
+
|
|
64
|
+
row = 0
|
|
65
|
+
|
|
66
|
+
# Expression
|
|
67
|
+
ttk.Label(main_frame, text=f"{get_config().anki.word_field}:", font=("-weight bold")).grid(row=row, column=0, sticky="ne", padx=5, pady=2)
|
|
68
|
+
ttk.Label(main_frame, text=expression, wraplength=400, justify="left").grid(row=row, column=1, sticky="w", padx=5, pady=2)
|
|
69
|
+
row += 1
|
|
70
|
+
|
|
71
|
+
# Sentence
|
|
72
|
+
ttk.Label(main_frame, text=f"{get_config().anki.sentence_field}:", font=("-weight bold")).grid(row=row, column=0, sticky="ne", padx=5, pady=2)
|
|
73
|
+
sentence_text = scrolledtext.ScrolledText(main_frame, height=4, width=50, wrap=tk.WORD)
|
|
74
|
+
sentence_text.insert(tk.END, sentence)
|
|
75
|
+
sentence_text.grid(row=row, column=1, sticky="w", padx=5, pady=2)
|
|
76
|
+
self.sentence_text = sentence_text
|
|
77
|
+
row += 1
|
|
78
|
+
|
|
79
|
+
# Translation
|
|
80
|
+
ttk.Label(main_frame, text=f"{get_config().ai.anki_field}:", font=("-weight bold")).grid(row=row, column=0, sticky="ne", padx=5, pady=2)
|
|
81
|
+
translation_text = scrolledtext.ScrolledText(main_frame, height=4, width=50, wrap=tk.WORD)
|
|
82
|
+
translation_text.insert(tk.END, translation)
|
|
83
|
+
translation_text.grid(row=row, column=1, sticky="w", padx=5, pady=2)
|
|
84
|
+
self.translation_text = translation_text
|
|
85
|
+
row += 1
|
|
86
|
+
|
|
87
|
+
# Screenshot
|
|
88
|
+
ttk.Label(main_frame, text=f"{get_config().anki.picture_field}:", font=("-weight bold")).grid(row=row, column=0, sticky="ne", padx=5, pady=2)
|
|
89
|
+
|
|
90
|
+
# <<< CHANGED: Step 1 - Create and store the label for the image
|
|
91
|
+
self.image_label = ttk.Label(main_frame)
|
|
92
|
+
self.image_label.grid(row=row, column=1, sticky="w", padx=5, pady=2)
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
img = Image.open(screenshot_path)
|
|
96
|
+
img.thumbnail((400, 300))
|
|
97
|
+
self.photo_image = ImageTk.PhotoImage(img)
|
|
98
|
+
# Configure the label we just created
|
|
99
|
+
self.image_label.config(image=self.photo_image)
|
|
100
|
+
# Keep a reference on the widget itself to prevent garbage collection!
|
|
101
|
+
self.image_label.image = self.photo_image
|
|
102
|
+
except Exception as e:
|
|
103
|
+
# Configure the label to show an error message
|
|
104
|
+
self.image_label.config(text=f"Could not load image:\n{screenshot_path}\n{e}", foreground="red")
|
|
105
|
+
|
|
106
|
+
# Open Screenshot Selector button
|
|
107
|
+
ttk.Button(main_frame, text="Open Screenshot Selector", command=self._get_different_screenshot).grid(row=row, column=2, sticky="w", padx=5, pady=2)
|
|
108
|
+
|
|
109
|
+
row += 1
|
|
110
|
+
|
|
111
|
+
# Audio Path
|
|
112
|
+
ttk.Label(main_frame, text="Audio Path:", font=("-weight bold")).grid(row=row, column=0, sticky="ne", padx=5, pady=2)
|
|
113
|
+
ttk.Label(main_frame, text=audio_path if audio_path else "No Audio", wraplength=400, justify="left").grid(row=row, column=1, sticky="w", padx=5, pady=2)
|
|
114
|
+
if audio_path and os.path.isfile(audio_path):
|
|
115
|
+
ttk.Button(main_frame, text="Play Audio", command=lambda: self._play_audio(audio_path)).grid(row=row, column=2, sticky="w", padx=5, pady=2)
|
|
116
|
+
|
|
117
|
+
row += 1
|
|
118
|
+
|
|
119
|
+
# Action Buttons
|
|
120
|
+
button_frame = ttk.Frame(main_frame)
|
|
121
|
+
button_frame.grid(row=row, column=0, columnspan=2, pady=15)
|
|
122
|
+
if audio_path and os.path.isfile(audio_path):
|
|
123
|
+
ttk.Button(button_frame, text="Voice", command=self._on_voice, bootstyle="success").pack(side="left", padx=10)
|
|
124
|
+
ttk.Button(button_frame, text="NO Voice", command=self._on_no_voice, bootstyle="danger").pack(side="left", padx=10)
|
|
125
|
+
else:
|
|
126
|
+
ttk.Button(button_frame, text="Confirm", command=self._on_no_voice, bootstyle="primary").pack(side="left", padx=10)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _get_different_screenshot(self):
|
|
130
|
+
video_path = gsm_state.current_replay
|
|
131
|
+
new_screenshot_path = self.config_app.show_screenshot_selector(
|
|
132
|
+
video_path, self.screenshot_timestamp, mode=get_config().screenshot.screenshot_timing_setting
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# If the user cancels the selector, it might return None or an empty string
|
|
136
|
+
if not new_screenshot_path:
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
self.screenshot_path = new_screenshot_path # Update the path to be returned later
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
img = Image.open(self.screenshot_path)
|
|
143
|
+
img.thumbnail((400, 300))
|
|
144
|
+
# Create the new image object
|
|
145
|
+
self.photo_image = ImageTk.PhotoImage(img)
|
|
146
|
+
|
|
147
|
+
# <<< CHANGED: Step 2 - Update the label with the new image
|
|
148
|
+
self.image_label.config(image=self.photo_image, text="") # Clear any previous error text
|
|
149
|
+
|
|
150
|
+
# This is crucial! Keep a reference to the new image object on the widget
|
|
151
|
+
# itself, so it doesn't get garbage-collected.
|
|
152
|
+
self.image_label.image = self.photo_image
|
|
153
|
+
|
|
154
|
+
except Exception as e:
|
|
155
|
+
# Handle cases where the newly selected file is invalid
|
|
156
|
+
self.image_label.config(image=None, text=f"Could not load new image:\n{e}", foreground="red")
|
|
157
|
+
self.image_label.image = None # Clear old image reference
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _play_audio(self, audio_path):
|
|
161
|
+
if not os.path.isfile(audio_path):
|
|
162
|
+
print(f"Audio file does not exist: {audio_path}")
|
|
163
|
+
return
|
|
164
|
+
try:
|
|
165
|
+
if platform.system() == "Windows":
|
|
166
|
+
os.startfile(audio_path)
|
|
167
|
+
elif platform.system() == "Darwin":
|
|
168
|
+
subprocess.run(["open", audio_path])
|
|
169
|
+
else:
|
|
170
|
+
subprocess.run(["xdg-open", audio_path])
|
|
171
|
+
except Exception as e:
|
|
172
|
+
print(f"Failed to play audio: {e}")
|
|
173
|
+
|
|
174
|
+
def _on_voice(self):
|
|
175
|
+
# The screenshot_path is now correctly updated if the user chose a new one
|
|
176
|
+
self.result = (True, self.sentence_text.get("1.0", tk.END).strip(), self.translation_text.get("1.0", tk.END).strip(), self.screenshot_path)
|
|
177
|
+
self.destroy()
|
|
178
|
+
|
|
179
|
+
def _on_no_voice(self):
|
|
180
|
+
self.result = (False, self.sentence_text.get("1.0", tk.END).strip(), self.translation_text.get("1.0", tk.END).strip(), self.screenshot_path)
|
|
181
|
+
self.destroy()
|
|
182
|
+
|
|
183
|
+
def _on_cancel(self):
|
|
184
|
+
# We block the cancel button, but if you wanted to enable it:
|
|
185
|
+
# self.result = None
|
|
186
|
+
# self.destroy()
|
|
187
|
+
pass
|
|
@@ -7,11 +7,14 @@ import sys
|
|
|
7
7
|
import time
|
|
8
8
|
import tkinter as tk
|
|
9
9
|
from tkinter import filedialog, messagebox, simpledialog, scrolledtext, font
|
|
10
|
+
from PIL import Image, ImageTk
|
|
10
11
|
|
|
11
12
|
import pyperclip
|
|
12
13
|
import ttkbootstrap as ttk
|
|
13
14
|
|
|
14
15
|
from GameSentenceMiner import obs
|
|
16
|
+
from GameSentenceMiner.ui.anki_confirmation import AnkiConfirmationDialog
|
|
17
|
+
from GameSentenceMiner.ui.screenshot_selector import ScreenshotSelectorDialog
|
|
15
18
|
from GameSentenceMiner.util import configuration
|
|
16
19
|
from GameSentenceMiner.util.communication.send import send_restart_signal
|
|
17
20
|
from GameSentenceMiner.util.configuration import Config, Locale, logger, CommonLanguages, ProfileConfig, General, Paths, \
|
|
@@ -40,7 +43,7 @@ def load_localization(locale=Locale.English):
|
|
|
40
43
|
try:
|
|
41
44
|
# Use a path relative to this script file
|
|
42
45
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
43
|
-
lang_file = os.path.join(script_dir, 'locales', f'{locale.value}.json')
|
|
46
|
+
lang_file = os.path.join(script_dir, '..', 'locales', f'{locale.value}.json')
|
|
44
47
|
with open(lang_file, 'r', encoding='utf-8') as f:
|
|
45
48
|
return json.load(f)['python']['config']
|
|
46
49
|
except (FileNotFoundError, json.JSONDecodeError) as e:
|
|
@@ -167,7 +170,6 @@ class ResetToDefaultButton(ttk.Button):
|
|
|
167
170
|
self.tooltip.destroy()
|
|
168
171
|
self.tooltip = None
|
|
169
172
|
|
|
170
|
-
|
|
171
173
|
class ConfigApp:
|
|
172
174
|
def __init__(self, root):
|
|
173
175
|
self.window = root
|
|
@@ -267,6 +269,56 @@ class ConfigApp:
|
|
|
267
269
|
def set_test_func(self, func):
|
|
268
270
|
self.test_func = func
|
|
269
271
|
|
|
272
|
+
def show_anki_confirmation_dialog(self, expression, sentence, screenshot_path, audio_path, translation, ss_timestamp):
|
|
273
|
+
"""
|
|
274
|
+
Displays a modal dialog for the user to confirm Anki card details and
|
|
275
|
+
choose whether to include audio.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
expression (str): The target word or expression.
|
|
279
|
+
sentence (str): The full sentence.
|
|
280
|
+
screenshot_path (str): The file path to the screenshot image.
|
|
281
|
+
audio_path (str): The file path to the audio clip.
|
|
282
|
+
translation (str): The translation or definition.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
str: 'voice' if the user chooses to add with voice,
|
|
286
|
+
'no_voice' if they choose to add without voice,
|
|
287
|
+
or None if they cancel.
|
|
288
|
+
"""
|
|
289
|
+
dialog = AnkiConfirmationDialog(self.window,
|
|
290
|
+
self,
|
|
291
|
+
expression=expression,
|
|
292
|
+
sentence=sentence,
|
|
293
|
+
screenshot_path=screenshot_path,
|
|
294
|
+
audio_path=audio_path,
|
|
295
|
+
translation=translation,
|
|
296
|
+
screenshot_timestamp=ss_timestamp)
|
|
297
|
+
return dialog.result
|
|
298
|
+
|
|
299
|
+
def show_screenshot_selector(self, video_path, timestamp, mode='beginning'):
|
|
300
|
+
"""
|
|
301
|
+
Displays a modal dialog for the user to select the best screenshot from
|
|
302
|
+
a series of extracted video frames.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
video_path (str): The file path to the source video.
|
|
306
|
+
timestamp (str or float): The timestamp (in seconds) around which to extract frames.
|
|
307
|
+
mode (str): 'beginning', 'middle', or 'end'. Determines the time offset
|
|
308
|
+
and which frame is highlighted as the "golden" frame.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
str: The file path of the image the user selected, or None if they canceled.
|
|
312
|
+
"""
|
|
313
|
+
dialog = ScreenshotSelectorDialog(self.window,
|
|
314
|
+
self,
|
|
315
|
+
video_path=video_path,
|
|
316
|
+
timestamp=str(timestamp), # Ensure it's a string for ffmpeg
|
|
317
|
+
mode=mode)
|
|
318
|
+
|
|
319
|
+
print(dialog.selected_path)
|
|
320
|
+
return dialog.selected_path
|
|
321
|
+
|
|
270
322
|
def create_vars(self):
|
|
271
323
|
"""
|
|
272
324
|
Initializes all the tkinter variables used in the configuration GUI.
|
|
@@ -290,9 +342,8 @@ class ConfigApp:
|
|
|
290
342
|
self.obs_password_value = tk.StringVar(value=self.settings.obs.password)
|
|
291
343
|
self.obs_open_obs_value = tk.BooleanVar(value=self.settings.obs.open_obs)
|
|
292
344
|
self.obs_close_obs_value = tk.BooleanVar(value=self.settings.obs.close_obs)
|
|
293
|
-
self.obs_get_game_from_scene_name_value = tk.BooleanVar(value=self.settings.obs.get_game_from_scene)
|
|
294
345
|
self.obs_minimum_replay_size_value = tk.StringVar(value=str(self.settings.obs.minimum_replay_size))
|
|
295
|
-
self.
|
|
346
|
+
self.automatically_manage_replay_buffer_value = tk.BooleanVar(value=self.settings.obs.automatically_manage_replay_buffer)
|
|
296
347
|
|
|
297
348
|
# Paths Settings
|
|
298
349
|
self.folder_to_watch_value = tk.StringVar(value=self.settings.paths.folder_to_watch)
|
|
@@ -306,6 +357,7 @@ class ConfigApp:
|
|
|
306
357
|
|
|
307
358
|
# Anki Settings
|
|
308
359
|
self.update_anki_value = tk.BooleanVar(value=self.settings.anki.update_anki)
|
|
360
|
+
self.show_update_confirmation_dialog_value = tk.BooleanVar(value=self.settings.anki.show_update_confirmation_dialog)
|
|
309
361
|
self.anki_url_value = tk.StringVar(value=self.settings.anki.url)
|
|
310
362
|
self.sentence_field_value = tk.StringVar(value=self.settings.anki.sentence_field)
|
|
311
363
|
self.sentence_audio_field_value = tk.StringVar(value=self.settings.anki.sentence_audio_field)
|
|
@@ -331,6 +383,7 @@ class ConfigApp:
|
|
|
331
383
|
self.open_anki_browser_value = tk.BooleanVar(value=self.settings.features.open_anki_in_browser)
|
|
332
384
|
self.backfill_audio_value = tk.BooleanVar(value=self.settings.features.backfill_audio)
|
|
333
385
|
self.browser_query_value = tk.StringVar(value=self.settings.features.browser_query)
|
|
386
|
+
self.generate_longplay_value = tk.BooleanVar(value=self.settings.features.generate_longplay)
|
|
334
387
|
|
|
335
388
|
# Screenshot Settings
|
|
336
389
|
self.screenshot_enabled_value = tk.BooleanVar(value=self.settings.screenshot.enabled)
|
|
@@ -534,6 +587,7 @@ class ConfigApp:
|
|
|
534
587
|
),
|
|
535
588
|
anki=Anki(
|
|
536
589
|
update_anki=self.update_anki_value.get(),
|
|
590
|
+
show_update_confirmation_dialog=self.show_update_confirmation_dialog_value.get(),
|
|
537
591
|
url=self.anki_url_value.get(),
|
|
538
592
|
sentence_field=self.sentence_field_value.get(),
|
|
539
593
|
sentence_audio_field=self.sentence_audio_field_value.get(),
|
|
@@ -559,6 +613,7 @@ class ConfigApp:
|
|
|
559
613
|
open_anki_in_browser=self.open_anki_browser_value.get(),
|
|
560
614
|
backfill_audio=self.backfill_audio_value.get(),
|
|
561
615
|
browser_query=self.browser_query_value.get(),
|
|
616
|
+
generate_longplay=self.generate_longplay_value.get(),
|
|
562
617
|
),
|
|
563
618
|
screenshot=Screenshot(
|
|
564
619
|
enabled=self.screenshot_enabled_value.get(),
|
|
@@ -590,9 +645,8 @@ class ConfigApp:
|
|
|
590
645
|
host=self.obs_host_value.get(),
|
|
591
646
|
port=int(self.obs_port_value.get()),
|
|
592
647
|
password=self.obs_password_value.get(),
|
|
593
|
-
get_game_from_scene=self.obs_get_game_from_scene_name_value.get(),
|
|
594
648
|
minimum_replay_size=int(self.obs_minimum_replay_size_value.get()),
|
|
595
|
-
|
|
649
|
+
automatically_manage_replay_buffer=self.automatically_manage_replay_buffer_value.get()
|
|
596
650
|
),
|
|
597
651
|
hotkeys=Hotkeys(
|
|
598
652
|
reset_line=self.reset_line_hotkey_value.get(),
|
|
@@ -838,21 +892,6 @@ class ConfigApp:
|
|
|
838
892
|
row=self.current_row, column=0)
|
|
839
893
|
ttk.Combobox(self.general_tab, textvariable=self.native_language_value, values=CommonLanguages.get_all_names_pretty(), state="readonly").grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
|
840
894
|
self.current_row += 1
|
|
841
|
-
|
|
842
|
-
legend_i18n = general_i18n.get('legend', {})
|
|
843
|
-
ttk.Label(self.general_tab, text=legend_i18n.get('important', '...'), foreground="dark orange",
|
|
844
|
-
font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
|
|
845
|
-
self.current_row += 1
|
|
846
|
-
ttk.Label(self.general_tab, text=legend_i18n.get('advanced', '...'), foreground="red",
|
|
847
|
-
font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
|
|
848
|
-
self.current_row += 1
|
|
849
|
-
ttk.Label(self.general_tab, text=legend_i18n.get('recommended', '...'), foreground="green",
|
|
850
|
-
font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
|
|
851
|
-
self.current_row += 1
|
|
852
|
-
ttk.Label(self.general_tab,
|
|
853
|
-
text=legend_i18n.get('tooltip_info', '...'),
|
|
854
|
-
font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
|
|
855
|
-
self.current_row += 1
|
|
856
895
|
|
|
857
896
|
if is_beangate:
|
|
858
897
|
ttk.Button(self.general_tab, text=self.i18n.get('buttons', {}).get('run_function', 'Run Function'), command=self.test_func, bootstyle="info").grid(
|
|
@@ -1040,6 +1079,26 @@ class ConfigApp:
|
|
|
1040
1079
|
row=self.current_row, column=3, sticky='W', pady=2)
|
|
1041
1080
|
self.current_row += 1
|
|
1042
1081
|
|
|
1082
|
+
# Add Horizontal Separator
|
|
1083
|
+
sep = ttk.Separator(feature_frame, orient='horizontal')
|
|
1084
|
+
sep.grid(row=self.current_row, column=0, columnspan=5, sticky='EW', pady=10)
|
|
1085
|
+
self.current_row += 1
|
|
1086
|
+
|
|
1087
|
+
legend_i18n = general_i18n.get('legend', {})
|
|
1088
|
+
ttk.Label(feature_frame,
|
|
1089
|
+
text=legend_i18n.get('tooltip_info', '...'),
|
|
1090
|
+
font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
|
|
1091
|
+
self.current_row += 1
|
|
1092
|
+
ttk.Label(feature_frame, text=legend_i18n.get('important', '...'), foreground="dark orange",
|
|
1093
|
+
font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
|
|
1094
|
+
self.current_row += 1
|
|
1095
|
+
ttk.Label(feature_frame, text=legend_i18n.get('advanced', '...'), foreground="red",
|
|
1096
|
+
font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
|
|
1097
|
+
self.current_row += 1
|
|
1098
|
+
ttk.Label(feature_frame, text=legend_i18n.get('recommended', '...'), foreground="green",
|
|
1099
|
+
font=("Helvetica", 10, "bold")).grid(row=self.current_row, column=0, columnspan=2, sticky='W', pady=2)
|
|
1100
|
+
self.current_row += 1
|
|
1101
|
+
|
|
1043
1102
|
# screenshot_i18n = simple_i18n.get('screenshot_enabled', {})
|
|
1044
1103
|
# HoverInfoLabelWidget(feature_frame, text=screenshot_i18n.get('label', '...'),
|
|
1045
1104
|
# tooltip=screenshot_i18n.get('tooltip', '...'), row=self.current_row, column=0)
|
|
@@ -1318,6 +1377,13 @@ class ConfigApp:
|
|
|
1318
1377
|
column=1, sticky='W', pady=2)
|
|
1319
1378
|
self.current_row += 1
|
|
1320
1379
|
|
|
1380
|
+
show_confirmation_i18n = anki_i18n.get('show_update_confirmation_dialog', {})
|
|
1381
|
+
HoverInfoLabelWidget(anki_frame, text=show_confirmation_i18n.get('label', '...'), tooltip=show_confirmation_i18n.get('tooltip', '...'),
|
|
1382
|
+
foreground="red", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
|
1383
|
+
ttk.Checkbutton(anki_frame, variable=self.show_update_confirmation_dialog_value, bootstyle="round-toggle").grid(row=self.current_row,
|
|
1384
|
+
column=1, sticky='W', pady=2)
|
|
1385
|
+
self.current_row += 1
|
|
1386
|
+
|
|
1321
1387
|
url_i18n = anki_i18n.get('url', {})
|
|
1322
1388
|
HoverInfoLabelWidget(anki_frame, text=url_i18n.get('label', '...'), tooltip=url_i18n.get('tooltip', '...'),
|
|
1323
1389
|
foreground="dark orange", font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
|
@@ -1492,6 +1558,12 @@ class ConfigApp:
|
|
|
1492
1558
|
row=self.current_row, column=1, sticky='W', pady=2)
|
|
1493
1559
|
self.current_row += 1
|
|
1494
1560
|
|
|
1561
|
+
HoverInfoLabelWidget(features_frame, text="Generate LongPlay", tooltip="Generate a LongPlay video using OBS recording, and write to a .srt file with all the text coming into gsm. RESTART REQUIRED FOR SETTING TO TAKE EFFECT.",
|
|
1562
|
+
row=self.current_row, column=0)
|
|
1563
|
+
ttk.Checkbutton(features_frame, variable=self.generate_longplay_value, bootstyle="round-toggle").grid(
|
|
1564
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
|
1565
|
+
self.current_row += 1
|
|
1566
|
+
|
|
1495
1567
|
self.add_reset_button(features_frame, "features", self.current_row, 0, self.create_features_tab)
|
|
1496
1568
|
|
|
1497
1569
|
for col in range(3):
|
|
@@ -1797,13 +1869,6 @@ class ConfigApp:
|
|
|
1797
1869
|
ttk.Entry(obs_frame, show="*", textvariable=self.obs_password_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
|
1798
1870
|
self.current_row += 1
|
|
1799
1871
|
|
|
1800
|
-
game_scene_i18n = obs_i18n.get('game_from_scene', {})
|
|
1801
|
-
HoverInfoLabelWidget(obs_frame, text=game_scene_i18n.get('label', '...'), tooltip=game_scene_i18n.get('tooltip', '...'),
|
|
1802
|
-
row=self.current_row, column=0)
|
|
1803
|
-
ttk.Checkbutton(obs_frame, variable=self.obs_get_game_from_scene_name_value, bootstyle="round-toggle").grid(
|
|
1804
|
-
row=self.current_row, column=1, sticky='W', pady=2)
|
|
1805
|
-
self.current_row += 1
|
|
1806
|
-
|
|
1807
1872
|
min_size_i18n = obs_i18n.get('min_replay_size', {})
|
|
1808
1873
|
HoverInfoLabelWidget(obs_frame, text=min_size_i18n.get('label', '...'),
|
|
1809
1874
|
tooltip=min_size_i18n.get('tooltip', '...'),
|
|
@@ -1811,13 +1876,13 @@ class ConfigApp:
|
|
|
1811
1876
|
ttk.Entry(obs_frame, textvariable=self.obs_minimum_replay_size_value).grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
|
1812
1877
|
self.current_row += 1
|
|
1813
1878
|
|
|
1814
|
-
turn_off_output_check_i18n = obs_i18n.get('
|
|
1815
|
-
HoverInfoLabelWidget(obs_frame, text=turn_off_output_check_i18n.get('label', '...'),
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
ttk.Checkbutton(obs_frame, variable=self.obs_turn_off_output_check_value, bootstyle="round-toggle").grid(
|
|
1819
|
-
|
|
1820
|
-
self.current_row += 1
|
|
1879
|
+
# turn_off_output_check_i18n = obs_i18n.get('turn_off_replay_buffer_management', {})
|
|
1880
|
+
# HoverInfoLabelWidget(obs_frame, text=turn_off_output_check_i18n.get('label', '...'),
|
|
1881
|
+
# tooltip=turn_off_output_check_i18n.get('tooltip', '...'),
|
|
1882
|
+
# row=self.current_row, column=0)
|
|
1883
|
+
# ttk.Checkbutton(obs_frame, variable=self.obs_turn_off_output_check_value, bootstyle="round-toggle").grid(
|
|
1884
|
+
# row=self.current_row, column=1, sticky='W', pady=2)
|
|
1885
|
+
# self.current_row += 1
|
|
1821
1886
|
|
|
1822
1887
|
self.add_reset_button(obs_frame, "obs", self.current_row, 0, self.create_obs_tab)
|
|
1823
1888
|
|