GameSentenceMiner 2.13.7__tar.gz → 2.13.8__tar.gz
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-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/anki.py +50 -9
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/config_gui.py +59 -75
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/gametext.py +1 -2
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/gsm.py +81 -70
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/locales/en_us.json +22 -14
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/locales/ja_jp.json +14 -6
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/locales/zh_cn.json +14 -6
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/obs.py +0 -6
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/configuration.py +24 -19
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/ffmpeg.py +59 -6
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/gsm_utils.py +8 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/web/texthooking_page.py +6 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner.egg-info/PKG-INFO +1 -1
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/PKG-INFO +1 -1
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/pyproject.toml +1 -1
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/__init__.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/ai/__init__.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/ai/ai_prompting.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/assets/__init__.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/assets/icon.png +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/assets/icon128.png +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/assets/icon256.png +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/assets/icon32.png +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/assets/icon512.png +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/assets/icon64.png +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/assets/pickaxe.png +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/ocr/__init__.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/ocr/gsm_ocr_config.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/ocr/ocrconfig.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/ocr/owocr_area_selector.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/ocr/owocr_helper.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/ocr/ss_picker.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/owocr/owocr/__init__.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/owocr/owocr/__main__.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/owocr/owocr/config.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/owocr/owocr/lens_betterproto.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/owocr/owocr/ocr.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/owocr/owocr/run.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/__init__.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/audio_offset_selector.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/communication/__init__.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/communication/send.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/communication/websocket.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/downloader/Untitled_json.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/downloader/__init__.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/downloader/download_tools.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/downloader/oneocr_dl.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/electron_config.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/model.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/notification.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/package.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/ss_selector.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/text_log.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/util/window_transparency.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/vad.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/web/__init__.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/web/service.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/web/static/__init__.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/web/static/apple-touch-icon.png +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/web/static/favicon-96x96.png +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/web/static/favicon.ico +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/web/static/favicon.svg +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/web/static/site.webmanifest +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/web/static/style.css +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/web/static/web-app-manifest-192x192.png +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/web/static/web-app-manifest-512x512.png +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/web/templates/__init__.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/web/templates/index.html +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/web/templates/text_replacements.html +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/web/templates/utility.html +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner/wip/get_overlay_coords.py +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner.egg-info/SOURCES.txt +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner.egg-info/dependency_links.txt +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner.egg-info/entry_points.txt +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner.egg-info/requires.txt +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/GameSentenceMiner.egg-info/top_level.txt +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/LICENSE +0 -0
- {gamesentenceminer-2.13.7 → gamesentenceminer-2.13.8}/setup.cfg +0 -0
@@ -1,4 +1,5 @@
|
|
1
1
|
import copy
|
2
|
+
from pathlib import Path
|
2
3
|
import queue
|
3
4
|
import time
|
4
5
|
|
@@ -10,7 +11,7 @@ from requests import post
|
|
10
11
|
|
11
12
|
from GameSentenceMiner import obs
|
12
13
|
from GameSentenceMiner.ai.ai_prompting import get_ai_prompt_result
|
13
|
-
from GameSentenceMiner.util.gsm_utils import wait_for_stable_file, remove_html_and_cloze_tags, combine_dialogue, \
|
14
|
+
from GameSentenceMiner.util.gsm_utils import make_unique, sanitize_filename, wait_for_stable_file, remove_html_and_cloze_tags, combine_dialogue, \
|
14
15
|
run_new_thread, open_audio_in_external
|
15
16
|
from GameSentenceMiner.util import ffmpeg, notification
|
16
17
|
from GameSentenceMiner.util.configuration import *
|
@@ -20,6 +21,8 @@ from GameSentenceMiner.util.text_log import get_all_lines, get_text_event, get_m
|
|
20
21
|
from GameSentenceMiner.obs import get_current_game
|
21
22
|
from GameSentenceMiner.web import texthooking_page
|
22
23
|
import re
|
24
|
+
import platform
|
25
|
+
import sys
|
23
26
|
|
24
27
|
# Global variables to track state
|
25
28
|
previous_note_ids = set()
|
@@ -28,7 +31,7 @@ card_queue = []
|
|
28
31
|
|
29
32
|
|
30
33
|
def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='', tango='', reuse_audio=False,
|
31
|
-
should_update_audio=True, ss_time=0, game_line=None, selected_lines=None, prev_ss_timing=0):
|
34
|
+
should_update_audio=True, ss_time=0, game_line=None, selected_lines=None, prev_ss_timing=0, start_time=None, end_time=None):
|
32
35
|
update_audio = should_update_audio and (get_config().anki.sentence_audio_field and not
|
33
36
|
last_note.get_field(get_config().anki.sentence_audio_field) or get_config().anki.overwrite_audio)
|
34
37
|
update_picture = (get_config().anki.picture_field and get_config().screenshot.enabled
|
@@ -37,6 +40,8 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
37
40
|
audio_in_anki = ''
|
38
41
|
screenshot_in_anki = ''
|
39
42
|
prev_screenshot_in_anki = ''
|
43
|
+
screenshot = ''
|
44
|
+
prev_screenshot = ''
|
40
45
|
if reuse_audio:
|
41
46
|
logger.info("Reusing Audio from last note")
|
42
47
|
anki_result = anki_results[game_line.id]
|
@@ -53,8 +58,6 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
53
58
|
screenshot = ffmpeg.get_screenshot(video_path, ss_time, try_selector=get_config().screenshot.use_screenshot_selector)
|
54
59
|
wait_for_stable_file(screenshot)
|
55
60
|
screenshot_in_anki = store_media_file(screenshot)
|
56
|
-
if get_config().paths.remove_screenshot:
|
57
|
-
os.remove(screenshot)
|
58
61
|
if get_config().anki.previous_image_field and game_line.prev:
|
59
62
|
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)
|
60
63
|
wait_for_stable_file(prev_screenshot)
|
@@ -64,6 +67,46 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
64
67
|
audio_html = f"[sound:{audio_in_anki}]"
|
65
68
|
image_html = f"<img src=\"{screenshot_in_anki}\">"
|
66
69
|
prev_screenshot_html = f"<img src=\"{prev_screenshot_in_anki}\">"
|
70
|
+
|
71
|
+
|
72
|
+
# Move files to output folder if configured
|
73
|
+
if get_config().paths.output_folder and get_config().paths.copy_temp_files_to_output_folder:
|
74
|
+
word_path = os.path.join(get_config().paths.output_folder, sanitize_filename(tango))
|
75
|
+
os.makedirs(word_path, exist_ok=True)
|
76
|
+
if audio_path:
|
77
|
+
audio_filename = Path(audio_path).name
|
78
|
+
new_audio_path = os.path.join(word_path, audio_filename)
|
79
|
+
if os.path.exists(audio_path):
|
80
|
+
shutil.copy(audio_path, new_audio_path)
|
81
|
+
if screenshot:
|
82
|
+
screenshot_filename = Path(screenshot).name
|
83
|
+
new_screenshot_path = os.path.join(word_path, screenshot_filename)
|
84
|
+
if os.path.exists(screenshot):
|
85
|
+
shutil.copy(screenshot, new_screenshot_path)
|
86
|
+
if prev_screenshot:
|
87
|
+
prev_screenshot_filename = Path(prev_screenshot).name
|
88
|
+
new_prev_screenshot_path = os.path.join(word_path, "prev_" + prev_screenshot_filename)
|
89
|
+
if os.path.exists(prev_screenshot):
|
90
|
+
shutil.copy(prev_screenshot, new_prev_screenshot_path)
|
91
|
+
|
92
|
+
if video_path and get_config().paths.copy_trimmed_replay_to_output_folder:
|
93
|
+
trimmed_video = ffmpeg.trim_replay_for_gameline(video_path, start_time, end_time, accurate=True)
|
94
|
+
new_video_path = os.path.join(word_path, Path(trimmed_video).name)
|
95
|
+
if os.path.exists(trimmed_video):
|
96
|
+
shutil.copy(trimmed_video, new_video_path)
|
97
|
+
|
98
|
+
# Open to word_path if configured
|
99
|
+
if get_config().paths.open_output_folder_on_card_creation:
|
100
|
+
try:
|
101
|
+
if platform.system() == "Windows":
|
102
|
+
subprocess.Popen(f'explorer "{word_path}"')
|
103
|
+
elif platform.system() == "Darwin":
|
104
|
+
subprocess.Popen(["open", word_path])
|
105
|
+
else:
|
106
|
+
subprocess.Popen(["xdg-open", word_path])
|
107
|
+
except Exception as e:
|
108
|
+
logger.error(f"Error opening output folder: {e}")
|
109
|
+
|
67
110
|
|
68
111
|
# note = {'id': last_note.noteId, 'fields': {}}
|
69
112
|
|
@@ -413,9 +456,7 @@ def get_note_ids():
|
|
413
456
|
|
414
457
|
|
415
458
|
def start_monitoring_anki():
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
obs_thread.daemon = True # Ensures the thread will exit when the main program exits
|
420
|
-
obs_thread.start()
|
459
|
+
obs_thread = threading.Thread(target=monitor_anki)
|
460
|
+
obs_thread.daemon = True
|
461
|
+
obs_thread.start()
|
421
462
|
|
@@ -143,7 +143,7 @@ class ConfigApp:
|
|
143
143
|
self.current_row = 0
|
144
144
|
|
145
145
|
self.master_config: Config = configuration.load_config()
|
146
|
-
self.i18n = load_localization(self.master_config.
|
146
|
+
self.i18n = load_localization(self.master_config.get_locale())
|
147
147
|
|
148
148
|
self.window.title(self.i18n.get('app', {}).get('title', 'GameSentenceMiner Configuration'))
|
149
149
|
|
@@ -212,7 +212,7 @@ class ConfigApp:
|
|
212
212
|
|
213
213
|
def change_locale(self):
|
214
214
|
"""Change the locale of the application."""
|
215
|
-
if self.locale_value.get() == self.master_config.
|
215
|
+
if self.locale_value.get() == self.master_config.get_locale().name:
|
216
216
|
return
|
217
217
|
self.i18n = load_localization(Locale[self.locale_value.get()])
|
218
218
|
self.save_settings()
|
@@ -245,7 +245,6 @@ class ConfigApp:
|
|
245
245
|
self.native_language_value = tk.StringVar(value=CommonLanguages.from_code(self.settings.general.native_language).name.replace('_', ' ').title())
|
246
246
|
|
247
247
|
# OBS Settings
|
248
|
-
self.obs_enabled_value = tk.BooleanVar(value=self.settings.obs.enabled)
|
249
248
|
self.obs_websocket_port_value = tk.StringVar(value=str(self.settings.obs.port))
|
250
249
|
self.obs_host_value = tk.StringVar(value=self.settings.obs.host)
|
251
250
|
self.obs_port_value = tk.StringVar(value=str(self.settings.obs.port))
|
@@ -257,8 +256,10 @@ class ConfigApp:
|
|
257
256
|
|
258
257
|
# Paths Settings
|
259
258
|
self.folder_to_watch_value = tk.StringVar(value=self.settings.paths.folder_to_watch)
|
260
|
-
self.
|
261
|
-
self.
|
259
|
+
self.output_folder_value = tk.StringVar(value=self.settings.paths.output_folder)
|
260
|
+
self.copy_temp_files_to_output_folder_value = tk.BooleanVar(value=self.settings.paths.copy_temp_files_to_output_folder)
|
261
|
+
self.open_output_folder_on_card_creation_value = tk.BooleanVar(value=self.settings.paths.open_output_folder_on_card_creation)
|
262
|
+
self.copy_trimmed_replay_to_output_folder_value = tk.BooleanVar(value=self.settings.paths.copy_trimmed_replay_to_output_folder)
|
262
263
|
self.remove_video_value = tk.BooleanVar(value=self.settings.paths.remove_video)
|
263
264
|
self.remove_audio_value = tk.BooleanVar(value=self.settings.paths.remove_audio)
|
264
265
|
self.remove_screenshot_value = tk.BooleanVar(value=self.settings.paths.remove_screenshot)
|
@@ -357,7 +358,7 @@ class ConfigApp:
|
|
357
358
|
|
358
359
|
# Master Config Settings
|
359
360
|
self.switch_to_default_if_not_found_value = tk.BooleanVar(value=self.master_config.switch_to_default_if_not_found)
|
360
|
-
self.locale_value = tk.StringVar(value=self.master_config.
|
361
|
+
self.locale_value = tk.StringVar(value=self.master_config.get_locale().name)
|
361
362
|
|
362
363
|
|
363
364
|
def create_tabs(self):
|
@@ -469,11 +470,11 @@ class ConfigApp:
|
|
469
470
|
),
|
470
471
|
paths=Paths(
|
471
472
|
folder_to_watch=self.folder_to_watch_value.get(),
|
472
|
-
|
473
|
-
|
473
|
+
output_folder=self.output_folder_value.get(),
|
474
|
+
open_output_folder_on_card_creation=self.open_output_folder_on_card_creation_value.get(),
|
474
475
|
remove_video=self.remove_video_value.get(),
|
475
|
-
|
476
|
-
|
476
|
+
copy_temp_files_to_output_folder=self.copy_temp_files_to_output_folder_value.get(),
|
477
|
+
copy_trimmed_replay_to_output_folder=self.copy_trimmed_replay_to_output_folder_value.get()
|
477
478
|
),
|
478
479
|
anki=Anki(
|
479
480
|
update_anki=self.update_anki_value.get(),
|
@@ -525,7 +526,6 @@ class ConfigApp:
|
|
525
526
|
pre_vad_end_offset=float(self.pre_vad_audio_offset_value.get()),
|
526
527
|
),
|
527
528
|
obs=OBS(
|
528
|
-
enabled=self.obs_enabled_value.get(),
|
529
529
|
open_obs=self.obs_open_obs_value.get(),
|
530
530
|
close_obs=self.obs_close_obs_value.get(),
|
531
531
|
host=self.obs_host_value.get(),
|
@@ -593,15 +593,6 @@ class ConfigApp:
|
|
593
593
|
dialog_i18n = self.i18n.get('dialogs', {}).get('config_error', {})
|
594
594
|
error_title = dialog_i18n.get('title', 'Configuration Error')
|
595
595
|
|
596
|
-
if config.features.backfill_audio and config.features.full_auto:
|
597
|
-
messagebox.showerror(error_title,
|
598
|
-
dialog_i18n.get('full_auto_and_backfill', 'Cannot have Full Auto and Backfill...'))
|
599
|
-
return
|
600
|
-
|
601
|
-
if not config.general.use_websocket and not config.general.use_clipboard:
|
602
|
-
messagebox.showerror(error_title, dialog_i18n.get('no_input_method', 'Cannot have both...'))
|
603
|
-
return
|
604
|
-
|
605
596
|
current_profile = self.profile_combobox.get()
|
606
597
|
prev_config = self.master_config.get_config()
|
607
598
|
self.master_config.switch_to_default_if_not_found = self.switch_to_default_if_not_found_value.get()
|
@@ -625,8 +616,7 @@ class ConfigApp:
|
|
625
616
|
if sync_changes:
|
626
617
|
self.master_config.sync_changed_fields(prev_config)
|
627
618
|
|
628
|
-
|
629
|
-
file.write(self.master_config.to_json(indent=4))
|
619
|
+
self.master_config.save()
|
630
620
|
|
631
621
|
logger.info("Settings saved successfully!")
|
632
622
|
|
@@ -934,13 +924,6 @@ class ConfigApp:
|
|
934
924
|
row=self.current_row, column=3, sticky='W', pady=2)
|
935
925
|
self.current_row += 1
|
936
926
|
|
937
|
-
# obs_enabled_i18n = simple_i18n.get('obs_enabled', {})
|
938
|
-
# HoverInfoLabelWidget(feature_frame, text=obs_enabled_i18n.get('label', '...'),
|
939
|
-
# tooltip=obs_enabled_i18n.get('tooltip', '...'), row=self.current_row, column=0)
|
940
|
-
# ttk.Checkbutton(feature_frame, variable=self.obs_enabled_value, bootstyle="round-toggle").grid(
|
941
|
-
# row=self.current_row, column=1, sticky='W', pady=2)
|
942
|
-
# self.current_row += 1
|
943
|
-
|
944
927
|
# screenshot_i18n = simple_i18n.get('screenshot_enabled', {})
|
945
928
|
# HoverInfoLabelWidget(feature_frame, text=screenshot_i18n.get('label', '...'),
|
946
929
|
# tooltip=screenshot_i18n.get('tooltip', '...'), row=self.current_row, column=0)
|
@@ -1094,46 +1077,61 @@ class ConfigApp:
|
|
1094
1077
|
ttk.Button(paths_frame, text=browse_text, command=lambda: self.browse_folder(folder_watch_entry),
|
1095
1078
|
bootstyle="outline").grid(row=self.current_row, column=2, padx=5, pady=2)
|
1096
1079
|
self.current_row += 1
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1080
|
+
|
1081
|
+
# Combine "Copy temp files to output folder" and "Output folder" on one row
|
1082
|
+
copy_to_output_i18n = paths_i18n.get('copy_temp_files_to_output_folder', {})
|
1083
|
+
combined_i18n = paths_i18n.get('output_folder', {})
|
1084
|
+
|
1085
|
+
# Output folder and "Copy temp files to output folder" on one row
|
1086
|
+
HoverInfoLabelWidget(paths_frame, text=combined_i18n.get('label', '...'),
|
1087
|
+
tooltip=combined_i18n.get('tooltip', '...'), foreground="dark orange",
|
1088
|
+
font=("Helvetica", 10, "bold"), row=self.current_row, column=0)
|
1089
|
+
output_folder_entry = ttk.Entry(paths_frame, width=30, textvariable=self.output_folder_value)
|
1090
|
+
output_folder_entry.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1091
|
+
ttk.Button(paths_frame, text=browse_text, command=lambda: self.browse_folder(output_folder_entry),
|
1092
|
+
bootstyle="outline").grid(row=self.current_row, column=2, padx=5, pady=2)
|
1093
|
+
|
1094
|
+
HoverInfoLabelWidget(paths_frame, text=copy_to_output_i18n.get('label', '...'),
|
1095
|
+
tooltip=copy_to_output_i18n.get('tooltip', '...'), row=self.current_row, column=3)
|
1096
|
+
ttk.Checkbutton(paths_frame, variable=self.copy_temp_files_to_output_folder_value, bootstyle="round-toggle").grid(
|
1097
|
+
row=self.current_row, column=4, sticky='W', pady=2)
|
1105
1098
|
self.current_row += 1
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
ttk.Button(paths_frame, text=browse_text, command=lambda: self.browse_folder(ss_dest_entry),
|
1114
|
-
bootstyle="outline").grid(row=self.current_row, column=2, padx=5, pady=2)
|
1099
|
+
|
1100
|
+
|
1101
|
+
copy_to_output_i18n = paths_i18n.get('copy_trimmed_replay_to_output_folder', {})
|
1102
|
+
HoverInfoLabelWidget(paths_frame, text=copy_to_output_i18n.get('label', '...'),
|
1103
|
+
tooltip=copy_to_output_i18n.get('tooltip', '...'), row=self.current_row, column=0)
|
1104
|
+
ttk.Checkbutton(paths_frame, variable=self.copy_trimmed_replay_to_output_folder_value, bootstyle="round-toggle").grid(
|
1105
|
+
row=self.current_row, column=1, sticky='W', pady=2)
|
1115
1106
|
self.current_row += 1
|
1116
1107
|
|
1117
|
-
|
1118
|
-
HoverInfoLabelWidget(paths_frame, text=
|
1119
|
-
row=self.current_row, column=0)
|
1120
|
-
ttk.Checkbutton(paths_frame, variable=self.
|
1108
|
+
open_output_folder_i18n = paths_i18n.get('open_output_folder_on_card_creation', {})
|
1109
|
+
HoverInfoLabelWidget(paths_frame, text=open_output_folder_i18n.get('label', '...'),
|
1110
|
+
tooltip=open_output_folder_i18n.get('tooltip', '...'), row=self.current_row, column=0)
|
1111
|
+
ttk.Checkbutton(paths_frame, variable=self.open_output_folder_on_card_creation_value, bootstyle="round-toggle").grid(row=self.current_row,
|
1121
1112
|
column=1, sticky='W', pady=2)
|
1122
1113
|
self.current_row += 1
|
1123
|
-
|
1124
|
-
|
1125
|
-
HoverInfoLabelWidget(paths_frame, text=
|
1114
|
+
|
1115
|
+
rm_video_i18n = paths_i18n.get('remove_video', {})
|
1116
|
+
HoverInfoLabelWidget(paths_frame, text=rm_video_i18n.get('label', '...'), tooltip=rm_video_i18n.get('tooltip', '...'),
|
1126
1117
|
row=self.current_row, column=0)
|
1127
|
-
ttk.Checkbutton(paths_frame, variable=self.
|
1128
|
-
|
1118
|
+
ttk.Checkbutton(paths_frame, variable=self.remove_video_value, bootstyle="round-toggle").grid(row=self.current_row,
|
1119
|
+
column=1, sticky='W', pady=2)
|
1129
1120
|
self.current_row += 1
|
1130
1121
|
|
1131
|
-
|
1132
|
-
HoverInfoLabelWidget(paths_frame, text=
|
1133
|
-
|
1134
|
-
ttk.Checkbutton(paths_frame, variable=self.
|
1135
|
-
|
1136
|
-
self.current_row += 1
|
1122
|
+
# rm_audio_i18n = paths_i18n.get('remove_audio', {})
|
1123
|
+
# HoverInfoLabelWidget(paths_frame, text=rm_audio_i18n.get('label', '...'), tooltip=rm_audio_i18n.get('tooltip', '...'),
|
1124
|
+
# row=self.current_row, column=0)
|
1125
|
+
# ttk.Checkbutton(paths_frame, variable=self.remove_audio_value, bootstyle="round-toggle").grid(row=self.current_row,
|
1126
|
+
# column=1, sticky='W', pady=2)
|
1127
|
+
# self.current_row += 1
|
1128
|
+
|
1129
|
+
# rm_ss_i18n = paths_i18n.get('remove_screenshot', {})
|
1130
|
+
# HoverInfoLabelWidget(paths_frame, text=rm_ss_i18n.get('label', '...'), tooltip=rm_ss_i18n.get('tooltip', '...'),
|
1131
|
+
# row=self.current_row, column=0)
|
1132
|
+
# ttk.Checkbutton(paths_frame, variable=self.remove_screenshot_value, bootstyle="round-toggle").grid(
|
1133
|
+
# row=self.current_row, column=1, sticky='W', pady=2)
|
1134
|
+
# self.current_row += 1
|
1137
1135
|
|
1138
1136
|
self.add_reset_button(paths_frame, "paths", self.current_row, 0, self.create_paths_tab)
|
1139
1137
|
|
@@ -1335,13 +1333,6 @@ class ConfigApp:
|
|
1335
1333
|
row=self.current_row, column=1, sticky='W', pady=2)
|
1336
1334
|
self.current_row += 1
|
1337
1335
|
|
1338
|
-
full_auto_i18n = features_i18n.get('full_auto', {})
|
1339
|
-
HoverInfoLabelWidget(features_frame, text=full_auto_i18n.get('label', '...'), tooltip=full_auto_i18n.get('tooltip', '...'),
|
1340
|
-
row=self.current_row, column=0)
|
1341
|
-
ttk.Checkbutton(features_frame, variable=self.full_auto_value, bootstyle="round-toggle").grid(row=self.current_row,
|
1342
|
-
column=1, sticky='W', pady=2)
|
1343
|
-
self.current_row += 1
|
1344
|
-
|
1345
1336
|
self.add_reset_button(features_frame, "features", self.current_row, 0, self.create_features_tab)
|
1346
1337
|
|
1347
1338
|
for col in range(3): features_frame.grid_columnconfigure(col, weight=0)
|
@@ -1607,13 +1598,6 @@ class ConfigApp:
|
|
1607
1598
|
obs_frame = self.obs_tab
|
1608
1599
|
obs_i18n = self.i18n.get('tabs', {}).get('obs', {})
|
1609
1600
|
|
1610
|
-
enabled_i18n = obs_i18n.get('enabled', {})
|
1611
|
-
HoverInfoLabelWidget(obs_frame, text=enabled_i18n.get('label', '...'), tooltip=enabled_i18n.get('tooltip', '...'),
|
1612
|
-
row=self.current_row, column=0)
|
1613
|
-
ttk.Checkbutton(obs_frame, variable=self.obs_enabled_value, bootstyle="round-toggle").grid(row=self.current_row,
|
1614
|
-
column=1, sticky='W', pady=2)
|
1615
|
-
self.current_row += 1
|
1616
|
-
|
1617
1601
|
open_i18n = obs_i18n.get('open_obs', {})
|
1618
1602
|
HoverInfoLabelWidget(obs_frame, text=open_i18n.get('label', '...'), tooltip=open_i18n.get('tooltip', '...'), row=self.current_row,
|
1619
1603
|
column=0)
|
@@ -149,10 +149,9 @@ async def handle_new_text_event(current_clipboard, line_time=None):
|
|
149
149
|
global current_line, current_line_time, current_line_after_regex, timer, current_sequence_start_time, last_raw_clipboard
|
150
150
|
obs.update_current_game()
|
151
151
|
current_line = current_clipboard
|
152
|
-
logger.info(f"Current Line: {current_line} last raw clipboard: {last_raw_clipboard}")
|
153
152
|
# Only apply this logic if merging is enabled
|
154
153
|
if get_config().general.merge_matching_sequential_text:
|
155
|
-
logger.info(f"
|
154
|
+
logger.info(f"Current Line: {current_line} last raw clipboard: {last_raw_clipboard}")
|
156
155
|
# If no timer is active, this is the start of a new sequence
|
157
156
|
if not timer:
|
158
157
|
logger.info("Starting a new sequence of text lines.")
|
@@ -9,6 +9,18 @@ import warnings
|
|
9
9
|
os.environ.pop('TCL_LIBRARY', None)
|
10
10
|
|
11
11
|
|
12
|
+
def handle_error_in_initialization(e):
|
13
|
+
"""Handle errors that occur during initialization."""
|
14
|
+
logger.exception(e, exc_info=True)
|
15
|
+
logger.info(
|
16
|
+
"An error occurred during initialization, Maybe try updating GSM from the menu or if running manually, try installing `pip install --update GameSentenceMiner`")
|
17
|
+
try:
|
18
|
+
while True:
|
19
|
+
time.sleep(1)
|
20
|
+
except KeyboardInterrupt:
|
21
|
+
logger.info("Exiting due to initialization error.")
|
22
|
+
sys.exit(1)
|
23
|
+
|
12
24
|
try:
|
13
25
|
import os.path
|
14
26
|
import signal
|
@@ -45,11 +57,7 @@ try:
|
|
45
57
|
from GameSentenceMiner.web.texthooking_page import run_text_hooker_page
|
46
58
|
except Exception as e:
|
47
59
|
from GameSentenceMiner.util.configuration import logger, is_linux, is_windows
|
48
|
-
|
49
|
-
"Something bad happened during import/initialization, closing in 5 seconds")
|
50
|
-
logger.exception(e)
|
51
|
-
time.sleep(5)
|
52
|
-
sys.exit(1)
|
60
|
+
handle_error_in_initialization(e)
|
53
61
|
|
54
62
|
if is_windows():
|
55
63
|
import win32api
|
@@ -143,7 +151,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
143
151
|
|
144
152
|
if get_config().anki.sentence_audio_field and get_config().audio.enabled:
|
145
153
|
logger.debug("Attempting to get audio from video")
|
146
|
-
final_audio_output, vad_result, vad_trimmed_audio = VideoToAudioHandler.get_audio(
|
154
|
+
final_audio_output, vad_result, vad_trimmed_audio, start_time, end_time = VideoToAudioHandler.get_audio(
|
147
155
|
start_line,
|
148
156
|
line_cutoff,
|
149
157
|
video_path,
|
@@ -178,7 +186,9 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
178
186
|
should_update_audio=vad_result.output_audio,
|
179
187
|
ss_time=ss_timing,
|
180
188
|
game_line=mined_line,
|
181
|
-
selected_lines=selected_lines
|
189
|
+
selected_lines=selected_lines,
|
190
|
+
start_time=start_time,
|
191
|
+
end_time=end_time
|
182
192
|
)
|
183
193
|
elif get_config().features.notify_on_update and vad_result.success:
|
184
194
|
notification.send_audio_generated_notification(
|
@@ -192,23 +202,25 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
192
202
|
f"Some error was hit catching to allow further work to be done: {e}", exc_info=True)
|
193
203
|
notification.send_error_no_anki_update()
|
194
204
|
finally:
|
195
|
-
if not skip_delete:
|
196
|
-
|
197
|
-
os.
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
205
|
+
if get_config().paths.remove_video and video_path and not skip_delete:
|
206
|
+
try:
|
207
|
+
if os.path.exists(video_path):
|
208
|
+
logger.debug(f"Removing video: {video_path}")
|
209
|
+
os.remove(video_path)
|
210
|
+
except Exception as e:
|
211
|
+
logger.error(
|
212
|
+
f"Error removing video file {video_path}: {e}", exc_info=True)
|
213
|
+
pass
|
202
214
|
|
203
215
|
@staticmethod
|
204
216
|
def get_audio(game_line, next_line_time, video_path, anki_card_creation_time=None, temporary=False, timing_only=False, mined_line=None):
|
205
|
-
trimmed_audio = get_audio_and_trim(
|
217
|
+
trimmed_audio, start_time, end_time = get_audio_and_trim(
|
206
218
|
video_path, game_line, next_line_time, anki_card_creation_time)
|
207
219
|
if temporary:
|
208
220
|
return ffmpeg.convert_audio_to_wav_lossless(trimmed_audio)
|
209
221
|
vad_trimmed_audio = make_unique_file_name(
|
210
222
|
f"{os.path.abspath(configuration.get_temporary_directory())}/{obs.get_current_game(sanitize=True)}.{get_config().audio.extension}")
|
211
|
-
final_audio_output = make_unique_file_name(os.path.join(
|
223
|
+
final_audio_output = make_unique_file_name(os.path.join(get_temporary_directory(),
|
212
224
|
f"{obs.get_current_game(sanitize=True)}.{get_config().audio.extension}"))
|
213
225
|
|
214
226
|
vad_result = vad_processor.trim_audio_with_vad(
|
@@ -222,7 +234,7 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
222
234
|
get_config().audio.ffmpeg_reencode_options_to_use)
|
223
235
|
elif os.path.exists(vad_trimmed_audio):
|
224
236
|
shutil.move(vad_trimmed_audio, final_audio_output)
|
225
|
-
return final_audio_output, vad_result, vad_trimmed_audio
|
237
|
+
return final_audio_output, vad_result, vad_trimmed_audio, start_time, end_time
|
226
238
|
|
227
239
|
|
228
240
|
def initial_checks():
|
@@ -451,41 +463,52 @@ def restart_obs():
|
|
451
463
|
|
452
464
|
|
453
465
|
def cleanup():
|
454
|
-
|
455
|
-
|
466
|
+
try:
|
467
|
+
logger.info("Performing cleanup...")
|
468
|
+
gsm_state.keep_running = False
|
456
469
|
|
457
|
-
|
470
|
+
if obs.obs_connection_manager and obs.obs_connection_manager.is_alive():
|
471
|
+
obs.obs_connection_manager.stop()
|
458
472
|
obs.stop_replay_buffer()
|
459
473
|
obs.disconnect_from_obs()
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
474
|
+
if get_config().obs.close_obs:
|
475
|
+
close_obs()
|
476
|
+
|
477
|
+
if texthooking_page.websocket_server_threads:
|
478
|
+
for thread in texthooking_page.websocket_server_threads:
|
479
|
+
if thread and isinstance(thread, threading.Thread) and thread.is_alive():
|
480
|
+
thread.stop_server()
|
481
|
+
thread.join()
|
482
|
+
|
483
|
+
proc: Popen
|
484
|
+
for proc in procs_to_close:
|
485
|
+
try:
|
486
|
+
logger.info(f"Terminating process {proc.args[0]}")
|
487
|
+
proc.terminate()
|
488
|
+
proc.wait()
|
489
|
+
logger.info(f"Process {proc.args[0]} terminated.")
|
490
|
+
except psutil.NoSuchProcess:
|
491
|
+
logger.info("PID already closed.")
|
492
|
+
except Exception as e:
|
493
|
+
proc.kill()
|
494
|
+
logger.error(f"Error terminating process {proc}: {e}")
|
495
|
+
|
496
|
+
if icon:
|
497
|
+
icon.stop()
|
498
|
+
|
499
|
+
for video in gsm_state.videos_to_remove:
|
500
|
+
try:
|
501
|
+
if os.path.exists(video):
|
502
|
+
os.remove(video)
|
503
|
+
except Exception as e:
|
504
|
+
logger.error(f"Error removing temporary video file {video}: {e}")
|
505
|
+
|
506
|
+
settings_window.window.destroy()
|
507
|
+
# time.sleep(5)
|
508
|
+
logger.info("Cleanup complete.")
|
509
|
+
except Exception as e:
|
510
|
+
logger.error(f"Error during cleanup: {e}", exc_info=True)
|
511
|
+
sys.exit(1)
|
489
512
|
|
490
513
|
|
491
514
|
def handle_exit():
|
@@ -508,15 +531,13 @@ def initialize(reloading=False):
|
|
508
531
|
if shutil.which("ffmpeg") is None:
|
509
532
|
os.environ["PATH"] += os.pathsep + \
|
510
533
|
os.path.dirname(get_ffmpeg_path())
|
511
|
-
if get_config().obs.
|
512
|
-
|
513
|
-
obs_process = obs.start_obs()
|
534
|
+
if get_config().obs.open_obs:
|
535
|
+
obs_process = obs.start_obs()
|
514
536
|
# obs.connect_to_obs(start_replay=True)
|
515
537
|
# anki.start_monitoring_anki()
|
516
538
|
# gametext.start_text_monitor()
|
517
539
|
os.makedirs(get_config().paths.folder_to_watch, exist_ok=True)
|
518
|
-
os.makedirs(get_config().paths.
|
519
|
-
os.makedirs(get_config().paths.audio_destination, exist_ok=True)
|
540
|
+
os.makedirs(get_config().paths.output_folder, exist_ok=True)
|
520
541
|
initial_checks()
|
521
542
|
register_websocket_message_handler(handle_websocket_message)
|
522
543
|
# if get_config().vad.do_vad_postprocessing:
|
@@ -568,9 +589,8 @@ def post_init2():
|
|
568
589
|
def async_loop():
|
569
590
|
async def loop():
|
570
591
|
await obs.connect_to_obs()
|
571
|
-
|
572
|
-
|
573
|
-
await check_obs_folder_is_correct()
|
592
|
+
await register_scene_switcher_callback()
|
593
|
+
await check_obs_folder_is_correct()
|
574
594
|
logger.info("Post-Initialization started.")
|
575
595
|
vad_processor.init()
|
576
596
|
# if is_beangate:
|
@@ -688,11 +708,7 @@ async def async_main(reloading=False):
|
|
688
708
|
except Exception as e:
|
689
709
|
logger.error(f"Error stopping observer: {e}")
|
690
710
|
except Exception as e:
|
691
|
-
|
692
|
-
notification.send_error_notification(
|
693
|
-
"An error occurred during initialization. Check the log for details.")
|
694
|
-
asyncio.sleep(5)
|
695
|
-
raise e
|
711
|
+
handle_error_in_initialization(e)
|
696
712
|
|
697
713
|
|
698
714
|
def main():
|
@@ -701,10 +717,8 @@ def main():
|
|
701
717
|
try:
|
702
718
|
asyncio.run(async_main())
|
703
719
|
except Exception as e:
|
704
|
-
|
705
|
-
|
706
|
-
"An error occurred during initialization, closing in 5 seconds")
|
707
|
-
time.sleep(5)
|
720
|
+
handle_error_in_initialization(e)
|
721
|
+
|
708
722
|
|
709
723
|
|
710
724
|
if __name__ == "__main__":
|
@@ -712,7 +726,4 @@ if __name__ == "__main__":
|
|
712
726
|
try:
|
713
727
|
asyncio.run(async_main())
|
714
728
|
except Exception as e:
|
715
|
-
|
716
|
-
logger.info(
|
717
|
-
"An error occurred during initialization, closing in 5 seconds")
|
718
|
-
time.sleep(5)
|
729
|
+
handle_error_in_initialization(e)
|