diff --git a/README.md b/README.md index 0433774..fb39ca5 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Not all endpoints in the official documentation are implemented. - [OBS Studio](https://obsproject.com/) - [OBS Websocket v5 Plugin](https://github.com/obsproject/obs-websocket/releases/tag/5.0.0) - - With the release of OBS Studio version 28, Websocket plugin is included by default. But it should be manually installed for earlier versions of OBS. + - With the release of OBS Studio version 28, Websocket plugin is included by default. But it should be manually installed for earlier versions of OBS. - Python 3.10 or greater ### How to install using pip @@ -23,7 +23,19 @@ pip install obsws-python ### How to Use -Load connection info from toml config. A valid `config.toml` might look like this: +By default the clients connect with parameters: + +- `host`: "localhost" +- `port`: 4455 +- `password`: None + +You may override these parameters by storing them in a toml config file or passing them as keyword arguments. + +Order of precedence: keyword arguments then config file then default values. + +#### `config file` + +A valid `config.toml` might look like this: ```toml [connection] @@ -36,12 +48,6 @@ It should be placed next to your `__main__.py` file. #### Otherwise: -Import and start using, keyword arguments are as follows: - -- `host`: obs websocket server -- `port`: port to access server -- `password`: obs websocket server password - Example `__main__.py`: ```python @@ -75,7 +81,7 @@ For a full list of requests refer to [Requests](https://github.com/obsproject/ob ### Events -When registering a function callback use the name of the expected API event in snake case form. +When registering a callback function use the name of the expected API event in snake case form, prepended with "on\_". example: @@ -83,23 +89,23 @@ example: # load conn info from config.toml cl = obs.EventClient() -def scene_created(data): +def on_scene_created(data): ... # SceneCreated -cl.callback.register(scene_created) +cl.callback.register(on_scene_created) -def input_mute_state_changed(data): +def on_input_mute_state_changed(data): ... # InputMuteStateChanged -cl.callback.register(input_mute_state_changed) +cl.callback.register(on_input_mute_state_changed) # returns a list of currently registered events print(cl.callback.get()) # You may also deregister a callback -cl.callback.deregister(input_mute_state_changed) +cl.callback.deregister(on_input_mute_state_changed) ``` `register(fns)` and `deregister(fns)` accept both single functions and lists of functions. @@ -116,7 +122,7 @@ example: resp = cl.get_version() print(resp.attrs()) -def scene_created(data): +def on_scene_created(data): print(data.attrs()) ``` @@ -126,6 +132,21 @@ If a request fails an `OBSSDKError` will be raised with a status code. For a full list of status codes refer to [Codes](https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#requeststatus) +### Logging + +If you want to see the raw messages simply set log level to DEBUG + +example: + +```python +import obsws_python as obs +import logging + + +logging.basicConfig(level=logging.DEBUG) +... +``` + ### Tests First install development dependencies: diff --git a/obsws_python/__init__.py b/obsws_python/__init__.py index 49b9f1a..80251d6 100644 --- a/obsws_python/__init__.py +++ b/obsws_python/__init__.py @@ -1,6 +1,6 @@ -from .version import version as __version__ from .enum import Subs from .events import EventClient from .reqs import ReqClient +from .version import version as __version__ __ALL__ = ["ReqClient", "EventClient", "Subs"] diff --git a/obsws_python/baseclient.py b/obsws_python/baseclient.py index fd61da6..4b8ba2f 100644 --- a/obsws_python/baseclient.py +++ b/obsws_python/baseclient.py @@ -19,28 +19,30 @@ class ObsClient: logger = logging.getLogger("baseclient.obsclient") def __init__(self, **kwargs): - defaultkwargs = { - **{key: None for key in ["host", "port", "password"]}, - "subs": 0, - } + defaultkwargs = {"host": "localhost", "port": 4455, "password": None, "subs": 0} + if not any(key in kwargs for key in ("host", "port", "password")): + kwargs |= self._conn_from_toml() kwargs = defaultkwargs | kwargs for attr, val in kwargs.items(): setattr(self, attr, val) - if not (self.host and self.port): - conn = self._conn_from_toml() - self.host = conn["host"] - self.port = conn["port"] - self.password = conn["password"] + + self.logger.info( + "Connecting with parameters: {host} {port} {password} {subs}".format( + **self.__dict__ + ) + ) self.ws = websocket.WebSocket() self.ws.connect(f"ws://{self.host}:{self.port}") self.server_hello = json.loads(self.ws.recv()) - def _conn_from_toml(self): + def _conn_from_toml(self) -> dict: + conn = {} filepath = Path.cwd() / "config.toml" - with open(filepath, "rb") as f: - conn = tomllib.load(f) - return conn["connection"] + if filepath.exists(): + with open(filepath, "rb") as f: + conn = tomllib.load(f) + return conn["connection"] if "connection" in conn else conn def authenticate(self): payload = { @@ -52,6 +54,8 @@ class ObsClient: } if "authentication" in self.server_hello["d"]: + if not self.password: + raise OBSSDKError("authentication enabled but no password provided") secret = base64.b64encode( hashlib.sha256( ( @@ -79,14 +83,13 @@ class ObsClient: raise OBSSDKError("failed to identify client with the server") def req(self, req_type, req_data=None): - id = randint(1, 1000) - self.logger.debug(f"Sending request with id {id}") payload = { "op": 6, - "d": {"requestType": req_type, "requestId": id}, + "d": {"requestType": req_type, "requestId": randint(1, 1000)}, } if req_data: payload["d"]["requestData"] = req_data + self.logger.debug(f"Sending request {payload}") self.ws.send(json.dumps(payload)) response = json.loads(self.ws.recv()) self.logger.debug(f"Response received {response}") diff --git a/obsws_python/reqs.py b/obsws_python/reqs.py index 7f15a35..d411b32 100644 --- a/obsws_python/reqs.py +++ b/obsws_python/reqs.py @@ -75,7 +75,7 @@ class ReqClient: """ self.send("BroadcastCustomEvent", eventData) - def call_vendor_request(self, vendorName, requestType, requestData=None): + def call_vendor_request(self, vendor_name, request_type, request_data=None): """ Call a request registered to a vendor. @@ -97,7 +97,10 @@ class ReqClient: """ - return self.send(requestType, requestData) + payload = {"vendorName": vendor_name, "requestType": request_type} + if request_data: + payload["requestData"] = request_data + return self.send("CallVendorRequest", payload) def get_hot_key_list(self): """ @@ -110,6 +113,8 @@ class ReqClient: """ return self.send("GetHotkeyList") + get_hotkey_list = get_hot_key_list + def trigger_hot_key_by_name(self, hotkeyName): """ Triggers a hotkey using its name. For hotkey names @@ -123,6 +128,8 @@ class ReqClient: payload = {"hotkeyName": hotkeyName} self.send("TriggerHotkeyByName", payload) + trigger_hotkey_by_name = trigger_hot_key_by_name + def trigger_hot_key_by_key_sequence( self, keyId, pressShift, pressCtrl, pressAlt, pressCmd ): @@ -155,6 +162,8 @@ class ReqClient: } self.send("TriggerHotkeyByKeySequence", payload) + trigger_hotkey_by_key_sequence = trigger_hot_key_by_key_sequence + def sleep(self, sleepMillis=None, sleepFrames=None): """ Sleeps for a time duration or number of frames. diff --git a/obsws_python/version.py b/obsws_python/version.py index 1350d4e..d28b3dd 100644 --- a/obsws_python/version.py +++ b/obsws_python/version.py @@ -1 +1 @@ -version = "1.2.0" \ No newline at end of file +version = "1.3.0"