GameSentenceMiner 2.17.6__py3-none-any.whl → 2.18.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- GameSentenceMiner/ai/ai_prompting.py +51 -51
- GameSentenceMiner/anki.py +236 -152
- GameSentenceMiner/gametext.py +7 -4
- GameSentenceMiner/gsm.py +49 -10
- GameSentenceMiner/locales/en_us.json +7 -3
- GameSentenceMiner/locales/ja_jp.json +8 -4
- GameSentenceMiner/locales/zh_cn.json +8 -4
- GameSentenceMiner/obs.py +238 -59
- GameSentenceMiner/ocr/owocr_helper.py +1 -1
- GameSentenceMiner/tools/ss_selector.py +7 -8
- GameSentenceMiner/ui/__init__.py +0 -0
- GameSentenceMiner/ui/anki_confirmation.py +187 -0
- GameSentenceMiner/{config_gui.py → ui/config_gui.py} +102 -37
- GameSentenceMiner/ui/screenshot_selector.py +215 -0
- GameSentenceMiner/util/configuration.py +124 -22
- GameSentenceMiner/util/db.py +22 -13
- GameSentenceMiner/util/downloader/download_tools.py +2 -2
- GameSentenceMiner/util/ffmpeg.py +24 -30
- GameSentenceMiner/util/get_overlay_coords.py +34 -34
- GameSentenceMiner/util/gsm_utils.py +31 -1
- GameSentenceMiner/util/text_log.py +11 -9
- GameSentenceMiner/vad.py +31 -12
- GameSentenceMiner/web/database_api.py +742 -123
- GameSentenceMiner/web/static/css/dashboard-shared.css +241 -0
- GameSentenceMiner/web/static/css/kanji-grid.css +94 -2
- GameSentenceMiner/web/static/css/overview.css +850 -0
- GameSentenceMiner/web/static/css/popups-shared.css +126 -0
- GameSentenceMiner/web/static/css/shared.css +97 -0
- GameSentenceMiner/web/static/css/stats.css +192 -597
- GameSentenceMiner/web/static/js/anki_stats.js +6 -4
- GameSentenceMiner/web/static/js/database.js +209 -5
- GameSentenceMiner/web/static/js/goals.js +610 -0
- GameSentenceMiner/web/static/js/kanji-grid.js +267 -4
- GameSentenceMiner/web/static/js/overview.js +1176 -0
- GameSentenceMiner/web/static/js/shared.js +25 -0
- GameSentenceMiner/web/static/js/stats.js +154 -1459
- GameSentenceMiner/web/stats.py +2 -2
- GameSentenceMiner/web/templates/anki_stats.html +5 -0
- GameSentenceMiner/web/templates/components/navigation.html +3 -1
- GameSentenceMiner/web/templates/database.html +73 -1
- GameSentenceMiner/web/templates/goals.html +376 -0
- GameSentenceMiner/web/templates/index.html +13 -11
- GameSentenceMiner/web/templates/overview.html +416 -0
- GameSentenceMiner/web/templates/stats.html +46 -251
- GameSentenceMiner/web/texthooking_page.py +18 -0
- {gamesentenceminer-2.17.6.dist-info → gamesentenceminer-2.18.0.dist-info}/METADATA +5 -1
- {gamesentenceminer-2.17.6.dist-info → gamesentenceminer-2.18.0.dist-info}/RECORD +51 -41
- {gamesentenceminer-2.17.6.dist-info → gamesentenceminer-2.18.0.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.17.6.dist-info → gamesentenceminer-2.18.0.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.17.6.dist-info → gamesentenceminer-2.18.0.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.17.6.dist-info → gamesentenceminer-2.18.0.dist-info}/top_level.txt +0 -0
GameSentenceMiner/gametext.py
CHANGED
|
@@ -9,10 +9,11 @@ import websockets
|
|
|
9
9
|
from websockets import InvalidStatus
|
|
10
10
|
from rapidfuzz import fuzz
|
|
11
11
|
|
|
12
|
-
from GameSentenceMiner.util.configuration import get_config, gsm_status, logger, gsm_state
|
|
12
|
+
from GameSentenceMiner.util.configuration import get_config, gsm_status, logger, gsm_state, is_dev
|
|
13
13
|
from GameSentenceMiner.util.db import GameLinesTable
|
|
14
14
|
from GameSentenceMiner.util.gsm_utils import do_text_replacements, TEXT_REPLACEMENTS_FILE, run_new_thread
|
|
15
15
|
from GameSentenceMiner import obs
|
|
16
|
+
from GameSentenceMiner.util.gsm_utils import add_srt_line
|
|
16
17
|
from GameSentenceMiner.util.text_log import add_line, get_text_log
|
|
17
18
|
from GameSentenceMiner.web.texthooking_page import add_event_to_texthooker, overlay_server_thread
|
|
18
19
|
|
|
@@ -87,7 +88,8 @@ async def listen_websockets():
|
|
|
87
88
|
message_received_time = datetime.now()
|
|
88
89
|
if not message:
|
|
89
90
|
continue
|
|
90
|
-
|
|
91
|
+
if is_dev:
|
|
92
|
+
logger.debug(message)
|
|
91
93
|
try:
|
|
92
94
|
data = json.loads(message)
|
|
93
95
|
if "sentence" in data:
|
|
@@ -183,6 +185,7 @@ async def handle_new_text_event(current_clipboard, line_time=None):
|
|
|
183
185
|
|
|
184
186
|
async def add_line_to_text_log(line, line_time=None):
|
|
185
187
|
global overlay_processor
|
|
188
|
+
|
|
186
189
|
if get_config().general.texthook_replacement_regex:
|
|
187
190
|
current_line_after_regex = re.sub(get_config().general.texthook_replacement_regex, '', line)
|
|
188
191
|
else:
|
|
@@ -191,7 +194,7 @@ async def add_line_to_text_log(line, line_time=None):
|
|
|
191
194
|
logger.info(f"Line Received: {current_line_after_regex}")
|
|
192
195
|
current_line_time = line_time if line_time else datetime.now()
|
|
193
196
|
gsm_status.last_line_received = current_line_time.strftime("%Y-%m-%d %H:%M:%S")
|
|
194
|
-
add_line(current_line_after_regex, line_time if line_time else datetime.now())
|
|
197
|
+
new_line = add_line(current_line_after_regex, line_time if line_time else datetime.now())
|
|
195
198
|
if len(get_text_log().values) > 0:
|
|
196
199
|
await add_event_to_texthooker(get_text_log()[-1])
|
|
197
200
|
if get_config().overlay.websocket_port and overlay_server_thread.has_clients():
|
|
@@ -199,9 +202,9 @@ async def add_line_to_text_log(line, line_time=None):
|
|
|
199
202
|
overlay_processor = OverlayProcessor()
|
|
200
203
|
if overlay_processor.ready:
|
|
201
204
|
await overlay_processor.find_box_and_send_to_overlay(current_line_after_regex)
|
|
205
|
+
add_srt_line(line_time, new_line)
|
|
202
206
|
GameLinesTable.add_line(get_text_log()[-1])
|
|
203
207
|
|
|
204
|
-
|
|
205
208
|
def reset_line_hotkey_pressed():
|
|
206
209
|
global current_line_time
|
|
207
210
|
logger.info("LINE RESET HOTKEY PRESSED")
|
GameSentenceMiner/gsm.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# There should be no imports here, as any error will crash the program.
|
|
2
|
+
# All imports should be done in the try/except block below.
|
|
3
|
+
|
|
4
|
+
|
|
1
5
|
def handle_error_in_initialization(e):
|
|
2
6
|
"""Handle errors that occur during initialization."""
|
|
3
7
|
logger.exception(e, exc_info=True)
|
|
@@ -24,6 +28,7 @@ try:
|
|
|
24
28
|
import requests
|
|
25
29
|
import os.path
|
|
26
30
|
import signal
|
|
31
|
+
import datetime
|
|
27
32
|
from subprocess import Popen
|
|
28
33
|
os.environ.pop('TCL_LIBRARY', None)
|
|
29
34
|
|
|
@@ -36,13 +41,17 @@ try:
|
|
|
36
41
|
import psutil
|
|
37
42
|
|
|
38
43
|
start_time = time.time()
|
|
44
|
+
import GameSentenceMiner.util.configuration
|
|
39
45
|
from GameSentenceMiner.util.configuration import logger, gsm_state, get_config, anki_results, AnkiUpdateResult, \
|
|
40
46
|
get_temporary_directory, get_log_path, get_master_config, switch_profile_and_save, get_app_directory, gsm_status, \
|
|
41
|
-
is_windows, is_linux
|
|
42
|
-
from GameSentenceMiner.util.get_overlay_coords import OverlayThread
|
|
43
|
-
from GameSentenceMiner.util.gsm_utils import remove_html_and_cloze_tags
|
|
47
|
+
is_windows, is_linux, get_ffmpeg_path
|
|
44
48
|
|
|
45
49
|
logger.debug(f"[Import] configuration: {time.time() - start_time:.3f}s")
|
|
50
|
+
|
|
51
|
+
start_time = time.time()
|
|
52
|
+
from GameSentenceMiner.util.get_overlay_coords import OverlayThread
|
|
53
|
+
from GameSentenceMiner.util.gsm_utils import remove_html_and_cloze_tags, add_srt_line
|
|
54
|
+
logger.debug(f"[Import] get_overlay_coords (OverlayThread, remove_html_and_cloze_tags): {time.time() - start_time:.3f}s")
|
|
46
55
|
|
|
47
56
|
start_time = time.time()
|
|
48
57
|
from GameSentenceMiner.util.model import VADResult
|
|
@@ -72,7 +81,7 @@ try:
|
|
|
72
81
|
logger.debug(f"[Import] anki: {time.time() - start_time:.3f}s")
|
|
73
82
|
|
|
74
83
|
start_time = time.time()
|
|
75
|
-
from GameSentenceMiner import config_gui
|
|
84
|
+
from GameSentenceMiner.ui import config_gui
|
|
76
85
|
logger.debug(f"[Import] config_gui: {time.time() - start_time:.3f}s")
|
|
77
86
|
|
|
78
87
|
start_time = time.time()
|
|
@@ -98,7 +107,7 @@ try:
|
|
|
98
107
|
f"[Import] websocket (connect_websocket, register_websocket_message_handler, FunctionName): {time.time() - start_time:.3f}s")
|
|
99
108
|
|
|
100
109
|
start_time = time.time()
|
|
101
|
-
from GameSentenceMiner.util.ffmpeg import get_audio_and_trim, get_video_timings
|
|
110
|
+
from GameSentenceMiner.util.ffmpeg import get_audio_and_trim, get_video_timings
|
|
102
111
|
logger.debug(
|
|
103
112
|
f"[Import] util.ffmpeg (get_audio_and_trim, get_video_timings, get_ffmpeg_path): {time.time() - start_time:.3f}s")
|
|
104
113
|
|
|
@@ -145,15 +154,28 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
|
145
154
|
super().__init__()
|
|
146
155
|
|
|
147
156
|
def on_created(self, event):
|
|
148
|
-
|
|
157
|
+
file_name = os.path.basename(event.src_path)
|
|
158
|
+
if event.is_directory or ("Replay" not in file_name and "GSM" not in file_name):
|
|
159
|
+
# This shows up as soon as recording starts, so it's kinda hard to use...
|
|
160
|
+
# if get_config().features.generate_longplay and event.src_path.endswith(".mkv") or event.src_path.endswith(".mp4"):
|
|
161
|
+
# add_srt_line(datetime.datetime.now(), get_all_lines()[-1])
|
|
162
|
+
# logger.info(f"Recording {event.src_path} FOUND, RUNNING LOGIC")
|
|
163
|
+
# wait_for_stable_file(event.src_path)
|
|
164
|
+
# current_srt = gsm_state.current_srt
|
|
165
|
+
# srt_name = os.path.splitext(os.path.basename(event.src_path))[0] + ".srt"
|
|
166
|
+
# srt_path = os.path.join(os.path.dirname(event.src_path), srt_name)
|
|
167
|
+
# shutil.move(current_srt, srt_path)
|
|
168
|
+
# gsm_state.current_srt = None
|
|
169
|
+
# # self.process_replay(event.src_path)
|
|
149
170
|
return
|
|
150
171
|
# Adjust based on your OBS output format
|
|
151
|
-
if
|
|
172
|
+
if file_name.endswith(".mkv") or file_name.endswith(".mp4"):
|
|
152
173
|
logger.info(f"MKV {event.src_path} FOUND, RUNNING LOGIC")
|
|
153
174
|
wait_for_stable_file(event.src_path)
|
|
154
175
|
self.process_replay(event.src_path)
|
|
155
176
|
|
|
156
177
|
def process_replay(self, video_path):
|
|
178
|
+
gsm_state.current_replay = video_path
|
|
157
179
|
vad_trimmed_audio = ''
|
|
158
180
|
final_audio_output = ''
|
|
159
181
|
skip_delete = False
|
|
@@ -194,6 +216,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
|
194
216
|
# Get Info of line mined
|
|
195
217
|
line_cutoff = None
|
|
196
218
|
start_line = None
|
|
219
|
+
full_text = ''
|
|
197
220
|
if selected_lines:
|
|
198
221
|
start_line = selected_lines[0]
|
|
199
222
|
mined_line = get_mined_line(last_note, selected_lines)
|
|
@@ -300,7 +323,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
|
300
323
|
f"{obs.get_current_game(sanitize=True)}.{get_config().audio.extension}"))
|
|
301
324
|
|
|
302
325
|
vad_result = vad_processor.trim_audio_with_vad(
|
|
303
|
-
trimmed_audio, vad_trimmed_audio, game_line)
|
|
326
|
+
trimmed_audio, vad_trimmed_audio, game_line, full_text)
|
|
304
327
|
if timing_only:
|
|
305
328
|
return vad_result
|
|
306
329
|
|
|
@@ -337,7 +360,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
|
337
360
|
|
|
338
361
|
def initial_checks():
|
|
339
362
|
try:
|
|
340
|
-
subprocess.run(
|
|
363
|
+
subprocess.run(GameSentenceMiner.util.configuration.ffmpeg_base_command_list)
|
|
341
364
|
logger.debug("FFMPEG is installed and accessible.")
|
|
342
365
|
except FileNotFoundError:
|
|
343
366
|
logger.error(
|
|
@@ -465,6 +488,13 @@ class GSMTray(threading.Thread):
|
|
|
465
488
|
self.run_tray()
|
|
466
489
|
|
|
467
490
|
def run_tray(self):
|
|
491
|
+
def run_anki_confirmation_window():
|
|
492
|
+
settings_window.show_anki_confirmation_dialog(expression="こんにちは",
|
|
493
|
+
sentence="こんにちは、世界!元気ですか?",
|
|
494
|
+
screenshot_path="test_image.png",
|
|
495
|
+
audio_path="C:/path/to/my/audio.mp3",
|
|
496
|
+
translation="Hello world! How are you?")
|
|
497
|
+
|
|
468
498
|
self.profile_menu = Menu(
|
|
469
499
|
*[MenuItem(("Active: " if profile == get_master_config().current_profile else "") + profile, self.switch_profile) for
|
|
470
500
|
profile in
|
|
@@ -478,9 +508,10 @@ class GSMTray(threading.Thread):
|
|
|
478
508
|
MenuItem("Toggle Replay Buffer", self.play_pause),
|
|
479
509
|
MenuItem("Restart OBS", restart_obs),
|
|
480
510
|
MenuItem("Switch Profile", self.profile_menu),
|
|
511
|
+
MenuItem("Run Test Code", run_anki_confirmation_window),
|
|
481
512
|
MenuItem("Exit", exit_program)
|
|
482
513
|
)
|
|
483
|
-
|
|
514
|
+
|
|
484
515
|
self.icon = Icon("TrayApp", create_image(), "GameSentenceMiner", menu)
|
|
485
516
|
self.icon.run()
|
|
486
517
|
|
|
@@ -574,6 +605,8 @@ def restart_obs():
|
|
|
574
605
|
|
|
575
606
|
def cleanup():
|
|
576
607
|
try:
|
|
608
|
+
if gsm_state.current_srt and len(get_all_lines()) > 0:
|
|
609
|
+
add_srt_line(datetime.datetime.now(), get_all_lines()[-1])
|
|
577
610
|
logger.info("Performing cleanup...")
|
|
578
611
|
gsm_state.keep_running = False
|
|
579
612
|
|
|
@@ -794,6 +827,7 @@ async def async_main(reloading=False):
|
|
|
794
827
|
root = ttk.Window(themename='darkly')
|
|
795
828
|
start_time = time.time()
|
|
796
829
|
settings_window = config_gui.ConfigApp(root)
|
|
830
|
+
gsm_state.config_app = settings_window
|
|
797
831
|
initialize_async()
|
|
798
832
|
observer = Observer()
|
|
799
833
|
observer.schedule(VideoToAudioHandler(),
|
|
@@ -823,6 +857,11 @@ async def async_main(reloading=False):
|
|
|
823
857
|
if get_config().general.open_config_on_startup:
|
|
824
858
|
root.after(50, settings_window.show)
|
|
825
859
|
root.after(50, gsm_tray.start)
|
|
860
|
+
# root.after(100, settings_window.show_anki_confirmation_dialog(expression="こんにちは",
|
|
861
|
+
# sentence="こんにちは、世界!元気ですか?",
|
|
862
|
+
# screenshot_path="test_image.png",
|
|
863
|
+
# audio_path="C:/path/to/my/audio.mp3",
|
|
864
|
+
# translation="Hello world! How are you?"))
|
|
826
865
|
settings_window.add_save_hook(gsm_tray.update_icon)
|
|
827
866
|
settings_window.on_exit = exit_program
|
|
828
867
|
root.mainloop()
|
|
@@ -154,6 +154,10 @@
|
|
|
154
154
|
"label": "Update Anki:",
|
|
155
155
|
"tooltip": "Automatically update Anki with new data."
|
|
156
156
|
},
|
|
157
|
+
"show_update_confirmation_dialog": {
|
|
158
|
+
"label": "Show Update Confirmation Dialog:",
|
|
159
|
+
"tooltip": "Show a confirmation dialog before updating Anki cards."
|
|
160
|
+
},
|
|
157
161
|
"url": {
|
|
158
162
|
"label": "Anki URL:",
|
|
159
163
|
"tooltip": "The URL to connect to your Anki instance."
|
|
@@ -440,9 +444,9 @@
|
|
|
440
444
|
"label": "Minimum Replay Size (KB):",
|
|
441
445
|
"tooltip": "Minimum Replay Size for OBS Replays in KB. If Replay is Under this, Audio/Screenshot Will not be grabbed."
|
|
442
446
|
},
|
|
443
|
-
"
|
|
444
|
-
"label": "Turn
|
|
445
|
-
"tooltip": "Disable
|
|
447
|
+
"turn_off_replay_buffer_management": {
|
|
448
|
+
"label": "Turn off Replay Buffer Management:",
|
|
449
|
+
"tooltip": "Disable OBS Replay Buffer Management. This is useful if you want to manually manage the replay buffer in OBS."
|
|
446
450
|
}
|
|
447
451
|
},
|
|
448
452
|
"profiles": {
|
|
@@ -153,6 +153,10 @@
|
|
|
153
153
|
"label": "Ankiを更新:",
|
|
154
154
|
"tooltip": "自動的にAnkiを更新します。"
|
|
155
155
|
},
|
|
156
|
+
"show_update_confirmation_dialog": {
|
|
157
|
+
"label": "更新確認ダイアログを表示:",
|
|
158
|
+
"tooltip": "Ankiカードを更新する前に確認ダイアログを表示します。"
|
|
159
|
+
},
|
|
156
160
|
"url": {
|
|
157
161
|
"label": "Anki URL:",
|
|
158
162
|
"tooltip": "AnkiConnectの接続URL。"
|
|
@@ -439,10 +443,10 @@
|
|
|
439
443
|
"label": "最小リプレイサイズ (KB):",
|
|
440
444
|
"tooltip": "このサイズ未満のリプレイは処理しません。"
|
|
441
445
|
},
|
|
442
|
-
|
|
443
|
-
"label": "
|
|
444
|
-
"tooltip": "
|
|
445
|
-
|
|
446
|
+
"turn_off_replay_buffer_management": {
|
|
447
|
+
"label": "リプレイバッファ管理を無効化:",
|
|
448
|
+
"tooltip": "OBSリプレイバッファの管理を無効化します。OBSでリプレイバッファを手動で管理したい場合に便利です。"
|
|
449
|
+
}
|
|
446
450
|
},
|
|
447
451
|
"profiles": {
|
|
448
452
|
"title": "プロファイル",
|
|
@@ -154,6 +154,10 @@
|
|
|
154
154
|
"label": "更新 Anki:",
|
|
155
155
|
"tooltip": "使用新数据自动更新 Anki。"
|
|
156
156
|
},
|
|
157
|
+
"show_update_confirmation_dialog": {
|
|
158
|
+
"label": "显示更新确认对话框:",
|
|
159
|
+
"tooltip": "在更新 Anki 卡片之前显示确认对话框。"
|
|
160
|
+
},
|
|
157
161
|
"url": {
|
|
158
162
|
"label": "Anki URL:",
|
|
159
163
|
"tooltip": "连接到您的 Anki 实例的 URL。"
|
|
@@ -440,10 +444,10 @@
|
|
|
440
444
|
"label": "最小回放大小 (KB):",
|
|
441
445
|
"tooltip": "OBS 录像回放的最小大小(KB)。如果回放小于此大小,将不抓取音频/截图。"
|
|
442
446
|
},
|
|
443
|
-
|
|
444
|
-
"label": "
|
|
445
|
-
"tooltip": "
|
|
446
|
-
|
|
447
|
+
"turn_off_replay_buffer_management": {
|
|
448
|
+
"label": "关闭回放缓冲区管理:",
|
|
449
|
+
"tooltip": "禁用 OBS 回放缓冲区管理。如果您想在 OBS 中手动管理回放缓冲区,这将很有用。"
|
|
450
|
+
}
|
|
447
451
|
},
|
|
448
452
|
"profiles": {
|
|
449
453
|
"title": "配置文件",
|