--[[ Author: iixisii contact: @iixisii ver: 1.2.0 | 12.17.2025 ]] local __global_current=nil;local isFreeroaming=nil function setup(settings) isFreeroaming= obs.utils.settings.bul("ck5C3N3_freeroame_value") or false switch_to_scene() obs.register.hotkey("ck5C3N3_freerome", "[CK5C3N3 FREE-ROAMING]", function(dv) if not dv then return end --__global_current=nil isFreeroaming= obs.utils.settings.bul("ck5C3N3_freeroame_value") isFreeroaming= not isFreeroaming obs.utils.settings.bul("ck5C3N3_freeroame_value", isFreeroaming) init_blackscreen() -- if isFreeroaming then -- show_free_roaming_message("[CK5C3N3] FREE-ROAMING MODE ACTIVE", true) -- else -- show_free_roaming_message("[CK5C3N3] FREE-ROAMING MODE DEACTIVATED", false) -- end return true end) obs.register.hotkey("ck5C3N3_lock","[CK5C3N3 LOCK]", function(dvalue) if not dvalue then return end local ck5= obs.shared.api("ck5c3n3-scenes-api") local current_scene_name= obs.scene:name() if ck5 and ck5.data and ck5.size() > 0 then -- make sure the current scene is not already saved! local it= ck5.find("scene_name", current_scene_name) if it then it.free();ck5.free() return false end end local it= obs.PairStack() it.str("scene_name", current_scene_name) ck5.insert(it.data);ck5.save();it.free();ck5.free() __global_current=current_scene_name init_blackscreen() return true end) obs.register.hotkey("ck5C3N3_unlock","[CK5C3N3 UNLOCK]", function(dvalue) if not dvalue then return end local ck5= obs.shared.api("ck5c3n3-scenes-api") local current_scene_name= obs.scene:name() local it, idx= ck5.find("scene_name", current_scene_name) if it then local a_scene= obs.scene:get_scene() local _blackscreen= a_scene.get_label("ck5C3N3_" .. current_scene_name .. "_blackscreen") if _blackscreen and _blackscreen.data then _blackscreen.remove() end a_scene.free() it.free();ck5.rm(idx);ck5.save();ck5.free();init_blackscreen();set_default_blk(); return true end ck5.free() __global_current=nil;switch_to_scene() return false end) obs.register.event("ck5c3n3-event", function(dvalue) if dvalue == obslua.OBS_FRONTEND_EVENT_SCENE_CHANGED then switch_to_scene() end end) init_blackscreen() end function init_blackscreen() local ck5= obs.shared.api("ck5c3n3-scenes-api") if not ck5 or not ck5.data or ck5.size() <= 0 then if ck5 then ck5.free() end for _, scene_name in pairs(obs.scene:names()) do local a_scene= obs.scene:get_scene(scene_name) if a_scene and a_scene.data then local _blackscreen= a_scene.get_label("ck5C3N3_" .. scene_name .. "_blackscreen") if _blackscreen and _blackscreen.data then obslua.obs_sceneitem_set_visible(_blackscreen.data, false) _blackscreen.free() end a_scene.free() end end return end ck5.free() for _, scene_name in pairs(obs.scene:names()) do local unique_id= "ck5C3N3_" .. scene_name .. "_blackscreen" local a_scene= obs.scene:get_scene(scene_name) if a_scene and a_scene.data then local _blackscreen= a_scene.get_label(unique_id) if not _blackscreen or not _blackscreen.data then _blackscreen= a_scene.add_label(unique_id) end _blackscreen.text("") local s= obs.PairStack();s.int("color", 0).int("opacity", 100).int("bk_opacity",100).int("bk_color", 0) local src= obs.front.source(unique_id) obslua.obs_source_update(src.data, s.data) s.free() obslua.obs_sceneitem_set_order(_blackscreen.data, obslua.OBS_ORDER_MOVE_TOP) obslua.obs_sceneitem_set_visible(_blackscreen.data, true) obslua.obs_sceneitem_set_bounds_type(_blackscreen.data, obslua.OBS_BOUNDS_STRETCH) local vec2= obslua.vec2() vec2.x= a_scene.get_width() vec2.y= a_scene.get_height() obslua.obs_sceneitem_set_bounds(_blackscreen.data, vec2) vec2=nil if __global_current == scene_name or isFreeroaming then obslua.obs_sceneitem_set_visible(_blackscreen.data, false) end src.free() _blackscreen.free() a_scene.free() end end end function switch_to_scene() if isFreeroaming then return false end local ck5= obs.shared.api("ck5c3n3-scenes-api") local current_scene_name= obs.scene:name() local iter= ck5.find("scene_name", current_scene_name) if iter and iter.data then iter.free();ck5.free() __global_current= current_scene_name init_blackscreen() return end local function default() local citer= ck5.get(0) if not citer or not citer.data then __global_current=nil return false end __global_current= citer.str("scene_name") citer.free() return true end if not __global_current or __global_current == "" then default() else local citer= ck5.find("scene_name", __global_current) if not citer or not citer.data then default() else citer.free() end end if __global_current then local source_scene= obs.front.source(__global_current) if source_scene and source_scene.data then obslua.obs_frontend_set_current_scene(source_scene.data) source_scene.free() end end ck5.free() init_blackscreen() return true end local flicker_caller= nil function do_flicker() if flicker_caller then obslua.timer_remove(flicker_caller) flicker_caller= nil end local flicker_limit = 4 local __calback_back__ = nil flicker_caller= function() if flicker_caller== nil then return end if flicker_limit <= 0 then obslua.timer_remove(flicker_caller) if type(__calback_back__) == "function" then __calback_back__() end flicker_caller=nil return end local scene= obs.scene:get_scene() local src= scene.get("ck5c3n3_free_roaming_label") if not src or not src.data then scene.free() obslua.timer_remove(flicker_caller) flicker_caller=nil return end local hidden = obslua.obs_sceneitem_visible(src.data) hidden= not hidden obslua.obs_sceneitem_set_visible(src.data, hidden) flicker_limit = flicker_limit - 1 if flicker_limit <= 0 then obslua.timer_remove(flicker_caller) if type(__calback_back__) == "function" then __calback_back__() end flicker_caller=nil obslua.obs_sceneitem_set_visible(src.data, false) end scene.free();src.free() end obslua.timer_add(flicker_caller, 500) return { after = function(callback) __calback_back__ = callback end;returnValue = true } end function script_properties() local p= obs.script.create() obs.script.label(p, nil, Topbar()) obs.script.label(p, nil, [[
...") local ck5= obs.shared.api("ck5c3n3-scenes-api") if ck5 and ck5.data and ck5.size() > 0 then l.hide() for iter in ck5.next() do local pp= obs.script.create() local view_scene= iter.str("scene_name") local gn= obs.script.group(p, view_scene .. "_gp", view_scene .. " ( LOCKED )", pp) local view= obs.script.button(pp).text("VIEW") view.click(function() local src= obs.front.source(view_scene) if src then obslua.obs_frontend_set_current_scene(src.data) src.free() end return true end) obs.script.button(pp, nil, "UNLOCK",function() local ck5= obs.shared.api("ck5c3n3-scenes-api") local it, idx= ck5.find("scene_name", view_scene) if it then it.free();ck5.rm(idx);ck5.save() local gp= obs.script.get(view_scene .. "_gp") if gp then gp.remove() end local a_scene= obs.scene:get_scene(view_scene) local _blackscreen= a_scene.get_label("ck5C3N3_" .. view_scene .. "_blackscreen") if _blackscreen and _blackscreen.data then _blackscreen.remove() end a_scene.free() __global_current=nil set_default_blk() switch_to_scene() end ck5.free() return true end) iter.free() end end obs.script.label(p, nil, Bottombar()) obs.script.label(p, nil, "").warn( [[
Go To Settings To Set Up The Hotkeys!
| CK5C3N3 - ver.1.1.0 | by iixisii |
How To use
To to switch a different scene make sure to first unlock the currently locked scene.]] end function script_description() return "" end -- [[ OBS CUSTOM API BEGIN ]] -- [[ OBS CUSTOM CALLBACKS ]] function script_load(settings) obs.script_shutdown=false settings= obs.PairStack(settings) obs.utils.settings= settings if setup and type(setup) == "function" then setup(settings) end for _, filter in pairs(obs.utils.filters) do obslua.obs_register_source(filter) end end function script_save(settings) -- [[ OBS REGISTER HOTKEY SAVE DATA]] for name, iter in pairs(obs.register.hotkey_id_list) do local new_data= obslua.obs_hotkey_save(iter.id) if new_data then obs.utils.settings.arr(name, new_data) obslua.obs_data_array_release(new_data) end end -- [[ OBS REGISTER HOTKEY SAVE DATA END]] end function script_unload() obs.utils.script_shutdown=true end -- [[ OBS CUSTOM CALLBACKS END ]] obs={utils={script_shutdown=false, OBS_SCENEITEM_TYPE = 1;OBS_SRC_TYPE = 2;OBS_OBJ_TYPE = 3; OBS_ARR_TYPE = 4;OBS_SCENE_TYPE = 5;OBS_SCENEITEM_LIST_TYPE = 6; OBS_SRC_LIST_TYPE = 7;OBS_UN_IN_TYPE = -1;table={}; expect_wrapper={}, properties={ list={};options={}; };filters={}};scene={};client={};mem={};script={},enum={ path={ read=obslua.OBS_PATH_FILE;write=obslua.OBS_PATH_FILE_SAVE;folder=obslua.OBS_PATH_DIRECTORY }; button={ default=obslua.OBS_BUTTON_DEFAULT;url=obslua.OBS_BUTTON_URL; };list={ string=obslua.OBS_EDITABLE_LIST_TYPE_STRINGS; url=obslua.OBS_EDITABLE_LIST_TYPE_FILES_AND_URLS; file=obslua.OBS_EDITABLE_LIST_TYPE_FILES }; text={ error=obslua.OBS_TEXT_INFO_ERROR; default=obslua.OBS_TEXT_INFO; warn=obslua.OBS_TEXT_INFO_WARNING; input=obslua.OBS_TEXT_DEFAULT;password=obslua.OBS_TEXT_PASSWORD; textarea=obslua.OBS_TEXT_MULTILINE; };group={ normal= obslua.OBS_GROUP_NORMAL;checked= obslua.OBS_GROUP_CHECKABLE; };options={ string=obslua.OBS_COMBO_FORMAT_STRING; int=obslua.OBS_COMBO_FORMAT_INT; float=obslua.OBS_COMBO_FORMAT_FLOAT;bool=obslua.OBS_COMBO_FORMAT_BOOL; edit=obslua.OBS_COMBO_TYPE_EDITABLE;default=obslua.OBS_COMBO_TYPE_LIST; radio=obslua.OBS_COMBO_TYPE_RADIO; };number={ int=obslua.OBS_COMBO_FORMAT_INT;float=obslua.OBS_COMBO_FORMAT_FLOAT; slider=1000;input=2000 } },register={ hotkey_id_list={},event_id_list={} };front={},shared={}}; bit= require('bit') -- dkjson= require('dkjson') math.randomseed(os.time()) -- schedule an event scheduled_events = {} -- [[ MEMORY MANAGE API ]] function obs.utils.scheduler(timeout) -- if type(timeout) ~= "number" or timeout < 0 then -- return obs.script_log(obslua.LOG_ERROR, "[Scheduler] invalid timeout value") -- end local scheduler_callback = nil local function interval() obslua.timer_remove(interval) if type(scheduler_callback) ~= "function" then return end return scheduler_callback(scheduler_callback) end local self = nil; self = { after = function(callback) if type(callback) == "function" or type(timeout) ~= "number" or timeout < 0 then scheduler_callback = callback else obslua.script_log(obslua.LOG_ERROR, "[Scheduler] invalid callback/timeout " .. type(callback)) return false end obslua.timer_add(interval, timeout) end;push = function(callback) if callback == nil or type(callback) ~= "function" then obslua.script_log(obslua.LOG_WARNING, "[Scheduler] invalid callback at {push} " .. type(callback)) return false end obslua.timer_add(callback, timeout) table.insert(scheduled_events, callback) return { clear = function() if callback == nil or type(callback) ~= "function" then return nil end return obslua.timer_remove(callback) end; } end; clear = function() if scheduler_callback ~= nil then obslua.timer_remove(scheduler_callback) end for _, clb in pairs(scheduled_events) do obslua.timer_remove(clb) end scheduled_events = {}; scheduler_callback = nil end;update=function(timeout_t) if type(timeout_t) ~= "number" or timeout_t < 0 then obslua.script_log(obslua.LOG_ERROR, "[Scheduler] invalid timeout value") return false end timeout= timeout_t return self end } return self end function obs.wrap(object, object_type) local self = nil self = { type = object_type, data = object;item=object; get_source=function() if self.type == obs.utils.OBS_SRC_TYPE then return self.data elseif self.type == obs.utils.OBS_SCENEITEM_TYPE then return obslua.obs_sceneitem_get_source(self.data) else return self.data end end;released=false; free = function() if self.data == nil or self.released then return end if self.type == obs.utils.OBS_SCENE_TYPE then obslua.obs_scene_release(self.data) self.data = nil;self.item=nil;self.released=true elseif self.type == obs.utils.OBS_SRC_TYPE then obslua.obs_source_release(self.data) self.data = nil;self.item=nil;self.released=true elseif self.type == obs.utils.OBS_ARR_TYPE then obslua.obs_data_array_release(self.data) self.data = nil;self.item=nil;self.released=true elseif self.type == obs.utils.OBS_OBJ_TYPE then obslua.obs_data_release(self.data) self.data = nil;self.item=nil;self.released=true elseif self.type == obs.utils.OBS_SCENEITEM_TYPE then obslua.obs_sceneitem_release(self.data) self.data = nil;self.item=nil;self.released=true elseif self.type == obs.utils.OBS_SCENEITEM_LIST_TYPE then obslua.sceneitem_list_release(self.data) self.data = nil;self.item=nil;self.released=true elseif self.type == obs.utils.OBS_SRC_LIST_TYPE then obslua.source_list_release(self.data) self.data = nil;self.item=nil;self.released=true elseif self.type == obs.utils.OBS_UN_IN_TYPE then self.data = nil;self.item=nil;self.released=true return else self.data = nil end end } table.insert(obs.utils.expect_wrapper, self) return self end function obs.shared.api(named_api) local arr_data_t= nil local function init_obs_data_t() for _, scene_name in pairs(obs.scene:names()) do local a_scene= obs.scene:get_scene(scene_name) if a_scene and a_scene.source then local s_data_t= obs.PairStack( obslua.obs_source_get_settings(a_scene.source) ) if not s_data_t or s_data_t.data == nil then a_scene.free() else if arr_data_t and arr_data_t.data then -- replace data to the current s_data_t.arr(named_api, arr_data_t.data) else -- register data to the current arr_data_t= s_data_t.arr(named_api) if not arr_data_t or arr_data_t.data == nil then arr_data_t= obs.ArrayStack() s_data_t.arr(named_api, arr_data_t.data) arr_data_t.free() arr_data_t=nil end end s_data_t.free() a_scene.free() end end end if not arr_data_t or arr_data_t.data == nil then arr_data_t= obs.ArrayStack() end end init_obs_data_t() function arr_data_t.save() init_obs_data_t() end function arr_data_t.del() local del_count=0 for _, scene_name in pairs(obs.scene:names()) do local a_scene= obs.scene:get_scene(scene_name) if a_scene and a_scene.source then local s_data_t= obs.PairStack( obslua.obs_source_get_settings(a_scene.source) ) if not s_data_t or s_data_t.data == nil then a_scene.free() else s_data_t.del(named_api) del_count=del_count+1 end end end return del_count end -- obs.utils.table.append(obj_data_t, arr_data_t) return arr_data_t end function obs.expect(callback) return function(...) local args = {...} local data = nil local caller = "" for i, v in ipairs(args) do if caller ~= "" then caller = caller .. "," end caller = caller .. "args[" .. tostring(i) .. "]" end caller = "return function(callback,args) return callback(" .. caller .. ") end"; local run = loadstring(caller) local success, result = pcall(function() data = run()(callback, args) end) local free_count=0 if not success then for _, iter in pairs(obs.utils.expect_wrapper) do if iter and type(iter.free) == "function" then local s, r = pcall(function() iter.free() end) if s then free_count = free_count + 1 end end end obslua.script_log(obslua.LOG_ERROR, "[ErrorWrapper ERROR] => " .. tostring(result)) end return data end end -- array handle function obs.ArrayStack(stack, name, fallback) if fallback == nil then fallback=true end local self = nil self = { index = 0;get = function(index) if type(index) ~= "number" or index < 0 or index > self.size() then return nil end return obs.PairStack(obslua.obs_data_array_item(self.data, index), nil, true) end;next = obs.expect(function(__index) if type(self.index) ~= "number" or self.index < 0 or self.index > self.size() then return assert(false,"[ArrayStack] Invalid data provided or corrupted data for (" .. tostring(name)..")") end return coroutine.wrap(function() if self.size() <= 0 then return nil end local i =0 if __index == nil or type(__index) ~= "number" or __index < 0 or __index > self.size() then __index=0 end for i=__index, self.size()-1 do coroutine.yield(obs.PairStack( obslua.obs_data_array_item(self.data, i), nil, false )) end end) -- local temp = self.index;self.index = self.index + 1 -- return obs.PairStack(obslua.obs_data_array_item(self.data, temp), nil, true) end);find= function(key, value) local index=0 for itm in self.next() do if itm.get_str(key) == value or itm.get_int(key) == value or itm.get_bul(key) == value or itm.get_dbl(key) == value then return itm, index end index = index + 1 itm.free() end return nil, nil end; free = function() if self.data == nil then return false end obslua.obs_data_array_release(self.data) self.data = nil return true end;insert = obs.expect(function(value) if value == nil or type(value) ~= "userdata" then obslua.script_log("FAILED TO INSERT OBJECT INTO [ArrayStack]") return false end obslua.obs_data_array_push_back(self.data, value) return self end); size = obs.expect(function() if self.data == nil then return 0 end return obslua.obs_data_array_count(self.data); end); rm= obs.expect(function(idx) if idx < 0 or self.size() <=0 or idx > self.size() then obslua.script_log("FAILED TO RM DATA FROM [ArrayStack] (INVALID INDEX)") return false end obslua.obs_data_array_erase(self.data, idx) return self end) } if stack and name then self.data = obslua.obs_data_get_array(stack, name) elseif not stack and fallback then self.data = obslua.obs_data_array_create() else self.data = stack end table.insert(obs.utils.expect_wrapper, self) return self end -- pair stack used to manage memory stuff :) function obs.PairStack(stack, name, fallback) if fallback == nil then fallback=true end local self = nil; self = { free = function() if self.data == nil then return false end obslua.obs_data_release(self.data) self.data = nil return true end; str = obs.expect(function(name, value, def) if name and value == nil then return self.get_str(name) end if (name == nil or type(name) ~= "string" or name == "") or (self.data == nil or type(self.data) ~= "userdata") or (value == nil or type(value) ~="string") then obslua.script_log(obslua.LOG_ERROR,"FAILED TO INSERT STR INTO [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value))) return false end if def then obslua.obs_data_set_default_string(self.data, name, value) else obslua.obs_data_set_string(self.data, name, value) end return self end);int = obs.expect(function(name, value, def) if name and value == nil then return self.get_int(name) end if (name == nil or type(name) ~= "string" or name == "") or (self.data == nil or type(self.data) ~= "userdata") or (value == nil or type(value) ~="number") then obslua.script_log(obslua.LOG_ERROR,"FAILED TO INSERT INT INTO [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value))) return false end if def then obslua.obs_data_set_default_int(self.data, name, value) else obslua.obs_data_set_int(self.data, name, value) end return self end);dbl=obs.expect(function(name, value, def) if name and value == nil then return self.get_dbl(name) end if (name == nil or type(name) ~= "string" or name == "") or (self.data == nil or type(self.data) ~= "userdata") or (value == nil or type(value) ~="number") then obslua.script_log(obslua.LOG_ERROR,"FAILED TO INSERT INT INTO [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value))) return nil end if def then obslua.obs_data_set_default_double(self.data, name, value) else obslua.obs_data_set_double(self.data, name, value) end return self end);bul = obs.expect(function(name, value, def) if name and value == nil then return self.get_bul(name) end if (name == nil or type(name) ~= "string" or name == "") or (self.data == nil or type(self.data) ~= "userdata") or (type(value) == "nil" or type(value) ~="boolean") then obslua.script_log(obslua.LOG_ERROR,"FAILED TO INSERT BUL [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value))) return nil end if def then obslua.obs_data_set_default_bool(self.data, name, value) else obslua.obs_data_set_bool(self.data, name, value) end return self end); arr = obs.expect(function(name, value, def) if name and value == nil then return self.get_arr(name) end if (name == nil or type(name) ~= "string" or name == "") or (self.data == nil or type(self.data) ~= "userdata") or (type(value) ~="userdata") then obslua.script_log(obslua.LOG_ERROR,"FAILED TO INSERT ARR INTO [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value))) return nil end if def then obslua.obs_data_set_default_array(self.data, name, value) else obslua.obs_data_set_array(self.data, name, value) end return self end); obj = obs.expect(function(name, value, def) if name and value == nil then return self.get_obj(name) end if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata") or (type(value) ~="userdata") then obslua.script_log(obslua.LOG_ERROR,"FAILED TO INSERT OBJ INTO [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value))) return nil end if def then obslua.obs_data_set_default_obj(self.data, name, value) else obslua.obs_data_set_obj(self.data, name, value) end return self end); -- getter get_str = obs.expect(function(name, def) if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata")then obslua.script_log(obslua.LOG_ERROR,"FAILED TO GET STR FROM [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value))) return nil end if not def then return obslua.obs_data_get_string(self.data, name) else return obslua.obs_data_get_default_string(self.data, name) end end);get_int = obs.expect(function(name, def) if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata")then obslua.script_log(obslua.LOG_ERROR,"FAILED TO GET INT FROM [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value))) return nil end if not def then return obslua.obs_data_get_int(self.data, name) else return obslua.obs_data_get_default_int(self.data, name) end end);get_dbl = obs.expect(function(name, def) if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata")then obslua.script_log(obslua.LOG_ERROR,"FAILED TO GET DBL FROM [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value))) return nil end if not def then return obslua.obs_data_get_double(self.data, name) else return obslua.obs_data_get_default_double(self.data, name) end end);get_obj = obs.expect(function(name, def) if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata")then obslua.script_log(obslua.LOG_ERROR,"FAILED TO GET OBJ FROM [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value))) return nil end if not def then return obs.PairStack( obslua.obs_data_get_obj(self.data, name),nil, false ) else return obs.PairStack( obslua.obs_data_get_default_obj(self.data, name),nil, false ) end end);get_arr = obs.expect(function(name, def) if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata")then obslua.script_log(obslua.LOG_ERROR,"FAILED TO GET ARR FROM [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value))) return nil end if not def then return obs.ArrayStack( obslua.obs_data_get_array(self.data, name),nil, false ) else return obs.ArrayStack(obslua.obs_data_get_default_array(self.data, name),nil, false) end end);get_bul = obs.expect(function(name, def) if (name == nil or type(name) ~= "string" or name == "") or (type(self.data) ~= "userdata") then obslua.script_log(obslua.LOG_ERROR,"FAILED TO GET BUL FROM [PairStack] " .. "FOR [" .. tostring(name) .. "] " .. " OF VALUE [" .. tostring(value) .. "] TYPE: " .. tostring(type(value))) return nil end if not def then return obslua.obs_data_get_bool(self.data, name) else return obslua.obs_data_get_default_bool(self.data, name) end end); del= obs.expect(function(name) obslua.obs_data_erase(self.data, name) return true end); } if stack and name then self.data = obslua.obs_data_get_obj(stack, name) elseif not stack and fallback then self.data = obslua.obs_data_create() else self.data = stack end table.insert(obs.utils.expect_wrapper, self) return self end -- [[ MEMORY MANAGE API END ]] -- [[ OBS REGISTER CUSTOM API]] function obs.register.hotkey(unique_id, title, callback) local script_path_value= script_path() unique_id= tostring(script_path_value) .. "_" .. tostring(unique_id) local hotkey_id= obslua.obs_hotkey_register_frontend( unique_id, title, callback ) -- load from data local hotkey_load_data= obs.utils.settings.get_arr(unique_id); if hotkey_load_data and hotkey_load_data.data ~= nil then obslua.obs_hotkey_load(hotkey_id, hotkey_load_data.data) hotkey_load_data.free() end obs.register.hotkey_id_list[unique_id]= { id= hotkey_id, title= title, callback= callback, remove=function(rss) if rss == nil then rss= false end -- obs.utils.settings.del(unique_id) if rss then if obs.register.hotkey_id_list[unique_id] and type(obs.register.hotkey_id_list[unique_id].callback) == "function" then obslua.obs_hotkey_unregister( obs.register.hotkey_id_list[unique_id].callback ) end end obs.register.hotkey_id_list[unique_id]= nil end } return obs.register.hotkey_id_list[unique_id] end function obs.register.get_hotkey(unique_id) unique_id= tostring(script_path()) .. "_" .. tostring(unique_id) if obs.register.hotkey_id_list[unique_id] then return obs.register.hotkey_id_list[unique_id] end return nil end function obs.register.event(unique_id, callback) if not callback and unique_id and type(unique_id) == "function" then callback= unique_id unique_id= tostring(script_path()) .. "_" .. obs.utils.get_unique_id(3) .. "_event" else unique_id= tostring(script_path()) .. "_" .. tostring(unique_id) .. "_event" end if type(callback) ~= "function" then obslua.script_log(obslua.LOG_ERROR, "[OBS REGISTER EVENT] Invalid callback provided") return nil end local event_id= obslua.obs_frontend_add_event_callback(callback) obs.register.event_id_list[unique_id]= { id= event_id,callback= callback, unique_id= unique_id, remove= function(rss) if rss == nil then rss= false end if rss then obslua.obs_frontend_remove_event_callback(callback) end obs.register.event_id_list[unique_id]= nil end }; end function obs.register.get_event(unique_id) unique_id= tostring(script_path()) .. "_" .. tostring(unique_id) .. "_event" if obs.register.event_id_list[unique_id] then return obs.register.event_id_list[unique_id] end return nil end -- [[ OBS REGISTER CUSTOM API END]] -- [[ OBS FILTER CUSTOM API]] function obs.script.filter(filter) local self;self={ id= filter and filter.id or obs.utils.get_unique_id(3), type= filter and filter.type or obslua.OBS_SOURCE_TYPE_FILTER, output_flags= filter and filter.output_flags or bit.bor( obslua.OBS_SOURCE_VIDEO ), get_height=function(src) return src and src.height or 0 end, get_width= function(src) return src and src.width or 0 end,update= function(_, settings) if filter and type(filter) == "table" and filter["update"] and type(filter["update"]) == "function" then return filter.update(_, obs.PairStack(settings)) end end, create= function(settings, source) function get_sceneitem() local a_source=obslua.obs_filter_get_target(source) while true do local temp= obslua.obs_filter_get_target(a_source) if temp == nil then break end a_source= temp end if not a_source then return nil end local source_name= obslua.obs_source_get_name(a_source) local a_scene= obs.scene:get_scene() if not a_scene then return nil end local a_scene_item= a_scene.get(source_name) a_scene.free() return a_scene_item end if filter and type(filter) == "table" and filter["create"] and type(filter["create"]) == "function" then local __a_sceneitem=get_sceneitem() local src=filter.create( obs.PairStack(settings), __a_sceneitem ) if __a_sceneitem and __a_sceneitem.data then __a_sceneitem.free() end if src ~= nil and type(src) == "table" then src.sceneitem=get_sceneitem self.src=src;src.filter=source src.is_custom=true if filter and filter["setup"] and type(filter["setup"]) == "function" then filter.setup(src) end return src end end -- default creation local src= { source= source,filter=source, params=nil,height=nil,isAlive=true, width=nil,settings=obs.PairStack(settings), sceneitem= get_sceneitem, item=get_sceneitem() } -- get width and height of source if source ~= nil then local target= obslua.obs_filter_get_target(source) if target ~= nil then src.width= obslua.obs_source_get_base_width(target) src.height= obslua.obs_source_get_base_height(target) end else src.width= 0;src.height= 0 end shader = [[ uniform float4x4 ViewProj; uniform texture2d image; uniform int width; uniform int height; sampler_state textureSampler { Filter = Linear; AddressU = Border; AddressV = Border; BorderColor = 00000000; }; struct VertData { float4 pos : POSITION; float2 uv : TEXCOORD0; }; float4 ps_get(VertData v_in) : TARGET { return image.Sample(textureSampler, v_in.uv.xy); } VertData VSDefault(VertData v_in) { VertData vert_out; vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj); vert_out.uv = v_in.uv; return vert_out; } technique Draw { pass { vertex_shader = VSDefault(v_in); pixel_shader = ps_get(v_in); } } ]] obslua.obs_enter_graphics() src.shader= obslua.gs_effect_create(shader, nil, nil) obslua.obs_leave_graphics() if src.shader ~= nil then src.params= { width= obslua.gs_effect_get_param_by_name(src.shader, "width"), height= obslua.gs_effect_get_param_by_name(src.shader, "height"), image= obslua.gs_effect_get_param_by_name(src.shader, "image"), } else return self.destroy() end if filter and filter["setup"] and type(filter["setup"]) == "function" then filter.setup(src) end self.src=src return src end, destroy= function(src) src.isAlive= false if filter and type(filter) == "table" and filter["destroy"] and type(filter["destroy"]) == "function" then for i, v in pairs(obs.utils.filters) do if v == self then table.remove(obs.utils.filters, i) break end end local result= filter.destroy(src) if src and src.item and src.item.data then src.item.free() end if src and src.is_custom then return result end end -- default destruction if src and src.item and src.item.data then src.item.free() end if src and type(src) == "table" and src.shader then obslua.obs_enter_graphics() obslua.gs_effect_destroy(src.shader) obslua.obs_leave_graphics() end for i, v in pairs(obs.utils.filters) do if v == self then table.remove(obs.utils.filters, i) break end end end,video_tick=function(src, fps) -- get width and height of source if src.source ~= nil then local target= obslua.obs_filter_get_target(src.source) if target ~= nil then src.width= obslua.obs_source_get_base_width(target) src.height= obslua.obs_source_get_base_height(target) else src.width= 0;src.height= 0 end else src.width= 0;src.height= 0 end -- call user-defined video_tick function if filter and type(filter) == "table" and filter["video_tick"] and type(filter["video_tick"]) == "function" then filter.video_tick(src, fps) end end, video_render= function(src) if filter and type(filter) == "table" and filter["video_render"] and type(filter["video_render"]) == "function" then local result = filter.video_render(src) if src.is_custom then return result end end -- default render if not src or not src.source or not src.shader or not src.params then return end if not src.item or not src.item.data then local target= obslua.obs_filter_get_target(src.source) if target ~= nil then src.item= src.sceneitem() src.width= obslua.obs_source_get_base_width(target) src.height= obslua.obs_source_get_base_height(target) end end local width= src.width;local height= src.height if not obslua.obs_source_process_filter_begin( src.source,obslua.GS_RGBA, obslua.OBS_NO_DIRECT_RENDERING ) then obslua.obs_source_skip_video_filter(src.source) return nil end if not src.params then obslua.obs_source_process_filter_end(src.source, src.shader, width, height) return nil end if type(width) == "number" then obslua.gs_effect_set_int(src.params.width, width) end if type(height) == "number" then obslua.gs_effect_set_int(src.params.height, height) end obslua.gs_blend_state_push() obslua.gs_blend_function( obslua.GS_BLEND_ONE, obslua.GS_BLEND_INVSRCALPHA ) if width and height then obslua.obs_source_process_filter_end(src.source, src.shader, width, height) end obslua.gs_blend_state_pop() return true end, get_name=function() return filter and filter.name or "Custom Filter" end,get_defaults=function(settings) local defaults=nil if filter and type(filter) == "table" and filter["get_defaults"] and type(filter["get_defaults"]) == "function" then defaults = filter.get_defaults end if filter and type(filter) == "table" and filter["defaults"] and type(filter["defaults"]) == "function" then defaults = filter.defaults end if defaults and type(defaults) == "function" then return defaults(obs.PairStack(settings)) end end,get_properties=function(src) local properties= nil if filter and type(filter) == "table" and filter["get_properties"] and type(filter["get_properties"]) == "function" then properties = filter.get_properties end if filter and type(filter) == "table" and filter["properties"] and type(filter["properties"]) == "function" then properties = filter.properties end if properties and type(properties) == "function" then return properties(src) end return nil end } table.insert(obs.utils.filters, self) if not filter or not type(filter) == "table" then filter={} end filter.get_name= self.get_name if not filter.id then filter.id= self.id end filter.get_width= self.get_width filter.get_height= self.get_height filter.type= self.type filter.output_flags= self.output_flags --filter.destroy= self.destroy return filter end -- [[ OBS FILTER CUSTOM API END]] --[[ OBS SCENE API CUSTOM ]] function obs.scene:get_source(source_name) if not source_name or not type(source_name) == "string" then return nil end local source = obslua.obs_get_source_by_name(source_name) if not source then return nil end return obs.wrap(source, obs.utils.OBS_SRC_TYPE) end function obs.scene:get_scene(scene_name) local scene;local source_scene; if not scene_name or not type(scene_name) == "string" then source_scene=obslua.obs_frontend_get_current_scene() if not source_scene then return nil end scene= obslua.obs_scene_from_source(source_scene) else source_scene= obslua.obs_get_source_by_name(scene_name) if not source_scene then return nil end scene=obslua.obs_scene_from_source(source_scene) end local obj_scene_t;obj_scene_t= { group_names=function() local scene_items_list = obs.wrap( obslua.obs_scene_enum_items(scene), obs.utils.OBS_SCENEITEM_LIST_TYPE ) if scene_items_list == nil or scene_items_list.data == nil then return nil end local list={} for _, item in ipairs(scene_items_list.data) do local source = obslua.obs_sceneitem_get_source(item) if source ~= nil then local sourceName = obslua.obs_source_get_name(source) if obslua.obs_sceneitem_is_group(item) then table.insert(list, sourceName) end end end scene_items_list.free() return list end;source_names=function(source_id_type) local scene_nodes_name_list= {} local scene_items_list = obs.wrap( obslua.obs_scene_enum_items(scene), obs.utils.OBS_SCENEITEM_LIST_TYPE ) for _, item in ipairs(scene_items_list.data) do local source = obslua.obs_sceneitem_get_source(item) if source ~= nil then local sourceName = obslua.obs_source_get_name(source) if source_id_type == nil or type(source_id_type) ~= "string" or source_id_type == "" then table.insert(scene_nodes_name_list, sourceName) else local sourceId = obslua.obs_source_get_id(source) if sourceId == source_id_type then table.insert(scene_nodes_name_list, sourceName) end end source= nil end end scene_items_list.free() return scene_nodes_name_list end;get= function(source_name) if not scene then return nil end local c=1 local scene_item;local scene_items_list = obs.wrap( obslua.obs_scene_enum_items(scene), obs.utils.OBS_SCENEITEM_LIST_TYPE ) if scene_items_list == nil or scene_items_list.data == nil then return nil end for _, item in ipairs(scene_items_list.data) do c = c + 1 local src= obslua.obs_sceneitem_get_source(item) local src_name= obslua.obs_source_get_name(src) if src ~= nil and src_name == source_name then obslua.obs_sceneitem_addref(item) scene_item= obs.wrap(item, obs.utils.OBS_SCENEITEM_TYPE) break end end scene_items_list.free() if scene_item == nil or scene_item.data == nil then return nil end local obj_source_t; obj_source_t={ free=scene_item.free; item=scene_item.data; data=scene_item.data; get_source=function() return obslua.obs_sceneitem_get_source(scene_item.data) end;get_name= function() return obslua.obs_source_get_name(obj_source_t.get_source()) end; } return obj_source_t end;add=function(source) if not source then return false end local sceneitem= obslua.obs_scene_add(scene, source) if sceneitem == nil then return nil end obslua.obs_sceneitem_addref(sceneitem) return obs.wrap(sceneitem, obs.utils.OBS_SCENEITEM_TYPE) end;get_label=function(name, source) if (source == nil or source.data == nil) and name ~= nil and type(name) == "string" and name ~= "" then source= obj_scene_t.get(name) end if not source or not source.data then return nil end local obj_label_t;obj_label_t={ remove= function() if obj_label_t.data == nil then return true end obslua.obs_sceneitem_remove(obj_label_t.data) source.free();obj_label_t.data=nil;obj_label_t.item=nil return true end; hide= function() return obslua.obs_sceneitem_set_visible(obj_label_t.data, false) end;show = function() return obslua.obs_sceneitem_set_visible(obj_label_t.data, true) end; font= { size= function(font_size) local src= obs.PairStack( obslua.obs_source_get_settings(source.get_source()), nil,true ) if not src or not src.data then src= obs.PairStack() end local font= src.get_obj("font") if not font or not font.data then font= obs.PairStack() --font.str("face","Arial") end if font_size == nil or not type(font_size) == "number" or font_size <= 0 then font_size=font.get_int("size") font.free();src.free(); return font_size else font.int("size", font_size) end font.free(); obslua.obs_source_update(source.get_source(), src.data) src.free() return true end;face= function(face_name) end };text=function(txt) local src= obs.PairStack( obslua.obs_source_get_settings(source.get_source()), nil,true ) if not src or not src.data then src= obs.PairStack() end local res=true if txt == nil or txt == "" or type(txt) ~= "string" then res=src.get_str("text") if not res == nil then res= "" end else src.str("text", txt) end obslua.obs_source_update(source.get_source(), src.data) src.free() return res end;free=function() source.free() obj_label_t=nil return true end;data=source.data;item=source.data;width= function(w) --local default_transform= obslua.obs_transform_info() --local default_source_info=obslua.obs_source_info() --obslua.obs_source_get_info(source.get_source(), default_source_info) --obslua.obs_sceneitem_get_info2(source.data, default_transform) local default_width= obslua.obs_source_get_base_width(source.get_source()) --local default_scale_x= default_transform.scale.x; if w == nil then return default_width end local info= obslua.obs_transform_info() obslua.obs_sceneitem_get_info2(source.data, info) return w end; height= function(h) local default_height= obslua.obs_source_get_base_height(source.get_source()) if h == nil then return default_height end return h end;size=nil, pos = { x=function(val) local default_transform= obslua.obs_transform_info() obslua.obs_sceneitem_get_info2(source.data, default_transform) if val == nil then return default_transform.pos.x end default_transform.pos.x= val obslua.obs_sceneitem_set_info(source.data, default_transform) return true end; y=function(val) local default_transform= obslua.obs_transform_info() obslua.obs_sceneitem_get_info2(source.data, default_transform) if val == nil then return default_transform.pos.y end default_transform.pos.y= val obslua.obs_sceneitem_set_info(source.data, default_transform) return true end; } } return obj_label_t end; add_label= function(name, text) local src= obs.PairStack() if not text then text= "Text - Label" end src.str("text", text) local source_label=obslua.obs_source_create("text_gdiplus", name, src.data, nil) src.free() local obj= obj_scene_t.get_label( nil, obj_scene_t.add(source_label) ) if not obj or not obj.data then if source_label then obslua.obs_source_release(source_label) end return nil end -- re-write the release function -- [[SEEM LIKE THIS LEADS TO CRUSHES?]] local free_func= obj.free; obj.free= function() obslua.obs_source_release(source_label) return free_func() end return obj end;add_group= function(name, refresh) if refresh == nil then refresh=true end local obj=obj_scene_t.get_group(nil, obslua.obs_scene_add_group2(scene, name, refresh)) if not obj or obj.data == nil then return nil end -- overwrite the free function to prevent crush/bugs obj.free=function() end return obj end;get_group= function(name, gp) local obj;if not gp and name ~= nil then obj= obs.wrap(obslua.obs_scene_get_group(scene, name), obs.utils.OBS_SCENEITEM_TYPE) elseif gp ~= nil then obj= obs.wrap(gp, obs.utils.OBS_SCENEITEM_TYPE) else return nil end obj["add"]= function(sceneitem) if not sceneitem then return false end obslua.obs_sceneitem_group_add_item(obj.data, sceneitem) return true end obj["release"]= function() return obj.free() end;obj["item"]= obj.data return obj end;free= function() obslua.obs_source_release(source_scene) scene=nil end;release= function() return obj_scene_t.free() end;get_width= function() return obslua.obs_source_get_base_width(source_scene) end;get_height = function() return obslua.obs_source_get_base_height(source_scene) end;data=scene;item=scene;source=source_scene }; return obj_scene_t end function obs.scene:name() source_scene=obslua.obs_frontend_get_current_scene() if not source_scene then return nil end local source_name= obslua.obs_source_get_name(source_scene) obslua.obs_source_release(source_scene) return source_name end function obs.scene:add_to_scene(source) if not source then return false end local current_source_scene= obslua.obs_frontend_get_current_scene() if not current_source_scene then return false end local current_scene= obslua.obs_scene_from_source(current_source_scene) if not current_scene then obslua.obs_source_release(current_source_scene) return false end obslua.obs_scene_add(current_scene, source) obslua.obs_source_release(current_source_scene) return true end function obs.scene:names() local scenes= obs.wrap( obslua.obs_frontend_get_scenes(), obs.utils.OBS_SRC_LIST_TYPE ); local obj_table_t= {} for _, a_scene in pairs(scenes.data) do if a_scene then local scene_source_name= obslua.obs_source_get_name(a_scene) table.insert(obj_table_t, scene_source_name) end end scenes.free() return obj_table_t end --[[ OBS SCENE API CUSTOM END ]] -- [[ OBS FRONT API ]] function obs.front.source_names() local list={} local all_sources= obs.wrap( obslua.obs_enum_sources(), obs.utils.OBS_SRC_LIST_TYPE ); for _, source in pairs(all_sources.data) do if source then local source_name= obslua.obs_source_get_name(source) table.insert(list, source_name) end end all_sources.free() return list end function obs.front.source(source_name) if not source_name or not type(source_name) == "string" then return nil end local source = obslua.obs_get_source_by_name(source_name) if not source then return nil end return obs.wrap(source, obs.utils.OBS_SRC_TYPE) end -- [[ OBS FRONT API END ]] -- [[ OBS SCRIPT PROPERTIES CUSTOM API]] function obs.script.create(settings) local p= obslua.obs_properties_create() if type(settings) == "userdata" then settings= obs.PairStack(settings) end obs.utils.properties[p]= settings or nil return p end function obs.script.options(p, unique_id, desc, enum_type_id, enum_format_id) if not desc or type(desc) ~= "string" then desc="" end if not unique_id or type(unique_id) ~= "string" or unique_id == "" then unique_id= obs.utils.get_unique_id(20) end if enum_format_id == nil then enum_format_id= obs.enum.options.string; end if enum_type_id == nil then enum_type_id= obs.enum.options.default; end local obj=obslua.obs_properties_add_list(p, unique_id, desc, enum_type_id, enum_format_id); if not obj then obslua.script_log(obslua.LOG_ERROR, "[obsapi_custom.lua] Failed to create list property: " .. tostring(unique_id) .. " description: " .. tostring(desc) .. " enum_type_id: " .. tostring(enum_type_id) .. " enum_format_id: " .. tostring(enum_format_id)) return nil end obs.utils.properties.options[unique_id]= { enum_format_id= enum_format_id; enum_type_id= enum_type_id;type=enum_format_id } return obs.utils.obs_api_properties_patch(obj, p) end function obs.script.button(p, unique_id, label, callback) if not label or type(label) ~= "string" then label="button" end if not unique_id or type(unique_id) ~= "string" or unique_id == "" then unique_id= obs.utils.get_unique_id(20) end if type(callback)~="function" then callback=function() end end obs.utils.properties[unique_id]=obs.utils.obs_api_properties_patch( obslua.obs_properties_add_button(p, unique_id, label, function(properties_t, property_t, obs_data_t) return callback(properties_t, property_t, obs.PairStack(obs_data_t)) end) ,p) return obs.utils.properties[unique_id] end function obs.script.label(p, unique_id, text, enum_type) if not text or type(text) ~= "string" then text="" end if not unique_id or type(unique_id) == nil or unique_id == "" or type(unique_id) ~= "string" then unique_id= obs.utils.get_unique_id(20) end local default_enum_type= obslua.OBS_TEXT_INFO; if(enum_type == nil) then enum_type= default_enum_type end local obj= obs.utils.obs_api_properties_patch(obslua.obs_properties_add_text(p, unique_id, text, default_enum_type), p) if enum_type == obs.enum.text.error then obj.error(text) elseif enum_type == obs.enum.text.warn then obj.warn(text) end obj.type= enum_type; obs.utils.properties[unique_id]= obj return obj; end function obs.script.group(p, unique_id, desc, op, enum_type) if not desc or type(desc) ~= "string" then desc="" end if not unique_id or type(unique_id) ~= "string" or unique_id == "" then unique_id= obs.utils.get_unique_id(20) end if enum_type == nil then enum_type= obs.enum.group.normal; end obs.utils.properties[unique_id]= obs.utils.obs_api_properties_patch(obslua.obs_properties_add_group(p, unique_id, desc, enum_type, op), op) 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 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","