OBS Studio Python Scripting Reference

repository·master·Indexed 19 days ago

https://github.com/upgradeq/streaming-software-scripting-reference

A reference and cheatsheet for scripting OBS Studio using Python. It provides practical examples for interacting with the OBS API, managing sources, filters, scenes, and UI properties, including identifier strings for source and filter types.

Tokens
18.5K
Snippets
47
Records
55
Agent score
63%

What's inside streaming-software-scripting-reference

  1. Read and write private data between scripts or plugins

    master

    You can share data between different scripts or plugins using OBS private data. This allows one script to write a value that another script can later retrieve.

    Writing Data (Python)

    Use S.obs_data_create(), set the value using the appropriate obs_data_set_<type> function, and then call S.obs_apply_private_data(settings) to commit it.

    Reading Data (Python)

    It is recommended to use a context manager to handle the lifecycle of the private data settings. Retrieve the data using S.obs_get_private_data() and the corresponding obs_data_get_<type> function, ensuring you call S.obs_data_release(settings) when finished.

    Writing Data (Lua)

    In Lua, create settings with S.obs_data_create(), set the value (e.g., S.obs_data_set_int), apply it with S.obs_apply_private_data(settings), and release the settings.

    # Writing in Python
    def send_to_private_data(data_type, field, result):
        settings = S.obs_data_create()
        set_func = getattr(obs, f"obs_data_set_{data_type}")
        set_func(settings, field, result)
        S.obs_apply_private_data(settings)
        S.obs_data_release(settings)
    
    # Reading in Python
    @contextmanager
    def p_data_ar(data_type, field):
        settings = S.obs_get_private_data()
        get_func = getattr(obs, f"obs_data_get_{data_type}")
        try:
            yield get_func(settings, field)
        finally:
            S.obs_data_release(settings)
  2. Create UI properties in OBS

    master

    You can create various UI elements for your script settings using the S.obs_properties_* functions within the script_properties() function. Common property types include:

    • Buttons: S.obs_properties_add_button(props, "id", "label", callback)
    • Booleans: S.obs_properties_add_bool(props, "id", "label")
    • Integers: S.obs_properties_add_int(props, "id", "label", min, max, step)
    • Integer Sliders: S.obs_properties_add_int_slider(props, "id", "label", min, max, step)
    • Text: S.obs_properties_add_text(props, "id", "label", default_text)
    • Color: S.obs_properties_add_color(props, "id", "label")
    • Font: S.obs_properties_add_font(props, "id", "label")

    Use S.obs_property_set_long_description(property, description) to add a detailed description to a property.

    props = S.obs_properties_create()
    S.obs_properties_add_button(props, "button1", "Refresh1:", callback)
    S.obs_properties_add_bool(props, "_bool", "_bool:")
    S.obs_properties_add_int(props, "_int", "_int:", 1, 100, 1)
    S.obs_properties_add_int_slider(props, "_slider", "_slider:", 1, 100, 1)
    S.obs_properties_add_text(props, "_text", "_text:", S.OBS_TEXT_DEFAULT)
    S.obs_properties_add_color(props, "_color", "_color:")
    S.obs_properties_add_font(props, "_font", "_font:")
  3. Show or hide properties based on input

    master

    You can implement conditional UI logic by checking the current settings in a modified callback and using S.obs_property_set_visible(property, bool) to show or hide other properties.

    def callback(props, prop, settings):
        _number = S.obs_data_get_int(settings, "_int")
        text_property = S.obs_properties_get(props, "_text")
        if _number > 50:
            S.obs_property_set_visible(text_property, True)
        else:
            S.obs_property_set_visible(text_property, False)
        return True
    
    def script_properties():
        props = S.obs_properties_create()
        number = S.obs_properties_add_int(props, "_int", "Number", 1, 100, 1)
        text_value = S.obs_properties_add_text(props, "_text", "Additional input:", S.OBS_TEXT_DEFAULT)
        S.obs_property_set_visible(text_value, False)
        S.obs_property_set_modified_callback(number, callback)
        return props
  4. Access source decibel (dB) volume levels via FFI

    master

    To access high-performance data like real-time volume meters, you can use Python's ctypes to wrap the native obs library.

    Note for Linux users: You must start OBS with LD_PRELOAD pointing to your Python shared library (e.g., LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libpython3.9.so obs) to ensure the FFI can correctly interface with the running process.

    # Example for Linux
    LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libpython3.9.so obs
  5. Modify properties and handle callbacks

    master

    To make your UI interactive, use S.obs_property_set_modified_callback(property, callback). When a property is changed, the callback is triggered. You can use S.obs_properties_get(props, "id") to retrieve a property object and S.obs_property_set_description(property, description) to update its description dynamically.

    def callback(props, prop, *args, **kwargs):
        p = S.obs_properties_get(props, "button")
        S.obs_property_set_description(p, "refresh pressed")
        return True
    
    def script_properties():
        props = S.obs_properties_create()
        b = S.obs_properties_add_button(props, "button", "refresh pressed 0 times", refresh_pressed)
        S.obs_property_set_modified_callback(b, callback)
        return props
  6. Set the current streaming service key

    master

    You can programmatically update the streaming key for the active service using the following steps:

    1. Get the current streaming service via S.obs_frontend_get_streaming_service().
    2. Retrieve its settings with S.obs_service_get_settings(service).
    3. Set the key field using S.obs_data_set_string(settings, "key", your_key).
    4. Update the service with S.obs_service_update(service, settings).
    5. Release the settings and save the service via S.obs_frontend_save_streaming_service().
    service = S.obs_frontend_get_streaming_service()
    settings = S.obs_service_get_settings(service)
    S.obs_data_set_string(settings, "key", _G._my_key)
    S.obs_service_update(service, settings)
    S.obs_data_release(settings)
    S.obs_frontend_save_streaming_service()
  7. Send hotkeys to a Browser Source

    master

    You can programmatically simulate key presses (like Tab or Shift+Tab) within a Browser Source. This involves converting an OBS key name to a virtual key, creating an event, and using S.obs_source_send_key_click.

    def send_hotkey_to_browser(source, obs_htk_id, key_modifiers=None, key_up=False):
        key = S.obs_key_from_name(obs_htk_id)
        vk = S.obs_key_to_virtual_key(key)
        event = S.obs_key_event()
        event.native_vkey = vk
        event.modifiers = get_modifiers(key_modifiers)
        event.native_modifiers = event.modifiers
        event.native_scancode = vk
        event.text = ""
        S.obs_source_send_key_click(source, event, key_up)
  8. Send JSON data to a Browser Source

    master

    To communicate with a web page inside a Browser Source, you can trigger a JavaScript event and pass a JSON string.

    1. In Python: Create a calldata object, set the eventName and the jsonString (containing your data), and call S.proc_handler_call(ph, "javascript_event", cd) where ph is the source's procedure handler.
    2. In JavaScript: Add an event listener for your custom event name and access the data via event.detail.
    # Python side
    cd = S.calldata_create()
    ph = S.obs_source_get_proc_handler(source)
    S.calldata_set_string(cd, "eventName", "my-test-event")
    S.calldata_set_string(cd, "jsonString", '{"key123": "\nvalue123"}')
    S.proc_handler_call(ph, "javascript_event", cd)
    S.calldata_destroy(cd)
    // JavaScript side
    window.addEventListener('my-test-event', function(event) {
      document.body.innerHTML += (event.detail['key123']);
    })
  9. Manage background thread lifecycles in OBS scripts

    master

    To prevent hanging threads or crashes when a script is unloaded, implement a shutdown flag that the background thread checks periodically. Use the script_unload() entrypoint to set this flag.

    Pattern for safe threads:

    1. Define a shared state object (or global variables) containing a shutdown flag.
    2. In script_load(), start a threading.Thread targeting a loop function.
    3. In the loop, check the shutdown flag frequently.
    4. In script_unload(), set the shutdown flag to True to allow the thread to exit gracefully.
    import threading
    
    # Shared state
    data = {
        'shutdown': False
    }
    
    def busy_thread():
        while not data['shutdown']:
            # Perform work
            pass
    
    def script_load(settings):
        t = threading.Thread(target=busy_thread)
        t.start()
    
    def script_unload():
        data['shutdown'] = True
  10. Implement an OBS script using a class structure

    master

    When writing complex OBS scripts, you can use a Python class to maintain state (like a target source name) and encapsulate logic. This allows you to separate the script's internal state from the global OBS script lifecycle functions like script_update or script_properties.

    Key lifecycle functions used in this pattern:

    • script_description(): Returns a string describing the script.
    • script_update(settings): Called when script settings change. Use this to update your class instance state and manage timers.
    • script_properties(): Defines the UI elements shown in the OBS Scripts window.

    To manage recurring tasks, use S.timer_add(callback, interval_ms) and S.timer_remove(callback) within script_update to ensure timers are correctly restarted when settings change.

    import obspython as S
    
    class Example:
        def __init__(self, source_name=None):
            self.source_name = source_name
    
        def update_text(self):
            source = S.obs_get_source_by_name(self.source_name)
            if source is not None:
                # Logic to update source settings
                settings = S.obs_data_create()
                S.obs_data_set_string(settings, "text", "new_value")
                S.obs_source_update(source, settings)
                S.obs_data_release(settings)
                S.obs_source_release(source)
    
    # Global instance
    eg = Example()
    
    def script_update(settings):
        eg.source_name = S.obs_data_get_string(settings, "source")
        S.timer_remove(eg.update_text)
        if eg.source_name != "":
            S.timer_add(eg.update_text, 1000)
    
    def script_properties():
        props = S.obs_properties_create()
        # Add UI properties here
        return props
  11. Implement script lifecycle functions in OBS

    master

    OBS scripts rely on specific lifecycle hooks to manage properties, settings updates, and initialization. When writing a script, implement the following functions:

    • script_properties(): Returns an obs_properties_t object created via S.obs_properties_create(). This defines the UI elements (like text fields or buttons) visible in the script settings.
    • script_update(settings): Called when settings are changed in the UI. Use this to extract values from the settings object (e.g., via S.obs_data_get_string) and update your script's internal state.
    • script_load(settings): Called when the script is loaded. Use this for initialization, such as setting up hotkeys or loading initial configuration.
    • script_save(settings): Called when the script settings are saved. Use this to persist custom data or hotkey bindings to the settings object.
    import obspython as S
    
    def script_properties():
        props = S.obs_properties_create()
        S.obs_properties_add_text(props, "_text1", "Label:", S.OBS_TEXT_DEFAULT)
        return props
    
    def script_update(settings):
        val = S.obs_data_get_string(settings, "_text1")
        # Update internal state with val
    
    def script_load(settings):
        # Initialize resources
        pass
    
    def script_save(settings):
        # Persist data
        pass
  12. Register and persist hotkeys in OBS scripts

    master

    To implement hotkeys that persist across OBS sessions, you must register the hotkey using obs_hotkey_register_frontend, load the previously saved key from the script settings, and save the key back to the settings when the script is saved.

    1. Register: Use S.obs_hotkey_register_frontend(id, description, callback) to create the hotkey.
    2. Load: Retrieve the saved key array from the settings using S.obs_data_get_array(settings, id) and apply it with S.obs_hotkey_load(hotkey_id, key_array).
    3. Save: Capture the current key using S.obs_hotkey_save(hotkey_id) and store it in the settings using S.obs_data_set_array(settings, id, key_array).

    Note: Always call S.obs_data_array_release(array) after using an array retrieved from obs_data to prevent memory leaks.

    import obspython as S
    
    # Inside your script logic
    class Hotkey:
        def __init__(self, callback, obs_settings, _id):
            self.obs_data = obs_settings
            self.hotkey_id = S.OBS_INVALID_HOTKEY_ID
            self.hotkey_saved_key = None
            self.callback = callback
            self._id = _id
    
        def register_hotkey(self):
            description = "Hotkey description"
            self.hotkey_id = S.obs_hotkey_register_frontend(
                "htk_id" + str(self._id), description, self.callback
            )
            # Load existing key if available
            if self.hotkey_saved_key:
                S.obs_hotkey_load(self.hotkey_id, self.hotkey_saved_key)
    
        def save_hotkey(self):
            self.hotkey_saved_key = S.obs_hotkey_save(self.hotkey_id)
            S.obs_data_set_array(
                self.obs_data, "htk_id" + str(self._id), self.hotkey_saved_key
            )
            S.obs_data_array_release(self.hotkey_saved_key)