--[[ Author: iixisii Date: 5.21.24 Ver. 1-alpha Description: OK ]] obs = obslua __setting__ = nil function welcomeIndex() return [[

upstream X

Ver. 1-alpha - Made by iixisii

You can learn more about this script by watching a tutorial video


]] end function script_description() return welcomeIndex() end function script_properties() local p = obs.obs_properties_create() local output_list = obs.obs_properties_add_list(p, "output-list", "output:", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING) obs.obs_property_list_add_string(output_list,"","stream"); obs.obs_property_list_add_string(output_list,"record","record"); obs.obs_property_list_add_string(output_list,"both (stream & record)","both"); local source_target_list = obs.obs_properties_add_list(p, "source-target", "uptime source:", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING) local info = obs.obs_properties_add_text(p, "info", "", obs.OBS_TEXT_INFO) obs.obs_property_set_visible(info, false) --obs.obs_property_list_add_string(source_target_list,"","def"); obs.obs_properties_add_text(p, "hr1", [[
]], obs.OBS_TEXT_INFO) local event_time = obs.obs_properties_add_text(p, "event-time", "event time:", obs.OBS_TEXT_DEFAULT); local event_source = obs.obs_properties_add_list(p, "event-source", "event source:", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING) --obs.obs_property_list_add_string(event_source,"","def"); show_sources(source_target_list, ""); show_sources(event_source, ""); -- execute local action_btn = obs.obs_properties_add_button(p, "execute","Execute", function(properties, prop, settings) local t =__setting__.get_str("event-time"); local s =__setting__.get_str("event-source") if s == nil or strTrim(s) == "" or t == nil or strTrim(t) == "" or s == "def" or s == "def2" then return end local timer,m = xparser(t); if(timer==nil) then return nil end local timer_obj = PairStack() for key, value in pairs(timer) do if type(value) == "number" then timer_obj.int(key, value) elseif type(value) == "boolean" then timer_obj.bul(key, value) else timer_obj.str(key, tostring(value)) end end local events = __setting__.get_arr(current_scene_name() .. "-event-list") if events == nil or events.data == nil then events = ArrayStack() __setting__.arr(current_scene_name() .. "-event-list", events.data) end timer_obj.str("source", s) timer_obj.bul("iscoolingdown",false) timer_obj.int("cooldown",5) timer_obj.str("output",__setting__.get_str("output-list")) timer_obj.bul("isActive",true) timer_obj.int("currTime",0) local src = obs_wrap_source(obs.obs_get_source_by_name(s), OBS_SRC_TYPE) if src and src.data ~= nil then if obs.obs_source_get_unversioned_id(src.data) == "ffmpeg_source" then timer_obj.str("type","video") else timer_obj.str("type","source") end src.free() else timer_obj.str("type","source") end events.insert(timer_obj.data) timer_obj.free() events.free() update_screen(properties) return true end) -- information obs.obs_properties_add_text(p, "hr2", [[
]], obs.OBS_TEXT_INFO) obs.obs_properties_add_text(p, "label", "DISPLAY & SCREEN " .. "(showing for " .. tostring(current_scene_name()) .. ")", obs.OBS_TEXT_INFO) obs.obs_properties_add_text(p, "screen", [[
No data ...
]], obs.OBS_TEXT_INFO) -- operation (delete stuff) obs.obs_properties_add_text(p, "hr3", [[
]], obs.OBS_TEXT_INFO) local delete_list = obs.obs_properties_add_list(p, "delete-list", "delete:", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING) show_events(delete_list, "") -- description for source-target obs.obs_property_set_long_description(source_target_list,"Select the source you want to show the uptime of the current stream.\nYou can select multiple sources, when selected it will display it down below! (do not have to execute to add the target)") -- desc for event time obs.obs_property_set_long_description(event_time,"(Optional)\nThe format is Hh:Mm:Ss stands for (hours, minutes, seconds)\nThe time of an event should execute. This by default is set to recursive, if you want it to execute the event one time only the after it insert (!)"); -- callback function obs.obs_property_set_modified_callback(source_target_list, function(properties, property, settings) local s = PairStack(settings, nil, true) if s == nil or s.data == nil then return end local source_name = s.get_str("source-target") if source_name == "" or source_name == nil or source_name == "def" or source_name == "def2" then return end local obj = PairStack() obj.str("name", source_name) local list = s.get_arr(current_scene_name() .. "-source-target-list") if list == nil or list.data == nil then list = ArrayStack(); s.arr(current_scene_name() .. "-source-target-list", list.data); end local info = obs.obs_properties_get(properties, "info") if list.size() <= 0 then list.insert(obj.data) obs.obs_property_set_visible(info, true) obs.obs_property_set_description(info, "New source added (" .. tostring(source_name) .. ")") else local is_already_added = false while(list.index < list.size()) do local data = list.next() local name = "" if type(data) == "table" then name = data.get_str("name") data.free() else name = data; end if name == source_name then is_already_added = true break end end -- add if not (is_already_added) if not is_already_added then list.insert(obj.data) obs.obs_property_set_visible(info, true) obs.obs_property_set_description(info, "New source added (" .. tostring(source_name) .. ")") else obs.obs_property_set_visible(info, false) end end --s.arr(current_scene_name() .. "-source-target-list", list.data); obj.free();list.free() s.str("source-target","def") update_screen(properties) return true end); obs.obs_property_set_modified_callback(delete_list, function(properties, property, settings) local delete_id = __setting__.get_str("delete-list"); if delete_id == nil or strTrim(delete_id) == "" or delete_id == "def" or delete_id == "def2" then return end local delete_index = nil local events = __setting__.get_arr(current_scene_name() .. "-event-list") if events ~= nil and events.data ~= nil then while(events.index < events.size()) do local curr = events.next() if curr ~= nil and curr.data ~= nil then local id = tostring(curr.get_str("source")) .. "-" .. tostring(curr.get_str("str")) curr.free() if id == delete_id then delete_index = events.index - 1 break end end end if delete_index ~= nil then events.remove(delete_index) --__setting__.arr(current_scene_name() .. "-event-list", events.data) else -- check sources local list = __setting__.get_arr(current_scene_name() .. "-source-target-list") if list and list.data ~= nil then while(list.index < list.size()) do local curr = list.next() if curr ~= nil and curr.data ~= nil then local id = tostring(curr.get_str("name")) curr.free() if id == delete_id then delete_index = list.index - 1 break end end end if delete_index ~= nil then list.remove(delete_index) end list.free() end end events.free() end local delete_list = obs.obs_properties_get(properties, "delete-list") __setting__.str("delete-list","def") show_events(delete_list,"") update_screen(properties) return true end); update_screen(p) return p end function script_defaults(settings) __setting__ = PairStack(settings, nil, true) __setting__.str("event-time","",true) __setting__.str("event-source","def",true) __setting__.str("source-target","def",true) end function script_load(settings) __setting__ = PairStack(settings, nil, true) if __setting__.get_int("recordTime") == nil then __setting__.int("recordTime", 0) end if __setting__.get_int("streamTime") == nil then __setting__.int("streamTime", 0) end 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.bul("iscoolingdown", false) curr.free() end end if events and events.data ~= nil then events.free() end obs.obs_frontend_add_event_callback(frontend_event_handle) end function splitBy(string_value, op) if not string_value then return nil end local pipes = {} local whole = "" for char in string.gmatch(string_value,".") do if op == char then if whole ~= "" then table.insert(pipes, whole) end whole = "" else whole = whole .. char end end if whole ~= "" then table.insert(pipes, whole) end return pipes; end function xparser(str) local result = {} local sl = splitBy(str,":") if sl == nil or #sl <= 2 or type(tonumber(sl[1])) ~= "number" or type(tonumber(sl[2])) ~= "number" then return nil, "Invalid time format" end local h,m, s = str:match("(%d+):(%d+):(%d+)") if (h and m and s) then result.hh = tonumber(h) * 3600000 -- 1 hour = 3600000 milliseconds result.mm = tonumber(m) * 60000 -- 1 minute = 60000 milliseconds result.ss = tonumber(s) * 1000 -- 1 second = 1000 milliseconds result.h = tonumber(h) result.m = tonumber(m) result.s = tonumber(s) if result.h <= 0 and result.m <= 0 and result.s <= 0 then return nil, "Invalid time format" end result.time = (result.h * 3600) + (result.m * 60) + result.s; else return nil, "Invalid time format" end if str:find("!") then result.cr = false else result.cr = true end result.str = str return result end function fixLog(iter, ib,idn) local v = "" if type(iter) == "table" then for x, y in pairs(iter) do if type(x) == "string" then v = v .. tostring(x) .. ": " .. fixLog(y, ib,idn) .. " " elseif ib then v = v .. tostring(x) .. ": " .. fixLog(y,ib,idn) .. " " else v = v .. fixLog(y) .. " " end end return "{" .. v .. "}" elseif type(iter) == "function" then return "(function)"; else return tostring(iter) end end function current_scene_name() -- get the current scene name; local f_obj = obs.obs_frontend_get_current_scene() local name = obs.obs_source_get_name(f_obj) obs.obs_source_release(f_obj); if name == nil then name = "" end return name end -- returns an array that holds all the sources(items/objects) in the scene! function get_all_current_sources_from_scene(ignoreSource) local source_name_list = nil -- Get the current scene local currentSource = obs.obs_frontend_get_current_scene() local currentScene = obs.obs_scene_from_source(currentSource) 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 source_name_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 local __ls = obs.obs_sceneitem_group_enum_items(sceneItem) if __ls ~= nil then -- iterate through all the items in the group; 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 not ignoreSource then table.insert(source_name_list, sN) elseif ignoreSource ~= sN then table.insert(source_name_list, sN) end end end obs.sceneitem_list_release(__ls) end end if not ignoreSource then table.insert(source_name_list, sourceName) elseif ignoreSource ~= sourceName then table.insert(source_name_list, sourceName) end end end end -- Release the scene items list obs.sceneitem_list_release(sceneItems) end -- Release the current scene source obs.obs_source_release(currentSource) return source_name_list end -- show all the events in a list function show_events(prop, def) if __setting__ == nil or __setting__.data == nil then return end if not def then def = "Select options" end obs.obs_property_list_clear(prop) obs.obs_property_list_add_string(prop, def, "def") local events = __setting__.get_arr(current_scene_name() .. "-event-list") local c = 0 if events and events.data ~= nil then while(events.index < events.size()) do local _next = events.next() if _next ~= nil and _next.data ~= nil then local str = _next.get_str("str") local source_name = _next.get_str('source') obs.obs_property_list_add_string(prop, tostring(str) .. " | " .. tostring(source_name), tostring(source_name) .. "-" .. tostring(str)) _next.free() c = c + 1 end end events.free() end local list = __setting__.get_arr(current_scene_name() .. "-source-target-list") if list and list.data ~= nil then while(list.index < list.size()) do local _next = list.next() if _next ~= nil and _next.data ~= nil then local str = _next.get_str("str") local source_name = _next.get_str('name') obs.obs_property_list_add_string(prop, "(Source) " .. tostring(source_name), source_name) _next.free() c = c + 1 end end list.free() end if c <= 0 then obs.obs_property_list_add_string(prop, "(NO DATA ARE AVALIABLE)", "def2") end return true end function show_sources(prop, def) if not def then def = "Select options" end obs.obs_property_list_clear(prop) obs.obs_property_list_add_string(prop, def, "def") -- local scene_source = obs_wrap_source(obs.obs_frontend_get_current_scene(), OBS_SRC_TYPE) local sources_names = get_all_current_sources_from_scene() for _, name in pairs(sources_names) do obs.obs_property_list_add_string(prop, name, name); end if #sources_names <= 0 then obs.obs_property_list_add_string(prop, "(NO SOURCES ARE AVALIABLE)", "def2") end return true end function strTrim(str) if not str then return "" end return str:gsub("^%s*(.-)%s*$", "%1"); end -- draws the screen function update_screen(properties) if properties == nil then return end if __setting__ == nil or __setting__.data == nil then return end local screen = obs.obs_properties_get(properties, "screen") local desc = ""; local list = __setting__.get_arr(current_scene_name() .. "-source-target-list") local events = __setting__.get_arr(current_scene_name() .. "-event-list") -- show all the events if events ~= nil and events.data ~= nil then while(events.index < events.size()) do local _next = events.next() if _next ~= nil and _next.data ~= nil then local source_name = _next.get_str("source") local h = _next.get_int("h") local m = _next.get_int("m") local s = _next.get_int("s") local cr = _next.get_bul("cr") desc = desc .. [[ (Event) ]] if cr == true then desc = desc .. [[ At every: ]] else desc = desc .. [[ At: ]] end local _t = "" if h > 0 and h > 1 then _t = tostring(h) .. " hours " elseif h == 1 then _t = tostring(h) .. " hour " end if m > 0 and m > 1 then if strTrim(_t) ~= "" then _t = _t .. ", and " .. tostring(m) .. " minutes " else _t = tostring(m) .. " minutes " end elseif m == 1 then if strTrim(_t) ~= "" then _t = _t .. ", and " .. tostring(m) .. " minute " else _t = tostring(m) .. " minute " end end if s > 0 and s > 1 then if strTrim(_t) ~= "" then _t = _t .. "and " .. tostring(s) .. " seconds " else _t = tostring(s) .. " seconds " end elseif s == 1 then if strTrim(_t) ~= "" then _t = _t .. "and " .. tostring(s) .. " second " else _t = tostring(s) .. " second " end end local act = ""; local _source = obs_wrap_source(obs.obs_get_source_by_name(source_name), OBS_SRC_TYPE) local source_id = obs.obs_source_get_unversioned_id(_source.data) if source_id == "ffmpeg_source" then act = "play" else act = "show" end desc = desc .."" .. _t .. " "; desc = desc .. "Do: " .. act .. " " desc = desc .."For: " .. tostring(source_name) .. '
'; _source.free() _next.free() end end end -- show all the added source targets if list ~= nil and list.data ~= nil then while(list.index < list.size()) do local data = list.next() local name = "" if type(data) == "table" then name = data.get_str("name") data.free() else name = data; end desc = desc .. [[ (SOURCE) ]] .. tostring(name) .. [[ ]] .. "
" end list.free() end if strTrim(desc) == "" then obs.obs_property_set_description(screen,"
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)