From d37cda9976537b7decbf51c66b127ef945bff72a Mon Sep 17 00:00:00 2001 From: onyx-and-iris <75868496+onyx-and-iris@users.noreply.github.com> Date: Tue, 26 Jul 2022 01:03:57 +0100 Subject: [PATCH] refreshed ignored files --- .gitignore | 5 + build/lib/obsstudio_sdk/__init__.py | 4 - build/lib/obsstudio_sdk/baseclient.py | 71 - build/lib/obsstudio_sdk/events.py | 43 - build/lib/obsstudio_sdk/reqs.py | 1928 ----------------- build/lib/obsstudio_sdk/subject.py | 58 - .../__pycache__/__main__.cpython-311.pyc | Bin 1847 -> 0 bytes .../__pycache__/__main__.cpython-311.pyc | Bin 1267 -> 0 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 300 -> 0 bytes .../__pycache__/baseclient.cpython-311.pyc | Bin 4405 -> 0 bytes .../__pycache__/callback.cpython-311.pyc | Bin 3874 -> 0 bytes .../__pycache__/events.cpython-311.pyc | Bin 2237 -> 0 bytes .../__pycache__/reqs.cpython-311.pyc | Bin 65867 -> 0 bytes .../__pycache__/subject.cpython-311.pyc | Bin 3873 -> 0 bytes 14 files changed, 5 insertions(+), 2104 deletions(-) delete mode 100644 build/lib/obsstudio_sdk/__init__.py delete mode 100644 build/lib/obsstudio_sdk/baseclient.py delete mode 100644 build/lib/obsstudio_sdk/events.py delete mode 100644 build/lib/obsstudio_sdk/reqs.py delete mode 100644 build/lib/obsstudio_sdk/subject.py delete mode 100644 examples/events/__pycache__/__main__.cpython-311.pyc delete mode 100644 examples/scene_rotate/__pycache__/__main__.cpython-311.pyc delete mode 100644 obsstudio_sdk/__pycache__/__init__.cpython-311.pyc delete mode 100644 obsstudio_sdk/__pycache__/baseclient.cpython-311.pyc delete mode 100644 obsstudio_sdk/__pycache__/callback.cpython-311.pyc delete mode 100644 obsstudio_sdk/__pycache__/events.cpython-311.pyc delete mode 100644 obsstudio_sdk/__pycache__/reqs.cpython-311.pyc delete mode 100644 obsstudio_sdk/__pycache__/subject.cpython-311.pyc diff --git a/.gitignore b/.gitignore index 057f050..a04522a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + # Distribution / packaging .Python build/ diff --git a/build/lib/obsstudio_sdk/__init__.py b/build/lib/obsstudio_sdk/__init__.py deleted file mode 100644 index 7c892fc..0000000 --- a/build/lib/obsstudio_sdk/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .events import EventsClient -from .reqs import ReqClient - -__ALL__ = ["ReqClient", "EventsClient"] diff --git a/build/lib/obsstudio_sdk/baseclient.py b/build/lib/obsstudio_sdk/baseclient.py deleted file mode 100644 index cc403f8..0000000 --- a/build/lib/obsstudio_sdk/baseclient.py +++ /dev/null @@ -1,71 +0,0 @@ -import base64 -import hashlib -import json -from pathlib import Path -from random import randint - -import tomllib -import websocket - - -class ObsClient(object): - def __init__(self, host=None, port=None, password=None): - self.host = host - self.port = port - self.password = password - if not (self.host and self.port and self.password): - conn = self._conn_from_toml() - self.host = conn["host"] - self.port = conn["port"] - self.password = conn["password"] - 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): - filepath = Path.cwd() / "config.toml" - self._conn = dict() - with open(filepath, "rb") as f: - self._conn = tomllib.load(f) - return self._conn["connection"] - - def authenticate(self): - secret = base64.b64encode( - hashlib.sha256( - ( - self.password + self.server_hello["d"]["authentication"]["salt"] - ).encode() - ).digest() - ) - - auth = base64.b64encode( - hashlib.sha256( - ( - secret.decode() - + self.server_hello["d"]["authentication"]["challenge"] - ).encode() - ).digest() - ).decode() - - payload = {"op": 1, "d": {"rpcVersion": 1, "authentication": auth}} - - self.ws.send(json.dumps(payload)) - return self.ws.recv() - - def req(self, req_type, req_data=None): - if req_data: - payload = { - "op": 6, - "d": { - "requestType": req_type, - "requestId": randint(1, 1000), - "requestData": req_data, - }, - } - else: - payload = { - "op": 6, - "d": {"requestType": req_type, "requestId": randint(1, 1000)}, - } - self.ws.send(json.dumps(payload)) - return json.loads(self.ws.recv()) diff --git a/build/lib/obsstudio_sdk/events.py b/build/lib/obsstudio_sdk/events.py deleted file mode 100644 index 3e007b0..0000000 --- a/build/lib/obsstudio_sdk/events.py +++ /dev/null @@ -1,43 +0,0 @@ -import json -import time -from threading import Thread - -from .baseclient import ObsClient -from .subject import Callback - -""" -A class to interact with obs-websocket events -defined in official github repo -https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#events -""" - - -class EventsClient(object): - DELAY = 0.001 - - def __init__(self, **kwargs): - self.base_client = ObsClient(**kwargs) - self.base_client.authenticate() - self.callback = Callback() - - self.running = True - worker = Thread(target=self.trigger, daemon=True) - worker.start() - - def trigger(self): - """ - Continuously listen for events. - - Triggers a callback on event received. - """ - while self.running: - self.data = json.loads(self.base_client.ws.recv()) - event, data = (self.data["d"].get("eventType"), self.data["d"]) - self.callback.trigger(event, data) - time.sleep(self.DELAY) - - def unsubscribe(self): - """ - stop listening for events - """ - self.running = False diff --git a/build/lib/obsstudio_sdk/reqs.py b/build/lib/obsstudio_sdk/reqs.py deleted file mode 100644 index 924682e..0000000 --- a/build/lib/obsstudio_sdk/reqs.py +++ /dev/null @@ -1,1928 +0,0 @@ -from .baseclient import ObsClient - -""" -A class to interact with obs-websocket requests -defined in official github repo -https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md#Requests -""" - - -class ReqClient(object): - def __init__(self, **kwargs): - self.base_client = ObsClient(**kwargs) - self.base_client.authenticate() - - def GetVersion(self): - """ - Gets data about the current plugin and RPC version. - - :return: The version info as a dictionary - :rtype: dict - - - """ - response = self.base_client.req("GetVersion") - return response - - def GetStats(self): - """ - Gets statistics about OBS, obs-websocket, and the current session. - - :return: The stats info as a dictionary - :rtype: dict - - - """ - response = self.base_client.req("GetStats") - return response - - def BroadcastCustomEvent(self, eventData): - """ - Broadcasts a CustomEvent to all WebSocket clients. Receivers are clients which are identified and subscribed. - - :param eventData: Data payload to emit to all receivers - :type eventData: object - :return: empty response - :rtype: str - - - """ - req_data = eventData - response = self.base_client.req("BroadcastCustomEvent", req_data) - return response - - def CallVendorRequest(self, vendorName, requestType, requestData=None): - """ - Call a request registered to a vendor. - - A vendor is a unique name registered by a - third-party plugin or script, which allows - for custom requests and events to be added - to obs-websocket. If a plugin or script - implements vendor requests or events, - documentation is expected to be provided with them. - - :param vendorName: Name of the vendor to use - :type vendorName: str - :param requestType: The request type to call - :type requestType: str - :param requestData: Object containing appropriate request data - :type requestData: dict, optional - :return: responseData - :rtype: dict - - - """ - response = self.base_client.req(req_type=requestType, req_data=requestData) - return response - - def GetHotkeyList(self): - """ - Gets an array of all hotkey names in OBS - - :return: hotkeys - :rtype: list[str] - - - """ - response = self.base_client.req("GetHotkeyList") - return response - - def TriggerHotkeyByName(self, hotkeyName): - """ - Triggers a hotkey using its name. For hotkey names - See GetHotkeyList - - :param hotkeyName: Name of the hotkey to trigger - :type hotkeyName: str - - - """ - payload = {"hotkeyName": hotkeyName} - response = self.base_client.req("TriggerHotkeyByName", payload) - return response - - def TriggerHotkeyByKeySequence( - self, keyId, pressShift, pressCtrl, pressAlt, pressCmd - ): - """ - Triggers a hotkey using a sequence of keys. - - :param keyId: The OBS key ID to use. See https://github.com/obsproject/obs-studio/blob/master/libobs/obs-hotkeys.h - :type keyId: str - :param keyModifiers: Object containing key modifiers to apply. - :type keyModifiers: dict - :param keyModifiers.shift: Press Shift - :type keyModifiers.shift: bool - :param keyModifiers.control: Press CTRL - :type keyModifiers.control: bool - :param keyModifiers.alt: Press ALT - :type keyModifiers.alt: bool - :param keyModifiers.cmd: Press CMD (Mac) - :type keyModifiers.cmd: bool - - - """ - payload = { - "keyId": keyId, - "keyModifiers": { - "shift": pressShift, - "control": pressCtrl, - "alt": pressAlt, - "cmd": pressCmd, - }, - } - - response = self.base_client.req("TriggerHotkeyByKeySequence", payload) - return response - - def Sleep(self, sleepMillis=None, sleepFrames=None): - """ - Sleeps for a time duration or number of frames. - Only available in request batches with types SERIAL_REALTIME or SERIAL_FRAME - - :param sleepMillis: Number of milliseconds to sleep for (if SERIAL_REALTIME mode) 0 <= sleepMillis <= 50000 - :type sleepMillis: int - :param sleepFrames: Number of frames to sleep for (if SERIAL_FRAME mode) 0 <= sleepFrames <= 10000 - :type sleepFrames: int - - - """ - payload = {"sleepMillis": sleepMillis, "sleepFrames": sleepFrames} - response = self.base_client.req("Sleep", payload) - return response - - def GetPersistentData(self, realm, slotName): - """ - Gets the value of a "slot" from the selected persistent data realm. - - :param realm: The data realm to select - OBS_WEBSOCKET_DATA_REALM_GLOBAL or OBS_WEBSOCKET_DATA_REALM_PROFILE - :type realm: str - :param slotName: The name of the slot to retrieve data from - :type slotName: str - :return: slotValue Value associated with the slot - :rtype: any - - - """ - payload = {"realm": realm, "slotName": slotName} - response = self.base_client.req("GetPersistentData", payload) - return response - - def SetPersistentData(self, realm, slotName, slotValue): - """ - Sets the value of a "slot" from the selected persistent data realm. - - :param realm: The data realm to select. - OBS_WEBSOCKET_DATA_REALM_GLOBAL or OBS_WEBSOCKET_DATA_REALM_PROFILE - :type realm: str - :param slotName: The name of the slot to retrieve data from - :type slotName: str - :param slotValue: The value to apply to the slot - :type slotValue: any - - - """ - payload = {"realm": realm, "slotName": slotName, "slotValue": slotValue} - response = self.base_client.req("SetPersistentData", payload) - return response - - def GetSceneCollectionList(self): - """ - Gets an array of all scene collections - - :return: sceneCollections - :rtype: list[str] - - - """ - response = self.base_client.req("GetSceneCollectionList") - return response - - def SetCurrentSceneCollection(self, name): - """ - Creates a new scene collection, switching to it in the process - Note: This will block until the collection has finished changing - - :param name: Name of the scene collection to switch to - :type name: str - - - """ - payload = {"sceneCollectionName": name} - response = self.base_client.req("SetCurrentSceneCollection", payload) - return response - - def CreateSceneCollection(self, name): - """ - Creates a new scene collection, switching to it in the process. - Note: This will block until the collection has finished changing. - - :param name: Name for the new scene collection - :type name: str - - - """ - payload = {"sceneCollectionName": name} - response = self.base_client.req("CreateSceneCollection", payload) - return response - - def GetProfileList(self): - """ - Gets a list of all profiles - - :return: profiles (List of all profiles) - :rtype: list[str] - - - """ - response = self.base_client.req("GetProfileList") - return response - - def SetCurrentProfile(self, name): - """ - Switches to a profile - - :param name: Name of the profile to switch to - :type name: str - - - """ - payload = {"profileName": name} - response = self.base_client.req("SetCurrentProfile", payload) - return response - - def CreateProfile(self, name): - """ - Creates a new profile, switching to it in the process - - :param name: Name for the new profile - :type name: str - - - """ - payload = {"profileName": name} - response = self.base_client.req("CreateProfile", payload) - return response - - def RemoveProfile(self, name): - """ - Removes a profile. If the current profile is chosen, - it will change to a different profile first. - - :param name: Name of the profile to remove - :type name: str - - - """ - payload = {"profileName": name} - response = self.base_client.req("RemoveProfile", payload) - return response - - def GetProfileParameter(self, category, name): - """ - Gets a parameter from the current profile's configuration.. - - :param category: Category of the parameter to get - :type category: str - :param name: Name of the parameter to get - :type name: str - - :return: Value and default value for the parameter - :rtype: str - - - """ - payload = {"parameterCategory": category, "parameterName": name} - response = self.base_client.req("GetProfileParameter", payload) - return response - - def SetProfileParameter(self, category, name, value): - """ - Gets a parameter from the current profile's configuration.. - - :param category: Category of the parameter to set - :type category: str - :param name: Name of the parameter to set - :type name: str - :param value: Value of the parameter to set. Use null to delete - :type value: str - - :return: Value and default value for the parameter - :rtype: str - - - """ - payload = { - "parameterCategory": category, - "parameterName": name, - "parameterValue": value, - } - response = self.base_client.req("SetProfileParameter", payload) - return response - - def GetVideoSettings(self): - """ - Gets the current video settings. - Note: To get the true FPS value, divide the FPS numerator by the FPS denominator. - Example: 60000/1001 - - - """ - response = self.base_client.req("GetVideoSettings") - return response - - def SetVideoSettings( - self, numerator, denominator, base_width, base_height, out_width, out_height - ): - """ - Sets the current video settings. - Note: Fields must be specified in pairs. - For example, you cannot set only baseWidth without needing to specify baseHeight. - - :param numerator: Numerator of the fractional FPS value >=1 - :type numerator: int - :param denominator: Denominator of the fractional FPS value >=1 - :type denominator: int - :param base_width: Width of the base (canvas) resolution in pixels (>= 1, <= 4096) - :type base_width: int - :param base_height: Height of the base (canvas) resolution in pixels (>= 1, <= 4096) - :type base_height: int - :param out_width: Width of the output resolution in pixels (>= 1, <= 4096) - :type out_width: int - :param out_height: Height of the output resolution in pixels (>= 1, <= 4096) - :type out_height: int - - - """ - payload = { - "fpsNumerator": numerator, - "fpsDenominator": denominator, - "baseWidth": base_width, - "baseHeight": base_height, - "outputWidth": out_width, - "outputHeight": out_height, - } - response = self.base_client.req("SetVideoSettings", payload) - return response - - def GetStreamServiceSettings(self): - """ - Gets the current stream service settings (stream destination). - - - """ - response = self.base_client.req("GetStreamServiceSettings") - return response - - def SetStreamServiceSettings(self, ss_type, ss_settings): - """ - Sets the current stream service settings (stream destination). - Note: Simple RTMP settings can be set with type rtmp_custom - and the settings fields server and key. - - :param ss_type: Type of stream service to apply. Example: rtmp_common or rtmp_custom - :type ss_type: string - :param ss_setting: Settings to apply to the service - :type ss_setting: dict - - - """ - payload = { - "streamServiceType": ss_type, - "streamServiceSettings": ss_settings, - } - response = self.base_client.req("SetStreamServiceSettings", payload) - return response - - def GetSourceActive(self, name): - """ - Gets the active and show state of a source - - :param name: Name of the source to get the active state of - :type name: str - - - """ - payload = {"sourceName": name} - response = self.base_client.req("GetSourceActive", payload) - return response - - def GetSourceScreenshot(self, name, img_format, width, height, quality): - """ - Gets a Base64-encoded screenshot of a source. - The imageWidth and imageHeight parameters are - treated as "scale to inner", meaning the smallest ratio - will be used and the aspect ratio of the original resolution is kept. - If imageWidth and imageHeight are not specified, the compressed image - will use the full resolution of the source. - - :param name: Name of the source to take a screenshot of - :type name: str - :param format: Image compression format to use. Use GetVersion to get compatible image formats - :type format: str - :param width: Width to scale the screenshot to (>= 8, <= 4096) - :type width: int - :param height: Height to scale the screenshot to (>= 8, <= 4096) - :type height: int - :param quality: Compression quality to use. 0 for high compression, 100 for uncompressed. -1 to use "default" - :type quality: int - - - """ - payload = { - "sourceName": name, - "imageFormat": img_format, - "imageWidth": width, - "imageHeight": height, - "imageCompressionQuality": quality, - } - response = self.base_client.req("GetSourceScreenshot", payload) - return response - - def SaveSourceScreenshot(self, name, img_format, file_path, width, height, quality): - """ - Saves a Base64-encoded screenshot of a source. - The imageWidth and imageHeight parameters are - treated as "scale to inner", meaning the smallest ratio - will be used and the aspect ratio of the original resolution is kept. - If imageWidth and imageHeight are not specified, the compressed image - will use the full resolution of the source. - - :param name: Name of the source to take a screenshot of - :type name: str - :param format: Image compression format to use. Use GetVersion to get compatible image formats - :type format: str - :param file_path: Path to save the screenshot file to. Eg. C:\\Users\\user\\Desktop\\screenshot.png - :type file_path: str - :param width: Width to scale the screenshot to (>= 8, <= 4096) - :type width: int - :param height: Height to scale the screenshot to (>= 8, <= 4096) - :type height: int - :param quality: Compression quality to use. 0 for high compression, 100 for uncompressed. -1 to use "default" - :type quality: int - - - """ - payload = { - "sourceName": name, - "imageFormat": img_format, - "imageFilePath": file_path, - "imageWidth": width, - "imageHeight": height, - "imageCompressionQuality": quality, - } - response = self.base_client.req("SaveSourceScreenshot", payload) - return response - - def GetSceneList(self): - """ - Gets a list of all scenes in OBS. - - - """ - response = self.base_client.req("GetSceneList") - return response - - def GetGroupList(self): - """ - Gets a list of all groups in OBS. - Groups in OBS are actually scenes, - but renamed and modified. In obs-websocket, - we treat them as scenes where we can.. - - - """ - response = self.base_client.req("GetSceneList") - return response - - def GetCurrentProgramScene(self): - """ - Gets the current program scene. - - - """ - response = self.base_client.req("GetCurrentProgramScene") - return response - - def SetCurrentProgramScene(self, name): - """ - Sets the current program scene - - :param name: Scene to set as the current program scene - :type name: str - - - """ - payload = {"sceneName": name} - response = self.base_client.req("SetCurrentProgramScene", payload) - return response - - def GetCurrentPreviewScene(self): - """ - Gets the current preview scene - - - """ - response = self.base_client.req("GetCurrentPreviewScene") - return response - - def SetCurrentPreviewScene(self, name): - """ - Sets the current program scene - - :param name: Scene to set as the current preview scene - :type name: str - - - """ - payload = {"sceneName": name} - response = self.base_client.req("SetCurrentPreviewScene", payload) - return response - - def CreateScene(self, name): - """ - Creates a new scene in OBS. - - :param name: Name for the new scene - :type name: str - - - """ - payload = {"sceneName": name} - response = self.base_client.req("CreateScene", payload) - return response - - def RemoveScene(self, name): - """ - Removes a scene from OBS - - :param name: Name of the scene to remove - :type name: str - - - """ - payload = {"sceneName": name} - response = self.base_client.req("RemoveScene", payload) - return response - - def SetSceneName(self, old_name, new_name): - """ - Sets the name of a scene (rename). - - :param old_name: Name of the scene to be renamed - :type old_name: str - :param new_name: New name for the scene - :type new_name: str - - - """ - payload = {"sceneName": old_name, "newSceneName": new_name} - response = self.base_client.req("SetSceneName", payload) - return response - - def GetSceneSceneTransitionOverride(self, name): - """ - Gets the scene transition overridden for a scene. - - :param name: Name of the scene - :type name: str - - - """ - payload = {"sceneName": name} - response = self.base_client.req("GetSceneSceneTransitionOverride", payload) - return response - - def SetSceneSceneTransitionOverride(self, scene_name, tr_name, tr_duration): - """ - Gets the scene transition overridden for a scene. - - :param scene_name: Name of the scene - :type scene_name: str - :param tr_name: Name of the scene transition to use as override. Specify null to remove - :type tr_name: str - :param tr_duration: Duration to use for any overridden transition. Specify null to remove (>= 50, <= 20000) - :type tr_duration: int - - - """ - payload = { - "sceneName": scene_name, - "transitionName": tr_name, - "transitionDuration": tr_duration, - } - response = self.base_client.req("SetSceneSceneTransitionOverride", payload) - return response - - def GetInputList(self, kind): - """ - Gets a list of all inputs in OBS. - - :param kind: Restrict the list to only inputs of the specified kind - :type kind: str - - - """ - payload = {"inputKind": kind} - response = self.base_client.req("GetInputList", payload) - return response - - def GetInputKindList(self, unversioned): - """ - Gets a list of all available input kinds in OBS. - - :param unversioned: True == Return all kinds as unversioned, False == Return with version suffixes (if available) - :type unversioned: bool - - - """ - payload = {"unversioned": unversioned} - response = self.base_client.req("GetInputKindList", payload) - return response - - def GetSpecialInputs(self): - """ - Gets the name of all special inputs. - - - """ - response = self.base_client.req("GetSpecialInputs") - return response - - def CreateInput( - self, sceneName, inputName, inputKind, inputSettings, sceneItemEnabled - ): - """ - Creates a new input, adding it as a scene item to the specified scene. - - :param sceneName: Name of the scene to add the input to as a scene item - :type sceneName: str - :param inputName Name of the new input to created - :type inputName: str - :param inputKind: The kind of input to be created - :type inputKind: str - :param inputSettings: Settings object to initialize the input with - :type inputSettings: object - :param sceneItemEnabled: Whether to set the created scene item to enabled or disabled - :type sceneItemEnabled: bool - - - """ - payload = { - "sceneName": sceneName, - "inputName": inputName, - "inputKind": inputKind, - "inputSettings": inputSettings, - "sceneItemEnabled": sceneItemEnabled, - } - response = self.base_client.req("CreateInput", payload) - return response - - def RemoveInput(self, name): - """ - Removes an existing input - - :param name: Name of the input to remove - :type name: str - - - """ - payload = {"inputName": name} - response = self.base_client.req("RemoveInput", payload) - return response - - def SetInputName(self, old_name, new_name): - """ - Sets the name of an input (rename). - - :param old_name: Current input name - :type old_name: str - :param new_name: New name for the input - :type new_name: str - - - """ - payload = {"inputName": old_name, "newInputName": new_name} - response = self.base_client.req("SetInputName", payload) - return response - - def GetInputDefaultSettings(self, kind): - """ - Gets the default settings for an input kind. - - :param kind: Input kind to get the default settings for - :type kind: str - - - """ - payload = {"inputKind": kind} - response = self.base_client.req("GetInputDefaultSettings", payload) - return response - - def GetInputSettings(self, name): - """ - Gets the settings of an input. - Note: Does not include defaults. To create the entire settings object, - overlay inputSettings over the defaultInputSettings provided by GetInputDefaultSettings. - - :param name: Input kind to get the default settings for - :type name: str - - - """ - payload = {"inputName": name} - response = self.base_client.req("GetInputSettings", payload) - return response - - def SetInputSettings(self, name, settings, overlay): - """ - Sets the settings of an input. - - :param name: Name of the input to set the settings of - :type name: str - :param settings: Object of settings to apply - :type settings: dict - :param overlay: True == apply the settings on top of existing ones, False == reset the input to its defaults, then apply settings. - :type overlay: bool - - - """ - payload = {"inputName": name, "inputSettings": settings, "overlay": overlay} - response = self.base_client.req("SetInputSettings", payload) - return response - - def GetInputMute(self, name): - """ - Gets the audio mute state of an input - - :param name: Name of input to get the mute state of - :type name: str - - - """ - payload = {"inputName": name} - response = self.base_client.req("GetInputMute", payload) - return response - - def SetInputMute(self, name, muted): - """ - Sets the audio mute state of an input. - - :param name: Name of the input to set the mute state of - :type name: str - :param muted: Whether to mute the input or not - :type muted: bool - - - """ - payload = {"inputName": name, "inputMuted": muted} - response = self.base_client.req("SetInputMute", payload) - return response - - def ToggleInputMute(self, name): - """ - Toggles the audio mute state of an input. - - :param name: Name of the input to toggle the mute state of - :type name: str - - - """ - payload = {"inputName": name} - response = self.base_client.req("ToggleInputMute", payload) - return response - - def GetInputVolume(self, name): - """ - Gets the current volume setting of an input. - - :param name: Name of the input to get the volume of - :type name: str - - - """ - payload = {"inputName": name} - response = self.base_client.req("GetInputVolume", payload) - return response - - def SetInputVolume(self, name, vol_mul=None, vol_db=None): - """ - Sets the volume setting of an input. - - :param name: Name of the input to set the volume of - :type name: str - :param vol_mul: Volume setting in mul (>= 0, <= 20) - :type vol_mul: int - :param vol_db: Volume setting in dB (>= -100, <= 26) - :type vol_db: int - - - """ - payload = { - "inputName": name, - "inputVolumeMul": vol_mul, - "inputVolumeDb": vol_db, - } - response = self.base_client.req("SetInputVolume", payload) - return response - - def GetInputAudioBalance(self, name): - """ - Gets the audio balance of an input. - - :param name: Name of the input to get the audio balance of - :type name: str - - - """ - payload = {"inputName": name} - response = self.base_client.req("GetInputAudioBalance", payload) - return response - - def SetInputAudioBalance(self, name, balance): - """ - Sets the audio balance of an input. - - :param name: Name of the input to get the audio balance of - :type name: str - :param balance: New audio balance value (>= 0.0, <= 1.0) - :type balance: int - - - """ - payload = {"inputName": name, "inputAudioBalance": balance} - response = self.base_client.req("SetInputAudioBalance", payload) - return response - - def GetInputAudioOffset(self, name): - """ - Gets the audio sync offset of an input. - - :param name: Name of the input to get the audio sync offset of - :type name: str - - - """ - payload = {"inputName": name} - response = self.base_client.req("GetInputAudioOffset", payload) - return response - - def SetInputAudioSyncOffset(self, name, offset): - """ - Sets the audio sync offset of an input. - - :param name: Name of the input to set the audio sync offset of - :type name: str - :param offset: New audio sync offset in milliseconds (>= -950, <= 20000) - :type offset: int - - - """ - payload = {"inputName": name, "inputAudioSyncOffset": offset} - response = self.base_client.req("SetInputAudioSyncOffset", payload) - return response - - def GetInputAudioMonitorType(self, name): - """ - Gets the audio monitor type of an input. - - The available audio monitor types are: - OBS_MONITORING_TYPE_NONE - OBS_MONITORING_TYPE_MONITOR_ONLY - OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT - - - :param name: Name of the input to get the audio monitor type of - :type name: str - - - """ - payload = {"inputName": name} - response = self.base_client.req("GetInputAudioMonitorType", payload) - return response - - def SetInputAudioMonitorType(self, name, mon_type): - """ - Sets the audio monitor type of an input. - - :param name: Name of the input to set the audio monitor type of - :type name: str - :param mon_type: Audio monitor type - :type mon_type: int - - - """ - payload = {"inputName": name, "monitorType": mon_type} - response = self.base_client.req("SetInputAudioMonitorType", payload) - return response - - def GetInputAudioTracks(self, name): - """ - Gets the enable state of all audio tracks of an input. - - :param name: Name of the input - :type name: str - - - """ - payload = {"inputName": name} - response = self.base_client.req("GetInputAudioTracks", payload) - return response - - def SetInputAudioTracks(self, name, track): - """ - Sets the audio monitor type of an input. - - :param name: Name of the input - :type name: str - :param track: Track settings to apply - :type track: int - - - """ - payload = {"inputName": name, "inputAudioTracks": track} - response = self.base_client.req("SetInputAudioTracks", payload) - return response - - def GetInputPropertiesListPropertyItems(self, input_name, prop_name): - """ - Gets the items of a list property from an input's properties. - Note: Use this in cases where an input provides a dynamic, - selectable list of items. For example, display capture, - where it provides a list of available displays. - - :param input_name: Name of the input - :type input_name: str - :param prop_name: Name of the list property to get the items of - :type prop_name: str - - - """ - payload = {"inputName": input_name, "propertyName": prop_name} - response = self.base_client.req("GetInputPropertiesListPropertyItems", payload) - return response - - def PressInputPropertiesButton(self, input_name, prop_name): - """ - Presses a button in the properties of an input. - Note: Use this in cases where there is a button - in the properties of an input that cannot be accessed in any other way. - For example, browser sources, where there is a refresh button. - - :param input_name: Name of the input - :type input_name: str - :param prop_name: Name of the button property to press - :type prop_name: str - - - """ - payload = {"inputName": input_name, "propertyName": prop_name} - response = self.base_client.req("PressInputPropertiesButton", payload) - return response - - def GetTransitionKindList(self): - """ - Gets an array of all available transition kinds. - Similar to GetInputKindList - - - """ - response = self.base_client.req("GetTransitionKindList") - return response - - def GetSceneTransitionList(self): - """ - Gets an array of all scene transitions in OBS. - - - """ - response = self.base_client.req("GetSceneTransitionList") - return response - - def GetCurrentSceneTransition(self): - """ - Gets an array of all scene transitions in OBS. - - - """ - response = self.base_client.req("GetCurrentSceneTransition") - return response - - def SetCurrentSceneTransition(self, name): - """ - Sets the current scene transition. - Small note: While the namespace of scene transitions is generally unique, - that uniqueness is not a guarantee as it is with other resources like inputs. - - :param name: Name of the transition to make active - :type name: str - - - """ - payload = {"transitionName": name} - response = self.base_client.req("SetCurrentSceneTransition", payload) - return response - - def SetCurrentSceneTransitionDuration(self, duration): - """ - Sets the duration of the current scene transition, if it is not fixed. - - :param duration: Duration in milliseconds (>= 50, <= 20000) - :type duration: str - - - """ - payload = {"transitionDuration": duration} - response = self.base_client.req("SetCurrentSceneTransitionDuration", payload) - return response - - def SetCurrentSceneTransitionSettings(self, settings, overlay=None): - """ - Sets the settings of the current scene transition. - - :param settings: Settings object to apply to the transition. Can be {} - :type settings: dict - :param overlay: Whether to overlay over the current settings or replace them - :type overlay: bool - - - """ - payload = {"transitionSettings": settings, "overlay": overlay} - response = self.base_client.req("SetCurrentSceneTransitionSettings", payload) - return response - - def GetCurrentSceneTransitionCursor(self): - """ - Gets the cursor position of the current scene transition. - Note: transitionCursor will return 1.0 when the transition is inactive. - - - """ - response = self.base_client.req("GetCurrentSceneTransitionCursor") - return response - - def TriggerStudioModeTransition(self): - """ - Triggers the current scene transition. - Same functionality as the Transition button in studio mode. - Note: Studio mode should be active. if not throws an - RequestStatus::StudioModeNotActive (506) in response - - - """ - response = self.base_client.req("TriggerStudioModeTransition") - return response - - def SetTBarPosition(self, pos, release=None): - """ - Sets the position of the TBar. - Very important note: This will be deprecated - and replaced in a future version of obs-websocket. - - :param pos: New position (>= 0.0, <= 1.0) - :type pos: float - :param release: Whether to release the TBar. Only set false if you know that you will be sending another position update - :type release: bool - - - """ - payload = {"position": pos, "release": release} - response = self.base_client.req("SetTBarPosition", payload) - return response - - def GetSourceFilterList(self, name): - """ - Gets a list of all of a source's filters. - - :param name: Name of the source - :type name: str - - - """ - payload = {"sourceName": name} - response = self.base_client.req("GetSourceFilterList", payload) - return response - - def GetSourceFilterDefaultSettings(self, kind): - """ - Gets the default settings for a filter kind. - - :param kind: Filter kind to get the default settings for - :type kind: str - - - """ - payload = {"filterKind": kind} - response = self.base_client.req("GetSourceFilterDefaultSettings", payload) - return response - - def CreateSourceFilter( - self, source_name, filter_name, filter_kind, filter_settings=None - ): - """ - Gets the default settings for a filter kind. - - :param source_name: Name of the source to add the filter to - :type source_name: str - :param filter_name: Name of the new filter to be created - :type filter_name: str - :param filter_kind: The kind of filter to be created - :type filter_kind: str - :param filter_settings: Settings object to initialize the filter with - :type filter_settings: dict - - - """ - payload = { - "sourceName": source_name, - "filterName": filter_name, - "filterKind": filter_kind, - "filterSettings": filter_settings, - } - response = self.base_client.req("CreateSourceFilter", payload) - return response - - def RemoveSourceFilter(self, source_name, filter_name): - """ - Gets the default settings for a filter kind. - - :param source_name: Name of the source the filter is on - :type source_name: str - :param filter_name: Name of the filter to remove - :type filter_name: str - - - """ - payload = { - "sourceName": source_name, - "filterName": filter_name, - } - response = self.base_client.req("RemoveSourceFilter", payload) - return response - - def SetSourceFilterName(self, source_name, old_filter_name, new_filter_name): - """ - Sets the name of a source filter (rename). - - :param source_name: Name of the source the filter is on - :type source_name: str - :param old_filter_name: Current name of the filter - :type old_filter_name: str - :param new_filter_name: New name for the filter - :type new_filter_name: str - - - """ - payload = { - "sourceName": source_name, - "filterName": old_filter_name, - "newFilterName": new_filter_name, - } - response = self.base_client.req("SetSourceFilterName", payload) - return response - - def GetSourceFilter(self, source_name, filter_name): - """ - Gets the info for a specific source filter. - - :param source_name: Name of the source - :type source_name: str - :param filter_name: Name of the filter - :type filter_name: str - - - """ - payload = {"sourceName": source_name, "filterName": filter_name} - response = self.base_client.req("GetSourceFilter", payload) - return response - - def SetSourceFilterIndex(self, source_name, filter_name, filter_index): - """ - Gets the info for a specific source filter. - - :param source_name: Name of the source the filter is on - :type source_name: str - :param filter_name: Name of the filter - :type filter_name: str - :param filterIndex: New index position of the filter (>= 0) - :type filterIndex: int - - - """ - payload = { - "sourceName": source_name, - "filterName": filter_name, - "filterIndex": filter_index, - } - response = self.base_client.req("SetSourceFilterIndex", payload) - return response - - def SetSourceFilterSettings(self, source_name, filter_name, settings, overlay=None): - """ - Gets the info for a specific source filter. - - :param source_name: Name of the source the filter is on - :type source_name: str - :param filter_name: Name of the filter to set the settings of - :type filter_name: str - :param settings: Dictionary of settings to apply - :type settings: dict - :param overlay: True == apply the settings on top of existing ones, False == reset the input to its defaults, then apply settings. - :type overlay: bool - - - """ - payload = { - "sourceName": source_name, - "filterName": filter_name, - "filterSettings": settings, - "overlay": overlay, - } - response = self.base_client.req("SetSourceFilterSettings", payload) - return response - - def SetSourceFilterEnabled(self, source_name, filter_name, enabled): - """ - Gets the info for a specific source filter. - - :param source_name: Name of the source the filter is on - :type source_name: str - :param filter_name: Name of the filter - :type filter_name: str - :param enabled: New enable state of the filter - :type enabled: bool - - - """ - payload = { - "sourceName": source_name, - "filterName": filter_name, - "filterEnabled": enabled, - } - response = self.base_client.req("SetSourceFilterEnabled", payload) - return response - - def GetSceneItemList(self, name): - """ - Gets a list of all scene items in a scene. - - :param name: Name of the scene to get the items of - :type name: str - - - """ - payload = {"sceneName": name} - response = self.base_client.req("GetSceneItemList", payload) - return response - - def GetGroupItemList(self, name): - """ - Gets a list of all scene items in a scene. - - :param name: Name of the group to get the items of - :type name: str - - - """ - payload = {"sceneName": name} - response = self.base_client.req("GetGroupItemList", payload) - return response - - def GetSceneItemId(self, scene_name, source_name, offset=None): - """ - Searches a scene for a source, and returns its id. - - :param scene_name: Name of the scene or group to search in - :type scene_name: str - :param source_name: Name of the source to find - :type source_name: str - :param offset: Number of matches to skip during search. >= 0 means first forward. -1 means last (top) item (>= -1) - :type offset: int - - - """ - payload = { - "sceneName": scene_name, - "sourceName": source_name, - "searchOffset": offset, - } - response = self.base_client.req("GetSceneItemId", payload) - return response - - def CreateSceneItem(self, scene_name, source_name, enabled=None): - """ - Creates a new scene item using a source. - Scenes only - - :param scene_name: Name of the scene to create the new item in - :type scene_name: str - :param source_name: Name of the source to add to the scene - :type source_name: str - :param enabled: Enable state to apply to the scene item on creation - :type enabled: bool - - - """ - payload = { - "sceneName": scene_name, - "sourceName": source_name, - "sceneItemEnabled": enabled, - } - response = self.base_client.req("CreateSceneItem", payload) - return response - - def RemoveSceneItem(self, scene_name, item_id): - """ - Removes a scene item from a scene. - Scenes only - - :param scene_name: Name of the scene the item is in - :type scene_name: str - :param item_id: Numeric ID of the scene item - :type item_id: int - - - """ - payload = { - "sceneName": scene_name, - "sceneItemId": item_id, - } - response = self.base_client.req("RemoveSceneItem", payload) - return response - - def DuplicateSceneItem(self, scene_name, item_id, dest_scene_name=None): - """ - Duplicates a scene item, copying all transform and crop info. - Scenes only - - :param scene_name: Name of the scene the item is in - :type scene_name: str - :param item_id: Numeric ID of the scene item (>= 0) - :type item_id: int - :param dest_scene_name: Name of the scene to create the duplicated item in - :type dest_scene_name: str - - - """ - payload = { - "sceneName": scene_name, - "sceneItemId": item_id, - "destinationSceneName": dest_scene_name, - } - response = self.base_client.req("DuplicateSceneItem", payload) - return response - - def GetSceneItemTransform(self, scene_name, item_id): - """ - Gets the transform and crop info of a scene item. - Scenes and Groups - - :param scene_name: Name of the scene the item is in - :type scene_name: str - :param item_id: Numeric ID of the scene item (>= 0) - :type item_id: int - - - """ - payload = { - "sceneName": scene_name, - "sceneItemId": item_id, - } - response = self.base_client.req("GetSceneItemTransform", payload) - return response - - def SetSceneItemTransform(self, scene_name, item_id, transform): - """ - Sets the transform and crop info of a scene item. - - :param scene_name: Name of the scene the item is in - :type scene_name: str - :param item_id: Numeric ID of the scene item (>= 0) - :type item_id: int - :param transform: Dictionary containing scene item transform info to update - :type transform: dict - """ - payload = { - "sceneName": scene_name, - "sceneItemId": item_id, - "sceneItemTransform": transform, - } - response = self.base_client.req("SetSceneItemTransform", payload) - return response - - def GetSceneItemEnabled(self, scene_name, item_id): - """ - Gets the enable state of a scene item. - Scenes and Groups - - :param scene_name: Name of the scene the item is in - :type scene_name: str - :param item_id: Numeric ID of the scene item (>= 0) - :type item_id: int - - - """ - payload = { - "sceneName": scene_name, - "sceneItemId": item_id, - } - response = self.base_client.req("GetSceneItemEnabled", payload) - return response - - def SetSceneItemEnabled(self, scene_name, item_id, enabled): - """ - Sets the enable state of a scene item. - Scenes and Groups' - - :param scene_name: Name of the scene the item is in - :type scene_name: str - :param item_id: Numeric ID of the scene item (>= 0) - :type item_id: int - :param enabled: New enable state of the scene item - :type enabled: bool - - - """ - payload = { - "sceneName": scene_name, - "sceneItemId": item_id, - "sceneItemEnabled": enabled, - } - response = self.base_client.req("SetSceneItemEnabled", payload) - return response - - def GetSceneItemLocked(self, scene_name, item_id): - """ - Gets the lock state of a scene item. - Scenes and Groups - - :param scene_name: Name of the scene the item is in - :type scene_name: str - :param item_id: Numeric ID of the scene item (>= 0) - :type item_id: int - - - """ - payload = { - "sceneName": scene_name, - "sceneItemId": item_id, - } - response = self.base_client.req("GetSceneItemLocked", payload) - return response - - def SetSceneItemLocked(self, scene_name, item_id, locked): - """ - Sets the lock state of a scene item. - Scenes and Groups - - :param scene_name: Name of the scene the item is in - :type scene_name: str - :param item_id: Numeric ID of the scene item (>= 0) - :type item_id: int - :param locked: New lock state of the scene item - :type locked: bool - - - """ - payload = { - "sceneName": scene_name, - "sceneItemId": item_id, - "sceneItemLocked": locked, - } - response = self.base_client.req("SetSceneItemLocked", payload) - return response - - def GetSceneItemIndex(self, scene_name, item_id): - """ - Gets the index position of a scene item in a scene. - An index of 0 is at the bottom of the source list in the UI. - Scenes and Groups - - :param scene_name: Name of the scene the item is in - :type scene_name: str - :param item_id: Numeric ID of the scene item (>= 0) - :type item_id: int - - - """ - payload = { - "sceneName": scene_name, - "sceneItemId": item_id, - } - response = self.base_client.req("GetSceneItemIndex", payload) - return response - - def SetSceneItemIndex(self, scene_name, item_id, item_index): - """ - Sets the index position of a scene item in a scene. - Scenes and Groups - - :param scene_name: Name of the scene the item is in - :type scene_name: str - :param item_id: Numeric ID of the scene item (>= 0) - :type item_id: int - :param item_index: New index position of the scene item (>= 0) - :type item_index: int - - - """ - payload = { - "sceneName": scene_name, - "sceneItemId": item_id, - "sceneItemLocked": item_index, - } - response = self.base_client.req("SetSceneItemIndex", payload) - return response - - def GetSceneItemBlendMode(self, scene_name, item_id): - """ - Gets the blend mode of a scene item. - Blend modes: - - OBS_BLEND_NORMAL - OBS_BLEND_ADDITIVE - OBS_BLEND_SUBTRACT - OBS_BLEND_SCREEN - OBS_BLEND_MULTIPLY - OBS_BLEND_LIGHTEN - OBS_BLEND_DARKEN - Scenes and Groups - - :param scene_name: Name of the scene the item is in - :type scene_name: str - :param item_id: Numeric ID of the scene item (>= 0) - :type item_id: int - - - """ - payload = { - "sceneName": scene_name, - "sceneItemId": item_id, - } - response = self.base_client.req("GetSceneItemBlendMode", payload) - return response - - def SetSceneItemBlendMode(self, scene_name, item_id, blend): - """ - Sets the blend mode of a scene item. - Scenes and Groups - - :param scene_name: Name of the scene the item is in - :type scene_name: str - :param item_id: Numeric ID of the scene item (>= 0) - :type item_id: int - :param blend: New blend mode - :type blend: str - - - """ - payload = { - "sceneName": scene_name, - "sceneItemId": item_id, - "sceneItemBlendMode": blend, - } - response = self.base_client.req("SetSceneItemBlendMode", payload) - return response - - def GetVirtualCamStatus(self): - """ - Gets the status of the virtualcam output. - - - """ - response = self.base_client.req("GetVirtualCamStatus") - return response - - def ToggleVirtualCam(self): - """ - Toggles the state of the virtualcam output. - - - """ - response = self.base_client.req("ToggleVirtualCam") - return response - - def StartVirtualCam(self): - """ - Starts the virtualcam output. - - - """ - response = self.base_client.req("StartVirtualCam") - return response - - def StopVirtualCam(self): - """ - Stops the virtualcam output. - - - """ - response = self.base_client.req("StopVirtualCam") - return response - - def GetReplayBufferStatus(self): - """ - Gets the status of the replay buffer output. - - - """ - response = self.base_client.req("GetReplayBufferStatus") - return response - - def ToggleReplayBuffer(self): - """ - Toggles the state of the replay buffer output. - - - """ - response = self.base_client.req("ToggleReplayBuffer") - return response - - def StartReplayBuffer(self): - """ - Starts the replay buffer output. - - - """ - response = self.base_client.req("StartReplayBuffer") - return response - - def StopReplayBuffer(self): - """ - Stops the replay buffer output. - - - """ - response = self.base_client.req("StopReplayBuffer") - return response - - def SaveReplayBuffer(self): - """ - Saves the contents of the replay buffer output. - - - """ - response = self.base_client.req("SaveReplayBuffer") - return response - - def GetLastReplayBufferReplay(self): - """ - Gets the filename of the last replay buffer save file. - - - """ - response = self.base_client.req("GetLastReplayBufferReplay") - return response - - def GetStreamStatus(self): - """ - Gets the status of the stream output. - - - """ - response = self.base_client.req("GetStreamStatus") - return response - - def ToggleStream(self): - """ - Toggles the status of the stream output. - - - """ - response = self.base_client.req("ToggleStream") - return response - - def StartStream(self): - """ - Starts the stream output. - - - """ - response = self.base_client.req("StartStream") - return response - - def StopStream(self): - """ - Stops the stream output. - - - """ - response = self.base_client.req("StopStream") - return response - - def SendStreamCaption(self, caption): - """ - Sends CEA-608 caption text over the stream output. - - :param caption: Caption text - :type caption: str - - - """ - response = self.base_client.req("SendStreamCaption") - return response - - def GetRecordStatus(self): - """ - Gets the status of the record output. - - - """ - response = self.base_client.req("GetRecordStatus") - return response - - def ToggleRecord(self): - """ - Toggles the status of the record output. - - - """ - response = self.base_client.req("ToggleRecord") - return response - - def StartRecord(self): - """ - Starts the record output. - - - """ - response = self.base_client.req("StartRecord") - return response - - def StopRecord(self): - """ - Stops the record output. - - - """ - response = self.base_client.req("StopRecord") - return response - - def ToggleRecordPause(self): - """ - Toggles pause on the record output. - - - """ - response = self.base_client.req("ToggleRecordPause") - return response - - def PauseRecord(self): - """ - Pauses the record output. - - - """ - response = self.base_client.req("PauseRecord") - return response - - def ResumeRecord(self): - """ - Resumes the record output. - - - """ - response = self.base_client.req("ResumeRecord") - return response - - def GetMediaInputStatus(self, name): - """ - Gets the status of a media input. - - Media States: - OBS_MEDIA_STATE_NONE - OBS_MEDIA_STATE_PLAYING - OBS_MEDIA_STATE_OPENING - OBS_MEDIA_STATE_BUFFERING - OBS_MEDIA_STATE_PAUSED - OBS_MEDIA_STATE_STOPPED - OBS_MEDIA_STATE_ENDED - OBS_MEDIA_STATE_ERROR - - :param name: Name of the media input - :type name: str - - - """ - payload = {"inputName": name} - response = self.base_client.req("GetMediaInputStatus", payload) - return response - - def SetMediaInputCursor(self, name, cursor): - """ - Sets the cursor position of a media input. - This request does not perform bounds checking of the cursor position. - - :param name: Name of the media input - :type name: str - :param cursor: New cursor position to set (>= 0) - :type cursor: int - - - """ - payload = {"inputName": name, "mediaCursor": cursor} - response = self.base_client.req("SetMediaInputCursor", payload) - return response - - def OffsetMediaInputCursor(self, name, offset): - """ - Offsets the current cursor position of a media input by the specified value. - This request does not perform bounds checking of the cursor position. - - :param name: Name of the media input - :type name: str - :param offset: Value to offset the current cursor position by - :type offset: int - - - """ - payload = {"inputName": name, "mediaCursorOffset": offset} - response = self.base_client.req("OffsetMediaInputCursor", payload) - return response - - def TriggerMediaInputAction(self, name, action): - """ - Triggers an action on a media input. - - :param name: Name of the media input - :type name: str - :param action: Identifier of the ObsMediaInputAction enum - :type action: str - - - """ - payload = {"inputName": name, "mediaAction": action} - response = self.base_client.req("TriggerMediaInputAction", payload) - return response - - def GetStudioModeEnabled(self): - """ - Gets whether studio is enabled. - - - """ - response = self.base_client.req("GetStudioModeEnabled") - return response - - def SetStudioModeEnabled(self, enabled): - """ - Enables or disables studio mode - - :param enabled: True == Enabled, False == Disabled - :type enabled: bool - - - """ - payload = {"studioModeEnabled": enabled} - response = self.base_client.req("SetStudioModeEnabled", payload) - return response - - def OpenInputPropertiesDialog(self, name): - """ - Opens the properties dialog of an input. - - :param name: Name of the input to open the dialog of - :type name: str - - - """ - payload = {"inputName": name} - response = self.base_client.req("OpenInputPropertiesDialog", payload) - return response - - def OpenInputFiltersDialog(self, name): - """ - Opens the filters dialog of an input. - - :param name: Name of the input to open the dialog of - :type name: str - - - """ - payload = {"inputName": name} - response = self.base_client.req("OpenInputFiltersDialog", payload) - return response - - def OpenInputInteractDialog(self, name): - """ - Opens the filters dialog of an input. - - :param name: Name of the input to open the dialog of - :type name: str - - - """ - payload = {"inputName": name} - response = self.base_client.req("OpenInputInteractDialog", payload) - return response - - def GetMonitorList(self, name): - """ - Gets a list of connected monitors and information about them. - - - """ - response = self.base_client.req("GetMonitorList") - return response diff --git a/build/lib/obsstudio_sdk/subject.py b/build/lib/obsstudio_sdk/subject.py deleted file mode 100644 index 24732c5..0000000 --- a/build/lib/obsstudio_sdk/subject.py +++ /dev/null @@ -1,58 +0,0 @@ -import re - - -class Callback: - """Adds support for callbacks""" - - def __init__(self): - """list of current callbacks""" - - self._callbacks = list() - - def to_camel_case(self, s): - s = "".join(word.title() for word in s.split("_")) - return s[2:] - - def to_snake_case(self, s): - s = re.sub(r"(? list: - """returns a list of registered events""" - - return [self.to_camel_case(fn.__name__) for fn in self._callbacks] - - def trigger(self, event, data=None): - """trigger callback on update""" - - for fn in self._callbacks: - if fn.__name__ == self.to_snake_case(event): - if "eventData" in data: - fn(data["eventData"]) - else: - fn() - - def register(self, fns): - """registers callback functions""" - - try: - iter(fns) - for fn in fns: - if fn not in self._callbacks: - self._callbacks.append(fn) - except TypeError as e: - if fns not in self._callbacks: - self._callbacks.append(fns) - - def deregister(self, callback): - """deregisters a callback from _callbacks""" - - try: - self._callbacks.remove(callback) - except ValueError: - print(f"Failed to remove: {callback}") - - def clear(self): - """clears the _callbacks list""" - - self._callbacks.clear() diff --git a/examples/events/__pycache__/__main__.cpython-311.pyc b/examples/events/__pycache__/__main__.cpython-311.pyc deleted file mode 100644 index d325b3d0c93ce31d6c5a504ad563a1f117342b67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1847 zcmaJBOKjsrbZp1zI8EA3s#2ngV6?e-EA0WwE^3z|mMvQ@S&2#=)TEZr}O5;hMOXIDj|KKJo z=CS4QFZRP!IPN0?kyxk-Rv)3N#N(-^ED5k|r8bc!2lzpq5&Do&Gl>u`%CIHa?B~!* zPv{{Xs0ey!J0{U~><=BBzMsfJm)i*e=p<7O@oZKEQ zY+DFo6)WAj4@tvj9-+ii$wT6LY(>?k0!cTW0Go#6)D3ed!1P!vP+P8Ub|?i+y-iyU zYBY6b5|`-amf<#t70iML*|j~Lc?R`>A-AdRdV$m+o+btMb9q1a`jdP9^2RXx!7~Y1&pCg>(cfvL%nWDV zKDhVIhF^a3X?gXiygDkc`sG#N!W!Wvp34`*&2EPprcUtb0a)(GGB@$F%}!k8;snm3~hkJ0rY13lWZIu56^y8cPW za3TrKfzdGSlt1Zv6kd+r$mf7^l!DFP1=LS|6H%7mnVn;rX8qXvj(2S-8xicddNN^0FLJRzEc0K$eyyw_3?v#Ix0t_1A$1#v? zx7~rUHk%fm2hmAHFYuix3v2Wh5b(ZZKLPr8h!&GIN#_K3`$hDO^PZmwRFujNx<^uR zg3>}^tP}@xzET`1i@vhhzdN2?80>y2e%{;b^*4UYzx2iYaN))%zvSnahMA?ob)Kxk kc(wppQVQd2ey~1~QDNaywNN)g3b+dWO)%UKd0~A20ZQJSs{jB1 diff --git a/examples/scene_rotate/__pycache__/__main__.cpython-311.pyc b/examples/scene_rotate/__pycache__/__main__.cpython-311.pyc deleted file mode 100644 index 7571f193ad6c60314449ec47d1f3a5c322caefe6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1267 zcmZWo&1(}u6o0#$k4c(Uv|25TGzby*5=47ZO2wvB!4kwkTT3*g*`eJwU(QVYC@HMa zLxmi?NDC_TR1_~BJoHcSn3OnO7}!9p*Lav$Pe@lNl;db=ltu-$CYMM}YYNTD5+i zf*DiZHGMy&XEN9IM<$3@!^^K?Wt$*+Y}=^$fAwcy1JSJoK*(%=m!+&j6)bhQqX0|m z;eE&aB=qOp!FNA)-|m;zl&Ufb4=2`C5$Yxe%U@OBz&o|ySLsL7Q|EDH_5T?C*t*=s zV+H=rT}xPy0yCJ*bxXxS*~)8p`6VQGLBW<*;bBCUZNV|*8Q4@5sOfSvrs{@xjuvS$ zTcF7gQdZ+vT*cyY;?d~g^{E@_+qPJ$ETl^doQsNOmonU1Nz-T9LOD-)+6&LHlE@00 z&SVN%yO_zu%g+x(V~d#hS(%Nmo|D@6SiY3Y=J|NMYapF!f4~oLpl;0KdH;Cp*4E0~ zH^KhN6c@dKU3}$z}H(ko Hm-qA^$c-O@ diff --git a/obsstudio_sdk/__pycache__/__init__.cpython-311.pyc b/obsstudio_sdk/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 6cf5468f34ab673b96f8c9caf5b67e24a30d9143..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 300 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 diff --git a/obsstudio_sdk/__pycache__/callback.cpython-311.pyc b/obsstudio_sdk/__pycache__/callback.cpython-311.pyc deleted file mode 100644 index 77dad5cb1c9b3df1e492eb459979ef7d9a28760c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3874 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