--[[
M0073R - ver.1.0.0
A Lua script that mute/unmute audio source based on a scene change.
]]
function setup(settings)
obs.utils.settings= settings
obs.register.event(function(event_id)
if event_id == obslua.OBS_FRONTEND_EVENT_SCENE_CHANGED then
InitAction()
end
end)
end
function get_scene_cz(scene_name)
local surs= obs.utils.settings.obj("m0073r_" .. scene_name .. "_scene_t")
if not surs or surs.data == nil then
return nil
end
local alist= surs.get_arr("m0073r_clz_obs_arr_t")
if not alist or alist.data == nil then
surs.free()
return nil
end
surs.free()
return alist
end
function script_properties()
local p= obs.script.create()
obs.script.label(p, nil, "
| M0073R - ver.1.0.0 | | by iixisii |
")
local audible_list_count=0
local audible_list;local add_options_error;
local add_options;
local add;add=obs.script.button(p, "add_new", "+", function()
if audible_list_count <= 0 then
return
end
add.hide()
add_options_error.hide()
add_options.show()
return true
end)
add_options= obs.script.form(p, "Adding new audible")
add_options.hide()
add_options_error= add_options.add.label("add_options_error","").hide()
add_options.onconfirm:idle()
add_options.cancel:click(function()
add_options.hide()
obs.utils.settings.str("audible_source", ""
).str("scene_name", "")
add.show()
return true
end)
add_options.confirm:click(function()
local audible_value= obs.utils.settings.get_str("audible_source")
if audible_value == nil or audible_value == "" then
add_options_error.show()
add_options_error.error("Please select a valid audible source!")
return true
end
local scene_value= obs.utils.settings.get_str("scene_name")
if scene_value == nil or scene_value == "def" then
add_options_error.show()
add_options_error.error("Please select a valid scene!")
return true
end
-- m0073r_clz_obs_arr_t
local surs= obs.utils.settings.obj("m0073r_" .. scene_value .. "_scene_t")
if not surs or surs.data == nil then
surs= obs.PairStack()
end
local alist= surs.get_arr("m0073r_clz_obs_arr_t")
if not alist or alist.data == nil then
alist= obs.ArrayStack()
surs.arr("m0073r_clz_obs_arr_t", alist.data)
end
local audible= obs.PairStack()
audible.str("source", audible_value).str(
"scene", scene_value
).int("muted", 2).str("unique_id", obs.utils.get_unique_id(10))
alist.insert(audible.data)
obs.utils.settings.obj("m0073r_" .. scene_value .. "_scene_t", surs.data)
audible.free()
alist.free()
surs.free()
add_options.hide()
obs.utils.settings.str("audible_source", ""
).str("scene_name", "")
add.show()
return true
end)
audible_list = add_options.add.options("audible_source","Audible")
audible_list.add.str("-- Click to view options --", "")
for _, source_name in ipairs(obs.front.source_names()) do
local source= obs.front.source(source_name)
local source_output_flags= obslua.obs_source_get_output_flags(source.data)
local is_audible_source= bit.band(source_output_flags, obslua.OBS_SOURCE_AUDIO) ~= 0
if is_audible_source then
audible_list_count= audible_list_count + 1
audible_list.add.str(source_name, source_name)
end
source.free()
end
if audible_list_count <= 0 then
audible_list.add.str("No Audible Sources Found", "none").cursor().disable()
end
local scene_list= add_options.add.options("scene_name","Scene")
scene_list.add.str("-- Click to view scenes --", "")
-- [[ Populate scene list and show all the audibles ]]
for _, scene_name in ipairs(obs.scene:names()) do
obs.script.label(p, "label_" .. tostring(scene_name), "-" .. tostring(scene_name).."")
scene_list.add.str(scene_name, scene_name)
local surs= obs.utils.settings.obj("m0073r_" .. scene_name .. "_scene_t")
if surs and surs.data ~= nil then
local alist= surs.get_arr("m0073r_clz_obs_arr_t")
if alist and alist.data ~= nil then
for itm in alist.next() do
local audible_source= itm.get_str("source")
local muted_state= itm.get_int("muted")
local scene_value= itm.get_str("scene")
local unique_id= itm.get_str("unique_id")
local mute_action = obs.script.bool(p, audible_source .. "_" .. scene_value, audible_source)
if muted_state >= 2 then
mute_action.title(audible_source .. " ( undefined state )")
elseif muted_state == 1 then
mute_action.title(audible_source .. " ( muted )")
else
mute_action.title(audible_source .. " ( unmuted )")
end
local delete_tick=os.clock()
mute_action.onchange(function(value, property)
local delete_tick_value=os.clock() - delete_tick
if delete_tick_value >= 0.15 and delete_tick_value <= 0.35 then
-- delete the item from list
local alist= get_scene_cz(scene_name)
if not alist then
return false
end
local iter, index= alist.find("unique_id", unique_id)
if iter then
iter.free()
alist.rm(index)
end
alist.free()
property.remove()
return true
end
if delete_tick_value <= 0.015 then
return false
end
delete_tick=os.clock()
local alist= get_scene_cz(scene_name)
local iter= alist.find("source", audible_source)
if not iter then
alist.free()
return false
else
iter.int("muted", value and 1 or 0)
iter.free()
end
if value then
mute_action.title(audible_source .. " ( muted )")
else
mute_action.title(audible_source .. " ( unmuted )")
end
InitAction()
return true
end)
itm.free()
end
alist.free()
end
surs.free()
end
end
return p
end
function InitAction()
local scene_name= obs.scene:name()
local alist= get_scene_cz(scene_name)
if not alist then
return
end
for itm in alist.next() do
local audible_source_name= itm.get_str("source")
local muted_state= itm.get_int("muted")
local source= obs.front.source(audible_source_name)
if source and source.data ~= nil then
if muted_state == 1 then
obslua.obs_source_set_muted(source.data, true)
else
obslua.obs_source_set_muted(source.data, false)
end
end
source.free()
itm.free()
end
alist.free()
end
--[[
Author: iixisii
contact: @iixisii
]]
-- [[ OBS CUSTOM API BEGIN ]]
-- [[ OBS CUSTOM CALLBACKS ]]
function script_load(settings)
obs.script_shutdown=false
settings= obs.PairStack(settings)
obs.utils.settings= settings
if setup and type(setup) == "function" then
setup(settings)
end
for _, filter in pairs(obs.utils.filters) do
obslua.obs_register_source(filter)
end
end
function script_save(settings)
-- [[ OBS REGISTER HOTKEY SAVE DATA]]
for name, iter in pairs(obs.register.hotkey_id_list) do
local new_data= obslua.obs_hotkey_save(iter.id)
if new_data then
obs.utils.settings.arr(name, new_data)
obslua.obs_data_array_release(new_data)
end
end
-- [[ OBS REGISTER HOTKEY SAVE DATA END]]
end
function script_unload()
obs.utils.script_shutdown=true
end
-- [[ OBS CUSTOM CALLBACKS END ]]
obs={utils={script_shutdown=false,
OBS_SCENEITEM_TYPE = 1;OBS_SRC_TYPE = 2;OBS_OBJ_TYPE = 3;
OBS_ARR_TYPE = 4;OBS_SCENE_TYPE = 5;OBS_SCENEITEM_LIST_TYPE = 6;
OBS_SRC_LIST_TYPE = 7;OBS_UN_IN_TYPE = -1;table={};
expect_wrapper={},
properties={
list={};options={};
};filters={}};scene={};client={};mem={};script={},enum={
path={
read=obslua.OBS_PATH_FILE;write=obslua.OBS_PATH_FILE_SAVE;folder=obslua.OBS_PATH_DIRECTORY
};
button={
default=obslua.OBS_BUTTON_DEFAULT;url=obslua.OBS_BUTTON_URL;
};list={
string=obslua.OBS_EDITABLE_LIST_TYPE_STRINGS;
url=obslua.OBS_EDITABLE_LIST_TYPE_FILES_AND_URLS;
file=obslua.OBS_EDITABLE_LIST_TYPE_FILES
};
text={
error=obslua.OBS_TEXT_INFO_ERROR;
default=obslua.OBS_TEXT_INFO;
warn=obslua.OBS_TEXT_INFO_WARNING;
input=obslua.OBS_TEXT_DEFAULT;password=obslua.OBS_TEXT_PASSWORD;
textarea=obslua.OBS_TEXT_MULTILINE;
};group={
normal= obslua.OBS_GROUP_NORMAL;checked= obslua.OBS_GROUP_CHECKABLE;
};options={
string=obslua.OBS_COMBO_FORMAT_STRING; int=obslua.OBS_COMBO_FORMAT_INT;
float=obslua.OBS_COMBO_FORMAT_FLOAT;bool=obslua.OBS_COMBO_FORMAT_BOOL;
edit=obslua.OBS_COMBO_TYPE_EDITABLE;default=obslua.OBS_COMBO_TYPE_LIST;
radio=obslua.OBS_COMBO_TYPE_RADIO;
};number={
int=obslua.OBS_COMBO_FORMAT_INT;float=obslua.OBS_COMBO_FORMAT_FLOAT;
slider=1000;input=2000
}
},register={
hotkey_id_list={},event_id_list={}
};front={}};
bit= require('bit')
-- dkjson= require('dkjson')
math.randomseed(os.time())
-- schedule an event
scheduled_events = {}
-- [[ MEMORY MANAGE API ]]
function obs.utils.scheduler(timeout)
-- if type(timeout) ~= "number" or timeout < 0 then
-- return obs.script_log(obslua.LOG_ERROR, "[Scheduler] invalid timeout value")
-- end
local scheduler_callback = nil
local function interval()
obslua.timer_remove(interval)
if type(scheduler_callback) ~= "function" then
return
end
return scheduler_callback(scheduler_callback)
end
local self = nil; self = {
after = function(callback)
if type(callback) == "function" or type(timeout) ~= "number" or timeout < 0 then
scheduler_callback = callback
else
obslua.script_log(obslua.LOG_ERROR, "[Scheduler] invalid callback/timeout " .. type(callback))
return false
end
obslua.timer_add(interval, timeout)
end;push = function(callback)
if callback == nil or type(callback) ~= "function" then
obslua.script_log(obslua.LOG_WARNING, "[Scheduler] invalid callback at {push} " .. type(callback))
return false
end
obslua.timer_add(callback, timeout)
table.insert(scheduled_events, callback)
return {
clear = function()
if callback == nil or type(callback) ~= "function" then
return nil
end
return obslua.timer_remove(callback)
end;
}
end; clear = function()
if scheduler_callback ~= nil then
obslua.timer_remove(scheduler_callback)
end
for _, clb in pairs(scheduled_events) do
obslua.timer_remove(clb)
end
scheduled_events = {}; scheduler_callback = nil
end;update=function(timeout_t)
if type(timeout_t) ~= "number" or timeout_t < 0 then
obslua.script_log(obslua.LOG_ERROR, "[Scheduler] invalid timeout value")
return false
end
timeout= timeout_t
return self
end
}
return self
end
function obs.wrap(object, object_type)
local self = nil
self = {
type = object_type, data = object;item=object;
get_source=function()
if self.type == obs.utils.OBS_SRC_TYPE then
return self.data
elseif self.type == obs.utils.OBS_SCENEITEM_TYPE then
return obslua.obs_sceneitem_get_source(self.data)
else
return self.data
end
end;released=false;
free = function()
if self.data == nil or self.released then
return
end
if self.type == obs.utils.OBS_SCENE_TYPE then
obslua.obs_scene_release(self.data)
self.data = nil;self.item=nil;self.released=true
elseif self.type == obs.utils.OBS_SRC_TYPE then
obslua.obs_source_release(self.data)
self.data = nil;self.item=nil;self.released=true
elseif self.type == obs.utils.OBS_ARR_TYPE then
obslua.obs_data_array_release(self.data)
self.data = nil;self.item=nil;self.released=true
elseif self.type == obs.utils.OBS_OBJ_TYPE then
obslua.obs_data_release(self.data)
self.data = nil;self.item=nil;self.released=true
elseif self.type == obs.utils.OBS_SCENEITEM_TYPE then
obslua.obs_sceneitem_release(self.data)
self.data = nil;self.item=nil;self.released=true
elseif self.type == obs.utils.OBS_SCENEITEM_LIST_TYPE then
obslua.sceneitem_list_release(self.data)
self.data = nil;self.item=nil;self.released=true
elseif self.type == obs.utils.OBS_SRC_LIST_TYPE then
obslua.source_list_release(self.data)
self.data = nil;self.item=nil;self.released=true
elseif self.type == obs.utils.OBS_UN_IN_TYPE then
self.data = nil;self.item=nil;self.released=true
return
else
self.data = nil
end
end
}
table.insert(obs.utils.expect_wrapper, self)
return self
end
function obs.expect(callback)
return function(...)
local args = {...}
local data = nil
local caller = ""
for i, v in ipairs(args) do
if caller ~= "" then
caller = caller .. ","
end
caller = caller .. "args[" .. tostring(i) .. "]"
end
caller = "return function(callback,args) return callback(" .. caller .. ") end";
local run = loadstring(caller)
local success, result = pcall(function()
data = run()(callback, args)
end)
local free_count=0
if not success then
for _, iter in pairs(obs.utils.expect_wrapper) do
if iter and type(iter.free) == "function" then
local s, r = pcall(function()
iter.free()
end)
if s then
free_count = free_count + 1
end
end
end
obslua.script_log(obslua.LOG_ERROR, "[ErrorWrapper ERROR] => " .. tostring(result))
end
return data
end
end
-- array handle
function obs.ArrayStack(stack, name, fallback)
if fallback == nil then
fallback=true
end
local self = nil
self = {
index = 0;get = function(index)
if type(index) ~= "number" or index < 0 or index > self.size() then
return nil
end
return obs.PairStack(obslua.obs_data_array_item(self.data, index), nil, true)
end;next = obs.expect(function(__index)
if type(self.index) ~= "number" or self.index < 0 or self.index > self.size() then
return assert(false,"[ArrayStack] Invalid data provided or corrupted data for (" .. tostring(name)..")")
end
return coroutine.wrap(function()
if self.size() <= 0 then
return nil
end
local i =0
if __index == nil or type(__index) ~= "number" or __index < 0 or __index > self.size() then
__index=0
end
for i=__index, self.size()-1 do
coroutine.yield(obs.PairStack(
obslua.obs_data_array_item(self.data, i), nil, false
))
end
end)
-- local temp = self.index;self.index = self.index + 1
-- return obs.PairStack(obslua.obs_data_array_item(self.data, temp), nil, true)
end);find= function(key, value)
local index=0
for itm in self.next() do
if itm.get_str(key) == value or itm.get_int(key) == value
or itm.get_bul(key) == value or itm.get_dbl(key) == value then
return itm, index
end
index = index + 1
itm.free()
end
return nil, nil
end;
free = function()
if self.data == nil then
return false
end
obslua.obs_data_array_release(self.data)
self.data = nil
return true
end;insert = obs.expect(function(value)
if value == nil or type(value) ~= "userdata" then
obslua.script_log("FAILED TO INSERT OBJECT INTO [ArrayStack]")
return false
end
obslua.obs_data_array_push_back(self.data, value)
return self
end); size = obs.expect(function()
if self.data == nil then
return 0
end
return obslua.obs_data_array_count(self.data);
end); rm= obs.expect(function(idx)
if idx < 0 or self.size() <=0 or idx > self.size() then
obslua.script_log("FAILED TO RM DATA FROM [ArrayStack] (INVALID INDEX)")
return false
end
obslua.obs_data_array_erase(self.data, idx)
return self
end)
}
if stack and name then
self.data = obslua.obs_data_get_array(stack, name)
elseif not stack and fallback then
self.data = obslua.obs_data_array_create()
else
self.data = stack
end
table.insert(obs.utils.expect_wrapper, self)
return self
end
-- pair stack used to manage memory stuff :)
function obs.PairStack(stack, name, fallback)
if fallback == nil then
fallback=true
end
local self = nil; self = {
free = function()
if self.data == nil then
return false
end
obslua.obs_data_release(self.data)
self.data = nil
return true
end; str = obs.expect(function(name, value, def)
if name and value == nil then
return self.get_str(name)
end
if (name == nil or type(name) ~= "string" or name == "") or (self.data == nil or type(self.data) ~= "userdata") or (value == nil or type(value) ~="string") then
obslua.script_log(obslua.LOG_ERROR,"FAILED TO INSERT STR INTO [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value)))
return false
end
if def then
obslua.obs_data_set_default_string(self.data, name, value)
else
obslua.obs_data_set_string(self.data, name, value)
end
return self
end);int = obs.expect(function(name, value, def)
if name and value == nil then
return self.get_int(name)
end
if (name == nil or type(name) ~= "string" or name == "") or (self.data == nil or type(self.data) ~= "userdata") or (value == nil or type(value) ~="number") then
obslua.script_log(obslua.LOG_ERROR,"FAILED TO INSERT INT INTO [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value)))
return false
end
if def then
obslua.obs_data_set_default_int(self.data, name, value)
else
obslua.obs_data_set_int(self.data, name, value)
end
return self
end);dbl=obs.expect(function(name, value, def)
if name and value == nil then
return self.get_dbl(name)
end
if (name == nil or type(name) ~= "string" or name == "") or (self.data == nil or type(self.data) ~= "userdata") or (value == nil or type(value) ~="number") then
obslua.script_log(obslua.LOG_ERROR,"FAILED TO INSERT INT INTO [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value)))
return nil
end
if def then
obslua.obs_data_set_default_double(self.data, name, value)
else
obslua.obs_data_set_double(self.data, name, value)
end
return self
end);bul = obs.expect(function(name, value, def)
if name and value == nil then
return self.get_bul(name)
end
if (name == nil or type(name) ~= "string" or name == "") or (self.data == nil or type(self.data) ~= "userdata") or (type(value) == "nil" or type(value) ~="boolean") then
obslua.script_log(obslua.LOG_ERROR,"FAILED TO INSERT BUL [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value)))
return nil
end
if def then
obslua.obs_data_set_default_bool(self.data, name, value)
else
obslua.obs_data_set_bool(self.data, name, value)
end
return self
end); arr = obs.expect(function(name, value, def)
if name and value == nil then
return self.get_arr(name)
end
if (name == nil or type(name) ~= "string" or name == "") or (self.data == nil or type(self.data) ~= "userdata") or (type(value) ~="userdata") then
obslua.script_log(obslua.LOG_ERROR,"FAILED TO INSERT ARR INTO [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value)))
return nil
end
if def then
obslua.obs_data_set_default_array(self.data, name, value)
else
obslua.obs_data_set_array(self.data, name, value)
end
return self
end); obj = obs.expect(function(name, value, def)
if name and value == nil then
return self.get_obj(name)
end
if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata") or (type(value) ~="userdata") then
obslua.script_log(obslua.LOG_ERROR,"FAILED TO INSERT OBJ INTO [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value)))
return nil
end
if def then
obslua.obs_data_set_default_obj(self.data, name, value)
else
obslua.obs_data_set_obj(self.data, name, value)
end
return self
end);
-- getter
get_str = obs.expect(function(name, def)
if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata")then
obslua.script_log(obslua.LOG_ERROR,"FAILED TO GET STR FROM [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value)))
return nil
end
if not def then
return obslua.obs_data_get_string(self.data, name)
else
return obslua.obs_data_get_default_string(self.data, name)
end
end);get_int = obs.expect(function(name, def)
if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata")then
obslua.script_log(obslua.LOG_ERROR,"FAILED TO GET INT FROM [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value)))
return nil
end
if not def then
return obslua.obs_data_get_int(self.data, name)
else
return obslua.obs_data_get_default_int(self.data, name)
end
end);get_dbl = obs.expect(function(name, def)
if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata")then
obslua.script_log(obslua.LOG_ERROR,"FAILED TO GET DBL FROM [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value)))
return nil
end
if not def then
return obslua.obs_data_get_double(self.data, name)
else
return obslua.obs_data_get_default_double(self.data, name)
end
end);get_obj = obs.expect(function(name, def)
if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata")then
obslua.script_log(obslua.LOG_ERROR,"FAILED TO GET OBJ FROM [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value)))
return nil
end
if not def then
return obs.PairStack(
obslua.obs_data_get_obj(self.data, name),nil, false
)
else
return obs.PairStack(
obslua.obs_data_get_default_obj(self.data, name),nil, false
)
end
end);get_arr = obs.expect(function(name, def)
if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata")then
obslua.script_log(obslua.LOG_ERROR,"FAILED TO GET ARR FROM [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value)))
return nil
end
if not def then
return obs.ArrayStack(
obslua.obs_data_get_array(self.data, name),nil, false
)
else
return obs.ArrayStack(obslua.obs_data_get_default_array(self.data, name),nil, false)
end
end);get_bul = obs.expect(function(name, def)
if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata") then
obslua.script_log(obslua.LOG_ERROR,"FAILED TO GET BUL FROM [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value)))
return nil
end
if not def then
return obslua.obs_data_get_bool(self.data, name)
else
return obslua.obs_data_get_default_bool(self.data, name)
end
end); del= obs.expect(function(name)
obslua.obs_data_erase(self.data, name)
return true
end);
}
if stack and name then
self.data = obslua.obs_data_get_obj(stack, name)
elseif not stack and fallback then
self.data = obslua.obs_data_create()
else
self.data = stack
end
table.insert(obs.utils.expect_wrapper, self)
return self
end
-- [[ MEMORY MANAGE API END ]]
-- [[ OBS REGISTER CUSTOM API]]
function obs.register.hotkey(unique_id, title, callback)
local script_path_value= script_path()
unique_id= tostring(script_path_value) .. "_" .. tostring(unique_id)
local hotkey_id= obslua.obs_hotkey_register_frontend(
unique_id, title, callback
)
-- load from data
local hotkey_load_data= obs.utils.settings.get_arr(unique_id);
if hotkey_load_data and hotkey_load_data.data ~= nil then
obslua.obs_hotkey_load(hotkey_id, hotkey_load_data.data)
hotkey_load_data.free()
end
obs.register.hotkey_id_list[unique_id]= {
id= hotkey_id, title= title, callback= callback,
remove=function(rss)
if rss == nil then
rss= false
end
-- obs.utils.settings.del(unique_id)
if rss then
if obs.register.hotkey_id_list[unique_id] and type(obs.register.hotkey_id_list[unique_id].callback) == "function" then
obslua.obs_hotkey_unregister(
obs.register.hotkey_id_list[unique_id].callback
)
end
end
obs.register.hotkey_id_list[unique_id]= nil
end
}
return obs.register.hotkey_id_list[unique_id]
end
function obs.register.get_hotkey(unique_id)
unique_id= tostring(script_path()) .. "_" .. tostring(unique_id)
if obs.register.hotkey_id_list[unique_id] then
return obs.register.hotkey_id_list[unique_id]
end
return nil
end
function obs.register.event(unique_id, callback)
if not callback and unique_id and type(unique_id) == "function" then
callback= unique_id
unique_id= tostring(script_path()) .. "_" .. obs.utils.get_unique_id(3) .. "_event"
else
unique_id= tostring(script_path()) .. "_" .. tostring(unique_id) .. "_event"
end
if type(callback) ~= "function" then
obslua.script_log(obslua.LOG_ERROR, "[OBS REGISTER EVENT] Invalid callback provided")
return nil
end
local event_id= obslua.obs_frontend_add_event_callback(callback)
obs.register.event_id_list[unique_id]= {
id= event_id,callback= callback,
unique_id= unique_id,
remove= function(rss)
if rss == nil then
rss= false
end
if rss then obslua.obs_frontend_remove_event_callback(callback) end
obs.register.event_id_list[unique_id]= nil
end
};
end
function obs.register.get_event(unique_id)
unique_id= tostring(script_path()) .. "_" .. tostring(unique_id) .. "_event"
if obs.register.event_id_list[unique_id] then
return obs.register.event_id_list[unique_id]
end
return nil
end
-- [[ OBS REGISTER CUSTOM API END]]
-- [[ OBS FILTER CUSTOM API]]
function obs.script.filter(filter)
local self;self={
id= filter and filter.id or obs.utils.get_unique_id(3),
type= filter and filter.type or obslua.OBS_SOURCE_TYPE_FILTER,
output_flags= filter and filter.output_flags or bit.bor(
obslua.OBS_SOURCE_VIDEO
),
get_height=function(src)
return src and src.height or 0
end, get_width= function(src)
return src and src.width or 0
end,update= function(_, settings)
if filter and type(filter) == "table" and
filter["update"] and type(filter["update"]) == "function" then
return filter.update(_, obs.PairStack(settings))
end
end, create= function(settings, source)
function get_sceneitem()
local a_source=obslua.obs_filter_get_target(source)
while true do
local temp= obslua.obs_filter_get_target(a_source)
if temp == nil then break end
a_source= temp
end
if not a_source then return nil end
local source_name= obslua.obs_source_get_name(a_source)
local a_scene= obs.scene:get_scene()
if not a_scene then
return nil
end
local a_scene_item= a_scene.get(source_name)
a_scene.free()
return a_scene_item
end
if filter and type(filter) == "table" and filter["create"]
and type(filter["create"]) == "function" then
local __a_sceneitem=get_sceneitem()
local src=filter.create(
obs.PairStack(settings),
__a_sceneitem
)
if __a_sceneitem and __a_sceneitem.data then __a_sceneitem.free() end
if src ~= nil and type(src) == "table" then
src.sceneitem=get_sceneitem
self.src=src;src.filter=source
src.is_custom=true
if filter and filter["setup"] and type(filter["setup"]) == "function" then
filter.setup(src)
end
return src
end
end
-- default creation
local src= {
source= source,filter=source,
params=nil,height=nil,isAlive=true,
width=nil,settings=obs.PairStack(settings),
sceneitem= get_sceneitem, item=get_sceneitem()
}
-- get width and height of source
if source ~= nil then
local target= obslua.obs_filter_get_target(source)
if target ~= nil then
src.width= obslua.obs_source_get_base_width(target)
src.height= obslua.obs_source_get_base_height(target)
end
else
src.width= 0;src.height= 0
end
shader = [[
uniform float4x4 ViewProj;
uniform texture2d image;
uniform int width;
uniform int height;
sampler_state textureSampler {
Filter = Linear;
AddressU = Border;
AddressV = Border;
BorderColor = 00000000;
};
struct VertData
{
float4 pos : POSITION;
float2 uv : TEXCOORD0;
};
float4 ps_get(VertData v_in) : TARGET
{
return image.Sample(textureSampler, v_in.uv.xy);
}
VertData VSDefault(VertData v_in)
{
VertData vert_out;
vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj);
vert_out.uv = v_in.uv;
return vert_out;
}
technique Draw
{
pass
{
vertex_shader = VSDefault(v_in);
pixel_shader = ps_get(v_in);
}
}
]]
obslua.obs_enter_graphics()
src.shader= obslua.gs_effect_create(shader, nil, nil)
obslua.obs_leave_graphics()
if src.shader ~= nil then
src.params= {
width= obslua.gs_effect_get_param_by_name(src.shader, "width"),
height= obslua.gs_effect_get_param_by_name(src.shader, "height"),
image= obslua.gs_effect_get_param_by_name(src.shader, "image"),
}
else
return self.destroy()
end
if filter and filter["setup"] and type(filter["setup"]) == "function" then
filter.setup(src)
end
self.src=src
return src
end, destroy= function(src)
src.isAlive= false
if filter and type(filter) == "table" and filter["destroy"]
and type(filter["destroy"]) == "function" then
for i, v in pairs(obs.utils.filters) do
if v == self then
table.remove(obs.utils.filters, i)
break
end
end
local result= filter.destroy(src)
if src and src.item and src.item.data then
src.item.free()
end
if src and src.is_custom then
return result
end
end
-- default destruction
if src and src.item and src.item.data then
src.item.free()
end
if src and type(src) == "table" and src.shader then
obslua.obs_enter_graphics()
obslua.gs_effect_destroy(src.shader)
obslua.obs_leave_graphics()
end
for i, v in pairs(obs.utils.filters) do
if v == self then
table.remove(obs.utils.filters, i)
break
end
end
end,video_tick=function(src, fps)
-- get width and height of source
if src.source ~= nil then
local target= obslua.obs_filter_get_target(src.source)
if target ~= nil then
src.width= obslua.obs_source_get_base_width(target)
src.height= obslua.obs_source_get_base_height(target)
else
src.width= 0;src.height= 0
end
else
src.width= 0;src.height= 0
end
-- call user-defined video_tick function
if filter and type(filter) == "table" and filter["video_tick"]
and type(filter["video_tick"]) == "function" then
filter.video_tick(src, fps)
end
end,
video_render= function(src)
if filter and type(filter) == "table" and filter["video_render"] and
type(filter["video_render"]) == "function" then
local result = filter.video_render(src)
if src.is_custom then
return result
end
end
-- default render
if not src or not src.source or not src.shader or not src.params then
return
end
if not src.item or not src.item.data then
local target= obslua.obs_filter_get_target(src.source)
if target ~= nil then
src.item= src.sceneitem()
src.width= obslua.obs_source_get_base_width(target)
src.height= obslua.obs_source_get_base_height(target)
end
end
local width= src.width;local height= src.height
if not obslua.obs_source_process_filter_begin(
src.source,obslua.GS_RGBA, obslua.OBS_NO_DIRECT_RENDERING
) then
obslua.obs_source_skip_video_filter(src.source)
return nil
end
if not src.params then
obslua.obs_source_process_filter_end(src.source, src.shader, width, height)
return nil
end
if type(width) == "number" then
obslua.gs_effect_set_int(src.params.width, width)
end
if type(height) == "number" then
obslua.gs_effect_set_int(src.params.height, height)
end
obslua.gs_blend_state_push()
obslua.gs_blend_function(
obslua.GS_BLEND_ONE, obslua.GS_BLEND_INVSRCALPHA
)
if width and height then
obslua.obs_source_process_filter_end(src.source, src.shader, width, height)
end
obslua.gs_blend_state_pop()
return true
end,
get_name=function()
return filter and filter.name or "Custom Filter"
end,get_defaults=function(settings)
local defaults=nil
if filter and type(filter) == "table" and filter["get_defaults"]
and type(filter["get_defaults"]) == "function" then
defaults = filter.get_defaults
end
if filter and type(filter) == "table" and filter["defaults"] and type(filter["defaults"]) == "function" then
defaults = filter.defaults
end
if defaults and type(defaults) == "function" then
return defaults(obs.PairStack(settings))
end
end,get_properties=function(src)
local properties= nil
if filter and type(filter) == "table" and filter["get_properties"]
and type(filter["get_properties"]) == "function" then
properties = filter.get_properties
end
if filter and type(filter) == "table" and filter["properties"] and type(filter["properties"]) == "function" then
properties = filter.properties
end
if properties and type(properties) == "function" then
return properties(src)
end
return nil
end
}
table.insert(obs.utils.filters, self)
if not filter or not type(filter) == "table" then
filter={}
end
filter.get_name= self.get_name
if not filter.id then
filter.id= self.id
end
filter.get_width= self.get_width
filter.get_height= self.get_height
filter.type= self.type
filter.output_flags= self.output_flags
--filter.destroy= self.destroy
return filter
end
-- [[ OBS FILTER CUSTOM API END]]
--[[ OBS SCENE API CUSTOM ]]
function obs.scene:get_source(source_name)
if not source_name or not type(source_name) == "string" then
return nil
end
local source = obslua.obs_get_source_by_name(source_name)
if not source then
return nil
end
return obs.wrap(source, obs.utils.OBS_SRC_TYPE)
end
function obs.scene:get_scene(scene_name)
local scene;local source_scene;
if not scene_name or not type(scene_name) == "string" then
source_scene=obslua.obs_frontend_get_current_scene()
if not source_scene then
return nil
end
scene= obslua.obs_scene_from_source(source_scene)
else
source_scene= obslua.obs_get_source_by_name(scene_name)
if not source_scene then
return nil
end
scene=obslua.obs_scene_from_source(source_scene)
end
local obj_scene_t;obj_scene_t= {
group_names=function()
local scene_items_list = obs.wrap(
obslua.obs_scene_enum_items(scene),
obs.utils.OBS_SCENEITEM_LIST_TYPE
)
if scene_items_list == nil or scene_items_list.data == nil then
return nil
end
local list={}
for _, item in ipairs(scene_items_list.data) do
local source = obslua.obs_sceneitem_get_source(item)
if source ~= nil then
local sourceName = obslua.obs_source_get_name(source)
if obslua.obs_sceneitem_is_group(item) then
table.insert(list, sourceName)
end
end
end
scene_items_list.free()
return list
end;source_names=function(source_id_type)
local scene_nodes_name_list= {}
local scene_items_list = obs.wrap(
obslua.obs_scene_enum_items(scene),
obs.utils.OBS_SCENEITEM_LIST_TYPE
)
for _, item in ipairs(scene_items_list.data) do
local source = obslua.obs_sceneitem_get_source(item)
if source ~= nil then
local sourceName = obslua.obs_source_get_name(source)
if source_id_type == nil or type(source_id_type) ~= "string" or source_id_type == "" then
table.insert(scene_nodes_name_list, sourceName)
else
local sourceId = obslua.obs_source_get_id(source)
if sourceId == source_id_type then
table.insert(scene_nodes_name_list, sourceName)
end
end
source= nil
end
end
scene_items_list.free()
return scene_nodes_name_list
end;get= function(source_name)
if not scene then
return nil
end
local c=1
local scene_item;local scene_items_list = obs.wrap(
obslua.obs_scene_enum_items(scene),
obs.utils.OBS_SCENEITEM_LIST_TYPE
)
if scene_items_list == nil or scene_items_list.data == nil then
return nil
end
for _, item in ipairs(scene_items_list.data) do
c = c + 1
local src= obslua.obs_sceneitem_get_source(item)
local src_name= obslua.obs_source_get_name(src)
if src ~= nil and src_name == source_name then
obslua.obs_sceneitem_addref(item)
scene_item= obs.wrap(item, obs.utils.OBS_SCENEITEM_TYPE)
break
end
end
scene_items_list.free()
if scene_item == nil or scene_item.data == nil then
return nil
end
local obj_source_t;
obj_source_t={
free=scene_item.free;
item=scene_item.data;
data=scene_item.data;
get_source=function()
return obslua.obs_sceneitem_get_source(scene_item.data)
end;get_name= function()
return obslua.obs_source_get_name(obj_source_t.get_source())
end;
}
return obj_source_t
end;add=function(source)
if not source then return false end
local sceneitem= obslua.obs_scene_add(scene, source)
if sceneitem == nil then return nil end
obslua.obs_sceneitem_addref(sceneitem)
return obs.wrap(sceneitem, obs.utils.OBS_SCENEITEM_TYPE)
end;get_label=function(name, source)
if (source == nil or source.data == nil) and name ~= nil and type(name) == "string" and name ~= "" then
source= obj_scene_t.get(name)
end
if not source or not source.data then
return nil
end
local obj_label_t;obj_label_t={
remove= function()
if obj_label_t.data == nil then return true end
obslua.obs_sceneitem_remove(obj_label_t.data)
source.free(); obj_label_t.data=nil;obj_label_t.item=nil
return true
end;
hide= function()
return obslua.obs_sceneitem_set_visible(obj_label_t.data, false)
end;show = function()
return obslua.obs_sceneitem_set_visible(obj_label_t.data, true)
end;
font= {
size= function(font_size)
local src= obs.PairStack(
obslua.obs_source_get_settings(source.get_source()),
nil,true
)
if not src or not src.data then
src= obs.PairStack()
end
local font= src.get_obj("font")
if not font or not font.data then
font= obs.PairStack()
--font.str("face","Arial")
end
if font_size == nil or not type(font_size) == "number" or font_size <= 0 then
font_size=font.get_int("size")
font.free();src.free();
return font_size
else
font.int("size", font_size)
end
font.free();
obslua.obs_source_update(source.get_source(), src.data)
src.free()
return true
end;face= function(face_name)
end
};text=function(txt)
local src= obs.PairStack(
obslua.obs_source_get_settings(source.get_source()),
nil,true
)
if not src or not src.data then
src= obs.PairStack()
end
local res=true
if txt == nil or txt == "" or type(txt) ~= "string" then
res=src.get_str("text")
if not res == nil then
res= ""
end
else
src.str("text", txt)
end
obslua.obs_source_update(source.get_source(), src.data)
src.free()
return res
end;free=function()
source.free()
obj_label_t=nil
return true
end;data=source.data;item=source.data;size={
width= function(w)
--local default_transform= obslua.obs_transform_info()
--local default_source_info=obslua.obs_source_info()
--obslua.obs_source_get_info(source.get_source(), default_source_info)
--obslua.obs_sceneitem_get_info2(source.data, default_transform)
local default_width= obslua.obs_source_get_width(source.get_source())
--local default_scale_x= default_transform.scale.x;
if w == nil then return default_width end
return w
end;
height= function(h)
local default_height= obslua.obs_source_get_height(source.get_source())
if h == nil then return default_height end
return h
end;
};pos = {
x=function(val)
local default_transform= obslua.obs_transform_info()
obslua.obs_sceneitem_get_info2(source.data, default_transform)
if val == nil then return default_transform.pos.x end
default_transform.pos.x= val
obslua.obs_sceneitem_set_info(source.data, default_transform)
return true
end;
y=function(val)
local default_transform= obslua.obs_transform_info()
obslua.obs_sceneitem_get_info2(source.data, default_transform)
if val == nil then return default_transform.pos.y end
default_transform.pos.y= val
obslua.obs_sceneitem_set_info(source.data, default_transform)
return true
end;
}
}
return obj_label_t
end;
add_label= function(name, text)
local src= obs.PairStack()
if not text then
text= "Text - Label"
end
src.str("text", text)
local source_label=obslua.obs_source_create("text_gdiplus", name, src.data, nil)
src.free()
local obj= obj_scene_t.get_label(
nil, obj_scene_t.add(source_label)
)
if not obj or not obj.data then
if source_label then obslua.obs_source_release(source_label) end
return nil
end
-- re-write the release function
-- [[SEEM LIKE THIS LEADS TO CRUSHES?]]
local free_func= obj.free;
obj.free= function()
obslua.obs_source_release(source_label)
return free_func()
end
return obj
end;add_group= function(name, refresh)
if refresh == nil then
refresh=true
end
local obj=obj_scene_t.get_group(nil, obslua.obs_scene_add_group2(scene, name, refresh))
if not obj or obj.data == nil then return nil end
-- overwrite the free function to prevent crush/bugs
obj.free=function() end
return obj
end;get_group= function(name, gp)
local obj;if not gp and name ~= nil then
obj= obs.wrap(obslua.obs_scene_get_group(scene, name), obs.utils.OBS_SCENEITEM_TYPE)
elseif gp ~= nil then
obj= obs.wrap(gp, obs.utils.OBS_SCENEITEM_TYPE)
else
return nil
end
obj["add"]= function(sceneitem)
if not sceneitem then
return false
end
obslua.obs_sceneitem_group_add_item(obj.data, sceneitem)
return true
end
obj["release"]= function()
return obj.free()
end;obj["item"]= obj.data
return obj
end;free= function()
obslua.obs_source_release(source_scene)
scene=nil
end;release= function()
return obj_scene_t.free()
end;get_width= function()
return obslua.obs_source_get_base_width(source_scene)
end;get_height = function()
return obslua.obs_source_get_base_height(source_scene)
end;data=scene;item=scene;
};
return obj_scene_t
end
function obs.scene:name()
source_scene=obslua.obs_frontend_get_current_scene()
if not source_scene then
return nil
end
local source_name= obslua.obs_source_get_name(source_scene)
obslua.obs_source_release(source_scene)
return source_name
end
function obs.scene:add_to_scene(source)
if not source then
return false
end
local current_source_scene= obslua.obs_frontend_get_current_scene()
if not current_source_scene then
return false
end
local current_scene= obslua.obs_scene_from_source(current_source_scene)
if not current_scene then
obslua.obs_source_release(current_source_scene)
return false
end
obslua.obs_scene_add(current_scene, source)
obslua.obs_source_release(current_source_scene)
return true
end
function obs.scene:names()
local scenes= obs.wrap(
obslua.obs_frontend_get_scenes(),
obs.utils.OBS_SRC_LIST_TYPE
);
local obj_table_t= {}
for _, a_scene in pairs(scenes.data) do
if a_scene then
local scene_source_name= obslua.obs_source_get_name(a_scene)
table.insert(obj_table_t, scene_source_name)
end
end
scenes.free()
return obj_table_t
end
--[[ OBS SCENE API CUSTOM END ]]
-- [[ OBS FRONT API ]]
function obs.front.source_names()
local list={}
local all_sources= obs.wrap(
obslua.obs_enum_sources(),
obs.utils.OBS_SRC_LIST_TYPE
);
for _, source in pairs(all_sources.data) do
if source then
local source_name= obslua.obs_source_get_name(source)
table.insert(list, source_name)
end
end
all_sources.free()
return list
end
function obs.front.source(source_name)
if not source_name or not type(source_name) == "string" then
return nil
end
local source = obslua.obs_get_source_by_name(source_name)
if not source then
return nil
end
return obs.wrap(source, obs.utils.OBS_SRC_TYPE)
end
-- [[ OBS FRONT API END ]]
-- [[ OBS SCRIPT PROPERTIES CUSTOM API]]
function obs.script.create()
return obslua.obs_properties_create()
end
function obs.script.options(p, unique_id, desc, enum_type_id, enum_format_id)
if not desc or type(desc) ~= "string" then
desc=""
end
if not unique_id or type(unique_id) ~= "string" or unique_id == "" then
unique_id= obs.utils.get_unique_id(20)
end
if enum_format_id == nil then
enum_format_id= obs.enum.options.string;
end
if enum_type_id == nil then
enum_type_id= obs.enum.options.default;
end
local obj=obslua.obs_properties_add_list(p, unique_id, desc, enum_type_id, enum_format_id);
if not obj then
obslua.script_log(obslua.LOG_ERROR, "[obsapi_custom.lua] Failed to create list property: " .. tostring(unique_id) .. " description: " .. tostring(desc) .. " enum_type_id: " .. tostring(enum_type_id) .. " enum_format_id: " .. tostring(enum_format_id))
return nil
end
obs.utils.properties.options[unique_id]= {
enum_format_id= enum_format_id;
enum_type_id= enum_type_id;type=enum_format_id
}
return obs.utils.obs_api_properties_patch(obj, p)
end
function obs.script.button(p, unique_id, label, callback)
if not label or type(label) ~= "string" then
label="button"
end
if not unique_id or type(unique_id) ~= "string" or unique_id == "" then
unique_id= obs.utils.get_unique_id(20)
end
if type(callback)~="function" then callback=function() end end
obs.utils.properties[unique_id]=obs.utils.obs_api_properties_patch(
obslua.obs_properties_add_button(p, unique_id, label, function(properties_t, property_t, obs_data_t)
return callback(properties_t, property_t, obs.PairStack(obs_data_t))
end)
,p)
return obs.utils.properties[unique_id]
end
function obs.script.label(p, unique_id, text, enum_type)
if not text or type(text) ~= "string" then
text=""
end
if not unique_id or type(unique_id) == nil or unique_id == "" or type(unique_id) ~= "string" then
unique_id= obs.utils.get_unique_id(20)
end
local default_enum_type= obslua.OBS_TEXT_INFO;
if(enum_type == nil) then
enum_type= default_enum_type
end
local obj= obs.utils.obs_api_properties_patch(obslua.obs_properties_add_text(p, unique_id, text, default_enum_type), p)
if enum_type == obs.enum.text.error then
obj.error(text)
elseif enum_type == obs.enum.text.warn then
obj.warn(text)
end
obj.type= enum_type;
obs.utils.properties[unique_id]= obj
return obj;
end
function obs.script.group(p, unique_id, desc, op, enum_type)
if not desc or type(desc) ~= "string" then
desc=""
end
if not unique_id or type(unique_id) ~= "string" or unique_id == "" then
unique_id= obs.utils.get_unique_id(20)
end
if enum_type == nil then
enum_type= obs.enum.group.normal;
end
-- if enum_type == obs.enum.group.bool and obs.utils.settings.get_bul(unique_id) == nil then
-- obs.utils.settings.bul(unique_id, false)
-- end
obs.utils.properties[unique_id]= obs.utils.obs_api_properties_patch(obslua.obs_properties_add_group(p, unique_id, desc, enum_type, op), p)
return obs.utils.properties[unique_id]
end
function obs.script.bool(p, unique_id, desc)
if not desc or type(desc) ~= "string" then
desc=""
end
if not unique_id or type(unique_id) ~= "string" or unique_id == "" then
unique_id= obs.utils.get_unique_id(20)
end
-- create a default value
-- if obs.utils.settings.get_bul(name) == nil then
-- obs.utils.settings.bul(name, false)
-- end
obs.utils.properties[unique_id]=obs.utils.obs_api_properties_patch(obslua.obs_properties_add_bool(p, unique_id, desc), p)
return obs.utils.properties[unique_id]
end
function obs.script.path(p, unique_id, desc, enum_type_id, filter_string, default_path_string)
if not unique_id or type(unique_id) ~= "string" or unique_id == "" then
unique_id= obs.utils.get_unique_id(20)
end
if not desc or type(desc) ~= "string" then
desc= ""
end
if enum_type_id == nil or type(enum_type_id) ~= "number" then
enum_type_id= obs.enum.path.read
end
if filter_string == nil or type(filter_string) ~= "string" then
filter_string=""
end
if default_path_string == nil or type(default_path_string) ~= "string" then
default_path_string= ""
end
obs.utils.properties[unique_id]= obs.utils.obs_api_properties_patch(obslua.obs_properties_add_path(p, unique_id, desc, enum_type_id, filter_string, default_path_string), p)
return obs.utils.properties[unique_id]
end
function obs.script.form(properties, title, unique_id)
local pp= obs.script.create();local __exit_click_callback__=nil;local __onexit_type__=1;
local __cancel_click_callback__=nil;local __oncancel_type__=1;
if unique_id == nil then
unique_id=obs.utils.get_unique_id(20)
end
local group_form= obs.script.group(properties,unique_id, "", pp, obs.enum.group.normal)
local label= obs.script.label(pp, unique_id .. "_label", title, obslua.OBS_TEXT_INFO);
obs.script.label(pp,"form_tt","
", obslua.OBS_TEXT_INFO);
local ipp= obs.script.create()
local group_inner= obs.script.group(pp, unique_id .. "_inner", "", ipp, obs.enum.group.normal)
local exit= obs.script.button(pp, unique_id .. "_exit", "Confirm",function(pp, s, ss)
if __exit_click_callback__ and type(__exit_click_callback__) == "function" then
__exit_click_callback__(pp,s, ss)
end
if __onexit_type__ == -1 then
group_form.free()
elseif __onexit_type__ == 1 then
group_form.hide()
end
return true
end)
local cancel= obs.script.button(pp, unique_id .. "_cancel", "Cancel", function(pp, s, ss)
if __cancel_click_callback__ and type(__cancel_click_callback__) == "function" then
__cancel_click_callback__(pp,s, ss)
end
if __oncancel_type__ == -1 then
group_form.free()
elseif __oncancel_type__ == 1 then
group_form.hide()
end
return true
end)
local obj_t;obj_t={
add={
button= function(...)
return obs.script.button(ipp, ...)
end;options= function(...)
return obs.script.options(ipp,...)
end;label= function(...)
return obs.script.label(ipp,...)
end;group= function(...)
return obs.script.group(ipp, ...)
end;bool= function(...)
return obs.script.bool(ipp, ...)
end;path=function(...)
return obs.script.path(ipp,...)
end;input= function(...)
return obs.script.input(ipp, ...)
end;number=function(...)
return obs.script.number(ipp, ...)
end
};get= function(name)
return obs.script.get(ipp,name)
end;free= function()
group_form.free();
obslua.obs_properties_destroy(ipp);ipp=nil
obslua.obs_properties_destroy(pp);pp=nil
return true
end;data=ipp;item=ipp;confirm={};onconfirm={};oncancel={};cancel={}
}
function obj_t.confirm:click(clb)
__exit_click_callback__=clb
return obj_t
end;function obj_t.confirm:text(title_value)
if not title_value or type(title_value) ~= "string" or title_value == "" then
return false
end
exit.text(title_value)
return true
end
function obj_t.onconfirm:hide()
__onexit_type__= 1
return obj_t
end;function obj_t.onconfirm:remove()
__onexit_type__=-1
return obj_t
end;function obj_t.onconfirm:idle()
__onexit_type__= 0
return obj_t
end
function obj_t.cancel:click(clb)
__cancel_click_callback__=clb
return obj_t
end;function obj_t.cancel:text(txt)
if not txt or type(txt) ~= "string" or txt == "" then
return false
end
cancel.text(txt)
return true
end
function obj_t.oncancel:idle()
__oncancel_type__= 0
return obj_t
end;function obj_t.oncancel:remove()
__oncancel_type__= -1
return obj_t
end;function obj_t.oncancel:hide()
__oncancel_type__= 1
return obj_t
end
function obj_t.show()
return group_form.show();
end;function obj_t.hide()
return group_form.hide();
end;function obj_t.remove()
return obj_t.free()
end
obs.utils.properties[unique_id]= obj_t
return obj_t
end
function obs.script.fps(properties_t, unique_id, title)
if not title or type(title) ~= "string" then
title=""
end
if not unique_id or type(unique_id) ~= "string" or unique_id == "" then
unique_id= obs.utils.get_unique_id(20)
end
obs.utils.properties[unique_id]=obs.utils.obs_api_properties_patch(
obslua.obs_properties_add_frame_rate(properties_t, unique_id, title),
properties_t
)
return obs.utils.properties[unique_id]
end
function obs.script.list(properties_t, unique_id, title, enum_type_id, filter_string, default_path_string)
if not filter_string or type(filter_string) ~= "string" then
filter_string=""
end
if not default_path_string or type(default_path_string) ~= "string" then
default_path_string= ""
end
if not enum_type_id or type(enum_type_id) ~= "number" or (
enum_type_id ~= obs.enum.list.string
and enum_type_id ~= obs.enum.list.file and
enum_type_id ~= obs.enum.list.url
) then
enum_type_id= obs.enum.list.string
end
if not title or type(title) ~= "string" then
title=""
end
if not unique_id or type(unique_id) ~= "string" or unique_id == "" then
unique_id= obs.utils.get_unique_id(20)
end
obs.utils.properties[unique_id]=obs.utils.obs_api_properties_patch(
obslua.obs_properties_add_editable_list(properties_t, unique_id, title, enum_type_id, filter_string, default_path_string),
properties_t)
return obs.utils.properties[unique_id]
end
function obs.script.input(p, unique_id, title, enum_type_id, callback)
if not title or type(title) ~= "string" then
title=""
end
if not unique_id or type(unique_id) ~= "string" or unique_id == "" then
unique_id= obs.utils.get_unique_id(20)
end
if not enum_type_id == nil or (
enum_type_id ~= obs.enum.text.input and enum_type_id ~= obs.enum.text.textarea and
enum_type_id ~= obs.enum.text.password) then
enum_type_id= obs.enum.text.input
end
obs.utils.properties[unique_id]=obs.utils.obs_api_properties_patch(
obslua.obs_properties_add_text(p, unique_id, title, enum_type_id), p
)
return obs.utils.properties[unique_id]
end
function obs.script.color(properties_t, unique_id, title)
if not title or type(title) ~= "string" then
title=""
end
if not unique_id or type(unique_id) ~= "string" or unique_id == "" then
unique_id= obs.utils.get_unique_id(20)
end
obs.utils.properties[unique_id]=obs.utils.obs_api_properties_patch(obslua.obs_properties_add_color_alpha(properties_t, unique_id, title), properties_t)
return obs.utils.properties[unique_id]
end
function obs.script.number(properties_t, min, max,steps, unique_id, title, enum_number_type_id, enum_type_id)
if not enum_number_type_id then
enum_number_type_id= obs.enum.number.int
end
if not enum_type_id then
enum_type_id= obs.enum.number.input
end
if not unique_id or type(unique_id) ~= "string" or unique_id == "" then
unique_id= obs.utils.get_unique_id(20)
end
local obj;if enum_type_id == obs.enum.number.slider then
if enum_number_type_id == obs.enum.number.float then
obj= obs.utils.obs_api_properties_patch(obslua.obs_properties_add_float(
properties_t, unique_id, title, min, max,steps
))
else
obj= obs.utils.obs_api_properties_patch(obslua.obs_properties_add_int_slider(
properties_t, unique_id, title, min, max, steps
))
end
else
if enum_number_type_id == obs.enum.number.float then
obj= obs.utils.obs_api_properties_patch(obslua.obs_properties_add_float(
properties_t, unique_id, title, min, max,steps
))
else
obj= obs.utils.obs_api_properties_patch(obslua.obs_properties_add_int(
properties_t, unique_id, title, min, max, steps
))
end
end
if obj then
obj["type"]= enum_number_type_id
end
obs.utils.properties[unique_id]=obj
return obj
end
function obs.script.get(name)
return obs.utils.properties[name]
end
-- [[ OBS SCRIPT PROPERTIES CUSTOM API END ]]
-- [[ API UTILS ]]
function obs.utils.obs_api_properties_patch(pp,pp_t, cb)
-- if pp_t ~= nil and not obs.utils.properties[pp] then
-- obs.utils.properties[pp]=pp_t;
-- end
local pp_unique_name= obslua.obs_property_name(pp)
local obs_pp_t=pp; -- extra
-- onchange [Event Handler]
local __onchange_list={}
local item=nil;local objText;local objInput;local objGlobal;objGlobal={
cb=cb;disable=function()
obslua.obs_property_set_disabled(pp, true)
return nil
end;enable=function()
obslua.obs_property_set_disabled(obs_pp_t, false)
return nil
end;onchange=function(callback)
if type(callback) ~= "function" then
return false
end
table.insert(__onchange_list, callback)
return true
end;hide= function()
obslua.obs_property_set_visible(obs_pp_t, false)
end;show = function()
obslua.obs_property_set_visible(obs_pp_t, true)
return nil
end;get= function()
return obs_pp_t
end;hint= function(txt)
if txt == nil or type(txt) ~= "string" or txt == "" then
return obs_property_get_long_description(obs_pp_t)
end
item=obslua.obs_property_set_long_description(obs_pp_t, txt)
return nil
end;free= function()
obs.utils.properties[pp_unique_name]=nil
obslua.obs_properties_remove_by_name(pp_t, pp_unique_name)
return true
end;remove=function()
return objGlobal.free()
end;data=pp;item=pp;title=function(txt)
if txt == nil or type(txt) ~= "string" then
return obslua.obs_property_get_description(pp)
end
obslua.obs_property_set_description(pp, txt)
return objGlobal
end
};objText={
error=function(txt)
if txt == nil or type(txt) ~= "string" then
return obslua.obs_property_description(pp)
end
obslua.obs_property_text_set_info_type(pp, obslua.OBS_TEXT_INFO_ERROR)
obslua.obs_property_set_description(pp, txt)
return objText
end;
text=function(txt)
local id_name= obslua.obs_property_name(pp)
objText.type=obs.enum.text.default
obslua.obs_property_text_set_info_type(pp, objText.type)
if txt ~= nil and type(txt) == "string" then obslua.obs_property_set_description(pp, txt) end
return objText
end;warn=function(txt)
local id_name= obslua.obs_property_name(pp)
local textarea_id= id_name .. "_obsapi_hotfix_textarea"
local input_id= id_name .. "_obsapi_hotfix_input"
local property= obs.script.get(pp_t, id_name)
local textarea_property= obs.script.get(pp_t, textarea_id)
local input_property= obs.script.get(pp_t, input_id)
objText.type=obs.enum.text.input
property.show();input_property.hide();textarea_property.hide()
objText.type=obs.enum.text.warn
obslua.obs_property_text_set_info_type(pp, objText.type)
if txt ~= nil and type(txt) == "string" then obslua.obs_property_set_description(pp, txt) end
return objText
end;textarea=obs.expect(function(txt)
local id_name= obslua.obs_property_name(pp)
local textarea_id= id_name .. "_obsapi_hotfix_textarea"
local input_id= id_name .. "_obsapi_hotfix_input"
local property= obs.script.get(pp_t, id_name)
local textarea_property= obs.script.get(pp_t, textarea_id)
obs_pp_t=textarea_property.get()
local input_property= obs.script.get(pp_t, input_id)
objText.type=obs.enum.text.textarea
property.hide();input_property.hide();textarea_property.show()
if txt ~= nil and type(txt) == "string" then obs.utils.settings.str(textarea_id, txt) end
return objText
end);type=-1
};objInput={
value=obs.expect(function(txt)
if txt ~= nil and type(txt) == "string" then obs.utils.settings.str(pp_unique_name, txt) end
return objInput
end);type=-1
};
local objOption;objOption={
item=nil;clear= function()
objOption.item=obslua.obs_property_list_clear(pp)
return objOption
end;add={
str= function(title, id)
if id == nil or type(id) ~= "string" or id == "" then
--id= obs.utils.get_unique_id(20)
obslua.script_log(obslua.LOG_INFO, "[obs.script.options.str] id is nil or invalid!")
return objOption
end
objOption.item=obslua.obs_property_list_add_string(pp, title, id)
return objOption
end;int= function(title, id)
if id == nil or type(id) ~= "number" then
--id= obs.utils.get_unique_id(20)
obslua.script_log(obslua.LOG_INFO, "[obs.script.options.int] id is nil or invalid!")
return objOption
end
objOption.item=obslua.obs_property_list_add_int(pp, title, id)
return objOption
end;dbl=function(title, id)
if id == nil or type(id) ~= "number" then
--id= obs.utils.get_unique_id(20)
obslua.script_log(obslua.LOG_INFO, "[obs.script.options.dbl] id is nil or invalid!")
return objOption
end
objOption.item=obslua.obs_property_list_add_float(pp, title, id)
return objOption
end;bul=function(title, id)
if id == nil or type(id) ~= "boolean" then
id= obs.utils.get_unique_id(20)
end
objOption.item=obslua.obs_property_list_add_bool(pp, title, id)
return objOption
end
};cursor = function(index)
if index == nil or type(index) ~= "number" or index < 0 then
if type(index) == "string" then -- find the index by the id value
for i=0, obslua.obs_property_list_item_count(pp)-1 do
if obslua.obs_property_list_item_string(pp, i) == index then
index= i
break
end
end
if type(index) ~= "number" then
return nil
end
else
index= objOption.item;if type(index) ~= "number" or index < 0 then
index=obslua.obs_property_list_item_count(pp)-1
end
end
end
local info_title;local info_id
info_title=obslua.obs_property_list_item_name(pp, index)
if obs.utils.properties.options[pp_unique_name] and obs.utils.properties.options[pp_unique_name].enum_format_id == obs.enum.options.string then
info_id= obslua.obs_property_list_item_string(pp, index)
elseif obs.utils.properties.options[pp_unique_name] and obs.utils.properties.options[pp_unique_name].enum_format_id == obs.enum.options.int then
info_id= obslua.obs_property_list_item_int(pp, index)
elseif obs.utils.properties.options[pp_unique_name] and obs.utils.properties.options[pp_unique_name].enum_format_id == obs.enum.options.float then
info_id= obslua.obs_property_list_item_float(pp, index)
elseif obs.utils.properties.options[pp_unique_name] and obs.utils.properties.options[pp_unique_name].enum_format_id == obs.enum.options.bool then
info_id= obslua.obs_property_list_item_bool(pp, index)
else
info_id= nil
end
local nn_obj=nil;nn_obj={
disable= function()
obslua.obs_property_list_item_disable(pp, index, true)
return nn_obj
end; enable= function()
obslua.obs_property_list_item_disable(pp, index, false)
return nn_obj
end;remove=function()
obslua.obs_property_list_item_remove(pp, index)
return true
end;title=info_title;value=info_id;index=index;
ret=function()
return objOption
end;isDisabled=function()
return obslua.obs_property_list_item_disabled(pp, index)
end
}
return nn_obj;
end;current=function()
local current_selected_option=nil
if obs.utils.properties.options[pp_unique_name] and obs.utils.properties.options[pp_unique_name].enum_format_id == obs.enum.options.string then
current_selected_option= obs.utils.settings.str(pp_unique_name)
elseif obs.utils.properties.options[pp_unique_name] and obs.utils.properties.options[pp_unique_name].enum_format_id == obs.enum.options.int then
current_selected_option= obs.utils.settings.int(pp_unique_name)
elseif obs.utils.properties.options[pp_unique_name] and obs.utils.properties.options[pp_unique_name].enum_format_id == obs.enum.options.float then
current_selected_option= obs.utils.settings.float(pp_unique_name)
elseif obs.utils.properties.options[pp_unique_name] and obs.utils.properties.options[pp_unique_name].enum_format_id == obs.enum.options.bool then
current_selected_option= obs.utils.settings.bul(pp_unique_name)
end
return objOption.cursor(current_selected_option)
end
};local fr_rt= false
local objButton;objButton={
item=nil;click= function(callback)
if type(callback) ~= "function" then
obslua.script_log(obslua.LOG_ERROR, "[button.click] invalid callback type " .. type(callback) .. " expected function")
return objButton
end
objButton.item=obslua.obs_property_set_modified_callback(pp,function(properties_t, property_t, obs_data_t)
return callback(properties_t, property_t, obs.PairStack(obs_data_t))
end)
return objButton
end;text= function(txt)
if txt == nil or type(txt) ~= "string" or txt == "" then
return obslua.obs_property_description(pp)
end
obslua.obs_property_set_description(pp, txt)
return objButton
end;url=function(url)
if not url or type(url) ~= "string" or url == "" then
obslua.script_log(obslua.LOG_ERROR, "[button.url] invalid url type, expected string, got " .. type(url))
return objButton --obslua.obs_property_button_get_url(pp)
end
obslua.obs_property_button_set_url(pp, url)
return objButton
end;type=function(button_type)
if button_type == nil or (button_type ~= obs.enum.button.url and button_type ~= obs.enum.button.default) then
obslua.script_log(obslua.LOG_ERROR, "[button.type] invalid type, expected obs.enum.button.url | obs.enum.button.default, got " .. type(button_type))
return objButton --obslua.obs_property_button_get_type(pp)
end
obslua.obs_property_button_set_type(pp, button_type)
return objButton
end
};
local objGroup;objGroup={
};local objBool;objBool={
checked=function(bool_value)
if not obs.utils.settings then
obslua.script_log(obslua.LOG_ERROR, "[obs.utils.settings] is not set, please use 'script_load' to set it")
return nil
end
local property_id=obslua.obs_property_name(pp)
if bool_value == nil or type(bool_value) ~= "boolean" then
return obs.utils.settings.get_bul(property_id)
end
obs.utils.settings.bul(property_id, bool_value)
return objBool
end;
};local objColor;objColor={
value= obs.expect(function(r_color, g_color, b_color, alpha_value)
if r_color == nil then
return obs.utils.settings.int(pp_unique_name)
end
if type(r_color) ~= "number" or type(g_color) ~= "number" or type(b_color) ~= "number" then
return false
end
if alpha_value == nil then
alpha_value=1
end
local color_value = bit.bor(
bit.lshift(alpha_value * 255, 24),
bit.lshift(b_color, 16),
bit.lshift(g_color, 8),
r_color
)
--(alpha_value << 24) | (b_color << 16) | (g_color << 8) | r_color
obs.utils.settings.int(pp_unique_name, color_value)
return color_value
end);type= obslua.OBS_PROPERTY_COLOR_ALPHA
}local objList;objList={
insert=function(value, selected, hidden)
if type(value) ~= "string" then
return objList
end
if type(selected) ~= "boolean" then
selected= false
end
if type(hidden) ~= "boolean" then
hidden= false
end
local unique_id= obs.utils.get_unique_id(20)
local obs_data_t= obs.PairStack()
obs_data_t.str("value", value)
obs_data_t.bul("selected", selected)
obs_data_t.bul("hidden", hidden)
obs_data_t.str("uuid", unique_id)
local obs_curr_data_t= obs.utils.settings.arr(pp_unique_name)
obs_curr_data_t.insert(obs_data_t.data)
obs_data_t.free();obs_curr_data_t.free()
return objList
end,filter= function()
return obslua.obs_property_editable_list_filter(pp)
end,default=function()
return obslua.obs_property_editable_list_default_path(pp)
end,type=function()
return obslua.obs_property_editable_list_type(pp)
end;
};local objNumber;objNumber={
suffix= function(text)
obslua.obs_property_float_set_suffix(pp, text)
obslua.obs_property_int_set_suffix(pp, text)
return objNumber
end;value=function(value)
if objNumber.type == obs.enum.number.int then
obs.utils.settings.int(pp_unique_name, value)
elseif objNumber.type == obs.enum.number.float then
obs.utils.settings.dbl(pp_unique_name, value)
else
return nil
end
return value
end;type=nil
}
local property_type= obslua.obs_property_get_type(pp)
-- [[ ON-CHANGE EVENT HANDLE FOR ANY KIND OF USER INTERACTIVE INPUT ]]
if property_type == obslua.OBS_PROPERTY_COLOR or property_type == obslua.OBS_PROPERTY_COLOR_ALPHA or
property_type == obslua.OBS_PROPERTY_BOOL or property_type == obslua.OBS_PROPERTY_LIST or
property_type == obslua.OBS_PROPERTY_EDITABLE_LIST or property_type == obslua.OBS_PROPERTY_PATH or
(property_type == obslua.OBS_PROPERTY_TEXT and (
obslua.obs_property_text_type(pp) == obs.enum.text.textarea or
obslua.obs_property_text_type(pp) == obs.enum.text.input or
obslua.obs_property_text_type(pp) == obs.enum.text.password
)) then
obslua.obs_property_set_modified_callback(obs_pp_t, function(properties_t, property_t, settings)
settings=obs.PairStack(settings)
local pp_unique_name= obslua.obs_property_name(property_t)
local current_value;property_type= obslua.obs_property_get_type(property_t)
if property_type == obslua.OBS_PROPERTY_BOOL then
current_value= settings.bul(pp_unique_name)
elseif property_type == obslua.OBS_PROPERTY_TEXT or
property_type == obslua.OBS_PROPERTY_PATH or
property_type == obslua.OBS_PROPERTY_BUTTON then
current_value= settings.str(pp_unique_name)
elseif property_type == obslua.OBS_PROPERTY_INT or property_type == obslua.OBS_PROPERTY_COLOR_ALPHA or property_type == obslua.OBS_PROPERTY_COLOR then
current_value= settings.int(pp_unique_name)
elseif property_type == obslua.OBS_PROPERTY_FLOAT then
current_value= settings.dbl(pp_unique_name)
elseif property_type == obslua.OBS_PROPERTY_LIST then
if obs.utils.properties.options[pp_unique_name].type == obs.enum.options.string then
current_value= settings.str(pp_unique_name)
elseif obs.utils.properties.options[pp_unique_name].type == obs.enum.options.int then
current_value= settings.int(pp_unique_name)
elseif obs.utils.properties.options[pp_unique_name].type == obs.enum.options.float then
current_value= settings.dbl(pp_unique_name)
elseif obs.utils.properties.options[pp_unique_name].type == obs.enum.options.bool then
current_value= settings.bul(pp_unique_name)
end
elseif property_type == obslua.OBS_PROPERTY_FONT then
current_value= settings.obj(pp_unique_name)
elseif property_type == obslua.OBS_PROPERTY_EDITABLE_LIST then
current_value= settings.arr(pp_unique_name)
end
for _, vclb in pairs(__onchange_list) do
vclb(current_value, obs.script.get(obslua.obs_property_name(property_t)), properties_t, settings)
end
if type(current_value) == "table" then
current_value.free()
end
return true
end);
end
if property_type == obslua.OBS_PROPERTY_GROUP then
obs.utils.table.append(objGroup, objGlobal)
return objGroup;
elseif property_type == obslua.OBS_PROPERTY_EDITABLE_LIST then
obs.utils.table.append(objList, objGlobal)
return objList
elseif property_type == obslua.OBS_PROPERTY_LIST then
obs.utils.table.append(objOption, objGlobal)
return objOption;
elseif property_type == obslua.OBS_PROPERTY_INT or property_type == obslua.OBS_PROPERTY_FLOAT then
obs.utils.table.append(objNumber, objGlobal)
return objNumber
elseif property_type == obslua.OBS_PROPERTY_BUTTON then
obs.utils.table.append(objButton, objGlobal)
return objButton
elseif property_type == obslua.OBS_PROPERTY_COLOR_ALPHA or property_type == obslua.OBS_PROPERTY_COLOR then
obs.utils.table.append(objColor, objGlobal)
return objColor
elseif property_type == obslua.OBS_PROPERTY_TEXT then
local obj_enum_type_id= obslua.obs_property_text_type(pp)
if obj_enum_type_id == obs.enum.text.textarea or
obj_enum_type_id == obs.enum.text.input or
obj_enum_type_id == obs.enum.text.password then
objInput.type= obj_enum_type_id
obs.utils.table.append(objInput, objGlobal)
return objInput;
else
objText.type= obj_enum_type_id
obs.utils.table.append(objText, objGlobal)
return objText;
end
elseif property_type == obslua.OBS_PROPERTY_BOOL then
obs.utils.table.append(objBool, objGlobal)
return objBool;
else
return objGlobal;
end
end
function obs.utils.get_unique_id(rs, i, mpc, cmpc)
local chars= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
if i == nil then
i= true;
end
if mpc == nil or type(mpc) ~= "string" then
mpc= tostring(os.time());
mpc=obs.utils.get_unique_id(rs, false, mpc, true)
elseif cmpc == true then
chars=mpc
end
local index= math.random(1, #chars)
local c= chars:sub(index, index)
if c == nil then
c=""
end
if rs <= 0 then
return c;
end
local val= obs.utils.get_unique_id(rs - 1,false, mpc, cmpc)
if i == true and mpc ~= nil and type(mpc) == "string" and #val > 1 then
val= val .. "_" .. mpc
end
return c .. val
end
function obs.utils.table.append(tb, vv)
for k, v in pairs(vv) do
if type(v) == "function" then
local old_v = v
v = function(...)
local retValue= old_v(...)
if retValue== nil then
return tb;
end
return retValue;
end
end
if type(k) == "string" then
tb[k]= v;
else
table.insert(tb, k, v)
end
end
end
function obs.utils.json_to_table(str)
local position = 1
local function skip_whitespace()
local _, e = str:find("^[ \n\r\t]*", position)
position = (e or position - 1) + 1
end
local function parse_value()
skip_whitespace()
local char = str:sub(position, position)
-- Object
if char == '{' then
position = position + 1
local obj = {}
skip_whitespace()
if str:sub(position, position) == '}' then
position = position + 1
return obj
end
while true do
skip_whitespace()
local key = parse_value()
skip_whitespace()
assert(str:sub(position, position) == ':', "Expected ':' after key")
position = position + 1
obj[key] = parse_value()
skip_whitespace()
local next_char = str:sub(position, position)
if next_char == '}' then
position = position + 1
break
end
assert(next_char == ',', "Expected ',' or '}' in object")
position = position + 1
end
return obj
-- Array
elseif char == '[' then
position = position + 1
local arr = {}
skip_whitespace()
if str:sub(position, position) == ']' then
position = position + 1
return arr
end
while true do
arr[#arr + 1] = parse_value()
skip_whitespace()
local next_char = str:sub(position, position)
if next_char == ']' then
position = position + 1
break
end
assert(next_char == ',', "Expected ',' or ']' in array")
position = position + 1
end
return arr
-- String
elseif char == '"' then
position = position + 1
local start = position
while true do
local c = str:sub(position, position)
if c == '"' then
local s = str:sub(start, position - 1)
position = position + 1
return s
elseif c == '\\' then
position = position + 2 -- Skip escaped char
elseif c == '' then
error("Unterminated string")
else
position = position + 1
end
end
-- Number
elseif char:match("[%d%-]") then
local num_str
num_str, position = str:match("^([-0-9.eE]+)()", position)
local num = tonumber(num_str)
assert(num ~= nil, "Invalid number: " .. tostring(num_str))
return num
-- True / False / Null
elseif str:sub(position, position + 3) == "true" then
position = position + 4
return true
elseif str:sub(position, position + 4) == "false" then
position = position + 5
return false
elseif str:sub(position, position + 3) == "null" then
position = position + 4
return nil
end
error("Unexpected character at position " .. position .. ": " .. tostring(char))
end
return parse_value()
end
-- [[ API UTILS END ]]
-- [[ OBS CUSTOM API END ]]