obs = obslua -- Version bump local script_version = "v1.1" -- User settings settings = { text_source_name = "", prefix = "", suffix = "", seconds_option = "show", test_mode = false, layout_option = "single" -- options: "single", "two", "three" } start_time = nil update_interval = 1 -- seconds timer_active = false test_mode_active = false -- Helper: Add all OBS text sources to a property list using unversioned IDs local function add_text_sources(p) obs.obs_property_list_add_string(p, "Text source not selected", "") local all_sources = obs.obs_enum_sources() if all_sources then for _, src in ipairs(all_sources) do local sid = obs.obs_source_get_unversioned_id(src) if sid == "text_gdiplus" or sid == "text_ft2_source" then local name = obs.obs_source_get_name(src) obs.obs_property_list_add_string(p, name, name) end end obs.source_list_release(all_sources) end end function script_description() return "Script Description:\n" .. "This script displays your stream uptime in an OBS text source. It can be used to clearly show the duration of a live stream.\n\n" .. "Usage:\n" .. "1. Select a text source from the dropdown list.\n" .. "2. Enter a prefix and a suffix (they will be automatically formatted).\n" .. "3. Choose the desired layout (single-line, two-line, or three-line).\n" .. "4. Set the seconds display option and enable test mode if desired.\n\n" .. "Update Notification (Essential changes since v1.0):\n" .. "• Added a dropdown for text source selection.\n" .. "• Automatic formatting for prefix and suffix.\n" .. "• Multi-line layout options for flexible display." end function script_properties() local props = obs.obs_properties_create() -- Text Source dropdown using the helper function local text_sources = obs.obs_properties_add_list(props, "text_source_name", "Text Source", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING) add_text_sources(text_sources) -- Layout dropdown for text formatting local layout_options = obs.obs_properties_add_list(props, "layout_option", "Text Layout", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING) obs.obs_property_list_add_string(layout_options, "Single Line: Prefix Uptimer Suffix", "single") obs.obs_property_list_add_string(layout_options, "Two Lines: Prefix Uptimer / Suffix", "two") obs.obs_property_list_add_string(layout_options, "Three Lines: Prefix / Uptimer / Suffix", "three") -- Prefix and Suffix text fields obs.obs_properties_add_text(props, "prefix", "Prefix", obs.OBS_TEXT_DEFAULT) obs.obs_properties_add_text(props, "suffix", "Suffix", obs.OBS_TEXT_DEFAULT) -- Seconds Display dropdown local seconds_options = obs.obs_properties_add_list(props, "seconds_option", "Seconds Display", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING) obs.obs_property_list_add_string(seconds_options, "Show", "show") obs.obs_property_list_add_string(seconds_options, "Hide", "hide") obs.obs_property_list_add_string(seconds_options, "Hide After First Minute", "hide_after_first_minute") -- Test Mode checkbox obs.obs_properties_add_bool(props, "test_mode", "Test Mode") return props end function script_update(new_settings) settings.text_source_name = obs.obs_data_get_string(new_settings, "text_source_name") settings.prefix = obs.obs_data_get_string(new_settings, "prefix") settings.suffix = obs.obs_data_get_string(new_settings, "suffix") settings.seconds_option = obs.obs_data_get_string(new_settings, "seconds_option") settings.test_mode = obs.obs_data_get_bool(new_settings, "test_mode") settings.layout_option = obs.obs_data_get_string(new_settings, "layout_option") -- Auto-format prefix: append trailing space if missing if settings.prefix ~= "" and string.sub(settings.prefix, -1) ~= " " then settings.prefix = settings.prefix .. " " end -- Auto-format suffix: prepend leading space if missing if settings.suffix ~= "" and string.sub(settings.suffix, 1, 1) ~= " " then settings.suffix = " " .. settings.suffix end if timer_active then obs.timer_remove(update_text_source) obs.timer_add(update_text_source, update_interval * 1000) end if settings.test_mode and not test_mode_active then start_time = os.time() test_mode_active = true if not timer_active then obs.timer_add(update_text_source, update_interval * 1000) timer_active = true end elseif not settings.test_mode and test_mode_active then start_time = nil test_mode_active = false if timer_active then obs.timer_remove(update_text_source) timer_active = false end end update_text_source() end function script_defaults(settings_data) obs.obs_data_set_default_string(settings_data, "text_source_name", "") obs.obs_data_set_default_string(settings_data, "prefix", "") obs.obs_data_set_default_string(settings_data, "suffix", "") obs.obs_data_set_default_string(settings_data, "seconds_option", "show") obs.obs_data_set_default_bool(settings_data, "test_mode", false) obs.obs_data_set_default_string(settings_data, "layout_option", "single") end function update_text_source() if settings.text_source_name == "" then return end local source = obs.obs_get_source_by_name(settings.text_source_name) if source then local uptime = get_uptime() local final_text = "" if settings.layout_option == "single" then final_text = settings.prefix .. uptime .. settings.suffix elseif settings.layout_option == "two" then final_text = settings.prefix .. uptime .. "\n" .. settings.suffix elseif settings.layout_option == "three" then final_text = settings.prefix .. "\n" .. uptime .. "\n" .. settings.suffix end local settings_data = obs.obs_data_create() obs.obs_data_set_string(settings_data, "text", final_text) obs.obs_source_update(source, settings_data) obs.obs_data_release(settings_data) obs.obs_source_release(source) end end function get_uptime() if not start_time then return settings.seconds_option == "hide" and "00:00" or "00:00:00" end local now = os.time() local elapsed = now - start_time if settings.test_mode then elapsed = os.difftime(now, start_time) end local hours = math.floor(elapsed / 3600) local minutes = math.floor((elapsed % 3600) / 60) local seconds = elapsed % 60 if settings.seconds_option == "hide" or (settings.seconds_option == "hide_after_first_minute" and elapsed >= 60) then return string.format("%02d:%02d", hours, minutes) else return string.format("%02d:%02d:%02d", hours, minutes, seconds) end end function on_event(event) if event == obs.OBS_FRONTEND_EVENT_STREAMING_STARTED then start_time = os.time() if not timer_active then obs.timer_add(update_text_source, update_interval * 1000) timer_active = true end elseif event == obs.OBS_FRONTEND_EVENT_STREAMING_STOPPED then start_time = nil if timer_active then obs.timer_remove(update_text_source) timer_active = false end update_text_source() -- Reset display end end function script_load(settings_data) obs.obs_frontend_add_event_callback(on_event) end function script_unload() if timer_active then obs.timer_remove(update_text_source) end end