Scripting - WarmUpTill/SceneSwitcher GitHub Wiki
This section will describe how to use the scripting API to add custom macro conditions and actions.
The API is not limited to scripts or Python, but the examples showcased here will only be using Python for brevity.
Examples
Custom macro condition
This examples shows how to register a new condition type which will randomly evaluate to true
based on provided user input percentage in the range from 0 to 100.
What it looks like
This is the custom condition type we will define with this script in action:
As you can see the frequency at which this condition returns true depends on the provided input value, just as we expect it to.
The code
import obspython as obs
import threading # Required by advss helpers
import random
CONDITION_NAME = "Random condition"
###############################################################################
# This function will define the UI for the custom condition type based on a obs_properties object.
# In this case only a single float selection field for specifying the probability of returning true.
# This isn't mandatory.
# You can also have a condition without any additional UI elements.
###############################################################################
def get_condition_properties():
props = obs.obs_properties_create()
obs.obs_properties_add_float(
props, "probability", "Probability of returning true", 0, 100, 0.1
)
return props
###############################################################################
# You can provide default values for each of the settings you have defined earlier.
# This isn't mandatory.
# You can also have a condition without any settings to be modified.
###############################################################################
def get_condition_defaults():
default_settings = obs.obs_data_create()
obs.obs_data_set_default_double(default_settings, "probability", 33.3)
return default_settings
###############################################################################
# This function will be used by the advanced scene switcher to determine if a condition evaluates to true or false.
# The settings for each instance of this condition type will be passed via the data parameter.
###############################################################################
def my_python_condition(data, instance_id):
target = obs.obs_data_get_double(data, "probability")
value = random.uniform(0, 100)
return value <= target
###############################################################################
# Let's register the new condition type
###############################################################################
def script_load(settings):
advss_register_condition(
CONDITION_NAME,
my_python_condition,
get_condition_properties,
get_condition_defaults(),
)
###############################################################################
# Deregistering is useful if you plan unloading the script files
###############################################################################
def script_unload():
advss_deregister_condition(CONDITION_NAME)
###############################################################################
# Advanced Scene Switcher helper functions below:
# Omitted on this wiki page for brevity!
The Advanced Scene Switcher helper functions
boilerplate code mentioned above can be found here for Python and Lua.
Usually you can just copy it directly into your script without any modifications.
Discord chat bot action
The following example will add an action which will allow you to send discord message to specified channels.
As the process is very similar to the example above the descriptions will be a bit more limited in detail.
This example assumes you have install the discord python package:
pip3 install discord
What it looks like
The bot credentials can be configured in the scripts menu:
The action itself will allow you to configure the message to send and the channel to send it to.
The code
import obspython as obs
import threading # Required by advss helpers
import discord
from discord.ext import commands
import asyncio
action_name = "Discord action"
token = None
loop = None
bot = None
bot_thread = None
###############################################################################
# Discord functions
###############################################################################
def create_bot():
global bot
intents = discord.Intents.default()
bot = commands.Bot(command_prefix="!", intents=intents)
@bot.event
async def on_ready():
obs.script_log(obs.LOG_WARNING, f"Logged in as {bot.user.name}!")
return bot
async def send_message(channel_id, content):
channel = bot.get_channel(channel_id)
if channel:
await channel.send(content)
def start_bot():
global loop, bot
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
bot = create_bot()
loop.create_task(bot.start(token))
loop.run_forever()
async def stop_bot():
await bot.close()
def restart_bot():
global token, bot_thread
if loop and bot.is_closed() == False:
asyncio.run_coroutine_threadsafe(stop_bot(), loop).result()
bot_thread = threading.Thread(target=start_bot)
bot_thread.start()
###############################################################################
# Macro action functions
###############################################################################
def run_action(data, instance_id):
message = obs.obs_data_get_string(data, "message")
channel_id_string = obs.obs_data_get_string(data, "channel_id")
try:
channel_id = int(channel_id_string)
except ValueError:
obs.script_log(
obs.LOG_WARNING, f"Invalid channel ID string {channel_id_string}!"
)
return
asyncio.run_coroutine_threadsafe(send_message(channel_id, message), loop)
def get_action_properties():
props = obs.obs_properties_create()
obs.obs_properties_add_text(props, "message", "Message", obs.OBS_TEXT_DEFAULT)
obs.obs_properties_add_text(props, "channel_id", "Channel ID", obs.OBS_TEXT_DEFAULT)
return props
def get_action_defaults():
default_settings = obs.obs_data_create()
obs.obs_data_set_default_string(default_settings, "message", "Your message here!")
obs.obs_data_set_default_string(default_settings, "channel_id", "0")
return default_settings
###############################################################################
# Script settings and description
###############################################################################
def script_description():
return f'Adds the macro action "{action_name}" for the advanced scene switcher'
def script_update(settings):
global token
token = obs.obs_data_get_string(settings, "token")
def script_defaults(settings):
obs.obs_data_set_default_string(settings, "token", "enter your bot token here")
def restart_pressed(props, prop):
restart_bot()
def script_properties():
props = obs.obs_properties_create()
obs.obs_properties_add_text(props, "token", "Bot Token", obs.OBS_TEXT_DEFAULT)
obs.obs_properties_add_button(props, "button", "Restart bot", restart_pressed)
return props
###############################################################################
# Main script entry point
###############################################################################
def script_load(settings):
global action_name
advss_register_action(
action_name,
run_action,
get_action_properties,
get_action_defaults(),
)
bot_thread = threading.Thread(target=start_bot)
bot_thread.start()
def script_unload():
global action_name
advss_deregister_action(action_name)
###############################################################################
# Advanced Scene Switcher helper functions below:
# Omitted on this wiki page for brevity!
Open Sound Control server condition
The following example will add a condition which will allow you to receive OSC messages.
As the process is very similar to the examples above the descriptions will be a bit more limited in detail.
This example assumes you have install the python-osc python package:
pip3 install python-osc
What it looks like
The OSC server details can be configured in the scripts menu and the message to look for can be configured in each condition instance.
Note that this is only a very simple example which probably does not cover all edge cases required for this condition type to be useful.
The code
import obspython as obs
import threading # Required by advss helpers
from pythonosc.dispatcher import Dispatcher
from pythonosc import osc_server
condition_name = "Open Sound Control"
server = None
ip = None
port = None
received_messages = []
###############################################################################
# OSC server helper functions
###############################################################################
def append_message(addr, message, *args):
global received_messages
print(f"received message! {message}")
received_messages.append(message)
def start_server():
global server, ip, port
if ip is None or port is None:
return
dispatcher = Dispatcher()
dispatcher.map("/testing", append_message)
server = osc_server.ThreadingOSCUDPServer((ip, port), dispatcher)
server_thread = threading.Thread(target=server.serve_forever)
server_thread.start()
def stop_server():
global received_messages
if server is not None:
server.shutdown()
received_messages.clear()
def restart_server():
stop_server()
start_server()
###############################################################################
# Macro condition functions
###############################################################################
def check_condition(data, instance_id):
global received_messages
expected_message = obs.obs_data_get_string(data, "value")
return_value = False
print(received_messages)
for message in received_messages:
if message == expected_message:
return_value = True
received_messages.clear()
return return_value
def get_condition_properties():
props = obs.obs_properties_create()
obs.obs_properties_add_text(props, "value", "Expected Value:", obs.OBS_TEXT_DEFAULT)
return props
def get_condition_defaults():
default_settings = obs.obs_data_create()
obs.obs_data_set_default_string(
default_settings, "value", "Your expected value here!"
)
return default_settings
###############################################################################
# Script settings and description
###############################################################################
def script_description():
return (
f'Adds the macro condition "{condition_name}" for the advanced scene switcher'
)
def script_update(settings):
global ip, port
ip = obs.obs_data_get_string(settings, "ip")
port = obs.obs_data_get_int(settings, "port")
def script_defaults(settings):
obs.obs_data_set_default_string(settings, "ip", "127.0.0.1")
obs.obs_data_set_default_int(settings, "port", 5005)
def restart_pressed(props, prop):
restart_server()
def script_properties():
props = obs.obs_properties_create()
obs.obs_properties_add_text(props, "ip", "IP Address", obs.OBS_TEXT_DEFAULT)
obs.obs_properties_add_int(props, "port", "Port", 0, 65535, 1)
obs.obs_properties_add_button(props, "button", "Restart OSC server", restart_pressed)
return props
###############################################################################
# Main script entry point
###############################################################################
def script_load(settings):
global condition_name
advss_register_condition(
condition_name,
check_condition,
get_condition_properties,
get_condition_defaults(),
)
start_server()
def script_unload():
global condition_name
advss_deregister_condition(condition_name)
###############################################################################
# Advanced Scene Switcher helper functions below:
# Omitted on this wiki page for brevity!
Other examples:
- An alternative to the "Cursor" condition type for Wayland based systems:
https://github.com/achow101/obs-avss-kwin-cursor/blob/main/kwin-cursor.py
General remarks
Note that if you should choose to unload the scripts, which define custom macro segments, while custom macro segments are still in use, the plugin will no longer be able to query settings of those custom macro segments.
If such a state should be saved (e.g. because OBS was closed with the script no longer being loaded) the plugin will instead replace the custom macro segments with macro segments of type Unknown
.
Even if you should choose to reload the script at a later point in time the settings for those Unknown
macro segments will be lost and you will have to reconfigure them.
Detailed API description
Procedures
The advanced scene switcher offers the following procedure handlers:
bool advss_register_script_action(in string name, in ptr default_settings, out string properties_signal_name, out string trigger_signal_name)
bool advss_register_script_condition(in string name, in ptr default_settings, out string properties_signal_name, out string trigger_signal_name)
bool advss_deregister_script_action(in string name)
bool advss_deregister_script_condition(in string name)
bool advss_get_variable_value(in string name, out string value)
bool advss_set_variable_value(in string name, in string value)
bool advss_register_temp_var(in string temp_var_id, in string temp_var_name, in string temp_var_help, in int instance_id)
bool advss_deregister_temp_vars(in int instance_id)
bool advss_set_temp_var_value(in string temp_var_id, in string value, in int instance_id)
bool advss_plugin_running()
Signals
The advanced scene switcher offers the following signals:
void advss_plugin_stopped()
void advss_plugin_started()
void advss_interval_reset()
advss_register_script_action
name
The name
field of calldata object associated with this procedure should specify the id of the action type you want to register.
It will also be the user facing name in the action type selection.
default_settings (optional)
The name
field of calldata object associated with this procedure should specify the a pointer to an obs_data_t object.
It should contain the default values for the settings for your custom action type.
The ownership and thus responsibility to free this obs_data_t* pointer will be that of the advanced scene switcher.
Thus you must not free / release this pointer yourself or you risk a crash of OBS.
This value can be null, if you do not which to provide any default settings.
properties_signal_name (optional)
The properties_signal_name
field of calldata object associated with this procedure will specify the name of the signal, which will be called by the advanced scene switcher, when it requests a new obs_properties_t object.
These objects are used to determine which settings UI elements should be shown to the user when creating an instance of your custom action type.
The signal will be registered by the advanced scene switcher.
You only have to connect to it.
When the signal is called by the advanced scene switcher you will have to pass the pointer to the obs_properties_t object you created via calldata_set_ptr in the field named properties
.
You can ignore this field, if you do not wish to provide any controls to the user to modify the settings of this action type.
trigger_signal_name
The trigger_signal_name
field of calldata object associated with this procedure will specify the name of the signal, which will be called by the advanced scene switcher, when your custom action type needs to be executed.
The signal will be registered by the advanced scene switcher.
You only have to connect to it.
Handling the trigger signal
In the following section trigger_signal_name
is just a placeholder for the actual signal name.
When trigger_signal_name
is called by the advanced scene switcher, the settings for the instance of this action type will be passed as a pointer to an obs_data_t object.
You can access it via calldata_ptr in the settings
field of calldata object associated with this procedure.
When trigger_signal_name
is called by the advanced scene switcher you will have to pass the result of your operation via calldata_set_bool in the field named result
.
In case of a macro action this should always be true
unless a catastrophic error occurred which should abort the whole macro's execution.
When trigger_signal_name
is called by the advanced scene switcher it will pass completion_id
field, which is a unique id for each instance of your custom action being triggered.
When trigger_signal_name
is called by the advanced scene switcher it will also pass the name of the signal you will to emit when your action type's operation is done in the completion_signal_name
field.
When you emit this completion signal you will also have to pass the completion_id
value you have received via trigger_signal_name
using calldata_set_int.
The signal will be registered by the advanced scene switcher.
You only have to emit it.
Return value
The return value can be queried via success
from the calldata object associated with this procedure.
Returns true
, if the operation was successful, and false
otherwise.
Example
def advss_register_action_type(name, callback, get_properties, default_settings):
proc_handler = obs.obs_get_proc_handler()
data = obs.calldata_create()
obs.calldata_set_string(data, "name", name)
obs.calldata_set_ptr(data, "default_settings", default_settings)
obs.proc_handler_call(proc_handler, "advss_register_script_action", data)
success = obs.calldata_bool(data, "success")
if success == False:
obs.script_log(obs.LOG_WARNING, f'failed to register custom action "{name}"')
obs.calldata_destroy(data)
return
# Run in separate thread to avoid blocking main OBS signal handler.
# Operation completion will be indicated via signal completion_signal_name.
def run_helper(data):
completion_signal_name = obs.calldata_string(data, "completion_signal_name")
id = obs.calldata_int(data, "completion_id")
def thread_func(settings):
settings = obs.obs_data_create_from_json(
obs.calldata_string(data, "settings")
)
callback(settings)
reply_data = obs.calldata_create()
obs.calldata_set_int(reply_data, "completion_id", id)
obs.calldata_set_bool(reply_data, "result", True)
signal_handler = obs.obs_get_signal_handler()
obs.signal_handler_signal(
signal_handler, completion_signal_name, reply_data
)
obs.obs_data_release(settings)
obs.calldata_destroy(reply_data)
threading.Thread(target=thread_func, args={data}).start()
def properties_helper(data):
if get_properties is not None:
properties = get_properties()
else:
properties = None
obs.calldata_set_ptr(data, "properties", properties)
trigger_signal_name = obs.calldata_string(data, "trigger_signal_name")
property_signal_name = obs.calldata_string(data, "properties_signal_name")
signal_handler = obs.obs_get_signal_handler()
obs.signal_handler_connect(signal_handler, trigger_signal_name, run_helper)
obs.signal_handler_connect(signal_handler, property_signal_name, properties_helper)
obs.calldata_destroy(data)
advss_register_script_condition
This is almost identical to advss_register_script_action
with the only difference being the handling of the result
field.
name
The name
field of calldata object associated with this procedure should specify the id of the condition type you want to register.
It will also be the user facing name in the condition type selection.
default_settings (optional)
The name
field of calldata object associated with this procedure should specify the a pointer to an obs_data_t object.
It should contain the default values for the settings for your custom condition type.
The ownership and thus responsibility to free this obs_data_t* pointer will be that of the advanced scene switcher.
Thus you must not free / release this pointer yourself or you risk a crash of OBS.
This value can be null, if you do not which to provide any default settings.
properties_signal_name (optional)
The properties_signal_name
field of calldata object associated with this procedure will specify the name of the signal, which will be called by the advanced scene switcher, when it requests a new obs_properties_t object.
These objects are used to determine which settings UI elements should be shown to the user when creating an instance of your custom condition type.
The signal will be registered by the advanced scene switcher.
You only have to connect to it.
When the signal is called by the advanced scene switcher you will have to pass the pointer to the obs_properties_t object you created via calldata_set_ptr in the field named properties
.
You can ignore this field, if you do not wish to provide any controls to the user to modify the settings of this condition type.
trigger_signal_name
The trigger_signal_name
field of calldata object associated with this procedure will specify the name of the signal, which will be called by the advanced scene switcher, when your custom condition check needs to be executed.
The signal will be registered by the advanced scene switcher.
You only have to connect to it.
Handling the trigger signal
In the following section trigger_signal_name
is just a placeholder for the actual signal name.
When trigger_signal_name
is called by the advanced scene switcher, the settings for the instance of this condition type will be passed as a pointer to an obs_data_t object.
You can access it via calldata_ptr in the settings
field of calldata object associated with this procedure.
When trigger_signal_name
is called by the advanced scene switcher you will have to pass the result of your condition check via calldata_set_bool in the field named result
.
When trigger_signal_name
is called by the advanced scene switcher it will pass completion_id
field, which is a unique id for each instance of your custom condition being triggered.
When trigger_signal_name
is called by the advanced scene switcher it will also pass the name of the signal you will to emit when your condition type's operation is done in the completion_signal_name
field.
When you emit this completion signal you will also have to pass the completion_id
value you have received via trigger_signal_name
using calldata_set_int.
The signal will be registered by the advanced scene switcher.
You only have to emit it.
Example
def advss_register_condition_type(name, callback, get_properties, default_settings):
proc_handler = obs.obs_get_proc_handler()
data = obs.calldata_create()
obs.calldata_set_string(data, "name", name)
obs.calldata_set_ptr(data, "default_settings", default_settings)
obs.proc_handler_call(proc_handler, "advss_register_script_condition", data)
success = obs.calldata_bool(data, "success")
if success == False:
obs.script_log(obs.LOG_WARNING, f'failed to register custom condition "{name}"')
obs.calldata_destroy(data)
return
# Run in separate thread to avoid blocking main OBS signal handler.
# Operation completion will be indicated via signal completion_signal_name.
def run_helper(data):
completion_signal_name = obs.calldata_string(data, "completion_signal_name")
id = obs.calldata_int(data, "completion_id")
def thread_func(settings):
settings = obs.obs_data_create_from_json(
obs.calldata_string(data, "settings")
)
callback_result = callback(settings)
reply_data = obs.calldata_create()
obs.calldata_set_int(reply_data, "completion_id", id)
obs.calldata_set_bool(reply_data, "result", callback_result)
signal_handler = obs.obs_get_signal_handler()
obs.signal_handler_signal(
signal_handler, completion_signal_name, reply_data
)
obs.obs_data_release(settings)
obs.calldata_destroy(reply_data)
threading.Thread(target=thread_func, args={data}).start()
def properties_helper(data):
if get_properties is not None:
properties = get_properties()
else:
properties = None
obs.calldata_set_ptr(data, "properties", properties)
trigger_signal_name = obs.calldata_string(data, "trigger_signal_name")
property_signal_name = obs.calldata_string(data, "properties_signal_name")
signal_handler = obs.obs_get_signal_handler()
obs.signal_handler_connect(signal_handler, trigger_signal_name, run_helper)
obs.signal_handler_connect(signal_handler, property_signal_name, properties_helper)
obs.calldata_destroy(data)
Return value
The return value can be queried via success
from the calldata object associated with this procedure.
Returns true
, if the operation was successful, and false
otherwise.
advss_deregister_script_action
name
The name
field of calldata object associated with this procedure should specify the id of the action type you want to deregister.
It will also be the user facing name in the condition type selection.
Return value
The return value can be queried via success
from the calldata object associated with this procedure.
Returns true
, if the operation was successful, and false
otherwise.
Example
def advss_deregister_action(name):
proc_handler = obs.obs_get_proc_handler()
data = obs.calldata_create()
obs.calldata_set_string(data, "name", name)
obs.proc_handler_call(proc_handler, "advss_deregister_script_action", data)
success = obs.calldata_bool(data, "success")
if success == False:
obs.script_log(obs.LOG_WARNING, f'failed to deregister custom action"{name}"')
obs.calldata_destroy(data)
advss_deregister_script_condition
This is identical to advss_deregister_script_action
.
name
The name
field of calldata object associated with this procedure should specify the id of the condition type you want to deregister.
Return value
The return value can be queried via success
from the calldata object associated with this procedure.
Returns true
, if the operation was successful, and false
otherwise.
Example
def advss_deregister_condition(name):
proc_handler = obs.obs_get_proc_handler()
data = obs.calldata_create()
obs.calldata_set_string(data, "name", name)
obs.proc_handler_call(proc_handler, "advss_deregister_script_condition", data)
success = obs.calldata_bool(data, "success")
if success == False:
obs.script_log(obs.LOG_WARNING, f'failed to deregister custom condition "{name}"')
obs.calldata_destroy(data)
advss_get_variable_value
name
The name
field of calldata object associated with this procedure should specify the id of the name of the variable for which you want to get the current value.
value
The value
field of calldata object associated with this procedure will contain the value of the specified variable, if the operation was successful.
Return value
The return value can be queried via success
from the calldata object associated with this procedure.
Returns true
, if the operation was successful, and false
otherwise.
Example
def advss_get_variable_value(name):
proc_handler = obs.obs_get_proc_handler()
data = obs.calldata_create()
obs.calldata_set_string(data, "name", name)
obs.proc_handler_call(proc_handler, "advss_get_variable_value", data)
success = obs.calldata_bool(data, "success")
if success == False:
obs.script_log(obs.LOG_WARNING, f'failed to get value for variable "{name}"')
obs.calldata_destroy(data)
return None
value = obs.calldata_string(data, "value")
obs.calldata_destroy(data)
return value
advss_set_variable_value
name
The name
field of calldata object associated with this procedure should specify the id of the name of the variable for which you want to change the value.
value
The value
field of calldata object associated with this procedure should specify the value you want to set for the specified variable.
Return value
The return value can be queried via success
from the calldata object associated with this procedure.
Returns true
, if the operation was successful, and false
otherwise.
Example
def advss_set_variable_value(name, value):
proc_handler = obs.obs_get_proc_handler()
data = obs.calldata_create()
obs.calldata_set_string(data, "name", name)
obs.calldata_set_string(data, "value", value)
obs.proc_handler_call(proc_handler, "advss_set_variable_value", data)
success = obs.calldata_bool(data, "success")
if success == False:
obs.script_log(obs.LOG_WARNING, f'failed to set value for variable "{name}"')
obs.calldata_destroy(data)
return success
advss_register_temp_var
Registers a macro property associated with a given action or condition instance.
temp_var_id
The temp_var_id
field of calldata object associated with this procedure should specify the unique id of the macro property you want to register.
temp_var_name
The temp_var_name
field of calldata object associated with this procedure should specify the user facing name of the macro property you want to register.
temp_var_help
The temp_var_help
field of calldata object associated with this procedure should specify the user facing help message of the macro property you want to register.
This value can be an empty string.
value
The value
field of calldata object associated with this procedure should specify the value you want to set for the specified macro property.
instance_id
The instance_id
field of calldata object associated with this procedure should specify the unique id of the macro segment for which the macro property value shall be set.
The instance_id
is passed as an argument to the signal with a given trigger_signal_name
name.
See advss_register_script_action
for more details.
Return value
The return value can be queried via success
from the calldata object associated with this procedure.
Returns true
, if the operation was successful, and false
otherwise.
advss_deregister_temp_vars
Deregister all macro properties associated with a given action or condition instance.
instance_id
The instance_id
field of calldata object associated with this procedure should specify the unique id of the macro segment for which all macro properties should be deregistered.
The instance_id
is passed as an argument to the signal with a given trigger_signal_name
name.
See advss_register_script_action
for more details.
Return value
The return value can be queried via success
from the calldata object associated with this procedure.
Returns true
, if the operation was successful, and false
otherwise.
advss_set_temp_var_value
Set the value of a macro property associated with a given action or condition instance.
temp_var_id
The temp_var_id
field of calldata object associated with this procedure should specify the unique id of the macro property you want to modify.
value
The value
field of calldata object associated with this procedure should specify the value you want to set for the specified macro property.
instance_id
The instance_id
field of calldata object associated with this procedure should specify the unique id of the macro segment for which the macro property value shall be set.
The instance_id
is passed as an argument to the signal with a given trigger_signal_name
name.
See advss_register_script_action
for more details.
Return value
The return value can be queried via success
from the calldata object associated with this procedure.
Returns true
, if the operation was successful, and false
otherwise.
advss_plugin_running
Can be used to query if the plugin is currently running or not.
Return value
The return value can be queried via is_running
from the calldata object associated with this procedure.
Returns true
, if the advanced scene switcher plugin is currently started, and false
otherwise.
advss_plugin_stopped
This signal is emitted when the advanced scene switcher plugin is stopped.
advss_plugin_started
This signal is emitted when the advanced scene switcher plugin is started.
advss_interval_reset
This signal is emitted when the advanced scene switcher plugin has processed all macro conditions. It can be used if you need to perform tasks before the next round of conditions checks starts. The frequency at which this signal is emitted depends mostly on the "Check conditions every ... ms" value configured on the General tab.