GameSentenceMiner 2.9.2__py3-none-any.whl → 2.9.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/anki.py +4 -0
- GameSentenceMiner/assets/__init__.py +0 -0
- GameSentenceMiner/assets/icon.png +0 -0
- GameSentenceMiner/assets/icon128.png +0 -0
- GameSentenceMiner/assets/icon256.png +0 -0
- GameSentenceMiner/assets/icon32.png +0 -0
- GameSentenceMiner/assets/icon512.png +0 -0
- GameSentenceMiner/assets/icon64.png +0 -0
- GameSentenceMiner/assets/pickaxe.png +0 -0
- GameSentenceMiner/configuration.py +10 -4
- GameSentenceMiner/ffmpeg.py +4 -3
- GameSentenceMiner/gsm.py +6 -5
- GameSentenceMiner/notification.py +22 -4
- GameSentenceMiner/obs.py +3 -3
- GameSentenceMiner/package.py +0 -1
- GameSentenceMiner/util.py +0 -9
- GameSentenceMiner/web/texthooking_page.py +69 -51
- {gamesentenceminer-2.9.2.dist-info → gamesentenceminer-2.9.4.dist-info}/METADATA +1 -1
- {gamesentenceminer-2.9.2.dist-info → gamesentenceminer-2.9.4.dist-info}/RECORD +23 -16
- GameSentenceMiner/obs_back.py +0 -309
- {gamesentenceminer-2.9.2.dist-info → gamesentenceminer-2.9.4.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.9.2.dist-info → gamesentenceminer-2.9.4.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.9.2.dist-info → gamesentenceminer-2.9.4.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.9.2.dist-info → gamesentenceminer-2.9.4.dist-info}/top_level.txt +0 -0
GameSentenceMiner/anki.py
CHANGED
@@ -82,6 +82,8 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
82
82
|
for key, value in get_config().anki.anki_custom_fields.items():
|
83
83
|
note['fields'][key] = str(value)
|
84
84
|
|
85
|
+
|
86
|
+
notification.open_browser_window(1)
|
85
87
|
invoke("updateNoteFields", note=note)
|
86
88
|
tags = []
|
87
89
|
if get_config().anki.custom_tags:
|
@@ -94,9 +96,11 @@ def update_anki_card(last_note: AnkiCard, note=None, audio_path='', video_path='
|
|
94
96
|
logger.info(f"UPDATED ANKI CARD FOR {last_note.noteId}")
|
95
97
|
if get_config().features.notify_on_update:
|
96
98
|
notification.send_note_updated(tango)
|
99
|
+
notification.open_browser_window(last_note.noteId)
|
97
100
|
if get_config().features.open_anki_edit:
|
98
101
|
notification.open_anki_card(last_note.noteId)
|
99
102
|
|
103
|
+
|
100
104
|
if get_config().audio.external_tool and get_config().audio.external_tool_enabled and update_audio:
|
101
105
|
open_audio_in_external(f"{get_config().audio.anki_media_collection}/{audio_in_anki}")
|
102
106
|
|
File without changes
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -39,6 +39,13 @@ DEFAULT_CONFIG = 'Default'
|
|
39
39
|
|
40
40
|
current_game = ''
|
41
41
|
|
42
|
+
def is_linux():
|
43
|
+
return platform == 'linux'
|
44
|
+
|
45
|
+
|
46
|
+
def is_windows():
|
47
|
+
return platform == 'win32'
|
48
|
+
|
42
49
|
|
43
50
|
class Language(Enum):
|
44
51
|
JAPANESE = "ja"
|
@@ -153,7 +160,7 @@ class Audio:
|
|
153
160
|
extension: str = 'opus'
|
154
161
|
beginning_offset: float = 0.0
|
155
162
|
end_offset: float = 0.5
|
156
|
-
ffmpeg_reencode_options: str = '-c:a libopus -f opus -af \"afade=t=in:d=0.10\"'
|
163
|
+
ffmpeg_reencode_options: str = '-c:a libopus -f opus -af \"afade=t=in:d=0.10\"' if is_windows() else ''
|
157
164
|
external_tool: str = ""
|
158
165
|
anki_media_collection: str = ""
|
159
166
|
external_tool_enabled: bool = True
|
@@ -619,7 +626,7 @@ console_handler.setFormatter(formatter)
|
|
619
626
|
logger.addHandler(console_handler)
|
620
627
|
|
621
628
|
# Create rotating file handler with level DEBUG
|
622
|
-
if 'gsm' in sys.argv[0]:
|
629
|
+
if 'gsm' in sys.argv[0].lower() or 'gamesentenceminer' in sys.argv[0].lower():
|
623
630
|
file_handler = RotatingFileHandler(get_log_path(), maxBytes=1024 * 1024, backupCount=5, encoding='utf-8')
|
624
631
|
file_handler.setLevel(logging.DEBUG)
|
625
632
|
file_handler.setFormatter(formatter)
|
@@ -637,5 +644,4 @@ class GsmAppState:
|
|
637
644
|
self.previous_screenshot = None
|
638
645
|
self.previous_replay = None
|
639
646
|
|
640
|
-
gsm_state = GsmAppState()
|
641
|
-
|
647
|
+
gsm_state = GsmAppState()
|
GameSentenceMiner/ffmpeg.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import shutil
|
2
2
|
import tempfile
|
3
3
|
|
4
|
+
import GameSentenceMiner.configuration
|
4
5
|
from GameSentenceMiner import obs, util, configuration
|
5
6
|
from GameSentenceMiner.configuration import *
|
6
7
|
from GameSentenceMiner.text_log import initial_time
|
@@ -8,10 +9,10 @@ from GameSentenceMiner.util import *
|
|
8
9
|
|
9
10
|
|
10
11
|
def get_ffmpeg_path():
|
11
|
-
return os.path.join(get_app_directory(), "ffmpeg", "ffmpeg.exe") if
|
12
|
+
return os.path.join(get_app_directory(), "ffmpeg", "ffmpeg.exe") if is_windows() else "ffmpeg"
|
12
13
|
|
13
14
|
def get_ffprobe_path():
|
14
|
-
return os.path.join(get_app_directory(), "ffmpeg", "ffprobe.exe") if
|
15
|
+
return os.path.join(get_app_directory(), "ffmpeg", "ffprobe.exe") if is_windows() else "ffprobe"
|
15
16
|
|
16
17
|
ffmpeg_base_command_list = [get_ffmpeg_path(), "-hide_banner", "-loglevel", "error", '-nostdin']
|
17
18
|
|
@@ -398,7 +399,7 @@ def convert_audio_to_wav(input_audio, output_wav):
|
|
398
399
|
"-i", input_audio,
|
399
400
|
"-ar", "16000", # Resample to 16kHz
|
400
401
|
"-ac", "1", # Convert to mono
|
401
|
-
"-af", "afftdn,dialoguenhance" if not
|
402
|
+
"-af", "afftdn,dialoguenhance" if not is_linux() else "afftdn",
|
402
403
|
output_wav
|
403
404
|
]
|
404
405
|
logger.debug(" ".join(command))
|
GameSentenceMiner/gsm.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import asyncio
|
2
|
-
import shutil
|
3
2
|
import sys
|
4
3
|
|
5
4
|
from GameSentenceMiner.vad.result import VADResult
|
@@ -39,7 +38,7 @@ try:
|
|
39
38
|
from GameSentenceMiner.web import texthooking_page
|
40
39
|
from GameSentenceMiner.web.texthooking_page import run_text_hooker_page
|
41
40
|
except Exception as e:
|
42
|
-
from GameSentenceMiner.configuration import logger
|
41
|
+
from GameSentenceMiner.configuration import logger, is_linux, is_windows
|
43
42
|
import time
|
44
43
|
logger.info("Something bad happened during import/initialization, closing in 5 seconds")
|
45
44
|
logger.exception(e)
|
@@ -74,7 +73,6 @@ class VideoToAudioHandler(FileSystemEventHandler):
|
|
74
73
|
|
75
74
|
def process_replay(self, video_path):
|
76
75
|
vad_trimmed_audio = ''
|
77
|
-
print(video_path)
|
78
76
|
if "previous.mkv" in video_path:
|
79
77
|
os.remove(video_path)
|
80
78
|
video_path = gsm_state.previous_replay
|
@@ -682,7 +680,7 @@ async def register_scene_switcher_callback():
|
|
682
680
|
|
683
681
|
await obs.register_scene_change_callback(scene_switcher_callback)
|
684
682
|
|
685
|
-
async def
|
683
|
+
async def async_main(reloading=False):
|
686
684
|
global root, settings_window
|
687
685
|
initialize(reloading)
|
688
686
|
logger.info("Script started.")
|
@@ -721,11 +719,14 @@ async def main(reloading=False):
|
|
721
719
|
except Exception as e:
|
722
720
|
logger.error(f"Error stopping observer: {e}")
|
723
721
|
|
722
|
+
def main():
|
723
|
+
asyncio.run(async_main())
|
724
|
+
|
724
725
|
|
725
726
|
if __name__ == "__main__":
|
726
727
|
logger.info("Starting GSM")
|
727
728
|
try:
|
728
|
-
asyncio.run(
|
729
|
+
asyncio.run(async_main())
|
729
730
|
except Exception as e:
|
730
731
|
logger.exception(e)
|
731
732
|
time.sleep(5)
|
@@ -2,14 +2,11 @@ import platform
|
|
2
2
|
|
3
3
|
import requests
|
4
4
|
from plyer import notification
|
5
|
-
|
6
|
-
from GameSentenceMiner.util import is_windows
|
5
|
+
from GameSentenceMiner.configuration import logger, is_windows
|
7
6
|
|
8
7
|
if is_windows():
|
9
8
|
from win10toast import ToastNotifier
|
10
9
|
|
11
|
-
from GameSentenceMiner.configuration import logger
|
12
|
-
|
13
10
|
if is_windows():
|
14
11
|
class MyToastNotifier(ToastNotifier):
|
15
12
|
def __init__(self):
|
@@ -24,6 +21,27 @@ if is_windows():
|
|
24
21
|
else:
|
25
22
|
notifier = notification
|
26
23
|
|
24
|
+
def open_browser_window(note_id):
|
25
|
+
url = "http://localhost:8765"
|
26
|
+
headers = {'Content-Type': 'application/json'}
|
27
|
+
|
28
|
+
data = {
|
29
|
+
"action": "guiBrowse",
|
30
|
+
"version": 6,
|
31
|
+
"params": {
|
32
|
+
"query": f"nid:{note_id}"
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
try:
|
37
|
+
response = requests.post(url, json=data, headers=headers)
|
38
|
+
if response.status_code == 200:
|
39
|
+
logger.info(f"Opened Anki note with ID {note_id}")
|
40
|
+
else:
|
41
|
+
logger.error(f"Failed to open Anki note with ID {note_id}")
|
42
|
+
except Exception as e:
|
43
|
+
logger.info(f"Error connecting to AnkiConnect: {e}")
|
44
|
+
|
27
45
|
|
28
46
|
def open_anki_card(note_id):
|
29
47
|
url = "http://localhost:8765"
|
GameSentenceMiner/obs.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import asyncio
|
2
|
-
import logging
|
3
2
|
import os.path
|
4
3
|
import subprocess
|
5
4
|
import threading
|
@@ -8,6 +7,7 @@ import psutil
|
|
8
7
|
|
9
8
|
import obsws_python as obs
|
10
9
|
|
10
|
+
import GameSentenceMiner.configuration
|
11
11
|
from GameSentenceMiner import util, configuration
|
12
12
|
from GameSentenceMiner.configuration import *
|
13
13
|
from GameSentenceMiner.model import *
|
@@ -140,7 +140,7 @@ async def connect_to_obs(retry_count=0):
|
|
140
140
|
if not get_config().obs.enabled:
|
141
141
|
return
|
142
142
|
|
143
|
-
if
|
143
|
+
if GameSentenceMiner.configuration.is_windows():
|
144
144
|
get_obs_websocket_config_values()
|
145
145
|
|
146
146
|
while True:
|
@@ -174,7 +174,7 @@ def connect_to_obs_sync(retry_count=0):
|
|
174
174
|
if not get_config().obs.enabled or client:
|
175
175
|
return
|
176
176
|
|
177
|
-
if
|
177
|
+
if GameSentenceMiner.configuration.is_windows():
|
178
178
|
get_obs_websocket_config_values()
|
179
179
|
|
180
180
|
while True:
|
GameSentenceMiner/package.py
CHANGED
GameSentenceMiner/util.py
CHANGED
@@ -7,7 +7,6 @@ import subprocess
|
|
7
7
|
import threading
|
8
8
|
import time
|
9
9
|
from datetime import datetime
|
10
|
-
from sys import platform
|
11
10
|
|
12
11
|
from rapidfuzz import process
|
13
12
|
|
@@ -131,12 +130,6 @@ def run_agent_and_hook(pname, agent_script):
|
|
131
130
|
keep_running = False
|
132
131
|
|
133
132
|
|
134
|
-
def is_linux():
|
135
|
-
return platform == 'linux'
|
136
|
-
|
137
|
-
def is_windows():
|
138
|
-
return platform == 'win32'
|
139
|
-
|
140
133
|
# def run_command(command, shell=False, input=None, capture_output=False, timeout=None, check=False, **kwargs):
|
141
134
|
# # Use shell=True if the OS is Linux, otherwise shell=False
|
142
135
|
# if is_linux():
|
@@ -258,8 +251,6 @@ TEXT_REPLACEMENTS_FILE = os.path.join(get_app_directory(), 'config', 'text_repla
|
|
258
251
|
OCR_REPLACEMENTS_FILE = os.path.join(get_app_directory(), 'config', 'ocr_replacements.json')
|
259
252
|
os.makedirs(os.path.dirname(TEXT_REPLACEMENTS_FILE), exist_ok=True)
|
260
253
|
|
261
|
-
import urllib.request
|
262
|
-
|
263
254
|
# if not os.path.exists(OCR_REPLACEMENTS_FILE):
|
264
255
|
# url = "https://raw.githubusercontent.com/bpwhelan/GameSentenceMiner/refs/heads/main/electron-src/assets/ocr_replacements.json"
|
265
256
|
# try:
|
@@ -247,7 +247,7 @@ def clear_history():
|
|
247
247
|
|
248
248
|
async def add_event_to_texthooker(line: GameLine):
|
249
249
|
new_event = event_manager.add_gameline(line)
|
250
|
-
await
|
250
|
+
await websocket_server_thread.send_text({
|
251
251
|
'event': 'text_received',
|
252
252
|
'sentence': line.text,
|
253
253
|
'data': new_event.to_serializable()
|
@@ -294,37 +294,6 @@ def play_audio():
|
|
294
294
|
return jsonify({}), 200
|
295
295
|
|
296
296
|
|
297
|
-
connected_clients = set()
|
298
|
-
|
299
|
-
async def websocket_handler(websocket):
|
300
|
-
logger.debug(f"Client connected: {websocket.remote_address}")
|
301
|
-
connected_clients.add(websocket)
|
302
|
-
try:
|
303
|
-
async for message in websocket:
|
304
|
-
try:
|
305
|
-
data = json.loads(message)
|
306
|
-
if 'type' in data and data['type'] == 'get_events':
|
307
|
-
initial_events = [{'id': 1, 'text': 'Initial event from WebSocket'}, {'id': 2, 'text': 'Another initial event'}]
|
308
|
-
await websocket.send(json.dumps({'event': 'initial_events', 'payload': initial_events}))
|
309
|
-
elif 'update_checkbox' in data:
|
310
|
-
print(f"Received checkbox update: {data}")
|
311
|
-
# Handle checkbox update logic
|
312
|
-
pass
|
313
|
-
await websocket.send(json.dumps({'response': f'Server received: {message}'}))
|
314
|
-
except json.JSONDecodeError:
|
315
|
-
await websocket.send(json.dumps({'error': 'Invalid JSON format'}))
|
316
|
-
except websockets.exceptions.ConnectionClosedError:
|
317
|
-
print(f"Client disconnected abruptly: {websocket.remote_address}")
|
318
|
-
except websockets.exceptions.ConnectionClosedOK:
|
319
|
-
print(f"Client disconnected gracefully: {websocket.remote_address}")
|
320
|
-
finally:
|
321
|
-
connected_clients.discard(websocket)
|
322
|
-
|
323
|
-
async def broadcast_message(message):
|
324
|
-
if connected_clients:
|
325
|
-
for client in connected_clients:
|
326
|
-
await client.send(json.dumps(message))
|
327
|
-
|
328
297
|
# async def main():
|
329
298
|
# async with websockets.serve(websocket_handler, "localhost", 8765): # Choose a port for WebSocket
|
330
299
|
# print("WebSocket server started on ws://localhost:8765/ws (adjust as needed)")
|
@@ -360,7 +329,7 @@ def are_lines_selected():
|
|
360
329
|
|
361
330
|
def reset_checked_lines():
|
362
331
|
async def send_reset_message():
|
363
|
-
await
|
332
|
+
await websocket_server_thread.send_text({
|
364
333
|
'event': 'reset_checkboxes',
|
365
334
|
})
|
366
335
|
event_manager.reset_checked_lines()
|
@@ -383,23 +352,69 @@ def start_web_server():
|
|
383
352
|
|
384
353
|
import signal
|
385
354
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
355
|
+
websocket_server_thread = None
|
356
|
+
websocket_queue = queue.Queue()
|
357
|
+
paused = False
|
358
|
+
|
359
|
+
|
360
|
+
class WebsocketServerThread(threading.Thread):
|
361
|
+
def __init__(self, read):
|
362
|
+
super().__init__(daemon=True)
|
363
|
+
self._loop = None
|
364
|
+
self.read = read
|
365
|
+
self.clients = set()
|
366
|
+
self._event = threading.Event()
|
367
|
+
|
368
|
+
@property
|
369
|
+
def loop(self):
|
370
|
+
self._event.wait()
|
371
|
+
return self._loop
|
372
|
+
|
373
|
+
async def send_text_coroutine(self, message):
|
374
|
+
for client in self.clients:
|
375
|
+
await client.send(message)
|
376
|
+
|
377
|
+
async def server_handler(self, websocket):
|
378
|
+
self.clients.add(websocket)
|
379
|
+
try:
|
380
|
+
async for message in websocket:
|
381
|
+
if self.read and not paused:
|
382
|
+
websocket_queue.put(message)
|
383
|
+
try:
|
384
|
+
await websocket.send('True')
|
385
|
+
except websockets.exceptions.ConnectionClosedOK:
|
386
|
+
pass
|
387
|
+
else:
|
388
|
+
try:
|
389
|
+
await websocket.send('False')
|
390
|
+
except websockets.exceptions.ConnectionClosedOK:
|
391
|
+
pass
|
392
|
+
except websockets.exceptions.ConnectionClosedError:
|
393
|
+
pass
|
394
|
+
finally:
|
395
|
+
self.clients.remove(websocket)
|
396
|
+
|
397
|
+
async def send_text(self, text):
|
398
|
+
if text:
|
399
|
+
return asyncio.run_coroutine_threadsafe(
|
400
|
+
self.send_text_coroutine(json.dumps(text)), self.loop)
|
401
|
+
|
402
|
+
def stop_server(self):
|
403
|
+
self.loop.call_soon_threadsafe(self._stop_event.set)
|
404
|
+
|
405
|
+
def run(self):
|
406
|
+
async def main():
|
407
|
+
self._loop = asyncio.get_running_loop()
|
408
|
+
self._stop_event = stop_event = asyncio.Event()
|
409
|
+
self._event.set()
|
410
|
+
self.server = start_server = websockets.serve(self.server_handler,
|
411
|
+
"0.0.0.0",
|
412
|
+
get_config().advanced.texthooker_communication_websocket_port,
|
413
|
+
max_size=1000000000)
|
414
|
+
async with start_server:
|
415
|
+
await stop_event.wait()
|
416
|
+
|
417
|
+
asyncio.run(main())
|
403
418
|
|
404
419
|
def handle_exit_signal(loop):
|
405
420
|
logger.info("Received exit signal. Shutting down...")
|
@@ -407,13 +422,16 @@ def handle_exit_signal(loop):
|
|
407
422
|
task.cancel()
|
408
423
|
|
409
424
|
async def texthooker_page_coro():
|
425
|
+
global websocket_server_thread
|
410
426
|
# Run the WebSocket server in the asyncio event loop
|
411
427
|
flask_thread = threading.Thread(target=start_web_server)
|
412
428
|
flask_thread.daemon = True
|
413
429
|
flask_thread.start()
|
414
430
|
|
431
|
+
websocket_server_thread = WebsocketServerThread(read=True)
|
432
|
+
websocket_server_thread.start()
|
433
|
+
|
415
434
|
# Keep the main asyncio event loop running (for the WebSocket server)
|
416
|
-
await run_websocket_server()
|
417
435
|
|
418
436
|
def run_text_hooker_page():
|
419
437
|
try:
|
@@ -1,21 +1,28 @@
|
|
1
1
|
GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
GameSentenceMiner/anki.py,sha256=
|
2
|
+
GameSentenceMiner/anki.py,sha256=HAYblBjkth5p0tx9Z0LNQn6zuQC8_54RIB2BMzN8-LE,14705
|
3
3
|
GameSentenceMiner/config_gui.py,sha256=h4zz85gfhxSphaJ-IZSu9D4jR70mDlKecZ9JRCO5Noc,80927
|
4
|
-
GameSentenceMiner/configuration.py,sha256=
|
4
|
+
GameSentenceMiner/configuration.py,sha256=KKW6fmpxya4dmXx9cERFVrzsKCTw0vmZrF2HAJDURBU,25667
|
5
5
|
GameSentenceMiner/electron_config.py,sha256=dGcPYCISPehXubYSzsDuI2Gl092MYK0u3bTnkL9Jh1Y,9787
|
6
|
-
GameSentenceMiner/ffmpeg.py,sha256=
|
6
|
+
GameSentenceMiner/ffmpeg.py,sha256=APa2vNdAgxYdG96_Z3Xdh1WqOiWaK6gTLJqzEvCMMeU,18323
|
7
7
|
GameSentenceMiner/gametext.py,sha256=sll-6Pficd4ZXYy8yL8hBrEOSpfa53TOye7vtHHKFN4,6218
|
8
|
-
GameSentenceMiner/gsm.py,sha256=
|
8
|
+
GameSentenceMiner/gsm.py,sha256=olG3BIWjbVHWTsRKmeDVE5X8XrgppWke73Fy1J15dxA,29868
|
9
9
|
GameSentenceMiner/model.py,sha256=1lRyJFf_LND_4O16h8CWVqDfosLgr0ZS6ufBZ3qJHpY,5699
|
10
|
-
GameSentenceMiner/notification.py,sha256=
|
11
|
-
GameSentenceMiner/obs.py,sha256=
|
12
|
-
GameSentenceMiner/
|
13
|
-
GameSentenceMiner/package.py,sha256=YlS6QRMuVlm6mdXx0rlXv9_3erTGS21jaP3PNNWfAH0,1250
|
10
|
+
GameSentenceMiner/notification.py,sha256=e6TOzZJD7RTvMgxaY-V01r5OiocHhdqEIVdAnj4MGSw,3437
|
11
|
+
GameSentenceMiner/obs.py,sha256=JiydRMpfSpNZ0nDAzH8OOECbYbsxMNSGz46lO8lZbvA,14871
|
12
|
+
GameSentenceMiner/package.py,sha256=uu3Yb3pqu8vN5ISzP87YCHlFNR9wxMMf5hPRncTr7ws,1181
|
14
13
|
GameSentenceMiner/ss_selector.py,sha256=csey9H3561-guRJcT6gQN6hXxvylP0CBI0dp2-kwo2Q,4446
|
15
14
|
GameSentenceMiner/text_log.py,sha256=U2_g8THAYeexRiE2bLk_bCt_2ShiA8SQ9VdJsi4riHs,5181
|
16
|
-
GameSentenceMiner/util.py,sha256=
|
15
|
+
GameSentenceMiner/util.py,sha256=ZbK7i1UeOzKyc5WtCcttiGljR_stfu7qpnEpgqFBwro,8976
|
17
16
|
GameSentenceMiner/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
17
|
GameSentenceMiner/ai/ai_prompting.py,sha256=xw8et6XNwQiDXOXZnw8iIntVSg8lni4YYZbgWsK7qDE,10013
|
18
|
+
GameSentenceMiner/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
|
+
GameSentenceMiner/assets/icon.png,sha256=9GRL8uXUAgkUSlvbm9Pv9o2poFVRGdW6s2ub_DeUD9M,937624
|
20
|
+
GameSentenceMiner/assets/icon128.png,sha256=l90j7biwdz5ahwOd5wZ-406ryEV9Pan93dquJQ3e1CI,18395
|
21
|
+
GameSentenceMiner/assets/icon256.png,sha256=JEW46wOrG1KR-907rvFaEdNbPtj5gu0HJmG7qUnIHxQ,51874
|
22
|
+
GameSentenceMiner/assets/icon32.png,sha256=Kww0hU_qke9_22wBuO_Nq0Dv2SfnOLwMhCyGgbgXdg8,6089
|
23
|
+
GameSentenceMiner/assets/icon512.png,sha256=HxUj2GHjyQsk8NV433256UxU9phPhtjCY-YB_7W4sqs,192487
|
24
|
+
GameSentenceMiner/assets/icon64.png,sha256=N8xgdZXvhqVQP9QUK3wX5iqxX9LxHljD7c-Bmgim6tM,9301
|
25
|
+
GameSentenceMiner/assets/pickaxe.png,sha256=VfIGyXyIZdzEnVcc4PmG3wszPMO1W4KCT7Q_nFK6eSE,1403829
|
19
26
|
GameSentenceMiner/communication/__init__.py,sha256=_jGn9PJxtOAOPtJ2rI-Qu9hEHVZVpIvWlxKvqk91_zI,638
|
20
27
|
GameSentenceMiner/communication/send.py,sha256=X0MytGv5hY-uUvkfvdCqQA_ljZFmV6UkJ6in1TA1bUE,217
|
21
28
|
GameSentenceMiner/communication/websocket.py,sha256=8eFZaTtoFggEPdqw2Jl4zqHC2I7J3-Gk27CxVX7SyBo,3277
|
@@ -43,7 +50,7 @@ GameSentenceMiner/vad/vad_utils.py,sha256=_YC6rW2eXSBeLnYbVl_F3na1KCRL90VrnOzKYJ
|
|
43
50
|
GameSentenceMiner/vad/vosk_helper.py,sha256=h7yNHrzrzT-J74UniA0T2ZX8cHqhflCzwyDjoIdKLO4,6479
|
44
51
|
GameSentenceMiner/vad/whisper_helper.py,sha256=B64-Eq_ZMCIyQX_A8uvYz-c48hSXJAyz6tSXNRaLjtA,4020
|
45
52
|
GameSentenceMiner/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
46
|
-
GameSentenceMiner/web/texthooking_page.py,sha256=
|
53
|
+
GameSentenceMiner/web/texthooking_page.py,sha256=7Z7TGDcnj-94Y9ws7bQph4oIXrqf8Q9qVKowKimHWxM,14749
|
47
54
|
GameSentenceMiner/web/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
48
55
|
GameSentenceMiner/web/static/apple-touch-icon.png,sha256=OcMI8af_68DA_tweOsQ5LytTyMwm7-hPW07IfrOVgEs,46132
|
49
56
|
GameSentenceMiner/web/static/favicon-96x96.png,sha256=lOePzjiKl1JY2J1kT_PMdyEnrlJmi5GWbmXJunM12B4,16502
|
@@ -57,9 +64,9 @@ GameSentenceMiner/web/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
|
|
57
64
|
GameSentenceMiner/web/templates/index.html,sha256=HZKiIjiGJV8PGQ9T2aLDUNSfJn71qOwbYCjbRuSIjpY,213583
|
58
65
|
GameSentenceMiner/web/templates/text_replacements.html,sha256=tV5c8mCaWSt_vKuUpbdbLAzXZ3ATZeDvQ9PnnAfqY0M,8598
|
59
66
|
GameSentenceMiner/web/templates/utility.html,sha256=3flZinKNqUJ7pvrZk6xu__v67z44rXnaK7UTZ303R-8,16946
|
60
|
-
gamesentenceminer-2.9.
|
61
|
-
gamesentenceminer-2.9.
|
62
|
-
gamesentenceminer-2.9.
|
63
|
-
gamesentenceminer-2.9.
|
64
|
-
gamesentenceminer-2.9.
|
65
|
-
gamesentenceminer-2.9.
|
67
|
+
gamesentenceminer-2.9.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
68
|
+
gamesentenceminer-2.9.4.dist-info/METADATA,sha256=jNhOj4IaiTEJqGlm4-VCtviW5hmN8J5XnvPBGWgcY0Y,7280
|
69
|
+
gamesentenceminer-2.9.4.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
70
|
+
gamesentenceminer-2.9.4.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
71
|
+
gamesentenceminer-2.9.4.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
72
|
+
gamesentenceminer-2.9.4.dist-info/RECORD,,
|
GameSentenceMiner/obs_back.py
DELETED
@@ -1,309 +0,0 @@
|
|
1
|
-
import os.path
|
2
|
-
import subprocess
|
3
|
-
import threading
|
4
|
-
import time
|
5
|
-
import psutil
|
6
|
-
|
7
|
-
from obswebsocket import obsws, requests, events
|
8
|
-
from obswebsocket.exceptions import ConnectionFailure
|
9
|
-
|
10
|
-
from GameSentenceMiner import util, configuration
|
11
|
-
from GameSentenceMiner.configuration import *
|
12
|
-
from GameSentenceMiner.model import *
|
13
|
-
|
14
|
-
client: obsws = None
|
15
|
-
obs_process_pid = None
|
16
|
-
# logging.getLogger('obswebsocket').setLevel(logging.CRITICAL)
|
17
|
-
OBS_PID_FILE = os.path.join(configuration.get_app_directory(), 'obs-studio', 'obs_pid.txt')
|
18
|
-
|
19
|
-
# REFERENCE: https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md
|
20
|
-
|
21
|
-
|
22
|
-
def get_obs_path():
|
23
|
-
return os.path.join(configuration.get_app_directory(), 'obs-studio/bin/64bit/obs64.exe')
|
24
|
-
|
25
|
-
def is_process_running(pid):
|
26
|
-
try:
|
27
|
-
process = psutil.Process(pid)
|
28
|
-
return 'obs' in process.exe()
|
29
|
-
except (psutil.NoSuchProcess, psutil.AccessDenied, OSError):
|
30
|
-
if os.path.exists(OBS_PID_FILE):
|
31
|
-
os.remove(OBS_PID_FILE)
|
32
|
-
return False
|
33
|
-
|
34
|
-
def start_obs():
|
35
|
-
global obs_process_pid
|
36
|
-
if os.path.exists(OBS_PID_FILE):
|
37
|
-
with open(OBS_PID_FILE, "r") as f:
|
38
|
-
try:
|
39
|
-
obs_process_pid = int(f.read().strip())
|
40
|
-
if is_process_running(obs_process_pid):
|
41
|
-
print(f"OBS is already running with PID: {obs_process_pid}")
|
42
|
-
connect_to_obs()
|
43
|
-
return obs_process_pid
|
44
|
-
except ValueError:
|
45
|
-
print("Invalid PID found in file. Launching new OBS instance.")
|
46
|
-
except OSError:
|
47
|
-
print("No process found with the stored PID. Launching new OBS instance.")
|
48
|
-
|
49
|
-
obs_path = get_obs_path()
|
50
|
-
if not os.path.exists(obs_path):
|
51
|
-
print(f"OBS not found at {obs_path}. Please install OBS.")
|
52
|
-
return None
|
53
|
-
try:
|
54
|
-
obs_process = subprocess.Popen([obs_path, '--disable-shutdown-check', '--portable', '--startreplaybuffer', ], cwd=os.path.dirname(obs_path))
|
55
|
-
obs_process_pid = obs_process.pid
|
56
|
-
connect_to_obs()
|
57
|
-
with open(OBS_PID_FILE, "w") as f:
|
58
|
-
f.write(str(obs_process_pid))
|
59
|
-
print(f"OBS launched with PID: {obs_process_pid}")
|
60
|
-
return obs_process_pid
|
61
|
-
except Exception as e:
|
62
|
-
print(f"Error launching OBS: {e}")
|
63
|
-
return None
|
64
|
-
|
65
|
-
def check_obs_folder_is_correct():
|
66
|
-
obs_record_directory = get_record_directory()
|
67
|
-
if obs_record_directory and os.path.normpath(obs_record_directory) != os.path.normpath(
|
68
|
-
get_config().paths.folder_to_watch):
|
69
|
-
logger.info("OBS Path Setting wrong, OBS Recording folder in GSM Config")
|
70
|
-
get_config().paths.folder_to_watch = os.path.normpath(obs_record_directory)
|
71
|
-
get_master_config().sync_shared_fields()
|
72
|
-
save_full_config(get_master_config())
|
73
|
-
|
74
|
-
|
75
|
-
def get_obs_websocket_config_values():
|
76
|
-
config_path = os.path.join(get_app_directory(), 'obs-studio', 'config', 'obs-studio', 'plugin_config', 'obs-websocket', 'config.json')
|
77
|
-
|
78
|
-
# Check if config file exists
|
79
|
-
if not os.path.isfile(config_path):
|
80
|
-
raise FileNotFoundError(f"OBS WebSocket config not found at {config_path}")
|
81
|
-
|
82
|
-
# Read the JSON configuration
|
83
|
-
with open(config_path, 'r') as file:
|
84
|
-
config = json.load(file)
|
85
|
-
|
86
|
-
# Extract values
|
87
|
-
server_enabled = config.get("server_enabled", False)
|
88
|
-
server_port = config.get("server_port", 7274) # Default to 4455 if not set
|
89
|
-
server_password = config.get("server_password", None)
|
90
|
-
|
91
|
-
if not server_enabled:
|
92
|
-
logger.info("OBS WebSocket server is not enabled. Enabling it now... Restart OBS for changes to take effect.")
|
93
|
-
config["server_enabled"] = True
|
94
|
-
|
95
|
-
with open(config_path, 'w') as file:
|
96
|
-
json.dump(config, file, indent=4)
|
97
|
-
|
98
|
-
if get_config().obs.password == 'your_password':
|
99
|
-
logger.info("OBS WebSocket password is not set. Setting it now...")
|
100
|
-
full_config = get_master_config()
|
101
|
-
full_config.get_config().obs.port = server_port
|
102
|
-
full_config.get_config().obs.password = server_password
|
103
|
-
full_config.sync_shared_fields()
|
104
|
-
full_config.save()
|
105
|
-
reload_config()
|
106
|
-
|
107
|
-
|
108
|
-
connected = False
|
109
|
-
|
110
|
-
def on_connect(obs):
|
111
|
-
global connected
|
112
|
-
logger.info("Reconnected to OBS WebSocket.")
|
113
|
-
start_replay_buffer()
|
114
|
-
connected = True
|
115
|
-
|
116
|
-
|
117
|
-
def on_disconnect(obs):
|
118
|
-
global connected
|
119
|
-
logger.error("OBS Connection Lost!")
|
120
|
-
connected = False
|
121
|
-
|
122
|
-
|
123
|
-
def connect_to_obs(retry_count=0):
|
124
|
-
global client
|
125
|
-
if not get_config().obs.enabled or client:
|
126
|
-
return
|
127
|
-
|
128
|
-
if util.is_windows():
|
129
|
-
get_obs_websocket_config_values()
|
130
|
-
|
131
|
-
try:
|
132
|
-
client = obsws(
|
133
|
-
host=get_config().obs.host,
|
134
|
-
port=get_config().obs.port,
|
135
|
-
password=get_config().obs.password,
|
136
|
-
authreconnect=1,
|
137
|
-
on_connect=on_connect,
|
138
|
-
on_disconnect=on_disconnect
|
139
|
-
)
|
140
|
-
client.connect()
|
141
|
-
update_current_game()
|
142
|
-
except ConnectionFailure as e:
|
143
|
-
if retry_count % 5 == 0:
|
144
|
-
logger.error(f"Failed to connect to OBS WebSocket: {e}. Retrying...")
|
145
|
-
time.sleep(1)
|
146
|
-
connect_to_obs(retry_count=retry_count + 1)
|
147
|
-
|
148
|
-
|
149
|
-
# Disconnect from OBS WebSocket
|
150
|
-
def disconnect_from_obs():
|
151
|
-
global client
|
152
|
-
if client:
|
153
|
-
client.disconnect()
|
154
|
-
client = None
|
155
|
-
logger.info("Disconnected from OBS WebSocket.")
|
156
|
-
|
157
|
-
def do_obs_call(request, from_dict=None, retry=3):
|
158
|
-
connect_to_obs()
|
159
|
-
for _ in range(retry + 1):
|
160
|
-
try:
|
161
|
-
response = client.call(request)
|
162
|
-
if response and response.status:
|
163
|
-
return from_dict(response.datain) if from_dict else response.datain
|
164
|
-
time.sleep(0.3)
|
165
|
-
except Exception as e:
|
166
|
-
logger.error(f"Error calling OBS: {e}")
|
167
|
-
if "socket is already closed" in str(e) or "object has no attribute" in str(e):
|
168
|
-
time.sleep(0.3)
|
169
|
-
else:
|
170
|
-
return None
|
171
|
-
return None
|
172
|
-
|
173
|
-
def toggle_replay_buffer():
|
174
|
-
try:
|
175
|
-
do_obs_call(requests.ToggleReplayBuffer())
|
176
|
-
logger.info("Replay buffer Toggled.")
|
177
|
-
except Exception as e:
|
178
|
-
logger.error(f"Error toggling buffer: {e}")
|
179
|
-
|
180
|
-
|
181
|
-
# Start replay buffer
|
182
|
-
def start_replay_buffer(retry=5):
|
183
|
-
try:
|
184
|
-
if not get_replay_buffer_status()['outputActive']:
|
185
|
-
do_obs_call(requests.StartReplayBuffer(), retry=0)
|
186
|
-
except Exception as e:
|
187
|
-
if "socket is already closed" in str(e):
|
188
|
-
if retry > 0:
|
189
|
-
time.sleep(1)
|
190
|
-
start_replay_buffer(retry - 1)
|
191
|
-
else:
|
192
|
-
logger.error(f"Error starting replay buffer: {e}")
|
193
|
-
|
194
|
-
def get_replay_buffer_status():
|
195
|
-
try:
|
196
|
-
return do_obs_call(requests.GetReplayBufferStatus())
|
197
|
-
except Exception as e:
|
198
|
-
logger.error(f"Error getting replay buffer status: {e}")
|
199
|
-
|
200
|
-
|
201
|
-
# Stop replay buffer
|
202
|
-
def stop_replay_buffer():
|
203
|
-
try:
|
204
|
-
client.call(requests.StopReplayBuffer())
|
205
|
-
logger.error("Replay buffer stopped.")
|
206
|
-
except Exception as e:
|
207
|
-
logger.error(f"Error stopping replay buffer: {e}")
|
208
|
-
|
209
|
-
# Save the current replay buffer
|
210
|
-
def save_replay_buffer():
|
211
|
-
replay_buffer_started = do_obs_call(requests.GetReplayBufferStatus())['outputActive']
|
212
|
-
if replay_buffer_started:
|
213
|
-
client.call(requests.SaveReplayBuffer())
|
214
|
-
logger.info("Replay buffer saved. If your log stops bere, make sure your obs output path matches \"Path To Watch\" in GSM settings.")
|
215
|
-
else:
|
216
|
-
logger.error("Replay Buffer is not active, could not save Replay Buffer!")
|
217
|
-
|
218
|
-
|
219
|
-
def get_current_scene():
|
220
|
-
try:
|
221
|
-
return do_obs_call(requests.GetCurrentProgramScene(), SceneInfo.from_dict, retry=0).sceneName
|
222
|
-
except Exception as e:
|
223
|
-
logger.debug(f"Couldn't get scene: {e}")
|
224
|
-
return ''
|
225
|
-
|
226
|
-
|
227
|
-
def get_source_from_scene(scene_name):
|
228
|
-
try:
|
229
|
-
return do_obs_call(requests.GetSceneItemList(sceneName=scene_name), SceneItemsResponse.from_dict).sceneItems[0]
|
230
|
-
except Exception as e:
|
231
|
-
logger.error(f"Error getting source from scene: {e}")
|
232
|
-
return ''
|
233
|
-
|
234
|
-
def get_record_directory():
|
235
|
-
try:
|
236
|
-
return do_obs_call(requests.GetRecordDirectory(), RecordDirectory.from_dict).recordDirectory
|
237
|
-
except Exception as e:
|
238
|
-
logger.error(f"Error getting recording folder: {e}")
|
239
|
-
return ''
|
240
|
-
|
241
|
-
def get_obs_scenes():
|
242
|
-
try:
|
243
|
-
response: SceneListResponse = do_obs_call(requests.GetSceneList(), SceneListResponse.from_dict, retry=0)
|
244
|
-
return response.scenes
|
245
|
-
except Exception as e:
|
246
|
-
logger.error(f"Error getting scenes: {e}")
|
247
|
-
return None
|
248
|
-
|
249
|
-
def register_scene_change_callback(callback):
|
250
|
-
global client
|
251
|
-
if not client:
|
252
|
-
logger.error("OBS client is not connected.")
|
253
|
-
return
|
254
|
-
|
255
|
-
def on_scene_change(data):
|
256
|
-
logger.info("Scene changed: " + str(data))
|
257
|
-
scene_name = data.getSceneName()
|
258
|
-
if scene_name:
|
259
|
-
callback(scene_name)
|
260
|
-
|
261
|
-
client.register(on_scene_change, events.CurrentProgramSceneChanged)
|
262
|
-
logger.info("Scene change callback registered.")
|
263
|
-
|
264
|
-
|
265
|
-
def get_screenshot(compression=-1):
|
266
|
-
try:
|
267
|
-
screenshot = util.make_unique_file_name(os.path.abspath(
|
268
|
-
configuration.get_temporary_directory()) + '/screenshot.png')
|
269
|
-
update_current_game()
|
270
|
-
current_source = get_source_from_scene(get_current_game())
|
271
|
-
current_source_name = current_source.sourceName
|
272
|
-
if not current_source_name:
|
273
|
-
logger.error("No active scene found.")
|
274
|
-
return
|
275
|
-
start = time.time()
|
276
|
-
logger.debug(f"Current source name: {current_source_name}")
|
277
|
-
response = client.call(requests.SaveSourceScreenshot(sourceName=current_source_name, imageFormat='png', imageFilePath=screenshot, imageCompressionQuality=compression))
|
278
|
-
logger.debug(f"Screenshot response: {response}")
|
279
|
-
logger.debug(f"Screenshot took {time.time() - start:.3f} seconds to save")
|
280
|
-
return screenshot
|
281
|
-
except Exception as e:
|
282
|
-
logger.error(f"Error getting screenshot: {e}")
|
283
|
-
|
284
|
-
def get_screenshot_base64():
|
285
|
-
try:
|
286
|
-
update_current_game()
|
287
|
-
current_source = get_source_from_scene(get_current_game())
|
288
|
-
current_source_name = current_source.sourceName
|
289
|
-
if not current_source_name:
|
290
|
-
logger.error("No active scene found.")
|
291
|
-
return
|
292
|
-
response = do_obs_call(requests.GetSourceScreenshot(sourceName=current_source_name, imageFormat='png', imageCompressionQuality=0))
|
293
|
-
with open('screenshot_response.txt', 'wb') as f:
|
294
|
-
f.write(str(response).encode())
|
295
|
-
return response['imageData']
|
296
|
-
except Exception as e:
|
297
|
-
logger.error(f"Error getting screenshot: {e}")
|
298
|
-
|
299
|
-
def update_current_game():
|
300
|
-
configuration.current_game = get_current_scene()
|
301
|
-
|
302
|
-
|
303
|
-
def get_current_game(sanitize=False):
|
304
|
-
if not configuration.current_game:
|
305
|
-
update_current_game()
|
306
|
-
|
307
|
-
if sanitize:
|
308
|
-
return util.sanitize_filename(configuration.current_game)
|
309
|
-
return configuration.current_game
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|