GameSentenceMiner 2.7.9__py3-none-any.whl → 2.7.10__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/ffmpeg.py +2 -2
- GameSentenceMiner/gsm.py +1 -1
- GameSentenceMiner/ocr/gsm_ocr_config.py +1 -17
- GameSentenceMiner/ocr/owocr_area_selector.py +1 -1
- GameSentenceMiner/ocr/owocr_helper.py +74 -55
- GameSentenceMiner/utility_gui.py +53 -59
- {gamesentenceminer-2.7.9.dist-info → gamesentenceminer-2.7.10.dist-info}/METADATA +1 -1
- {gamesentenceminer-2.7.9.dist-info → gamesentenceminer-2.7.10.dist-info}/RECORD +12 -12
- {gamesentenceminer-2.7.9.dist-info → gamesentenceminer-2.7.10.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.7.9.dist-info → gamesentenceminer-2.7.10.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.7.9.dist-info → gamesentenceminer-2.7.10.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.7.9.dist-info → gamesentenceminer-2.7.10.dist-info}/top_level.txt +0 -0
GameSentenceMiner/ffmpeg.py
CHANGED
@@ -52,7 +52,7 @@ def get_screenshot_for_line(video_file, game_line):
|
|
52
52
|
return get_screenshot(video_file, get_screenshot_time(video_file, game_line))
|
53
53
|
|
54
54
|
|
55
|
-
def get_screenshot_time(video_path, game_line, default_beginning=False, vad_beginning=None, vad_end=None):
|
55
|
+
def get_screenshot_time(video_path, game_line, default_beginning=False, vad_beginning=None, vad_end=None, doing_multi_line=False):
|
56
56
|
if game_line:
|
57
57
|
line_time = game_line.time
|
58
58
|
else:
|
@@ -68,7 +68,7 @@ def get_screenshot_time(video_path, game_line, default_beginning=False, vad_begi
|
|
68
68
|
screenshot_offset = get_config().screenshot.seconds_after_line
|
69
69
|
|
70
70
|
# Calculate screenshot time from the beginning by adding the offset
|
71
|
-
if vad_beginning and vad_end:
|
71
|
+
if vad_beginning and vad_end and not doing_multi_line:
|
72
72
|
logger.debug("Using VAD to determine screenshot time")
|
73
73
|
screenshot_time_from_beginning = line_timestamp_in_video + vad_end - screenshot_offset
|
74
74
|
elif get_config().screenshot.use_new_screenshot_logic:
|
GameSentenceMiner/gsm.py
CHANGED
@@ -141,7 +141,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
141
141
|
|
142
142
|
ss_timing = 1
|
143
143
|
if mined_line and line_cutoff or mined_line and get_config().screenshot.use_beginning_of_line_as_screenshot:
|
144
|
-
ss_timing = ffmpeg.get_screenshot_time(video_path, mined_line, vad_beginning, vad_end)
|
144
|
+
ss_timing = ffmpeg.get_screenshot_time(video_path, mined_line, vad_beginning, vad_end, bool(selected_lines))
|
145
145
|
|
146
146
|
if get_config().anki.update_anki and last_note:
|
147
147
|
anki.update_anki_card(last_note, note, audio_path=final_audio_output, video_path=video_path,
|
@@ -23,20 +23,4 @@ class Rectangle:
|
|
23
23
|
class OCRConfig:
|
24
24
|
scene: str
|
25
25
|
rectangles: List[Rectangle]
|
26
|
-
window: Optional[str] = None
|
27
|
-
|
28
|
-
# Example of how you might use from_dict (assuming you have a dictionary called 'data')
|
29
|
-
data = {
|
30
|
-
"scene": "CODEVEIN",
|
31
|
-
"window": "CODE VEIN",
|
32
|
-
"rectangles": [
|
33
|
-
{
|
34
|
-
"monitor": {"left": 0, "top": 0, "width": 2560, "height": 1440, "index": 0},
|
35
|
-
"coordinates": [749, 1178, 1100, 147],
|
36
|
-
"is_excluded": False,
|
37
|
-
}
|
38
|
-
],
|
39
|
-
}
|
40
|
-
|
41
|
-
config = OCRConfig.from_dict(data)
|
42
|
-
print(config)
|
26
|
+
window: Optional[str] = None
|
@@ -88,7 +88,7 @@ class ScreenSelector:
|
|
88
88
|
serializable_rects.append(rect_data)
|
89
89
|
|
90
90
|
print(serializable_rects)
|
91
|
-
with open(config_path, 'w') as f:
|
91
|
+
with open(config_path, 'w', encoding="utf-8") as f:
|
92
92
|
json.dump({"scene": sanitize_filename(obs.get_current_scene()), "window": self.window_name, "rectangles": serializable_rects}, f, indent=4)
|
93
93
|
print("Rectangles saved.")
|
94
94
|
self.result['rectangles'] = self.rectangles.copy()
|
@@ -13,6 +13,7 @@ from tkinter import messagebox
|
|
13
13
|
|
14
14
|
import mss
|
15
15
|
import websockets
|
16
|
+
from rapidfuzz import fuzz
|
16
17
|
from PIL import Image, ImageDraw
|
17
18
|
|
18
19
|
from GameSentenceMiner import obs, util
|
@@ -75,7 +76,7 @@ def get_new_game_cords():
|
|
75
76
|
return coords_list
|
76
77
|
|
77
78
|
|
78
|
-
def get_ocr_config():
|
79
|
+
def get_ocr_config() -> OCRConfig:
|
79
80
|
"""Loads and updates screen capture areas from the corresponding JSON file."""
|
80
81
|
app_dir = Path.home() / "AppData" / "Roaming" / "GameSentenceMiner"
|
81
82
|
ocr_config_dir = app_dir / "ocr_config"
|
@@ -85,7 +86,7 @@ def get_ocr_config():
|
|
85
86
|
if not config_path.exists():
|
86
87
|
raise Exception(f"No config file found at {config_path}.")
|
87
88
|
try:
|
88
|
-
with open(config_path, 'r') as f:
|
89
|
+
with open(config_path, 'r', encoding="utf-8") as f:
|
89
90
|
config_data = json.load(f)
|
90
91
|
if "rectangles" in config_data and isinstance(config_data["rectangles"], list) and all(
|
91
92
|
isinstance(item, list) and len(item) == 4 for item in config_data["rectangles"]):
|
@@ -120,7 +121,7 @@ def get_ocr_config():
|
|
120
121
|
})
|
121
122
|
new_config_data = {"scene": config_data.get("scene", scene), "window": config_data.get("window", None),
|
122
123
|
"rectangles": new_rectangles}
|
123
|
-
with open(config_path, 'w') as f:
|
124
|
+
with open(config_path, 'w', encoding="utf-8") as f:
|
124
125
|
json.dump(new_config_data, f, indent=4)
|
125
126
|
return OCRConfig.from_dict(new_config_data)
|
126
127
|
elif "rectangles" in config_data and isinstance(config_data["rectangles"], list) and all(
|
@@ -206,63 +207,78 @@ class WebsocketServerThread(threading.Thread):
|
|
206
207
|
all_cords = None
|
207
208
|
rectangles = None
|
208
209
|
|
210
|
+
def do_second_ocr(ocr1_text, rectangle_index, time, img):
|
211
|
+
global twopassocr, ocr2, last_ocr1_results, last_ocr2_results
|
212
|
+
last_result = ([], -1)
|
213
|
+
|
214
|
+
previous_ocr1_text = last_ocr1_results[rectangle_index]
|
215
|
+
if fuzz.ratio(previous_ocr1_text, ocr1_text) >= 80:
|
216
|
+
logger.info("Seems like the same text, not doing 2nd pass")
|
217
|
+
last_ocr1_results[rectangle_index] = ocr1_text
|
218
|
+
try:
|
219
|
+
orig_text, text = run.process_and_write_results(img, None, None, last_result, TextFiltering(),
|
220
|
+
engine=ocr2)
|
221
|
+
previous_ocr2_text = last_ocr2_results[rectangle_index]
|
222
|
+
if fuzz.ratio(previous_ocr2_text, text) >= 80:
|
223
|
+
logger.info("Seems like the same text from previous ocr2 result, not sending")
|
224
|
+
return
|
225
|
+
last_ocr2_results[rectangle_index] = text
|
226
|
+
if get_config().advanced.ocr_sends_to_clipboard:
|
227
|
+
import pyperclip
|
228
|
+
pyperclip.copy(text)
|
229
|
+
websocket_server_thread.send_text(text, time)
|
230
|
+
except json.JSONDecodeError:
|
231
|
+
print("Invalid JSON received.")
|
232
|
+
except Exception as e:
|
233
|
+
logger.exception(e)
|
234
|
+
print(f"Error processing message: {e}")
|
235
|
+
|
236
|
+
|
237
|
+
last_oneocr_results_to_check = {} # Store last OCR result for each rectangle
|
238
|
+
last_oneocr_times = {} # Store last OCR time for each rectangle
|
239
|
+
text_stable_start_times = {} # Store the start time when text becomes stable for each rectangle
|
240
|
+
TEXT_APPEARENCE_DELAY = get_ocr_scan_rate() * 1000 + 500 # Adjust as needed
|
209
241
|
|
210
242
|
def text_callback(text, rectangle_index, time, img=None):
|
211
|
-
global twopassocr, ocr2,
|
243
|
+
global twopassocr, ocr2, last_oneocr_results_to_check, last_oneocr_times, text_stable_start_times
|
244
|
+
|
245
|
+
current_time = time if time else datetime.now()
|
246
|
+
|
247
|
+
previous_text = last_oneocr_results_to_check.get(rectangle_index, "")
|
248
|
+
|
212
249
|
if not text:
|
250
|
+
if previous_text:
|
251
|
+
if rectangle_index in text_stable_start_times:
|
252
|
+
stable_time = text_stable_start_times[rectangle_index]
|
253
|
+
if twopassocr:
|
254
|
+
do_second_ocr(previous_text, rectangle_index, time, img)
|
255
|
+
else:
|
256
|
+
previous_ocr1_text = last_ocr1_results[rectangle_index]
|
257
|
+
if fuzz.ratio(previous_ocr1_text, text) >= 80:
|
258
|
+
logger.info("Seems like the same text, not sending")
|
259
|
+
last_ocr1_results[rectangle_index] = text
|
260
|
+
websocket_server_thread.send_text(previous_text, stable_time)
|
261
|
+
del text_stable_start_times[rectangle_index]
|
262
|
+
del last_oneocr_results_to_check[rectangle_index]
|
213
263
|
return
|
214
|
-
|
215
|
-
|
264
|
+
|
265
|
+
if rectangle_index not in last_oneocr_results_to_check:
|
266
|
+
last_oneocr_results_to_check[rectangle_index] = text
|
267
|
+
last_oneocr_times[rectangle_index] = current_time
|
268
|
+
text_stable_start_times[rectangle_index] = current_time
|
216
269
|
return
|
217
|
-
with mss.mss() as sct:
|
218
|
-
line_time = time if time else datetime.now()
|
219
|
-
logger.info(f"Received message: {text}, ATTEMPTING LENS OCR")
|
220
|
-
if rectangles:
|
221
|
-
rect_data: Rectangle = rectangles[rectangle_index]
|
222
|
-
cords = rect_data.coordinates
|
223
|
-
i = rectangle_index
|
224
|
-
else:
|
225
|
-
i = 0
|
226
|
-
mon = sct.monitors
|
227
|
-
cords = [mon[1]['left'], mon[1]['top'], mon[1]['width'], mon[1]['height']]
|
228
|
-
similarity = difflib.SequenceMatcher(None, last_oneocr_results[i], text).ratio()
|
229
|
-
if similarity > .8:
|
230
|
-
return
|
231
|
-
logger.debug(f"Similarity for region {i}: {similarity}")
|
232
|
-
last_oneocr_results[i] = text
|
233
|
-
last_result = ([], -1)
|
234
|
-
try:
|
235
|
-
# sct_params = {'left': cords[0], 'top': cords[1], 'width': cords[2], 'height': cords[3]}
|
236
|
-
# sct_img = sct.grab(sct_params)
|
237
|
-
# img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
|
238
|
-
# img = img.convert("RGBA")
|
239
|
-
# draw = ImageDraw.Draw(img)
|
240
|
-
# excluded_rects = ocr_config.get("excluded_rectangles", [])
|
241
|
-
# if isinstance(excluded_rects, list) and all(
|
242
|
-
# isinstance(item, list) and len(item) == 4 for item in excluded_rects):
|
243
|
-
# for exclusion in excluded_rects:
|
244
|
-
# left, top, right, bottom = exclusion
|
245
|
-
# draw.rectangle((left, top, right, bottom), fill=(0, 0, 0, 0))
|
246
|
-
# elif isinstance(excluded_rects, list) and all(
|
247
|
-
# isinstance(item, dict) and "coordinates" in item and isinstance(item["coordinates"], list) and len(
|
248
|
-
# item["coordinates"]) == 4 for item in excluded_rects):
|
249
|
-
# for exclusion in excluded_rects:
|
250
|
-
# left, top, right, bottom = exclusion["coordinates"]
|
251
|
-
# draw.rectangle((left, top, right, bottom), fill=(0, 0, 0, 0))
|
252
|
-
orig_text, text = run.process_and_write_results(img, None, None, last_result, TextFiltering(),
|
253
|
-
engine=ocr2)
|
254
|
-
if ":gsm_prefix:" in text:
|
255
|
-
text = text.split(":gsm_prefix:")[1]
|
256
|
-
if get_config().advanced.ocr_sends_to_clipboard:
|
257
|
-
import pyperclip
|
258
|
-
pyperclip.copy(text)
|
259
|
-
websocket_server_thread.send_text(text, line_time)
|
260
|
-
except json.JSONDecodeError:
|
261
|
-
print("Invalid JSON received.")
|
262
|
-
except Exception as e:
|
263
|
-
logger.exception(e)
|
264
|
-
print(f"Error processing message: {e}")
|
265
270
|
|
271
|
+
stable = text_stable_start_times.get(rectangle_index)
|
272
|
+
|
273
|
+
if stable:
|
274
|
+
time_since_stable_ms = int((current_time - stable).total_seconds() * 1000)
|
275
|
+
|
276
|
+
if time_since_stable_ms >= TEXT_APPEARENCE_DELAY:
|
277
|
+
last_oneocr_results_to_check[rectangle_index] = text
|
278
|
+
last_oneocr_times[rectangle_index] = current_time
|
279
|
+
else:
|
280
|
+
last_oneocr_results_to_check[rectangle_index] = text
|
281
|
+
last_oneocr_times[rectangle_index] = current_time
|
266
282
|
|
267
283
|
done = False
|
268
284
|
|
@@ -305,12 +321,15 @@ if __name__ == "__main__":
|
|
305
321
|
twopassocr = False
|
306
322
|
else:
|
307
323
|
ocr1 = "oneocr"
|
324
|
+
ocr2 = "glens"
|
325
|
+
twopassocr = True
|
308
326
|
logger.info(f"Received arguments: ocr1={ocr1}, ocr2={ocr2}, twopassocr={twopassocr}")
|
309
327
|
global ocr_config
|
310
328
|
ocr_config: OCRConfig = get_ocr_config()
|
311
329
|
if ocr_config and ocr_config.rectangles:
|
312
330
|
rectangles = ocr_config.rectangles
|
313
|
-
|
331
|
+
last_ocr1_results = [""] * len(rectangles) if rectangles else [""]
|
332
|
+
last_ocr2_results = [""] * len(rectangles) if rectangles else [""]
|
314
333
|
oneocr_threads = []
|
315
334
|
run.init_config(False)
|
316
335
|
if rectangles:
|
GameSentenceMiner/utility_gui.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import json
|
2
2
|
import os
|
3
3
|
import tkinter as tk
|
4
|
-
from tkinter import ttk
|
4
|
+
from tkinter import ttk, Scrollbar
|
5
5
|
|
6
6
|
from GameSentenceMiner import obs
|
7
7
|
from GameSentenceMiner.configuration import logger, get_app_directory, get_config
|
@@ -13,19 +13,19 @@ class UtilityApp:
|
|
13
13
|
self.items = []
|
14
14
|
self.play_audio_buttons = []
|
15
15
|
self.get_screenshot_buttons = []
|
16
|
-
self.
|
16
|
+
self.checkbox_vars = []
|
17
17
|
self.multi_mine_window = None # Store the multi-mine window reference
|
18
18
|
self.checkbox_frame = None
|
19
|
+
self.canvas = None
|
20
|
+
self.scrollbar = None
|
19
21
|
self.line_for_audio = None
|
20
22
|
self.line_for_screenshot = None
|
21
|
-
self.line_counter = 0
|
22
23
|
|
23
24
|
style = ttk.Style()
|
24
25
|
style.configure("TCheckbutton", font=("Arial", 20)) # Change the font and size
|
25
26
|
self.config_file = os.path.join(get_app_directory(), "multi-mine-window-config.json")
|
26
27
|
self.load_window_config()
|
27
28
|
|
28
|
-
|
29
29
|
def save_window_config(self):
|
30
30
|
if self.multi_mine_window:
|
31
31
|
config = {
|
@@ -58,20 +58,33 @@ class UtilityApp:
|
|
58
58
|
self.multi_mine_window.title("Multi Mine Window")
|
59
59
|
|
60
60
|
self.multi_mine_window.geometry(f"{self.window_width}x{self.window_height}+{self.window_x}+{self.window_y}")
|
61
|
-
|
62
61
|
self.multi_mine_window.minsize(800, 400)
|
63
62
|
|
64
|
-
self.
|
65
|
-
self.
|
63
|
+
self.canvas = tk.Canvas(self.multi_mine_window)
|
64
|
+
self.scrollbar = Scrollbar(self.multi_mine_window, orient="vertical", command=self.canvas.yview)
|
65
|
+
self.checkbox_frame = ttk.Frame(self.canvas)
|
66
|
+
|
67
|
+
self.canvas.configure(yscrollcommand=self.scrollbar.set)
|
68
|
+
|
69
|
+
self.scrollbar.pack(side="right", fill="y")
|
70
|
+
self.canvas.pack(side="left", fill="both", expand=True)
|
71
|
+
self.canvas.create_window((0, 0), window=self.checkbox_frame, anchor="nw")
|
72
|
+
|
73
|
+
self.checkbox_frame.bind("<Configure>", self.on_frame_configure)
|
66
74
|
|
67
75
|
for line, var in self.items:
|
68
76
|
self.add_checkbox_to_gui(line, var)
|
77
|
+
self.scroll_to_bottom()
|
69
78
|
|
70
79
|
self.multi_mine_window.protocol("WM_DELETE_WINDOW", self.on_close)
|
71
80
|
else:
|
72
81
|
self.multi_mine_window.deiconify()
|
73
82
|
self.multi_mine_window.lift()
|
74
83
|
|
84
|
+
def on_frame_configure(self, event):
|
85
|
+
"""Reset the scroll region to encompass the inner frame"""
|
86
|
+
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
|
87
|
+
|
75
88
|
def on_close(self):
|
76
89
|
self.save_window_config()
|
77
90
|
self.multi_mine_window.withdraw()
|
@@ -81,48 +94,44 @@ class UtilityApp:
|
|
81
94
|
try:
|
82
95
|
var = tk.BooleanVar()
|
83
96
|
self.items.append((line, var))
|
97
|
+
if self.multi_mine_window and tk.Toplevel.winfo_exists(self.multi_mine_window):
|
98
|
+
self.add_checkbox_to_gui(line, var)
|
99
|
+
self.scroll_to_bottom()
|
84
100
|
except Exception as e:
|
85
101
|
logger.error(f"NOT AN ERROR: Attempted to add text to multi-mine window, before it was initialized: {e}")
|
86
102
|
return
|
87
103
|
|
88
|
-
if len(self.items) > 10:
|
89
|
-
if self.checkboxes:
|
90
|
-
self.checkboxes[0].destroy()
|
91
|
-
self.checkboxes.pop(0)
|
92
|
-
self.items.pop(0)
|
93
|
-
if self.play_audio_buttons:
|
94
|
-
self.play_audio_buttons[0].destroy()
|
95
|
-
self.play_audio_buttons.pop(0)
|
96
|
-
if self.get_screenshot_buttons:
|
97
|
-
self.get_screenshot_buttons[0].destroy()
|
98
|
-
self.get_screenshot_buttons.pop(0)
|
99
|
-
|
100
|
-
if self.multi_mine_window and tk.Toplevel.winfo_exists(self.multi_mine_window):
|
101
|
-
self.add_checkbox_to_gui(line, var)
|
102
104
|
self.line_for_audio = None
|
103
105
|
self.line_for_screenshot = None
|
104
106
|
|
105
107
|
def add_checkbox_to_gui(self, line, var):
|
106
108
|
""" Add a single checkbox without repainting everything. """
|
107
109
|
if self.checkbox_frame:
|
110
|
+
row = len(self.checkbox_vars)
|
108
111
|
column = 0
|
112
|
+
|
109
113
|
if get_config().advanced.show_screenshot_buttons:
|
110
114
|
get_screenshot_button = ttk.Button(self.checkbox_frame, text="📸", command=lambda: self.take_screenshot(line))
|
111
|
-
get_screenshot_button.grid(row=
|
112
|
-
self.get_screenshot_buttons.append(get_screenshot_button)
|
115
|
+
get_screenshot_button.grid(row=row, column=column, sticky='w', padx=5)
|
113
116
|
column += 1
|
114
117
|
|
115
118
|
if get_config().advanced.video_player_path or get_config().advanced.audio_player_path:
|
116
119
|
play_audio_button = ttk.Button(self.checkbox_frame, text="🔊", command=lambda: self.play_audio(line))
|
117
|
-
play_audio_button.grid(row=
|
118
|
-
self.play_audio_buttons.append(play_audio_button)
|
120
|
+
play_audio_button.grid(row=row, column=column, sticky='w', padx=5)
|
119
121
|
column += 1
|
120
122
|
|
121
123
|
chk = ttk.Checkbutton(self.checkbox_frame, text=f"{line.time.strftime('%H:%M:%S')} - {line.text}", variable=var)
|
122
|
-
chk.grid(row=
|
123
|
-
self.
|
124
|
+
chk.grid(row=row, column=column, sticky='w', padx=5)
|
125
|
+
self.checkbox_vars.append(var)
|
126
|
+
|
127
|
+
# Update scroll region after adding a new item
|
128
|
+
self.checkbox_frame.update_idletasks()
|
129
|
+
self.canvas.config(scrollregion=self.canvas.bbox("all"))
|
124
130
|
|
125
|
-
|
131
|
+
|
132
|
+
def scroll_to_bottom(self):
|
133
|
+
"""Scroll the canvas to the bottom"""
|
134
|
+
self.canvas.yview_moveto(1.0)
|
126
135
|
|
127
136
|
|
128
137
|
def play_audio(self, line):
|
@@ -134,25 +143,16 @@ class UtilityApp:
|
|
134
143
|
obs.save_replay_buffer()
|
135
144
|
|
136
145
|
|
137
|
-
# def update_multi_mine_window(self):
|
138
|
-
# for widget in self.multi_mine_window.winfo_children():
|
139
|
-
# widget.destroy()
|
140
|
-
#
|
141
|
-
# for i, (text, var, time) in enumerate(self.items):
|
142
|
-
# time: datetime
|
143
|
-
# chk = ttk.Checkbutton(self.checkbox_frame, text=f"{time.strftime('%H:%M:%S')} - {text}", variable=var)
|
144
|
-
# chk.pack(anchor='w')
|
145
|
-
|
146
146
|
def get_selected_lines(self):
|
147
|
-
filtered_items = [line for line, var in self.items if var.get()]
|
147
|
+
filtered_items = [line for (line, _), var in zip(self.items, self.checkbox_vars) if var.get()]
|
148
148
|
return filtered_items if len(filtered_items) > 0 else []
|
149
149
|
|
150
150
|
|
151
151
|
def get_next_line_timing(self):
|
152
|
-
selected_lines = [line for line, var in self.items if var.get()]
|
152
|
+
selected_lines = [line for (line, _), var in zip(self.items, self.checkbox_vars) if var.get()]
|
153
153
|
|
154
154
|
if len(selected_lines) >= 2:
|
155
|
-
last_checked_index = max(i for i,
|
155
|
+
last_checked_index = max(i for i, var in enumerate(self.checkbox_vars) if var.get())
|
156
156
|
|
157
157
|
if last_checked_index + 1 < len(self.items):
|
158
158
|
next_time = self.items[last_checked_index + 1][0].time
|
@@ -167,30 +167,14 @@ class UtilityApp:
|
|
167
167
|
|
168
168
|
|
169
169
|
def lines_selected(self):
|
170
|
-
filter_times = [line.time for line, var in self.items if var.get()]
|
170
|
+
filter_times = [line.time for (line, _), var in zip(self.items, self.checkbox_vars) if var.get()]
|
171
171
|
if len(filter_times) > 0:
|
172
172
|
return True
|
173
173
|
return False
|
174
174
|
|
175
|
-
# def validate_checkboxes(self, *args):
|
176
|
-
# logger.debug("Validating checkboxes")
|
177
|
-
# found_checked = False
|
178
|
-
# found_unchecked = False
|
179
|
-
# for _, var in self.items:
|
180
|
-
# if var.get():
|
181
|
-
# if found_unchecked:
|
182
|
-
# messagebox.showinfo("Invalid", "Can only select neighboring checkboxes.")
|
183
|
-
# break
|
184
|
-
# found_checked = True
|
185
|
-
# if found_checked and not var.get():
|
186
|
-
# found_unchecked = True
|
187
|
-
|
188
175
|
def reset_checkboxes(self):
|
189
|
-
for
|
176
|
+
for var in self.checkbox_vars:
|
190
177
|
var.set(False)
|
191
|
-
# if self.multi_mine_window:
|
192
|
-
# for checkbox in self.checkboxes:
|
193
|
-
# checkbox.set(False)
|
194
178
|
|
195
179
|
|
196
180
|
def init_utility_window(root):
|
@@ -207,4 +191,14 @@ utility_window: UtilityApp = None
|
|
207
191
|
if __name__ == "__main__":
|
208
192
|
root = tk.Tk()
|
209
193
|
app = UtilityApp(root)
|
210
|
-
|
194
|
+
|
195
|
+
# Simulate adding a lot of lines
|
196
|
+
import datetime
|
197
|
+
now = datetime.datetime.now()
|
198
|
+
for i in range(100):
|
199
|
+
from GameSentenceMiner.gametext import GameLine
|
200
|
+
line = GameLine(f"This is line {i}", now + datetime.timedelta(seconds=i), prev=None, next=None)
|
201
|
+
app.add_text(line)
|
202
|
+
app.show()
|
203
|
+
|
204
|
+
root.mainloop()
|
@@ -3,15 +3,15 @@ GameSentenceMiner/anki.py,sha256=9E9GRR2zylW3Gp4PNlwYS_Nn-mhojZkjFqfYlTazte8,140
|
|
3
3
|
GameSentenceMiner/config_gui.py,sha256=-1PanqdtTKiwItxeyt0piXrWo7lGMWwrC4iSo4NiPz4,67521
|
4
4
|
GameSentenceMiner/configuration.py,sha256=TIL8yCr-FOScCA4OJt-BAtjEb50wtqFFrpmN-UlaQh4,20405
|
5
5
|
GameSentenceMiner/electron_config.py,sha256=dGcPYCISPehXubYSzsDuI2Gl092MYK0u3bTnkL9Jh1Y,9787
|
6
|
-
GameSentenceMiner/ffmpeg.py,sha256=
|
6
|
+
GameSentenceMiner/ffmpeg.py,sha256=Du31elvSmcbfeNlx_TvqkbkmSfXCHCf4mfklBt5rLaU,13408
|
7
7
|
GameSentenceMiner/gametext.py,sha256=AAke4swwmN16da0IpyL5xMhU23nTz_c6z2kMfXPYv-8,9194
|
8
|
-
GameSentenceMiner/gsm.py,sha256=
|
8
|
+
GameSentenceMiner/gsm.py,sha256=aTJho7V9WTup7kxwMrpwCEXg1u_rsPn2XaRzClK2qI0,24849
|
9
9
|
GameSentenceMiner/model.py,sha256=JdnkT4VoPOXmOpRgFdvERZ09c9wLN6tUJxdrKlGZcqo,5305
|
10
10
|
GameSentenceMiner/notification.py,sha256=FY39ChSRK0Y8TQ6lBGsLnpZUFPtFpSy2tweeXVoV7kc,2809
|
11
11
|
GameSentenceMiner/obs.py,sha256=3h1hh868zdXQFGXJ7_mQZs5kcudEMa3yBOrbCVckhCs,9139
|
12
12
|
GameSentenceMiner/package.py,sha256=YlS6QRMuVlm6mdXx0rlXv9_3erTGS21jaP3PNNWfAH0,1250
|
13
13
|
GameSentenceMiner/util.py,sha256=W49gqYlhbzZe-17zHMFcAjNeeteXrv_USHT7aBDKSqM,6825
|
14
|
-
GameSentenceMiner/utility_gui.py,sha256=
|
14
|
+
GameSentenceMiner/utility_gui.py,sha256=JcNjk9Wrz_Au7P4ITslSBqXBU-o6nA2sc2vJvglAk4o,7432
|
15
15
|
GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
16
|
GameSentenceMiner/ai/gemini.py,sha256=6kTQPuRH16D-1srhrWa5uPGIy-jBqNm9eRdKBSbpiXA,5423
|
17
17
|
GameSentenceMiner/communication/__init__.py,sha256=_jGn9PJxtOAOPtJ2rI-Qu9hEHVZVpIvWlxKvqk91_zI,638
|
@@ -21,10 +21,10 @@ GameSentenceMiner/downloader/Untitled_json.py,sha256=RUUl2bbbCpUDUUS0fP0tdvf5Fng
|
|
21
21
|
GameSentenceMiner/downloader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
22
|
GameSentenceMiner/downloader/download_tools.py,sha256=mI1u_FGBmBqDIpCH3jOv8DOoZ3obgP5pIf9o9SVfX2Q,8131
|
23
23
|
GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
|
-
GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=
|
24
|
+
GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=FZ3CCgtpVawJ2mq3EP0KheJjYte5PlyOO1VnGND-__Y,467
|
25
25
|
GameSentenceMiner/ocr/ocrconfig.py,sha256=hTROOZ3On2HngXKxwQFZvnr5AxlmlMV0mPxv-F3NbMg,6476
|
26
|
-
GameSentenceMiner/ocr/owocr_area_selector.py,sha256=
|
27
|
-
GameSentenceMiner/ocr/owocr_helper.py,sha256=
|
26
|
+
GameSentenceMiner/ocr/owocr_area_selector.py,sha256=MggSnJSUQhs7SD6YTKRNnVhEZEliLgaUTOPkBUCjAss,11952
|
27
|
+
GameSentenceMiner/ocr/owocr_helper.py,sha256=wU6zXr27XzH8cw2xkAJ6HtFSPRVpWOWGuhpC775NMQQ,14742
|
28
28
|
GameSentenceMiner/owocr/owocr/__init__.py,sha256=opjBOyGGyEqZCE6YdZPnyt7nVfiwyELHsXA0jAsjm14,25
|
29
29
|
GameSentenceMiner/owocr/owocr/__main__.py,sha256=r8MI6RAmbkTWqOJ59uvXoDS7CSw5jX5war9ULGWELrA,128
|
30
30
|
GameSentenceMiner/owocr/owocr/config.py,sha256=738QCJHEWpFhMh966plOcXYWwcshSiRsxjjIwldeTtI,7461
|
@@ -36,9 +36,9 @@ GameSentenceMiner/vad/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
|
|
36
36
|
GameSentenceMiner/vad/silero_trim.py,sha256=ULf3zwS-JMsY82cKF7gZxREHw8L6lgpWF2U1YqgE9Oc,1681
|
37
37
|
GameSentenceMiner/vad/vosk_helper.py,sha256=125X8C9NxFPlWWpoNsbOnEqKx8RCjXN109zNx_QXhyg,6070
|
38
38
|
GameSentenceMiner/vad/whisper_helper.py,sha256=JJ-iltCh813XdjyEw0Wn5DaErf6PDqfH0Efu1Md8cIY,3543
|
39
|
-
gamesentenceminer-2.7.
|
40
|
-
gamesentenceminer-2.7.
|
41
|
-
gamesentenceminer-2.7.
|
42
|
-
gamesentenceminer-2.7.
|
43
|
-
gamesentenceminer-2.7.
|
44
|
-
gamesentenceminer-2.7.
|
39
|
+
gamesentenceminer-2.7.10.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
40
|
+
gamesentenceminer-2.7.10.dist-info/METADATA,sha256=mfPx9X0zpeSfjd_nLr3ZXx-twqQE-BjS7h6YFMIRMlw,5840
|
41
|
+
gamesentenceminer-2.7.10.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
|
42
|
+
gamesentenceminer-2.7.10.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
43
|
+
gamesentenceminer-2.7.10.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
44
|
+
gamesentenceminer-2.7.10.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|