--[[ Author: iixisii Date: 5.21.24 Ver. 1-alpha Description: OK ]] obs = obslua __setting__ = nil function welcomeIndex() return [[
You can learn more about this script by watching a tutorial video
No data ...]], obs.OBS_TEXT_INFO) -- operation (delete stuff) obs.obs_properties_add_text(p, "hr3", [[
No data ...") else obs.obs_property_set_description(screen, desc) end return true end scheduled_events = {} function 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() obs.timer_remove(interval) if type(scheduler_callback) ~= "function" then return end return 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 obs.script_log(obslua.LOG_ERROR, "[Scheduler] invalid callback/timeout " .. type(callback)) return false end obs.timer_add(interval, timeout) end;push = function(callback) if callback == nil or type(callback) ~= "function" then obs.script_log(obslua.LOG_WARNING, "[Scheduler] invalid callback at {push} " .. type(callback)) return false end obs.timer_add(callback, timeout) table.insert(scheduled_events, callback) return { clear = function() if callback == nil or type(callback) ~= "function" then return nil end return obs.timer_remove(callback) end; } end; clear = function() if scheduler_callback ~= nil then obs.timer_remove(scheduler_callback) end for _, clb in pairs(scheduled_events) do obs.timer_remove(clb) end scheduled_events = {}; scheduler_callback = nil end } return self end 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 obs_wrap_source = {}; function obs_wrap_source(object, object_type) local self = nil self = { type = object_type, data = object;free = function() if self.type == OBS_SCENE_TYPE then obs.obs_scene_release(self.data) elseif self.type == OBS_SRC_TYPE then obs.obs_source_release(self.data) elseif self.type == OBS_ARR_TYPE then obs.obs_data_array_release(self.data) elseif self.type == OBS_OBJ_TYPE then obs.obs_data_release(self.data) elseif self.type == OBS_SCENEITEM_TYPE then obs.obs_sceneitem_release(self.data) elseif self.type == OBS_SCENEITEM_LIST_TYPE then obs.sceneitem_list_release(self.data) elseif self.type == OBS_SRC_LIST_TYPE then obs.source_list_release(self.data) elseif self.type == OBS_UN_IN_TYPE then self.data = nil return else self.data = nil end end } table.insert(error_wrapper, self) return self end error_freed = 0 error_wrapper = {};function 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 obs.script_log(obs.LOG_ERROR, "[ErrorWrapper ERROR] => " .. tostring(result)) end return data end end -- array handle function ArrayStack(stack, name, ignoreStack) if not ignoreStack then if type(stack) ~= "userdata" then stack = nil elseif stack and (type(name) ~= "string" or name == "") then stack = nil obs.script_log(obs.LOG_ERROR, "FAILED TO LOAD AN [ArrayStack] INVALID NAME GIVEN") return nil end end local self = nil self = { index = 0;get = function(index) if type(index) ~= "number" or index < 0 then return nil end if index > self.size() then return nil end return obs_wrap_source(obs.obs_data_array_item(self.data, index),OBS_OBJ_TYPE) end;next = function() if self.data == nil then return nil end if type(self.index) ~= "number" or self.index < 0 or self.index > self.size() then return nil end local temp = self.index;self.index = self.index + 1 return PairStack(obs.obs_data_array_item(self.data, temp), nil, true) end;get = function(index) if self.data == nil then return nil end if type(index) ~= "number" or index< 0 or index > self.size() then return nil end return PairStack(obs.obs_data_array_item(self.data, index), nil, true) end;free = function() if self.data == nil then return false end obs.obs_data_array_release(self.data) self.data = nil return true end;insert = error_wrapper_handler(function(value) if self.data == nil then return false end if value == nil or type(value) ~= "userdata" then obs.script_log("FAILED TO INSERT OBJECT INTO [ArrayStack]") return false end obs.obs_data_array_push_back(self.data, value) return true end); size = error_wrapper_handler(function() if self.data == nil then return 0 end return obs.obs_data_array_count(self.data); end);remove = error_wrapper_handler(function(index) if self.data == nil or self.size() <= 0 or type(index) ~= "number" or self.size() < index or index < 0 then return false end obs.obs_data_array_erase(self.data, index) return true end); } if not ignoreStack then if stack and name then self.data = obs.obs_data_get_array(stack, name) else self.data = obs.obs_data_array_create() end else self.data = stack end table.insert(error_wrapper, self) return self end -- pair stack used to manage memory stuff :) function PairStack(stack, name, ignoreStack) if not ignoreStack then if type(stack) ~= "userdata" then stack = nil elseif stack and (type(name) ~= "string" or name == "")then stack = nil obs.script_log(obs.LOG_ERROR, "FAILED TO LOAD AN [PairStack] INVALID NAME GIVEN") return nil end end local self = nil; self = { free = function() if self.data == nil then return false end obs.obs_data_release(self.data) self.data = nil return true end; str = error_wrapper_handler(function(name, value, def) if self.data == nil then return false 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 obs.script_log(obs.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 obs.obs_data_set_default_string(self.data, name, value) else obs.obs_data_set_string(self.data, name, value) end return true end);int = error_wrapper_handler(function(name, value, def) 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 obs.script_log(obs.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 obs.obs_data_set_default_int(self.data, name, value) else obs.obs_data_set_int(self.data, name, value) end return true end);dbl=error_wrapper_handler(function(name, value, def) 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 obs.script_log(obs.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 obs.obs_data_set_default_double(self.data, name, value) else obs.obs_data_set_double(self.data, name, value) end return true end);bul = error_wrapper_handler(function(name, value, def) 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 obs.script_log(obs.LOG_ERROR,"FAILED TO INSERT BUL [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value))) return false end if def then obs.obs_data_set_default_bool(self.data, name, value) else obs.obs_data_set_bool(self.data, name, value) end return true end); arr = error_wrapper_handler(function(name, value, def) if (name == nil or type(name) ~= "string" or name == "") or (self.data == nil or type(self.data) ~= "userdata") or (type(value) ~="userdata") then obs.script_log(obs.LOG_ERROR,"FAILED TO INSERT ARR INTO [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value))) return false end if def then obs.obs_data_set_default_array(self.data, name, value) else obs.obs_data_set_array(self.data, name, value) end return true end); obj = error_wrapper_handler(function(name, value, def) if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata") or (type(value) ~="userdata") then obs.script_log(obs.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 obs.obs_data_set_default_obj(self.data, name, value) else obs.obs_data_set_obj(self.data, name, value) end return true end); -- getter get_str = error_wrapper_handler(function(name, def) if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata")then obs.script_log(obs.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 obs.obs_data_get_string(self.data, name) else return obs.obs_data_get_default_string(self.data, name) end end);get_int = error_wrapper_handler(function(name, def) if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata")then obs.script_log(obs.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 obs.obs_data_get_int(self.data, name) else return obs.obs_data_get_default_int(self.data, name) end end);get_dbl = error_wrapper_handler(function(name, def) if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata")then obs.script_log(obs.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 obs.obs_data_get_double(self.data, name) else return obs.obs_data_get_default_double(self.data, name) end end);get_obj = error_wrapper_handler(function(name, def) if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata")then obs.script_log(obs.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 PairStack(obs.obs_data_get_obj(self.data, name), nil, true) else return PairStack(obs.obs_data_get_default_obj(self.data, name), nil, true) end end);get_arr =error_wrapper_handler(function(name, def) if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata")then obs.script_log(obs.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 ArrayStack(obs.obs_data_get_array(self.data, name), nil, true) else return ArrayStack(obs.obs_data_get_default_array(self.data, name), nil, true) end end);get_bul = error_wrapper_handler(function(name, def) if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata") then obs.script_log(obs.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 obs.obs_data_get_bool(self.data, name) else return obs.obs_data_get_default_bool(self.data, name) end end); } if not ignoreStack then if stack and name then self.data = obs.obs_data_get_obj(stack, name) else self.data = obs.obs_data_create() end else self.data = stack end table.insert(error_wrapper, self) return self end __LIST_SCENE_ITEMS__ = {} function __GroupList() -- Get the current scene local currentSource = obs.obs_frontend_get_current_scene() local currentScene = obs.obs_scene_from_source(currentSource) -- local list; if currentScene ~= nil and currentSource ~= nil then -- Enumerate the items in the current scene local sceneItems = obs.obs_scene_enum_items(currentScene) if sceneItems ~= nil then list = {} for _, sceneItem in ipairs(sceneItems) do local source = obs.obs_sceneitem_get_source(sceneItem) if source ~= nil then local sourceName = obs.obs_source_get_name(source) if obs.obs_sceneitem_is_group(sceneItem) then table.insert(list, sourceName) end end end obs.sceneitem_list_release(sceneItems) end end obs.obs_source_release(currentSource) return list; end function __GET_SCENE_ITEM__(item_name) local sourceObject = obs.obs_get_source_by_name(item_name) local currentSource = obs.obs_frontend_get_current_scene() local currentScene = obs.obs_scene_from_source(currentSource) if currentScene ~= nil then local scene_item = obs.obs_scene_sceneitem_from_source(currentScene, sourceObject) -- check in groups if the current item doesn't exist; if not scene_item then for _, gN in ipairs(__GroupList()) do local groupSource = obs.obs_get_source_by_name(gN) if groupSource then local groupItem = obs.obs_scene_sceneitem_from_source(currentScene, groupSource) obs.obs_source_release(groupSource) if groupItem then -- iterate through the items in the group and check for (item_name); local hasItem = false local __ls = obs.obs_sceneitem_group_enum_items(groupItem) if __ls ~= nil then for _, it in ipairs(__ls) do local s = obs.obs_sceneitem_get_source(it) if s ~= nil then local sN = obs.obs_source_get_name(s) if sN == item_name then obs.obs_sceneitem_addref(it) scene_item = it hasItem = true; break end end end obs.sceneitem_list_release(__ls) end obs.obs_sceneitem_release(groupItem) if hasItem then break end end end end end obs.obs_source_release(currentSource) obs.obs_source_release(sourceObject) local item_obj = { index = (#__LIST_SCENE_ITEMS__) + 1; data = scene_item } item_obj["free"] = function() if item_obj.data ~= nil then obs.obs_sceneitem_release(item_obj.data) item_obj.data = nil table.remove(__LIST_SCENE_ITEMS__, item_obj.index) return true end table.remove(__LIST_SCENE_ITEMS__, item_obj.index) return false end table.insert(__LIST_SCENE_ITEMS__, item_obj) return __LIST_SCENE_ITEMS__[#__LIST_SCENE_ITEMS__] end obs.obs_source_release(currentSource) obs.obs_source_release(sourceObject) return nil end function format_time(seconds) -- Calculate days, hours, minutes, and remaining seconds local days = math.floor(seconds / (24 * 3600)) seconds = seconds % (24 * 3600) local hours = math.floor(seconds / 3600) seconds = seconds % 3600 local minutes = math.floor(seconds / 60) seconds = seconds % 60 -- Build the formatted string local formatted_time = "" if days > 0 then formatted_time = formatted_time .. days .. " day" .. (days > 1 and "s and " or " and ") end formatted_time = formatted_time .. string.format("%02d:%02d:%02d", hours, minutes, seconds) return formatted_time end -- listen to events from the frontend function frontend_event_handle(id, caller) if id == obs.OBS_FRONTEND_EVENT_RECORDING_STARTING or id == obs.OBS_FRONTEND_EVENT_RECORDING_STOPPED then __setting__.int("recordTime", 0) local events;if(__setting__) then events = __setting__.get_arr(current_scene_name() .. "-event-list") end while(events and events.data ~= nil and events.index < events.size()) do local curr = events.next() if curr and curr.data ~= nil then curr.int("currTime",0) curr.bul("iscoolingdown", false) curr.free() end end if events and events.data ~= nil then events.free() end elseif id == obs.OBS_FRONTEND_EVENT_STREAMING_STARTING or id == obs.OBS_FRONTEND_EVENT_STREAMING_STOPPING then __setting__.int("streamTime", 0) local events;if(__setting__) then events = __setting__.get_arr(current_scene_name() .. "-event-list") end while(events and events.data ~= nil and events.index < events.size()) do local curr = events.next() if curr and curr.data ~= nil then curr.int("currTime",0) curr.bul("iscoolingdown", false) curr.free() end end if events and events.data ~= nil then events.free() end -- exit elseif id == obs.OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN then local events;if(__setting__) then events = __setting__.get_arr(current_scene_name() .. "-event-list") end while(events and events.data ~= nil and events.index < events.size()) do local curr = events.next() if curr and curr.data ~= nil then curr.int("currTime",0) curr.bul("isActive", true) curr.bul("iscoolingdown", false) curr.free() end end if events and events.data ~= nil then events.free() end end end --- main do stuff function main() local is_recording = obs.obs_frontend_recording_active() local is_streaming = obs.obs_frontend_streaming_active() local sources;if(__setting__) then sources = __setting__.get_arr(current_scene_name() .. "-source-target-list") end if is_streaming then if __setting__ and __setting__.get_int("streamTime") ~= nil then __setting__.int("streamTime", __setting__.get_int("streamTime") + 1) end if sources ~= nil and sources.data ~= nil and sources.size() > 0 then local labelobj = sources.next() if labelobj and labelobj.data ~= nil then local source_obj = obs_wrap_source(obs.obs_get_source_by_name(labelobj.get_str("name")), OBS_SRC_TYPE) if source_obj and source_obj.data ~= nil then local update_text = PairStack() update_text.str("text", format_time(__setting__.get_int("streamTime"))) obs.obs_source_update(source_obj.data, update_text.data); update_text.free() source_obj.free() end labelobj.free() end end end if is_recording then if __setting__ and __setting__.get_int("recordTime") ~= nil then __setting__.int("recordTime", __setting__.get_int("recordTime") + 1) end if sources ~= nil and sources.data ~= nil and sources.size() > 0 then local labelobj = sources.next() if labelobj and labelobj.data ~= nil then local source_obj = obs_wrap_source(obs.obs_get_source_by_name(labelobj.get_str("name")), OBS_SRC_TYPE) if source_obj and source_obj.data ~= nil then local update_text = PairStack() update_text.str("text", format_time(__setting__.get_int("recordTime"))) obs.obs_source_update(source_obj.data, update_text.data); update_text.free() source_obj.free() end labelobj.free() end end end if sources and sources.data ~= nil then sources.free() end if is_recording or is_streaming then local events;if(__setting__) then events = __setting__.get_arr(current_scene_name() .. "-event-list") end while(events and events.data ~= nil and events.index < events.size()) do local curr = events.next() if curr and curr.data ~= nil and curr.get_bul("isActive") == true and curr.get_bul("iscoolingdown") == false then curr.int("currTime",curr.get_int("currTime")+1) local curr_time = curr.get_int("currTime") if curr.get_int("time") and curr_time >= curr.get_int("time") then if(not curr.get_bul("cr") == true) then curr.bul("isActive", false) end curr.int("currTime", 0) -- execute event local source_name = curr.get_str("source") local _sceneitem = __GET_SCENE_ITEM__(source_name) if _sceneitem and _sceneitem.data ~= nil then obs.obs_source_set_enabled(obs.obs_sceneitem_get_source(_sceneitem.data), true) obs.obs_sceneitem_set_visible(_sceneitem.data, true) if curr.get_str("type") == "video" then obs.obs_source_media_restart(obs.obs_sceneitem_get_source(_sceneitem.data)) obs.obs_source_media_play_pause(obs.obs_sceneitem_get_source(_sceneitem.data), true) end _sceneitem.free() end curr.bul("iscoolingdown",true) local curr_index = events.index - 1 -- reset cooldown scheduler(curr.get_int("cooldown") * 1000).after(function() local events = __setting__.get_arr(current_scene_name() .. "-event-list") if events and events.data ~= nil then local iter = events.get(curr_index) if iter and iter.data ~= nil then local source_name = iter.get_str("source") local _sceneitem = __GET_SCENE_ITEM__(source_name) if _sceneitem and _sceneitem.data ~= nil then if iter.get_str("type") == "video" then obs.obs_source_media_play_pause(obs.obs_sceneitem_get_source(_sceneitem.data), false) obs.obs_source_media_restart(obs.obs_sceneitem_get_source(_sceneitem.data)) end obs.obs_sceneitem_set_visible(_sceneitem.data, false) _sceneitem.free() end iter.bul("iscoolingdown", false) iter.free() end events.free() end end) end curr.free() end end if events and events.data ~= nil then events.free() end end end obs.timer_add(main, 1000)