GameSentenceMiner 2.5.11__py3-none-any.whl → 2.5.13__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 +15 -12
- GameSentenceMiner/config_gui.py +17 -8
- GameSentenceMiner/configuration.py +3 -1
- GameSentenceMiner/gsm.py +59 -68
- GameSentenceMiner/obs.py +5 -21
- GameSentenceMiner/util.py +29 -35
- {gamesentenceminer-2.5.11.dist-info → gamesentenceminer-2.5.13.dist-info}/METADATA +2 -2
- {gamesentenceminer-2.5.11.dist-info → gamesentenceminer-2.5.13.dist-info}/RECORD +12 -12
- {gamesentenceminer-2.5.11.dist-info → gamesentenceminer-2.5.13.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.5.11.dist-info → gamesentenceminer-2.5.13.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.5.11.dist-info → gamesentenceminer-2.5.13.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.5.11.dist-info → gamesentenceminer-2.5.13.dist-info}/top_level.txt +0 -0
GameSentenceMiner/anki.py
CHANGED
@@ -1,22 +1,22 @@
|
|
1
|
+
import time
|
2
|
+
|
1
3
|
import base64
|
2
4
|
import subprocess
|
3
5
|
import threading
|
4
|
-
import time
|
5
6
|
import urllib.request
|
6
7
|
from datetime import datetime, timedelta
|
8
|
+
from requests import post
|
7
9
|
|
8
|
-
import
|
9
|
-
|
10
|
-
from GameSentenceMiner import obs, util, notification, ffmpeg, gametext
|
11
|
-
|
10
|
+
from GameSentenceMiner import obs, util, notification, ffmpeg
|
12
11
|
from GameSentenceMiner.configuration import *
|
13
12
|
from GameSentenceMiner.configuration import get_config
|
14
13
|
from GameSentenceMiner.gametext import get_text_event
|
15
14
|
from GameSentenceMiner.model import AnkiCard
|
16
|
-
from GameSentenceMiner.utility_gui import
|
15
|
+
from GameSentenceMiner.utility_gui import get_utility_window
|
17
16
|
from GameSentenceMiner.obs import get_current_game
|
18
17
|
from GameSentenceMiner.util import remove_html_and_cloze_tags, combine_dialogue
|
19
18
|
|
19
|
+
|
20
20
|
audio_in_anki = None
|
21
21
|
screenshot_in_anki = None
|
22
22
|
prev_screenshot_in_anki = None
|
@@ -32,8 +32,9 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
32
32
|
global audio_in_anki, screenshot_in_anki, prev_screenshot_in_anki
|
33
33
|
update_audio = should_update_audio and (get_config().anki.sentence_audio_field and not
|
34
34
|
last_note.get_field(get_config().anki.sentence_audio_field) or get_config().anki.overwrite_audio)
|
35
|
-
update_picture = (get_config().anki.picture_field and get_config().
|
36
|
-
|
35
|
+
update_picture = (get_config().anki.picture_field and get_config().screenshot.enabled
|
36
|
+
and (get_config().anki.overwrite_picture or not last_note.get_field(get_config().anki.picture_field)))
|
37
|
+
|
37
38
|
|
38
39
|
if not reuse_audio:
|
39
40
|
if update_audio:
|
@@ -54,11 +55,13 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
54
55
|
|
55
56
|
# note = {'id': last_note.noteId, 'fields': {}}
|
56
57
|
|
57
|
-
if update_audio:
|
58
|
+
if update_audio and audio_in_anki:
|
58
59
|
note['fields'][get_config().anki.sentence_audio_field] = audio_html
|
59
60
|
|
60
|
-
if update_picture:
|
61
|
+
if update_picture and screenshot_in_anki:
|
61
62
|
note['fields'][get_config().anki.picture_field] = image_html
|
63
|
+
if not get_config().screenshot.enabled:
|
64
|
+
logger.info("Skipping Adding Screenshot to Anki, Screenshot is disabled in settings")
|
62
65
|
|
63
66
|
if prev_screenshot_in_anki:
|
64
67
|
note['fields'][get_config().anki.previous_image_field] = prev_screenshot_html
|
@@ -82,7 +85,7 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
82
85
|
if get_config().features.open_anki_edit:
|
83
86
|
notification.open_anki_card(last_note.noteId)
|
84
87
|
|
85
|
-
if get_config().audio.external_tool:
|
88
|
+
if get_config().audio.external_tool and get_config().audio.external_tool_enabled and update_audio:
|
86
89
|
open_audio_in_external(f"{get_config().audio.anki_media_collection}/{audio_in_anki}")
|
87
90
|
|
88
91
|
|
@@ -309,7 +312,7 @@ def monitor_anki():
|
|
309
312
|
|
310
313
|
# Fetch recent note IDs from Anki
|
311
314
|
def get_note_ids():
|
312
|
-
response =
|
315
|
+
response = post(get_config().anki.url, json={
|
313
316
|
"action": "findNotes",
|
314
317
|
"version": 6,
|
315
318
|
"params": {"query": "added:1"}
|
GameSentenceMiner/config_gui.py
CHANGED
@@ -147,6 +147,7 @@ class ConfigApp:
|
|
147
147
|
backfill_audio=self.backfill_audio.get()
|
148
148
|
),
|
149
149
|
screenshot=Screenshot(
|
150
|
+
enabled=self.screenshot_enabled.get(),
|
150
151
|
width=self.screenshot_width.get(),
|
151
152
|
height=self.screenshot_height.get(),
|
152
153
|
quality=self.screenshot_quality.get(),
|
@@ -157,12 +158,14 @@ class ConfigApp:
|
|
157
158
|
use_beginning_of_line_as_screenshot=self.use_beginning_of_line_as_screenshot.get()
|
158
159
|
),
|
159
160
|
audio=Audio(
|
161
|
+
enabled=self.audio_enabled.get(),
|
160
162
|
extension=self.audio_extension.get(),
|
161
163
|
beginning_offset=float(self.beginning_offset.get()),
|
162
164
|
end_offset=float(self.end_offset.get()),
|
163
165
|
ffmpeg_reencode_options=self.ffmpeg_reencode_options.get(),
|
164
166
|
external_tool = self.external_tool.get(),
|
165
167
|
anki_media_collection=self.anki_media_collection.get(),
|
168
|
+
external_tool_enabled=self.external_tool_enabled.get(),
|
166
169
|
),
|
167
170
|
obs=OBS(
|
168
171
|
enabled=self.obs_enabled.get(),
|
@@ -171,7 +174,6 @@ class ConfigApp:
|
|
171
174
|
host=self.obs_host.get(),
|
172
175
|
port=int(self.obs_port.get()),
|
173
176
|
password=self.obs_password.get(),
|
174
|
-
start_buffer=self.obs_start_buffer.get(),
|
175
177
|
get_game_from_scene=self.get_game_from_scene_name.get(),
|
176
178
|
minimum_replay_size=int(self.minimum_replay_size.get())
|
177
179
|
),
|
@@ -679,6 +681,11 @@ class ConfigApp:
|
|
679
681
|
screenshot_frame = ttk.Frame(self.notebook)
|
680
682
|
self.notebook.add(screenshot_frame, text='Screenshot')
|
681
683
|
|
684
|
+
ttk.Label(screenshot_frame, text="Enabled:").grid(row=self.current_row, column=0, sticky='W')
|
685
|
+
self.screenshot_enabled = tk.BooleanVar(value=self.settings.screenshot.enabled)
|
686
|
+
ttk.Checkbutton(screenshot_frame, variable=self.screenshot_enabled).grid(row=self.current_row, column=1, sticky='W')
|
687
|
+
self.add_label_and_increment_row(screenshot_frame, "Enable or disable screenshot processing.", row=self.current_row, column=2)
|
688
|
+
|
682
689
|
ttk.Label(screenshot_frame, text="Width:").grid(row=self.current_row, column=0, sticky='W')
|
683
690
|
self.screenshot_width = ttk.Entry(screenshot_frame)
|
684
691
|
self.screenshot_width.insert(0, str(self.settings.screenshot.width))
|
@@ -737,6 +744,11 @@ class ConfigApp:
|
|
737
744
|
audio_frame = ttk.Frame(self.notebook)
|
738
745
|
self.notebook.add(audio_frame, text='Audio')
|
739
746
|
|
747
|
+
ttk.Label(audio_frame, text="Enabled:").grid(row=self.current_row, column=0, sticky='W')
|
748
|
+
self.audio_enabled = tk.BooleanVar(value=self.settings.audio.enabled)
|
749
|
+
ttk.Checkbutton(audio_frame, variable=self.audio_enabled).grid(row=self.current_row, column=1, sticky='W')
|
750
|
+
self.add_label_and_increment_row(audio_frame, "Enable or disable audio processing.", row=self.current_row, column=2)
|
751
|
+
|
740
752
|
ttk.Label(audio_frame, text="Audio Extension:").grid(row=self.current_row, column=0, sticky='W')
|
741
753
|
self.audio_extension = ttk.Combobox(audio_frame, values=['opus', 'mp3', 'ogg', 'aac', 'm4a'])
|
742
754
|
self.audio_extension.insert(0, self.settings.audio.extension)
|
@@ -777,10 +789,13 @@ class ConfigApp:
|
|
777
789
|
self.external_tool = ttk.Entry(audio_frame)
|
778
790
|
self.external_tool.insert(0, self.settings.audio.external_tool)
|
779
791
|
self.external_tool.grid(row=self.current_row, column=1)
|
792
|
+
ttk.Label(audio_frame, text="Enabled:").grid(row=self.current_row, column=2, sticky='W')
|
793
|
+
self.external_tool_enabled = tk.BooleanVar(value=self.settings.audio.external_tool_enabled)
|
794
|
+
ttk.Checkbutton(audio_frame, variable=self.external_tool_enabled).grid(row=self.current_row, column=3, sticky='W')
|
780
795
|
self.add_label_and_increment_row(audio_frame,
|
781
796
|
"Path to External tool that opens the audio up for manual trimming. I recommend OcenAudio for in-place Editing.",
|
782
797
|
row=self.current_row,
|
783
|
-
column=
|
798
|
+
column=4)
|
784
799
|
|
785
800
|
ttk.Button(audio_frame, text="Install Ocenaudio", command=self.download_and_install_ocen).grid(
|
786
801
|
row=self.current_row, column=0, pady=5)
|
@@ -835,12 +850,6 @@ class ConfigApp:
|
|
835
850
|
self.add_label_and_increment_row(obs_frame, "Password for the OBS WebSocket server.", row=self.current_row,
|
836
851
|
column=2)
|
837
852
|
|
838
|
-
ttk.Label(obs_frame, text="Start/Stop Buffer:").grid(row=self.current_row, column=0, sticky='W')
|
839
|
-
self.obs_start_buffer = tk.BooleanVar(value=self.settings.obs.start_buffer)
|
840
|
-
ttk.Checkbutton(obs_frame, variable=self.obs_start_buffer).grid(row=self.current_row, column=1, sticky='W')
|
841
|
-
self.add_label_and_increment_row(obs_frame, "Start and Stop the Buffer when Script runs.", row=self.current_row,
|
842
|
-
column=2)
|
843
|
-
|
844
853
|
ttk.Label(obs_frame, text="Get Game From Scene Name:").grid(row=self.current_row, column=0, sticky='W')
|
845
854
|
self.get_game_from_scene_name = tk.BooleanVar(value=self.settings.obs.get_game_from_scene)
|
846
855
|
ttk.Checkbutton(obs_frame, variable=self.get_game_from_scene_name).grid(row=self.current_row, column=1,
|
@@ -98,6 +98,7 @@ class Features:
|
|
98
98
|
@dataclass_json
|
99
99
|
@dataclass
|
100
100
|
class Screenshot:
|
101
|
+
enabled: bool = True
|
101
102
|
width: str = 0
|
102
103
|
height: str = 0
|
103
104
|
quality: str = 85
|
@@ -111,12 +112,14 @@ class Screenshot:
|
|
111
112
|
@dataclass_json
|
112
113
|
@dataclass
|
113
114
|
class Audio:
|
115
|
+
enabled: bool = True
|
114
116
|
extension: str = 'opus'
|
115
117
|
beginning_offset: float = 0.0
|
116
118
|
end_offset: float = 0.5
|
117
119
|
ffmpeg_reencode_options: str = ''
|
118
120
|
external_tool: str = ""
|
119
121
|
anki_media_collection: str = ""
|
122
|
+
external_tool_enabled: bool = True
|
120
123
|
|
121
124
|
|
122
125
|
@dataclass_json
|
@@ -128,7 +131,6 @@ class OBS:
|
|
128
131
|
host: str = "localhost"
|
129
132
|
port: int = 4455
|
130
133
|
password: str = "your_password"
|
131
|
-
start_buffer: bool = True
|
132
134
|
get_game_from_scene: bool = True
|
133
135
|
minimum_replay_size: int = 0
|
134
136
|
|
GameSentenceMiner/gsm.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
+
|
1
2
|
import os.path
|
2
3
|
import signal
|
3
|
-
import time
|
4
4
|
from subprocess import Popen
|
5
5
|
|
6
6
|
import keyboard
|
@@ -11,6 +11,8 @@ from pystray import Icon, Menu, MenuItem
|
|
11
11
|
from watchdog.events import FileSystemEventHandler
|
12
12
|
from watchdog.observers import Observer
|
13
13
|
|
14
|
+
import time
|
15
|
+
|
14
16
|
from GameSentenceMiner import anki
|
15
17
|
from GameSentenceMiner import config_gui
|
16
18
|
from GameSentenceMiner import configuration
|
@@ -29,11 +31,11 @@ from GameSentenceMiner.gametext import get_text_event, get_mined_line, GameLine
|
|
29
31
|
from GameSentenceMiner.obs import check_obs_folder_is_correct
|
30
32
|
from GameSentenceMiner.util import *
|
31
33
|
from GameSentenceMiner.utility_gui import init_utility_window, get_utility_window
|
32
|
-
from GameSentenceMiner.vad import silero_trim, whisper_helper, vosk_helper
|
33
34
|
|
34
35
|
if is_windows():
|
35
36
|
import win32api
|
36
37
|
|
38
|
+
silero_trim, whisper_helper, vosk_helper = None, None, None
|
37
39
|
procs_to_close = []
|
38
40
|
settings_window: config_gui.ConfigApp = None
|
39
41
|
obs_paused = False
|
@@ -42,34 +44,16 @@ menu: Menu
|
|
42
44
|
root = None
|
43
45
|
|
44
46
|
|
47
|
+
|
45
48
|
class VideoToAudioHandler(FileSystemEventHandler):
|
46
49
|
def on_created(self, event):
|
47
50
|
if event.is_directory or ("Replay" not in event.src_path and "GSM" not in event.src_path):
|
48
51
|
return
|
49
52
|
if event.src_path.endswith(".mkv") or event.src_path.endswith(".mp4"): # Adjust based on your OBS output format
|
50
53
|
logger.info(f"MKV {event.src_path} FOUND, RUNNING LOGIC")
|
51
|
-
|
54
|
+
wait_for_stable_file(event.src_path)
|
52
55
|
self.convert_to_audio(event.src_path)
|
53
56
|
|
54
|
-
@staticmethod
|
55
|
-
def wait_for_stable_file(file_path, timeout=10, check_interval=0.5):
|
56
|
-
elapsed_time = 0
|
57
|
-
last_size = -1
|
58
|
-
|
59
|
-
while elapsed_time < timeout:
|
60
|
-
try:
|
61
|
-
current_size = os.path.getsize(file_path)
|
62
|
-
if current_size == last_size:
|
63
|
-
return True
|
64
|
-
last_size = current_size
|
65
|
-
time.sleep(check_interval)
|
66
|
-
elapsed_time += check_interval
|
67
|
-
except Exception as e:
|
68
|
-
logger.warning(f"Error checking file size, will still try updating Anki Card!: {e}")
|
69
|
-
return False
|
70
|
-
logger.warning("File size did not stabilize within the timeout period. Continuing...")
|
71
|
-
return False
|
72
|
-
|
73
57
|
@staticmethod
|
74
58
|
def convert_to_audio(video_path):
|
75
59
|
try:
|
@@ -141,7 +125,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
141
125
|
tango = last_note.get_field(get_config().anki.word_field) if last_note else ''
|
142
126
|
get_utility_window().reset_checkboxes()
|
143
127
|
|
144
|
-
if get_config().anki.sentence_audio_field:
|
128
|
+
if get_config().anki.sentence_audio_field and get_config().audio.enabled:
|
145
129
|
logger.debug("Attempting to get audio from video")
|
146
130
|
final_audio_output, should_update_audio, vad_trimmed_audio = VideoToAudioHandler.get_audio(
|
147
131
|
start_line,
|
@@ -151,7 +135,10 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
151
135
|
final_audio_output = ""
|
152
136
|
should_update_audio = False
|
153
137
|
vad_trimmed_audio = ""
|
154
|
-
|
138
|
+
if not get_config().audio.enabled:
|
139
|
+
logger.info("Audio is disabled in config, skipping audio processing!")
|
140
|
+
elif not get_config().anki.sentence_audio_field:
|
141
|
+
logger.info("No SentenceAudio Field in config, skipping audio processing!")
|
155
142
|
if get_config().anki.update_anki and last_note:
|
156
143
|
anki.update_anki_card(last_note, note, audio_path=final_audio_output, video_path=video_path,
|
157
144
|
tango=tango,
|
@@ -183,26 +170,10 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
183
170
|
f"{obs.get_current_game(sanitize=True)}.{get_config().audio.extension}"))
|
184
171
|
should_update_audio = True
|
185
172
|
if get_config().vad.do_vad_postprocessing:
|
186
|
-
|
187
|
-
case configuration.SILERO:
|
188
|
-
should_update_audio = silero_trim.process_audio_with_silero(trimmed_audio, vad_trimmed_audio)
|
189
|
-
case configuration.VOSK:
|
190
|
-
should_update_audio = vosk_helper.process_audio_with_vosk(trimmed_audio, vad_trimmed_audio)
|
191
|
-
case configuration.WHISPER:
|
192
|
-
should_update_audio = whisper_helper.process_audio_with_whisper(trimmed_audio,
|
193
|
-
vad_trimmed_audio)
|
173
|
+
should_update_audio = do_vad_processing(get_config().vad.selected_vad_model, trimmed_audio, vad_trimmed_audio)
|
194
174
|
if not should_update_audio:
|
195
|
-
|
196
|
-
|
197
|
-
pass
|
198
|
-
case configuration.SILERO:
|
199
|
-
should_update_audio = silero_trim.process_audio_with_silero(trimmed_audio,
|
200
|
-
vad_trimmed_audio)
|
201
|
-
case configuration.VOSK:
|
202
|
-
should_update_audio = vosk_helper.process_audio_with_vosk(trimmed_audio, vad_trimmed_audio)
|
203
|
-
case configuration.WHISPER:
|
204
|
-
should_update_audio = whisper_helper.process_audio_with_whisper(trimmed_audio,
|
205
|
-
vad_trimmed_audio)
|
175
|
+
should_update_audio = do_vad_processing(get_config().vad.selected_vad_model, trimmed_audio,
|
176
|
+
vad_trimmed_audio)
|
206
177
|
if not should_update_audio and get_config().vad.add_audio_on_no_results:
|
207
178
|
logger.info("No voice activity detected, using full audio.")
|
208
179
|
vad_trimmed_audio = trimmed_audio
|
@@ -214,6 +185,22 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
214
185
|
shutil.move(vad_trimmed_audio, final_audio_output)
|
215
186
|
return final_audio_output, should_update_audio, vad_trimmed_audio
|
216
187
|
|
188
|
+
|
189
|
+
def do_vad_processing(model, trimmed_audio, vad_trimmed_audio, second_pass=False):
|
190
|
+
match model:
|
191
|
+
case configuration.OFF:
|
192
|
+
pass
|
193
|
+
case configuration.SILERO:
|
194
|
+
from GameSentenceMiner.vad import silero_trim
|
195
|
+
return silero_trim.process_audio_with_silero(trimmed_audio, vad_trimmed_audio)
|
196
|
+
case configuration.VOSK:
|
197
|
+
from GameSentenceMiner.vad import vosk_helper
|
198
|
+
return vosk_helper.process_audio_with_vosk(trimmed_audio, vad_trimmed_audio)
|
199
|
+
case configuration.WHISPER:
|
200
|
+
from GameSentenceMiner.vad import whisper_helper
|
201
|
+
return whisper_helper.process_audio_with_whisper(trimmed_audio, vad_trimmed_audio)
|
202
|
+
|
203
|
+
|
217
204
|
def play_audio_in_external(filepath):
|
218
205
|
exe = get_config().advanced.audio_player_path
|
219
206
|
|
@@ -244,6 +231,8 @@ def play_video_in_external(line, filepath):
|
|
244
231
|
command.append(convert_to_vlc_seconds(start))
|
245
232
|
command.append(os.path.normpath(filepath))
|
246
233
|
|
234
|
+
logger.info(" ".join(command))
|
235
|
+
|
247
236
|
try:
|
248
237
|
proc = subprocess.Popen(command)
|
249
238
|
print(f"Opened {filepath} in {get_config().advanced.video_player_path}.")
|
@@ -286,7 +275,7 @@ def register_hotkeys():
|
|
286
275
|
def get_screenshot():
|
287
276
|
try:
|
288
277
|
image = obs.get_screenshot()
|
289
|
-
|
278
|
+
wait_for_stable_file(image, timeout=3)
|
290
279
|
if not image:
|
291
280
|
raise Exception("Failed to get Screenshot from OBS")
|
292
281
|
encoded_image = ffmpeg.process_image(image)
|
@@ -479,9 +468,8 @@ def cleanup():
|
|
479
468
|
util.keep_running = False
|
480
469
|
|
481
470
|
if get_config().obs.enabled:
|
482
|
-
|
483
|
-
|
484
|
-
obs.disconnect_from_obs()
|
471
|
+
obs.stop_replay_buffer()
|
472
|
+
obs.disconnect_from_obs()
|
485
473
|
if get_config().obs.close_obs:
|
486
474
|
close_obs()
|
487
475
|
|
@@ -521,9 +509,9 @@ def initialize(reloading=False):
|
|
521
509
|
if is_windows():
|
522
510
|
download_obs_if_needed()
|
523
511
|
download_ffmpeg_if_needed()
|
524
|
-
|
525
|
-
|
526
|
-
|
512
|
+
if get_config().obs.enabled:
|
513
|
+
if get_config().obs.open_obs:
|
514
|
+
obs_process = obs.start_obs()
|
527
515
|
# obs.connect_to_obs(start_replay=True)
|
528
516
|
# anki.start_monitoring_anki()
|
529
517
|
# gametext.start_text_monitor()
|
@@ -541,27 +529,29 @@ def initialize(reloading=False):
|
|
541
529
|
def initialize_async():
|
542
530
|
tasks = [gametext.start_text_monitor, connect_websocket, run_tray]
|
543
531
|
threads = []
|
544
|
-
|
545
|
-
if get_config().obs.open_obs:
|
546
|
-
tasks.append(obs.start_obs)
|
547
|
-
else:
|
548
|
-
tasks.append(obs.connect_to_obs)
|
549
|
-
tasks.append(anki.start_monitoring_anki)
|
550
|
-
if get_config().vad.do_vad_postprocessing:
|
551
|
-
if VOSK in (get_config().vad.backup_vad_model, get_config().vad.selected_vad_model):
|
552
|
-
tasks.append(vosk_helper.get_vosk_model)
|
553
|
-
if WHISPER in (get_config().vad.backup_vad_model, get_config().vad.selected_vad_model):
|
554
|
-
tasks.append(whisper_helper.initialize_whisper_model)
|
532
|
+
tasks.append(anki.start_monitoring_anki)
|
555
533
|
for task in tasks:
|
556
534
|
threads.append(util.run_new_thread(task))
|
557
535
|
return threads
|
558
536
|
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
537
|
+
|
538
|
+
def post_init():
|
539
|
+
def do_post_init():
|
540
|
+
global silero_trim, whisper_helper, vosk_helper
|
541
|
+
logger.info("Post-Initialization started.")
|
542
|
+
if get_config().obs.enabled:
|
543
|
+
obs.connect_to_obs()
|
544
|
+
check_obs_folder_is_correct()
|
545
|
+
from GameSentenceMiner.vad import vosk_helper
|
546
|
+
from GameSentenceMiner.vad import whisper_helper
|
547
|
+
if get_config().vad.is_vosk():
|
548
|
+
vosk_helper.get_vosk_model()
|
549
|
+
if get_config().vad.is_whisper():
|
550
|
+
whisper_helper.initialize_whisper_model()
|
551
|
+
if get_config().vad.is_silero():
|
552
|
+
from GameSentenceMiner.vad import silero_trim
|
553
|
+
|
554
|
+
util.run_new_thread(do_post_init)
|
565
555
|
|
566
556
|
|
567
557
|
def handle_websocket_message(message: Message):
|
@@ -581,7 +571,6 @@ def main(reloading=False):
|
|
581
571
|
init_utility_window(root)
|
582
572
|
initialize(reloading)
|
583
573
|
initialize_async()
|
584
|
-
check_obs_folder_is_correct()
|
585
574
|
observer = Observer()
|
586
575
|
observer.schedule(VideoToAudioHandler(), get_config().paths.folder_to_watch, recursive=False)
|
587
576
|
observer.start()
|
@@ -599,6 +588,7 @@ def main(reloading=False):
|
|
599
588
|
root.after(0, settings_window.show)
|
600
589
|
if get_config().general.open_multimine_on_startup:
|
601
590
|
root.after(0, get_utility_window().show)
|
591
|
+
root.after(0, post_init)
|
602
592
|
settings_window.add_save_hook(update_icon)
|
603
593
|
settings_window.on_exit = exit_program
|
604
594
|
root.mainloop()
|
@@ -613,4 +603,5 @@ def main(reloading=False):
|
|
613
603
|
|
614
604
|
|
615
605
|
if __name__ == "__main__":
|
606
|
+
logger.info("Starting GSM")
|
616
607
|
main()
|
GameSentenceMiner/obs.py
CHANGED
@@ -28,12 +28,9 @@ def start_obs():
|
|
28
28
|
return None
|
29
29
|
|
30
30
|
try:
|
31
|
-
|
32
|
-
|
33
|
-
return obs_pid
|
34
|
-
obs_process = subprocess.Popen([obs_path, '--disable-shutdown-check', '--portable'], cwd=os.path.dirname(obs_path))
|
31
|
+
obs_process = subprocess.Popen([obs_path, '--disable-shutdown-check', '--portable', '--startreplaybuffer'], cwd=os.path.dirname(obs_path))
|
32
|
+
|
35
33
|
logger.info("OBS launched")
|
36
|
-
connect_to_obs()
|
37
34
|
return obs_process.pid
|
38
35
|
except Exception as e:
|
39
36
|
logger.error(f"Error launching OBS: {e}")
|
@@ -48,15 +45,6 @@ def check_obs_folder_is_correct():
|
|
48
45
|
get_master_config().sync_shared_fields()
|
49
46
|
save_full_config(get_master_config())
|
50
47
|
|
51
|
-
def is_obs_running(obs_path):
|
52
|
-
obs_path = os.path.abspath(obs_path) # Normalize path
|
53
|
-
for process in psutil.process_iter(['exe']):
|
54
|
-
try:
|
55
|
-
if process.info['exe'] and os.path.abspath(process.info['exe']) == obs_path:
|
56
|
-
return process.pid
|
57
|
-
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
58
|
-
continue
|
59
|
-
return False
|
60
48
|
|
61
49
|
def get_obs_websocket_config_values():
|
62
50
|
config_path = os.path.join(get_app_directory(), 'obs-studio', 'config', 'obs-studio', 'plugin_config', 'obs-websocket', 'config.json')
|
@@ -92,10 +80,8 @@ def get_obs_websocket_config_values():
|
|
92
80
|
|
93
81
|
|
94
82
|
def on_connect(obs):
|
95
|
-
logger.info("
|
96
|
-
|
97
|
-
if get_config().obs.start_buffer:
|
98
|
-
start_replay_buffer()
|
83
|
+
logger.info("Reconnected to OBS WebSocket.")
|
84
|
+
start_replay_buffer()
|
99
85
|
|
100
86
|
|
101
87
|
def on_disconnect(obs):
|
@@ -111,9 +97,6 @@ def connect_to_obs():
|
|
111
97
|
password=get_config().obs.password, authreconnect=1, on_connect=on_connect,
|
112
98
|
on_disconnect=on_disconnect)
|
113
99
|
client.connect()
|
114
|
-
|
115
|
-
if get_config().obs.start_buffer:
|
116
|
-
start_replay_buffer()
|
117
100
|
update_current_game()
|
118
101
|
|
119
102
|
|
@@ -135,6 +118,7 @@ def do_obs_call(request, from_dict = None, retry=10):
|
|
135
118
|
return from_dict(response.datain)
|
136
119
|
return None
|
137
120
|
except Exception as e:
|
121
|
+
logger.error(e)
|
138
122
|
if "socket is already closed" in str(e) or "object has no attribute" in str(e):
|
139
123
|
if retry > 0:
|
140
124
|
time.sleep(1)
|
GameSentenceMiner/util.py
CHANGED
@@ -6,6 +6,7 @@ import string
|
|
6
6
|
import subprocess
|
7
7
|
import sys
|
8
8
|
import threading
|
9
|
+
import time
|
9
10
|
from datetime import datetime
|
10
11
|
from sys import platform
|
11
12
|
|
@@ -178,38 +179,31 @@ def combine_dialogue(dialogue_lines, new_lines=None):
|
|
178
179
|
|
179
180
|
return new_lines
|
180
181
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
# if get_config().vad.is_vosk():
|
210
|
-
# if check_and_install("vosk"):
|
211
|
-
# from GameSentenceMiner.vad import vosk_helper
|
212
|
-
# else:
|
213
|
-
# logger.error("Vosk is enabled and vosk package could not be installed.")
|
214
|
-
#
|
215
|
-
# return silero_trim, whisper_helper, vosk_helper
|
182
|
+
def wait_for_stable_file(file_path, timeout=10, check_interval=0.1):
|
183
|
+
elapsed_time = 0
|
184
|
+
last_size = -1
|
185
|
+
|
186
|
+
while elapsed_time < timeout:
|
187
|
+
try:
|
188
|
+
current_size = os.path.getsize(file_path)
|
189
|
+
if current_size == last_size:
|
190
|
+
return True
|
191
|
+
last_size = current_size
|
192
|
+
time.sleep(check_interval)
|
193
|
+
elapsed_time += check_interval
|
194
|
+
except Exception as e:
|
195
|
+
logger.warning(f"Error checking file size, will still try updating Anki Card!: {e}")
|
196
|
+
return False
|
197
|
+
logger.warning("File size did not stabilize within the timeout period. Continuing...")
|
198
|
+
return False
|
199
|
+
|
200
|
+
|
201
|
+
def import_vad_models():
|
202
|
+
silero_trim, whisper_helper, vosk_helper = None, None, None
|
203
|
+
if get_config().vad.is_silero():
|
204
|
+
from GameSentenceMiner.vad import silero_trim
|
205
|
+
if get_config().vad.is_whisper():
|
206
|
+
from GameSentenceMiner.vad import whisper_helper
|
207
|
+
if get_config().vad.is_vosk():
|
208
|
+
from GameSentenceMiner.vad import vosk_helper
|
209
|
+
return silero_trim, whisper_helper, vosk_helper
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: GameSentenceMiner
|
3
|
-
Version: 2.5.
|
3
|
+
Version: 2.5.13
|
4
4
|
Summary: A tool for mining sentences from games. Update: Multi-Line Mining! Fixed!
|
5
5
|
Author-email: Beangate <bpwhelan95@gmail.com>
|
6
6
|
License: MIT License
|
@@ -97,7 +97,7 @@ steps before making an issue:
|
|
97
97
|
|
98
98
|
- Try Restarting OBS
|
99
99
|
- Make sure your hook is the best you can find. (Preferably it gives you the text RIGHT when the voice line starts)
|
100
|
-
- Try Adjusting Offset Configuration in
|
100
|
+
- Try Adjusting Offset Configuration in the config to better match your situation. (i.e. if the hook is late, add a
|
101
101
|
negative beginning offset)
|
102
102
|
- Try using "Trim beginning" in `VAD` settings.
|
103
103
|
|
@@ -1,15 +1,15 @@
|
|
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=
|
2
|
+
GameSentenceMiner/anki.py,sha256=FHm76i7WYEs--szhzZjz0B6DW4JuDQopmjR3RjYBogs,13358
|
3
|
+
GameSentenceMiner/config_gui.py,sha256=WVSN239lyVm5GwKzE1HXiCSCu2CNpkcDm8TGKlTJpMc,62093
|
4
|
+
GameSentenceMiner/configuration.py,sha256=_lxd1x78NX7tktt6RU7nhO4zkKzSoNKFQ-0r1zwNilU,20208
|
5
5
|
GameSentenceMiner/ffmpeg.py,sha256=fzOxrn-a4KqFdUY2oove164CTDOSsdPQtzqRW5f1P7c,12002
|
6
6
|
GameSentenceMiner/gametext.py,sha256=LORVdE2WEo1CDI8gonc7qxrhbS4KFKXFQVKjhlkpLbc,7368
|
7
|
-
GameSentenceMiner/gsm.py,sha256=
|
7
|
+
GameSentenceMiner/gsm.py,sha256=OHOoO0U-YMjLnMvwG9zg63jDTcXLg-rG90Cw27l5yBo,24224
|
8
8
|
GameSentenceMiner/model.py,sha256=JdnkT4VoPOXmOpRgFdvERZ09c9wLN6tUJxdrKlGZcqo,5305
|
9
9
|
GameSentenceMiner/notification.py,sha256=FY39ChSRK0Y8TQ6lBGsLnpZUFPtFpSy2tweeXVoV7kc,2809
|
10
|
-
GameSentenceMiner/obs.py,sha256=
|
10
|
+
GameSentenceMiner/obs.py,sha256=MlxRToq5wALPI1XrD8rxEU-N8mWII91cNJWY7rUa5uI,7642
|
11
11
|
GameSentenceMiner/package.py,sha256=YlS6QRMuVlm6mdXx0rlXv9_3erTGS21jaP3PNNWfAH0,1250
|
12
|
-
GameSentenceMiner/util.py,sha256=
|
12
|
+
GameSentenceMiner/util.py,sha256=Awhy57vX4NgQzygqKaGQn2EJ75T0uiXlhmINFOWlQkU,6825
|
13
13
|
GameSentenceMiner/utility_gui.py,sha256=H4aOddlsrVR768RwbMzYScCziuOz1JeySUigNrPlaac,7692
|
14
14
|
GameSentenceMiner/communication/__init__.py,sha256=_jGn9PJxtOAOPtJ2rI-Qu9hEHVZVpIvWlxKvqk91_zI,638
|
15
15
|
GameSentenceMiner/communication/send.py,sha256=oOJdCS6-LNX90amkRn5FL2xqx6THGm56zHR2ntVIFTE,229
|
@@ -21,9 +21,9 @@ GameSentenceMiner/vad/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
|
|
21
21
|
GameSentenceMiner/vad/silero_trim.py,sha256=-thDIZLuTLra3YBj7WR16Z6JeDgSpge2YuahprBvD8I,1585
|
22
22
|
GameSentenceMiner/vad/vosk_helper.py,sha256=BI_mg_qyrjNbuEJjXSUDoV0FWEtQtEOAPmrrNixnZ_8,5974
|
23
23
|
GameSentenceMiner/vad/whisper_helper.py,sha256=OF4J8TPPoKPJR1uFwrWAZ2Q7v0HJkVvNGmF8l1tACX0,3447
|
24
|
-
gamesentenceminer-2.5.
|
25
|
-
gamesentenceminer-2.5.
|
26
|
-
gamesentenceminer-2.5.
|
27
|
-
gamesentenceminer-2.5.
|
28
|
-
gamesentenceminer-2.5.
|
29
|
-
gamesentenceminer-2.5.
|
24
|
+
gamesentenceminer-2.5.13.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
25
|
+
gamesentenceminer-2.5.13.dist-info/METADATA,sha256=WpCjip7e_2FhZIkC9bEgZhknsc9qjoAhZDw5Q8Tq0Uw,5433
|
26
|
+
gamesentenceminer-2.5.13.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
27
|
+
gamesentenceminer-2.5.13.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
28
|
+
gamesentenceminer-2.5.13.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
29
|
+
gamesentenceminer-2.5.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|