--[[
Author: iixisii
contact: @iixisii
ver. 1.2 | 12.01.25 | 12.26.25 {
1.2.0 -> MAJOR UPDATE ->
- added new option, which would allow user to select 'File' or 'folder'
1.2.1 -> MINOR UPDATE ->
- fixed a bug whenever you close obs and open it again the program would not start automatically!
}
]]
local data = ""
local __START_HOTKEY_ID= obslua.OBS_INVALID_HOTKEY_ID
local __STOP_HOTKEY_ID= obslua.OBS_INVALID_HOTKEY_ID
local __DISPLAY_ACTION_DEFAULT_INDEX=10
local __DISPLAY_ACTION_INDEX=__DISPLAY_ACTION_DEFAULT_INDEX
local __DISPLAY_ACTION_CURRENT_INDEX=0
__SCENE=nil
program_running=false;local __await_time_restart_video_callback=false
local locally_viewed={};local video_index=0;local current_video_path="";local current_video_source="";
function script_properties()
if not __SCENE or __SCENE.data == nil then
__SCENE= obs.scene.get_scene()
end
__DISPLAY_ACTION_CURRENT_INDEX=0
local p= obs.script.create()
local fld_path_input;local vid_path_input;local display_action;
local active_label=obs.script.label(p, "active_label","(Currently Not Active)")
local shuffle_option= obs.script.bool(p, "shuffle_option","Shuffle Videos")
shuffle_option.hint(
"When enabled, the videos will be shuffled everytime it is played."
)
local vid_source_list= obs.script.options(p,"vid_source_list","VIDEO SOURCE: ")
local path_view_list= obs.script.options(p, "path_view_list", "OPEN PATH: ")
path_view_list.add.str(
"File < Insert multiple videos file >",""
).add.str(
"Folder < Specific videos folder >",""
)
local function display_all_media_source()
vid_source_list.clear()
vid_source_list.add.str("Source < Select A Video Source >","")
local source_names=__SCENE.source_names()
local video_source_count=0
for _, source_name in pairs(source_names) do
local scene_item= __SCENE.get(source_name)
if scene_item and scene_item.data ~= nil then
local source= scene_item.get_source()
if source then
local source_id= obslua.obs_source_get_id(source)
if source_id == "ffmpeg_source" or source_id == "vlc_source" then
video_source_count=video_source_count+1
vid_source_list.add.str(source_name, source_name)
end
end
end
scene_item.free()
end
if video_source_count <= 0 then
vid_source_list.add.str("< NO VIDEO SOURCES FOUND >","").cursor().disable()
end
source_names=nil
end
local function refresh_callback()
return (function()
if obs.utils.settings.str("vid_source_list") == "" or not obs.utils.settings.bul("isPressed") then
active_label.text("Running: 🔴")
else
active_label.text("Running: 🟢")
end
display_all_media_source()
return true
end)()
end
local t=os.clock()
path_view_list.onchange(function(value)
if os.clock() - t >= 1 then
t=os.clock()
program_stop(true)
refresh_callback()
end
if value == "" then
vid_path_input.show();fld_path_input.hide()
display_action.show();local invalue= obs.utils.settings.str("display_action");
if invalue == "" then
videoFilesVisible(true)
elseif invalue ~= "" and invalue ~= "" then
videoFilesVisible(true, invalue)
end
elseif value == "" then
vid_path_input.hide();fld_path_input.show()
display_action.hide();videoFilesVisible(false)
end
return true
end)
vid_source_list.onchange(function()
return refresh_callback()
end)
fld_path_input= obs.script.path(p, "fld_path_input", "FOLDER PATH: ",obs.enum.path.folder)
fld_path_input.hint(
"Be sure to only store videos files in this folder!
"
)
fld_path_input.hide()
-- fld_path_input.onchange(function(value)
-- end)
vid_path_input= obs.script.path(p,"vid_path_input","ADD VIDEO: ")
display_action= obs.script.options(p, "display_action")
local refresh_button= obs.script.button(p, "refresh_button", "Refresh 💫", refresh_callback)
local hr_label=obs.script.label(p, "hr_label","")
vid_path_input.hint(
"Add a video path to the list."
)
local next_in_display_action_list;
vid_path_input.onchange(function(video_path_value, _, __, settings)
if not settings or not settings.data then
return false
end
if not video_path_value or video_path_value == "" then
return false
end
local video_title= video_path_value:match("([^/]+)%.%w+$")
obs.utils.settings.str("vid_path_input", "")
local video_list= obs.utils.settings.get_arr("video_list")
local video= obs.utils.PairStack()
video.str("path", video_path_value)
video.str("id", obs.utils.get_unique_id(20))
video.str("title", video_title)
local item= add_to_video_list(video, p)
if obs.utils.settings.str("display_action") ~= "all" then
item.hide()
end
video_list.insert(video.data)
-- hr_label.text([[
--
--
-- | ]]..tostring(video_list.size())..[[ |
--
|
--
--
-- ]])
local no_videos= display_action.cursor("")
if no_videos then
no_videos.remove()
end
video.free();video_list.free()
local load_more= display_action.cursor("")
local none_option= display_action.cursor("")
local all_option= display_action.cursor("")
if not all_option then
display_action.add.str("< View All Videos >", "")
end
if not none_option then
display_action.add.str("-","").cursor().disable()
end
if not load_more then
if __DISPLAY_ACTION_CURRENT_INDEX < __DISPLAY_ACTION_INDEX then
next_in_display_action_list()
else
display_action.add.str("< Load more >", "")
end
end
return true
end)
local video_list= obs.utils.settings.get_arr("video_list")
local total_size=0;local video_list_cursor= video_list.next()
next_in_display_action_list = function()
local load_more= display_action.cursor("")
if load_more then
obs.utils.settings.str("display_action", "")
load_more.remove()
__DISPLAY_ACTION_INDEX=__DISPLAY_ACTION_INDEX+__DISPLAY_ACTION_DEFAULT_INDEX
end
local video_list= obs.utils.settings.get_arr("video_list")
for video in video_list.next(__DISPLAY_ACTION_CURRENT_INDEX) do
if __DISPLAY_ACTION_CURRENT_INDEX >= (__DISPLAY_ACTION_INDEX) then
display_action.add.str("< Load more >", "")
break
end
__DISPLAY_ACTION_CURRENT_INDEX=__DISPLAY_ACTION_CURRENT_INDEX+1
display_action.add.str(video.str("title"), video.str("id"))
video.free()
end
video_list.free()
end
display_action.add.str("< Select View Option >", "")
if video_list.size() <= 0 then
display_action.add.str("< No Videos Added >", "").cursor().disable()
else
display_action.add.str("< View All Videos >", "")
display_action.add.str("-","").cursor().disable()
next_in_display_action_list()
end
display_action.onchange(function(action_value, _, properties_t, __ , ___)
if action_value == "" then
next_in_display_action_list()
return true
end
if not properties_t or type(properties_t) ~= "userdata" then
return false
end
DisplayActionUpdate(action_value, properties_t, -1)
return true
end)
hr_label.text([[
]])
if obs.utils.settings.str("path_view_list") == "" then
vid_path_input.hide();display_action.hide();fld_path_input.show()
end
video_list.free()
display_all_media_source();
refresh_callback()
return p
end
function script_defaults(settings)
settings= obs.utils.PairStack(settings)
settings.str("vid_path_input", "", true)
settings.str("vid_source_list", "", true)
settings.bul("shuffle_option", false, true)
settings.bul("isPressed", false, true)
-- local video_list= obs.utils.ArrayStack()
-- settings.arr("video_list", video_list.data, true)
-- video_list.free()
return true
end
function script_save(settings)
-- [[ UPDATE HOTKEYS ]]
local start_hotkey_id= script_path() .. "_start_global_hotkey_1"
local stop_hotkey_id = script_path() .. "_stop_global_hotkey_1"
local hotkey_start_arr_t= obslua.obs_hotkey_save(__START_HOTKEY_ID)
local hotkey_stop_arr_t= obslua.obs_hotkey_save(__STOP_HOTKEY_ID)
if hotkey_start_arr_t then
obs.utils.settings.arr(start_hotkey_id, hotkey_start_arr_t)
obslua.obs_data_array_release(hotkey_start_arr_t)
end
if hotkey_stop_arr_t then
obs.utils.settings.arr(stop_hotkey_id, hotkey_stop_arr_t)
obslua.obs_data_array_release(hotkey_stop_arr_t)
end
end
function script_load(settings)
obs.utils.settings= obs.utils.PairStack(settings, nil, false) -- this is freed automatically
obs.utils.settings.str("display_action", "")
-- [[ HOTKEY SETUP ]]
__START_HOTKEY_ID = obslua.obs_hotkey_register_frontend(script_path() .. "_start_global_hotkey_1","[MOVA] Start program", program_start)
__STOP_HOTKEY_ID = obslua.obs_hotkey_register_frontend(script_path() .. "_stop_global_hotkey_1","[MOVA] Terminate program", program_stop)
local start_name_id=script_path() .. "_start_global_hotkey_1"
local stop_name_id=script_path() .. "_stop_global_hotkey_1"
local hotkey_start_arr_t= obs.utils.settings.get_arr(start_name_id)
local hotkey_stop_arr_t= obs.utils.settings.get_arr(stop_name_id)
if hotkey_start_arr_t and hotkey_start_arr_t.data then
obslua.obs_hotkey_load(__START_HOTKEY_ID, hotkey_start_arr_t.data)
hotkey_start_arr_t.free()
end
if hotkey_stop_arr_t and hotkey_stop_arr_t.data then
obslua.obs_hotkey_load(__STOP_HOTKEY_ID, hotkey_stop_arr_t.data)
hotkey_stop_arr_t.free()
end
-- [[]]
local video_list= obs.utils.settings.get_arr("video_list")
if not video_list or video_list.data == nil then
video_list= obs.utils.ArrayStack()
obs.utils.settings.arr("video_list", video_list.data)
end
video_list.free()
__SCENE= obs.scene.get_scene()
if obs.utils.settings.bul("isPressed") then
program_start(true)
end
end
function script_unload()
if __SCENE and __SCENE.data then
__SCENE.free()
end
end
function index()
return [[
Useful Links
Video Tutorial: click here
Ver. 1.2.0
Dev
Author: @iixisii
updates follow XDeviixisiiX
]]
end
--[[ FUNCTIONALITY ]]
function videoFilesVisible(bool, invalue)
if not invalue then
local video_list= obs.utils.settings.get_arr("video_list")
for video in video_list.next() do
local item= obs.script.get(video.str("id") .. "_group")
if item then
if bool then
item.show()
else
item.hide()
end
end
video.free()
end
return video_list.free()
else
local item= obs.script.get(invalue .. "_group")
if item then
if bool then
item.show()
else
item.hide()
end
end
end
end
local currentActionDisplayValue="";
function DisplayActionUpdate(action_value, properties_t)
if not properties_t or type(properties_t) ~= "userdata" or currentActionDisplayValue == action_value then
return nil
end
currentActionDisplayValue=action_value
local video_list= obs.utils.settings.get_arr("video_list")
for video in video_list.next() do
local item= obs.script.get(video.str("id") .. "_group")
if action_value == "" then
if item and item.data then
item.show()
else
add_to_video_list(video, properties_t)
end
elseif action_value == video.str("id") then
if item and item.data then
item.show()
else
add_to_video_list(video, properties_t);
end
else
if item and item.data ~= nil then
item.hide()
end
end
video.free()
end
video_list.free()
end
function add_to_video_list(video, properties_t)
if not properties_t or type(properties_t) ~= "userdata" then
return nil
end
local px= obs.script.create()
local unique_id= video.str("id")
local video_form= obs.script.group(properties_t, unique_id .. "_group","", px)
obs.script.label(px, unique_id .. "_title", "" .. video.str("title") .. "
")
obs.script.label(px, unique_id .. "_path", "" .. video.str("path") .. "
")
obs.script.label(px, unique_id .. "_id", "" .. video.str("id") .. "
")
obs.script.button(px, unique_id .. "_remove", "remove", function(xxx)
-- remove the video from the list
local video_list= obs.utils.settings.get_arr("video_list")
if video_list and video_list.data then
local video, index= video_list.find("id", unique_id)
if video and video.data then
video.free()
video_list.rm(index)
end
-- local hr_label=obs.script.get(properties_t, "hr_label")
-- hr_label.text([[
--
--
-- | ]]..tostring(video_list.size())..[[ |
--
|
--
--
-- ]])
end
video_form.remove()
local display_action= obs.script.get("display_action")
if display_action then
local cursor_option= display_action.cursor(unique_id)
if cursor_option and cursor_option.value == unique_id then
local current_option= display_action.current()
if current_option.value == unique_id then
obs.utils.settings.str("display_action", "")
end
cursor_option.remove()
end
if video_list.size() <= 0 then
local load_more= display_action.cursor("")
local none_option= display_action.cursor("")
local all_option= display_action.cursor("")
if all_option then
all_option.remove()
end
if none_option then
none_option.remove()
end
if load_more then
load_more.remove()
end
display_action.add.str("< No Videos Added >", "").cursor().disable()
end
end
video_list.free()
__DISPLAY_ACTION_CURRENT_INDEX= math.max(0, __DISPLAY_ACTION_CURRENT_INDEX-1)
if __DISPLAY_ACTION_CURRENT_INDEX <= __DISPLAY_ACTION_INDEX - __DISPLAY_ACTION_DEFAULT_INDEX then
__DISPLAY_ACTION_INDEX=math.max(__DISPLAY_ACTION_DEFAULT_INDEX, __DISPLAY_ACTION_INDEX - __DISPLAY_ACTION_DEFAULT_INDEX)
end
return true
end);
return video_form
end
local last_clock=os.clock()
function program_start(isPressed)
if not isPressed or program_running then return false end
obs.utils.settings.bul("isPressed", true)
program_running= true
local function video_state()
if not program_running then
-- pause the current video
local video_source_name= obs.utils.settings.str("vid_source_list")
if not scene or scene.data == nil then
program_running=false;obs.utils.settings.bul("isPressed", false)
return obslua.timer_remove(video_state)
end
local video_scene_item= scene.get(video_source_name)
if video_scene_item and video_scene_item.data then
local video_source= video_scene_item.get_source()
obslua.obs_source_media_play_pause(video_source, true)
video_scene_item.free()
end
return obslua.timer_remove(video_state)
end
local video_source_name= obs.utils.settings.str("vid_source_list")
if not __SCENE or not __SCENE.data then
__SCENE= obs.scene:get_scene()
-- program_running=false;obs.utils.settings.bul("isPressed", false)
return -- obslua.timer_remove(video_state)
end
local video_scene_item= __SCENE.get(video_source_name)
if not video_scene_item or video_scene_item.data == nil then
return obslua.timer_remove(video_state)
end
local video_source= video_scene_item.get_source()
local state= obslua.obs_source_media_get_state(video_source)
local video_current_time=obslua.obs_source_media_get_time(video_source)
local video_max_time=obslua.obs_source_media_get_duration(video_source)
if type(video_current_time) ~= "number" then
video_current_time=0
end
if type(video_max_time) ~= "number" then
video_max_time=0
end
if (state == obslua.OBS_MEDIA_STATE_PAUSED and current_video_source and current_video_path) or
(state == obslua.OBS_MEDIA_STATE_STOPPED and current_video_source and current_video_path) then
if program_running and isPressed and not __await_time_restart_video_callback then
__await_time_restart_video_callback= true
obs.utils.scheduler(5000).after(function()
__await_time_restart_video_callback=false
if program_running and isPressed and ((
state == obslua.OBS_MEDIA_STATE_PAUSED and current_video_source and current_video_path) or
(state == obslua.OBS_MEDIA_STATE_STOPPED and current_video_source and current_video_path)) then
return update_source_video_path(current_video_source, current_video_path)
end
end)
end
elseif (
state == obslua.OBS_MEDIA_STATE_ENDED or
state == obslua.OBS_MEDIA_STATE_ERROR or state == obslua.OBS_MEDIA_STATE_NONE)
or (
--[[(video_max_time > 0 and state == obslua.OBS_MEDIA_STATE_PLAYING) or]]
(video_current_time >= video_max_time) or
(video_current_time < 0)
) then
if os.clock() - last_clock <= 3 then
return
end
last_clock=os.clock()
local opera_= obs.utils.settings.str("path_view_list")
local video_path_value;local function set_next_index(min_value, max_value)
local select_randomly= obs.utils.settings.bul("shuffle_option")
if select_randomly then
-- only select a video that is not on the locally_viewed list randomly
local random_index= math.random(min_value, max_value)
if max_value > 1 then
while locally_viewed[random_index] do
random_index= math.random(min_value, max_value)
if table.count(locally_viewed) >= max_value then
locally_viewed={}
break
end
end
else
locally_viewed={}
end
video_index= random_index
locally_viewed[random_index]= true
if table.count(locally_viewed) >= max_value then
locally_viewed={}
end
else
video_index= (video_index + 1) % max_value
end
end
if opera_ == "" then
local video_list= obs.utils.settings.get_arr("video_list")
if video_list.size() <= 0 then
program_running=false
video_list.free();video_scene_item.free();
return obslua.timer_remove(video_state)
end
set_next_index(0, video_list.size()-1)
local vid= video_list.get(video_index)
if not vid or vid.data == nil then
video_list.free();video_scene_item.free();
program_running=false
return obslua.timer_remove(video_state)
end
video_path_value= vid.str("path")
vid.free();video_list.free();
elseif opera_ == "" then
local dirc_name=obs.utils.settings.str("fld_path_input")
local dirc= obslua.os_opendir(dirc_name)
local files={}
repeat
local centry= obslua.os_readdir(dirc)
if centry then
if centry.d_name ~= "." and centry.d_name ~= ".." and not centry.directory then
table.insert(files, dirc_name .. "/" .. centry.d_name)
end
end
until not centry
set_next_index(1, #files)
video_path_value= files[video_index]
obslua.os_closedir(dirc)
else
return -- handle error case!
end
current_video_path= video_path_value
local video_source_name= obs.utils.settings.str("vid_source_list")
current_video_source= video_source_name
update_source_video_path(video_source_name, video_path_value)
elseif state == obslua.OBS_MEDIA_STATE_PLAYING and current_video_source == "" and current_video_path == "" then
current_video_source=obs.utils.settings.str("vid_source_list")
local source_settings= obs.utils.PairStack(obslua.obs_source_get_settings(video_source))
current_video_path=source_settings.str("local_file")
source_settings.free()
end
video_scene_item.free();
return true
end
return obslua.timer_add(video_state, 1000)
end
function program_stop(isPressed)
if not isPressed then return false end
obs.utils.settings.bul("isPressed", false)
program_running=false
return true
end
function update_source_video_path(source_name, path)
local scene_item= __SCENE.get(source_name)
if not scene_item or scene_item.data == nil then
return false
end
local source= scene_item.get_source()
if not source then
scene_item.free();
return false
end
local source_id= obslua.obs_source_get_id(source)
if source_id ~= "ffmpeg_source" and source_id ~= "vlc_source" then
scene_item.free();
return false
end
local source_settings= obslua.obs_source_get_settings(source)
if not source_settings then
scene_item.free();
return false
end
obslua.obs_data_set_string(source_settings, "local_file", path)
obslua.obs_source_update(source, source_settings)
obslua.obs_data_release(source_settings)
obslua.obs_source_media_stop(source)
obslua.obs_source_media_restart(source)
--obslua.obs_sceneitem_set_visible(scene_item.data, true)
scene_item.free();
return true
end
-- [[ TABLE FUNCTIONS ]]
function table.count(t)
local count=0
for _ in pairs(t) do
count=count+1
end
return count
end
--[[
Author: iixisii
contact: @iixisii
]]
obs={utils={
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={};properties={
list={};options={};
};
};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
}
}};
bit= require('bit')
-- dkjson= require('dkjson')
math.randomseed(os.time())
-- schedule an event
scheduled_events = {}
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.utils.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;
free = function()
if self.type == obs.utils.OBS_SCENE_TYPE then
obslua.obs_scene_release(self.data)
elseif self.type == obs.utils.OBS_SRC_TYPE then
obslua.obs_source_release(self.data)
elseif self.type == obs.utils.OBS_ARR_TYPE then
obslua.obs_data_array_release(self.data)
elseif self.type == obs.utils.OBS_OBJ_TYPE then
obslua.obs_data_release(self.data)
elseif self.type == obs.utils.OBS_SCENEITEM_TYPE then
obslua.obs_sceneitem_release(self.data)
elseif self.type == obs.utils.OBS_SCENEITEM_LIST_TYPE then
obslua.sceneitem_list_release(self.data)
elseif self.type == obs.utils.OBS_SRC_LIST_TYPE then
obslua.source_list_release(self.data)
elseif self.type == obs.utils.OBS_UN_IN_TYPE then
self.data = nil;self.item=nil
return
else
self.data = nil
end
end
}
table.insert(error_wrapper, self)
return self
end
error_freed = 0
error_wrapper = {};function obs.utils.error_wrapper_handler (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)
if not success then
error_freed = 0
for _, iter in pairs(error_wrapper) do
if iter and type(iter.free) == "function" then
local s, r = pcall(function()
iter.free()
end)
if s then
error_freed = error_freed + 1
end
end
end
obslua.script_log(obslua.LOG_ERROR, "[ErrorWrapper ERROR] => " .. tostring(result))
end
return data
end
end
-- array handle
function obs.utils.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.utils.PairStack(obslua.obs_data_array_item(self.data, index), nil, true)
end;next = obs.utils.error_wrapper_handler(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.utils.PairStack(
obslua.obs_data_array_item(self.data, i), nil, false
))
end
end)
-- local temp = self.index;self.index = self.index + 1
-- return obs.utils.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.utils.error_wrapper_handler(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.utils.error_wrapper_handler(function()
if self.data == nil then
return 0
end
return obslua.obs_data_array_count(self.data);
end); rm= obs.utils.error_wrapper_handler(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(error_wrapper, self)
return self
end
-- pair stack used to manage memory stuff :)
function obs.utils.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.utils.error_wrapper_handler(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.utils.error_wrapper_handler(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.utils.error_wrapper_handler(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.utils.error_wrapper_handler(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.utils.error_wrapper_handler(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.utils.error_wrapper_handler(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.utils.error_wrapper_handler(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.utils.error_wrapper_handler(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.utils.error_wrapper_handler(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.utils.error_wrapper_handler(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.utils.PairStack(
obslua.obs_data_get_obj(self.data, name),nil, false
)
else
return obs.utils.PairStack(
obslua.obs_data_get_default_obj(self.data, name),nil, false
)
end
end);get_arr = obs.utils.error_wrapper_handler(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.utils.ArrayStack(
obslua.obs_data_get_array(self.data, name),nil, false
)
else
return obs.utils.ArrayStack(obslua.obs_data_get_default_array(self.data, name),nil, false)
end
end);get_bul = obs.utils.error_wrapper_handler(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.utils.error_wrapper_handler(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(error_wrapper, self)
return self
end
--[[ OBS 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.utils.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.utils.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.utils.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.utils.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.utils.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
}
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.utils.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.utils.PairStack(
obslua.obs_source_get_settings(source.get_source()),
nil,true
)
if not src or not src.data then
src= obs.utils.PairStack()
end
local font= src.get_obj("font")
if not font or not font.data then
font= obs.utils.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.utils.PairStack(
obslua.obs_source_get_settings(source.get_source()),
nil,true
)
if not src or not src.data then
src= obs.utils.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_info(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_info(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_info(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.utils.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.utils.wrap(obslua.obs_scene_get_group(scene, name), obs.utils.OBS_SCENEITEM_TYPE)
elseif gp ~= nil then
obj= obs.utils.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_width(source_scene)
end;get_height = function()
return obslua.obs_source_get_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.utils.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 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;
}
local obj_raw= obs.utils.obs_api_properties_patch(obj, p)
obj_raw.type= enum_format_id;
obs.utils.properties[unique_id]= obj_raw
return obj_raw
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.utils.settings)
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
-- [[ 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
};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.utils.error_wrapper_handler(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.utils.error_wrapper_handler(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;
ret=function()
return objOption
end
}
return nn_obj;
end;current=function()
local current_selected_option;
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.utils.settings)
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.utils.error_wrapper_handler(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.utils.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)
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= obs.utils.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= obs.utils.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= obs.utils.settings.int(pp_unique_name)
elseif property_type == obslua.OBS_PROPERTY_FLOAT then
current_value= obs.utils.settings.dbl(pp_unique_name)
elseif property_type == obslua.OBS_PROPERTY_LIST then
if objOption.type == obs.enum.options.string then
current_value= obs.utils.settings.str(pp_unique_name)
elseif objOption.type == obs.enum.options.int then
current_value= obs.utils.settings.int(pp_unique_name)
elseif objOption.type == obs.enum.options.float then
current_value= obs.utils.settings.dbl(pp_unique_name)
elseif objOption.type == obs.enum.options.bool then
current_value= obs.utils.settings.bul(pp_unique_name)
end
elseif property_type == obslua.OBS_PROPERTY_FONT then
current_value= obs.utils.settings.obj(pp_unique_name)
elseif property_type == obslua.OBS_PROPERTY_EDITABLE_LIST then
current_value= obs.utils.settings.arr(pp_unique_name)
end
for _, vclb in pairs(__onchange_list) do
vclb(current_value, property_t, properties_t, obs.utils.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
-- [[ OTHER UTILS ]]
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