diff --git a/scripting/examples.lua b/scripting/examples.lua index fbba7752c..01e953524 100644 --- a/scripting/examples.lua +++ b/scripting/examples.lua @@ -16,7 +16,7 @@ obs = obslua -- Simple action callback example -function my_lua_action(data) +function my_lua_action(data, instance_id) obs.script_log(obs.LOG_WARNING, "hello from lua!") end @@ -24,19 +24,23 @@ end -- Action showcasing how to provide configurable settings +-- Define the settings available for the user for this action type function get_action_properties() local props = obs.obs_properties_create() obs.obs_properties_add_text(props, "name", "Name", obs.OBS_TEXT_DEFAULT) return props end +-- Set default values for each setting function get_action_defaults() local default_settings = obs.obs_data_create() obs.obs_data_set_default_string(default_settings, "name", "John") return default_settings end -function my_lua_settings_action(data) +-- The settings for each instance of this action the will be passed in the +-- "data" argument +function my_lua_settings_action(data, instance_id) local name = obs.obs_data_get_string(data, "name") obs.script_log(obs.LOG_WARNING, string.format("hello %s from lua", name)) end @@ -47,7 +51,9 @@ end counter = 0 -function variable_lua_action(data) +-- You can use "advss_get_variable_value()" to get the value of a variable +-- Use "advss_set_variable_value()" to set the value of a variable +function variable_lua_action(data, instance_id) local value = advss_get_variable_value("variable") if value ~= nil then obs.script_log(obs.LOG_WARNING, string.format("variable has value: %s", value)) @@ -64,18 +70,22 @@ end math.randomseed(os.time()) +-- Define the settings available to the user for this condition type function get_condition_properties() local props = obs.obs_properties_create() obs.obs_properties_add_float(props, "probability", "Probability of returning true", 0, 100, 0.1) return props end +-- Define default values for each setting function get_condition_defaults() local default_settings = obs.obs_data_create() obs.obs_data_set_default_double(default_settings, "probability", 33.3) return default_settings end +-- The settings for each instance of this condition the will be passed in the +-- "data" argument function my_lua_condition(data) local target = obs.obs_data_get_double(data, "probability") local value = math.random(0, 100) @@ -84,19 +94,50 @@ end ------------------------------------------------------------------------------- +-- Example condition which demonstrates how to set macro properties. + +-- Set the value of each macro property with the "advss_set_temp_var_value()" +-- function +-- +-- The "instance_id" uniquely identifies each instance of this condition type +-- and needs to be passed to "advss_set_temp_var_value()" +function my_temp_var_lua_condition(data, instance_id) + local value = math.random(0, 100) + advss_set_temp_var_value("random_int", tonumber(value), instance_id) + advss_set_temp_var_value("some_other_value", value, instance_id) + return true +end + +------------------------------------------------------------------------------- + function script_load(settings) -- Register an example action - advss_register_action("My simple Lua action", my_lua_action, nil, nil) + advss_register_action("My simple Lua action", my_lua_action, nil, nil, nil) -- Register an example action with settings advss_register_action("My Lua action with settings", my_lua_settings_action, get_action_properties, - get_action_defaults()) + get_action_defaults(), nil) -- This example action demonstrates how to interact with variables - advss_register_action("My variable Lua action", variable_lua_action, nil, nil) + advss_register_action("My variable Lua action", variable_lua_action, nil, nil, nil) -- Register an example condition - advss_register_condition("My Lua condition", my_lua_condition, get_condition_properties, get_condition_defaults()) + advss_register_condition("My Lua condition", my_lua_condition, get_condition_properties, get_condition_defaults(), + nil) + + -- Register an example condition which registers and sets macro properties + local macro_property1 = {} + macro_property1.id = "random_int" + macro_property1.name = "Random number" + macro_property1.description = "A random number generated in the range from 0 to 100" + + local macro_property2 = {} + macro_property2.id = "some_other_value" + macro_property2.name = "Another value" + macro_property2.description = "" + + advss_register_condition("My Lua condition (Macro properties)", my_temp_var_lua_condition, nil, nil, + {macro_property1, macro_property2}) end function script_unload() @@ -106,6 +147,7 @@ function script_unload() advss_deregister_action("My variable Lua action") advss_deregister_condition("My Lua condition") + advss_deregister_condition("My Lua condition (Macro properties)") end ------------------------------------------------------------------------------- @@ -127,8 +169,10 @@ end -- 4. The optional default_settings pointer used to set the default settings of -- newly created actions. -- The pointer must not be freed within this script. -function advss_register_action(name, callback, get_properties, default_settings) - advss_register_segment_type(true, name, callback, get_properties, default_settings) +-- 5. The optional list of macro properties associated with this action type. +-- You can set values using advss_set_temp_var_value(). +function advss_register_action(name, callback, get_properties, default_settings, macro_properties) + advss_register_segment_type(true, name, callback, get_properties, default_settings, macro_properties) end function advss_deregister_action(name) @@ -148,8 +192,10 @@ end -- 4. The optional default_settings pointer used to set the default settings of -- newly created condition. -- The pointer must not be freed within this script. -function advss_register_condition(name, callback, get_properties, default_settings) - advss_register_segment_type(false, name, callback, get_properties, default_settings) +-- 5. The optional list of macro properties associated with this condition type. +-- You can set values using advss_set_temp_var_value(). +function advss_register_condition(name, callback, get_properties, default_settings, macro_properties) + advss_register_segment_type(false, name, callback, get_properties, default_settings, macro_properties) end function advss_deregister_condition(name) @@ -160,7 +206,7 @@ end -- (De)register helpers ------------------------------------------------------------------------------- -function advss_register_segment_type(is_action, name, callback, get_properties, default_settings) +function advss_register_segment_type(is_action, name, callback, get_properties, default_settings, macro_properties) local proc_handler = obs.obs_get_proc_handler() local data = obs.calldata_create() @@ -182,10 +228,11 @@ function advss_register_segment_type(is_action, name, callback, get_properties, -- So, we will call the provided callback function directly. local run_helper = (function(data) local completion_signal_name = obs.calldata_string(data, "completion_signal_name") - local id = obs.calldata_int(data, "completion_id") + local completion_id = obs.calldata_int(data, "completion_id") + local instance_id = obs.calldata_int(data, "instance_id") local settings = obs.obs_data_create_from_json(obs.calldata_string(data, "settings")) - local callback_result = callback(settings) + local callback_result = callback(settings, instance_id) if is_action then callback_result = true end @@ -193,7 +240,7 @@ function advss_register_segment_type(is_action, name, callback, get_properties, obs.obs_data_release(settings) local reply_data = obs.calldata_create() - obs.calldata_set_int(reply_data, "completion_id", id) + obs.calldata_set_int(reply_data, "completion_id", completion_id) obs.calldata_set_bool(reply_data, "result", callback_result) local signal_handler = obs.obs_get_signal_handler() obs.signal_handler_signal(signal_handler, completion_signal_name, reply_data) @@ -208,12 +255,41 @@ function advss_register_segment_type(is_action, name, callback, get_properties, end end) + -- Helper to register the macro properties every time a new instance of the + -- macro segment is created. + local register_temp_vars_helper = (function(data) + local id = obs.calldata_int(data, "instance_id") + local proc_handler = obs.obs_get_proc_handler() + local data = obs.calldata_create() + for _, prop in ipairs(macro_properties) do + obs.calldata_set_string(data, "temp_var_id", prop.id) + obs.calldata_set_string(data, "temp_var_name", prop.name) + obs.calldata_set_string(data, "temp_var_help", prop.description) + obs.calldata_set_int(data, "instance_id", id) + + obs.proc_handler_call(proc_handler, "advss_register_temp_var", data) + + local success = obs.calldata_bool(data, "success") + if success == false then + local segment_type = is_action and "action" or "condition" + log_msg = f 'failed to register macro property {prop.id} for {segment_type} "{name}"' + obs.script_log(obs.LOG_WARNING, log_msg) + obs.script_log(obs.LOG_WARNING, string.format("failed to register macro property %s for %s \"%s\"", + prop.id, segment_type, name)) + end + end + obs.calldata_destroy(data) + end) + local signal_name = obs.calldata_string(data, "trigger_signal_name") local property_signal_name = obs.calldata_string(data, "properties_signal_name") + local new_instance_signal_name = obs.calldata_string(data, "new_instance_signal_name") local signal_handler = obs.obs_get_signal_handler() obs.signal_handler_connect(signal_handler, signal_name, run_helper) obs.signal_handler_connect(signal_handler, property_signal_name, properties_helper) - + if macro_properties ~= nil then + obs.signal_handler_connect(signal_handler, new_instance_signal_name, register_temp_vars_helper) + end obs.calldata_destroy(data) end @@ -235,6 +311,27 @@ function advss_deregister_segment(is_action, name) obs.calldata_destroy(data) end +------------------------------------------------------------------------------- +-- Macro properties (temporary variables) +------------------------------------------------------------------------------- + +function advss_set_temp_var_value(temp_var_id, value, instance_id) + local proc_handler = obs.obs_get_proc_handler() + local data = obs.calldata_create() + + obs.calldata_set_string(data, "temp_var_id", tostring(temp_var_id)) + obs.calldata_set_string(data, "value", tostring(value)) + obs.calldata_set_int(data, "instance_id", tonumber(instance_id)) + obs.proc_handler_call(proc_handler, "advss_set_temp_var_value", data) + + local success = obs.calldata_bool(data, "success") + if success == false then + obs.script_log(obs.LOG_WARNING, string.format("failed to set value for macro property \"%s\"", temp_var_id)) + end + + obs.calldata_destroy(data) +end + ------------------------------------------------------------------------------- -- Variables ------------------------------------------------------------------------------- diff --git a/scripting/examples.py b/scripting/examples.py index 06fcffcda..af313694e 100644 --- a/scripting/examples.py +++ b/scripting/examples.py @@ -1,13 +1,18 @@ import obspython as obs -import threading # Required by advss helpers -import random # Required for example condition + +# Required by advss helpers +import threading +from typing import NamedTuple + +# Required for the example conditions +import random ############################################################################### # Simple action callback example -def my_python_action(data): +def my_python_action(data, instance_id): obs.script_log(obs.LOG_WARNING, "hello from python!") @@ -16,19 +21,23 @@ def my_python_action(data): # Action showcasing how to provide configurable settings +# Define the settings available for the user for this action type def get_action_properties(): props = obs.obs_properties_create() obs.obs_properties_add_text(props, "name", "Name", obs.OBS_TEXT_DEFAULT) return props +# Set default values for each setting def get_action_defaults(): default_settings = obs.obs_data_create() obs.obs_data_set_default_string(default_settings, "name", "John") return default_settings -def my_python_settings_action(data): +# The settings for each instance of this action the will be passed in the +# "data" argument +def my_python_settings_action(data, instance_id): name = obs.obs_data_get_string(data, "name") obs.script_log(obs.LOG_WARNING, f"hello {name} from python!") @@ -40,7 +49,9 @@ def my_python_settings_action(data): counter = 0 -def variable_python_action(data): +# You can use "advss_get_variable_value()" to get the value of a variable +# Use "advss_set_variable_value()" to set the value of a variable +def variable_python_action(data, instance_id): value = advss_get_variable_value("variable") if value is not None: obs.script_log(obs.LOG_WARNING, f"variable has value: {value}") @@ -56,6 +67,7 @@ def variable_python_action(data): # probability value +# Define the settings available to the user for this condition type def get_condition_properties(): props = obs.obs_properties_create() obs.obs_properties_add_float( @@ -64,18 +76,38 @@ def get_condition_properties(): return props +# Define default values for each setting def get_condition_defaults(): default_settings = obs.obs_data_create() obs.obs_data_set_default_double(default_settings, "probability", 33.3) return default_settings -def my_python_condition(data): +# The settings for each instance of this condition the will be passed in the +# "data" argument +def my_python_condition(data, instance_id): target = obs.obs_data_get_double(data, "probability") value = random.uniform(0, 100) return value <= target +############################################################################### + +# Example condition which demonstrates how to set macro properties. + + +# Set the value of each macro property with the "advss_set_temp_var_value()" +# function +# +# The "instance_id" uniquely identifies each instance of this condition type +# and needs to be passed to "advss_set_temp_var_value()" +def my_temp_var_python_condition(data, instance_id): + value = random.uniform(0, 100) + advss_set_temp_var_value("random_int", int(value), instance_id) + advss_set_temp_var_value("some_other_value", value, instance_id) + return True + + ############################################################################### @@ -102,6 +134,20 @@ def script_load(settings): get_condition_defaults(), ) + # Register an example condition which registers and sets macro properties + advss_register_condition( + "My Python condition (Macro properties)", + my_temp_var_python_condition, + macro_properties=[ + MacroProperty( + "random_int", + "Random number", + "A random number generated in the range from 0 to 100", + ), + MacroProperty("some_other_value", "Another value", ""), + ], + ) + def script_unload(): # Deregistering is useful if you plan on reloading the script files @@ -110,6 +156,7 @@ def script_unload(): advss_deregister_action("My variable Python action") advss_deregister_condition("My Python condition") + advss_deregister_condition("My Python condition (Macro properties)") ############################################################################### @@ -118,6 +165,7 @@ def script_unload(): # Usually you should not have to modify this code. # Simply copy paste it into your scripts. + ############################################################################### # Actions ############################################################################### @@ -132,8 +180,20 @@ def script_unload(): # 4. The optional default_settings pointer used to set the default settings of # newly created actions. # The pointer must not be freed within this script. -def advss_register_action(name, callback, get_properties=None, default_settings=None): - advss_register_segment_type(True, name, callback, get_properties, default_settings) +# 5. The optional list of macro properties associated with this action type. +# You can set values using advss_set_temp_var_value(). + + +def advss_register_action( + name, + callback, + get_properties=None, + default_settings=None, + macro_properties=None, +): + advss_register_segment_type( + True, name, callback, get_properties, default_settings, macro_properties + ) def advss_deregister_action(name): @@ -154,10 +214,18 @@ def advss_deregister_action(name): # 4. The optional default_settings pointer used to set the default settings of # newly created condition. # The pointer must not be freed within this script. +# 5. The optional list of macro properties associated with this condition type. +# You can set values using advss_set_temp_var_value(). def advss_register_condition( - name, callback, get_properties=None, default_settings=None + name, + callback, + get_properties=None, + default_settings=None, + macro_properties=None, ): - advss_register_segment_type(False, name, callback, get_properties, default_settings) + advss_register_segment_type( + False, name, callback, get_properties, default_settings, macro_properties + ) def advss_deregister_condition(name): @@ -170,7 +238,7 @@ def advss_deregister_condition(name): def advss_register_segment_type( - is_action, name, callback, get_properties, default_settings + is_action, name, callback, get_properties, default_settings, macro_properties ): proc_handler = obs.obs_get_proc_handler() data = obs.calldata_create() @@ -186,7 +254,7 @@ def advss_register_segment_type( obs.proc_handler_call(proc_handler, register_proc, data) success = obs.calldata_bool(data, "success") - if success == False: + if success is False: segment_type = "action" if is_action else "condition" log_msg = f'failed to register custom {segment_type} "{name}"' obs.script_log(obs.LOG_WARNING, log_msg) @@ -197,18 +265,19 @@ def advss_register_segment_type( # 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") + completion_id = obs.calldata_int(data, "completion_id") + instance_id = obs.calldata_int(data, "instance_id") def thread_func(settings): settings = obs.obs_data_create_from_json( obs.calldata_string(data, "settings") ) - callback_result = callback(settings) + callback_result = callback(settings, instance_id) if is_action: callback_result = True reply_data = obs.calldata_create() - obs.calldata_set_int(reply_data, "completion_id", id) + obs.calldata_set_int(reply_data, "completion_id", completion_id) obs.calldata_set_bool(reply_data, "result", callback_result) signal_handler = obs.obs_get_signal_handler() obs.signal_handler_signal( @@ -226,13 +295,38 @@ def properties_helper(data): properties = None obs.calldata_set_ptr(data, "properties", properties) + # Helper to register the macro properties every time a new instance of the + # macro segment is created. + def register_temp_vars_helper(data): + id = obs.calldata_int(data, "instance_id") + proc_handler = obs.obs_get_proc_handler() + data = obs.calldata_create() + for prop in macro_properties: + obs.calldata_set_string(data, "temp_var_id", prop.id) + obs.calldata_set_string(data, "temp_var_name", prop.name) + obs.calldata_set_string(data, "temp_var_help", prop.description) + obs.calldata_set_int(data, "instance_id", id) + + obs.proc_handler_call(proc_handler, "advss_register_temp_var", data) + + success = obs.calldata_bool(data, "success") + if success is False: + segment_type = "action" if is_action else "condition" + log_msg = f'failed to register macro property {prop.id} for {segment_type} "{name}"' + obs.script_log(obs.LOG_WARNING, log_msg) + obs.calldata_destroy(data) + trigger_signal_name = obs.calldata_string(data, "trigger_signal_name") property_signal_name = obs.calldata_string(data, "properties_signal_name") + new_instance_signal_name = obs.calldata_string(data, "new_instance_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) - + if isinstance(macro_properties, list): + obs.signal_handler_connect( + signal_handler, new_instance_signal_name, register_temp_vars_helper + ) obs.calldata_destroy(data) @@ -251,7 +345,7 @@ def advss_deregister_segment(is_action, name): obs.proc_handler_call(proc_handler, deregister_proc, data) success = obs.calldata_bool(data, "success") - if success == False: + if success is False: segment_type = "action" if is_action else "condition" log_msg = f'failed to deregister custom {segment_type} "{name}"' obs.script_log(obs.LOG_WARNING, log_msg) @@ -259,6 +353,35 @@ def advss_deregister_segment(is_action, name): obs.calldata_destroy(data) +############################################################################### +# Macro properties (temporary variables) +############################################################################### + + +class MacroProperty(NamedTuple): + id: str # Internal identifier used by advss_set_temp_var_value() + name: str # User facing name + description: str # User facing description + + +def advss_set_temp_var_value(temp_var_id, value, instance_id): + proc_handler = obs.obs_get_proc_handler() + data = obs.calldata_create() + + obs.calldata_set_string(data, "temp_var_id", str(temp_var_id)) + obs.calldata_set_string(data, "value", str(value)) + obs.calldata_set_int(data, "instance_id", int(instance_id)) + obs.proc_handler_call(proc_handler, "advss_set_temp_var_value", data) + + success = obs.calldata_bool(data, "success") + if success is False: + obs.script_log( + obs.LOG_WARNING, f'failed to set value for macro property "{temp_var_id}"' + ) + + obs.calldata_destroy(data) + + ############################################################################### # Variables ############################################################################### @@ -275,7 +398,7 @@ def advss_get_variable_value(name): obs.proc_handler_call(proc_handler, "advss_get_variable_value", data) success = obs.calldata_bool(data, "success") - if success == False: + if success is False: obs.script_log(obs.LOG_WARNING, f'failed to get value for variable "{name}"') obs.calldata_destroy(data) return None @@ -298,7 +421,7 @@ def advss_set_variable_value(name, value): obs.proc_handler_call(proc_handler, "advss_set_variable_value", data) success = obs.calldata_bool(data, "success") - if success == False: + if success is False: obs.script_log(obs.LOG_WARNING, f'failed to set value for variable "{name}"') obs.calldata_destroy(data)