diff --git a/README.md b/README.md
index dc5014d..56b98ac 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,15 @@ Be sure to select the folder with your frames.
If you want to change your crop filter name, then load the script and *refresh it*. That's the only way to update the list in OBS properties after it's been created, since one can't update a list on properties update. (In reality refreshing recreates the properties object)
Tinker with it to suit your needs.
+## [frame_updater.lua](https://github.com/4aiman/obs_scripts/blob/main/frame_updater.lua)
+A more elaborate version of the previous script.
+It adds stream number tracking via a text source and a browser source.
+You probably don't need it "as is", but there are things to learn inside:
+ - fixing hotkeys firing twice and/or not firing when clicking buttons in the UI
+ - settings custom css to a browser source (sadly requeires source refresh)
+ - setting up hotkeys and buttons callbacks
+ - reading and writing text source text
+
## [death_counter.lua](https://github.com/4aiman/obs_scripts/blob/main/death_counter.lua)
Uses one of the text sources as a death counter.
Set up a hotkey and press it for the counter to go up.
diff --git a/frame_updater_custom.lua b/frame_updater_custom.lua
new file mode 100644
index 0000000..95fc1d1
--- /dev/null
+++ b/frame_updater_custom.lua
@@ -0,0 +1,450 @@
+-- "load" OBS bindings
+obs = obslua
+stream_counter_source_name = ""
+hotkey_id = obs.OBS_INVALID_HOTKEY_ID
+hotkey_id_1 = obs.OBS_INVALID_HOTKEY_ID -- add stream
+hotkey_id_2 = obs.OBS_INVALID_HOTKEY_ID -- dec stream
+hotkey_id_3 = obs.OBS_INVALID_HOTKEY_ID -- reset stream
+force_apply = false
+
+-- fix settings
+require("obs_source_set_settings_fix")
+
+-- total seconds elapsed since the script has started
+total_seconds = 0
+-- seconds elapsed in this cycle
+cur_seconds = 0
+-- names of the sources that are being synced up
+master_source_name = ""
+padding_filter_name = "pad to frame"
+slave_source_name = ""
+-- actual sources
+master_combobox = nil
+slave_combobox = nil
+master_filers_combobox = nil
+global_props = nil
+
+-- seconds between sync attempts
+sync_delay = 5
+activated = false
+
+-- common path to all your frames
+base_fmames_path = ""
+
+sizes_3_by_2 = {{width = 1440, height = 960}}
+sizes_3_by_4 = {{width = 1280, height = 960}}
+sizes_16_by_9 = {{width = 1920, height = 1080}}
+
+-- a hotkey to force synchronisation of a given pair
+hotkey_id = obs.OBS_INVALID_HOTKEY_ID
+
+css_values = [[
+#titleicon {display:none; position:relative; top:-4px!important;}
+body {
+ background-color: rgba(0, 0, 0, 0);
+ b_ackground-image: var(--clear_bg);
+}
+
+.homealert {display: none}
+#c_hannel_title_text {top:-12px!important}
+#c_hannel_title_shadow {top:-12px!important}
+.o_ga_h1:after {content: " & 4aiboy #13"}
+.o_ga_h1:after {content: " - Тихой сапой #18 "}
+.og_a_h1:after {content: " - Джекбокс по-русски: 1, 3, 4, 5 (jackbox.fun)"}
+.oga_jackbox {display: none}
+
+.oga_h1_ { margin-left:70px; font-size:54px;}
+#title_opacity {opacity:1}
+#donation-goal {display:none;}
+
+.oga__h {display:none;}
+.oga__h1 {display:none;}
+.oga_h2 {display:none;}
+.oga_jackbox {display:none;}
+.widget-stat-runner__item{color:red!important}
+#jo_y {display:none}
+#ya-train {display:none}
+
+#root, .span {
+ text-shadow: #424200 -1px -1px 0px, #424200 1px 1px 0px, #424200 -1px 1px 0px, #424200 1px -1px 0px, #424200 -1px 0px 0px, #424200 1px 0px 0px, #424200 0px -1px 0px, #424200 0px 1px 0px!important ;
+}
+]]
+
+
+function string.split(inputstr, sep)
+ if sep == nil then
+ sep = "%s"
+ end
+ local t={}
+ for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
+ table.insert(t, str)
+ end
+ return t
+end
+
+
+local last_sizing_mode = nil
+
+function timer_callback()
+ cur_seconds = cur_seconds - 1
+
+ if cur_seconds < 0 then
+ --print("Source Synchronizer script cycle ended. Delay is " .. total_seconds .. '; Master source is "' .. master_source_name .. '"; Slave source is "' .. slave_source_name ..'".')
+
+ -- get both master and slave sources by names set up in the script dialog settings
+ local master_source = obs.obs_get_source_by_name(master_source_name)
+ local slave_source = obs.obs_get_source_by_name(slave_source_name)
+ local browser_source = obs.obs_get_source_by_name("Браузер 2")
+ local sizing_mode = false
+
+
+ if master_source~= nil and slave_source~= nil then
+ local master_filters = obs.obs_source_enum_filters(master_source)
+
+ local master_height = obs.obs_source_get_height(master_source)
+ local master_width = obs.obs_source_get_width(master_source)
+ local padding_left = 0
+ local padding_top = 0
+ local actual_height = master_height
+ local actual_width = master_width
+
+ -- obs_source_get_filter_by_name
+ for idx, filter in pairs(master_filters) do
+ local filter_name = obs.obs_source_get_name(filter)
+ if filter_name == padding_filter_name then
+ -- check if the padding filter is enabled at all
+ if obs.obs_source_enabled(filter) == true then
+ -- if it is - modify actual size with regard to set padding
+ local filter_props = obs.obs_source_get_settings(filter)
+ padding_left = obs.obs_data_get_int(filter_props, 'left')
+ padding_top = obs.obs_data_get_int(filter_props, 'top')
+ actual_height = master_height + padding_top
+ actual_width = master_width + padding_left
+ else
+ break
+ end
+ end
+ end
+
+ --print(master_width .. "x"..master_height ..'; '.. (padding_left or 0) .. ':' .. (padding_top or 0) .. '; ' .. actual_width .. 'x' .. actual_height)
+
+ if sizing_mode ~= true then
+ for id, size in ipairs (sizes_3_by_2) do
+ if actual_width == size.width and actual_height == size.height then
+ --print("it's 3:2 ratio!")
+ sizing_mode = 'frame_3x2'
+ break
+ end
+ end
+ end
+
+ if sizing_mode ~= true then
+ for id, size in ipairs (sizes_3_by_4) do
+ if actual_width == size.width and actual_height == size.height then
+ --print("it's 4:3 ratio!")
+ sizing_mode = 'frame_4x3'
+ break
+ end
+ end
+ end
+
+ if last_sizing_mode ~= sizing_mode or force_apply then
+ force_apply = false
+ local counter_source = obs.obs_get_source_by_name(stream_counter_source_name)
+ local __STREAM_NUMBER__ = ""
+ if counter_source ~= nil then
+ local counter_props = obs.obs_source_get_settings(counter_source)
+ __STREAM_NUMBER__ = tonumber(obs.obs_data_get_string(counter_props, 'text'))
+ obs.obs_source_release(counter_source)
+ end
+
+ local css = ""
+ if sizing_mode == 'frame_4x3' then
+ print('updating chat size to 4x3/16x9')
+ local _ = {
+ css_values,
+ '\n',
+ '#chat {margin-left:-50px; width: 500px; word-break: break-word;} /* for 4:3 and 16:9 agames*/',
+ '\n',
+ '.oga_h1:after {content: " #',
+ __STREAM_NUMBER__,
+ '"}'
+ }
+ css = table.concat(_)
+ else
+ print('updating chat size to 3x2')
+ local _ = {
+ css_values,
+ '\n',
+ '#chat {margin-left:50px; width: 400px;word-break: break-word;} /* for 3:2 GBA games*/',
+ '\n',
+ '.oga_h1:after {content: " #',
+ __STREAM_NUMBER__,
+ '"}'
+ }
+ css = table.concat(_)
+ end
+
+ local props = obs.obs_source_get_settings(browser_source)
+ obs.obs_data_set_string(props, 'css', css)
+ obs.obs_source_update(browser_source, props)
+ last_sizing_mode = sizing_mode
+ end
+
+
+
+ if sizing_mode ~= true then
+ for id, size in ipairs (sizes_16_by_9) do
+ if actual_width == size.width and actual_height == size.height then
+ --print("it's 16:9 ratio!")
+ -- obs.obs_source_enabled doesn't want the second param, despite what's said in the docs
+ -- obs.obs_source_active is readonly as per documentation, but only returns false
+ -- obs.obs_source_set_hidden does nothing
+ -- obs.obs_source_dec_showing does nothing
+ -- obs.obs_source_dec_active does nothing
+ -- obs.obs_scene_from_source doesn't produce anything, making it impossible to work with any scene items despite being used is some people's code
+ obs.obs_source_set_enabled(slave_source, false)
+ break
+ end
+ end
+ end
+
+
+ if sizing_mode ~= false then
+ local slave_props = obs.obs_source_get_settings(slave_source)
+ --print(obs.obs_data_get_json(slave_props))
+ local image = obs.obs_data_get_string(slave_props, 'file')
+ local month = tonumber(os.date('%m'))
+ local date = tonumber(os.date('%d'))
+ local season = 'summer'
+ if month >2 and month < 6 then season = 'spring' end
+ if month >5 and month < 9 then season = 'summer' end
+ if month >8 and month < 12 then season = 'autumn' end
+ if month >11 or month < 3 then season = 'winter' end
+ if month == 1 and date < 9 then season = 'winter_snow' end
+
+ local frame_path = base_fmames_path .. sizing_mode .. '_' .. season .. ".png"
+
+ if image ~= frame_path then
+ obs.obs_data_set_string(slave_props, 'file', frame_path)
+ obs.obs_source_update(slave_source, slave_props)
+ end
+ obs.obs_source_set_enabled(slave_source, true)
+ end
+
+ obs.obs_source_release(master_source)
+ obs.obs_source_release(slave_source)
+ end
+ if browser_source~= nil then
+ obs.obs_source_release(browser_source)
+ end
+ -- reset loop timer to the default value, so the cycle can continue
+ cur_seconds = sync_delay
+ end
+
+ --set_time_text()
+end
+
+function activate(activating)
+ if activated == activating then
+ return
+ end
+
+ activated = activating
+
+ if activating then
+ cur_seconds = total_seconds
+ obs.timer_add(timer_callback, 1000)
+ else
+ obs.timer_remove(timer_callback)
+ end
+end
+
+
+----------------------------------------------------------
+
+-- A function named script_properties defines the properties that the user
+-- can change for the entire script module itself
+function script_properties()
+ -- create properties dialogue
+ global_props = obs.obs_properties_create()
+ -- add sync delay settings
+ obs.obs_properties_add_int(global_props, "sync_delay", "Sync cycle (seconds)", 1, 3600, 1)
+ -- add main source global_props
+ master_combobox = obs.obs_properties_add_list(global_props, "master_source", "Game source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)
+ -- add master filters list; a more convenient way to select the padding filter
+ master_filers_combobox = obs.obs_properties_add_list(global_props, "filter_source", "Pading filter", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING )
+ -- add the source that will sync up with the main one
+ slave_combobox = obs.obs_properties_add_list(global_props, "slave_source", "Frame source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)
+ frame_path_edit = obs.obs_properties_add_path(global_props, "frame_path_edit", "Frame Folder", obs.OBS_PATH_DIRECTORY, nil ,nil)
+
+ -- stream counter
+ local p = obs.obs_properties_add_list(global_props, "source", "Stream Counter Source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)
+ --
+
+
+ -- get all the sources
+ local sources = obs.obs_enum_sources()
+ if sources ~= nil then
+ for _, source in ipairs(sources) do
+ source_id = obs.obs_source_get_unversioned_id(source)
+ -- if the source is not a group, then ad it to both master and slave lists
+ if source_id ~= "group" then
+ local name = obs.obs_source_get_name(source)
+ if source_id == "game_capture" then
+ obs.obs_property_list_add_string(master_combobox, name, name)
+ end
+ if source_id == "image_source" then
+ obs.obs_property_list_add_string(slave_combobox, name, name)
+ end
+ end
+ -- stream counter
+ if source_id == "text_gdiplus" or source_id == "text_ft2_source" then
+ local name = obs.obs_source_get_name(source)
+ obs.obs_property_list_add_string(p, name, name)
+ end
+ --
+ end
+ end
+
+
+ if master_source_name ~= '' then
+ local master_source = obs.obs_get_source_by_name(master_source_name)
+ if master_source~= nil and master_filers_combobox ~= nil then
+ local master_filters = obs.obs_source_enum_filters(master_source)
+ for idx, filter in pairs(master_filters) do
+ local filter_name = obs.obs_source_get_name(filter)
+ obs.obs_property_list_add_string(master_filers_combobox, filter_name, filter_name)
+ end
+ obs.obs_source_release(master_source)
+ end
+ end
+
+ -- stream counter
+ obs.obs_properties_add_button(global_props, "reset_stream_counter_button", "Reset Stream Counter", reset_counter)
+ obs.obs_properties_add_button(global_props, "add_stream_counter_button", "Increase Stream Counter", set_text1)
+ obs.obs_properties_add_button(global_props, "dec_stream_counter_button", "Decrease Stream Counter", set_text2)
+ --
+
+ -- they say it's important to release sources
+ obs.source_list_release(sources)
+
+ return global_props
+end
+
+-- A function named script_description returns the description shown to
+-- the user
+function script_description()
+ return "This script allows one to automatically change 'frames' depending on a game capture source's size.\n\nThe idea is to have several overlays for different aspect ratios of the game capture source and auto-select them dependinf on that.\nJust pick some overlays below for every aspect you'd like to have, and enjoy automation.\n\nOnce the Game source is set *refresh* the script in order to be able to select the crop filter.\n\nIt also manages stream number in browser's css\n\nMade by 4aiman"
+end
+
+-- A function named script_update will be called when settings are changed
+function script_update(settings)
+ -- script counter
+ stream_counter_source_name = obs.obs_data_get_string(settings, "source")
+ --
+ total_seconds = obs.obs_data_get_int(settings, "sync_delay")
+ master_source_name = obs.obs_data_get_string(settings, "master_source")
+ padding_filter_name = obs.obs_data_get_string(settings, "filter_source")
+ slave_source_name = obs.obs_data_get_string(settings, "slave_source")
+ base_fmames_path = obs.obs_data_get_string(settings, "frame_path_edit") .. '/'
+
+ print("Frame Synchronizer script upadted. Delay has been set to " .. total_seconds .. '; Master source is now "' .. master_source_name .. '; Filter source is now "' .. padding_filter_name .. '"; Slave source is now "' .. slave_source_name ..'".')
+
+end
+
+-- A function named script_defaults will be called to set the default settings
+function script_defaults(settings)
+ obs.obs_data_set_default_int(settings, "sync_delay", sync_delay)
+end
+
+-- A function named script_save will be called when the script is saved
+--
+-- NOTE: This function is usually used for saving extra data (such as in this
+-- case, a hotkey's save data). Settings set via the properties are saved
+-- automatically.
+function script_save(settings)
+ local force_sources_sync_hotkey = obs.obs_hotkey_save(hotkey_id)
+ obs.obs_data_set_array(settings, "force_sources_sync_hotkey", force_sources_sync_hotkey)
+ obs.obs_data_array_release(force_sources_sync_hotkey)
+
+ -- script counter
+ local hotkey_add_stream = obs.obs_hotkey_save(hotkey_id_1)
+ local hotkey_dec_stream = obs.obs_hotkey_save(hotkey_id_2)
+ obs.obs_data_set_array(settings, "add_stream_hotkey", hotkey_add_stream)
+ obs.obs_data_set_array(settings, "dec_stream_hotkey", hotkey_dec_stream)
+ obs.obs_data_array_release(hotkey_add_stream)
+ obs.obs_data_array_release(hotkey_dec_stream)
+ --
+end
+
+-- a function named script_load will be called on startup
+function script_load(settings)
+ print("Frame Synchronizer script loaded")
+ -- script counter
+ hotkey_id_1 = obs.obs_hotkey_register_frontend("add_stream_thingy", "Increase Stream Counter", set_text1)
+ hotkey_id_2 = obs.obs_hotkey_register_frontend("dec_stream_thingy", "Decrease Stream Counter", set_text2)
+ hotkey_id_3 = obs.obs_hotkey_register_frontend("reset_stream_thingy", "Reset Stream Counter", reset_counter)
+ local hotkey_add_stream = obs.obs_data_get_array(settings, "add_stream_hotkey")
+ local hotkey_dec_stream = obs.obs_data_get_array(settings, "dec_stream_hotkey")
+ local hotkey_reset_stream = obs.obs_data_get_array(settings, "dec_stream_hotkey")
+ obs.obs_hotkey_load(hotkey_id_1, hotkey_add_stream)
+ obs.obs_hotkey_load(hotkey_id_2, hotkey_dec_stream)
+ obs.obs_hotkey_load(hotkey_id_3, hotkey_reset_stream)
+ obs.obs_data_array_release(hotkey_add_stream)
+ obs.obs_data_array_release(hotkey_dec_stream)
+ obs.obs_data_array_release(hotkey_reset_stream)
+ --
+ activate(true)
+end
+
+--
+-- text field stuff
+--
+-- Function to set the time text
+function set_text1(pressed)
+
+ if not pressed or type(pressed) == 'userdata' then
+ set_text(true)
+ end
+end
+
+function set_text2(pressed)
+
+ if not pressed or type(pressed) == 'userdata' then
+ set_text()
+ end
+end
+
+
+function set_text(increase, value)
+ local counter_source = obs.obs_get_source_by_name(stream_counter_source_name)
+ if counter_source ~= nil then
+ local counter_props = obs.obs_source_get_settings(counter_source)
+ local counter = math.floor(obs.obs_data_get_string(counter_props, 'text'))
+ if increase then
+ print("increasing stream counter")
+ counter = counter + 1
+ else
+ if value then
+ print("setting value")
+ counter = value
+ else
+ print("decreasing stream counter")
+ counter = counter - 1
+ end
+ end
+ print(counter)
+ obs.obs_data_set_string(counter_props, "text", math.floor(counter))
+ obs.obs_source_update(counter_source, counter_props)
+ obs.obs_data_release(counter_props)
+ obs.obs_source_release(counter_source)
+ end
+ force_apply = true
+ return true
+end
+
+function reset_counter()
+ set_text(nil, 0)
+ end
+
\ No newline at end of file