-
Notifications
You must be signed in to change notification settings - Fork 79
Scripting
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.
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.
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.
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):
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.
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
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.
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):
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!
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
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.
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):
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!
- An alternative to the "Cursor" condition type for Wayland based systems:
https://github.com/achow101/obs-avss-kwin-cursor/blob/main/kwin-cursor.py
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.
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)
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.
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.
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.
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.
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.
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.
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)
This is almost identical to advss_register_script_action
with the only difference being the handling of the result
field.
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.
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.
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.
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.
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.
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)
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.
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.
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.
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)
This is identical to advss_deregister_script_action
.
The name
field of calldata object associated with this procedure should specify the id of the condition type you want to deregister.
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.
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)
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.
The value
field of calldata object associated with this procedure will contain the value of the specified variable, if the operation was successful.
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.
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
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.
The value
field of calldata object associated with this procedure should specify the value you want to set for the specified variable.
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.
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
- Show webcam only when speaking
- Twitch Category Changer
- Show text source with latest Twitch follower
- Detect elements on screen and hide them automatically
- Motion detection
- Start other programs when starting OBS
- Crossfading audio during scene changes
- Switch scenes randomly
- Re-shuffle VLC source
- Automatically switch scene if a game capture's target window no longer exists
- Audio based scene switching in podcast setting
- Switching scenes based on portrait or landscape mode resolution of a window capture source
- Set up a hotkey to start and stop recording with a fade from and to black
- Automatically cycle through a list of scenes
- Toggle visibility of scene items on a timer
- Advance through a list of scenes by hotkey
- Performing actions only when transitioning from A to B
- Media playlist with commercial interruptions
- Split recording of stream into chunks
- Switching scenes for Aitum Vertical plugin
- Using MIDI devices
- Change capture window of Window Capture source
- Show URLs in clipboard in browser source
- General tab overview
- Starting and stopping the plugin
- Macros explained
- Creating a macro
- Pausing macros
- Macro duration modifiers
- Exporting and importing individual macros
- Audio condition
- Cursor condition
- Date condition
- Hotkey condition
- Media condition
- Process condition
- Scene item transform condition
- Slide Show condition
- Video condition
- Audio action
- Http action
- Hotkey action
- Random action
- Scene item visibility
- Sequence action
- Action Queue example
- Variables
- Websockets
- Scripting
- Scene groups
- Troubleshooting
- Saving and loading settings