GameSentenceMiner 2.15.2__py3-none-any.whl → 2.15.4__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/config_gui.py +53 -26
- GameSentenceMiner/locales/en_us.json +4 -0
- GameSentenceMiner/locales/ja_jp.json +4 -0
- GameSentenceMiner/locales/zh_cn.json +4 -0
- GameSentenceMiner/obs.py +241 -194
- GameSentenceMiner/ocr/owocr_helper.py +1 -1
- GameSentenceMiner/owocr/owocr/ocr.py +2 -24
- GameSentenceMiner/util/configuration.py +1 -0
- GameSentenceMiner/util/get_overlay_coords.py +0 -1
- GameSentenceMiner/vad.py +3 -1
- {gamesentenceminer-2.15.2.dist-info → gamesentenceminer-2.15.4.dist-info}/METADATA +1 -1
- {gamesentenceminer-2.15.2.dist-info → gamesentenceminer-2.15.4.dist-info}/RECORD +16 -16
- {gamesentenceminer-2.15.2.dist-info → gamesentenceminer-2.15.4.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.15.2.dist-info → gamesentenceminer-2.15.4.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.15.2.dist-info → gamesentenceminer-2.15.4.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.15.2.dist-info → gamesentenceminer-2.15.4.dist-info}/top_level.txt +0 -0
GameSentenceMiner/config_gui.py
CHANGED
@@ -379,6 +379,7 @@ class ConfigApp:
|
|
379
379
|
self.ai_enabled_value = tk.BooleanVar(value=self.settings.ai.enabled)
|
380
380
|
self.ai_provider_value = tk.StringVar(value=self.settings.ai.provider)
|
381
381
|
self.gemini_model_value = tk.StringVar(value=self.settings.ai.gemini_model)
|
382
|
+
self.use_cpu_for_inference_value = tk.BooleanVar(value=self.settings.vad.use_cpu_for_inference)
|
382
383
|
self.groq_model_value = tk.StringVar(value=self.settings.ai.groq_model)
|
383
384
|
self.gemini_api_key_value = tk.StringVar(value=self.settings.ai.gemini_api_key)
|
384
385
|
self.groq_api_key_value = tk.StringVar(value=self.settings.ai.groq_api_key)
|
@@ -592,6 +593,7 @@ class ConfigApp:
|
|
592
593
|
language=self.language_value.get(),
|
593
594
|
cut_and_splice_segments=self.cut_and_splice_segments_value.get(),
|
594
595
|
splice_padding=float(self.splice_padding_value.get()) if self.splice_padding_value.get() else 0.0,
|
596
|
+
use_cpu_for_inference=self.use_cpu_for_inference_value.get(),
|
595
597
|
),
|
596
598
|
advanced=Advanced(
|
597
599
|
audio_player_path=self.audio_player_path_value.get(),
|
@@ -1137,12 +1139,11 @@ class ConfigApp:
|
|
1137
1139
|
ttk.Entry(vad_frame, textvariable=self.splice_padding_value).grid(row=self.current_row, column=3, sticky='EW', pady=2)
|
1138
1140
|
self.current_row += 1
|
1139
1141
|
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
return vad_frame
|
1142
|
+
# Force CPU for Whisper
|
1143
|
+
use_cpu_i18n = vad_i18n.get('use_cpu_for_inference', {})
|
1144
|
+
HoverInfoLabelWidget(vad_frame, text=use_cpu_i18n.get('label', 'Force CPU'), tooltip=use_cpu_i18n.get('tooltip', 'Even if CUDA is installed, use CPU for Whisper'), row=self.current_row, column=0)
|
1145
|
+
ttk.Checkbutton(vad_frame, variable=self.use_cpu_for_inference_value, bootstyle="round-toggle").grid(row=self.current_row, column=1, sticky='W', pady=2)
|
1146
|
+
self.current_row += 1
|
1146
1147
|
|
1147
1148
|
@new_tab
|
1148
1149
|
def create_paths_tab(self):
|
@@ -1224,8 +1225,10 @@ class ConfigApp:
|
|
1224
1225
|
|
1225
1226
|
self.add_reset_button(paths_frame, "paths", self.current_row, 0, self.create_paths_tab)
|
1226
1227
|
|
1227
|
-
for col in range(3):
|
1228
|
-
|
1228
|
+
for col in range(3):
|
1229
|
+
paths_frame.grid_columnconfigure(col, weight=0)
|
1230
|
+
for row in range(self.current_row):
|
1231
|
+
paths_frame.grid_rowconfigure(row, minsize=30)
|
1229
1232
|
|
1230
1233
|
return paths_frame
|
1231
1234
|
|
@@ -1368,8 +1371,10 @@ class ConfigApp:
|
|
1368
1371
|
|
1369
1372
|
self.add_reset_button(anki_frame, "anki", self.current_row, 0, self.create_anki_tab)
|
1370
1373
|
|
1371
|
-
for col in range(2):
|
1372
|
-
|
1374
|
+
for col in range(2):
|
1375
|
+
anki_frame.grid_columnconfigure(col, weight=0)
|
1376
|
+
for row in range(self.current_row):
|
1377
|
+
anki_frame.grid_rowconfigure(row, minsize=30)
|
1373
1378
|
|
1374
1379
|
return anki_frame
|
1375
1380
|
|
@@ -1431,8 +1436,10 @@ class ConfigApp:
|
|
1431
1436
|
|
1432
1437
|
self.add_reset_button(features_frame, "features", self.current_row, 0, self.create_features_tab)
|
1433
1438
|
|
1434
|
-
for col in range(3):
|
1435
|
-
|
1439
|
+
for col in range(3):
|
1440
|
+
features_frame.grid_columnconfigure(col, weight=0)
|
1441
|
+
for row in range(self.current_row):
|
1442
|
+
features_frame.grid_rowconfigure(row, minsize=30)
|
1436
1443
|
|
1437
1444
|
return features_frame
|
1438
1445
|
|
@@ -1533,8 +1540,10 @@ class ConfigApp:
|
|
1533
1540
|
|
1534
1541
|
self.add_reset_button(screenshot_frame, "screenshot", self.current_row, 0, self.create_screenshot_tab)
|
1535
1542
|
|
1536
|
-
for col in range(3):
|
1537
|
-
|
1543
|
+
for col in range(3):
|
1544
|
+
screenshot_frame.grid_columnconfigure(col, weight=0)
|
1545
|
+
for row in range(self.current_row):
|
1546
|
+
screenshot_frame.grid_rowconfigure(row, minsize=30)
|
1538
1547
|
|
1539
1548
|
return screenshot_frame
|
1540
1549
|
|
@@ -1647,8 +1656,10 @@ class ConfigApp:
|
|
1647
1656
|
|
1648
1657
|
self.add_reset_button(audio_frame, "audio", self.current_row, 0, self.create_audio_tab)
|
1649
1658
|
|
1650
|
-
for col in range(5):
|
1651
|
-
|
1659
|
+
for col in range(5):
|
1660
|
+
audio_frame.grid_columnconfigure(col, weight=0)
|
1661
|
+
for row in range(self.current_row):
|
1662
|
+
audio_frame.grid_rowconfigure(row, minsize=30)
|
1652
1663
|
|
1653
1664
|
return audio_frame
|
1654
1665
|
|
@@ -1752,8 +1763,10 @@ class ConfigApp:
|
|
1752
1763
|
|
1753
1764
|
self.add_reset_button(obs_frame, "obs", self.current_row, 0, self.create_obs_tab)
|
1754
1765
|
|
1755
|
-
for col in range(3):
|
1756
|
-
|
1766
|
+
for col in range(3):
|
1767
|
+
obs_frame.grid_columnconfigure(col, weight=0)
|
1768
|
+
for row in range(self.current_row):
|
1769
|
+
obs_frame.grid_rowconfigure(row, minsize=30)
|
1757
1770
|
|
1758
1771
|
return obs_frame
|
1759
1772
|
|
@@ -1814,8 +1827,10 @@ class ConfigApp:
|
|
1814
1827
|
row=self.current_row, column=1, sticky='W', pady=2)
|
1815
1828
|
self.current_row += 1
|
1816
1829
|
|
1817
|
-
for col in range(4):
|
1818
|
-
|
1830
|
+
for col in range(4):
|
1831
|
+
profiles_frame.grid_columnconfigure(col, weight=0)
|
1832
|
+
for row in range(self.current_row):
|
1833
|
+
profiles_frame.grid_rowconfigure(row, minsize=30)
|
1819
1834
|
|
1820
1835
|
return profiles_frame
|
1821
1836
|
|
@@ -1945,8 +1960,10 @@ class ConfigApp:
|
|
1945
1960
|
|
1946
1961
|
self.add_reset_button(advanced_frame, "advanced", self.current_row, 0, self.create_advanced_tab)
|
1947
1962
|
|
1948
|
-
for col in range(4):
|
1949
|
-
|
1963
|
+
for col in range(4):
|
1964
|
+
advanced_frame.grid_columnconfigure(col, weight=0)
|
1965
|
+
for row in range(self.current_row):
|
1966
|
+
advanced_frame.grid_rowconfigure(row, minsize=30)
|
1950
1967
|
|
1951
1968
|
return advanced_frame
|
1952
1969
|
|
@@ -1997,6 +2014,12 @@ class ConfigApp:
|
|
1997
2014
|
self.groq_models_combobox = ttk.Combobox(ai_frame, textvariable=self.groq_model_value, values=RECOMMENDED_GROQ_MODELS, state="readonly")
|
1998
2015
|
self.groq_models_combobox.grid(row=self.current_row, column=1, sticky='EW', pady=2)
|
1999
2016
|
self.current_row += 1
|
2017
|
+
|
2018
|
+
# Force CPU for Whisper
|
2019
|
+
use_cpu_i18n = ai_i18n.get('use_cpu_for_inference', {})
|
2020
|
+
HoverInfoLabelWidget(ai_frame, text=use_cpu_i18n.get('label', 'Force CPU'), tooltip=use_cpu_i18n.get('tooltip', 'Even if CUDA is installed, use CPU for Whisper'), row=self.current_row, column=0)
|
2021
|
+
ttk.Checkbutton(ai_frame, variable=self.use_cpu_for_inference_value, bootstyle="round-toggle").grid(row=self.current_row, column=1, sticky='W', pady=2)
|
2022
|
+
self.current_row += 1
|
2000
2023
|
|
2001
2024
|
groq_key_i18n = ai_i18n.get('groq_api_key', {})
|
2002
2025
|
HoverInfoLabelWidget(ai_frame, text=groq_key_i18n.get('label', '...'), tooltip=groq_key_i18n.get('tooltip', '...'),
|
@@ -2067,8 +2090,10 @@ class ConfigApp:
|
|
2067
2090
|
|
2068
2091
|
self.add_reset_button(ai_frame, "ai", self.current_row, 0, self.create_ai_tab)
|
2069
2092
|
|
2070
|
-
for col in range(3):
|
2071
|
-
|
2093
|
+
for col in range(3):
|
2094
|
+
ai_frame.grid_columnconfigure(col, weight=0)
|
2095
|
+
for row in range(self.current_row):
|
2096
|
+
ai_frame.grid_rowconfigure(row, minsize=30)
|
2072
2097
|
|
2073
2098
|
return ai_frame
|
2074
2099
|
|
@@ -2245,8 +2270,10 @@ class ConfigApp:
|
|
2245
2270
|
|
2246
2271
|
self.add_reset_button(wip_frame, "wip", self.current_row, 0, self.create_wip_tab)
|
2247
2272
|
|
2248
|
-
for col in range(2):
|
2249
|
-
|
2273
|
+
for col in range(2):
|
2274
|
+
wip_frame.grid_columnconfigure(col, weight=0)
|
2275
|
+
for row in range(self.current_row):
|
2276
|
+
wip_frame.grid_rowconfigure(row, minsize=30)
|
2250
2277
|
|
2251
2278
|
return wip_frame
|
2252
2279
|
|
@@ -260,6 +260,10 @@
|
|
260
260
|
"splice_padding": {
|
261
261
|
"label": "Padding:",
|
262
262
|
"tooltip": "Cut Detected Voice Segments and Paste them back together. More Padding = More Space between voicelines."
|
263
|
+
},
|
264
|
+
"use_cpu_for_inference": {
|
265
|
+
"label": "Force CPU:",
|
266
|
+
"tooltip": "Even if CUDA is installed, use CPU for Whisper"
|
263
267
|
}
|
264
268
|
},
|
265
269
|
"features": {
|
GameSentenceMiner/obs.py
CHANGED
@@ -5,6 +5,7 @@ import subprocess
|
|
5
5
|
import threading
|
6
6
|
import time
|
7
7
|
import logging
|
8
|
+
import contextlib
|
8
9
|
|
9
10
|
import psutil
|
10
11
|
|
@@ -16,7 +17,7 @@ from GameSentenceMiner.util.gsm_utils import sanitize_filename, make_unique_file
|
|
16
17
|
import tkinter as tk
|
17
18
|
from tkinter import messagebox
|
18
19
|
|
19
|
-
|
20
|
+
connection_pool: 'OBSConnectionPool' = None
|
20
21
|
event_client: obs.EventClient = None
|
21
22
|
obs_process_pid = None
|
22
23
|
OBS_PID_FILE = os.path.join(configuration.get_app_directory(), 'obs-studio', 'obs_pid.txt')
|
@@ -24,6 +25,81 @@ obs_connection_manager = None
|
|
24
25
|
logging.getLogger("obsws_python").setLevel(logging.CRITICAL)
|
25
26
|
connecting = False
|
26
27
|
|
28
|
+
class OBSConnectionPool:
|
29
|
+
"""Manages a pool of thread-safe connections to the OBS WebSocket."""
|
30
|
+
def __init__(self, size=3, **kwargs):
|
31
|
+
self.size = size
|
32
|
+
self.connection_kwargs = kwargs
|
33
|
+
self._clients = [None] * self.size
|
34
|
+
self._locks = [threading.Lock() for _ in range(self.size)]
|
35
|
+
self._next_idx = 0
|
36
|
+
self._idx_lock = threading.Lock()
|
37
|
+
logger.info(f"Initialized OBSConnectionPool with size {self.size}")
|
38
|
+
|
39
|
+
def connect_all(self):
|
40
|
+
"""Initializes all client objects in the pool."""
|
41
|
+
for i in range(self.size):
|
42
|
+
try:
|
43
|
+
self._clients[i] = obs.ReqClient(**self.connection_kwargs)
|
44
|
+
except Exception as e:
|
45
|
+
logger.error(f"Failed to create client {i} in pool: {e}")
|
46
|
+
return True
|
47
|
+
|
48
|
+
def disconnect_all(self):
|
49
|
+
"""Disconnects all clients in the pool."""
|
50
|
+
for client in self._clients:
|
51
|
+
if client:
|
52
|
+
try:
|
53
|
+
client.disconnect()
|
54
|
+
except Exception:
|
55
|
+
pass
|
56
|
+
self._clients = [None] * self.size
|
57
|
+
logger.info("Disconnected all clients in OBSConnectionPool.")
|
58
|
+
|
59
|
+
def _check_and_reconnect(self, index):
|
60
|
+
"""Checks a specific client and reconnects if necessary."""
|
61
|
+
client = self._clients[index]
|
62
|
+
if not client:
|
63
|
+
self._clients[index] = obs.ReqClient(**self.connection_kwargs)
|
64
|
+
logger.info(f"Re-initialized client {index} in pool.")
|
65
|
+
return
|
66
|
+
try:
|
67
|
+
client.get_version()
|
68
|
+
except Exception:
|
69
|
+
logger.info(f"Reconnecting client {index} in pool.")
|
70
|
+
try:
|
71
|
+
client.disconnect()
|
72
|
+
except Exception:
|
73
|
+
pass
|
74
|
+
self._clients[index] = obs.ReqClient(**self.connection_kwargs)
|
75
|
+
|
76
|
+
@contextlib.contextmanager
|
77
|
+
def get_client(self):
|
78
|
+
"""A context manager to safely get a client from the pool."""
|
79
|
+
with self._idx_lock:
|
80
|
+
idx = self._next_idx
|
81
|
+
self._next_idx = (self._next_idx + 1) % self.size
|
82
|
+
|
83
|
+
lock = self._locks[idx]
|
84
|
+
lock.acquire()
|
85
|
+
try:
|
86
|
+
self._check_and_reconnect(idx)
|
87
|
+
yield self._clients[idx]
|
88
|
+
finally:
|
89
|
+
lock.release()
|
90
|
+
|
91
|
+
def get_healthcheck_client(self):
|
92
|
+
"""Returns a dedicated client for health checks, separate from the main pool."""
|
93
|
+
if not hasattr(self, '_healthcheck_client') or self._healthcheck_client is None:
|
94
|
+
try:
|
95
|
+
self._healthcheck_client = obs.ReqClient(**self.connection_kwargs)
|
96
|
+
logger.info("Initialized dedicated healthcheck client.")
|
97
|
+
except Exception as e:
|
98
|
+
logger.error(f"Failed to create healthcheck client: {e}")
|
99
|
+
self._healthcheck_client = None
|
100
|
+
return self._healthcheck_client
|
101
|
+
|
102
|
+
|
27
103
|
class OBSConnectionManager(threading.Thread):
|
28
104
|
def __init__(self, check_output=True):
|
29
105
|
super().__init__()
|
@@ -38,8 +114,11 @@ class OBSConnectionManager(threading.Thread):
|
|
38
114
|
while self.running:
|
39
115
|
time.sleep(self.check_connection_interval)
|
40
116
|
try:
|
41
|
-
if
|
117
|
+
client = connection_pool.get_healthcheck_client() if connection_pool else None
|
118
|
+
if client and not connecting:
|
42
119
|
client.get_version()
|
120
|
+
else:
|
121
|
+
raise ConnectionError("Healthcheck client not healthy or not initialized")
|
43
122
|
except Exception as e:
|
44
123
|
logger.info(f"OBS WebSocket not connected. Attempting to reconnect... {e}")
|
45
124
|
gsm_status.obs_connected = False
|
@@ -128,12 +207,12 @@ def start_obs():
|
|
128
207
|
return None
|
129
208
|
|
130
209
|
async def wait_for_obs_connected():
|
131
|
-
|
132
|
-
if not client:
|
210
|
+
if not connection_pool:
|
133
211
|
return False
|
134
212
|
for _ in range(10):
|
135
213
|
try:
|
136
|
-
|
214
|
+
with connection_pool.get_client() as client:
|
215
|
+
response = client.get_version()
|
137
216
|
if response:
|
138
217
|
return True
|
139
218
|
except Exception as e:
|
@@ -183,19 +262,25 @@ def get_obs_websocket_config_values():
|
|
183
262
|
reload_config()
|
184
263
|
|
185
264
|
async def connect_to_obs(retry=5, check_output=True):
|
186
|
-
global
|
265
|
+
global connection_pool, obs_connection_manager, event_client, connecting
|
187
266
|
if is_windows():
|
188
267
|
get_obs_websocket_config_values()
|
189
268
|
|
190
269
|
while True:
|
191
270
|
connecting = True
|
192
271
|
try:
|
193
|
-
|
194
|
-
host
|
195
|
-
port
|
196
|
-
password
|
197
|
-
timeout
|
198
|
-
|
272
|
+
pool_kwargs = {
|
273
|
+
'host': get_config().obs.host,
|
274
|
+
'port': get_config().obs.port,
|
275
|
+
'password': get_config().obs.password,
|
276
|
+
'timeout': 3,
|
277
|
+
}
|
278
|
+
connection_pool = OBSConnectionPool(size=3, **pool_kwargs)
|
279
|
+
connection_pool.connect_all()
|
280
|
+
|
281
|
+
with connection_pool.get_client() as client:
|
282
|
+
client.get_version() # Test one connection to confirm it works
|
283
|
+
|
199
284
|
event_client = obs.EventClient(
|
200
285
|
host=get_config().obs.host,
|
201
286
|
port=get_config().obs.port,
|
@@ -213,7 +298,7 @@ async def connect_to_obs(retry=5, check_output=True):
|
|
213
298
|
if retry <= 0:
|
214
299
|
gsm_status.obs_connected = False
|
215
300
|
logger.error(f"Failed to connect to OBS WebSocket: {e}")
|
216
|
-
|
301
|
+
connection_pool = None
|
217
302
|
event_client = None
|
218
303
|
connecting = False
|
219
304
|
break
|
@@ -222,18 +307,24 @@ async def connect_to_obs(retry=5, check_output=True):
|
|
222
307
|
connecting = False
|
223
308
|
|
224
309
|
def connect_to_obs_sync(retry=2, check_output=True):
|
225
|
-
global
|
310
|
+
global connection_pool, obs_connection_manager, event_client
|
226
311
|
if is_windows():
|
227
312
|
get_obs_websocket_config_values()
|
228
313
|
|
229
314
|
while True:
|
230
315
|
try:
|
231
|
-
|
232
|
-
host
|
233
|
-
port
|
234
|
-
password
|
235
|
-
timeout
|
236
|
-
|
316
|
+
pool_kwargs = {
|
317
|
+
'host': get_config().obs.host,
|
318
|
+
'port': get_config().obs.port,
|
319
|
+
'password': get_config().obs.password,
|
320
|
+
'timeout': 3,
|
321
|
+
}
|
322
|
+
connection_pool = OBSConnectionPool(size=5, **pool_kwargs)
|
323
|
+
connection_pool.connect_all()
|
324
|
+
|
325
|
+
with connection_pool.get_client() as client:
|
326
|
+
client.get_version() # Test one connection to confirm it works
|
327
|
+
|
237
328
|
event_client = obs.EventClient(
|
238
329
|
host=get_config().obs.host,
|
239
330
|
port=get_config().obs.port,
|
@@ -250,7 +341,7 @@ def connect_to_obs_sync(retry=2, check_output=True):
|
|
250
341
|
if retry <= 0:
|
251
342
|
gsm_status.obs_connected = False
|
252
343
|
logger.error(f"Failed to connect to OBS WebSocket: {e}")
|
253
|
-
|
344
|
+
connection_pool = None
|
254
345
|
event_client = None
|
255
346
|
break
|
256
347
|
time.sleep(1)
|
@@ -258,33 +349,44 @@ def connect_to_obs_sync(retry=2, check_output=True):
|
|
258
349
|
|
259
350
|
|
260
351
|
def disconnect_from_obs():
|
261
|
-
global
|
262
|
-
if
|
263
|
-
|
264
|
-
|
352
|
+
global connection_pool
|
353
|
+
if connection_pool:
|
354
|
+
connection_pool.disconnect_all()
|
355
|
+
connection_pool = None
|
265
356
|
logger.info("Disconnected from OBS WebSocket.")
|
266
357
|
|
267
|
-
def do_obs_call(
|
268
|
-
|
269
|
-
|
358
|
+
def do_obs_call(method_name: str, from_dict=None, retry=3, **kwargs):
|
359
|
+
if not connection_pool:
|
360
|
+
connect_to_obs_sync(retry=1)
|
361
|
+
if not connection_pool:
|
270
362
|
return None
|
363
|
+
|
364
|
+
last_exception = None
|
271
365
|
for _ in range(retry + 1):
|
272
366
|
try:
|
273
|
-
|
274
|
-
|
275
|
-
|
367
|
+
with connection_pool.get_client() as client:
|
368
|
+
method_to_call = getattr(client, method_name)
|
369
|
+
response = method_to_call(**kwargs)
|
370
|
+
if response and response.ok:
|
371
|
+
return from_dict(response.datain) if from_dict else response.datain
|
276
372
|
time.sleep(0.3)
|
373
|
+
except AttributeError:
|
374
|
+
logger.error(f"OBS client has no method '{method_name}'")
|
375
|
+
return None
|
277
376
|
except Exception as e:
|
278
|
-
|
377
|
+
last_exception = e
|
378
|
+
logger.error(f"Error calling OBS ('{method_name}'): {e}")
|
279
379
|
if "socket is already closed" in str(e) or "object has no attribute" in str(e):
|
280
380
|
time.sleep(0.3)
|
281
381
|
else:
|
282
382
|
return None
|
383
|
+
logger.error(f"OBS call '{method_name}' failed after retries. Last error: {last_exception}")
|
283
384
|
return None
|
284
385
|
|
285
386
|
def toggle_replay_buffer():
|
286
387
|
try:
|
287
|
-
|
388
|
+
with connection_pool.get_client() as client:
|
389
|
+
response = client.toggle_replay_buffer()
|
288
390
|
if response:
|
289
391
|
logger.info("Replay buffer Toggled.")
|
290
392
|
except Exception as e:
|
@@ -292,7 +394,8 @@ def toggle_replay_buffer():
|
|
292
394
|
|
293
395
|
def start_replay_buffer():
|
294
396
|
try:
|
295
|
-
|
397
|
+
with connection_pool.get_client() as client:
|
398
|
+
response = client.start_replay_buffer()
|
296
399
|
if response and response.ok:
|
297
400
|
logger.info("Replay buffer started.")
|
298
401
|
except Exception as e:
|
@@ -300,14 +403,16 @@ def start_replay_buffer():
|
|
300
403
|
|
301
404
|
def get_replay_buffer_status():
|
302
405
|
try:
|
303
|
-
|
406
|
+
with connection_pool.get_client() as client:
|
407
|
+
return client.get_replay_buffer_status().output_active
|
304
408
|
except Exception as e:
|
305
409
|
logger.debug(f"Error getting replay buffer status: {e}")
|
306
410
|
return None
|
307
411
|
|
308
412
|
def stop_replay_buffer():
|
309
413
|
try:
|
310
|
-
|
414
|
+
with connection_pool.get_client() as client:
|
415
|
+
response = client.stop_replay_buffer()
|
311
416
|
if response and response.ok:
|
312
417
|
logger.info("Replay buffer stopped.")
|
313
418
|
except Exception as e:
|
@@ -316,7 +421,8 @@ def stop_replay_buffer():
|
|
316
421
|
def save_replay_buffer():
|
317
422
|
status = get_replay_buffer_status()
|
318
423
|
if status:
|
319
|
-
|
424
|
+
with connection_pool.get_client() as client:
|
425
|
+
response = client.save_replay_buffer()
|
320
426
|
if response and response.ok:
|
321
427
|
logger.info("Replay buffer saved. If your log stops here, make sure your obs output path matches \"Path To Watch\" in GSM settings.")
|
322
428
|
else:
|
@@ -324,7 +430,8 @@ def save_replay_buffer():
|
|
324
430
|
|
325
431
|
def get_current_scene():
|
326
432
|
try:
|
327
|
-
|
433
|
+
with connection_pool.get_client() as client:
|
434
|
+
response = client.get_current_program_scene()
|
328
435
|
return response.scene_name if response else ''
|
329
436
|
except Exception as e:
|
330
437
|
logger.debug(f"Couldn't get scene: {e}")
|
@@ -332,7 +439,8 @@ def get_current_scene():
|
|
332
439
|
|
333
440
|
def get_source_from_scene(scene_name):
|
334
441
|
try:
|
335
|
-
|
442
|
+
with connection_pool.get_client() as client:
|
443
|
+
response = client.get_scene_item_list(name=scene_name)
|
336
444
|
return response.scene_items[0] if response and response.scene_items else ''
|
337
445
|
except Exception as e:
|
338
446
|
logger.error(f"Error getting source from scene: {e}")
|
@@ -346,7 +454,8 @@ def get_active_source():
|
|
346
454
|
|
347
455
|
def get_record_directory():
|
348
456
|
try:
|
349
|
-
|
457
|
+
with connection_pool.get_client() as client:
|
458
|
+
response = client.get_record_directory()
|
350
459
|
return response.record_directory if response else ''
|
351
460
|
except Exception as e:
|
352
461
|
logger.error(f"Error getting recording folder: {e}")
|
@@ -354,17 +463,17 @@ def get_record_directory():
|
|
354
463
|
|
355
464
|
def get_obs_scenes():
|
356
465
|
try:
|
357
|
-
|
466
|
+
with connection_pool.get_client() as client:
|
467
|
+
response = client.get_scene_list()
|
358
468
|
return response.scenes if response else None
|
359
469
|
except Exception as e:
|
360
470
|
logger.error(f"Error getting scenes: {e}")
|
361
471
|
return None
|
362
472
|
|
363
473
|
async def register_scene_change_callback(callback):
|
364
|
-
global client
|
365
474
|
if await wait_for_obs_connected():
|
366
|
-
if not
|
367
|
-
logger.error("OBS
|
475
|
+
if not connection_pool:
|
476
|
+
logger.error("OBS connection pool is not connected.")
|
368
477
|
return
|
369
478
|
|
370
479
|
def on_current_program_scene_changed(data):
|
@@ -391,7 +500,8 @@ def get_screenshot(compression=-1):
|
|
391
500
|
return None
|
392
501
|
start = time.time()
|
393
502
|
logger.debug(f"Current source name: {current_source_name}")
|
394
|
-
|
503
|
+
with connection_pool.get_client() as client:
|
504
|
+
client.save_source_screenshot(name=current_source_name, img_format='png', width=None, height=None, file_path=screenshot, quality=compression)
|
395
505
|
logger.debug(f"Screenshot took {time.time() - start:.3f} seconds to save")
|
396
506
|
return screenshot
|
397
507
|
except Exception as e:
|
@@ -410,11 +520,10 @@ def get_screenshot_base64(compression=75, width=None, height=None):
|
|
410
520
|
if not current_source_name:
|
411
521
|
logger.error("No active source found in the current scene.")
|
412
522
|
return None
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
# print(responseraw)
|
523
|
+
|
524
|
+
with connection_pool.get_client() as client:
|
525
|
+
response = client.get_source_screenshot(name=current_source_name, img_format='png', quality=compression, width=width, height=height)
|
526
|
+
|
418
527
|
if response and response.image_data:
|
419
528
|
return response.image_data.split(',', 1)[-1] # Remove data:image/png;base64, prefix if present
|
420
529
|
else:
|
@@ -435,7 +544,8 @@ def get_screenshot_PIL(source_name=None, compression=75, img_format='png', width
|
|
435
544
|
logger.error("No active source found in the current scene.")
|
436
545
|
return None
|
437
546
|
while True:
|
438
|
-
|
547
|
+
with connection_pool.get_client() as client:
|
548
|
+
response = client.get_source_screenshot(name=source_name, img_format=img_format, quality=compression, width=width, height=height)
|
439
549
|
try:
|
440
550
|
response.image_data = response.image_data.split(',', 1)[-1] # Remove data:image/png;base64, prefix if present
|
441
551
|
except AttributeError:
|
@@ -448,8 +558,6 @@ def get_screenshot_PIL(source_name=None, compression=75, img_format='png', width
|
|
448
558
|
image_data = response.image_data.split(',', 1)[-1] # Remove data:image/png;base64, prefix if present
|
449
559
|
image_data = base64.b64decode(image_data)
|
450
560
|
img = Image.open(io.BytesIO(image_data)).convert("RGBA")
|
451
|
-
# if width and height:
|
452
|
-
# img = img.resize((width, height), Image.Resampling.LANCZOS)
|
453
561
|
return img
|
454
562
|
return None
|
455
563
|
|
@@ -472,103 +580,84 @@ def get_current_game(sanitize=False, update=True):
|
|
472
580
|
def set_fit_to_screen_for_scene_items(scene_name: str):
|
473
581
|
"""
|
474
582
|
Sets all sources in a given scene to "Fit to Screen" (like Ctrl+F in OBS).
|
475
|
-
|
476
|
-
This function fetches the canvas dimensions, then iterates through all scene
|
477
|
-
items in the specified scene and applies a transform that scales them to
|
478
|
-
fit within the canvas while maintaining aspect ratio and centering them.
|
479
|
-
|
480
|
-
Args:
|
481
|
-
scene_name: The name of the scene to modify.
|
482
583
|
"""
|
584
|
+
if not scene_name:
|
585
|
+
return
|
586
|
+
|
483
587
|
try:
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
588
|
+
with connection_pool.get_client() as client:
|
589
|
+
# 1. Get the canvas (base) resolution from OBS video settings
|
590
|
+
video_settings = client.get_video_settings()
|
591
|
+
if not hasattr(video_settings, 'base_width') or not hasattr(video_settings, 'base_height'):
|
592
|
+
logger.debug("Video settings do not have base_width or base_height attributes, probably weird websocket error issue? Idk what causes it..")
|
593
|
+
return
|
594
|
+
canvas_width = video_settings.base_width
|
595
|
+
canvas_height = video_settings.base_height
|
491
596
|
|
492
|
-
|
493
|
-
|
494
|
-
|
597
|
+
# 2. Get the list of items in the specified scene
|
598
|
+
scene_items_response = client.get_scene_item_list(scene_name)
|
599
|
+
items = scene_items_response.scene_items if scene_items_response.scene_items else []
|
495
600
|
|
496
|
-
|
497
|
-
|
498
|
-
|
601
|
+
if not items:
|
602
|
+
logger.warning(f"No items found in scene '{scene_name}'.")
|
603
|
+
return
|
499
604
|
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
aspect_ratio_different
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
'boundsType': 'OBS_BOUNDS_SCALE_INNER',
|
546
|
-
'alignment': 5, # 5 = Center alignment (horizontal and vertical)
|
547
|
-
'boundsWidth': canvas_width,
|
548
|
-
'boundsHeight': canvas_height,
|
549
|
-
'positionX': 0,
|
550
|
-
'positionY': 0,
|
551
|
-
}
|
552
|
-
|
553
|
-
if not already_cropped:
|
554
|
-
fit_to_screen_transform.update({
|
555
|
-
'cropLeft': 0 if not aspect_ratio_different or canvas_width > source_width else (source_width - canvas_width) // 2,
|
556
|
-
'cropRight': 0 if not aspect_ratio_different or canvas_width > source_width else (source_width - canvas_width) // 2,
|
557
|
-
'cropTop': 0 if not aspect_ratio_different or canvas_height > source_height else (source_height - canvas_height) // 2,
|
558
|
-
'cropBottom': 0 if not aspect_ratio_different or canvas_height > source_height else (source_height - canvas_height) // 2,
|
559
|
-
})
|
605
|
+
# 3. Loop through each item and apply the "Fit to Screen" transform
|
606
|
+
for item in items:
|
607
|
+
item_id = item['sceneItemId']
|
608
|
+
source_name = item['sourceName']
|
609
|
+
|
610
|
+
scene_item_transform = item.get('sceneItemTransform', {})
|
611
|
+
|
612
|
+
source_width = scene_item_transform.get('sourceWidth', None)
|
613
|
+
source_height = scene_item_transform.get('sourceHeight', None)
|
614
|
+
|
615
|
+
aspect_ratio_different = False
|
616
|
+
already_cropped = any([
|
617
|
+
scene_item_transform.get('cropLeft', 0) != 0,
|
618
|
+
scene_item_transform.get('cropRight', 0) != 0,
|
619
|
+
scene_item_transform.get('cropTop', 0) != 0,
|
620
|
+
scene_item_transform.get('cropBottom', 0) != 0,
|
621
|
+
])
|
622
|
+
|
623
|
+
if source_width and source_height and not already_cropped:
|
624
|
+
source_aspect_ratio = source_width / source_height
|
625
|
+
canvas_aspect_ratio = canvas_width / canvas_height
|
626
|
+
aspect_ratio_different = abs(source_aspect_ratio - canvas_aspect_ratio) > 0.01
|
627
|
+
|
628
|
+
standard_ratios = [4 / 3, 16 / 9, 16 / 10, 21 / 9, 32 / 9, 5 / 4, 3 / 2]
|
629
|
+
|
630
|
+
def is_standard_ratio(ratio):
|
631
|
+
return any(abs(ratio - std) < 0.02 for std in standard_ratios)
|
632
|
+
|
633
|
+
if aspect_ratio_different:
|
634
|
+
if not (is_standard_ratio(source_aspect_ratio) and is_standard_ratio(canvas_aspect_ratio)):
|
635
|
+
aspect_ratio_different = False
|
636
|
+
|
637
|
+
fit_to_screen_transform = {
|
638
|
+
'boundsType': 'OBS_BOUNDS_SCALE_INNER', 'alignment': 5,
|
639
|
+
'boundsWidth': canvas_width, 'boundsHeight': canvas_height,
|
640
|
+
'positionX': 0, 'positionY': 0,
|
641
|
+
}
|
642
|
+
|
643
|
+
if not already_cropped:
|
644
|
+
fit_to_screen_transform.update({
|
645
|
+
'cropLeft': 0 if not aspect_ratio_different or canvas_width > source_width else (source_width - canvas_width) // 2,
|
646
|
+
'cropRight': 0 if not aspect_ratio_different or canvas_width > source_width else (source_width - canvas_width) // 2,
|
647
|
+
'cropTop': 0 if not aspect_ratio_different or canvas_height > source_height else (source_height - canvas_height) // 2,
|
648
|
+
'cropBottom': 0 if not aspect_ratio_different or canvas_height > source_height else (source_height - canvas_height) // 2,
|
649
|
+
})
|
560
650
|
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
651
|
+
try:
|
652
|
+
client.set_scene_item_transform(
|
653
|
+
scene_name=scene_name,
|
654
|
+
item_id=item_id,
|
655
|
+
transform=fit_to_screen_transform
|
656
|
+
)
|
657
|
+
except obs.error.OBSSDKError as e:
|
658
|
+
logger.error(f"Failed to set transform for source '{source_name}': {e}")
|
569
659
|
|
570
660
|
except obs.error.OBSSDKError as e:
|
571
|
-
# This will catch errors like "scene not found"
|
572
661
|
logger.error(f"An OBS error occurred: {e}")
|
573
662
|
except Exception as e:
|
574
663
|
logger.error(f"An unexpected error occurred: {e}")
|
@@ -576,12 +665,14 @@ def set_fit_to_screen_for_scene_items(scene_name: str):
|
|
576
665
|
|
577
666
|
def main():
|
578
667
|
start_obs()
|
579
|
-
connect_to_obs()
|
668
|
+
# connect_to_obs() is async, main is not. Use the sync version.
|
669
|
+
connect_to_obs_sync()
|
580
670
|
# Test each method
|
581
671
|
print("Testing `get_obs_path`:", get_obs_path())
|
582
672
|
print("Testing `is_process_running` with PID 1:", is_process_running(1))
|
583
673
|
print("Testing `check_obs_folder_is_correct`:")
|
584
|
-
|
674
|
+
# This is async, need to run it in an event loop if testing from main
|
675
|
+
asyncio.run(check_obs_folder_is_correct())
|
585
676
|
print("Testing `get_obs_websocket_config_values`:")
|
586
677
|
try:
|
587
678
|
get_obs_websocket_config_values()
|
@@ -595,10 +686,13 @@ def main():
|
|
595
686
|
print("Testing `stop_replay_buffer`:")
|
596
687
|
stop_replay_buffer()
|
597
688
|
print("Testing `save_replay_buffer`:")
|
598
|
-
|
689
|
+
try:
|
690
|
+
save_replay_buffer()
|
691
|
+
except Exception as e:
|
692
|
+
print(f"Could not save replay buffer: {e}")
|
599
693
|
current_scene = get_current_scene()
|
600
694
|
print("Testing `get_current_scene`:", current_scene)
|
601
|
-
print("Testing `get_source_from_scene` with
|
695
|
+
print("Testing `get_source_from_scene` with current scene:", get_source_from_scene(current_scene))
|
602
696
|
print("Testing `get_record_directory`:", get_record_directory())
|
603
697
|
print("Testing `get_obs_scenes`:", get_obs_scenes())
|
604
698
|
print("Testing `get_screenshot`:", get_screenshot())
|
@@ -612,51 +706,4 @@ def main():
|
|
612
706
|
if __name__ == '__main__':
|
613
707
|
logging.basicConfig(level=logging.INFO)
|
614
708
|
connect_to_obs_sync()
|
615
|
-
set_fit_to_screen_for_scene_items(get_current_scene())
|
616
|
-
# main()
|
617
|
-
# connect_to_obs_sync()
|
618
|
-
# img = get_screenshot_PIL(source_name="Display Capture 2", compression=75, img_format='png', width=1280, height=720)
|
619
|
-
# img.show()
|
620
|
-
# while True:
|
621
|
-
# print(get_active_source())
|
622
|
-
# time.sleep(3)
|
623
|
-
# i = 100
|
624
|
-
# for i in range(1, 100):
|
625
|
-
# print(f"Getting screenshot {i}")
|
626
|
-
# start = time.time()
|
627
|
-
# # get_screenshot(compression=95)
|
628
|
-
# # get_screenshot_base64(compression=95, width=1280, height=720)
|
629
|
-
|
630
|
-
# img = get_screenshot_PIL(compression=i, img_format='jpg', width=1280, height=720)
|
631
|
-
# end = time.time()
|
632
|
-
# print(f"Time taken to get screenshot with compression {i}: {end - start} seconds")
|
633
|
-
|
634
|
-
# for i in range(1, 100):
|
635
|
-
# print(f"Getting screenshot {i}")
|
636
|
-
# start = time.time()
|
637
|
-
# # get_screenshot(compression=95)
|
638
|
-
# # get_screenshot_base64(compression=95, width=1280, height=720)
|
639
|
-
|
640
|
-
# img = get_screenshot_PIL(compression=i, img_format='jpg', width=2560, height=1440)
|
641
|
-
# end = time.time()
|
642
|
-
# print(f"Time taken to get screenshot full sized jpg with compression {i}: {end - start} seconds")
|
643
|
-
|
644
|
-
# png_img = get_screenshot_PIL(compression=75, img_format='png', width=1280, height=720)
|
645
|
-
|
646
|
-
# jpg_img = get_screenshot_PIL(compression=100, img_format='jpg', width=2560, height=1440)
|
647
|
-
|
648
|
-
# png_img.show()
|
649
|
-
# jpg_img.show()
|
650
|
-
|
651
|
-
# start = time.time()
|
652
|
-
# with mss() as sct:
|
653
|
-
# monitor = sct.monitors[1]
|
654
|
-
# sct_img = sct.grab(monitor)
|
655
|
-
# img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
|
656
|
-
# img.show()
|
657
|
-
# end = time.time()
|
658
|
-
# print(f"Time taken to get screenshot with mss: {end - start} seconds")
|
659
|
-
|
660
|
-
|
661
|
-
# print(get_screenshot_base64(compression=75, width=1280, height=720))
|
662
|
-
|
709
|
+
set_fit_to_screen_for_scene_items(get_current_scene())
|
@@ -577,7 +577,7 @@ if __name__ == "__main__":
|
|
577
577
|
keep_newline = args.keep_newline
|
578
578
|
obs_ocr = args.obs_ocr
|
579
579
|
|
580
|
-
obs.connect_to_obs_sync(
|
580
|
+
obs.connect_to_obs_sync(check_output=False)
|
581
581
|
|
582
582
|
# Start config change checker thread
|
583
583
|
config_check_thread = ConfigChangeCheckThread()
|
@@ -1014,7 +1014,6 @@ class OneOCR:
|
|
1014
1014
|
res = ocr_resp['text']
|
1015
1015
|
|
1016
1016
|
if multiple_crop_coords:
|
1017
|
-
logger.info(f"Getting multiple crop coords for {len(filtered_lines)} lines")
|
1018
1017
|
for line in filtered_lines:
|
1019
1018
|
crop_coords_list.append(
|
1020
1019
|
(line['bounding_rect']['x1'] - 5, line['bounding_rect']['y1'] - 5,
|
@@ -1434,13 +1433,10 @@ class localLLMOCR:
|
|
1434
1433
|
self.keep_warm = config.get('keep_warm', True)
|
1435
1434
|
self.custom_prompt = config.get('prompt', None)
|
1436
1435
|
self.available = True
|
1437
|
-
if any(x in self.api_url for x in ['localhost', '127.0.0.1']):
|
1438
|
-
if not self.check_connection(self.api_url):
|
1439
|
-
logger.warning('Local LLM OCR API is not reachable')
|
1440
|
-
return
|
1441
1436
|
self.client = openai.OpenAI(
|
1442
1437
|
base_url=self.api_url.replace('/v1/chat/completions', '/v1'),
|
1443
|
-
api_key=self.api_key
|
1438
|
+
api_key=self.api_key,
|
1439
|
+
timeout=3
|
1444
1440
|
)
|
1445
1441
|
if self.client.models.retrieve(self.model):
|
1446
1442
|
self.model = self.model
|
@@ -1450,24 +1446,6 @@ class localLLMOCR:
|
|
1450
1446
|
self.keep_llm_hot_thread.start()
|
1451
1447
|
except Exception as e:
|
1452
1448
|
logger.warning(f'Error initializing Local LLM OCR, Local LLM OCR will not work!')
|
1453
|
-
|
1454
|
-
def check_connection(self, url, port=None):
|
1455
|
-
import http.client
|
1456
|
-
conn = http.client.HTTPConnection(url, port or 1234, timeout=0.1)
|
1457
|
-
try:
|
1458
|
-
conn.request("GET", "/v1/models")
|
1459
|
-
response = conn.getresponse()
|
1460
|
-
if response.status == 200:
|
1461
|
-
logger.info('Local LLM OCR API is reachable')
|
1462
|
-
return True
|
1463
|
-
else:
|
1464
|
-
logger.warning('Local LLM OCR API is not reachable')
|
1465
|
-
return False
|
1466
|
-
except Exception as e:
|
1467
|
-
logger.warning(f'Error connecting to Local LLM OCR API: {e}')
|
1468
|
-
return False
|
1469
|
-
finally:
|
1470
|
-
conn.close()
|
1471
1449
|
|
1472
1450
|
def keep_llm_warm(self):
|
1473
1451
|
def ocr_blank_black_image():
|
@@ -559,6 +559,7 @@ class VAD:
|
|
559
559
|
add_audio_on_no_results: bool = False
|
560
560
|
cut_and_splice_segments: bool = False
|
561
561
|
splice_padding: float = 0.1
|
562
|
+
use_cpu_for_inference: bool = False
|
562
563
|
|
563
564
|
def is_silero(self):
|
564
565
|
return self.selected_vad_model == SILERO or self.backup_vad_model == SILERO
|
GameSentenceMiner/vad.py
CHANGED
@@ -167,7 +167,8 @@ class WhisperVADProcessor(VADProcessor):
|
|
167
167
|
import stable_whisper as whisper
|
168
168
|
if not self.vad_model:
|
169
169
|
with warnings.catch_warnings():
|
170
|
-
|
170
|
+
warnings.simplefilter("ignore")
|
171
|
+
self.vad_model = whisper.load_model(get_config().vad.whisper_model, device="cpu" if get_config().vad.use_cpu_for_inference else None)
|
171
172
|
logger.info(f"Whisper model '{get_config().vad.whisper_model}' loaded.")
|
172
173
|
return self.vad_model
|
173
174
|
|
@@ -181,6 +182,7 @@ class WhisperVADProcessor(VADProcessor):
|
|
181
182
|
|
182
183
|
# Transcribe the audio using Whisper
|
183
184
|
with warnings.catch_warnings():
|
185
|
+
warnings.simplefilter("ignore")
|
184
186
|
result: WhisperResult = self.vad_model.transcribe(temp_wav, vad=True, language=get_config().vad.language,
|
185
187
|
temperature=0.0)
|
186
188
|
voice_activity = []
|
@@ -1,10 +1,10 @@
|
|
1
1
|
GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
GameSentenceMiner/anki.py,sha256=4Tq6OGjfN-5tYorYRWiih7FZjSKMG6amrLv6DFKkFQc,25344
|
3
|
-
GameSentenceMiner/config_gui.py,sha256=
|
3
|
+
GameSentenceMiner/config_gui.py,sha256=i79PrY2pP8_VKvIL7uoDv5cgHvCCQBIe0mS_YnX2AVg,140792
|
4
4
|
GameSentenceMiner/gametext.py,sha256=fgBgLchezpauWELE9Y5G3kVCLfAneD0X4lJFoI3FYbs,10351
|
5
5
|
GameSentenceMiner/gsm.py,sha256=4mJn5v4WKqKAJEtph5e0v4YPVDOpvFN1ylV2vQvf_Dg,31913
|
6
|
-
GameSentenceMiner/obs.py,sha256=
|
7
|
-
GameSentenceMiner/vad.py,sha256=
|
6
|
+
GameSentenceMiner/obs.py,sha256=eejvd6N7_3YeC0FwED9WOZAeYmKWkA27vNT7ylh0B8k,28722
|
7
|
+
GameSentenceMiner/vad.py,sha256=EEUFLDbkJ4jYmctfVaV99BwMNRuLx7DLRTYmKUo-myw,19394
|
8
8
|
GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
9
|
GameSentenceMiner/ai/ai_prompting.py,sha256=41xdBzE88Jlt12A0D-T_cMfLO5j6MSxfniOptpwNZm0,24068
|
10
10
|
GameSentenceMiner/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -15,20 +15,20 @@ GameSentenceMiner/assets/icon32.png,sha256=Kww0hU_qke9_22wBuO_Nq0Dv2SfnOLwMhCyGg
|
|
15
15
|
GameSentenceMiner/assets/icon512.png,sha256=HxUj2GHjyQsk8NV433256UxU9phPhtjCY-YB_7W4sqs,192487
|
16
16
|
GameSentenceMiner/assets/icon64.png,sha256=N8xgdZXvhqVQP9QUK3wX5iqxX9LxHljD7c-Bmgim6tM,9301
|
17
17
|
GameSentenceMiner/assets/pickaxe.png,sha256=VfIGyXyIZdzEnVcc4PmG3wszPMO1W4KCT7Q_nFK6eSE,1403829
|
18
|
-
GameSentenceMiner/locales/en_us.json,sha256=
|
19
|
-
GameSentenceMiner/locales/ja_jp.json,sha256
|
20
|
-
GameSentenceMiner/locales/zh_cn.json,sha256=
|
18
|
+
GameSentenceMiner/locales/en_us.json,sha256=4lCV34FnDOe0c02qHlHnfujQedmqHSL-feN3lYCCCfs,26744
|
19
|
+
GameSentenceMiner/locales/ja_jp.json,sha256=LNLo2qIugMcDGiPbSo018zVAU8K_HG8Q4zvIcsHUzTA,28517
|
20
|
+
GameSentenceMiner/locales/zh_cn.json,sha256=lZYB3HAcxhVCSVWcnvuepuCvn6_Y2mvd0-SKJEYx_ko,24829
|
21
21
|
GameSentenceMiner/ocr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
22
|
GameSentenceMiner/ocr/gsm_ocr_config.py,sha256=DfcR3bHTu26JJerLzqfW_KpdgUBSrRV4hqSy_LYclps,5967
|
23
23
|
GameSentenceMiner/ocr/ocrconfig.py,sha256=_tY8mjnzHMJrLS8E5pHqYXZjMuLoGKYgJwdhYgN-ny4,6466
|
24
24
|
GameSentenceMiner/ocr/owocr_area_selector.py,sha256=Rm1_nuZotJhfOfoJ_3mesh9udtOBjYqKhnAvSief6fo,29181
|
25
|
-
GameSentenceMiner/ocr/owocr_helper.py,sha256=
|
25
|
+
GameSentenceMiner/ocr/owocr_helper.py,sha256=0pzeWY0L49pH_KjAg4enmg5t_Om0wFb4R2BTP6yBISI,28428
|
26
26
|
GameSentenceMiner/ocr/ss_picker.py,sha256=0IhxUdaKruFpZyBL-8SpxWg7bPrlGpy3lhTcMMZ5rwo,5224
|
27
27
|
GameSentenceMiner/owocr/owocr/__init__.py,sha256=87hfN5u_PbL_onLfMACbc0F5j4KyIK9lKnRCj6oZgR0,49
|
28
28
|
GameSentenceMiner/owocr/owocr/__main__.py,sha256=XQaqZY99EKoCpU-gWQjNbTs7Kg17HvBVE7JY8LqIE0o,157
|
29
29
|
GameSentenceMiner/owocr/owocr/config.py,sha256=qM7kISHdUhuygGXOxmgU6Ef2nwBShrZtdqu4InDCViE,8103
|
30
30
|
GameSentenceMiner/owocr/owocr/lens_betterproto.py,sha256=oNoISsPilVVRBBPVDtb4-roJtAhp8ZAuFTci3TGXtMc,39141
|
31
|
-
GameSentenceMiner/owocr/owocr/ocr.py,sha256=
|
31
|
+
GameSentenceMiner/owocr/owocr/ocr.py,sha256=JXUboda00-1GYzYYDTiYcPaeWKKwvMdQ-q_CHVB7h10,70014
|
32
32
|
GameSentenceMiner/owocr/owocr/run.py,sha256=xbBpyFCVfITZDztsRLT8_sX6BGf1o5LxOPxE9zUWfQc,79975
|
33
33
|
GameSentenceMiner/owocr/owocr/screen_coordinate_picker.py,sha256=Na6XStbQBtpQUSdbN3QhEswtKuU1JjReFk_K8t5ezQE,3395
|
34
34
|
GameSentenceMiner/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -37,11 +37,11 @@ GameSentenceMiner/tools/furigana_filter_preview.py,sha256=BXv7FChPEJW_VeG5XYt6su
|
|
37
37
|
GameSentenceMiner/tools/ss_selector.py,sha256=cbjMxiKOCuOfbRvLR_PCRlykBrGtm1LXd6u5czPqkmc,4793
|
38
38
|
GameSentenceMiner/tools/window_transparency.py,sha256=GtbxbmZg0-UYPXhfHff-7IKZyY2DKe4B9GdyovfmpeM,8166
|
39
39
|
GameSentenceMiner/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
40
|
-
GameSentenceMiner/util/configuration.py,sha256=
|
40
|
+
GameSentenceMiner/util/configuration.py,sha256=cpgusOu4lItCBsZyB8QoAJaXjDovIXNlqyKzh2GMYzw,40241
|
41
41
|
GameSentenceMiner/util/db.py,sha256=2bO0rD4i8A1hhsRBER-wgZy9IK17ibRbI8DHxdKvYsI,16598
|
42
42
|
GameSentenceMiner/util/electron_config.py,sha256=KfeJToeFFVw0IR5MKa-gBzpzaGrU-lyJbR9z-sDEHYU,8767
|
43
43
|
GameSentenceMiner/util/ffmpeg.py,sha256=jA-cFtCmdCWrUSPpdtFSLr-GSoqs4qNUzW20v4HPHf0,28715
|
44
|
-
GameSentenceMiner/util/get_overlay_coords.py,sha256=
|
44
|
+
GameSentenceMiner/util/get_overlay_coords.py,sha256=P5tI7H0cnveGs33aQdvJGy9DV6aIAGh8K8Al1XjNPzw,15114
|
45
45
|
GameSentenceMiner/util/gsm_utils.py,sha256=Piwv88Q9av2LBeN7M6QDi0Mp0_R2lNbkcI6ekK5hd2o,11851
|
46
46
|
GameSentenceMiner/util/model.py,sha256=R-_RYTYLSDNgBoVTPuPBcIHeOznIqi_vBzQ7VQ20WYk,6727
|
47
47
|
GameSentenceMiner/util/notification.py,sha256=YBhf_mSo_i3cjBz-pmeTPx3wchKiG9BK2VBdZSa2prQ,4597
|
@@ -72,9 +72,9 @@ GameSentenceMiner/web/templates/index.html,sha256=LqXZx7-NE42pXSpHNZ3To680rD-vt9
|
|
72
72
|
GameSentenceMiner/web/templates/text_replacements.html,sha256=tV5c8mCaWSt_vKuUpbdbLAzXZ3ATZeDvQ9PnnAfqY0M,8598
|
73
73
|
GameSentenceMiner/web/templates/utility.html,sha256=3flZinKNqUJ7pvrZk6xu__v67z44rXnaK7UTZ303R-8,16946
|
74
74
|
GameSentenceMiner/wip/__init___.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
75
|
-
gamesentenceminer-2.15.
|
76
|
-
gamesentenceminer-2.15.
|
77
|
-
gamesentenceminer-2.15.
|
78
|
-
gamesentenceminer-2.15.
|
79
|
-
gamesentenceminer-2.15.
|
80
|
-
gamesentenceminer-2.15.
|
75
|
+
gamesentenceminer-2.15.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
76
|
+
gamesentenceminer-2.15.4.dist-info/METADATA,sha256=kiXbjNcMkmEwfiVBKr3AH0Wtil4Hxe32XJWNPJjulHE,7317
|
77
|
+
gamesentenceminer-2.15.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
78
|
+
gamesentenceminer-2.15.4.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
79
|
+
gamesentenceminer-2.15.4.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
80
|
+
gamesentenceminer-2.15.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|