obs = obslua my_settings = nil timeFormat = "%02d:%02d:%02d" -- Format used for countdown timer. clockFormat = "%02d:%02d:%02d" -- Format used for current time ("HH:MM:SS"). Adjust if desired, e.g. to omit seconds and/or add adtl. text. cur_seconds = 0 last_text = "" activated = false ----------------------------------------------------------------------------------------------- -- Optional Ending: Hide/Un-Hide Objects -- Hides object defined in the script settings. Hide object should be executed after a -- pause period--not in the standard callback loop. ----------------------------------------------------------------------------------------------- function End_Object_ChangeVisibility(src_name, makeVisible) local aSceneNames = obs.obs_frontend_get_scene_names() for aSceneNum, aSceneName in ipairs(aSceneNames) do local aScene = obs.obs_get_scene_by_name(aSceneName) local sItems = obs.obs_scene_enum_items(aScene) if sItems ~= nil then for _, sItem in ipairs(sItems) do local hideSrce = obs.obs_sceneitem_get_source(sItem) local hideName = obs.obs_source_get_name(hideSrce) local source_id = obs.obs_source_get_unversioned_id(hideSrce) if hideName == src_name then obs.obs_sceneitem_set_visible(sItem, makeVisible) elseif source_id == "group" then -- Only search inside group if it isn't object to be turned off End_ObjectGroup_ChangeVisibility(sItem, src_name, makeVisible) end end end obs.obs_scene_release(aScene) obs.sceneitem_list_release(sItems) end end function End_ObjectGroup_ChangeVisibility(group, src_name, makeVisible) -- The function recursively checks within a group for an object to hide. -- At this time, I am unable to test recursivity. (Groups may not presently -- support nexting?) local gItems = obs.obs_sceneitem_group_enum_items(group) if gItems ~= nil then for _, gItem in ipairs(gItems) do local hideSrce = obs.obs_sceneitem_get_source(gItem) local hideName = obs.obs_source_get_name(hideSrce) local source_id = obs.obs_source_get_unversioned_id(hideSrce) if hideName == src_name then obs.obs_sceneitem_set_visible(gItem, makeVisible) elseif source_id == "group" then -- Only search inside group if it isn't object to be turned off End_ObjectGroup_ChangeVisibility(gItem, src_name, makeVisible) end end end obs.sceneitem_list_release(gItems) end ----------------------------------------------------------------------------------------------- -- Optional Ending: Start Program Scene -- Switches active program scene to scene named in the script settings. Scene trigger should be -- executed after a pause period--not in the standard callback loop. ----------------------------------------------------------------------------------------------- function End_StartProgramScene() local goto_scene = obs.obs_data_get_string(my_settings, "scene") local nScene = obs.obs_get_source_by_name(goto_scene) if nScene then obs.obs_frontend_set_current_scene(nScene) obs.obs_source_release(nScene) else print("Scene not found!") end end ----------------------------------------------------------------------------------------------- -- Initialization: Start The Time -- Function defines how many seconds to countdown to until the desired program start time. -- The standard callback loop is executed every 1000 ms. (This could cause an error in the -- timer function if the callback is delayed. If so, timer may need to be reinitialized.) ----------------------------------------------------------------------------------------------- function StartTheTimer() -- Get current system date and time local start_time = obs.obs_data_get_string(my_settings, "start_time") local dateTable = os.date("*t") local pattern = "(%d+):(%d+):(%d+)" local hour, minute, second = start_time:match(pattern) -- Set countdown end timer to Start Time on today's date dateTable.hour = hour dateTable.min = minute dateTable.sec = second -- Determine total_seconds based on the current OS time value local total_seconds = os.difftime(os.time(dateTable), os.time()) -- If deadline has already passed, set to tomorrow's date if total_seconds < 0 then total_seconds = total_seconds + 24*60*60 end cur_seconds = total_seconds end ----------------------------------------------------------------------------------------------- -- Function: Set Time Text -- Function assigns the updated value to the selected text object. If the text value has not -- changed since the previous iteration, object is not modified. -- Function is used during and at end of countdown only. After countdown, another function is -- used if the script is set to show the current time. ----------------------------------------------------------------------------------------------- function set_time_text() local text = "" local stop_text = obs.obs_data_get_string(my_settings, "stop_text") local endText = obs.obs_data_get_int(my_settings, "TextEnd") local endHide = obs.obs_data_get_bool(my_settings, "HideEnd") local endVisi = obs.obs_data_get_bool(my_settings, "VisiEnd") local endGoto = obs.obs_data_get_bool(my_settings, "GotoEnd") local ClockPause = obs.obs_data_get_int(my_settings, "ClockPause") local HidePause = obs.obs_data_get_int(my_settings, "HidePause") local VisiPause = obs.obs_data_get_int(my_settings, "VisiPause") local ScenePause = obs.obs_data_get_int(my_settings, "ScenePause") if cur_seconds < 1 then if endText == 1 then -- 1: Show Final Text text = stop_text elseif endText == 3 then -- 3: Show Zero text = string.format(timeFormat, 0, 0, 0) elseif endText == 2 then -- 2: Show Clock -- If end text is to show clock, countdown will remain at zero seconds through pause period. text = string.format(timeFormat, 0, 0, 0) else --ERROR CONDITION: Does not match known value. text = stop_text print("Stop Error: Unknow value in stop text value") end if endText == 2 then -- 2: Show Clock obs.timer_add(pause_clock_callback, ClockPause) end if endHide then obs.timer_add(pause_hide_callback, HidePause) end if endVisi then obs.timer_add(pause_visi_callback, VisiPause) end if endGoto then obs.timer_add(pause_goto_callback, ScenePause) end else local seconds = math.floor(cur_seconds % 60) local total_minutes = math.floor(cur_seconds / 60) local minutes = math.floor(total_minutes % 60) local hours = math.floor(total_minutes / 60) text = string.format(timeFormat, hours, minutes, seconds) end -- If the text value has changed since last iteration, update the text value if text ~= last_text then local text_src_name = obs.obs_data_get_string(my_settings, "text_source_name") local source = obs.obs_get_source_by_name(text_src_name) if source ~= nil then local settings = obs.obs_data_create() obs.obs_data_set_string(settings, "text", text) obs.obs_source_update(source, settings) obs.obs_data_release(settings) obs.obs_source_release(source) end end last_text = text end function timer_callback() cur_seconds = cur_seconds - 1 -- Disable standard callback function if at zero seconds. if cur_seconds <= 0 then obs.remove_current_callback() cur_seconds = 0 end set_time_text() end function clock_callback() local dateTable = os.date("*t") text = string.format(clockFormat, dateTable.hour, dateTable.min, dateTable.sec) -- If the text value has changed since last iteration, update the text value if text ~= last_text then local text_src_name = obs.obs_data_get_string(my_settings, "text_source_name") local source = obs.obs_get_source_by_name(text_src_name) if source ~= nil then local settings = obs.obs_data_create() obs.obs_data_set_string(settings, "text", text) obs.obs_source_update(source, settings) obs.obs_data_release(settings) obs.obs_source_release(source) end end last_text = text end function pause_clock_callback() obs.timer_remove(pause_clock_callback) clock_callback() obs.timer_add(clock_callback, 250) end function pause_hide_callback() obs.timer_remove(pause_hide_callback) local hide_src_name = obs.obs_data_get_string(my_settings, "hide_source_name") End_Object_ChangeVisibility(hide_src_name, false) end function pause_visi_callback() obs.timer_remove(pause_visi_callback) local visi_src_name = obs.obs_data_get_string(my_settings, "visi_source_name") End_Object_ChangeVisibility(visi_src_name, true) end function pause_goto_callback() obs.timer_remove(pause_goto_callback) End_StartProgramScene() end function activate(activating) if activated == activating then return end activated = activating if activating then StartTheTimer() set_time_text() obs.timer_add(timer_callback, 1000) else obs.timer_remove(timer_callback) obs.timer_remove(clock_callback) end end function activate_signal(cd, activating) local source = obs.calldata_source(cd, "source") local text_src_name = obs.obs_data_get_string(my_settings, "text_source_name") if source ~= nil then local name = obs.obs_source_get_name(source) if (name == text_src_name) then activate(activating) end end end function source_activated(cd) activate_signal(cd, true) end function source_deactivated(cd) activate_signal(cd, false) end function start_button_clicked(props, p) activate(true) return false end function stop_button_clicked(props, p) activate(false) return false end ------------------------------------------------------------------------------------------------- function script_properties() local props = obs.obs_properties_create() -- Start Time: Deadline for program to begin. Program expects 24-hr clock (military time). -- Seconds are optional. obs.obs_properties_add_text(props, "start_time", "Start Time (HH:MM:SS)", obs.OBS_TEXT_DEFAULT) -- Text Source Name: Name of the text object that timer information will be placed into. local p = obs.obs_properties_add_list(props, "text_source_name", "Text Source", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING) 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 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 -- Set Text Value (at End): Text value to be placed in text source after the countdown is -- complete. If Show Text option is selected, a text property below -- applies. If Show Clock option is selected, the current time will -- begin showing after a pause. Otherwise, the text will remain at -- zero (00:00:00). p = obs.obs_properties_add_list(props, "TextEnd", "Set Text Value (at End)", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT) MY_OPTIONS = {"Show Final Text", "Show Clock", "Show Zero"} for i,v in ipairs(MY_OPTIONS) do obslua.obs_property_list_add_int(p, v, i) end obs.obs_property_set_modified_callback(p, trigger_settings_modes_changed) -- Pause Time: Time in milliseconds to pause after the countdown reaches zero before triggering -- any applicable hide/un-hide/scene transition event(s). Countdown will show final -- text value (00:00:00 or final text) for this period of time. Similar pause values -- are available at each trigger event. obs.obs_properties_add_int(props, "ClockPause", "Clock/Text Delay (ms)", 0, 120000, 1) -- Final Text: Text value to show after countdown reaches zero. Final test is only used if -- Show Text option above is selected. Otherwise, "00:00:00" will show during the -- pause period. (If selected, a clock will show after that.) obs.obs_properties_add_text(props, "stop_text", "Final Text", obs.OBS_TEXT_DEFAULT) -- Hide Source at End: Boolean value of whether to hide the object selected below. p = obs.obs_properties_add_bool(props, "HideEnd", "Hide Object at End") obs.obs_property_set_modified_callback(p, trigger_settings_modes_changed) obs.obs_properties_add_int(props, "HidePause", "Trigger Delay (ms)", 0, 120000, 1) -- Hide Source Name: Name of the object that will be hidden at the end of the timer. This -- value is only relevant if the end function is set to Hide Object. If -- no value is placed in this setting when initialized, the Text Source -- Name will be used. (Hiding a group is acceptable--and very useful.) p = obs.obs_properties_add_list(props, "hide_source_name", "Source Object", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING) if sources ~= nil then for _, source in ipairs(sources) do source_id = obs.obs_source_get_unversioned_id(source) local name = obs.obs_source_get_name(source) obs.obs_property_list_add_string(p, name, name) end end -- Un-Hide Source at End: Boolean value of whether to make visible the object selected below. local p = obs.obs_properties_add_bool(props, "VisiEnd", "Un-Hide Object at End") obs.obs_property_set_modified_callback(p, trigger_settings_modes_changed) obs.obs_properties_add_int(props, "VisiPause", "Trigger Delay (ms)", 0, 120000, 1) -- Un-Hide Source Name: Name of the object that will be made visible at the end of the -- timer. This value is only relevant if the end function is set to -- un-hide an object. (Un-hiding a group is acceptable--and very -- useful. Un-hide may be used on one object and hide on another.) p = obs.obs_properties_add_list(props, "visi_source_name", "Source Object", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING) if sources ~= nil then for _, source in ipairs(sources) do source_id = obs.obs_source_get_unversioned_id(source) local name = obs.obs_source_get_name(source) obs.obs_property_list_add_string(p, name, name) end end -- Go to Program Scene: Boolean value of whether to transition to another scene at the -- end of the countdown. p = obs.obs_properties_add_bool(props, "GotoEnd", "Go to Program Scene at End") obs.obs_property_set_modified_callback(p, trigger_settings_modes_changed) obs.obs_properties_add_int(props, "ScenePause", "Trigger Delay (ms)", 0, 120000, 1) -- Program Scene: Name of the scene that will be activated as the program scene at the end -- of the timer. This value is only relevant if the end function is set to -- Start Program Scene. local s = obs.obs_properties_add_list(props, "scene", "Program Scene", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING) local sceneNames = obs.obs_frontend_get_scene_names() if sceneNames ~= nil then for _, sceneName in ipairs(sceneNames) do obs.obs_property_list_add_string(s, sceneName, sceneName) end end obs.obs_properties_add_button(props, "start_button", "Start Count", start_button_clicked) obs.obs_properties_add_button(props, "stop_count", "Stop Count", stop_button_clicked) obs.source_list_release(sources) obslua.obs_properties_apply_settings(props, my_settings) return props end function trigger_settings_modes_changed(props, property, settings) local mode = obs.obs_data_get_bool(settings, "HideEnd") obs.obs_property_set_visible(obslua.obs_properties_get(props, "HidePause"), mode) obs.obs_property_set_visible(obslua.obs_properties_get(props, "hide_source_name"), mode) mode = obs.obs_data_get_bool(settings, "VisiEnd") obs.obs_property_set_visible(obslua.obs_properties_get(props, "VisiPause"), mode) obs.obs_property_set_visible(obslua.obs_properties_get(props, "visi_source_name"), mode) mode = obs.obs_data_get_bool(settings, "GotoEnd") obs.obs_property_set_visible(obslua.obs_properties_get(props, "ScenePause"), mode) obs.obs_property_set_visible(obslua.obs_properties_get(props, "scene"), mode) mode = obs.obs_data_get_int(settings, "TextEnd") obs.obs_property_set_visible(obs.obs_properties_get(props, "stop_text"), mode==1) obs.obs_property_set_visible(obs.obs_properties_get(props, "ClockPause"), mode<3) if mode==1 then obs.obs_property_set_description(obs.obs_properties_get(props, "ClockPause"), "Text Delay (ms)") elseif mode==2 then obs.obs_property_set_description(obs.obs_properties_get(props, "ClockPause"), "Clock Delay (ms)") end return true end function script_description() return [[

Start Timer

Sets a text source to act as a countdown to a set time of day, when the source is active. The Start Time represents the begining time for your event. Optionally, object may be hidden/made visible at the end; an alternate program scene may be triggered; or the text may become an active clock.

Note: Start Time should be written in a 24-hour clock (military time) format. Times will not be adjusted for AM/PM designations.


]] end function script_update(settings) my_settings = settings end function script_defaults(settings) obs.obs_data_set_default_string(settings, "start_time", "09:00:00") obs.obs_data_set_default_string(settings, "stop_text", "Starting Soon") obs.obs_data_set_default_string(settings, "TextEnd", "Show Text") obs.obs_data_set_default_bool(settings, "HideEnd", false) obs.obs_data_set_default_bool(settings, "VisiEnd", false) obs.obs_data_set_default_bool(settings, "GotoEnd", false) obs.obs_data_set_default_int(settings, "ClockPause", 5000) obs.obs_data_set_default_int(settings, "HidePause", 5000) obs.obs_data_set_default_int(settings, "VisiPause", 5000) obs.obs_data_set_default_int(settings, "ScenePause", 5000) end function script_load(settings) local sh = obs.obs_get_signal_handler() obs.signal_handler_connect(sh, "source_activate", source_activated) obs.signal_handler_connect(sh, "source_deactivate", source_deactivated) end