--[[ OBS-SWAPPER v1.0 A script for Open Broadcaster Software that causes two sources to swap locations, sizes, and ordering using a shrink & grow effect. The positional alignment of the sources should be set to "center" for best results. albedozero@gmail.com Learned a lot about lua scripting for OBS by picking apart (and re-using bits of) @bromagosa's OBS-TRANSFORMER script. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ]]-- local obs = obslua local active = false local flip = false local src_a = {} local src_b = {} local effect = { delay = 0, remaining_delay = 0 } local sceneItemA = nil local sceneItemB = nil local hotkey_id = obs.OBS_INVALID_HOTKEY_ID local function currentSceneName() local src = obs.obs_frontend_get_current_scene() local name = obs.obs_source_get_name(src) obs.obs_source_release(src) return name end -- -- OBS Script Overrides -- function script_properties() local props = obs.obs_properties_create() local sa = obs.obs_properties_add_list( props, 'source a', 'Source A', obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING) local sb = obs.obs_properties_add_list( props, 'source b', 'Source B', obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING) local sources = obs.obs_enum_sources() if sources then for _, source in ipairs(sources) do local name = obs.obs_source_get_name(source) obs.obs_property_list_add_string(sa, name, name) obs.obs_property_list_add_string(sb, name, name) end end obs.source_list_release(sources) obs.obs_properties_add_float( props, 'duration', 'Duration (seconds):', 0, 100000, 1) obs.obs_properties_add_float( props, 'delay', 'Start delay (seconds):', 0, 100000, 1) obs.obs_properties_add_float( props, 'minsz', 'Minimum Size (percent):', 0, 100, 1) obs.obs_properties_add_button(props, 'button', 'Swap!', trigger) return props end function script_description() return 'Swap location, size, and order of two sources\n' .. 'with a cool shrink & grow effect.\n\n' .. 'by Bill Peterson 2019' end function script_update(settings) src_a_name = obs.obs_data_get_string(settings, 'source a') src_b_name = obs.obs_data_get_string(settings, 'source b') effect.duration = obs.obs_data_get_double(settings, 'duration') effect.delay = obs.obs_data_get_double(settings, 'delay') effect.minsz = obs.obs_data_get_double(settings, 'minsz')*.01 end function script_save(settings) local hotkey_save_array = obs.obs_hotkey_save(hotkey_id) obs.obs_data_set_array(settings, 'trigger_hotkey', hotkey_save_array) obs.obs_data_array_release(hotkey_save_array) end function script_load(settings) hotkey_id = obs.obs_hotkey_register_frontend( 'trigger_swapper', 'Trigger Swapper', trigger) local hotkey_save_array = obs.obs_data_get_array(settings, 'trigger_hotkey') obs.obs_hotkey_load(hotkey_id, hotkey_save_array) obs.obs_data_array_release(hotkey_save_array) end function script_tick(seconds) if (active) then effect.remaining_delay = effect.remaining_delay - seconds if (effect.remaining_delay <= 0) then effect.elapsed_time = effect.elapsed_time + seconds local t=effect.elapsed_time/effect.duration obs.obs_sceneitem_set_pos(sceneItemA, get_pos(src_a, src_b)) obs.obs_sceneitem_set_pos(sceneItemB, get_pos(src_b, src_a)) obs.obs_sceneitem_set_bounds(sceneItemA, get_bounds(src_a, src_b)) obs.obs_sceneitem_set_bounds(sceneItemB, get_bounds(src_b, src_a)) if (effect.elapsed_time > 0.5*effect.duration) then flip=true end if (flip) then obs.obs_sceneitem_set_order_position(sceneItemB, src_a.order) obs.obs_sceneitem_set_order_position(sceneItemA, src_b.order) flip=false end if (effect.elapsed_time >= effect.duration) then active = false effect.elapsed_time = 0 obs.obs_sceneitem_set_pos(sceneItemA, src_b.pos) obs.obs_sceneitem_set_bounds(sceneItemA, src_b.bounds) obs.obs_sceneitem_set_pos(sceneItemB, src_a.pos) obs.obs_sceneitem_set_bounds(sceneItemB, src_a.bounds) local a = src_a_name src_a_name=src_b_name src_b_name=a end end end end function trigger(pressed) if not pressed then return end getSourceInfo() obs.obs_sceneitem_set_bounds_type(sceneItemA, obs.OBS_BOUNDS_STRETCH) obs.obs_sceneitem_set_bounds_type(sceneItemB, obs.OBS_BOUNDS_STRETCH) effect.elapsed_time = 0 effect.remaining_delay = effect.delay active = true end -- -- custom functions -- function getSourceInfo() local src = obs.obs_get_source_by_name(currentSceneName()) if src then local scene = obs.obs_scene_from_source(src) obs.obs_source_release(src) if scene then sceneItemA = obs.obs_scene_find_source(scene, src_a_name) sceneItemB = obs.obs_scene_find_source(scene, src_b_name) src_a.pos = obs.vec2() obs.obs_sceneitem_get_pos(sceneItemA, src_a.pos) src_a.bounds = obs.vec2() obs.obs_sceneitem_get_bounds(sceneItemA, src_a.bounds) src_b.pos = obs.vec2() obs.obs_sceneitem_get_pos(sceneItemB, src_b.pos) src_b.bounds = obs.vec2() obs.obs_sceneitem_get_bounds(sceneItemB, src_b.bounds) local items = obs.obs_scene_enum_items(scene) local i = 0 if items then for _, item in ipairs(items) do local src2 = obs.obs_sceneitem_get_source(item) local name = obs.obs_source_get_name(src2) if (name == src_a_name) then src_a.order = i end if (name == src_b_name) then src_b.order = i end i=i+1 end obs.sceneitem_list_release(items) end end end end function get_pos(from, to) local newpos=obs.vec2() obs.vec2_set(newpos, cosine_ease( effect.elapsed_time/effect.duration, to.pos.x - from.pos.x, from.pos.x), cosine_ease( effect.elapsed_time/effect.duration, to.pos.y - from.pos.y, from.pos.y)) return newpos end function get_bounds(from, to) local newsz=obs.vec2() obs.vec2_set(newsz, shrinkgrow( effect.elapsed_time/effect.duration, to.bounds.x - from.bounds.x, from.bounds.x), shrinkgrow( effect.elapsed_time/effect.duration, to.bounds.y - from.bounds.y, from.bounds.y)) return newsz end function cosine_ease(t, delta, initial) return initial + delta * 0.5 * (math.cos(math.pi * (t + 1)) + 1) end function shrinkgrow(t, delta, initial) return (initial + delta * t) * (0.5 * (1 - effect.minsz) * (math.cos(2 * math.pi * t) + 1) + effect.minsz) end