GameSentenceMiner 2.11.7__py3-none-any.whl → 2.12.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 +3 -3
- GameSentenceMiner/anki.py +30 -0
- GameSentenceMiner/config_gui.py +79 -2
- GameSentenceMiner/gametext.py +20 -1
- GameSentenceMiner/gsm.py +9 -0
- GameSentenceMiner/obs.py +11 -3
- GameSentenceMiner/ocr/owocr_helper.py +1 -1
- GameSentenceMiner/owocr/owocr/ocr.py +122 -52
- GameSentenceMiner/owocr/owocr/run.py +37 -4
- GameSentenceMiner/util/configuration.py +261 -2
- GameSentenceMiner/util/model.py +1 -0
- GameSentenceMiner/util/text_log.py +2 -2
- GameSentenceMiner/util/window_transparency.py +28 -0
- GameSentenceMiner/vad.py +1 -1
- GameSentenceMiner/web/texthooking_page.py +23 -13
- GameSentenceMiner/wip/get_overlay_coords.py +241 -0
- {gamesentenceminer-2.11.7.dist-info → gamesentenceminer-2.12.0.dist-info}/METADATA +8 -13
- {gamesentenceminer-2.11.7.dist-info → gamesentenceminer-2.12.0.dist-info}/RECORD +22 -21
- {gamesentenceminer-2.11.7.dist-info → gamesentenceminer-2.12.0.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.11.7.dist-info → gamesentenceminer-2.12.0.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.11.7.dist-info → gamesentenceminer-2.12.0.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.11.7.dist-info → gamesentenceminer-2.12.0.dist-info}/top_level.txt +0 -0
@@ -384,7 +384,6 @@ class TextFiltering:
|
|
384
384
|
block_filtered = self.latin_extended_regex.findall(block)
|
385
385
|
else:
|
386
386
|
block_filtered = self.latin_extended_regex.findall(block)
|
387
|
-
|
388
387
|
if block_filtered:
|
389
388
|
orig_text_filtered.append(''.join(block_filtered))
|
390
389
|
else:
|
@@ -548,6 +547,39 @@ class ScreenshotThread(threading.Thread):
|
|
548
547
|
else:
|
549
548
|
raise ValueError('Window capture is only currently supported on Windows and macOS')
|
550
549
|
|
550
|
+
def __del__(self):
|
551
|
+
if self.macos_window_tracker_instance:
|
552
|
+
self.macos_window_tracker_instance.join()
|
553
|
+
elif self.windows_window_tracker_instance:
|
554
|
+
self.windows_window_tracker_instance.join()
|
555
|
+
|
556
|
+
def setup_persistent_windows_window_tracker(self):
|
557
|
+
global window_open
|
558
|
+
window_open = False
|
559
|
+
def setup_tracker():
|
560
|
+
global window_open
|
561
|
+
self.window_handle, window_title = self.get_windows_window_handle(self.screen_capture_window)
|
562
|
+
|
563
|
+
if not self.window_handle:
|
564
|
+
# print(f"Window '{screen_capture_window}' not found.")
|
565
|
+
return
|
566
|
+
|
567
|
+
set_dpi_awareness()
|
568
|
+
window_open = True
|
569
|
+
self.windows_window_tracker_instance = threading.Thread(target=self.windows_window_tracker)
|
570
|
+
self.windows_window_tracker_instance.start()
|
571
|
+
logger.opt(ansi=True).info(f'Selected window: {window_title}')
|
572
|
+
|
573
|
+
while not terminated:
|
574
|
+
if not window_open:
|
575
|
+
try:
|
576
|
+
setup_tracker()
|
577
|
+
except ValueError as e:
|
578
|
+
logger.error(f"Error setting up persistent windows window tracker: {e}")
|
579
|
+
break
|
580
|
+
time.sleep(5)
|
581
|
+
|
582
|
+
|
551
583
|
def get_windows_window_handle(self, window_title):
|
552
584
|
def callback(hwnd, window_title_part):
|
553
585
|
window_title = win32gui.GetWindowText(hwnd)
|
@@ -570,7 +602,7 @@ class ScreenshotThread(threading.Thread):
|
|
570
602
|
|
571
603
|
def windows_window_tracker(self):
|
572
604
|
found = True
|
573
|
-
while not terminated:
|
605
|
+
while not terminated or window_open:
|
574
606
|
found = win32gui.IsWindow(self.window_handle)
|
575
607
|
if not found:
|
576
608
|
break
|
@@ -1086,10 +1118,11 @@ def signal_handler(sig, frame):
|
|
1086
1118
|
|
1087
1119
|
|
1088
1120
|
def on_window_closed(alive):
|
1089
|
-
global terminated
|
1121
|
+
global terminated, window_open
|
1090
1122
|
if not (alive or terminated):
|
1091
1123
|
logger.info('Window closed or error occurred, terminated!')
|
1092
|
-
|
1124
|
+
window_open = False
|
1125
|
+
# terminated = True
|
1093
1126
|
|
1094
1127
|
|
1095
1128
|
def on_screenshot_combo():
|
@@ -15,6 +15,7 @@ from enum import Enum
|
|
15
15
|
import toml
|
16
16
|
from dataclasses_json import dataclass_json
|
17
17
|
|
18
|
+
|
18
19
|
OFF = 'OFF'
|
19
20
|
# VOSK = 'VOSK'
|
20
21
|
SILERO = 'SILERO'
|
@@ -71,10 +72,240 @@ class Language(Enum):
|
|
71
72
|
PORTUGUESE = "pt"
|
72
73
|
HINDI = "hi"
|
73
74
|
ARABIC = "ar"
|
75
|
+
TURKISH = "tr"
|
76
|
+
DUTCH = "nl"
|
77
|
+
SWEDISH = "sv"
|
78
|
+
FINNISH = "fi"
|
79
|
+
DANISH = "da"
|
80
|
+
NORWEGIAN = "no"
|
81
|
+
|
74
82
|
|
75
83
|
AVAILABLE_LANGUAGES = [lang.value for lang in Language]
|
76
84
|
AVAILABLE_LANGUAGES_DICT = {lang.value: lang for lang in Language}
|
77
85
|
|
86
|
+
class CommonLanguages(str, Enum):
|
87
|
+
"""
|
88
|
+
An Enum of the world's most common languages, based on total speaker count.
|
89
|
+
|
90
|
+
The enum member is the common English name (e.g., ENGLISH) and its
|
91
|
+
value is the ISO 639-1 two-letter code (e.g., 'en').
|
92
|
+
|
93
|
+
Inheriting from `str` allows for direct comparison and use in functions
|
94
|
+
that expect a string, e.g., `CommonLanguages.FRENCH == 'fr'`.
|
95
|
+
|
96
|
+
This list is curated from Wikipedia's "List of languages by total number of speakers"
|
97
|
+
and contains over 200 entries to provide broad but practical coverage.
|
98
|
+
"""
|
99
|
+
ENGLISH = 'en'
|
100
|
+
AFRIKAANS = 'af'
|
101
|
+
AKAN = 'ak'
|
102
|
+
ALBANIAN = 'sq'
|
103
|
+
ALGERIAN_SPOKEN_ARABIC = 'arq'
|
104
|
+
AMHARIC = 'am'
|
105
|
+
ARMENIAN = 'hy'
|
106
|
+
ASSAMESE = 'as'
|
107
|
+
BAMBARA = 'bm'
|
108
|
+
BASQUE = 'eu'
|
109
|
+
BELARUSIAN = 'be'
|
110
|
+
BENGALI = 'bn'
|
111
|
+
BHOJPURI = 'bho'
|
112
|
+
BOSNIAN = 'bs'
|
113
|
+
BODO = 'brx'
|
114
|
+
BULGARIAN = 'bg'
|
115
|
+
BURMESE = 'my'
|
116
|
+
CAPE_VERDEAN_CREOLE = 'kea'
|
117
|
+
CATALAN = 'ca'
|
118
|
+
CEBUANO = 'ceb'
|
119
|
+
CHHATTISGARHI = 'hns'
|
120
|
+
CHITTAGONIAN = 'ctg'
|
121
|
+
CROATIAN = 'hr'
|
122
|
+
CZECH = 'cs'
|
123
|
+
DANISH = 'da'
|
124
|
+
DECCAN = 'dcc'
|
125
|
+
DOGRI = 'doi'
|
126
|
+
DZONGKHA = 'dz'
|
127
|
+
DUTCH = 'nl'
|
128
|
+
EGYPTIAN_SPOKEN_ARABIC = 'arz'
|
129
|
+
ESTONIAN = 'et'
|
130
|
+
EWE = 'ee'
|
131
|
+
FAROESE = 'fo'
|
132
|
+
FIJIAN = 'fj'
|
133
|
+
FINNISH = 'fi'
|
134
|
+
FRENCH = 'fr'
|
135
|
+
GALICIAN = 'gl'
|
136
|
+
GAN_CHINESE = 'gan'
|
137
|
+
GEORGIAN = 'ka'
|
138
|
+
GERMAN = 'de'
|
139
|
+
GREEK = 'el'
|
140
|
+
GREENLANDIC = 'kl'
|
141
|
+
GUJARATI = 'gu'
|
142
|
+
HAITIAN_CREOLE = 'ht'
|
143
|
+
HAUSA = 'ha'
|
144
|
+
HAKKA_CHINESE = 'hak'
|
145
|
+
HARYANVI = 'bgc'
|
146
|
+
HEBREW = 'he'
|
147
|
+
HINDI = 'hi'
|
148
|
+
HUNGARIAN = 'hu'
|
149
|
+
ICELANDIC = 'is'
|
150
|
+
IGBO = 'ig'
|
151
|
+
INDONESIAN = 'id'
|
152
|
+
IRANIAN_PERSIAN = 'fa'
|
153
|
+
IRISH = 'ga'
|
154
|
+
ITALIAN = 'it'
|
155
|
+
JAVANESE = 'jv'
|
156
|
+
JAMAICAN_PATOIS = 'jam'
|
157
|
+
JAPANESE = 'ja'
|
158
|
+
KANNADA = 'kn'
|
159
|
+
KASHMIRI = 'ks'
|
160
|
+
KAZAKH = 'kk'
|
161
|
+
KHMER = 'km'
|
162
|
+
KONGO = 'kg'
|
163
|
+
KONKANI = 'kok'
|
164
|
+
KOREAN = 'ko'
|
165
|
+
KURDISH = 'kmr'
|
166
|
+
LAO = 'lo'
|
167
|
+
LATVIAN = 'lv'
|
168
|
+
LINGALA = 'ln'
|
169
|
+
LITHUANIAN = 'lt'
|
170
|
+
LUBA_KASAI = 'lua'
|
171
|
+
LUXEMBOURGISH = 'lb'
|
172
|
+
MACEDONIAN = 'mk'
|
173
|
+
MADURESE = 'mad'
|
174
|
+
MAGAHI = 'mag'
|
175
|
+
MAITHILI = 'mai'
|
176
|
+
MALAGASY = 'mg'
|
177
|
+
MALAYALAM = 'ml'
|
178
|
+
MALTESE = 'mt'
|
179
|
+
MANDARIN_CHINESE = 'zh'
|
180
|
+
MANIPURI = 'mni'
|
181
|
+
MARATHI = 'mr'
|
182
|
+
MAORI = 'mi'
|
183
|
+
MAURITIAN_CREOLE = 'mfe'
|
184
|
+
MIN_NAN_CHINESE = 'nan'
|
185
|
+
MINANGKABAU = 'min'
|
186
|
+
MONGOLIAN = 'mn'
|
187
|
+
MONTENEGRIN = 'cnr'
|
188
|
+
MOROCCAN_SPOKEN_ARABIC = 'ary'
|
189
|
+
NDEBELE = 'nr'
|
190
|
+
NEPALI = 'ne'
|
191
|
+
NIGERIAN_PIDGIN = 'pcm'
|
192
|
+
NORTHERN_KURDISH = 'kmr'
|
193
|
+
NORTHERN_PASHTO = 'pbu'
|
194
|
+
NORTHERN_UZBEK = 'uz'
|
195
|
+
NORWEGIAN = 'no'
|
196
|
+
ODIA = 'or'
|
197
|
+
PAPIAMENTO = 'pap'
|
198
|
+
POLISH = 'pl'
|
199
|
+
PORTUGUESE = 'pt'
|
200
|
+
ROMANIAN = 'ro'
|
201
|
+
RWANDA = 'rw'
|
202
|
+
RUSSIAN = 'ru'
|
203
|
+
SAMOAN = 'sm'
|
204
|
+
SANTALI = 'sat'
|
205
|
+
SARAIKI = 'skr'
|
206
|
+
SCOTTISH_GAELIC = 'gd'
|
207
|
+
SEYCHELLOIS_CREOLE = 'crs'
|
208
|
+
SERBIAN = 'sr'
|
209
|
+
SHONA = 'sn'
|
210
|
+
SINDHI = 'sd'
|
211
|
+
SINHALA = 'si'
|
212
|
+
SLOVAK = 'sk'
|
213
|
+
SLOVENIAN = 'sl'
|
214
|
+
SOMALI = 'so'
|
215
|
+
SOTHO = 'st'
|
216
|
+
SOUTH_AZERBAIJANI = 'azb'
|
217
|
+
SOUTHERN_PASHTO = 'ps'
|
218
|
+
SPANISH = 'es'
|
219
|
+
STANDARD_ARABIC = 'ar'
|
220
|
+
SUDANESE_SPOKEN_ARABIC = 'apd'
|
221
|
+
SUNDANESE = 'su'
|
222
|
+
SWAHILI = 'sw'
|
223
|
+
SWATI = 'ss'
|
224
|
+
SWEDISH = 'sv'
|
225
|
+
SYLHETI = 'syl'
|
226
|
+
TAGALOG = 'tl'
|
227
|
+
TAMIL = 'ta'
|
228
|
+
TELUGU = 'te'
|
229
|
+
THAI = 'th'
|
230
|
+
TIGRINYA = 'ti'
|
231
|
+
TIBETAN = 'bo'
|
232
|
+
TONGAN = 'to'
|
233
|
+
TSONGA = 'ts'
|
234
|
+
TSWANA = 'tn'
|
235
|
+
TWI = 'twi'
|
236
|
+
UKRAINIAN = 'uk'
|
237
|
+
URDU = 'ur'
|
238
|
+
UYGHUR = 'ug'
|
239
|
+
VENDA = 've'
|
240
|
+
VIETNAMESE = 'vi'
|
241
|
+
WELSH = 'cy'
|
242
|
+
WESTERN_PUNJABI = 'pnb'
|
243
|
+
WOLOF = 'wo'
|
244
|
+
WU_CHINESE = 'wuu'
|
245
|
+
XHOSA = 'xh'
|
246
|
+
YORUBA = 'yo'
|
247
|
+
YUE_CHINESE = 'yue'
|
248
|
+
ZULU = 'zu'
|
249
|
+
|
250
|
+
|
251
|
+
# Helper methods
|
252
|
+
@classmethod
|
253
|
+
def get_all_codes(cls) -> list[str]:
|
254
|
+
"""Returns a list of all language codes (e.g., ['en', 'zh', 'hi'])."""
|
255
|
+
return [lang.value for lang in cls]
|
256
|
+
|
257
|
+
@classmethod
|
258
|
+
def get_all_names(cls) -> list[str]:
|
259
|
+
"""Returns a list of all language names (e.g., ['ENGLISH', 'MANDARIN_CHINESE'])."""
|
260
|
+
return [lang.name for lang in cls]
|
261
|
+
|
262
|
+
@classmethod
|
263
|
+
def get_all_names_pretty(cls) -> list[str]:
|
264
|
+
"""Returns a list of all language names formatted for display (e.g., ['English', 'Mandarin Chinese'])."""
|
265
|
+
return [lang.name.replace('_', ' ').title() for lang in cls]
|
266
|
+
|
267
|
+
@classmethod
|
268
|
+
def get_choices(cls) -> list[tuple[str, str]]:
|
269
|
+
"""
|
270
|
+
Returns a list of (value, label) tuples for use in web framework
|
271
|
+
choice fields (e.g., Django, Flask).
|
272
|
+
|
273
|
+
Example: [('en', 'English'), ('zh', 'Mandarin Chinese')]
|
274
|
+
"""
|
275
|
+
return [(lang.value, lang.name.replace('_', ' ').title()) for lang in cls]
|
276
|
+
|
277
|
+
# Method to lookup language by it's name
|
278
|
+
@classmethod
|
279
|
+
def from_name(cls, name: str) -> 'CommonLanguages':
|
280
|
+
"""
|
281
|
+
Looks up a language by its name (e.g., 'ENGLISH') and returns the corresponding enum member.
|
282
|
+
Raises ValueError if not found.
|
283
|
+
"""
|
284
|
+
try:
|
285
|
+
return cls[name.upper()]
|
286
|
+
except KeyError:
|
287
|
+
raise ValueError(f"Language '{name}' not found in CommonLanguages")
|
288
|
+
|
289
|
+
# Method to lookup language by its code
|
290
|
+
@classmethod
|
291
|
+
def from_code(cls, code: str) -> 'CommonLanguages':
|
292
|
+
"""
|
293
|
+
Looks up a language by its code (e.g., 'en') and returns the corresponding enum member.
|
294
|
+
Raises ValueError if not found.
|
295
|
+
"""
|
296
|
+
for lang in cls:
|
297
|
+
if lang.value == code:
|
298
|
+
return lang
|
299
|
+
raise ValueError(f"Language code '{code}' not found in CommonLanguages")
|
300
|
+
|
301
|
+
@classmethod
|
302
|
+
def name_from_code(cls, code: str) -> str:
|
303
|
+
"""
|
304
|
+
Returns the name of the language given its code (e.g., 'en' -> 'ENGLISH').
|
305
|
+
Raises ValueError if not found.
|
306
|
+
"""
|
307
|
+
return cls.from_code(code).name
|
308
|
+
|
78
309
|
@dataclass_json
|
79
310
|
@dataclass
|
80
311
|
class General:
|
@@ -86,6 +317,13 @@ class General:
|
|
86
317
|
open_multimine_on_startup: bool = True
|
87
318
|
texthook_replacement_regex: str = ""
|
88
319
|
texthooker_port: int = 55000
|
320
|
+
native_language: str = CommonLanguages.ENGLISH.value
|
321
|
+
|
322
|
+
def get_native_language_name(self) -> str:
|
323
|
+
try:
|
324
|
+
return CommonLanguages.name_from_code(self.native_language)
|
325
|
+
except ValueError:
|
326
|
+
return "Unknown"
|
89
327
|
|
90
328
|
|
91
329
|
@dataclass_json
|
@@ -253,7 +491,6 @@ class Advanced:
|
|
253
491
|
multi_line_sentence_storage_field: str = ''
|
254
492
|
ocr_websocket_port: int = 9002
|
255
493
|
texthooker_communication_websocket_port: int = 55001
|
256
|
-
use_anki_note_creation_time: bool = True
|
257
494
|
|
258
495
|
def __post_init__(self):
|
259
496
|
if self.plaintext_websocket_port == -1:
|
@@ -284,6 +521,16 @@ class Ai:
|
|
284
521
|
self.provider = AI_GEMINI
|
285
522
|
if self.provider == 'groq':
|
286
523
|
self.provider = AI_GROQ
|
524
|
+
|
525
|
+
|
526
|
+
# Experimental Features section, will change often
|
527
|
+
@dataclass_json
|
528
|
+
@dataclass
|
529
|
+
class WIP:
|
530
|
+
overlay_websocket_port: int = 55499
|
531
|
+
overlay_websocket_send: bool = False
|
532
|
+
|
533
|
+
|
287
534
|
|
288
535
|
@dataclass_json
|
289
536
|
@dataclass
|
@@ -301,7 +548,15 @@ class ProfileConfig:
|
|
301
548
|
vad: VAD = field(default_factory=VAD)
|
302
549
|
advanced: Advanced = field(default_factory=Advanced)
|
303
550
|
ai: Ai = field(default_factory=Ai)
|
304
|
-
|
551
|
+
wip: WIP = field(default_factory=WIP)
|
552
|
+
|
553
|
+
|
554
|
+
def get_field_value(self, section: str, field_name: str):
|
555
|
+
section_obj = getattr(self, section, None)
|
556
|
+
if section_obj and hasattr(section_obj, field_name):
|
557
|
+
return getattr(section_obj, field_name)
|
558
|
+
else:
|
559
|
+
raise ValueError(f"Field '{field_name}' not found in section '{section}' of ProfileConfig.")
|
305
560
|
|
306
561
|
# This is just for legacy support
|
307
562
|
def load_from_toml(self, file_path: str):
|
@@ -482,6 +737,7 @@ class Config:
|
|
482
737
|
self.sync_shared_field(config, profile, "advanced")
|
483
738
|
self.sync_shared_field(config, profile, "paths")
|
484
739
|
self.sync_shared_field(config, profile, "obs")
|
740
|
+
self.sync_shared_field(config, profile, "wip")
|
485
741
|
self.sync_shared_field(config.ai, profile.ai, "anki_field")
|
486
742
|
self.sync_shared_field(config.ai, profile.ai, "provider")
|
487
743
|
self.sync_shared_field(config.ai, profile.ai, "api_key")
|
@@ -743,4 +999,7 @@ anki_results = {}
|
|
743
999
|
gsm_state = GsmAppState()
|
744
1000
|
is_dev = is_running_from_source()
|
745
1001
|
|
1002
|
+
is_beangate = os.path.exists("C:/Users/Beangate")
|
1003
|
+
|
746
1004
|
logger.debug(f"Running in development mode: {is_dev}")
|
1005
|
+
logger.debug(f"Running on Beangate's PC: {is_beangate}")
|
GameSentenceMiner/util/model.py
CHANGED
@@ -113,8 +113,8 @@ def similar(a, b):
|
|
113
113
|
|
114
114
|
|
115
115
|
def lines_match(texthooker_sentence, anki_sentence):
|
116
|
-
texthooker_sentence = texthooker_sentence.replace("\n", "").replace("\r", "").strip()
|
117
|
-
anki_sentence = anki_sentence.replace("\n", "").replace("\r", "").strip()
|
116
|
+
texthooker_sentence = texthooker_sentence.replace("\n", "").replace("\r", "").replace(' ', '').strip()
|
117
|
+
anki_sentence = anki_sentence.replace("\n", "").replace("\r", "").replace(' ', '').strip()
|
118
118
|
similarity = similar(texthooker_sentence, anki_sentence)
|
119
119
|
if texthooker_sentence in anki_sentence:
|
120
120
|
logger.debug(f"One contains the other: {texthooker_sentence} in {anki_sentence} - Similarity: {similarity}")
|
@@ -1,9 +1,11 @@
|
|
1
|
+
import sys
|
1
2
|
import win32gui
|
2
3
|
import win32con
|
3
4
|
import win32api
|
4
5
|
import keyboard
|
5
6
|
import time
|
6
7
|
import threading
|
8
|
+
import signal
|
7
9
|
|
8
10
|
from GameSentenceMiner.util.configuration import logger
|
9
11
|
|
@@ -137,6 +139,22 @@ def mouse_monitor_loop():
|
|
137
139
|
# A small delay to reduce CPU usage
|
138
140
|
time.sleep(0.1)
|
139
141
|
|
142
|
+
class HandleSTDINThread(threading.Thread):
|
143
|
+
def run(self):
|
144
|
+
while True:
|
145
|
+
try:
|
146
|
+
line = input()
|
147
|
+
if "exit" in line.strip().lower():
|
148
|
+
handle_quit()
|
149
|
+
break
|
150
|
+
except EOFError:
|
151
|
+
break
|
152
|
+
|
153
|
+
def handle_quit():
|
154
|
+
if is_toggled and target_hwnd:
|
155
|
+
reset_window_state(target_hwnd)
|
156
|
+
logger.info("Exiting Window Transparency Tool.")
|
157
|
+
|
140
158
|
# --- Main Execution Block ---
|
141
159
|
|
142
160
|
if __name__ == "__main__":
|
@@ -155,8 +173,18 @@ if __name__ == "__main__":
|
|
155
173
|
# Register the global hotkey
|
156
174
|
keyboard.add_hotkey(hotkey, toggle_functionality)
|
157
175
|
|
176
|
+
# Handle SigINT/SigTERM gracefully
|
177
|
+
def signal_handler(sig, frame):
|
178
|
+
handle_quit()
|
179
|
+
sys.exit(0)
|
180
|
+
|
181
|
+
signal.signal(signal.SIGINT, signal_handler)
|
182
|
+
signal.signal(signal.SIGTERM, signal_handler)
|
183
|
+
|
158
184
|
logger.info(f"Script running. Press '{hotkey}' on a window to toggle transparency.")
|
159
185
|
logger.info("Press Ctrl+C in this console to exit.")
|
186
|
+
|
187
|
+
HandleSTDINThread().start()
|
160
188
|
|
161
189
|
# Keep the script running to listen for the hotkey.
|
162
190
|
# keyboard.wait() is a blocking call that waits indefinitely.
|
GameSentenceMiner/vad.py
CHANGED
@@ -136,7 +136,7 @@ class VADProcessor(ABC):
|
|
136
136
|
if get_config().vad.cut_and_splice_segments:
|
137
137
|
self.extract_audio_and_combine_segments(input_audio, voice_activity, output_audio, padding=get_config().vad.splice_padding)
|
138
138
|
else:
|
139
|
-
ffmpeg.trim_audio(input_audio, start_time + get_config().vad.beginning_offset, end_time + get_config().audio.end_offset, output_audio, trim_beginning=get_config().vad.trim_beginning, fade_in_duration=0.05, fade_out_duration=
|
139
|
+
ffmpeg.trim_audio(input_audio, start_time + get_config().vad.beginning_offset, end_time + get_config().audio.end_offset, output_audio, trim_beginning=get_config().vad.trim_beginning, fade_in_duration=0.05, fade_out_duration=0)
|
140
140
|
return VADResult(True, start_time + get_config().vad.beginning_offset, end_time + get_config().audio.end_offset, self.vad_system_name, voice_activity, output_audio)
|
141
141
|
|
142
142
|
class SileroVADProcessor(VADProcessor):
|
@@ -268,6 +268,11 @@ async def add_event_to_texthooker(line: GameLine):
|
|
268
268
|
})
|
269
269
|
if get_config().advanced.plaintext_websocket_port:
|
270
270
|
await plaintext_websocket_server_thread.send_text(line.text)
|
271
|
+
|
272
|
+
|
273
|
+
async def send_word_coordinates_to_overlay(boxes):
|
274
|
+
if boxes and len(boxes) > 0 and overlay_server_thread:
|
275
|
+
await overlay_server_thread.send_text(boxes)
|
271
276
|
|
272
277
|
|
273
278
|
@app.route('/update_checkbox', methods=['POST'])
|
@@ -384,19 +389,18 @@ def start_web_server():
|
|
384
389
|
app.run(host='0.0.0.0', port=port, debug=False) # debug=True provides helpful error messages during development
|
385
390
|
|
386
391
|
|
387
|
-
websocket_server_thread = None
|
388
392
|
websocket_queue = queue.Queue()
|
389
393
|
paused = False
|
390
394
|
|
391
395
|
|
392
396
|
class WebsocketServerThread(threading.Thread):
|
393
|
-
def __init__(self, read,
|
397
|
+
def __init__(self, read, get_ws_port_func):
|
394
398
|
super().__init__(daemon=True)
|
395
399
|
self._loop = None
|
396
400
|
self.read = read
|
397
401
|
self.clients = set()
|
398
402
|
self._event = threading.Event()
|
399
|
-
self.
|
403
|
+
self.get_ws_port_func = get_ws_port_func
|
400
404
|
self.backedup_text = []
|
401
405
|
|
402
406
|
@property
|
@@ -437,10 +441,13 @@ class WebsocketServerThread(threading.Thread):
|
|
437
441
|
|
438
442
|
async def send_text(self, text):
|
439
443
|
if text:
|
440
|
-
if isinstance(text, dict):
|
444
|
+
if isinstance(text, dict) or isinstance(text, list):
|
441
445
|
text = json.dumps(text)
|
442
446
|
return asyncio.run_coroutine_threadsafe(
|
443
447
|
self.send_text_coroutine(text), self.loop)
|
448
|
+
|
449
|
+
def has_clients(self):
|
450
|
+
return len(self.clients) > 0
|
444
451
|
|
445
452
|
def stop_server(self):
|
446
453
|
self.loop.call_soon_threadsafe(self._stop_event.set)
|
@@ -454,7 +461,7 @@ class WebsocketServerThread(threading.Thread):
|
|
454
461
|
try:
|
455
462
|
self.server = start_server = websockets.serve(self.server_handler,
|
456
463
|
"0.0.0.0",
|
457
|
-
self.
|
464
|
+
self.get_ws_port_func(),
|
458
465
|
max_size=1000000000)
|
459
466
|
async with start_server:
|
460
467
|
await stop_event.wait()
|
@@ -469,21 +476,24 @@ def handle_exit_signal(loop):
|
|
469
476
|
logger.info("Received exit signal. Shutting down...")
|
470
477
|
for task in asyncio.all_tasks(loop):
|
471
478
|
task.cancel()
|
479
|
+
|
480
|
+
websocket_server_thread = WebsocketServerThread(read=True, get_ws_port_func=lambda : get_config().get_field_value('advanced', 'texthooker_communication_websocket_port'))
|
481
|
+
websocket_server_thread.start()
|
482
|
+
|
483
|
+
if get_config().advanced.plaintext_websocket_port:
|
484
|
+
plaintext_websocket_server_thread = WebsocketServerThread(read=False, get_ws_port_func=lambda : get_config().get_field_value('advanced', 'plaintext_websocket_port'))
|
485
|
+
plaintext_websocket_server_thread.start()
|
486
|
+
|
487
|
+
overlay_server_thread = WebsocketServerThread(read=False, get_ws_port_func=lambda : get_config().get_field_value('wip', 'overlay_websocket_port'))
|
488
|
+
overlay_server_thread.start()
|
472
489
|
|
473
490
|
async def texthooker_page_coro():
|
474
|
-
global websocket_server_thread, plaintext_websocket_server_thread
|
491
|
+
global websocket_server_thread, plaintext_websocket_server_thread, overlay_server_thread
|
475
492
|
# Run the WebSocket server in the asyncio event loop
|
476
493
|
flask_thread = threading.Thread(target=start_web_server)
|
477
494
|
flask_thread.daemon = True
|
478
495
|
flask_thread.start()
|
479
496
|
|
480
|
-
websocket_server_thread = WebsocketServerThread(read=True, ws_port=get_config().advanced.texthooker_communication_websocket_port)
|
481
|
-
websocket_server_thread.start()
|
482
|
-
|
483
|
-
if get_config().advanced.plaintext_websocket_port:
|
484
|
-
plaintext_websocket_server_thread = WebsocketServerThread(read=False, ws_port=get_config().advanced.plaintext_websocket_port)
|
485
|
-
plaintext_websocket_server_thread.start()
|
486
|
-
|
487
497
|
# Keep the main asyncio event loop running (for the WebSocket server)
|
488
498
|
|
489
499
|
def run_text_hooker_page():
|