GameSentenceMiner 2.9.10__py3-none-any.whl → 2.9.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- GameSentenceMiner/ai/ai_prompting.py +4 -0
- GameSentenceMiner/anki.py +9 -16
- GameSentenceMiner/config_gui.py +19 -9
- GameSentenceMiner/gsm.py +14 -8
- GameSentenceMiner/ocr/owocr_helper.py +3 -1
- GameSentenceMiner/owocr/owocr/ocr.py +7 -0
- GameSentenceMiner/owocr/owocr/run.py +13 -6
- GameSentenceMiner/util/configuration.py +11 -6
- GameSentenceMiner/util/electron_config.py +20 -20
- GameSentenceMiner/util/ffmpeg.py +10 -3
- GameSentenceMiner/util/gsm_utils.py +17 -0
- GameSentenceMiner/util/notification.py +9 -0
- GameSentenceMiner/util/text_log.py +3 -1
- {gamesentenceminer-2.9.10.dist-info → gamesentenceminer-2.9.12.dist-info}/METADATA +2 -3
- {gamesentenceminer-2.9.10.dist-info → gamesentenceminer-2.9.12.dist-info}/RECORD +19 -19
- {gamesentenceminer-2.9.10.dist-info → gamesentenceminer-2.9.12.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.9.10.dist-info → gamesentenceminer-2.9.12.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.9.10.dist-info → gamesentenceminer-2.9.12.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.9.10.dist-info → gamesentenceminer-2.9.12.dist-info}/top_level.txt +0 -0
@@ -10,6 +10,7 @@ from google.generativeai import GenerationConfig
|
|
10
10
|
from groq import Groq
|
11
11
|
|
12
12
|
from GameSentenceMiner.util.configuration import get_config, Ai, logger
|
13
|
+
from GameSentenceMiner.util.gsm_utils import is_connected
|
13
14
|
from GameSentenceMiner.util.text_log import GameLine
|
14
15
|
|
15
16
|
# Suppress debug logs from httpcore
|
@@ -184,6 +185,9 @@ current_ai_config: Ai | None = None
|
|
184
185
|
def get_ai_prompt_result(lines: List[GameLine], sentence: str, current_line: GameLine, game_title: str = ""):
|
185
186
|
global ai_manager, current_ai_config
|
186
187
|
try:
|
188
|
+
if not is_connected():
|
189
|
+
logger.error("No internet connection. Unable to proceed with AI prompt.")
|
190
|
+
return ""
|
187
191
|
if not ai_manager or get_config().ai != current_ai_config:
|
188
192
|
if get_config().ai.provider == AIType.GEMINI.value:
|
189
193
|
ai_manager = GeminiAI(model=get_config().ai.gemini_model, api_key=get_config().ai.gemini_api_key, logger=logger)
|
GameSentenceMiner/anki.py
CHANGED
@@ -8,7 +8,8 @@ from requests import post
|
|
8
8
|
|
9
9
|
from GameSentenceMiner import obs
|
10
10
|
from GameSentenceMiner.ai.ai_prompting import get_ai_prompt_result
|
11
|
-
from GameSentenceMiner.util.gsm_utils import wait_for_stable_file, remove_html_and_cloze_tags, combine_dialogue
|
11
|
+
from GameSentenceMiner.util.gsm_utils import wait_for_stable_file, remove_html_and_cloze_tags, combine_dialogue, \
|
12
|
+
run_new_thread, open_audio_in_external
|
12
13
|
from GameSentenceMiner.util import ffmpeg, notification
|
13
14
|
from GameSentenceMiner.util.configuration import *
|
14
15
|
from GameSentenceMiner.util.configuration import get_config
|
@@ -39,6 +40,8 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
39
40
|
if not reuse_audio:
|
40
41
|
if update_audio:
|
41
42
|
audio_in_anki = store_media_file(audio_path)
|
43
|
+
if get_config().audio.external_tool and get_config().audio.external_tool_enabled:
|
44
|
+
open_audio_in_external(f"{get_config().audio.anki_media_collection}/{audio_in_anki}")
|
42
45
|
if update_picture:
|
43
46
|
logger.info("Getting Screenshot...")
|
44
47
|
screenshot = ffmpeg.get_screenshot(video_path, ss_time, try_selector=get_config().screenshot.use_screenshot_selector)
|
@@ -92,12 +95,7 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
92
95
|
tag_string = " ".join(tags)
|
93
96
|
invoke("addTags", tags=tag_string, notes=[last_note.noteId])
|
94
97
|
|
95
|
-
check_and_update_note(last_note, note, tags)
|
96
|
-
|
97
|
-
if get_config().features.notify_on_update:
|
98
|
-
notification.send_note_updated(tango)
|
99
|
-
if get_config().audio.external_tool and get_config().audio.external_tool_enabled and update_audio:
|
100
|
-
open_audio_in_external(f"{get_config().audio.anki_media_collection}/{audio_in_anki}")
|
98
|
+
run_new_thread(lambda: check_and_update_note(last_note, note, tags))
|
101
99
|
|
102
100
|
def check_and_update_note(last_note, note, tags=[]):
|
103
101
|
selected_notes = invoke("guiSelectedNotes")
|
@@ -110,13 +108,8 @@ def check_and_update_note(last_note, note, tags=[]):
|
|
110
108
|
notification.open_browser_window(last_note.noteId, get_config().features.browser_query)
|
111
109
|
if get_config().features.open_anki_edit:
|
112
110
|
notification.open_anki_card(last_note.noteId)
|
113
|
-
|
114
|
-
|
115
|
-
logger.info(f"Opening audio in external program...")
|
116
|
-
if shell:
|
117
|
-
subprocess.Popen(f' "{get_config().audio.external_tool}" "{fileabspath}" ', shell=True)
|
118
|
-
else:
|
119
|
-
subprocess.Popen([get_config().audio.external_tool, fileabspath])
|
111
|
+
if get_config().features.notify_on_update:
|
112
|
+
notification.send_note_updated(last_note.noteId)
|
120
113
|
|
121
114
|
|
122
115
|
def add_image_to_card(last_note: AnkiCard, image_path):
|
@@ -135,7 +128,7 @@ def add_image_to_card(last_note: AnkiCard, image_path):
|
|
135
128
|
if update_picture:
|
136
129
|
note['fields'][get_config().anki.picture_field] = image_html
|
137
130
|
|
138
|
-
check_and_update_note(last_note, note)
|
131
|
+
run_new_thread(lambda: check_and_update_note(last_note, note))
|
139
132
|
|
140
133
|
logger.info(f"UPDATED IMAGE FOR ANKI CARD {last_note.noteId}")
|
141
134
|
|
@@ -311,7 +304,7 @@ def update_new_card():
|
|
311
304
|
def sentence_is_same_as_previous(last_card):
|
312
305
|
if not gsm_state.last_mined_line:
|
313
306
|
return False
|
314
|
-
return
|
307
|
+
return gsm_state.last_mined_line.id == get_mined_line(last_card).id
|
315
308
|
|
316
309
|
def get_sentence(card):
|
317
310
|
return card.get_field(get_config().anki.sentence_field)
|
GameSentenceMiner/config_gui.py
CHANGED
@@ -199,6 +199,7 @@ class ConfigApp:
|
|
199
199
|
external_tool = self.external_tool.get(),
|
200
200
|
anki_media_collection=self.anki_media_collection.get(),
|
201
201
|
external_tool_enabled=self.external_tool_enabled.get(),
|
202
|
+
pre_vad_end_offset=float(self.pre_vad_audio_offset.get()),
|
202
203
|
),
|
203
204
|
obs=OBS(
|
204
205
|
enabled=self.obs_enabled.get(),
|
@@ -444,7 +445,7 @@ class ConfigApp:
|
|
444
445
|
|
445
446
|
ttk.Label(vad_frame, text="Whisper Model:").grid(row=self.current_row, column=0, sticky='W')
|
446
447
|
self.whisper_model = ttk.Combobox(vad_frame, values=[WHISPER_TINY, WHISPER_BASE, WHISPER_SMALL, WHISPER_MEDIUM,
|
447
|
-
WHSIPER_LARGE])
|
448
|
+
WHSIPER_LARGE, WHISPER_TURBO])
|
448
449
|
self.whisper_model.set(self.settings.vad.whisper_model)
|
449
450
|
self.whisper_model.grid(row=self.current_row, column=1)
|
450
451
|
self.add_label_and_increment_row(vad_frame, "Select the Whisper model size for VAD.", row=self.current_row,
|
@@ -484,6 +485,13 @@ class ConfigApp:
|
|
484
485
|
self.vad_beginning_offset.grid(row=self.current_row, column=1)
|
485
486
|
self.add_label_and_increment_row(vad_frame, 'Beginning offset after VAD Trim, Only active if "Trim Beginning" is ON. Negative values = more time at the beginning', row=self.current_row, column=2)
|
486
487
|
|
488
|
+
ttk.Label(vad_frame, text="Audio End Offset:").grid(row=self.current_row, column=0, sticky='W')
|
489
|
+
self.end_offset = ttk.Entry(vad_frame)
|
490
|
+
self.end_offset.insert(0, str(self.settings.audio.end_offset))
|
491
|
+
self.end_offset.grid(row=self.current_row, column=1)
|
492
|
+
self.add_label_and_increment_row(vad_frame, "Offset in seconds from end of the video to extract.",
|
493
|
+
row=self.current_row, column=2)
|
494
|
+
|
487
495
|
ttk.Label(vad_frame, text="Add Audio on No Results:").grid(row=self.current_row, column=0, sticky='W')
|
488
496
|
self.add_audio_on_no_results = tk.BooleanVar(value=self.settings.vad.add_audio_on_no_results)
|
489
497
|
ttk.Checkbutton(vad_frame, variable=self.add_audio_on_no_results).grid(row=self.current_row, column=1, sticky='W')
|
@@ -890,19 +898,21 @@ class ConfigApp:
|
|
890
898
|
self.audio_extension.grid(row=self.current_row, column=1)
|
891
899
|
self.add_label_and_increment_row(audio_frame, "File extension for audio files.", row=self.current_row, column=2)
|
892
900
|
|
893
|
-
|
901
|
+
|
902
|
+
ttk.Label(audio_frame, text="Audio Extraction Beginning Offset:").grid(row=self.current_row, column=0, sticky='W')
|
894
903
|
self.beginning_offset = ttk.Entry(audio_frame)
|
895
904
|
self.beginning_offset.insert(0, str(self.settings.audio.beginning_offset))
|
896
905
|
self.beginning_offset.grid(row=self.current_row, column=1)
|
897
|
-
self.add_label_and_increment_row(audio_frame, "Offset in seconds
|
906
|
+
self.add_label_and_increment_row(audio_frame, "Offset in seconds from beginning of the video to extract",
|
898
907
|
row=self.current_row, column=2)
|
899
908
|
|
900
|
-
ttk.Label(audio_frame, text="End Offset:").grid(row=self.current_row, column=0, sticky='W')
|
901
|
-
self.
|
902
|
-
self.
|
903
|
-
self.
|
904
|
-
self.add_label_and_increment_row(audio_frame, "Offset in seconds to end
|
905
|
-
|
909
|
+
ttk.Label(audio_frame, text="Audio Extraction End Offset:").grid(row=self.current_row, column=0, sticky='W')
|
910
|
+
self.pre_vad_audio_offset = ttk.Entry(audio_frame)
|
911
|
+
self.pre_vad_audio_offset.insert(0, str(self.settings.audio.pre_vad_end_offset))
|
912
|
+
self.pre_vad_audio_offset.grid(row=self.current_row, column=1)
|
913
|
+
self.add_label_and_increment_row(audio_frame, "Offset in seconds to trim from the end before VAD processing starts. Negative = Less time on the end of the pre-vad trimmed audio (should usually be negative)",
|
914
|
+
row=self.current_row, column=2)
|
915
|
+
|
906
916
|
|
907
917
|
ttk.Label(audio_frame, text="FFmpeg Preset Options:").grid(row=self.current_row, column=0, sticky='W')
|
908
918
|
|
GameSentenceMiner/gsm.py
CHANGED
@@ -2,7 +2,12 @@ import asyncio
|
|
2
2
|
import subprocess
|
3
3
|
import sys
|
4
4
|
|
5
|
-
|
5
|
+
import os
|
6
|
+
|
7
|
+
os.environ.pop('TCL_LIBRARY', None)
|
8
|
+
|
9
|
+
from GameSentenceMiner.util.gsm_utils import wait_for_stable_file, make_unique_file_name, run_new_thread, \
|
10
|
+
open_audio_in_external
|
6
11
|
from GameSentenceMiner.util.communication.send import send_restart_signal
|
7
12
|
from GameSentenceMiner.util.downloader.download_tools import download_obs_if_needed, download_ffmpeg_if_needed
|
8
13
|
from GameSentenceMiner.vad import vad_processor, VADResult
|
@@ -13,12 +18,12 @@ try:
|
|
13
18
|
from subprocess import Popen
|
14
19
|
|
15
20
|
import keyboard
|
16
|
-
import psutil
|
17
21
|
import ttkbootstrap as ttk
|
18
22
|
from PIL import Image, ImageDraw
|
19
23
|
from pystray import Icon, Menu, MenuItem
|
20
24
|
from watchdog.events import FileSystemEventHandler
|
21
25
|
from watchdog.observers import Observer
|
26
|
+
import psutil
|
22
27
|
|
23
28
|
|
24
29
|
from GameSentenceMiner import anki
|
@@ -86,7 +91,8 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
86
91
|
logger.info("Replay buffer initiated externally. Skipping processing.")
|
87
92
|
return
|
88
93
|
with gsm_state.lock:
|
89
|
-
|
94
|
+
mined_line = get_text_event(last_note)
|
95
|
+
gsm_state.last_mined_line = mined_line
|
90
96
|
if os.path.exists(video_path) and os.access(video_path, os.R_OK):
|
91
97
|
logger.debug(f"Video found and is readable: {video_path}")
|
92
98
|
if get_config().obs.minimum_replay_size and not ffmpeg.is_video_big_enough(video_path,
|
@@ -104,7 +110,6 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
104
110
|
last_note = anki.get_cards_by_sentence(gametext.current_line_after_regex)
|
105
111
|
line_cutoff = None
|
106
112
|
start_line = None
|
107
|
-
mined_line = get_text_event(last_note)
|
108
113
|
if mined_line:
|
109
114
|
start_line = mined_line
|
110
115
|
if mined_line.next:
|
@@ -151,12 +156,14 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
151
156
|
# timing_only=True) ,doing_multi_line=bool(selected_lines), previous_line=True)
|
152
157
|
|
153
158
|
if get_config().anki.update_anki and last_note:
|
154
|
-
anki.update_anki_card(
|
159
|
+
anki.update_anki_card(
|
160
|
+
last_note, note, audio_path=final_audio_output, video_path=video_path,
|
155
161
|
tango=tango,
|
156
162
|
should_update_audio=vad_result.success,
|
157
163
|
ss_time=ss_timing,
|
158
164
|
game_line=start_line,
|
159
|
-
|
165
|
+
selected_lines=selected_lines
|
166
|
+
)
|
160
167
|
elif get_config().features.notify_on_update and vad_result.success:
|
161
168
|
notification.send_audio_generated_notification(vad_trimmed_audio)
|
162
169
|
except Exception as e:
|
@@ -268,7 +275,7 @@ def play_video_in_external(line, filepath):
|
|
268
275
|
|
269
276
|
command = [get_config().advanced.video_player_path]
|
270
277
|
|
271
|
-
start, _, _ = get_video_timings(filepath, line)
|
278
|
+
start, _, _, _ = get_video_timings(filepath, line)
|
272
279
|
|
273
280
|
if start:
|
274
281
|
if "vlc" in get_config().advanced.video_player_path:
|
@@ -708,4 +715,3 @@ if __name__ == "__main__":
|
|
708
715
|
asyncio.run(async_main())
|
709
716
|
except Exception as e:
|
710
717
|
logger.exception(e)
|
711
|
-
time.sleep(5)
|
@@ -211,7 +211,7 @@ def do_second_ocr(ocr1_text, time, img, filtering, scrolling=False):
|
|
211
211
|
engine=ocr2)
|
212
212
|
if scrolling:
|
213
213
|
return text
|
214
|
-
if fuzz.ratio(last_ocr2_result, text) >=
|
214
|
+
if fuzz.ratio(last_ocr2_result, text) >= 90:
|
215
215
|
logger.info("Seems like the same text from previous ocr2 result, not sending")
|
216
216
|
return
|
217
217
|
save_result_image(img)
|
@@ -280,6 +280,8 @@ def text_callback(text, orig_text, time, img=None, came_from_ss=False, filtering
|
|
280
280
|
last_oneocr_time = None
|
281
281
|
return
|
282
282
|
if not text or force_stable:
|
283
|
+
# or FUTURE ATTEMPT, I THINK THIS IS CLOSE?
|
284
|
+
# (orig_text and previous_text and len(orig_text) == len(previous_text_list) and len(orig_text[0] < len(previous_text_list)))):
|
283
285
|
force_stable = False
|
284
286
|
if previous_text and text_stable_start_time:
|
285
287
|
stable_time = text_stable_start_time
|
@@ -763,6 +763,13 @@ class OneOCR:
|
|
763
763
|
|
764
764
|
def __call__(self, img):
|
765
765
|
img = input_to_pil_image(img)
|
766
|
+
if img.width < 51 or img.height < 51:
|
767
|
+
new_width = max(img.width, 51)
|
768
|
+
new_height = max(img.height, 51)
|
769
|
+
new_img = Image.new("RGBA", (new_width, new_height), (0, 0, 0, 0))
|
770
|
+
new_img.paste(img, ((new_width - img.width) // 2, (new_height - img.height) // 2))
|
771
|
+
img = new_img
|
772
|
+
|
766
773
|
if not img:
|
767
774
|
return (False, 'Invalid image provided')
|
768
775
|
crop_coords = None
|
@@ -381,11 +381,15 @@ class TextFiltering:
|
|
381
381
|
else:
|
382
382
|
orig_text_filtered.append(None)
|
383
383
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
384
|
+
try:
|
385
|
+
if isinstance(last_result, list):
|
386
|
+
last_text = last_result
|
387
|
+
elif last_result and last_result[1] == engine_index:
|
388
|
+
last_text = last_result[0]
|
389
|
+
else:
|
390
|
+
last_text = []
|
391
|
+
except Exception as e:
|
392
|
+
logger.error(f"Error processing last_result {last_result}: {e}")
|
389
393
|
last_text = []
|
390
394
|
|
391
395
|
new_blocks = []
|
@@ -895,7 +899,6 @@ def process_and_write_results(img_or_path, write_to=None, last_result=None, filt
|
|
895
899
|
result = engine_instance(img_or_path)
|
896
900
|
res, text, crop_coords = (*result, None)[:3]
|
897
901
|
|
898
|
-
end_time = time.time()
|
899
902
|
|
900
903
|
if not res and ocr_2 == engine:
|
901
904
|
logger.opt(ansi=True).info(f"<{engine_color}>{engine_instance.readable_name}</{engine_color}> failed with message: {text}, trying <{engine_color}>{ocr_1}</{engine_color}>")
|
@@ -905,9 +908,13 @@ def process_and_write_results(img_or_path, write_to=None, last_result=None, filt
|
|
905
908
|
if last_result:
|
906
909
|
last_result = []
|
907
910
|
break
|
911
|
+
start_time = time.time()
|
908
912
|
result = engine_instance(img_or_path)
|
909
913
|
res, text, crop_coords = (*result, None)[:3]
|
910
914
|
|
915
|
+
end_time = time.time()
|
916
|
+
|
917
|
+
|
911
918
|
orig_text = []
|
912
919
|
# print(filtering)
|
913
920
|
#
|
@@ -29,6 +29,7 @@ WHISPER_BASE = 'base'
|
|
29
29
|
WHISPER_SMALL = 'small'
|
30
30
|
WHISPER_MEDIUM = 'medium'
|
31
31
|
WHSIPER_LARGE = 'large'
|
32
|
+
WHISPER_TURBO = 'turbo'
|
32
33
|
|
33
34
|
AI_GEMINI = 'Gemini'
|
34
35
|
AI_GROQ = 'Groq'
|
@@ -164,6 +165,7 @@ class Audio:
|
|
164
165
|
extension: str = 'opus'
|
165
166
|
beginning_offset: float = 0.0
|
166
167
|
end_offset: float = 0.5
|
168
|
+
pre_vad_end_offset: float = 0.0
|
167
169
|
ffmpeg_reencode_options: str = '-c:a libopus -f opus -af \"afade=t=in:d=0.10\"' if is_windows() else ''
|
168
170
|
external_tool: str = ""
|
169
171
|
anki_media_collection: str = ""
|
@@ -200,7 +202,7 @@ class VAD:
|
|
200
202
|
do_vad_postprocessing: bool = True
|
201
203
|
language: str = 'ja'
|
202
204
|
vosk_url: str = VOSK_BASE
|
203
|
-
selected_vad_model: str =
|
205
|
+
selected_vad_model: str = WHISPER
|
204
206
|
backup_vad_model: str = OFF
|
205
207
|
trim_beginning: bool = False
|
206
208
|
beginning_offset: float = -0.25
|
@@ -630,11 +632,14 @@ console_handler.setFormatter(formatter)
|
|
630
632
|
logger.addHandler(console_handler)
|
631
633
|
|
632
634
|
# Create rotating file handler with level DEBUG
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
635
|
+
file_path = get_log_path()
|
636
|
+
if os.path.exists(file_path) and os.path.getsize(file_path) > 10 * 1024 * 1024:
|
637
|
+
shutil.move(os.path.join(file_path, "gamesentenceminer.log"), os.path.join(file_path, "gamesentenceminer_old.log"))
|
638
|
+
|
639
|
+
file_handler = logging.FileHandler(file_path, encoding='utf-8')
|
640
|
+
file_handler.setLevel(logging.DEBUG)
|
641
|
+
file_handler.setFormatter(formatter)
|
642
|
+
logger.addHandler(file_handler)
|
638
643
|
|
639
644
|
DB_PATH = os.path.join(get_app_directory(), 'gsm.db')
|
640
645
|
|
@@ -7,13 +7,13 @@ from dataclasses_json import dataclass_json
|
|
7
7
|
from GameSentenceMiner.util.configuration import get_app_directory
|
8
8
|
|
9
9
|
|
10
|
-
@dataclass_json
|
11
|
-
@dataclass
|
12
|
-
class SteamGame:
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
# @dataclass_json
|
11
|
+
# @dataclass
|
12
|
+
# class SteamGame:
|
13
|
+
# id: str = ''
|
14
|
+
# name: str = ''
|
15
|
+
# processName: str = ''
|
16
|
+
# script: str = ''
|
17
17
|
|
18
18
|
@dataclass_json
|
19
19
|
@dataclass
|
@@ -31,13 +31,13 @@ class VNConfig:
|
|
31
31
|
launchVNOnStart: str = ""
|
32
32
|
lastVNLaunched: str = ""
|
33
33
|
|
34
|
-
@dataclass_json
|
35
|
-
@dataclass
|
36
|
-
class SteamConfig:
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
34
|
+
# @dataclass_json
|
35
|
+
# @dataclass
|
36
|
+
# class SteamConfig:
|
37
|
+
# steamPath: str = ""
|
38
|
+
# steamGames: List[SteamGame] = field(default_factory=list)
|
39
|
+
# launchSteamOnStart: int = 0
|
40
|
+
# lastGameLaunched: int = 0
|
41
41
|
|
42
42
|
@dataclass_json
|
43
43
|
@dataclass
|
@@ -60,7 +60,7 @@ class StoreConfig:
|
|
60
60
|
autoUpdateGSMApp: bool = False
|
61
61
|
pythonPath: str = ""
|
62
62
|
VN: VNConfig = field(default_factory=VNConfig)
|
63
|
-
steam: SteamConfig = field(default_factory=SteamConfig)
|
63
|
+
# steam: SteamConfig = field(default_factory=SteamConfig)
|
64
64
|
agentPath: str = ""
|
65
65
|
OCR: OCRConfig = field(default_factory=OCRConfig)
|
66
66
|
|
@@ -290,12 +290,12 @@ def get_last_steam_game_launched() -> int:
|
|
290
290
|
def set_last_steam_game_launched(game_id: int):
|
291
291
|
store.set('steam.lastGameLaunched', game_id)
|
292
292
|
|
293
|
-
def get_steam_games() -> List[SteamGame]:
|
294
|
-
|
295
|
-
|
293
|
+
# def get_steam_games() -> List[SteamGame]:
|
294
|
+
# steam_games_data = store.get('steam.steamGames')
|
295
|
+
# return [SteamGame.from_dict(game_data) for game_data in steam_games_data] if isinstance(steam_games_data, list) else []
|
296
296
|
|
297
|
-
def set_steam_games(games: List[SteamGame]):
|
298
|
-
|
297
|
+
# def set_steam_games(games: List[SteamGame]):
|
298
|
+
# store.set('steam.steamGames', [game.to_dict() for game in games])
|
299
299
|
|
300
300
|
# if __name__ == "__main__":
|
301
301
|
# # Example usage:
|
GameSentenceMiner/util/ffmpeg.py
CHANGED
@@ -298,20 +298,27 @@ def get_video_duration(file_path):
|
|
298
298
|
def trim_audio_based_on_last_line(untrimmed_audio, video_path, game_line, next_line, anki_card_creation_time):
|
299
299
|
trimmed_audio = tempfile.NamedTemporaryFile(dir=configuration.get_temporary_directory(),
|
300
300
|
suffix=f".{get_config().audio.extension}").name
|
301
|
-
start_trim_time, total_seconds, total_seconds_after_offset = get_video_timings(video_path, game_line, anki_card_creation_time)
|
301
|
+
start_trim_time, total_seconds, total_seconds_after_offset, file_length = get_video_timings(video_path, game_line, anki_card_creation_time)
|
302
302
|
end_trim_time = ""
|
303
303
|
|
304
304
|
ffmpeg_command = ffmpeg_base_command_list + [
|
305
305
|
"-i", untrimmed_audio,
|
306
306
|
"-ss", str(start_trim_time)]
|
307
307
|
if next_line and next_line > game_line.time:
|
308
|
-
end_total_seconds = total_seconds + (next_line - game_line.time).total_seconds() +
|
308
|
+
end_total_seconds = total_seconds + (next_line - game_line.time).total_seconds() + get_config().audio.pre_vad_end_offset
|
309
309
|
hours, remainder = divmod(end_total_seconds, 3600)
|
310
310
|
minutes, seconds = divmod(remainder, 60)
|
311
311
|
end_trim_time = "{:02}:{:02}:{:06.3f}".format(int(hours), int(minutes), seconds)
|
312
312
|
ffmpeg_command.extend(['-to', end_trim_time])
|
313
313
|
logger.debug(
|
314
314
|
f"Looks Like this is mining from History, or Multiple Lines were selected Trimming end of audio to {end_trim_time}")
|
315
|
+
elif get_config().audio.pre_vad_end_offset and get_config().audio.pre_vad_end_offset < 0:
|
316
|
+
end_total_seconds = file_length + get_config().audio.pre_vad_end_offset
|
317
|
+
hours, remainder = divmod(end_total_seconds, 3600)
|
318
|
+
minutes, seconds = divmod(remainder, 60)
|
319
|
+
end_trim_time = "{:02}:{:02}:{:06.3f}".format(int(hours), int(minutes), seconds)
|
320
|
+
ffmpeg_command.extend(['-to', end_trim_time])
|
321
|
+
logger.debug(f"Trimming end of audio to {end_trim_time} due to pre-vad end offset")
|
315
322
|
|
316
323
|
ffmpeg_command.extend([
|
317
324
|
"-c", "copy", # Using copy to avoid re-encoding, adjust if needed
|
@@ -349,7 +356,7 @@ def get_video_timings(video_path, game_line, anki_card_creation_time=None):
|
|
349
356
|
hours, remainder = divmod(total_seconds_after_offset, 3600)
|
350
357
|
minutes, seconds = divmod(remainder, 60)
|
351
358
|
start_trim_time = "{:02}:{:02}:{:06.3f}".format(int(hours), int(minutes), seconds)
|
352
|
-
return start_trim_time, total_seconds, total_seconds_after_offset
|
359
|
+
return start_trim_time, total_seconds, total_seconds_after_offset, file_length
|
353
360
|
|
354
361
|
|
355
362
|
def reencode_file_with_user_config(input_file, final_output_audio, user_ffmpeg_options):
|
@@ -2,6 +2,7 @@ import json
|
|
2
2
|
import os
|
3
3
|
import random
|
4
4
|
import re
|
5
|
+
import socket
|
5
6
|
import string
|
6
7
|
import subprocess
|
7
8
|
import threading
|
@@ -220,6 +221,22 @@ def do_text_replacements(text, replacements_json):
|
|
220
221
|
return text
|
221
222
|
|
222
223
|
|
224
|
+
def open_audio_in_external(fileabspath, shell=False):
|
225
|
+
logger.info(f"Opening audio in external program...")
|
226
|
+
if shell:
|
227
|
+
subprocess.Popen(f' "{get_config().audio.external_tool}" "{fileabspath}" ', shell=True)
|
228
|
+
else:
|
229
|
+
subprocess.Popen([get_config().audio.external_tool, fileabspath])
|
230
|
+
|
231
|
+
def is_connected():
|
232
|
+
try:
|
233
|
+
# Attempt to connect to a well-known host
|
234
|
+
socket.create_connection(("www.google.com", 80), timeout=2)
|
235
|
+
return True
|
236
|
+
except OSError:
|
237
|
+
return False
|
238
|
+
|
239
|
+
|
223
240
|
TEXT_REPLACEMENTS_FILE = os.path.join(get_app_directory(), 'config', 'text_replacements.json')
|
224
241
|
OCR_REPLACEMENTS_FILE = os.path.join(get_app_directory(), 'config', 'ocr_replacements.json')
|
225
242
|
os.makedirs(os.path.dirname(TEXT_REPLACEMENTS_FILE), exist_ok=True)
|
@@ -32,6 +32,15 @@ def open_browser_window(note_id, query=None):
|
|
32
32
|
}
|
33
33
|
|
34
34
|
try:
|
35
|
+
if query:
|
36
|
+
blank_req_data = {
|
37
|
+
"action": "guiBrowse",
|
38
|
+
"version": 6,
|
39
|
+
"params": {
|
40
|
+
"query": "refreshing...",
|
41
|
+
}
|
42
|
+
}
|
43
|
+
requests.post(url, json=blank_req_data, headers=headers)
|
35
44
|
response = requests.post(url, json=data, headers=headers)
|
36
45
|
if response.status_code == 200:
|
37
46
|
if query:
|
@@ -151,7 +151,9 @@ def get_line_and_future_lines(last_note):
|
|
151
151
|
return found_lines
|
152
152
|
|
153
153
|
|
154
|
-
def get_mined_line(last_note: AnkiCard, lines):
|
154
|
+
def get_mined_line(last_note: AnkiCard, lines=None):
|
155
|
+
if lines is None:
|
156
|
+
lines = []
|
155
157
|
if not last_note:
|
156
158
|
return lines[-1]
|
157
159
|
if not lines:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: GameSentenceMiner
|
3
|
-
Version: 2.9.
|
3
|
+
Version: 2.9.12
|
4
4
|
Summary: A tool for mining sentences from games.
|
5
5
|
Author-email: Beangate <bpwhelan95@gmail.com>
|
6
6
|
License: MIT License
|
@@ -24,7 +24,7 @@ Requires-Dist: rapidfuzz~=3.9.7
|
|
24
24
|
Requires-Dist: plyer~=2.1.0
|
25
25
|
Requires-Dist: keyboard~=0.13.5
|
26
26
|
Requires-Dist: websockets~=15.0.1
|
27
|
-
Requires-Dist: stable-ts~=2.
|
27
|
+
Requires-Dist: stable-ts~=2.19.0
|
28
28
|
Requires-Dist: silero-vad~=5.1.2
|
29
29
|
Requires-Dist: ttkbootstrap~=1.10.1
|
30
30
|
Requires-Dist: dataclasses_json~=0.6.7
|
@@ -37,7 +37,6 @@ Requires-Dist: pygetwindow; sys_platform == "win32"
|
|
37
37
|
Requires-Dist: flask
|
38
38
|
Requires-Dist: groq
|
39
39
|
Requires-Dist: obsws-python~=1.7.2
|
40
|
-
Requires-Dist: Flask-SocketIO
|
41
40
|
Dynamic: license-file
|
42
41
|
|
43
42
|
# GameSentenceMiner (GSM)
|
@@ -1,12 +1,12 @@
|
|
1
1
|
GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
GameSentenceMiner/anki.py,sha256=
|
3
|
-
GameSentenceMiner/config_gui.py,sha256=
|
2
|
+
GameSentenceMiner/anki.py,sha256=1DN00SUCSd4adIRrEqirXXf-2jSeFEuYNrr_WB8nlVE,14950
|
3
|
+
GameSentenceMiner/config_gui.py,sha256=iAOLD47sQW67kzBcZKSQ0Dwctc1ngZK1lwSVIaLpQPI,83559
|
4
4
|
GameSentenceMiner/gametext.py,sha256=mM-gw1d7c2EEvMUznaAevTQFLswNZavCuxMXhA9pV4g,6251
|
5
|
-
GameSentenceMiner/gsm.py,sha256=
|
5
|
+
GameSentenceMiner/gsm.py,sha256=ycv1wMEqTBM919qC5rlr0AVzfPwpYP1yIrbSgfibv4I,29174
|
6
6
|
GameSentenceMiner/obs.py,sha256=O9NYOGu7kwp4flq8LLXp8YJQg0JTZ8qBqiQNQ6u4ku4,14724
|
7
7
|
GameSentenceMiner/vad.py,sha256=Gk_VthD7mDp3-wM_S6bEv8ykGmqzCDbbcRiaEBzAE_o,14835
|
8
8
|
GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
GameSentenceMiner/ai/ai_prompting.py,sha256=
|
9
|
+
GameSentenceMiner/ai/ai_prompting.py,sha256=0jBAnngNwmc3dqJiVWe_QRy4Syr-muV-ML2rq0FiUtU,10215
|
10
10
|
GameSentenceMiner/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
11
|
GameSentenceMiner/assets/icon.png,sha256=9GRL8uXUAgkUSlvbm9Pv9o2poFVRGdW6s2ub_DeUD9M,937624
|
12
12
|
GameSentenceMiner/assets/icon128.png,sha256=l90j7biwdz5ahwOd5wZ-406ryEV9Pan93dquJQ3e1CI,18395
|
@@ -19,24 +19,24 @@ GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
|
|
19
19
|
GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=fEQ2o2NXksGRHpueO8c4TfAp75GEdAtAr1ngTFOsdpg,2257
|
20
20
|
GameSentenceMiner/ocr/ocrconfig.py,sha256=_tY8mjnzHMJrLS8E5pHqYXZjMuLoGKYgJwdhYgN-ny4,6466
|
21
21
|
GameSentenceMiner/ocr/owocr_area_selector.py,sha256=71trzwz9Isyy-kN9mLS8vIX-giC8Lkin4slLXaxudac,47162
|
22
|
-
GameSentenceMiner/ocr/owocr_helper.py,sha256=
|
22
|
+
GameSentenceMiner/ocr/owocr_helper.py,sha256=upbuBWKGj6VIg0P5lTopZ8kPPu_ErpdIduM21kpvGmI,18310
|
23
23
|
GameSentenceMiner/owocr/owocr/__init__.py,sha256=opjBOyGGyEqZCE6YdZPnyt7nVfiwyELHsXA0jAsjm14,25
|
24
24
|
GameSentenceMiner/owocr/owocr/__main__.py,sha256=XQaqZY99EKoCpU-gWQjNbTs7Kg17HvBVE7JY8LqIE0o,157
|
25
25
|
GameSentenceMiner/owocr/owocr/config.py,sha256=qM7kISHdUhuygGXOxmgU6Ef2nwBShrZtdqu4InDCViE,8103
|
26
26
|
GameSentenceMiner/owocr/owocr/lens_betterproto.py,sha256=oNoISsPilVVRBBPVDtb4-roJtAhp8ZAuFTci3TGXtMc,39141
|
27
|
-
GameSentenceMiner/owocr/owocr/ocr.py,sha256=
|
28
|
-
GameSentenceMiner/owocr/owocr/run.py,sha256=
|
27
|
+
GameSentenceMiner/owocr/owocr/ocr.py,sha256=vZR3du1fGg5-3cmPvYKTO4PFk7Lxyf6-BrIy7CmeG0I,42578
|
28
|
+
GameSentenceMiner/owocr/owocr/run.py,sha256=U6VIfCvsNPADG3twhp4SQVX1xhihSAGGrBnQj2x0C2c,54964
|
29
29
|
GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=Na6XStbQBtpQUSdbN3QhEswtKuU1JjReFk_K8t5ezQE,3395
|
30
30
|
GameSentenceMiner/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
|
-
GameSentenceMiner/util/configuration.py,sha256=
|
32
|
-
GameSentenceMiner/util/electron_config.py,sha256=
|
33
|
-
GameSentenceMiner/util/ffmpeg.py,sha256=
|
34
|
-
GameSentenceMiner/util/gsm_utils.py,sha256=
|
31
|
+
GameSentenceMiner/util/configuration.py,sha256=5YJSKelIpBTK7ggpGUERf17xVwH8Hy9GzvJ12eP8kHs,26095
|
32
|
+
GameSentenceMiner/util/electron_config.py,sha256=3VmIrcXhC-wIMMc4uqV85NrNenRl4ZUbnQfSjWEwuig,9852
|
33
|
+
GameSentenceMiner/util/ffmpeg.py,sha256=daItJprEqi5PQe-aFb836rls3tBHNqIQKz61vlJK07M,19276
|
34
|
+
GameSentenceMiner/util/gsm_utils.py,sha256=TLKA4CFAdboE8jOoN0MB0aKS1U5mXcINhOkKbnJqw_g,8802
|
35
35
|
GameSentenceMiner/util/model.py,sha256=iDtLTfR6D-ZC0gCiDqYno6-gA6Z07PZTM4B5MAA6xZI,5704
|
36
|
-
GameSentenceMiner/util/notification.py,sha256=
|
36
|
+
GameSentenceMiner/util/notification.py,sha256=hAUKWWDB4F_9exQhgiajfP5DT8u464RowsJGmBVRN_E,3882
|
37
37
|
GameSentenceMiner/util/package.py,sha256=u1ym5z869lw5EHvIviC9h9uH97bzUXSXXA8KIn8rUvk,1157
|
38
38
|
GameSentenceMiner/util/ss_selector.py,sha256=oCzmDbpEGvVselF-oDPIrBcQktGIZT0Zt16uDLDAHMQ,4493
|
39
|
-
GameSentenceMiner/util/text_log.py,sha256=
|
39
|
+
GameSentenceMiner/util/text_log.py,sha256=wdFOapIwHzDntR9G0YzJzDcbhO6b1taFkhdgsTnRGJ4,5356
|
40
40
|
GameSentenceMiner/util/communication/__init__.py,sha256=xh__yn2MhzXi9eLi89PeZWlJPn-cbBSjskhi1BRraXg,643
|
41
41
|
GameSentenceMiner/util/communication/send.py,sha256=Wki9qIY2CgYnuHbmnyKVIYkcKAN_oYS4up93XMikBaI,222
|
42
42
|
GameSentenceMiner/util/communication/websocket.py,sha256=gPgxA2R2U6QZJjPqbUgODC87gtacPhmuC8lCprIkSmA,3287
|
@@ -59,9 +59,9 @@ GameSentenceMiner/web/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
|
|
59
59
|
GameSentenceMiner/web/templates/index.html,sha256=HZKiIjiGJV8PGQ9T2aLDUNSfJn71qOwbYCjbRuSIjpY,213583
|
60
60
|
GameSentenceMiner/web/templates/text_replacements.html,sha256=tV5c8mCaWSt_vKuUpbdbLAzXZ3ATZeDvQ9PnnAfqY0M,8598
|
61
61
|
GameSentenceMiner/web/templates/utility.html,sha256=3flZinKNqUJ7pvrZk6xu__v67z44rXnaK7UTZ303R-8,16946
|
62
|
-
gamesentenceminer-2.9.
|
63
|
-
gamesentenceminer-2.9.
|
64
|
-
gamesentenceminer-2.9.
|
65
|
-
gamesentenceminer-2.9.
|
66
|
-
gamesentenceminer-2.9.
|
67
|
-
gamesentenceminer-2.9.
|
62
|
+
gamesentenceminer-2.9.12.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
63
|
+
gamesentenceminer-2.9.12.dist-info/METADATA,sha256=YgJO-mllUkDN7cY9Dq56LrbdBG7op8_1jvB4f9dITgY,7221
|
64
|
+
gamesentenceminer-2.9.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
65
|
+
gamesentenceminer-2.9.12.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
66
|
+
gamesentenceminer-2.9.12.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
67
|
+
gamesentenceminer-2.9.12.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|