GameSentenceMiner 2.9.2__py3-none-any.whl → 2.9.3__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/configuration.py +10 -4
- GameSentenceMiner/ffmpeg.py +4 -3
- GameSentenceMiner/gsm.py +6 -5
- GameSentenceMiner/notification.py +1 -4
- GameSentenceMiner/obs.py +3 -2
- GameSentenceMiner/util.py +0 -9
- {gamesentenceminer-2.9.2.dist-info → gamesentenceminer-2.9.3.dist-info}/METADATA +1 -1
- {gamesentenceminer-2.9.2.dist-info → gamesentenceminer-2.9.3.dist-info}/RECORD +12 -13
- GameSentenceMiner/obs_back.py +0 -309
- {gamesentenceminer-2.9.2.dist-info → gamesentenceminer-2.9.3.dist-info}/WHEEL +0 -0
- {gamesentenceminer-2.9.2.dist-info → gamesentenceminer-2.9.3.dist-info}/entry_points.txt +0 -0
- {gamesentenceminer-2.9.2.dist-info → gamesentenceminer-2.9.3.dist-info}/licenses/LICENSE +0 -0
- {gamesentenceminer-2.9.2.dist-info → gamesentenceminer-2.9.3.dist-info}/top_level.txt +0 -0
@@ -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):
|
GameSentenceMiner/obs.py
CHANGED
@@ -8,6 +8,7 @@ import psutil
|
|
8
8
|
|
9
9
|
import obsws_python as obs
|
10
10
|
|
11
|
+
import GameSentenceMiner.configuration
|
11
12
|
from GameSentenceMiner import util, configuration
|
12
13
|
from GameSentenceMiner.configuration import *
|
13
14
|
from GameSentenceMiner.model import *
|
@@ -140,7 +141,7 @@ async def connect_to_obs(retry_count=0):
|
|
140
141
|
if not get_config().obs.enabled:
|
141
142
|
return
|
142
143
|
|
143
|
-
if
|
144
|
+
if GameSentenceMiner.configuration.is_windows():
|
144
145
|
get_obs_websocket_config_values()
|
145
146
|
|
146
147
|
while True:
|
@@ -174,7 +175,7 @@ def connect_to_obs_sync(retry_count=0):
|
|
174
175
|
if not get_config().obs.enabled or client:
|
175
176
|
return
|
176
177
|
|
177
|
-
if
|
178
|
+
if GameSentenceMiner.configuration.is_windows():
|
178
179
|
get_obs_websocket_config_values()
|
179
180
|
|
180
181
|
while True:
|
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:
|
@@ -1,19 +1,18 @@
|
|
1
1
|
GameSentenceMiner/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
GameSentenceMiner/anki.py,sha256=JnVfFkLpEfWaPfOLngU0PSQq4vrgWuhd_VLYZEBqNTY,14608
|
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/obs_back.py,sha256=_N_UV7Nh5cyy3mnH5lOUOzhgZwHMACeFEuBo1Z-bNzg,10894
|
10
|
+
GameSentenceMiner/notification.py,sha256=fPTbZJG82YLsppjC0sByQ0SX3bSsvDWXzG498HBEGMY,2823
|
11
|
+
GameSentenceMiner/obs.py,sha256=OntCO14G2KjzMk6bztqhQWiDiNzjzPpL_KHKXasxJq4,14886
|
13
12
|
GameSentenceMiner/package.py,sha256=YlS6QRMuVlm6mdXx0rlXv9_3erTGS21jaP3PNNWfAH0,1250
|
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
|
19
18
|
GameSentenceMiner/communication/__init__.py,sha256=_jGn9PJxtOAOPtJ2rI-Qu9hEHVZVpIvWlxKvqk91_zI,638
|
@@ -57,9 +56,9 @@ GameSentenceMiner/web/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
|
|
57
56
|
GameSentenceMiner/web/templates/index.html,sha256=HZKiIjiGJV8PGQ9T2aLDUNSfJn71qOwbYCjbRuSIjpY,213583
|
58
57
|
GameSentenceMiner/web/templates/text_replacements.html,sha256=tV5c8mCaWSt_vKuUpbdbLAzXZ3ATZeDvQ9PnnAfqY0M,8598
|
59
58
|
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.
|
59
|
+
gamesentenceminer-2.9.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
60
|
+
gamesentenceminer-2.9.3.dist-info/METADATA,sha256=XlzEKeMFA9JKtiEU_pxShJbopSnIKKdL7cRsg25JxqQ,7280
|
61
|
+
gamesentenceminer-2.9.3.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
62
|
+
gamesentenceminer-2.9.3.dist-info/entry_points.txt,sha256=2APEP25DbfjSxGeHtwBstMH8mulVhLkqF_b9bqzU6vQ,65
|
63
|
+
gamesentenceminer-2.9.3.dist-info/top_level.txt,sha256=V1hUY6xVSyUEohb0uDoN4UIE6rUZ_JYx8yMyPGX4PgQ,18
|
64
|
+
gamesentenceminer-2.9.3.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
|