GameSentenceMiner 2.9.9__py3-none-any.whl → 2.9.11__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 +11 -18
- GameSentenceMiner/config_gui.py +27 -10
- 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 +10 -5
- GameSentenceMiner/util/electron_config.py +20 -20
- GameSentenceMiner/util/ffmpeg.py +10 -3
- GameSentenceMiner/util/gsm_utils.py +17 -0
- GameSentenceMiner/util/notification.py +13 -1
- GameSentenceMiner/util/text_log.py +3 -1
- {gamesentenceminer-2.9.9.dist-info → gamesentenceminer-2.9.11.dist-info}/METADATA +1 -2
- {gamesentenceminer-2.9.9.dist-info → gamesentenceminer-2.9.11.dist-info}/RECORD +19 -19
- {gamesentenceminer-2.9.9.dist-info → gamesentenceminer-2.9.11.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.9.9.dist-info → gamesentenceminer-2.9.11.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.9.9.dist-info → gamesentenceminer-2.9.11.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.9.9.dist-info → gamesentenceminer-2.9.11.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,15 +40,17 @@ 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
|
-
screenshot = ffmpeg.get_screenshot(video_path, ss_time)
|
47
|
+
screenshot = ffmpeg.get_screenshot(video_path, ss_time, try_selector=get_config().screenshot.use_screenshot_selector)
|
45
48
|
wait_for_stable_file(screenshot)
|
46
49
|
screenshot_in_anki = store_media_file(screenshot)
|
47
50
|
if get_config().paths.remove_screenshot:
|
48
51
|
os.remove(screenshot)
|
49
52
|
if get_config().anki.previous_image_field and game_line.prev:
|
50
|
-
prev_screenshot = ffmpeg.get_screenshot_for_line(video_path, selected_lines[0].prev if selected_lines else game_line.prev)
|
53
|
+
prev_screenshot = ffmpeg.get_screenshot_for_line(video_path, selected_lines[0].prev if selected_lines else game_line.prev, try_selector=get_config().screenshot.use_screenshot_selector)
|
51
54
|
wait_for_stable_file(prev_screenshot)
|
52
55
|
prev_screenshot_in_anki = store_media_file(prev_screenshot)
|
53
56
|
if get_config().paths.remove_screenshot:
|
@@ -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
@@ -184,10 +184,11 @@ class ConfigApp:
|
|
184
184
|
extension=self.screenshot_extension.get(),
|
185
185
|
custom_ffmpeg_settings=self.screenshot_custom_ffmpeg_settings.get(),
|
186
186
|
screenshot_hotkey_updates_anki=self.screenshot_hotkey_update_anki.get(),
|
187
|
-
seconds_after_line = self.seconds_after_line.get(),
|
187
|
+
seconds_after_line = float(self.seconds_after_line.get()) if self.seconds_after_line.get() else 0.0,
|
188
188
|
# use_beginning_of_line_as_screenshot=self.use_beginning_of_line_as_screenshot.get(),
|
189
189
|
# use_new_screenshot_logic=self.use_new_screenshot_logic.get(),
|
190
|
-
screenshot_timing_setting=self.screenshot_timing.get()
|
190
|
+
screenshot_timing_setting=self.screenshot_timing.get(),
|
191
|
+
use_screenshot_selector=self.use_screenshot_selector.get(),
|
191
192
|
),
|
192
193
|
audio=Audio(
|
193
194
|
enabled=self.audio_enabled.get(),
|
@@ -198,6 +199,7 @@ class ConfigApp:
|
|
198
199
|
external_tool = self.external_tool.get(),
|
199
200
|
anki_media_collection=self.anki_media_collection.get(),
|
200
201
|
external_tool_enabled=self.external_tool_enabled.get(),
|
202
|
+
pre_vad_end_offset=float(self.pre_vad_audio_offset.get()),
|
201
203
|
),
|
202
204
|
obs=OBS(
|
203
205
|
enabled=self.obs_enabled.get(),
|
@@ -483,6 +485,13 @@ class ConfigApp:
|
|
483
485
|
self.vad_beginning_offset.grid(row=self.current_row, column=1)
|
484
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)
|
485
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
|
+
|
486
495
|
ttk.Label(vad_frame, text="Add Audio on No Results:").grid(row=self.current_row, column=0, sticky='W')
|
487
496
|
self.add_audio_on_no_results = tk.BooleanVar(value=self.settings.vad.add_audio_on_no_results)
|
488
497
|
ttk.Checkbutton(vad_frame, variable=self.add_audio_on_no_results).grid(row=self.current_row, column=1, sticky='W')
|
@@ -847,6 +856,12 @@ class ConfigApp:
|
|
847
856
|
self.add_label_and_increment_row(screenshot_frame, "Time in seconds to offset the screenshot based on the Timing setting above (should almost always be positive, can be negative if you use \"middle\")", row=self.current_row,
|
848
857
|
column=2)
|
849
858
|
|
859
|
+
ttk.Label(screenshot_frame, text="Use Screenshot Selector for every card:").grid(row=self.current_row, column=0, sticky='W')
|
860
|
+
self.use_screenshot_selector = tk.BooleanVar(value=self.settings.screenshot.use_screenshot_selector)
|
861
|
+
ttk.Checkbutton(screenshot_frame, variable=self.use_screenshot_selector).grid(row=self.current_row, column=1, sticky='W')
|
862
|
+
self.add_label_and_increment_row(screenshot_frame, "Enable to use the screenshot selector to choose the screenshot point on every card.", row=self.current_row,
|
863
|
+
column=2)
|
864
|
+
|
850
865
|
# ttk.Label(screenshot_frame, text="Use Beginning of Line as Screenshot:").grid(row=self.current_row, column=0, sticky='W')
|
851
866
|
# self.use_beginning_of_line_as_screenshot = tk.BooleanVar(value=self.settings.screenshot.use_beginning_of_line_as_screenshot)
|
852
867
|
# ttk.Checkbutton(screenshot_frame, variable=self.use_beginning_of_line_as_screenshot).grid(row=self.current_row, column=1, sticky='W')
|
@@ -883,19 +898,21 @@ class ConfigApp:
|
|
883
898
|
self.audio_extension.grid(row=self.current_row, column=1)
|
884
899
|
self.add_label_and_increment_row(audio_frame, "File extension for audio files.", row=self.current_row, column=2)
|
885
900
|
|
886
|
-
|
901
|
+
|
902
|
+
ttk.Label(audio_frame, text="Audio Extraction Beginning Offset:").grid(row=self.current_row, column=0, sticky='W')
|
887
903
|
self.beginning_offset = ttk.Entry(audio_frame)
|
888
904
|
self.beginning_offset.insert(0, str(self.settings.audio.beginning_offset))
|
889
905
|
self.beginning_offset.grid(row=self.current_row, column=1)
|
890
|
-
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",
|
891
907
|
row=self.current_row, column=2)
|
892
908
|
|
893
|
-
ttk.Label(audio_frame, text="End Offset:").grid(row=self.current_row, column=0, sticky='W')
|
894
|
-
self.
|
895
|
-
self.
|
896
|
-
self.
|
897
|
-
self.add_label_and_increment_row(audio_frame, "Offset in seconds to end
|
898
|
-
|
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
|
+
|
899
916
|
|
900
917
|
ttk.Label(audio_frame, text="FFmpeg Preset Options:").grid(row=self.current_row, column=0, sticky='W')
|
901
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
|
#
|
@@ -145,6 +145,7 @@ class Screenshot:
|
|
145
145
|
use_beginning_of_line_as_screenshot: bool = True
|
146
146
|
use_new_screenshot_logic: bool = False
|
147
147
|
screenshot_timing_setting: str = '' # 'middle', 'end'
|
148
|
+
use_screenshot_selector: bool = False
|
148
149
|
|
149
150
|
def __post_init__(self):
|
150
151
|
if not self.screenshot_timing_setting and self.use_beginning_of_line_as_screenshot:
|
@@ -163,6 +164,7 @@ class Audio:
|
|
163
164
|
extension: str = 'opus'
|
164
165
|
beginning_offset: float = 0.0
|
165
166
|
end_offset: float = 0.5
|
167
|
+
pre_vad_end_offset: float = 0.0
|
166
168
|
ffmpeg_reencode_options: str = '-c:a libopus -f opus -af \"afade=t=in:d=0.10\"' if is_windows() else ''
|
167
169
|
external_tool: str = ""
|
168
170
|
anki_media_collection: str = ""
|
@@ -629,11 +631,14 @@ console_handler.setFormatter(formatter)
|
|
629
631
|
logger.addHandler(console_handler)
|
630
632
|
|
631
633
|
# Create rotating file handler with level DEBUG
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
634
|
+
file_path = get_log_path()
|
635
|
+
if os.path.exists(file_path) and os.path.getsize(file_path) > 10 * 1024 * 1024:
|
636
|
+
shutil.move(os.path.join(file_path, "gamesentenceminer.log"), os.path.join(file_path, "gamesentenceminer_old.log"))
|
637
|
+
|
638
|
+
file_handler = logging.FileHandler(file_path, encoding='utf-8')
|
639
|
+
file_handler.setLevel(logging.DEBUG)
|
640
|
+
file_handler.setFormatter(formatter)
|
641
|
+
logger.addHandler(file_handler)
|
637
642
|
|
638
643
|
DB_PATH = os.path.join(get_app_directory(), 'gsm.db')
|
639
644
|
|
@@ -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,9 +32,21 @@ 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:
|
47
|
+
logger.info(f"Opened Anki browser with query: {query}")
|
48
|
+
else:
|
49
|
+
logger.info(f"Opened Anki note in browser with ID {note_id}")
|
38
50
|
else:
|
39
51
|
logger.error(f"Failed to open Anki note with ID {note_id}")
|
40
52
|
except Exception as e:
|
@@ -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.11
|
4
4
|
Summary: A tool for mining sentences from games.
|
5
5
|
Author-email: Beangate <bpwhelan95@gmail.com>
|
6
6
|
License: MIT License
|
@@ -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=3prvLmQ8LBw5jEYBIelPW_cdmxcpS375tYs4YY7FbSI,83544
|
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=mUhdjtuWJGWzgtcuM3b4zpqlM4m0f8Vl431AjfZfnaw,26070
|
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.11.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
63
|
+
gamesentenceminer-2.9.11.dist-info/METADATA,sha256=PIPovVreLcPokhDBGRaLjZBTxCbQBqk0lxMNjlx9YmU,7221
|
64
|
+
gamesentenceminer-2.9.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
65
|
+
gamesentenceminer-2.9.11.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
66
|
+
gamesentenceminer-2.9.11.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
67
|
+
gamesentenceminer-2.9.11.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|