Skip to content

Instantly share code, notes, and snippets.

@vurpo
Last active February 7, 2024 00:24
Show Gist options
  • Save vurpo/80741c5d03c98cc957975dc6929af61f to your computer and use it in GitHub Desktop.
Save vurpo/80741c5d03c98cc957975dc6929af61f to your computer and use it in GitHub Desktop.
obs = obslua
output_size = {x = 50, y = 50, width = 1820, height = 980}
transition_time = 0.2
tic_size = {x = 240, y = 136}
reorder = true
function vec2(x,y)
local vec = obs.vec2()
vec.x = x
vec.y = y
return vec
end
-- Description displayed in the Scripts dialog window
function script_description()
return [[automatic tic-80 arranger]]
end
function script_properties()
props = obs.obs_properties_create()
obs.obs_properties_add_int(props, "x", "Output area X", 0, 1000000, 1)
obs.obs_properties_add_int(props, "y", "Output area Y", 0, 1000000, 1)
obs.obs_properties_add_int(props, "w", "Output area width", 0, 1000000, 1)
obs.obs_properties_add_int(props, "h", "Output area height", 0, 1000000, 1)
obs.obs_properties_add_int(props, "t", "Transition time", 0, 1000000, 1)
obs.obs_properties_add_int(props, "tic_w", "TIC-80 aspect ratio (width)", 0, 1000000, 1)
obs.obs_properties_add_int(props, "tic_h", "TIC-80 aspect ratio (height)", 0, 1000000, 1)
obs.obs_properties_add_bool(props, "reorder", "Reorder sources")
return props
end
function script_defaults(settings)
obs.obs_data_set_default_int(settings, "x", 0)
obs.obs_data_set_default_int(settings, "y", 0)
obs.obs_data_set_default_int(settings, "w", 1920)
obs.obs_data_set_default_int(settings, "h", 1080)
obs.obs_data_set_default_int(settings, "t", 200)
obs.obs_data_set_default_int(settings, "tic_w", 240)
obs.obs_data_set_default_int(settings, "tic_h", 136)
obs.obs_data_set_default_bool(settings, "reorder", true)
end
function script_update(settings)
output_size.x = obs.obs_data_get_int(settings, "x")
output_size.y = obs.obs_data_get_int(settings, "y")
output_size.width = obs.obs_data_get_int(settings, "w")
output_size.height = obs.obs_data_get_int(settings, "h")
transition_time = obs.obs_data_get_int(settings, "t")/1000
tic_size.x = obs.obs_data_get_int(settings, "tic_w")
tic_size.y = obs.obs_data_get_int(settings, "tic_h")
reorder = obs.obs_data_get_bool(settings, "reorder")
end
function distance(x, y)
return math.abs(x-y)
end
function clamp(x, a, b)
if x<a then
return a
elseif x>b then
return b
else
return x
end
end
function inOutCubic(t, b, c, d)
t = t / d * 2
if t < 1 then
return c / 2 * t * t * t + b
else
t = t - 2
return c / 2 * (t * t * t + 2) + b
end
end
function lerp(a, b, t)
return a + (b - a) * clamp(t, 0, 1)
end
function find_num_of_rows(num_screens)
local best_rows = 0
local best_ratio = 0
-- try all possible number of rows, save the one that has the closest ratio to the output
for rows=1,num_screens do
screens_per_row = math.ceil(num_screens/rows)
ratio = (tic_size.x*screens_per_row)/(tic_size.y*rows)
if distance(ratio, output_size.width/output_size.height) < distance(best_ratio, output_size.width/output_size.height) or best_rows == 0 then
best_rows = rows
best_ratio = ratio
end
end
return best_rows
end
function len(t)
local count = 0
for _ in pairs(t) do count = count + 1 end
return count
end
function calculate_screen_arrangement(screens)
local num_rows = find_num_of_rows(len(screens))
local scenes_per_row = math.ceil(len(screens)/num_rows)
local screen_width = output_size.width / scenes_per_row
local screen_height = output_size.height / num_rows
local arrangement = {}
local j=0
for i,_ in pairs(screens) do
arrangement[i] = {
x = output_size.x + (j%scenes_per_row)*screen_width,
y = output_size.y + (math.floor(j/scenes_per_row))*screen_height,
width = screen_width,
height = screen_height
}
j = j+1
end
return arrangement
end
transition_from = {}
arrangement = {}
previously_visible = {}
transitioned_at = 0
function contains(set, item)
if set[item] ~= nil then return true
else return false end
end
function different(list1, list2)
if len(list1) ~= len(list2) then return true end
for i, _ in pairs(list1) do
if not contains(list2, i) then
return true
end
end
return false
end
lowest = nil
highest = nil
function script_tick(seconds)
local current_scene_as_source = obs.obs_frontend_get_current_scene()
if current_scene_as_source then
local current_scene = obs.obs_scene_from_source(current_scene_as_source)
-- find how many are visible
local new_visible = {}
local all_sources = {}
for i=1,100 do
local scene_item = obs.obs_scene_find_source(current_scene, tostring(i))
if scene_item and obs.obs_sceneitem_visible(scene_item) then
new_visible[i] = true
end
if scene_item then
all_sources[i] = true
order = obs.obs_sceneitem_get_order_position(scene_item)
if lowest == nil or lowest > order then lowest = order end
if highest == nil or highest < order then highest = order end
end
end
if different(previously_visible, new_visible) then
transition_from = arrangement
transitioned_at = os.clock()
if reorder then
if len(new_visible) < len(all_sources)-len(new_visible) then -- there's fewer visible ones, so raise them instead
for i in pairs(all_sources) do
if contains(new_visible, i) then
local scene_item = obs.obs_scene_find_source(current_scene, tostring(i))
if scene_item then
obs.obs_sceneitem_set_order_position(scene_item, highest)
end
end
end
else -- there's more visible ones than invisible
for i in pairs(all_sources) do
if not contains(new_visible, i) then
local scene_item = obs.obs_scene_find_source(current_scene, tostring(i))
if scene_item then
obs.obs_sceneitem_set_order_position(scene_item, lowest)
end
end
end
end
end
print("there are "..len(new_visible).." visible ones")
end
previously_visible = new_visible
arrangement = calculate_screen_arrangement(new_visible)
t = inOutCubic(os.clock()-transitioned_at, 0, 1, transition_time)
for i=1,100 do
i_0 = i-1
local scene_item = obs.obs_scene_find_source(current_scene, tostring(i))
if scene_item then
obs.obs_sceneitem_set_bounds_type(scene_item, obs.OBS_BOUNDS_SCALE_INNER)
local new_pos = arrangement[i]
if new_pos then
local old_pos = transition_from[i]
if old_pos then
obs.obs_sceneitem_set_pos(scene_item, vec2(
lerp(old_pos.x, new_pos.x, t),
lerp(old_pos.y, new_pos.y, t)
))
obs.obs_sceneitem_set_bounds(scene_item, vec2(
lerp(old_pos.width, new_pos.width, t),
lerp(old_pos.height, new_pos.height, t)
))
else
obs.obs_sceneitem_set_pos(scene_item, vec2(new_pos.x, new_pos.y))
obs.obs_sceneitem_set_bounds(scene_item, vec2(new_pos.width, new_pos.height))
end
end
end
end
obs.obs_source_release(current_scene_as_source)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment