os= require("os")
function setup()
local _= obs.script.filter({
name= "4IM3 (ver| 1.0.0)",id="4im3_source_filter_ver1",
})
function _.update(src)
src.update_tick= os.clock()
local animeList= src.settings.get_str("anime_list")
local durlist= src.settings.get_str("durlist")
local rep= src.settings.get_str("rep")
src.clock= os.clock()
src.seed= 3.5;src.count= tonumber(src.settings.str("custom_input")) or 1
local parsed_tm = get_time(durlist)
src.tm= parsed_tm
src.anime= animeList
src.rep= rep;src.isOnCoolDown= false
src.coolDown= tonumber(src.settings.str("cooldown_input")) or 1000;src.returnValue= false
end
function _.destroy(src)
local scene_item= obs.front:weak_source(src.source)
if scene_item and scene_item.data then
if src.transform then scene_item.transform(src.transform) end
scene_item.free()
end
src.transform= nil;src.transform_ok= false;
end
function _.defaults(settings)
settings.str("durlist", "1s", true)
settings.str("rep", "fr", true)
settings.str("anime_list", " Useful Links
")
ui:label([[
4IM3 by iixisii
]] .. about_me())
end)
end
function get_time(value)
if type(value) ~= "string" or value == "" then
return nil
end
-- Trim whitespace
value = value:match("^%s*(.-)%s*$")
if not value or value == "" then
return nil
end
local num, ty = value:match("^(%d+)(%a+)$")
if not num or not ty then
return nil
end
num = tonumber(num)
ty = ty:lower()
local opt= {
ms=1, s= 1000, m= 60000, h= 3600000, d= 86400000
}
if not opt[ty] or num == nil or num <= 0 then
return nil
end
return num * opt[ty]
end
function about_me()
return [[
Video Tutorial: click here
Ver. 1.0.0
Dev
Author: @iixisii
updates follow XDeviixisiiX]] end __animation_list= { floating_v = "Floating (Vertical)", shake = "Shake / Rumble",dvd_bounce = "DVD Screensaver Bounce", ghost_fade= "Battery low", elastic_pop= "Elastic Pop", rainbow_cycle="Rainbow step", sway= "Sway",ghost_pulse="Fade pluse",spin="360° Spin", glitch="Glitch Effect", breathing= "Breathing Effect",jello_wobble= "Jello Wobble", orbit= "Orbiting Motion",flash_alert= "Flash Alert", handheld_drift= "Handheld Drift", loading_bar= "Loading Bar", } ui_options = { cooldown_input = { jello_wobble = true,loading_bar= true } } nt= { loading_bar = function(src) local item = obs.front:weak_source(src.source) if not item or not item.data then return end local bounds= item.get_bounds_type() local target_percent = nil if bounds == 1 then target_percent = src.defaultBoundsX / src.defaultWidth else target_percent= src.defaultScaleX end src.current_fill = src.current_fill or 0 local speed = 0.02 if src.current_fill < target_percent then src.current_fill = src.current_fill + speed else src.current_fill = target_percent end local new_w = src.defaultWidth * src.current_fill -- Check if we are in "Stretch to bounds" mode (Type 1) if bounds == 1 then item.bounds({x = new_w}) else -- Use your implemented width setter for default mode item.width(new_w) end item.free() if src.current_fill >= target_percent then src.current_fill = nil src.isOnCoolDown= true end src.isActive = false end, handheld_drift = function(src) local item = obs.front:weak_source(src.source) if not item or not item.data then return end src.drift_x = (src.drift_x or 0) + 0.02 src.drift_y = (src.drift_y or 0) + 0.03 local dx = math.sin(src.drift_x) * 10 local dy = math.cos(src.drift_y) * 15 item.pos({x = src.defaultX + dx, y = src.defaultY + dy}) -- Add a tiny bit of rotation for realism item.rot(math.sin(src.drift_x * 0.5) * 2) item.free() src.isActive = false end, flash_alert = function(src) local item = obs.front:weak_source(src.source) if not item or not item.data then return end src.flash_progress = (src.flash_progress or 0) + 0.1 -- Fade from Red (255,0,0) back to White (255,255,255) local r = 255 local gb = 255 * math.min(src.flash_progress, 1) item.style.grad.enable() item.style.grad.color(r, gb, gb) if src.flash_progress >= 1 then src.flash_progress = nil end item.free() src.isActive = false end, orbit = function(src) local item = obs.front:weak_source(src.source) if not item or not item.data then return end src.orbit_angle = (src.orbit_angle or 0) + 0.05 local radius = src.radius or 50 local newX = src.defaultX + (math.cos(src.orbit_angle) * radius) local newY = src.defaultY + (math.sin(src.orbit_angle) * radius) item.pos({x = newX, y = newY}) item.free() src.isActive = false end, jello_wobble = function(src) local item = obs.front:weak_source(src.source) if not item or not item.data then return end src.wobble_timer = (src.wobble_timer or 0) + 0.15 if src.wobble_timer > math.pi * 2 then item.scale({x = 1, y = 1}) src.wobble_timer = nil item.free() src.isOnCoolDown= true src.isActive = false return end src.isActive = false -- Inverse X and Y scaling for "volume conservation" look local stretch = math.sin(src.wobble_timer) * 0.2 item.scale({x = 1 + stretch, y = 1 - stretch}) item.free() end, breathing = function(src) local item = obs.front:weak_source(src.source) if not item or not item.data then return end src.breath_timer = (src.breath_timer or 0) + 0.04 local wave = math.sin(src.breath_timer) -- Scale between 1.0 and 1.05 local s = 1 + (wave * 0.025 + 0.025) item.scale({x = s, y = s}) -- Pulse background opacity between 30 and 70 local op = 50 + (wave * 20) item.style.bg_opacity(math.floor(op)) item.free() src.isActive = false end, glitch = function(src) local scene_item = obs.front:weak_source(src.source) if not scene_item or not scene_item.data then src.isActive= false return end local intensity = 15 if math.random() > 0.8 then -- Only glitch 20% of frames for "stutter" effect local dx = math.random(-intensity, intensity) local dy = math.random(-intensity, intensity) local ds = 1 + (math.random(-10, 10) / 100) scene_item.pos({x= src.defaultX + dx, y= src.defaultY + dy}) scene_item.scale({x = ds}) else scene_item.pos({x =src.defaultX, y= src.defaultY}) scene_item.scale({x = 1}) end scene_item.free() src.isActive = false end, spin = function(src) local scene_item = obs.front:weak_source(src.source) if not scene_item or not scene_item.data then return end local speed = src.seed or 5 src.rotation = (src.rotation or 0) + speed if src.rotation >= 360 then src.rotation = 0 end scene_item.rot(src.rotation) scene_item.free() src.isActive = false end, floating_v = function(src) local scene_item= obs.front:weak_source(src.source) if not scene_item or not scene_item.data then return end -- Initialize angle if not present if not src.sine_angle then src.sine_angle = 0 end -- Adjust speed with src.seed (default is 3.5, might be too fast for sine, so we divide) local speed = (src.seed or 3.5) * 0.05 local amplitude = 20 -- How many pixels up/down it moves src.sine_angle = src.sine_angle + speed -- Calculate offset local offset = math.sin(src.sine_angle) * amplitude -- Apply to Default Y scene_item.pos({y= src.defaultY + offset}) scene_item.free() src.isActive= false end,shake = function(src) local scene_item= obs.front:weak_source(src.source) if not scene_item or not scene_item.data then return end -- intensity based on seed local intensity = 10 -- Generate random offset between -intensity and +intensity local dx = math.random(-intensity, intensity) local dy = math.random(-intensity, intensity) scene_item.pos({x=src.defaultX + dx, y=src.defaultY + dy}) scene_item.free() src.isActive=false end,dvd_bounce = function(src) if not src.base_height or not src.base_width then src.isActive= false return end local scene_item= obs.front:weak_source(src.source) if not scene_item or not scene_item.data then src.isActive= false return end local speed = src.seed or 3.5 -- Initialize velocity direction if missing if not src.vel_x then src.vel_x = speed end if not src.vel_y then src.vel_y = speed end local pos= scene_item.pos() local cur_x = pos.x local cur_y = pos.y local width = src.width or 100 -- You might need to verify src.width is populated correctly local height = src.height or 100 -- Calculate next position local next_x = cur_x + src.vel_x local next_y = cur_y + src.vel_y -- Bounce X (Left or Right Edge) if next_x <= 0 then next_x = 0 src.vel_x = math.abs(src.vel_x) -- Force positive elseif next_x >= (src.base_width - width) then next_x = src.base_width - width src.vel_x = -math.abs(src.vel_x) -- Force negative end -- Bounce Y (Top or Bottom Edge) if next_y <= 0 then next_y = 0 src.vel_y = math.abs(src.vel_y) elseif next_y >= (src.base_height - height) then next_y = src.base_height - height src.vel_y = -math.abs(src.vel_y) end scene_item.pos({x =next_x, y= next_y}) scene_item.free() src.isActive= false end,ghost_fade = function(src) local scene_item = obs.front:weak_source(src.source) if not scene_item or not scene_item.data then src.isActive= false return end if not src.fade_angle then src.fade_angle = 0 end src.fade_angle = src.fade_angle + 0.05 local opacity = 60 + (math.sin(src.fade_angle) * 40) scene_item.style.opacity(opacity) scene_item.style.bg_opacity(opacity) scene_item.free() src.isActive = false end, elastic_pop = function(src) local scene_item = obs.front:weak_source(src.source) if not scene_item or not scene_item.data then src.isActive= false return end src.progress = (src.progress or 0) + 0.04 -- Elastic Out Easing Formula local p = src.progress local elastic_scale = (2 ^ (-10 * p) * math.sin((p - 0.075) * (2 * math.pi) / 0.3) + 1) if p >= 1 then scene_item.scale({x=1, y=1}) src.progress = nil else scene_item.scale({x =elastic_scale, y=elastic_scale}) end scene_item.free() src.isActive = false end,sway = function(src) local item = obs.front:weak_source(src.source) if not item or not item.data then src.isActive= false; return; end src.sway_angle = (src.sway_angle or 0) + 0.05 local tilt = math.sin(src.sway_angle) * 15 -- 15 degree swing item.rot(tilt) item.free() src.isActive = false end,ghost_pulse = function(src) local item = obs.front:weak_source(src.source) if not item or not item.data then src.isActive= false return end src.fade_angle = (src.fade_angle or 0) + 0.08 -- Range: 20 to 100 local op = 60 + (math.sin(src.fade_angle) * 40) item.style.opacity(math.floor(op)) item.style.bg_opacity(math.floor(op)) item.free() src.isActive = false end, rainbow_cycle = function(src) local item = obs.front:weak_source(src.source) if not item or not item.data then src.isActive= false return end src.hue = (src.hue or 0) + 0.01 if src.hue > 1 then src.hue = 0 end -- Helper to get RGB from Hue local function h_to_rgb(h) local r, g, b local i = math.floor(h * 6) local f = h * 6 - i local q = 1 - f local t = f i = i % 6 if i == 0 then r,g,b = 1,t,0 elseif i == 1 then r,g,b = q,1,0 elseif i == 2 then r,g,b = 0,1,t elseif i == 3 then r,g,b = 0,q,1 elseif i == 4 then r,g,b = t,0,1 elseif i == 5 then r,g,b = 1,0,q end return r*255, g*255, b*255 end local r, g, b = h_to_rgb(src.hue) item.style.grad.enable() item.style.grad.color(r, g, b) -- Your API handles the BGR swap inside item.style.grad.dir(src.hue * 360) item.free() src.isActive = false end, } -- [[ OBS CUSTOM API BEGIN ]] -- [[ OBS CUSTOM CALLBACKS ]] function script_load(settings) obs.utils.script_shutdown = false settings = obs.PairStack(settings, nil, nil, true) 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) if obs.utils.script_shutdown then return end -- [[ 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]] if type(onSaving) == "function" then return onSaving(obs.PairStack(settings, nil, nil, true)) end end function script_unload() obs.utils.script_shutdown = true -- if obs.utils.scheduled then -- for _, clb in pairs(obs.utils.scheduled) do -- obslua.timer_remove(clb) -- end -- obs.utils.scheduled = {} -- end -- for _, iter in pairs(obs.mem.freeup) do -- if iter and iter.data then -- iter.free() -- end -- end if unset and type(unset) == "function" then return unset() end end function script_defaults(settings) if type(defaults) == "function" then return defaults(obs.PairStack(settings, nil, nil, true)) end end function script_properties() if obs.utils.ui and type(obs.utils.ui) == "function" then return obs.utils.ui() end end -- [[ OBS CUSTOM CALLBACKS END ]] obs={ utils={ scheduled={},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={} },time={}, scene={};client={};mem={freeup={}};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') os= require('os') -- dkjson= require('dkjson') math.randomseed(os.time()) -- schedule an event -- [[ MEMORY MANAGE API ]] 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 function obs.ArrayStack(stack, name, fallback, unsafe) 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(i, 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 or unsafe then return false end obslua.obs_data_array_release(self.data) self.data = nil return true end;insert = obs.expect(function(value) if type(value) ~= "userdata" and type(value) == "table" and value["data"] and type(value["data"]) == "userdata" then value= value.data end 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 type(idx) ~= "number" or 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 return self end function obs.time.schedule(timeout) local scheduler_callback = nil local function interval() obslua.timer_remove(interval) -- Safety check if obs.utils.script_shutdown or type(scheduler_callback) ~= "function" then return end return scheduler_callback(scheduler_callback) end local self = nil; self = { after = function(callback) if obs.utils.script_shutdown then return end 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) table.insert(obs.utils.scheduled, interval) -- Track timer end; push = function(callback) if obs.utils.script_shutdown then return end 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(obs.utils.scheduled, callback) -- Track timer 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(obs.utils.scheduled) do obslua.timer_remove(clb) end obs.utils.scheduled = {}; 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.time.tick(fn, interval) local tm= nil local wrapper = function() if obs.utils.script_shutdown then return end return fn(tm, os.clock()) end if not interval or type(interval) ~= "number" or interval == 0 or (interval <= 0 and not interval > 0) then interval=0.001 end tm={ clear=function() return obslua.timer_remove(wrapper) end } obslua.timer_add(wrapper, interval) return tm end function obs.wrap(self) if not self or self == nil then self = {type= obs.utils.OBS_UN_IN_TYPE, data=nil, item= nil} end if not self.data then self.data = self.item end if not self.item then self.item = self.data end -- Debugging name helper for k, v in pairs(obs.utils) do if v == self.type then self.type_name = tostring(k) end end function self.get_source() if not self.data then return nil end 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 function self.free() -- 1. Shutdown Guard if obs.utils.script_shutdown then return end -- 2. Validity Guard if self.released or not self.data then return end -- 3. Borrowed/Unsafe Guard (Do NOT free if unsafe=true) if self.unsafe then self.data = nil; self.released = true return end -- 4. Actual Release Logic if self.type == obs.utils.OBS_SCENE_TYPE then obslua.obs_scene_release(self.data) elseif self.type == obs.utils.OBS_SRC_TYPE then obslua.obs_source_release(self.data) elseif self.type == obs.utils.OBS_ARR_TYPE then obslua.obs_data_array_release(self.data) elseif self.type == obs.utils.OBS_OBJ_TYPE then obslua.obs_data_release(self.data) elseif self.type == obs.utils.OBS_SCENEITEM_TYPE then obslua.obs_sceneitem_release(self.data) elseif self.type == obs.utils.OBS_SCENEITEM_LIST_TYPE then -- NOTE: OBS Lua usually returns a table for enum_items, not a C-list. -- If 'sceneitem_list_release' doesn't exist in your API version, remove this line. if obslua.sceneitem_list_release then obslua.sceneitem_list_release(self.data) end elseif self.type == obs.utils.OBS_SRC_LIST_TYPE then if obslua.source_list_release then obslua.source_list_release(self.data) else obslua.obs_source_list_release(self.data) end end self.data = nil; self.item = nil; self.released = true end if not self.unsafe then table.insert(obs.utils.expect_wrapper, self) end return self end function obs.PairStack(stack, name, fallback, unsafe) if fallback == nil then fallback = true end local self = nil; self = { free = function() if self.data == nil or unsafe or obs.utils.script_shutdown then return false end obslua.obs_data_release(self.data) self.data = nil return true end, json = function(p) if not p then return obslua.obs_data_get_json(self.data) else return obslua.obs_data_get_json_pretty(self.data) end end, -- ... (rest of PairStack methods are fine) ... str = obs.expect(function(name, value, def) if name and value == nil then return self.get_str(name) end if self.data and name then if def then obslua.obs_data_set_default_string(self.data, name, value) else obslua.obs_data_set_string(self.data, name, value) end end return self end); int = obs.expect(function(name, value, def) if name and value == nil then return self.get_int(name) end if self.data and name then if def then obslua.obs_data_set_default_int(self.data, name, value) else obslua.obs_data_set_int(self.data, name, value) end end return self end); dbl = obs.expect(function(name, value, def) if name and value == nil then return self.get_dbl(name) end if self.data and name then if def then obslua.obs_data_set_default_double(self.data, name, value) else obslua.obs_data_set_double(self.data, name, value) end end return self end); bul = obs.expect(function(name, value, def) if name and value == nil then return self.get_bul(name) end if self.data and name then if def then obslua.obs_data_set_default_bool(self.data, name, value) else obslua.obs_data_set_bool(self.data, name, value) end end return self end); arr = obs.expect(function(name, value, def) if name and value == nil then return self.get_arr(name) end -- Unwrap wrapper if passed if type(value) ~= "userdata" and type(value) == "table" and value["data"] then value = value.data end if self.data and name and value then if def then obslua.obs_data_set_default_array(self.data, name, value) else obslua.obs_data_set_array(self.data, name, value) end end return self end); obj = obs.expect(function(name, value, def) if name and value == nil then return self.get_obj(name) end if type(value) ~= "userdata" and type(value) == "table" and value["data"] then value = value.data end if self.data and name and value then if def then obslua.obs_data_set_default_obj(self.data, name, value) else obslua.obs_data_set_obj(self.data, name, value) end end return self end); -- Getters (Simplified for brevity, logic unchanged) get_str = obs.expect(function(name, def) return def and obslua.obs_data_get_default_string(self.data, name) or obslua.obs_data_get_string(self.data, name) end); get_int = obs.expect(function(name, def) return def and obslua.obs_data_get_default_int(self.data, name) or obslua.obs_data_get_int(self.data, name) end); get_dbl = obs.expect(function(name, def) return def and obslua.obs_data_get_default_double(self.data, name) or obslua.obs_data_get_double(self.data, name) end); get_bul = obs.expect(function(name, def) return def and obslua.obs_data_get_default_bool(self.data, name) or obslua.obs_data_get_bool(self.data, name) end); get_obj = obs.expect(function(name, def) local res = def and obslua.obs_data_get_default_obj(self.data, name) or obslua.obs_data_get_obj(self.data, name) return obs.PairStack(res, nil, false) -- Return safe wrapper end); get_arr = obs.expect(function(name, def) local res = def and obslua.obs_data_get_default_array(self.data, name) or obslua.obs_data_get_array(self.data, name) return obs.ArrayStack(res, nil, false) 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 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 not _ or not _.isAlive or (obs.utils and obs.utils.script_shutdown) then return end if filter and type(filter) == "table" and filter["update"] and type(filter["update"]) == "function" then return filter.update(_, obs.PairStack(settings, nil, nil, true)) end end, create = function(settings, source) -- 1. Check custom create logic if filter and type(filter) == "table" and filter["create"] and type(filter["create"]) == "function" then local src = filter.create(obs.PairStack(settings, nil, nil, true)) if src ~= nil and type(src) == "table" then self.src = src src.filter = source src.is_custom = true src.isAlive = true -- Ensure isAlive is set for custom sources too if filter["setup"] and type(filter["setup"]) == "function" then filter.setup(src) end return src end end -- 2. Default creation local src = { filter = source, source = nil, params = nil, height = 0, width = 0, isAlive = true, -- explicit alive flag settings = obs.PairStack(settings, nil, nil, true) } -- 3. Initial sizing (Safe check) if source ~= nil then local target = obslua.obs_filter_get_parent(source) if target ~= nil then src.source= target src.width = obslua.obs_source_get_base_width(target) src.height = obslua.obs_source_get_base_height(target) end 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 -- 4. Asynchronous Source Assignment (SAFER) obs.time.schedule(380).after(function() -- CRITICAL: Check shutdown before touching C-pointers if not src or not src.isAlive or (obs.utils and obs.utils.script_shutdown) then return end -- Verify filter still exists in OBS if src.filter then src.source = obslua.obs_filter_get_parent(src.filter) if filter and filter["finally"] and type(filter["finally"]) == "function" then filter.finally(src) end end end) return src end, destroy = function(src) if not src then return end src.isAlive = false -- Mark dead immediately 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 if filter and type(filter) == "table" and filter["destroy"] and type(filter["destroy"]) == "function" then filter.destroy(src) end -- Clear references to prevent dangling pointer crashes src.source = nil src.filter = nil src.params = nil end, video_tick = function(src, fps) -- 1. CRITICAL SAFETY CHECK if not src or not src.isAlive or (obs.utils and obs.utils.script_shutdown) then return end -- 2. Fallback: Try to get parent if missing (Common in startup) if src.source == nil and src.filter then src.source = obslua.obs_filter_get_parent(src.filter) end -- 3. Update Dimensions (Preventing the Crash) -- Only attempt this if we have a valid source pointer if src.source and src.filter then src.width = obslua.obs_source_get_base_width(src.source) src.height = obslua.obs_source_get_base_height(src.source) else src.width=0;src.height=0 end -- 4. Execute User Tick local __tick = (filter["video_tick"] or filter["tick"]) or function() end __tick(src, fps) end, video_render = function(src) -- 1. CRITICAL SAFETY CHECK if not src or not src.isAlive or (obs.utils and obs.utils.script_shutdown) then return end 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 -- 2. Validate Source/Filter before rendering if src.source == nil and src.filter then src.source = obslua.obs_filter_get_parent(src.filter) end if src.source and src.filter then src.width = obslua.obs_source_get_base_width(src.source) src.height = obslua.obs_source_get_base_height(src.source) end -- 3. Standard Filter Process if src.filter then local width= src.width;local height= src.height if not obslua.obs_source_process_filter_begin( src.filter,obslua.GS_RGBA, obslua.OBS_NO_DIRECT_RENDERING ) then obslua.obs_source_skip_video_filter(src.filter) return nil end if not src.params then obslua.obs_source_process_filter_end(src.filter, 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.filter, src.shader, width, height) end obslua.gs_blend_state_pop() end 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" then if filter["get_defaults"] and type(filter["get_defaults"]) == "function" then defaults = filter.get_defaults elseif filter["defaults"] and type(filter["defaults"]) == "function" then defaults = filter.defaults end end if defaults and type(defaults) == "function" then return defaults(obs.PairStack(settings, nil, nil, true)) end end, get_properties = function(src) local properties = nil if filter and type(filter) == "table" then if filter["get_properties"] and type(filter["get_properties"]) == "function" then properties = filter.get_properties elseif filter["properties"] and type(filter["properties"]) == "function" then properties = filter.properties end 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 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 return filter end -- [[ OBS FILTER CUSTOM API END]] -- [[ OBS SCENE API CUSTOM ]] 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({ data= obslua.obs_scene_enum_items(scene), type=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({ data=obslua.obs_scene_enum_items(scene), type=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({ data=obslua.obs_scene_enum_items(scene), type=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({ data=item, type=obs.utils.OBS_SCENEITEM_TYPE, name=source_name }) 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={ cached={}, 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, pos = function(pos) if not obj_source_t or not obj_source_t.data then return nil end local base_info= obslua.obs_transform_info() obslua.obs_sceneitem_get_info2(obj_source_t.data, base_info) if pos == nil or not (type(pos) == "table") or (pos.x == nil and pos.y == nil) then pos= {x= base_info.pos.x, y= base_info.pos.y} base_info=nil return pos end if type(pos.x) == "number" or type(pos.y) == "number" then if type(pos.y) == "number" then base_info.pos.y = pos.y end if type(pos.x) == "number" then base_info.pos.x = pos.x end if obj_source_t and obj_source_t.data and base_info then obslua.obs_sceneitem_set_info2(obj_source_t.data, base_info) end end base_info=nil return true end,scale= function(scale) if not obj_source_t or not obj_source_t.data then return nil end local base_info= obslua.obs_transform_info() obslua.obs_sceneitem_get_info2(obj_source_t.data, base_info) if scale == nil or not (type(scale) == "table") or (scale.x == nil and scale.y == nil) then scale= {x= base_info.scale.x, y= base_info.scale.y} base_info=nil return scale end if type(scale.x) == "number" or type(scale.y) == "number" then if type(scale.y) == "number" then base_info.scale.y = scale.y end if type(scale.x) == "number" then base_info.scale.x = scale.x end if obj_source_t and obj_source_t.data and base_info then obslua.obs_sceneitem_set_info2(obj_source_t.data, base_info) end end base_info=nil return true end,rot= function(val) if not obj_source_t or not obj_source_t.data then return nil end local base_info= obslua.obs_transform_info() obslua.obs_sceneitem_get_info2(obj_source_t.data, base_info) if val == nil then return base_info.rot end base_info.rot = val if obj_source_t and obj_source_t.data and base_info then obslua.obs_sceneitem_set_info2(obj_source_t.data, base_info) end base_info=nil return true end,transform= function(tf) local info = obslua.obs_transform_info() obslua.obs_sceneitem_get_info2(obj_source_t.data, info) if not tf then return info end obslua.obs_sceneitem_set_info2(obj_source_t.data, tf) info = nil end, get_bounds_type = function() if not obj_source_t or not obj_source_t.data then return 0 end local info = obslua.obs_transform_info() obslua.obs_sceneitem_get_info2(obj_source_t.data, info) -- 0 = No Bounds, 2 = Stretch to bounds local temp=info.bounds_type info=nil return temp end, -- Add this to your obj_source_t table bounds = function(size) if not obj_source_t or not obj_source_t.data then return nil end local info = obslua.obs_transform_info() obslua.obs_sceneitem_get_info2(obj_source_t.data, info) -- If no arguments, return current bounds if size == nil or not (type(size) == "table") then local res = {x = info.bounds.x, y = info.bounds.y} info = nil return res end -- Set the bounding box size if type(size.x) == "number" then info.bounds.x = size.x end if type(size.y) == "number" then info.bounds.y = size.y end obslua.obs_sceneitem_set_info2(obj_source_t.data, info) info = nil return true end, -- Add these into your obj_source_t definition width = function(val) local source = obj_source_t.get_source() local base_w = obslua.obs_source_get_base_width(source) if val == nil then return base_w * obj_source_t.scale().x end obj_source_t.scale({x = val / base_w}) return val end, height = function(val) local source = obj_source_t.get_source() local base_h = obslua.obs_source_get_base_height(source) if val == nil then return base_h * obj_source_t.scale().y end obj_source_t.scale({y = val / base_h}) return val end, remove= function() if obj_source_t.data == nil then return true end obslua.obs_sceneitem_remove(obj_source_t.data) obj_source_t.free();obj_source_t.data=nil;obj_source_t.item=nil return true end,hide= function() return obslua.obs_sceneitem_set_visible(obj_source_t.data, false) end,show = function() return obslua.obs_sceneitem_set_visible(obj_source_t.data, true) end, isHidden=function() return obslua.obs_sceneitem_visible(obj_source_t.data) end, style= { grad= { enable= function() if obj_source_t.cached["gradient"] then return end local src= obs.PairStack( obslua.obs_source_get_settings(obj_source_t.get_source()) ) if not src or not src.data then src= obs.PairStack() end obj_source_t.cached["gradient"]= true src.bul("gradient", true) obslua.obs_source_update(obj_source_t.get_source(), src.data) src.free() end, disable= function() if obj_source_t.cached["gradient"] ~= nil and obj_source_t.cached["gradient"] == false then return end local src= obs.PairStack( obslua.obs_source_get_settings(obj_source_t.get_source()) ) if not src or not src.data then src= obs.PairStack() end obj_source_t.cached["gradient"]= false src.bul("gradient", false) obslua.obs_source_update(obj_source_t.get_source(), src.data) src.free() end, dir= function(val) local src= obs.PairStack( obslua.obs_source_get_settings(obj_source_t.get_source()) ) if not src or not src.data then src= obs.PairStack() end if val == nil then local tempv= src.dbl("gradient_dir") src.free() return tempv end src.dbl("gradient_dir", val) obslua.obs_source_update(obj_source_t.get_source(), src.data) src.free() end, color= function(r, g, b) local src= obs.PairStack( obslua.obs_source_get_settings(obj_source_t.get_source()) ) if not src or not src.data then src= obs.PairStack() end if not r or not g or not b then local tempv= src.int("gradient_color") src.free() return obs.utils.argb_to_rgb(tempv) end src.int("gradient_color", obs.utils.rgb_to_argb(r, g, b)) obslua.obs_source_update(obj_source_t.get_source(), src.data) src.free() return true end }, bg_opacity= function(val) local src= obs.PairStack( obslua.obs_source_get_settings(obj_source_t.get_source()) ) if not src or not src.data then src= obs.PairStack() end if val == nil then local tempv= src.int("bk_opacity") src.free() return tempv end src.int("bk_opacity", val) obslua.obs_source_update(obj_source_t.get_source(), src.data) src.free() end, opacity= function(val) local src= obs.PairStack( obslua.obs_source_get_settings(obj_source_t.get_source()) ) if not src or not src.data then src= obs.PairStack() end if val == nil then local tempv= src.int("opacity") src.free() return tempv end src.int("opacity", val) obslua.obs_source_update(obj_source_t.get_source(), src.data) src.free() end, }, } function obj_source_t.style.bg_color(r, g, b) local src= obs.PairStack( obslua.obs_source_get_settings(obj_source_t.get_source()) ) if not src or not src.data then src= obs.PairStack() end if not r or not g or not b then local tempv= src.int("bk_color") src.free() return obs.utils.argb_to_rgb(tempv) end src.int("bk_color", obs.utils.rgb_to_argb(r, g, b)) obslua.obs_source_update(obj_source_t.get_source(), src.data) src.free() end function obj_source_t.style.color(r, g, b) local src= obs.PairStack( obslua.obs_source_get_settings(obj_source_t.get_source()) ) if not src or not src.data then src= obs.PairStack() end if not r or not g or not b then local tempv= src.int("color") src.free() return obs.utils.argb_to_rgb(tempv) end local src= obs.PairStack( obslua.obs_source_get_settings(obj_source_t.get_source()) ) if not src or not src.data then src= obs.PairStack() end src.int("color", obs.utils.rgb_to_argb(r, g, b)) obslua.obs_source_update(obj_source_t.get_source(), src.data) src.free() 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) local dt=obs.wrap({ data=sceneitem, type=obs.utils.OBS_SCENEITEM_TYPE }) return dt 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={ font= { size= function(font_size) local src= obs.PairStack( obslua.obs_source_get_settings(source.get_source()) ) 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()) ) 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 } obs.utils.table.append(obj_label_t, source) 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() if source_label == nil or not source_label then return end 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 obj.free=function() end return obj end;get_group= function(name, gp) local obj;if not gp and name ~= nil then obj= obs.wrap({ data=obslua.obs_scene_get_group(scene, name), type=obs.utils.OBS_SCENEITEM_TYPE }) elseif gp ~= nil then obj= obs.wrap({ data=gp, type=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() if not source_scene then return end 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:scene_from(source) if not source or type(source) == 'string' then return nil end local sc= obslua.obs_scene_from_source(source) local ss= obslua.obs_scene_get_source(sc) return obs.scene:get_scene(obslua.obs_source_get_name(ss)) 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({ data=obslua.obs_frontend_get_scenes(), type=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 function obs.scene:size() local scene= obs.scene:get_scene() if not scene or not scene.data then return nil end local w= scene:get_width() local h= scene:get_height() scene.free() return {width= w, height= h} end -- [[ OBS SCENE API CUSTOM END ]] -- [[ OBS FRONT API ]] function obs.front.source_names() local list={} local all_sources= obs.wrap({ data=obslua.obs_enum_sources(), type=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) local source=nil if not source_name or not type(source_name) == "string" then if(type(source_name) == "userdata") then source= source_name else return end else source=obslua.obs_get_source_by_name(source_name) end if not source then return nil end local dt= obs.wrap({ data=source, type=obs.utils.OBS_SRC_TYPE }) return dt end function obs.front:weak_source(source) local scene;local source_name; if source and type(source) ~= "string" then scene= obs.scene:scene_from(source) source_name=obslua.obs_source_get_name(source) elseif type(source) == "string" then source_name= source local temp= obs.front.source(source_name) if not temp or not temp.data then return nil end scene= obs.scene:scene_from(temp.get_source()) temp.free() end if not scene or not scene.data then return end local sct= scene.get(source_name) scene.free() return sct end -- [[ OBS FRONT API END ]] -- [[ OBS SCRIPT PROPERTIES CUSTOM API]] function obs.script:ui(clb, s) if obs.utils.ui then obslua.script_log(obslua.LOG_ERROR, "[SCRIPT.UI] UI is already created") return false end if type(clb) ~= "function" then obslua.script_log(obslua.LOG_ERROR, "[SCRIPT.UI] Invalid callback provided") return false end obs.utils.ui= function() obs.utils.properties={list={};options={};} local p= obs.script.create(s) local self= {};for key, fnc in pairs(obs.script) do self[key]= function(...) return fnc(p, ...) end end clb(self, p) return p end return true end function obs.script.create(settings) local p= obslua.obs_properties_create() if type(settings) == "userdata" then settings= obs.PairStack(settings, nil, nil, true) end obs.utils.properties[p]= settings 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 options 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 } obs.utils.properties[unique_id]= obs.utils.obs_api_properties_patch(obj, p) return obs.utils.properties[unique_id] 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) return callback( property_t, properties_t, obs.utils.properties[properties_t] and obs.utils.properties[properties_t] or obs.utils.settings ) end) ,p) return obs.utils.properties[unique_id] end function obs.script.label(p, unique_id, text, enum_type) if not text or type(text) ~= "string" then text="" end if not unique_id or type(unique_id) == nil or unique_id == "" or type(unique_id) ~= "string" then unique_id= obs.utils.get_unique_id(20) end local default_enum_type= obslua.OBS_TEXT_INFO; if(enum_type == nil) then enum_type= default_enum_type end local obj= obs.utils.obs_api_properties_patch(obslua.obs_properties_add_text(p, unique_id, text, default_enum_type), p) if enum_type == obs.enum.text.error then obj.error(text) elseif enum_type == obs.enum.text.warn then obj.warn(text) end obj.type= enum_type; obs.utils.properties[unique_id]= obj return obj; end function obs.script.group(p, unique_id, desc, enum_type) local pp= obs.script.create() 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, pp), pp) obs.utils.properties[unique_id].parent= pp obs.utils.properties[unique_id].add={} for key, fnc in pairs(obs.script) do obs.utils.properties[unique_id].add[key]= function(...) return fnc(obs.utils.properties[unique_id].parent, ...) end end 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","