pax_global_header00006660000000000000000000000064143102047570014515gustar00rootroot0000000000000052 comment=8e5b58d8f2bcf4a3121a9911253b38e9e43e0ff0 simpleobsws-master/000077500000000000000000000000001431020475700147215ustar00rootroot00000000000000simpleobsws-master/.gitignore000066400000000000000000000000611431020475700167060ustar00rootroot00000000000000build/** dist/** simpleobsws.egg-info/** venv/** simpleobsws-master/LICENSE000066400000000000000000000020601431020475700157240ustar00rootroot00000000000000MIT License Copyright (c) 2022 IRLToolkit Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. simpleobsws-master/README.md000066400000000000000000000015611431020475700162030ustar00rootroot00000000000000# simpleobsws A simple obs-websocket library in Python for people who just want JSON output. ## Please Note simpleobsws >= v1.0.0 is **only** for versions of obs-websocket that are 5.0.0 or higher. If you are using obs-websocket pre-5.0.0, use a pre v1.0.0 version of simpleobsws. Click [here](https://github.com/IRLToolkit/simpleobsws/tree/simpleobsws-4.x) to go to the pre-5.0.0 branch. ## Installing: `pip install simpleobsws` [PyPi Link](https://pypi.org/project/simpleobsws/) ## Docs: - Library docs: [the sort-of docs](/usage.md) - obs-websocket docs: https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md ## Samples: Samples are located in the samples folder [here.](/samples) ## IRLTookit Links - Please go to the [obs-websocket Discord](https://discord.gg/WBaSQ3A) for support. - https://twitter.com/IRLToolkit - https://irltoolkit.com simpleobsws-master/requirements.txt000066400000000000000000000000131431020475700201770ustar00rootroot00000000000000websockets simpleobsws-master/samples/000077500000000000000000000000001431020475700163655ustar00rootroot00000000000000simpleobsws-master/samples/sample_dvd_logo.py000066400000000000000000000125641431020475700221050ustar00rootroot00000000000000import logging logging.basicConfig(level=logging.INFO) import os import asyncio import json import simpleobsws import random password = 'pp123' url = 'ws://127.0.0.1:4444' dvdStageSceneName = 'dvd' dvdStageSourceName = 'dvdIcon' # Velocity range, in pixels-per-frame velocityMin = 2.5 velocityMax = 4.5 # =============== NO NEED TO CHANGE ANYTHING BELOW =============== ws = simpleobsws.WebSocketClient(url=url, password=password) # Runtime constants obsXResolution = 0 obsYResolution = 0 sceneItemId = 0 # X and Y values for the true width and height of the scene item sceneItemWidth = 0 sceneItemHeight = 0 # The "current" position of the scene item on the screen currentXPosition = 0 currentYPosition = 0 # The starting velocity of the scene item xTransformVelocity = 3 yTransformVelocity = 3 def is_positive(number): return number > 0 async def init(): await ws.connect() await ws.wait_until_identified() logging.info('Connected and identified.') global obsXResolution global obsYResolution req = simpleobsws.Request('GetVideoSettings') ret = await ws.call(req) if not ret.ok(): logging.error('Failed to fetch OBS info!') return False obsXResolution = ret.responseData['baseWidth'] obsYResolution = ret.responseData['baseHeight'] global sceneItemId req = simpleobsws.Request('GetSceneItemId', {'sceneName': dvdStageSceneName, 'sourceName': dvdStageSourceName}) ret = await ws.call(req) if not ret.ok(): logging.error('Failed to fetch scene item ID!') return False sceneItemId = ret.responseData['sceneItemId'] global sceneItemWidth global sceneItemHeight req = simpleobsws.Request('GetSceneItemTransform', {'sceneName': dvdStageSceneName, 'sceneItemId': sceneItemId}) ret = await ws.call(req) if not ret.ok(): logging.error('Failed to fetch scene item transform!') return False cropLeft = ret.responseData['sceneItemTransform']['cropLeft'] cropRight = ret.responseData['sceneItemTransform']['cropRight'] cropTop = ret.responseData['sceneItemTransform']['cropTop'] cropBottom = ret.responseData['sceneItemTransform']['cropBottom'] # Funky math. In order to get the true width and height of the scene item, we need to take the sum of the source dimensions and the crop values, *then* apply the scale value, as scale is applied last in OBS. sceneItemWidth = (ret.responseData['sceneItemTransform']['sourceWidth'] - cropLeft - cropRight) * ret.responseData['sceneItemTransform']['scaleX'] sceneItemHeight = (ret.responseData['sceneItemTransform']['sourceHeight'] - cropTop - cropBottom) * ret.responseData['sceneItemTransform']['scaleY'] return True async def calc_batch(): global currentXPosition global currentYPosition global xTransformVelocity global yTransformVelocity # Generate 180 frame requests (Number can be changed. Smaller is actually generally better.) ret = [] for i in range(180): # Apply velocity to the position for the current frame. currentXPosition += xTransformVelocity currentYPosition += yTransformVelocity # If edge is reached, generate a new velocity and reverse direction. newVelocity = random.randrange(velocityMin * 100, velocityMax * 100) / 100 if is_positive(xTransformVelocity) and currentXPosition >= (obsXResolution - sceneItemWidth): xTransformVelocity = -newVelocity elif not is_positive(xTransformVelocity) and currentXPosition <= 0: xTransformVelocity = newVelocity if is_positive(yTransformVelocity) and currentYPosition >= (obsYResolution - sceneItemHeight): yTransformVelocity = -newVelocity elif not is_positive(yTransformVelocity) and currentYPosition <= 0: yTransformVelocity = newVelocity # Generate the requests for the current frame sceneItemTransform = {} sceneItemTransform['positionX'] = currentXPosition sceneItemTransform['positionY'] = currentYPosition ret.append(simpleobsws.Request('SetSceneItemTransform', {'sceneName': dvdStageSceneName, 'sceneItemId': sceneItemId, 'sceneItemTransform': sceneItemTransform})) ret.append(simpleobsws.Request('Sleep', {'sleepFrames': 1})) return ret async def update_loop(): # First, initialize the scene item to the corner of the screen. req = simpleobsws.Request('SetSceneItemTransform', {'sceneName': dvdStageSceneName, 'sceneItemId': sceneItemId, 'sceneItemTransform': {'positionX': 0, 'positionY': 0}}) await ws.call(req) # Call a batch, wait for it to finish, then call a new one infinitely. while True: # Generate a new batch of per-frame requests. requests = await calc_batch() # Send it, wait for response responses = await ws.call_batch(requests, halt_on_failure = True, execution_type = simpleobsws.RequestBatchExecutionType.SerialFrame) # Stop sending new requests if they start failing. if len(requests) != len(responses): logging.warning('Received {} responses for {} requests!'.format(len(responses), len(requests))) break loop = asyncio.get_event_loop() if not loop.run_until_complete(init()): os._exit(1) loop.create_task(update_loop()) try: loop.run_forever() except KeyboardInterrupt: pass except: logging.exception('Exception:\n') finally: loop.run_until_complete(ws.disconnect()) simpleobsws-master/samples/sample_events.py000066400000000000000000000021741431020475700216100ustar00rootroot00000000000000import logging logging.basicConfig(level=logging.DEBUG) import asyncio import simpleobsws parameters = simpleobsws.IdentificationParameters() # Create an IdentificationParameters object parameters.eventSubscriptions = (1 << 0) | (1 << 2) # Subscribe to the General and Scenes categories ws = simpleobsws.WebSocketClient(url = 'ws://localhost:4444', password = 'test', identification_parameters = parameters) # Every possible argument has been passed, but none are required. See lib code for defaults. async def on_event(eventType, eventData): print('New event! Type: {} | Raw Data: {}'.format(eventType, eventData)) # Print the event data. Note that `update-type` is also provided in the data async def on_switchscenes(eventData): print('Scene switched to "{}".'.format(eventData['sceneName'])) async def init(): await ws.connect() await ws.wait_until_identified() loop = asyncio.get_event_loop() loop.run_until_complete(init()) ws.register_event_callback(on_event) # By not specifying an event to listen to, all events are sent to this callback. ws.register_event_callback(on_switchscenes, 'SwitchScenes') loop.run_forever() simpleobsws-master/samples/sample_request.py000066400000000000000000000020271431020475700217710ustar00rootroot00000000000000import logging logging.basicConfig(level=logging.DEBUG) import asyncio import simpleobsws parameters = simpleobsws.IdentificationParameters(ignoreNonFatalRequestChecks = False) # Create an IdentificationParameters object (optional for connecting) ws = simpleobsws.WebSocketClient(url = 'ws://localhost:4444', password = 'test', identification_parameters = parameters) # Every possible argument has been passed, but none are required. See lib code for defaults. async def make_request(): await ws.connect() # Make the connection to obs-websocket await ws.wait_until_identified() # Wait for the identification handshake to complete request = simpleobsws.Request('GetVersion') # Build a Request object ret = await ws.call(request) # Perform the request if ret.ok(): # Check if the request succeeded print("Request succeeded! Response data: {}".format(ret.responseData)) await ws.disconnect() # Disconnect from the websocket server cleanly loop = asyncio.get_event_loop() loop.run_until_complete(make_request()) simpleobsws-master/samples/sample_request_batch.py000066400000000000000000000023131431020475700231300ustar00rootroot00000000000000import logging logging.basicConfig(level=logging.DEBUG) import asyncio import simpleobsws parameters = simpleobsws.IdentificationParameters(ignoreNonFatalRequestChecks = False) # Create an IdentificationParameters object (optional for connecting) ws = simpleobsws.WebSocketClient(url = 'ws://localhost:4444', password = 'test', identification_parameters = parameters) # Every possible argument has been passed, but none are required. See lib code for defaults. async def make_request(): await ws.connect() # Make the connection to obs-websocket await ws.wait_until_identified() # Wait for the identification handshake to complete requests = [] requests.append(simpleobsws.Request('GetVersion')) # Build a Request object, then append it to the batch requests.append(simpleobsws.Request('GetStats')) # Build another request object, and append it ret = await ws.call_batch(requests, halt_on_failure = False) # Perform the request batch for result in ret: if ret.ok(): # Check if the request succeeded print("Request succeeded! Response data: {}".format(ret.responseData)) await ws.disconnect() # Disconnect from the websocket server cleanly asyncio.run(make_request()) simpleobsws-master/setup.cfg000066400000000000000000000000501431020475700165350ustar00rootroot00000000000000[metadata] description-file = README.md simpleobsws-master/setup.py000066400000000000000000000015641431020475700164410ustar00rootroot00000000000000import setuptools with open("README.md", "r") as fh: long_description = fh.read() requirements = open('requirements.txt', 'rt').readlines() setuptools.setup( name="simpleobsws", version="1.3.1", author="tt2468", author_email="tt2468@gmail.com", description="A simple obs-websocket library in async Python for people who just want JSON output.", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/IRLToolkit/simpleobsws", py_modules=['simpleobsws'], classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Operating System :: OS Independent", "Intended Audience :: Developers", "Environment :: Plugins", ], python_requires='>=3.7', install_requires=requirements, ) simpleobsws-master/simpleobsws.py000066400000000000000000000326601431020475700176510ustar00rootroot00000000000000import logging wsLogger = logging.getLogger('websockets') wsLogger.setLevel(logging.INFO) log = logging.getLogger(__name__) import asyncio import websockets import base64 import hashlib import json import uuid import time import inspect import enum from dataclasses import dataclass, field from inspect import signature RPC_VERSION = 1 class RequestBatchExecutionType(enum.Enum): SerialRealtime = 0 SerialFrame = 1 Parallel = 2 @dataclass class IdentificationParameters: ignoreNonFatalRequestChecks: bool = None eventSubscriptions: int = None @dataclass class Request: requestType: str requestData: dict = None inputVariables: dict = None # Request batch only outputVariables: dict = None # Request batch only @dataclass class RequestStatus: result: bool = False code: int = 0 comment: str = None @dataclass class RequestResponse: requestType: str = '' requestStatus: RequestStatus = field(default_factory=RequestStatus) responseData: dict = None def has_data(self): return self.responseData != None def ok(self): return self.requestStatus.result @dataclass class _ResponseWaiter: event: asyncio.Event = field(default_factory=asyncio.Event) response_data: dict = None class MessageTimeout(Exception): pass class EventRegistrationError(Exception): pass class NotIdentifiedError(Exception): pass async def _wait_for_cond(cond, func): async with cond: await cond.wait_for(func) class WebSocketClient: def __init__(self, url: str = "ws://localhost:4444", password: str = '', identification_parameters: IdentificationParameters = IdentificationParameters() ): self.url = url self.password = password self.identification_parameters = identification_parameters self.ws = None self.waiters = {} self.identified = False self.recv_task = None self.hello_message = None self.event_callbacks = [] self.cond = asyncio.Condition() # Todo: remove bool return, raise error if already open async def connect(self): if self.ws != None and self.ws.open: log.debug('WebSocket session is already open. Returning early.') return False self.answers = {} self.recv_task = None self.identified = False self.hello_message = None self.ws = await websockets.connect(self.url, max_size=2**24) self.recv_task = asyncio.create_task(self._ws_recv_task()) return True async def wait_until_identified(self, timeout: int = 10): if not self.ws.open: log.debug('WebSocket session is not open. Returning early.') return False try: await asyncio.wait_for(_wait_for_cond(self.cond, self.is_identified), timeout=timeout) return True except asyncio.TimeoutError: return False # Todo: remove bool return, raise error if already closed async def disconnect(self): if self.recv_task == None: log.debug('WebSocket session is not open. Returning early.') return False self.recv_task.cancel() await self.ws.close() self.ws = None self.answers = {} self.identified = False self.recv_task = None self.hello_message = None return True async def call(self, request: Request, timeout: int = 15): if not self.identified: raise NotIdentifiedError('Calls to requests cannot be made without being identified with obs-websocket.') request_id = str(uuid.uuid1()) request_payload = { 'op': 6, 'd': { 'requestType': request.requestType, 'requestId': request_id } } if request.requestData != None: request_payload['d']['requestData'] = request.requestData log.debug('Sending Request message:\n{}'.format(json.dumps(request_payload, indent=2))) waiter = _ResponseWaiter() try: self.waiters[request_id] = waiter await self.ws.send(json.dumps(request_payload)) await asyncio.wait_for(waiter.event.wait(), timeout=timeout) except asyncio.TimeoutError: raise MessageTimeout('The request with type {} timed out after {} seconds.'.format(request.requestType, timeout)) finally: del self.waiters[request_id] return self._build_request_response(waiter.response_data) async def emit(self, request: Request): if not self.identified: raise NotIdentifiedError('Emits to requests cannot be made without being identified with obs-websocket.') request_id = str(uuid.uuid1()) request_payload = { 'op': 6, 'd': { 'requestType': request.requestType, 'requestId': 'emit_{}'.format(request_id) } } if request.requestData != None: request_payload['d']['requestData'] = request.requestData log.debug('Sending Request message:\n{}'.format(json.dumps(request_payload, indent=2))) await self.ws.send(json.dumps(request_payload)) async def call_batch(self, requests: list, timeout: int = 15, halt_on_failure: bool = None, execution_type: RequestBatchExecutionType = None, variables: dict = None): if not self.identified: raise NotIdentifiedError('Calls to requests cannot be made without being identified with obs-websocket.') request_batch_id = str(uuid.uuid1()) request_batch_payload = { 'op': 8, 'd': { 'requestId': request_batch_id, 'requests': [] } } if halt_on_failure != None: request_batch_payload['d']['haltOnFailure'] = halt_on_failure if execution_type: request_batch_payload['d']['executionType'] = execution_type.value if variables: request_batch_payload['d']['variables'] = variables for request in requests: request_payload = { 'requestType': request.requestType } if request.inputVariables: request_payload['inputVariables'] = request.inputVariables if request.outputVariables: request_payload['outputVariables'] = request.outputVariables if request.requestData: request_payload['requestData'] = request.requestData request_batch_payload['d']['requests'].append(request_payload) log.debug('Sending Request batch message:\n{}'.format(json.dumps(request_batch_payload, indent=2))) waiter = _ResponseWaiter() try: self.waiters[request_batch_id] = waiter await self.ws.send(json.dumps(request_batch_payload)) await asyncio.wait_for(waiter.event.wait(), timeout=timeout) except asyncio.TimeoutError: raise MessageTimeout('The request batch timed out after {} seconds.'.format(timeout)) finally: del self.waiters[request_batch_id] ret = [] for result in waiter.response_data['results']: ret.append(self._build_request_response(result)) return ret async def emit_batch(self, requests: list, halt_on_failure: bool = None, execution_type: RequestBatchExecutionType = None, variables: dict = None): if not self.identified: raise NotIdentifiedError('Emits to requests cannot be made without being identified with obs-websocket.') request_batch_id = str(uuid.uuid1()) request_batch_payload = { 'op': 8, 'd': { 'requestId': 'emit_{}'.format(request_batch_id), 'requests': [] } } if halt_on_failure != None: request_batch_payload['d']['haltOnFailure'] = halt_on_failure if execution_type: request_batch_payload['d']['executionType'] = execution_type.value if variables: request_batch_payload['d']['variables'] = variables for request in requests: request_payload = { 'requestType': request.requestType } if request.requestData: request_payload['requestData'] = request.requestData request_batch_payload['d']['requests'].append(request_payload) log.debug('Sending Request batch message:\n{}'.format(json.dumps(request_batch_payload, indent=2))) await self.ws.send(json.dumps(request_batch_payload)) def register_event_callback(self, callback, event: str = None): if not inspect.iscoroutinefunction(callback): raise EventRegistrationError('Registered functions must be async') else: self.event_callbacks.append((callback, event)) def deregister_event_callback(self, callback, event: str = None): for c, t in self.event_callbacks.copy(): if (c == callback) and (event == None or t == event): self.event_callbacks.remove((c, t)) def is_identified(self): return self.identified def _get_hello_data(self): return self.hello_message def _build_request_response(self, response: dict): ret = RequestResponse(response['requestType'], responseData = response.get('responseData')) ret.requestStatus.result = response['requestStatus']['result'] ret.requestStatus.code = response['requestStatus']['code'] ret.requestStatus.comment = response['requestStatus'].get('comment') return ret async def _send_identify(self, password, identification_parameters): if self.hello_message == None: return identify_message = {'op': 1, 'd': {}} identify_message['d']['rpcVersion'] = RPC_VERSION if 'authentication' in self.hello_message: secret = base64.b64encode(hashlib.sha256((self.password + self.hello_message['authentication']['salt']).encode('utf-8')).digest()) authentication_string = base64.b64encode(hashlib.sha256(secret + (self.hello_message['authentication']['challenge'].encode('utf-8'))).digest()).decode('utf-8') identify_message['d']['authentication'] = authentication_string if self.identification_parameters.ignoreNonFatalRequestChecks != None: identify_message['d']['ignoreNonFatalRequestChecks'] = self.identification_parameters.ignoreNonFatalRequestChecks if self.identification_parameters.eventSubscriptions != None: identify_message['d']['eventSubscriptions'] = self.identification_parameters.eventSubscriptions log.debug('Sending Identify message:\n{}'.format(json.dumps(identify_message, indent=2))) await self.ws.send(json.dumps(identify_message)) async def _ws_recv_task(self): while self.ws.open: message = '' try: message = await self.ws.recv() if not message: continue incoming_payload = json.loads(message) log.debug('Received message:\n{}'.format(json.dumps(incoming_payload, indent=2))) op_code = incoming_payload['op'] data_payload = incoming_payload['d'] if op_code == 7 or op_code == 9: # RequestResponse or RequestBatchResponse paylod_request_id = data_payload['requestId'] if paylod_request_id.startswith('emit_'): continue try: waiter = self.waiters[paylod_request_id] waiter.response_data = data_payload waiter.event.set() except KeyError: log.warning('Discarding request response {} because there is no waiter for it.'.format(paylod_request_id)) elif op_code == 5: # Event for callback, trigger in self.event_callbacks: if trigger == None: params = len(signature(callback).parameters) if params == 1: asyncio.create_task(callback(data_payload)) elif params == 2: asyncio.create_task(callback(data_payload['eventType'], data_payload.get('eventData'))) elif params == 3: asyncio.create_task(callback(data_payload['eventType'], data_payload.get('eventIntent'), data_payload.get('eventData'))) elif trigger == data_payload['eventType']: asyncio.create_task(callback(data_payload.get('eventData'))) elif op_code == 0: # Hello self.hello_message = data_payload await self._send_identify(self.password, self.identification_parameters) elif op_code == 2: # Identified self.identified = True async with self.cond: self.cond.notify_all() else: log.warning('Unknown OpCode: {}'.format(op_code)) except (websockets.exceptions.ConnectionClosed, websockets.exceptions.ConnectionClosedError, websockets.exceptions.ConnectionClosedOK): log.debug('The WebSocket connection was closed. Code: {} | Reason: {}'.format(self.ws.close_code, self.ws.close_reason)) break except json.JSONDecodeError: continue self.identified = False simpleobsws-master/usage.md000066400000000000000000000133201431020475700163460ustar00rootroot00000000000000# The sort-of docs ## Enum `RequestBatchExecutionType` **Identifiers:** - `SerialRealtime = 0` - `SerialFrame = 1` - `Parallel = 2` ## Class `IdentificationParameters` **Parameters:** - `ignoreNonFatalRequestChecks: bool = None` - See [here](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#identify-opcode-1). Leave `None` for default - `eventSubscriptions: int = None` - See [here](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#identify-opcode-1). Leave `None` for default ## Class `Request` **Parameters:** - `requestType: str` - Request type. Only required element of a request - `requestData: dict = None` - Optional request data object - `inputVariables: dict = None` - Optional input variables, only used in serial request batches - `outputVariables: dict = None` - Optional output variables, only used in serial request batches ## Class `RequestStatus` **Parameters:** - `result: bool = False` - Boolean status of the request's result - `code: int = 0` - Integer status of the request's result - `comment: str = None` - Text comment if the request failed ## Class `RequestResponse` **Parameters:** - `requestType: str` - The type of request that was made - `requestStatus: RequestStatus = RequestStatus()` - [`RequestStatus`](#class-requeststatus) object - `responseData: dict = None` - Request response data object if any was returned ### `def has_data(self):` - Returns `bool` | `True` if there is a response data object, `False` if not ### `def ok(self):` - Returns `bool` | `True` if the request succeeded, `False` if not ## Class `WebSocketClient` ### `def __init__(self, url: str = "ws://localhost:4444", password: str = '', identification_parameters: IdentificationParameters = IdentificationParameters()):` - `url` - WebSocket URL to reach obs-websocket at - `password` - The password set on the obs-websocket server (if any) - `identification_parameters` - An IdentificationParameters() object with session parameters to be set during identification ### `async def connect(self):` - Returns `bool` - `True` if connected, `False` if not. Exceptions may still be raised by `websockets` Connect to the configured obs-websocket server. The library will automatically begin the identification handshake once connected. ### `async def wait_until_identified(self, timeout: int = 10):` - Returns `bool` - `True` if identified, `False` if the timeout was reached Wait for the identification handshake to complete, or until the timeout is reached. - `timeout` - Time to wait until giving up (does not close the websocket connection) ### `async def disconnect(self):` - Returns `bool` - `True` if disconnected, `False` if *already disconnected* Disconnect from the obs-websocket server. Once disconnected, the server may be connected to again using `connect()`. ### `async def call(self, request: Request, timeout: int = 15):` - Returns `RequestResponse` - Object with populated response data Make a request to the obs-websocket server and return the response. - `request` - The request object to perform the request with - `timeout` - How long to wait for obs-websocket responses before giving up and throwing a `MessageTimeout` error ### `async def emit(self, request: Request)` - Returns nothing Similar to the `call()` function, but does not wait for an obs-websocket response. - `request` - The request object to emit to the server ### `async def call_batch(self, requests: list, timeout: int = 15, halt_on_failure: bool = None, execution_type: RequestBatchExecutionType = None, variables: dict = None):` - Returns list of `RequestResponse` Call a request batch, which is to be completed all at once by obs-websocket. Feed it a list of requests, and it will return a list of results. - `requests` - List of requests to perform - `timeout` - How long to wait for the request batch to finish before giving up and throwing a `MessageTimeout` error - `halt_on_failure` - Tells obs-websocket to stop processing the request batch if one fails. Only available in serial modes - `execution_type` - `RequestBatchExecutionType` to use to process the batch - `variables` - Batch variables to use. Only available in serial modes ### `async def emit_batch(self, requests: list, halt_on_failure: bool = None, execution_type: RequestBatchExecutionType = None):` - Returns nothing Similar to the `call_batch()` function, but does not wait for an obs-websocket response. - `requests` - List of requests to perform - `halt_on_failure` - Tells obs-websocket to stop processing the request batch if one fails. Only available in serial modes - `execution_type` - `RequestBatchExecutionType` to use to process the batch - `variables` - Batch variables to use. Only available in serial modes ### `def register_event_callback(self, callback, event: str = None):` - Returns nothing Register a callback function for an obs-websocket event. *Must* be a coroutine. Note about callbacks: simpleobsws inspects the number of parameters of the callback to determine how to formulate its arguments. Here is the behavior: - 1 parameter - Raw payload - 2 parameters - event type, event data - 3 parameters - event type, event intent, event data - `callback` - Callback to an async handler function. See examples for more info - `event` - Event name to trigger the callback. If not specified, all obs-websocket events will be sent to the callback ### `def deregister_event_callback(self, callback, event = None):` - Returns nothing Similar to `register()`, but deregisters a callback function from obs-websocket. Requires matching `function` and `event` parameters to the original callback registration. ### `def is_identified(self):` - Returns `bool` - `True` if connected and identified, `False` if not identified Pretty simple one.