GameSentenceMiner 2.2.3__py3-none-any.whl → 2.3.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/anki.py +16 -7
- GameSentenceMiner/config_gui.py +60 -14
- GameSentenceMiner/configuration.py +5 -0
- GameSentenceMiner/ffmpeg.py +2 -2
- GameSentenceMiner/gametext.py +89 -26
- GameSentenceMiner/gsm.py +32 -15
- GameSentenceMiner/package_updater.py +0 -8
- GameSentenceMiner/utility_gui.py +127 -0
- {GameSentenceMiner-2.2.3.dist-info → GameSentenceMiner-2.3.0.dist-info}/METADATA +2 -2
- {GameSentenceMiner-2.2.3.dist-info → GameSentenceMiner-2.3.0.dist-info}/RECORD +13 -12
- {GameSentenceMiner-2.2.3.dist-info → GameSentenceMiner-2.3.0.dist-info}/WHEEL +0 -0
- {GameSentenceMiner-2.2.3.dist-info → GameSentenceMiner-2.3.0.dist-info}/entry_points.txt +0 -0
- {GameSentenceMiner-2.2.3.dist-info → GameSentenceMiner-2.3.0.dist-info}/top_level.txt +0 -0
GameSentenceMiner/anki.py
CHANGED
@@ -6,7 +6,7 @@ import urllib.request
|
|
6
6
|
|
7
7
|
import requests as req
|
8
8
|
|
9
|
-
from GameSentenceMiner import obs, util, notification, ffmpeg
|
9
|
+
from GameSentenceMiner import obs, util, notification, ffmpeg, gametext
|
10
10
|
|
11
11
|
from GameSentenceMiner.configuration import *
|
12
12
|
from GameSentenceMiner.configuration import get_config
|
@@ -103,15 +103,25 @@ def add_image_to_card(last_note, image_path):
|
|
103
103
|
logger.info(f"UPDATED IMAGE FOR ANKI CARD {last_note['noteId']}")
|
104
104
|
|
105
105
|
|
106
|
-
def get_initial_card_info(last_note):
|
106
|
+
def get_initial_card_info(last_note, selected_lines):
|
107
107
|
note = {'id': last_note['noteId'], 'fields': {}}
|
108
108
|
if not last_note:
|
109
109
|
return note
|
110
|
-
current_line, previous_line = get_last_two_sentences()
|
110
|
+
current_line, previous_line = get_last_two_sentences(last_note)
|
111
111
|
logger.debug(f"Previous Sentence {previous_line}")
|
112
112
|
logger.debug(f"Current Sentence {current_line}")
|
113
113
|
util.use_previous_audio = True
|
114
114
|
|
115
|
+
if get_config().audio.mining_from_history_grab_all_audio and get_config().anki.multi_overwrites_sentence:
|
116
|
+
lines = gametext.get_line_and_future_lines(last_note)
|
117
|
+
logger.info(lines)
|
118
|
+
logger.info("".join(lines))
|
119
|
+
if lines:
|
120
|
+
note['fields'][get_config().anki.sentence_field] = "".join(lines)
|
121
|
+
|
122
|
+
if selected_lines and get_config().anki.multi_overwrites_sentence:
|
123
|
+
note['fields'][get_config().anki.sentence_field] = "".join(selected_lines)
|
124
|
+
|
115
125
|
logger.debug(
|
116
126
|
f"Adding Previous Sentence: {get_config().anki.previous_sentence_field and previous_line and not last_note['fields'][get_config().anki.previous_sentence_field]['value']}")
|
117
127
|
if get_config().anki.previous_sentence_field and previous_line and not \
|
@@ -200,7 +210,6 @@ def check_for_new_cards():
|
|
200
210
|
def update_new_card():
|
201
211
|
last_card = get_last_anki_card()
|
202
212
|
if not check_tags_for_should_update(last_card):
|
203
|
-
logger.info("Card not tagged properly! Not updating!")
|
204
213
|
return
|
205
214
|
|
206
215
|
use_prev_audio = util.use_previous_audio
|
@@ -212,7 +221,7 @@ def update_new_card():
|
|
212
221
|
if get_config().obs.get_game_from_scene:
|
213
222
|
obs.update_current_game()
|
214
223
|
if use_prev_audio:
|
215
|
-
update_anki_card(last_card, note=get_initial_card_info(last_card), reuse_audio=True)
|
224
|
+
update_anki_card(last_card, note=get_initial_card_info(last_card, []), reuse_audio=True)
|
216
225
|
else:
|
217
226
|
logger.info("New card(s) detected!")
|
218
227
|
obs.save_replay_buffer()
|
@@ -222,11 +231,11 @@ def check_tags_for_should_update(last_card):
|
|
222
231
|
if get_config().anki.tags_to_check:
|
223
232
|
found = False
|
224
233
|
for tag in last_card['tags']:
|
225
|
-
logger.info(tag)
|
226
|
-
logger.info(get_config().anki.tags_to_check)
|
227
234
|
if tag.lower() in get_config().anki.tags_to_check:
|
228
235
|
found = True
|
229
236
|
break
|
237
|
+
if not found:
|
238
|
+
logger.info(f"Card not tagged properly! Not updating! Note Tags: {last_card['tags']}, Tags_To_Check {get_config().anki.tags_to_check}")
|
230
239
|
return found
|
231
240
|
else:
|
232
241
|
return True
|
GameSentenceMiner/config_gui.py
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
import tkinter as tk
|
2
2
|
from tkinter import filedialog, messagebox, simpledialog
|
3
3
|
|
4
|
+
import pyperclip
|
4
5
|
import ttkbootstrap as ttk
|
5
6
|
|
6
|
-
from GameSentenceMiner.package_updater import check_for_updates, get_latest_version,
|
7
|
+
from GameSentenceMiner.package_updater import check_for_updates, get_latest_version, get_current_version
|
7
8
|
from GameSentenceMiner import obs, configuration
|
8
9
|
from GameSentenceMiner.configuration import *
|
9
10
|
|
10
11
|
settings_saved = False
|
11
12
|
on_save = []
|
13
|
+
exit_func = None
|
12
14
|
|
13
15
|
|
14
16
|
def new_tab(func):
|
@@ -45,9 +47,11 @@ class HoverInfoWidget:
|
|
45
47
|
self.tooltip = None
|
46
48
|
|
47
49
|
|
50
|
+
|
48
51
|
class ConfigApp:
|
49
|
-
def __init__(self):
|
50
|
-
self.window =
|
52
|
+
def __init__(self, root):
|
53
|
+
self.window = root
|
54
|
+
# self.window = ttk.Window(themename='darkly')
|
51
55
|
self.window.title('GameSentenceMiner Configuration')
|
52
56
|
self.window.protocol("WM_DELETE_WINDOW", self.hide)
|
53
57
|
|
@@ -75,7 +79,12 @@ class ConfigApp:
|
|
75
79
|
|
76
80
|
self.window.withdraw()
|
77
81
|
|
82
|
+
def add_save_hook(self, func):
|
83
|
+
on_save.append(func)
|
78
84
|
|
85
|
+
def add_exit_hook(self, func):
|
86
|
+
global exit_func
|
87
|
+
exit_func = func
|
79
88
|
|
80
89
|
def show(self):
|
81
90
|
obs.update_current_game()
|
@@ -93,9 +102,8 @@ class ConfigApp:
|
|
93
102
|
update_available, version = check_for_updates()
|
94
103
|
if update_available:
|
95
104
|
messagebox.showinfo("Update", "GSM Will Copy the Update Command to your clipboard, please run it in a terminal.")
|
96
|
-
|
97
|
-
|
98
|
-
messagebox.showinfo("Update Unsuccessful", "Couldn't Start Update, please update manually.")
|
105
|
+
pyperclip.copy("pip install --upgrade GameSentenceMiner")
|
106
|
+
exit_func(None, None)
|
99
107
|
else:
|
100
108
|
messagebox.showinfo("No Update Found", "No update found.")
|
101
109
|
|
@@ -109,9 +117,6 @@ class ConfigApp:
|
|
109
117
|
elif show_no_update:
|
110
118
|
messagebox.showinfo("No Update Found", "No update found.")
|
111
119
|
|
112
|
-
def add_save_hook(self, func):
|
113
|
-
on_save.append(func)
|
114
|
-
|
115
120
|
def save_settings(self, profile_change=False):
|
116
121
|
global settings_saved
|
117
122
|
|
@@ -121,7 +126,8 @@ class ConfigApp:
|
|
121
126
|
use_websocket=self.websocket_enabled.get(),
|
122
127
|
websocket_uri=self.websocket_uri.get(),
|
123
128
|
open_config_on_startup=self.open_config_on_startup.get(),
|
124
|
-
check_for_update_on_startup=self.check_for_update_on_startup.get()
|
129
|
+
check_for_update_on_startup=self.check_for_update_on_startup.get(),
|
130
|
+
texthook_replacement_regex=self.texthook_replacement_regex.get()
|
125
131
|
),
|
126
132
|
paths=Paths(
|
127
133
|
folder_to_watch=self.folder_to_watch.get(),
|
@@ -139,12 +145,14 @@ class ConfigApp:
|
|
139
145
|
picture_field=self.picture_field.get(),
|
140
146
|
word_field=self.word_field.get(),
|
141
147
|
previous_sentence_field=self.previous_sentence_field.get(),
|
148
|
+
previous_image_field=self.previous_image_field.get(),
|
142
149
|
custom_tags=[tag.strip() for tag in self.custom_tags.get().split(',') if tag.strip()],
|
143
150
|
tags_to_check=[tag.strip().lower() for tag in self.tags_to_check.get().split(',') if tag.strip()],
|
144
151
|
add_game_tag=self.add_game_tag.get(),
|
145
152
|
polling_rate=int(self.polling_rate.get()),
|
146
153
|
overwrite_audio=self.overwrite_audio.get(),
|
147
154
|
overwrite_picture=self.overwrite_picture.get(),
|
155
|
+
multi_overwrites_sentence=self.multi_overwrites_sentence.get(),
|
148
156
|
anki_custom_fields={
|
149
157
|
key_entry.get(): value_entry.get() for key_entry, value_entry, delete_button in
|
150
158
|
self.custom_field_entries if key_entry.get()
|
@@ -171,7 +179,8 @@ class ConfigApp:
|
|
171
179
|
end_offset=float(self.end_offset.get()),
|
172
180
|
ffmpeg_reencode_options=self.ffmpeg_reencode_options.get(),
|
173
181
|
external_tool = self.external_tool.get(),
|
174
|
-
anki_media_collection=self.anki_media_collection.get()
|
182
|
+
anki_media_collection=self.anki_media_collection.get(),
|
183
|
+
mining_from_history_grab_all_audio=self.mining_from_history_grab_all_audio.get()
|
175
184
|
),
|
176
185
|
obs=OBS(
|
177
186
|
enabled=self.obs_enabled.get(),
|
@@ -184,7 +193,8 @@ class ConfigApp:
|
|
184
193
|
),
|
185
194
|
hotkeys=Hotkeys(
|
186
195
|
reset_line=self.reset_line_hotkey.get(),
|
187
|
-
take_screenshot=self.take_screenshot_hotkey.get()
|
196
|
+
take_screenshot=self.take_screenshot_hotkey.get(),
|
197
|
+
open_utility=self.open_utility_hotkey.get()
|
188
198
|
),
|
189
199
|
vad=VAD(
|
190
200
|
whisper_model=self.whisper_model.get(),
|
@@ -257,11 +267,11 @@ class ConfigApp:
|
|
257
267
|
general_frame = ttk.Frame(self.notebook)
|
258
268
|
self.notebook.add(general_frame, text='General')
|
259
269
|
|
260
|
-
ttk.Label(general_frame, text="Websocket Enabled:").grid(row=self.current_row, column=0, sticky='W')
|
270
|
+
ttk.Label(general_frame, text="Websocket Enabled (Clipboard Disabled):").grid(row=self.current_row, column=0, sticky='W')
|
261
271
|
self.websocket_enabled = tk.BooleanVar(value=self.settings.general.use_websocket)
|
262
272
|
ttk.Checkbutton(general_frame, variable=self.websocket_enabled).grid(row=self.current_row, column=1,
|
263
273
|
sticky='W')
|
264
|
-
self.add_label_and_increment_row(general_frame, "Enable or disable WebSocket communication.",
|
274
|
+
self.add_label_and_increment_row(general_frame, "Enable or disable WebSocket communication. Enabling this will disable the clipboard monitor. RESTART REQUIRED.",
|
265
275
|
row=self.current_row, column=2)
|
266
276
|
|
267
277
|
ttk.Label(general_frame, text="Websocket URI:").grid(row=self.current_row, column=0, sticky='W')
|
@@ -271,6 +281,13 @@ class ConfigApp:
|
|
271
281
|
self.add_label_and_increment_row(general_frame, "WebSocket URI for connecting.", row=self.current_row,
|
272
282
|
column=2)
|
273
283
|
|
284
|
+
ttk.Label(general_frame, text="TextHook Replacement Regex:").grid(row=self.current_row, column=0, sticky='W')
|
285
|
+
self.texthook_replacement_regex = ttk.Entry(general_frame)
|
286
|
+
self.texthook_replacement_regex.insert(0, self.settings.general.texthook_replacement_regex)
|
287
|
+
self.texthook_replacement_regex.grid(row=self.current_row, column=1)
|
288
|
+
self.add_label_and_increment_row(general_frame, "Regex to run replacement on texthook input, set this to the same as what you may have in your texthook page.", row=self.current_row,
|
289
|
+
column=2)
|
290
|
+
|
274
291
|
ttk.Label(general_frame, text="Open Config on Startup:").grid(row=self.current_row, column=0, sticky='W')
|
275
292
|
self.open_config_on_startup = tk.BooleanVar(value=self.settings.general.open_config_on_startup)
|
276
293
|
ttk.Checkbutton(general_frame, variable=self.open_config_on_startup).grid(row=self.current_row, column=1,
|
@@ -476,6 +493,15 @@ class ConfigApp:
|
|
476
493
|
row=self.current_row,
|
477
494
|
column=2)
|
478
495
|
|
496
|
+
ttk.Label(anki_frame, text="Previous Image Field:").grid(row=self.current_row, column=0, sticky='W')
|
497
|
+
self.previous_image_field = ttk.Entry(anki_frame)
|
498
|
+
self.previous_image_field.insert(0, self.settings.anki.previous_image_field)
|
499
|
+
self.previous_image_field.grid(row=self.current_row, column=1)
|
500
|
+
self.add_label_and_increment_row(anki_frame,
|
501
|
+
"Field in Anki for the image line of previous Image. If Empty, will not populate",
|
502
|
+
row=self.current_row,
|
503
|
+
column=2)
|
504
|
+
|
479
505
|
ttk.Label(anki_frame, text="Custom Tags:").grid(row=self.current_row, column=0, sticky='W')
|
480
506
|
self.custom_tags = ttk.Entry(anki_frame)
|
481
507
|
self.custom_tags.insert(0, ', '.join(self.settings.anki.custom_tags))
|
@@ -518,6 +544,13 @@ class ConfigApp:
|
|
518
544
|
self.add_label_and_increment_row(anki_frame, "Overwrite existing pictures in Anki cards.", row=self.current_row,
|
519
545
|
column=2)
|
520
546
|
|
547
|
+
ttk.Label(anki_frame, text="Multi-line Mining Overwrite Sentence:").grid(row=self.current_row, column=0, sticky='W')
|
548
|
+
self.multi_overwrites_sentence = tk.BooleanVar(
|
549
|
+
value=self.settings.anki.multi_overwrites_sentence)
|
550
|
+
ttk.Checkbutton(anki_frame, variable=self.multi_overwrites_sentence).grid(row=self.current_row, column=1, sticky='W')
|
551
|
+
self.add_label_and_increment_row(anki_frame, "When using Multi-line Mining, overrwrite the sentence with a concatenation of the lines selected.", row=self.current_row,
|
552
|
+
column=2)
|
553
|
+
|
521
554
|
self.anki_custom_fields = self.settings.anki.anki_custom_fields
|
522
555
|
self.custom_field_entries = []
|
523
556
|
|
@@ -715,6 +748,13 @@ class ConfigApp:
|
|
715
748
|
row=self.current_row,
|
716
749
|
column=2)
|
717
750
|
|
751
|
+
ttk.Label(audio_frame, text="Grab all Future Audio when Mining from History:").grid(row=self.current_row, column=0, sticky='W')
|
752
|
+
self.mining_from_history_grab_all_audio = tk.BooleanVar(
|
753
|
+
value=self.settings.audio.mining_from_history_grab_all_audio)
|
754
|
+
ttk.Checkbutton(audio_frame, variable=self.mining_from_history_grab_all_audio).grid(row=self.current_row, column=1, sticky='W')
|
755
|
+
self.add_label_and_increment_row(audio_frame, "When mining from History, this option will allow the script to get all audio from that line to the current time.", row=self.current_row,
|
756
|
+
column=2)
|
757
|
+
|
718
758
|
@new_tab
|
719
759
|
def create_obs_tab(self):
|
720
760
|
obs_frame = ttk.Frame(self.notebook)
|
@@ -786,6 +826,12 @@ class ConfigApp:
|
|
786
826
|
self.take_screenshot_hotkey.grid(row=self.current_row, column=1)
|
787
827
|
self.add_label_and_increment_row(hotkeys_frame, "Hotkey to take a screenshot.", row=self.current_row, column=2)
|
788
828
|
|
829
|
+
ttk.Label(hotkeys_frame, text="Open Utility Hotkey:").grid(row=self.current_row, column=0, sticky='W')
|
830
|
+
self.open_utility_hotkey = ttk.Entry(hotkeys_frame)
|
831
|
+
self.open_utility_hotkey.insert(0, self.settings.hotkeys.open_utility)
|
832
|
+
self.open_utility_hotkey.grid(row=self.current_row, column=1)
|
833
|
+
self.add_label_and_increment_row(hotkeys_frame, "Hotkey to open the text utility.", row=self.current_row, column=2)
|
834
|
+
|
789
835
|
|
790
836
|
@new_tab
|
791
837
|
def create_profiles_tab(self):
|
@@ -41,6 +41,7 @@ class General:
|
|
41
41
|
websocket_uri: str = 'localhost:6677'
|
42
42
|
open_config_on_startup: bool = False
|
43
43
|
check_for_update_on_startup: bool = False
|
44
|
+
texthook_replacement_regex: str = ""
|
44
45
|
|
45
46
|
|
46
47
|
@dataclass_json
|
@@ -64,12 +65,14 @@ class Anki:
|
|
64
65
|
picture_field: str = "Picture"
|
65
66
|
word_field: str = 'Word'
|
66
67
|
previous_sentence_field: str = ''
|
68
|
+
previous_image_field: str = ''
|
67
69
|
custom_tags: List[str] = None # Initialize to None and set it in __post_init__
|
68
70
|
tags_to_check: List[str] = None
|
69
71
|
add_game_tag: bool = True
|
70
72
|
polling_rate: int = 200
|
71
73
|
overwrite_audio: bool = False
|
72
74
|
overwrite_picture: bool = True
|
75
|
+
multi_overwrites_sentence: bool = True
|
73
76
|
anki_custom_fields: Dict[str, str] = None # Initialize to None and set it in __post_init__
|
74
77
|
|
75
78
|
def __post_init__(self):
|
@@ -111,6 +114,7 @@ class Audio:
|
|
111
114
|
ffmpeg_reencode_options: str = ''
|
112
115
|
external_tool: str = ""
|
113
116
|
anki_media_collection: str = ""
|
117
|
+
mining_from_history_grab_all_audio: bool = False
|
114
118
|
|
115
119
|
|
116
120
|
@dataclass_json
|
@@ -130,6 +134,7 @@ class OBS:
|
|
130
134
|
class Hotkeys:
|
131
135
|
reset_line: str = 'f5'
|
132
136
|
take_screenshot: str = 'f6'
|
137
|
+
open_utility: str = 'ctrl+m'
|
133
138
|
|
134
139
|
|
135
140
|
@dataclass_json
|
GameSentenceMiner/ffmpeg.py
CHANGED
@@ -185,14 +185,14 @@ def trim_audio_based_on_last_line(untrimmed_audio, video_path, line_time, next_l
|
|
185
185
|
ffmpeg_command = ffmpeg_base_command_list + [
|
186
186
|
"-i", untrimmed_audio,
|
187
187
|
"-ss", start_trim_time]
|
188
|
-
if next_line and next_line > line_time:
|
188
|
+
if next_line and next_line > line_time and not get_config().audio.mining_from_history_grab_all_audio:
|
189
189
|
end_total_seconds = total_seconds + (next_line - line_time).total_seconds() + 1
|
190
190
|
hours, remainder = divmod(end_total_seconds, 3600)
|
191
191
|
minutes, seconds = divmod(remainder, 60)
|
192
192
|
end_trim_time = "{:02}:{:02}:{:06.3f}".format(int(hours), int(minutes), seconds)
|
193
193
|
ffmpeg_command.extend(['-to', end_trim_time])
|
194
194
|
logger.info(
|
195
|
-
f"Looks
|
195
|
+
f"Looks Like this is mining from History, or Multiple Lines were selected Trimming end of audio to {end_trim_time}")
|
196
196
|
|
197
197
|
ffmpeg_command.extend([
|
198
198
|
"-c", "copy", # Using copy to avoid re-encoding, adjust if needed
|
GameSentenceMiner/gametext.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
import asyncio
|
2
|
+
import re
|
2
3
|
import threading
|
3
4
|
import time
|
4
5
|
from collections import OrderedDict
|
5
6
|
from datetime import datetime
|
7
|
+
from typing import Callable
|
6
8
|
|
7
9
|
import pyperclip
|
8
10
|
import websockets
|
@@ -14,11 +16,13 @@ from GameSentenceMiner.util import remove_html_tags
|
|
14
16
|
from difflib import SequenceMatcher
|
15
17
|
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
+
current_line = ''
|
20
|
+
current_line_after_regex = ''
|
21
|
+
current_line_time = datetime.now()
|
19
22
|
|
20
23
|
line_history = OrderedDict()
|
21
24
|
reconnecting = False
|
25
|
+
multi_mine_event_bus: Callable[[str, datetime], None] = None
|
22
26
|
|
23
27
|
|
24
28
|
class ClipboardMonitor(threading.Thread):
|
@@ -28,25 +32,22 @@ class ClipboardMonitor(threading.Thread):
|
|
28
32
|
self.daemon = True
|
29
33
|
|
30
34
|
def run(self):
|
31
|
-
global
|
35
|
+
global current_line_time, current_line, line_history
|
32
36
|
|
33
37
|
# Initial clipboard content
|
34
|
-
|
38
|
+
current_line = pyperclip.paste()
|
35
39
|
|
36
40
|
while True:
|
37
41
|
current_clipboard = pyperclip.paste()
|
38
42
|
|
39
|
-
if current_clipboard !=
|
40
|
-
|
41
|
-
previous_line_time = datetime.now()
|
42
|
-
line_history[previous_line] = previous_line_time
|
43
|
-
util.use_previous_audio = False
|
43
|
+
if current_clipboard != current_line:
|
44
|
+
handle_new_text_event(current_clipboard)
|
44
45
|
|
45
46
|
time.sleep(0.05)
|
46
47
|
|
47
48
|
|
48
49
|
async def listen_websocket():
|
49
|
-
global
|
50
|
+
global current_line, current_line_time, line_history, reconnecting
|
50
51
|
while True:
|
51
52
|
try:
|
52
53
|
async with websockets.connect(f'ws://{get_config().general.websocket_uri}', ping_interval=None) as websocket:
|
@@ -62,25 +63,33 @@ async def listen_websocket():
|
|
62
63
|
current_clipboard = data["sentence"]
|
63
64
|
except json.JSONDecodeError:
|
64
65
|
current_clipboard = message
|
65
|
-
|
66
|
-
|
67
|
-
previous_line = current_clipboard
|
68
|
-
previous_line_time = datetime.now()
|
69
|
-
line_history[previous_line] = previous_line_time
|
70
|
-
util.use_previous_audio = False
|
71
|
-
|
66
|
+
if current_clipboard != current_line:
|
67
|
+
handle_new_text_event(current_clipboard)
|
72
68
|
except (websockets.ConnectionClosed, ConnectionError) as e:
|
73
69
|
if not reconnecting:
|
74
70
|
logger.warning(f"Texthooker WebSocket connection lost: {e}. Attempting to Reconnect...")
|
75
71
|
reconnecting = True
|
76
72
|
await asyncio.sleep(5)
|
77
73
|
|
74
|
+
def handle_new_text_event(current_clipboard):
|
75
|
+
global current_line, current_line_time, line_history, current_line_after_regex
|
76
|
+
current_line = current_clipboard
|
77
|
+
if get_config().general.texthook_replacement_regex:
|
78
|
+
current_line_after_regex = re.sub(get_config().general.texthook_replacement_regex, '', current_line)
|
79
|
+
else:
|
80
|
+
current_line_after_regex = current_line
|
81
|
+
current_line_time = datetime.now()
|
82
|
+
line_history[current_line_after_regex] = current_line_time
|
83
|
+
util.use_previous_audio = False
|
84
|
+
multi_mine_event_bus(current_line_after_regex, current_line_time)
|
85
|
+
logger.debug(f"New Line: {current_clipboard}")
|
86
|
+
|
78
87
|
|
79
88
|
def reset_line_hotkey_pressed():
|
80
|
-
global
|
89
|
+
global current_line_time
|
81
90
|
logger.info("LINE RESET HOTKEY PRESSED")
|
82
|
-
|
83
|
-
line_history[
|
91
|
+
current_line_time = datetime.now()
|
92
|
+
line_history[current_line_after_regex] = current_line_time
|
84
93
|
util.use_previous_audio = False
|
85
94
|
|
86
95
|
|
@@ -88,7 +97,9 @@ def run_websocket_listener():
|
|
88
97
|
asyncio.run(listen_websocket())
|
89
98
|
|
90
99
|
|
91
|
-
def start_text_monitor():
|
100
|
+
def start_text_monitor(send_to_mine_event_bus):
|
101
|
+
global multi_mine_event_bus
|
102
|
+
multi_mine_event_bus = send_to_mine_event_bus
|
92
103
|
if get_config().general.use_websocket:
|
93
104
|
text_thread = threading.Thread(target=run_websocket_listener, daemon=True)
|
94
105
|
else:
|
@@ -101,9 +112,9 @@ def get_line_timing(last_note):
|
|
101
112
|
return SequenceMatcher(None, a, b).ratio()
|
102
113
|
|
103
114
|
if not last_note:
|
104
|
-
return
|
115
|
+
return current_line_time, 0
|
105
116
|
|
106
|
-
line_time =
|
117
|
+
line_time = current_line_time
|
107
118
|
next_line = 0
|
108
119
|
prev_clip_time = 0
|
109
120
|
|
@@ -112,7 +123,7 @@ def get_line_timing(last_note):
|
|
112
123
|
if sentence:
|
113
124
|
for i, (line, clip_time) in enumerate(reversed(line_history.items())):
|
114
125
|
similarity = similar(remove_html_tags(sentence), line)
|
115
|
-
if similarity >= 0.60: # 80% similarity threshold
|
126
|
+
if similarity >= 0.60 or line in remove_html_tags(sentence): # 80% similarity threshold
|
116
127
|
line_time = clip_time
|
117
128
|
next_line = prev_clip_time
|
118
129
|
break
|
@@ -123,6 +134,58 @@ def get_line_timing(last_note):
|
|
123
134
|
return line_time, next_line
|
124
135
|
|
125
136
|
|
126
|
-
def get_last_two_sentences():
|
137
|
+
def get_last_two_sentences(last_note):
|
138
|
+
def similar(a, b):
|
139
|
+
return SequenceMatcher(None, a, b).ratio()
|
127
140
|
lines = list(line_history.items())
|
128
|
-
|
141
|
+
|
142
|
+
if not last_note:
|
143
|
+
return lines[-1][0] if lines else '', lines[-2][0] if len(lines) > 1 else ''
|
144
|
+
|
145
|
+
current_line = ""
|
146
|
+
prev_line = ""
|
147
|
+
|
148
|
+
sentence = last_note['fields'][get_config().anki.sentence_field]['value']
|
149
|
+
if sentence:
|
150
|
+
found = False
|
151
|
+
for i, (line, clip_time) in enumerate(reversed(lines)):
|
152
|
+
similarity = similar(remove_html_tags(sentence), line)
|
153
|
+
logger.debug(f"Comparing: {remove_html_tags(sentence)} with {line} - Similarity: {similarity}")
|
154
|
+
if found:
|
155
|
+
prev_line = line
|
156
|
+
break
|
157
|
+
if similarity >= 0.60 or line in remove_html_tags(sentence): # 80% similarity threshold
|
158
|
+
found = True
|
159
|
+
current_line = line
|
160
|
+
|
161
|
+
logger.debug(f"Current Line: {current_line}")
|
162
|
+
logger.debug(f"Previous Line: {prev_line}")
|
163
|
+
|
164
|
+
if not current_line or not prev_line:
|
165
|
+
logger.debug("Couldn't find lines in history, using last two lines")
|
166
|
+
return lines[-1][0] if lines else '', lines[-2][0] if len(lines) > 1 else ''
|
167
|
+
|
168
|
+
return current_line, prev_line
|
169
|
+
|
170
|
+
|
171
|
+
def get_line_and_future_lines(last_note):
|
172
|
+
def similar(a, b):
|
173
|
+
return SequenceMatcher(None, a, b).ratio()
|
174
|
+
lines = list(line_history.items())
|
175
|
+
|
176
|
+
if not last_note:
|
177
|
+
return []
|
178
|
+
|
179
|
+
sentence = last_note['fields'][get_config().anki.sentence_field]['value']
|
180
|
+
found_lines = []
|
181
|
+
if sentence:
|
182
|
+
found = False
|
183
|
+
for i, (line, clip_time) in enumerate(lines):
|
184
|
+
similarity = similar(remove_html_tags(sentence), line)
|
185
|
+
logger.debug(f"Comparing: {remove_html_tags(sentence)} with {line} - Similarity: {similarity}")
|
186
|
+
if found:
|
187
|
+
found_lines.append(line)
|
188
|
+
if similarity >= 0.60 or line in remove_html_tags(sentence): # 80% similarity threshold
|
189
|
+
found = True
|
190
|
+
found_lines.append(line)
|
191
|
+
return found_lines
|
GameSentenceMiner/gsm.py
CHANGED
@@ -2,6 +2,7 @@ import signal
|
|
2
2
|
import subprocess
|
3
3
|
import sys
|
4
4
|
import time
|
5
|
+
import ttkbootstrap as ttk
|
5
6
|
from subprocess import Popen
|
6
7
|
|
7
8
|
import keyboard
|
@@ -11,6 +12,7 @@ from pystray import Icon, Menu, MenuItem
|
|
11
12
|
from watchdog.events import FileSystemEventHandler
|
12
13
|
from watchdog.observers import Observer
|
13
14
|
|
15
|
+
from GameSentenceMiner import utility_gui
|
14
16
|
from GameSentenceMiner import anki
|
15
17
|
from GameSentenceMiner import config_gui
|
16
18
|
from GameSentenceMiner import configuration
|
@@ -29,12 +31,14 @@ from GameSentenceMiner.util import *
|
|
29
31
|
if is_windows():
|
30
32
|
import win32api
|
31
33
|
|
32
|
-
obs_process: Popen
|
34
|
+
obs_process: Popen = None
|
33
35
|
procs_to_close = []
|
34
36
|
settings_window: config_gui.ConfigApp = None
|
37
|
+
utility_window: utility_gui.UtilityApp = None
|
35
38
|
obs_paused = False
|
36
39
|
icon: Icon
|
37
40
|
menu: Menu
|
41
|
+
root = None
|
38
42
|
|
39
43
|
|
40
44
|
class VideoToAudioHandler(FileSystemEventHandler):
|
@@ -66,15 +70,17 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
66
70
|
if get_config().anki.update_anki:
|
67
71
|
last_note = anki.get_last_anki_card()
|
68
72
|
if get_config().features.backfill_audio:
|
69
|
-
last_note = anki.get_cards_by_sentence(gametext.
|
73
|
+
last_note = anki.get_cards_by_sentence(gametext.current_line)
|
70
74
|
line_time, next_line_time = get_line_timing(last_note)
|
75
|
+
if utility_window.lines_selected():
|
76
|
+
line_time, next_line_time = utility_window.get_selected_times()
|
71
77
|
ss_timing = 0
|
72
78
|
if line_time and next_line_time:
|
73
79
|
ss_timing = ffmpeg.get_screenshot_time(video_path, line_time)
|
74
80
|
if last_note:
|
75
81
|
logger.debug(json.dumps(last_note))
|
76
82
|
|
77
|
-
note = anki.get_initial_card_info(last_note)
|
83
|
+
note = anki.get_initial_card_info(last_note, utility_window.get_selected_lines())
|
78
84
|
|
79
85
|
tango = last_note['fields'][get_config().anki.word_field]['value'] if last_note else ''
|
80
86
|
|
@@ -106,6 +112,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
106
112
|
os.remove(video_path) # Optionally remove the video after conversion
|
107
113
|
if get_config().paths.remove_audio and os.path.exists(vad_trimmed_audio):
|
108
114
|
os.remove(vad_trimmed_audio) # Optionally remove the screenshot after conversion
|
115
|
+
utility_window.reset_checkboxes()
|
109
116
|
|
110
117
|
@staticmethod
|
111
118
|
def get_audio(line_time, next_line_time, video_path):
|
@@ -153,7 +160,7 @@ def initialize(reloading=False):
|
|
153
160
|
obs_process = obs.start_obs()
|
154
161
|
obs.connect_to_obs(start_replay=True)
|
155
162
|
anki.start_monitoring_anki()
|
156
|
-
gametext.start_text_monitor()
|
163
|
+
gametext.start_text_monitor(utility_window.add_text)
|
157
164
|
os.makedirs(get_config().paths.folder_to_watch, exist_ok=True)
|
158
165
|
os.makedirs(get_config().paths.screenshot_destination, exist_ok=True)
|
159
166
|
os.makedirs(get_config().paths.audio_destination, exist_ok=True)
|
@@ -175,6 +182,7 @@ def initial_checks():
|
|
175
182
|
def register_hotkeys():
|
176
183
|
keyboard.add_hotkey(get_config().hotkeys.reset_line, gametext.reset_line_hotkey_pressed)
|
177
184
|
keyboard.add_hotkey(get_config().hotkeys.take_screenshot, get_screenshot)
|
185
|
+
keyboard.add_hotkey(get_config().hotkeys.open_utility, open_multimine)
|
178
186
|
|
179
187
|
|
180
188
|
def get_screenshot():
|
@@ -189,7 +197,7 @@ def get_screenshot():
|
|
189
197
|
if last_note:
|
190
198
|
logger.debug(json.dumps(last_note))
|
191
199
|
if get_config().features.backfill_audio:
|
192
|
-
last_note = anki.get_cards_by_sentence(gametext.
|
200
|
+
last_note = anki.get_cards_by_sentence(gametext.current_line)
|
193
201
|
if last_note:
|
194
202
|
anki.add_image_to_card(last_note, encoded_image)
|
195
203
|
notification.send_screenshot_updated(last_note['fields'][get_config().anki.word_field]['value'])
|
@@ -219,11 +227,14 @@ def create_image():
|
|
219
227
|
|
220
228
|
return image
|
221
229
|
|
222
|
-
|
223
230
|
def open_settings():
|
224
231
|
obs.update_current_game()
|
225
232
|
settings_window.show()
|
226
233
|
|
234
|
+
def open_multimine():
|
235
|
+
obs.update_current_game()
|
236
|
+
utility_window.show()
|
237
|
+
|
227
238
|
|
228
239
|
def open_log():
|
229
240
|
"""Function to handle opening log."""
|
@@ -267,6 +278,7 @@ def update_icon():
|
|
267
278
|
|
268
279
|
menu = Menu(
|
269
280
|
MenuItem("Open Settings", open_settings),
|
281
|
+
MenuItem("Open Multi-Mine GUI", open_multimine),
|
270
282
|
MenuItem("Open Log", open_log),
|
271
283
|
MenuItem("Toggle Replay Buffer", play_pause),
|
272
284
|
MenuItem("Restart OBS", restart_obs),
|
@@ -299,6 +311,7 @@ def run_tray():
|
|
299
311
|
|
300
312
|
menu = Menu(
|
301
313
|
MenuItem("Open Settings", open_settings),
|
314
|
+
MenuItem("Open Multi-Mine GUI", open_multimine),
|
302
315
|
MenuItem("Open Log", open_log),
|
303
316
|
MenuItem("Toggle Replay Buffer", play_pause),
|
304
317
|
MenuItem("Restart OBS", restart_obs),
|
@@ -317,10 +330,11 @@ def close_obs():
|
|
317
330
|
|
318
331
|
def restart_obs():
|
319
332
|
global obs_process
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
333
|
+
if obs_process:
|
334
|
+
close_obs()
|
335
|
+
time.sleep(2)
|
336
|
+
obs_process = obs.start_obs()
|
337
|
+
obs.connect_to_obs(start_replay=True)
|
324
338
|
|
325
339
|
def cleanup():
|
326
340
|
logger.info("Performing cleanup...")
|
@@ -362,8 +376,11 @@ def handle_exit():
|
|
362
376
|
|
363
377
|
|
364
378
|
def main(reloading=False, do_config_input=True):
|
365
|
-
global settings_window
|
379
|
+
global root, settings_window, utility_window
|
366
380
|
logger.info("Script started.")
|
381
|
+
root = ttk.Window(themename='darkly')
|
382
|
+
settings_window = config_gui.ConfigApp(root)
|
383
|
+
utility_window = utility_gui.UtilityApp(root)
|
367
384
|
initialize(reloading)
|
368
385
|
initial_checks()
|
369
386
|
event_handler = VideoToAudioHandler()
|
@@ -384,13 +401,13 @@ def main(reloading=False, do_config_input=True):
|
|
384
401
|
util.run_new_thread(run_tray)
|
385
402
|
|
386
403
|
try:
|
387
|
-
settings_window = config_gui.ConfigApp()
|
388
404
|
if get_config().general.check_for_update_on_startup:
|
389
|
-
|
405
|
+
root.after(0, settings_window.check_update)
|
390
406
|
if get_config().general.open_config_on_startup:
|
391
|
-
|
407
|
+
root.after(0, settings_window.show)
|
392
408
|
settings_window.add_save_hook(update_icon)
|
393
|
-
settings_window.
|
409
|
+
settings_window.on_exit = exit_program
|
410
|
+
root.mainloop()
|
394
411
|
except KeyboardInterrupt:
|
395
412
|
cleanup()
|
396
413
|
|
@@ -39,11 +39,3 @@ def check_for_updates(force=False):
|
|
39
39
|
return False, latest_version
|
40
40
|
except Exception as e:
|
41
41
|
logger.error(f"Error checking for updates: {e}")
|
42
|
-
|
43
|
-
def update():
|
44
|
-
try:
|
45
|
-
pyperclip.copy("pip install --upgrade GameSentenceMiner")
|
46
|
-
exit()
|
47
|
-
except Exception as e:
|
48
|
-
logger.error(f"Error updating {PACKAGE_NAME}: {e}")
|
49
|
-
return False
|
@@ -0,0 +1,127 @@
|
|
1
|
+
import tkinter as tk
|
2
|
+
from tkinter import ttk
|
3
|
+
|
4
|
+
from GameSentenceMiner.configuration import logger
|
5
|
+
|
6
|
+
|
7
|
+
class UtilityApp:
|
8
|
+
def __init__(self, root):
|
9
|
+
self.root = root
|
10
|
+
|
11
|
+
self.items = []
|
12
|
+
self.checkboxes = []
|
13
|
+
self.multi_mine_window = None # Store the multi-mine window reference
|
14
|
+
self.checkbox_frame = None
|
15
|
+
|
16
|
+
style = ttk.Style()
|
17
|
+
style.configure("TCheckbutton", font=("Arial", 20)) # Change the font and size
|
18
|
+
|
19
|
+
# def show(self):
|
20
|
+
# if self.multi_mine_window is None or not tk.Toplevel.winfo_exists(self.multi_mine_window):
|
21
|
+
# self.multi_mine_window = tk.Toplevel(self.root)
|
22
|
+
# self.multi_mine_window.title("Multi-Mine Window")
|
23
|
+
# self.update_multi_mine_window()
|
24
|
+
#
|
25
|
+
def show(self):
|
26
|
+
""" Open the multi-mine window only if it doesn't exist. """
|
27
|
+
if not self.multi_mine_window or not tk.Toplevel.winfo_exists(self.multi_mine_window):
|
28
|
+
logger.info("opening multi-mine_window")
|
29
|
+
self.multi_mine_window = tk.Toplevel(self.root)
|
30
|
+
self.multi_mine_window.title("Multi Mine Window")
|
31
|
+
|
32
|
+
self.multi_mine_window.minsize(800, 400) # Set a minimum size to prevent shrinking too
|
33
|
+
|
34
|
+
self.checkbox_frame = ttk.Frame(self.multi_mine_window)
|
35
|
+
self.checkbox_frame.pack(padx=10, pady=10, fill="both", expand=True)
|
36
|
+
|
37
|
+
# Add existing items
|
38
|
+
for text, var, time in self.items:
|
39
|
+
self.add_checkbox_to_gui(text, var, time)
|
40
|
+
else:
|
41
|
+
self.multi_mine_window.deiconify()
|
42
|
+
self.multi_mine_window.lift()
|
43
|
+
|
44
|
+
def add_text(self, text, time):
|
45
|
+
if text:
|
46
|
+
var = tk.BooleanVar()
|
47
|
+
self.items.append((text, var, time))
|
48
|
+
|
49
|
+
# Remove the first checkbox if there are more than 10
|
50
|
+
if len(self.items) > 10:
|
51
|
+
self.checkboxes[0].destroy()
|
52
|
+
self.checkboxes.pop(0)
|
53
|
+
self.items.pop(0)
|
54
|
+
|
55
|
+
if self.multi_mine_window and tk.Toplevel.winfo_exists(self.multi_mine_window):
|
56
|
+
self.add_checkbox_to_gui(text, var, time)
|
57
|
+
|
58
|
+
def add_checkbox_to_gui(self, text, var, time):
|
59
|
+
""" Add a single checkbox without repainting everything. """
|
60
|
+
if self.checkbox_frame:
|
61
|
+
chk = ttk.Checkbutton(self.checkbox_frame, text=f"{time.strftime('%H:%M:%S')} - {text}", variable=var)
|
62
|
+
chk.pack(anchor='w')
|
63
|
+
self.checkboxes.append(chk)
|
64
|
+
|
65
|
+
|
66
|
+
# def update_multi_mine_window(self):
|
67
|
+
# for widget in self.multi_mine_window.winfo_children():
|
68
|
+
# widget.destroy()
|
69
|
+
#
|
70
|
+
# for i, (text, var, time) in enumerate(self.items):
|
71
|
+
# time: datetime
|
72
|
+
# chk = ttk.Checkbutton(self.checkbox_frame, text=f"{time.strftime('%H:%M:%S')} - {text}", variable=var)
|
73
|
+
# chk.pack(anchor='w')
|
74
|
+
|
75
|
+
def get_selected_lines(self):
|
76
|
+
filtered_items = [text for text, var, _ in self.items if var.get()]
|
77
|
+
return filtered_items if len(filtered_items) >= 2 else []
|
78
|
+
|
79
|
+
def get_selected_times(self):
|
80
|
+
filtered_times = [time for _, var, time in self.items if var.get()]
|
81
|
+
|
82
|
+
if len(filtered_times) >= 2:
|
83
|
+
logger.info(filtered_times)
|
84
|
+
# Find the index of the last checked checkbox
|
85
|
+
last_checked_index = max(i for i, (_, var, _) in enumerate(self.items) if var.get())
|
86
|
+
|
87
|
+
# Get the time AFTER the last checked checkbox, if it exists
|
88
|
+
if last_checked_index + 1 < len(self.items):
|
89
|
+
next_time = self.items[last_checked_index + 1][2]
|
90
|
+
else:
|
91
|
+
next_time = 0
|
92
|
+
|
93
|
+
return filtered_times[0], next_time
|
94
|
+
|
95
|
+
return None
|
96
|
+
|
97
|
+
def lines_selected(self):
|
98
|
+
filter_times = [time for _, var, time in self.items if var.get()]
|
99
|
+
if len(filter_times) >= 2:
|
100
|
+
return True
|
101
|
+
return False
|
102
|
+
|
103
|
+
# def validate_checkboxes(self, *args):
|
104
|
+
# logger.debug("Validating checkboxes")
|
105
|
+
# found_checked = False
|
106
|
+
# found_unchecked = False
|
107
|
+
# for _, var in self.items:
|
108
|
+
# if var.get():
|
109
|
+
# if found_unchecked:
|
110
|
+
# messagebox.showinfo("Invalid", "Can only select neighboring checkboxes.")
|
111
|
+
# break
|
112
|
+
# found_checked = True
|
113
|
+
# if found_checked and not var.get():
|
114
|
+
# found_unchecked = True
|
115
|
+
|
116
|
+
def reset_checkboxes(self):
|
117
|
+
for _, var, _ in self.items:
|
118
|
+
var.set(False)
|
119
|
+
# if self.multi_mine_window:
|
120
|
+
# for checkbox in self.checkboxes:
|
121
|
+
# checkbox.set(False)
|
122
|
+
|
123
|
+
|
124
|
+
if __name__ == "__main__":
|
125
|
+
root = tk.Tk()
|
126
|
+
app = UtilityApp(root)
|
127
|
+
root.mainloop()
|
@@ -1,7 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: GameSentenceMiner
|
3
|
-
Version: 2.
|
4
|
-
Summary: A tool for mining sentences from games. Update:
|
3
|
+
Version: 2.3.0
|
4
|
+
Summary: A tool for mining sentences from games. Update: Multi-Line Mining!
|
5
5
|
Author-email: Beangate <bpwhelan95@gmail.com>
|
6
6
|
License: MIT License
|
7
7
|
Project-URL: Homepage, https://github.com/bpwhelan/GameSentenceMiner
|
@@ -1,15 +1,16 @@
|
|
1
1
|
GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
GameSentenceMiner/anki.py,sha256=
|
3
|
-
GameSentenceMiner/config_gui.py,sha256=
|
4
|
-
GameSentenceMiner/configuration.py,sha256=
|
5
|
-
GameSentenceMiner/ffmpeg.py,sha256=
|
6
|
-
GameSentenceMiner/gametext.py,sha256=
|
7
|
-
GameSentenceMiner/gsm.py,sha256=
|
2
|
+
GameSentenceMiner/anki.py,sha256=895FTnqZCn2AmfUqxWKsSOkzumFoPRNhwPRf00mB7OI,9755
|
3
|
+
GameSentenceMiner/config_gui.py,sha256=IYM_qZV22Hn_eGbXIqrQx2r7UGlTFDLBa0bqu3HqFl0,52491
|
4
|
+
GameSentenceMiner/configuration.py,sha256=X61ks7iuzf0Wu7U4QuhggUGB9-1KLka-V4cduoEOddA,14294
|
5
|
+
GameSentenceMiner/ffmpeg.py,sha256=VExJYWSFhYuWukIXgOiHufsoSROEDA8LnVQFG8srRGc,10924
|
6
|
+
GameSentenceMiner/gametext.py,sha256=ci1ZTwObi-5pxFWHcXp1cDWMrCz_zaPtUsWvTO_jwRU,6570
|
7
|
+
GameSentenceMiner/gsm.py,sha256=GY9T3h2dwY-XofXqESiyZpezRvlfaZrhzo0PDuuE87g,17065
|
8
8
|
GameSentenceMiner/model.py,sha256=oh8VVT8T1UKekbmP6MGNgQ8jIuQ_7Rg4GPzDCn2kJo8,1999
|
9
9
|
GameSentenceMiner/notification.py,sha256=WBaQWoPNhW4XqdPBUmxPBgjk0ngzH_4v9zMQ-XQAKC8,2010
|
10
10
|
GameSentenceMiner/obs.py,sha256=3Flcjxy812VpF78EPI7sxlGx6yyM3GfqzlinW17SK20,6231
|
11
|
-
GameSentenceMiner/package_updater.py,sha256=
|
11
|
+
GameSentenceMiner/package_updater.py,sha256=0uaLAp0WrWqostNTBWRS0laITjI9aN9Yt_6GXosS4NQ,1278
|
12
12
|
GameSentenceMiner/util.py,sha256=cgKpPfRpouWI6tjE_35MWp8nXqRzXs3LvsYXWm5_DOg,4584
|
13
|
+
GameSentenceMiner/utility_gui.py,sha256=-T5b14Nx6KvNnBEmdVz0mWkXoYi-bZzHIce6ADwfVEQ,4701
|
13
14
|
GameSentenceMiner/downloader/Untitled_json.py,sha256=RUUl2bbbCpUDUUS0fP0tdvf5FngZ7ILdA_J5TFYAXUQ,15272
|
14
15
|
GameSentenceMiner/downloader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
16
|
GameSentenceMiner/downloader/download_tools.py,sha256=M7vLo6_0QMuk1Ji4CsZqk1C2g7Bq6PyM29r7XNoP6Rw,6406
|
@@ -17,8 +18,8 @@ GameSentenceMiner/vad/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
|
|
17
18
|
GameSentenceMiner/vad/silero_trim.py,sha256=syDJX_KbFmdyFFtnQqYTD0tICsUCJizYhs-atPgXtxA,1549
|
18
19
|
GameSentenceMiner/vad/vosk_helper.py,sha256=-AAwK0cgOC5rK3_gL0sQgrPJ75E49g_PxZR4d5ckwc4,5826
|
19
20
|
GameSentenceMiner/vad/whisper_helper.py,sha256=bpR1HVnJRn9H5u8XaHBqBJ6JwIjzqn-Fajps8QmQ4zc,3411
|
20
|
-
GameSentenceMiner-2.
|
21
|
-
GameSentenceMiner-2.
|
22
|
-
GameSentenceMiner-2.
|
23
|
-
GameSentenceMiner-2.
|
24
|
-
GameSentenceMiner-2.
|
21
|
+
GameSentenceMiner-2.3.0.dist-info/METADATA,sha256=isZtUWBqPSC2Q-MG3HbL9aUT4uAbjHIiXhJFSYSR4mg,10113
|
22
|
+
GameSentenceMiner-2.3.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
23
|
+
GameSentenceMiner-2.3.0.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
24
|
+
GameSentenceMiner-2.3.0.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
25
|
+
GameSentenceMiner-2.3.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|