--[[ ---------------------------------------------------------------------------------------------------------------------------------------- Open Broadcaster Software®️ OBS > Tools > Scripts @midnight-studios Stopwatch *************************************************************************************************************************************** Version 5 Published / Released: 2023-05-26 01:07 NEW FEATURES - Add multiple Time stamps - Add multiple media files - Reset time stamp after defined time to normal colour OPTIMIZATION - USER EXPERIENCE & FEATURE ENHANCEMENTS - BUGS - *************************************************************************************************************************************** ]] --Globals obs = obslua; gversion = "5"; luafile = "StopWatch.lua"; obsurl = "comprehensive-stopwatch-countdown-timer.1364/"; patch_notes = "Patch Notes"; icon="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAYAAACpF6WWAAAENElEQVQ4jY1UTUgjZxh+ksl/JuMkMYb4F40bNZqK0KJFqBZqS9ddyl76dyhdKPRQShH2sNDSnnopCz11D10KS/dSKNiDoD2I7KXFQ0XSSGpM1llFMYn5mZiMY2IymfIOhgazXfaDj5n53u975vme531fnaqqeMHxJYCvAOgAlABcAyA1jxLO1tYW1tbWoL+Kd3x8jGg0imw2C0VRWkMEYgNgBeAFYKTFRqOh7aVnE9xwFTSZTGJ7exszMzPQ6XSQZRk8z9P7YrVa/Y5hmKLBYHCpqirW63Wcn5/j7OwMHo9HA6bvNqY2mw1Op1N70qaTkxPkcjmbLMsDZrN5hOO4NxuNhlMUxTFiSCA0FEW5GQ6H/wmHwzfamDavUKlUYDKZAoFA4Gue52/r9f/9v6OjQ5uKojwpFAr3RFF8UCwWjW63OzQ/P/9yGyiBnZ6eEtN3eZ7/9XJZrlQqP2cymcf5fL4QDAbHdTrd2yzLXvd4PD9yHHdLEISFXC7nsdvtuTb3c7kcEokEJiYmhliWtaiqWs5ms4f1el0lE2lOTU0hn8/DYrF09vb23jebze9JkvRXNBqdMpvNaIJaLh1tHScAzpvsSd+joyOkUimEQiFNa4vFAlEU4Xa7HwYCgduFQuHRxsbGx5p+qqq+o/7/SF7uQSaTwcHBgZYdgiBMqKqa2dnZ8S8tLaFcLicIIR6PjzU13Qew+gzPKNEj9JJOp5tag+O41/v7+x/v7u7+sLOzc8BxHN1icXR0dMXlcn3xQhW1v7+PSCSC6enptxwOx3WWZRcbjcbTjY2NAJ1nWRYGgwHj4+OqoigFYnr/UlPlClYFwJ1arVYjU8bGxhZ8Pt9KMxiLxd5gGEbTlTSv1WqQJOmJw+G4RqCfPYfkN4qiFDs7O9HT0/Nqa4BhmKd2u10DrFaruLi4oJmncibQSUCrLHJabDlHzItGo1E7FIvFvg+FQjMmkykkCMK9eDwOivl8PvqhBspxXJAOEujfz2HazzBMdXh4OJNMJoupVGre7/cbBEGor6+vY2RkROsLlwY6jUajS5KkSGvtf0oVemUeAPiDgsFgUHMeQJ3MmZycxNzcnMZWkiT4/f67FJRl+UFrmcYB/N7y3UyLSHOBzNjb20MgEMDg4CC6urqwublJZo12d3ffVRRFEQTh4TNTqlQqaawoTShOVdOsqMPDQ8zOzmqFQK3PZrO91NPTs2U0GkmWG4lEYrWt9cViMSwvL1Ntvw9gRafT/aTX6z8AwFKcuhU5zjDMkNfr/XZgYCBKgMfHx3eSyeSqw+Fob9LEipxMp9MRp9P5uclkWuB5/hOKWa3Wvb6+vjLP8wNer5fXUkRRLkql0ofZbPY3ug019TZQ6jKU0AzD7Iqi+Josy6+4XK6P7Hb7LbvdPkS5SXpXKpU/ZVn+5ezs7FG9Xi9brVZNLr1ej38BVDs6EbSfFQsAAAAASUVORK5CYII="; desc = [[

Advanced Stopwatch

( Version: %s )


Find it on GitHub
]] .. patch_notes ..[[

The Properties for this script will adjust visibility as needed. Some advanced properties will only be visible if the Configuration is set to "Advanced". If the Configuration is set to "Basic" the defined values will still be used, so ensure you define those correctly.

Find help on the OBS Forum Thread.


]]; debug_file = "" debug_file_name = "Debug Log" text_prefix = ""; text_suffix = ""; last_text = ""; custom_time_format = ""; timer_source = ""; countdown_type = ""; backup_folder = ""; import_list = ""; longtimetext_s = ""; longtimetext_p = ""; last_split_data = ""; split_source = ""; active_source = ""; next_scene = ""; stop_text = ""; toggle_mili_trigger = ""; sec_add_1 = ""; sec_add_2 = ""; sec_add_3 = ""; sec_sub_1 = ""; sec_sub_2 = ""; sec_sub_3 = ""; output_file_name = "-backup($date_stamp).json"; font_normal = "#ffffff"; font_dimmed = "#bfbbbf"; font_highlight = "#fffdcf"; add_limit_note_source = ""; sub_limit_note_source = ""; note_source_marker_a = ""; note_source_marker_b = ""; audio_marker_b = ""; audio_marker_a = ""; current_count_direction = "UP"; count_orientation = "NORMAL"; last_orientation = "NORMAL"; debug_entry = 0; debug_entry = 0; add_limit_note_source_visible = 0; sub_limit_note_source_visible = 0; sources_loaded = 0; timer_manipulation = 1; sec_add_limit = 0; sec_add_limit_used = 0; sec_sub_limit_used = 0; sec_sub_limit = 0; total_sources = 0; sw_hours_saved = 0; sw_minutes_saved = 0; sw_seconds_saved = 0; sw_milliseconds_saved = 0; split_type = 2; current_seconds = 0; cycle_direction = 1; default_seconds = 0; split_count = 0; timer_year = 0; timer_month = 0; timer_day = 0; timer_hours = 0; timer_minutes = 0; timer_seconds = 0; timer_mode = 0; last_timer_mode = 0; timer_format = 1; timer_display = 1; start_recording = 0; media_playback_limit = 0; recording_type = 0; enable_marker_notes = 1; orig_time = 0; time_frequency = 0; completed_cycles = 0; ns_last = 0; cycle_index = 1; timer_cycle = 10; --milliseconds split_itm = {}; ignore_list = { "", "none", "None", "Select", "select", "list" }; split_data = nil; minute_format = nil; local ctx = { propsDef = nil, -- property definition propsDefSrc = nil, -- property definition (source scene) propsSet = nil, -- property settings (model) propsVal = {}, -- property values propsValSrc = nil, -- property values (first source scene) }; props = nil; timer_mode_changed = false; debug_enabled = true; -- careful, may use more sytem memory script_ready = false; set_timer_activated = false; color_normal_updated = false; activated = false; prevent_callback = false; timer_active = false; reset_activated = false; start_on_visible = false; force_reset_on_visible = false; force_reset_on_scene_active = false; active_source_force_visible = false; start_on_scene_active = false; disable_script = false; enable_direction_toggle = false; show_mili = true; timer_expired = true; mili_toggle_triggered = false; direction_changed = false; prevent_negative_time = false; record_timer_set = false; media = { -- table start text_marker_b = "", text_marker_a = "", source_name_audio_marker_b = "", source_name_audio_marker_a = "", source_name_audio_marker_end = "", note_source_marker_a = "", note_source_marker_b = "", note_marker_a = "", note_marker_b = "", activated_marker_b = false, activated_marker_a = false, activated_media_marker_b = false, activated_media_marker_a = false, activated_time_marker_b = 0, activated_time_marker_a = 0, cycle_direction_marker_a = 2; cycle_direction_marker_b = 2; cycle_index_marker_a = 1; -- index from 1-based table cycle_index_marker_b = 1; -- index from 1-based table current_seconds_marker_a = 0, current_seconds_marker_b = 0, duration_marker_a = 0, duration_marker_b = 0, reset_text_marker_a = 0, reset_text_marker_b = 0, duration_marker_end = 0, media_ended_marker_a = false, media_ended_marker_b = false, color_normal = 4294967295, -- 4294967295 0xFFFFFFFF color_marker_a = 4256749, -- 4256749 0x40f3ed color_marker_b = 329050, -- 329050 0x05055a last_state_marker_a = obs.OBS_MEDIA_STATE_NONE, last_state_marker_b = obs.OBS_MEDIA_STATE_NONE }; -- table end selected_source_list = {}; hotkey_id_reset = obs.OBS_INVALID_HOTKEY_ID; hotkey_id_pause = obs.OBS_INVALID_HOTKEY_ID; hotkey_id_split = obs.OBS_INVALID_HOTKEY_ID; hotkey_id_mili = obs.OBS_INVALID_HOTKEY_ID; hotkey_id_direction = obs.OBS_INVALID_HOTKEY_ID; hotkey_id_sec_add_1 = obs.OBS_INVALID_HOTKEY_ID; hotkey_id_sec_add_2 = obs.OBS_INVALID_HOTKEY_ID; hotkey_id_sec_add_3 = obs.OBS_INVALID_HOTKEY_ID; hotkey_id_sec_sub_1 = obs.OBS_INVALID_HOTKEY_ID; hotkey_id_sec_sub_2 = obs.OBS_INVALID_HOTKEY_ID; hotkey_id_sec_sub_3 = obs.OBS_INVALID_HOTKEY_ID; --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: A function named script_description returns the description shown to the user Credit: OBS Modified: User dependent function: Script Description type: OBS Core input type: data returns: string ---------------------------------------------------------------------------------------------------------------------------------------- ]] function script_description() debug_log( 'script_description() -- function variable names: ' ) return string.format( desc, tostring( gversion ) ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Get the name of this script Credit: midnight-studios, et al Modified: function: regular expression type: Support input type: string returns: string ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function filename() local str = debug.getinfo(2).source:sub(2); return str:match("^.*/(.*).lua$") or str; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Dumps input to string, if input is a table it returns the expanded table Credit: et al Modified: yes function: type: Support (debug tool) input type: variable returns: string ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function pre_dump(input) if type(input) ~= "table" then return tostring(input) else local tbl = {} for key, value in pairs(input) do local keyStr = (type(key) ~= "number") and "'" .. key .. "'" or tostring(key) tbl[#tbl + 1] = "[" .. keyStr .. "] = " .. pre_dump(value) end return "{ " .. table.concat(tbl, ", ") .. " }" end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Use this to create a Script Log Output used in testing Credit: et al Modified: No function: type: Support (debug tool) input type: string returns: print(string) ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function log( name, msg ) if msg ~= nil then msg = " > " .. tostring( msg ); else msg = ""; end; obs.script_log( obs.LOG_DEBUG, tostring( name ) .. msg ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function debug_log( content ) if not debug_enabled then return end if debug_file == "" then debug_file = create_debug_file( debug_file_name, content ) else update_debug_file( debug_file, content ) end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function create_debug_file( input_file_name, content ) content = content or string.format( "%s [%s]\n", "Debug Information", os.date("%Y-%m-%d_%H.%M.%S")) local file_name = string.format( "%s-%s[%s]%s", filename(), input_file_name, os.date("%Y-%m-%d_%H.%M.%S"), ".txt"); -- set output path as the script path by default local script_path = script_path(); local output_path = script_path .. file_name; -- if specified output path exists, then set this as the new output path output_path = script_path .. file_name; output_path = output_path:gsub( [[\]], "/" ); log( "create_debug_file", output_path ) -- Open file in write mode, this will create the file if it does not exist local file = io.open( output_path, "w" ) -- If the file has been opened successfully if file then -- Write content to the file file:write( content ) -- Close the file file:close() else -- Print error message print("Failed to open file " .. file_name .. " for writing") end return output_path; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function rewrite_line_debug_file( filename, line_num, content ) -- Store all lines in memory local lines = {} -- Open the file in read mode local file = io.open(filename, 'r') -- If the file has been opened successfully if file then -- Loop over all lines in the file for line in file:lines() do table.insert(lines, line) end -- Close the file file:close() else -- Print error message print("Failed to open file " .. filename .. " for reading") end -- Replace the line at the specified line number if line_num <= #lines then lines[line_num] = content end -- Open the file in write mode file = io.open(filename, 'w') -- If the file has been opened successfully if file then for i = 1, #lines do -- Only add a newline if it's not the last line if i < #lines then file:write(lines[i] .. "\n") else file:write(lines[i]) end end -- Close the file file:close() else -- Print error message print("Failed to open file " .. filename .. " for writing") end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function update_debug_file( filename, content ) if filename ~= nil then debug_entry = debug_entry + 1 content = tostring(debug_entry) .. ") " .. string.rep( " ", string.len(debug_entry) ) .. content -- Open file in append mode local file = io.open( filename, "a" ) -- If the file has been opened successfully if file then -- Write new content on a new line file:write( "\n" .. content ) -- Close the file file:close() else -- Print error message print("Failed to open file " .. tostring(filename) .. " for appending. Could not add content: " .. pre_dump(content)) end -- adds unnecessary processing --rewrite_line_debug_file( filename, 2, "Last item entered: " .. content ) else --print("Failed " .. tostring(filename) .. " Does not exist. Could not add content: " .. pre_dump(content)) end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Builds a table by splitting a string by defined character or sequence of characters marking the beginning or end of a unit of data. That which delimits, that separates. Credit: midnight-studios, et al Modified: function: breaks string into sections by a reference that is returned in a table type: input type: string, delimiter returns: table ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function explode_alt( str, delim ) debug_log( 'explode_alt(' .. pre_dump(str) .. ", " .. pre_dump(delim) .. ') -- function variable names: str, delim ' ) local tbl, index; tbl = {}; index = 0; if( #str == 1 ) then return {str} end; -- returns a table with the input string as the only value while true do local trace_index = string.find( str, delim, index, true ); -- find the next d in the string if trace_index ~= nil then -- if "not not" found then.. table.insert( tbl, string.sub( str, index, trace_index - 1 ) ); -- Save it in our array. index = trace_index + 1; -- save just after where we found it for searching next time. else table.insert( tbl, string.sub( str, index ) ); -- Save what's left in our array. break; -- Break at end, as it should be, according to the lua manual. end; end; return tbl; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Builds a table by splitting a string by defined character or sequence of characters marking the beginning or end of a unit of data. That which delimits, that separates. Credit: midnight-studios, et al Modified: function: breaks string into sections by a reference that is returned in a table type: input type: string, delimiter returns: table ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function explode( str, div ) debug_log( 'explode(' .. pre_dump(str) .. ", " .. pre_dump(delim) .. ') -- function variable names: str, delim ' ) if ( div == nil or div == '' ) or ( str == nil or str == '' )then return {} end local pos, arr = 0, {} for st, sp in function() return string.find(str, div, pos, true) end do table.insert(arr, string.sub(str, pos, st - 1)) pos = sp + 1 end table.insert(arr, string.sub(str, pos)) return arr end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Gives you an iterator that moves through an ordinary table (eg. string keys) but sorted into key sequence. It does that by copying the table keys into a temporary table and sorting that. Possibly being string referenced the list will be compiled chronologically, thus the list names (values) may appear unordered and random. To reorganise and arrange the list alphabetically we will use pairsByKeys(). This will make it easier for the user to review and select the desired item from the list. Credit: https://github.com/nickgammon/mushclient/blob/master/lua/pairsbykeys.lua https://github.com/nickgammon/mushclient/tree/master/lua If you need to sort keys other than strings, see: See: http://lua-users.org/wiki/SortedIteration Modified: Yes, minor changes function: support: This prints the math functions in key order type: sort table input type: table, function (optional) returns: table ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function pairsByKeys( tbl, input_function ) debug_log( 'pairsByKeys(' .. pre_dump( tbl ) .. ", " .. pre_dump( input_function ) .. ')') if type( tbl ) ~= "table" then return tbl end -- instead of creating a new table and inserting all items into it, just get the keys local keys = {} for k in pairs(tbl) do keys[#keys+1] = k end table.sort( keys, input_function ) local i = 0 local iter = function () i = i + 1 if keys[i] == nil then return nil else -- use keys[i] to get value from tbl return keys[i], tbl[keys[i]] end end return iter end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Provides the length of a table (how many items the table contains) Credit: midnight-studios, et al Modified: Author function: Create a table with unique items type: Support input type: table returns: integer ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function tablelength( tbl ) debug_log( 'tablelength(' .. pre_dump(tbl) .. ') -- function variable names: tbl ' ) local count = 0; if type( tbl ) == "table" then -- if the input table is not of type table return 0 for _ in pairs( tbl ) do count = count + 1 end; end; return count; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Remove duplicated values from table Credit: midnight-studios, et al Modified: Author function: Create a table with unique items type: Support input type: table, string returns: bool ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function tableHasKey( tbl, key ) debug_log( 'tableHasKey(' .. pre_dump(tbl) .. ", " .. pre_dump(key) .. ') -- function variable names: tbl, key ' ) if type( tbl ) ~= "table" or key == nil then return false end; -- if the input table is not of type table return bool(false) return tbl[key] ~= nil; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Remove duplicated values from table Credit: midnight-studios, et al Modified: Author function: Create a table with unique items type: Support input type: table, string returns: bool ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function in_table( tbl, input_value ) debug_log( 'in_table(' .. pre_dump(tbl) .. ", " .. pre_dump(input_value) .. ') -- function variable names: tbl, input_value ' ) if type( tbl ) ~= "table" or input_value == nil then return false end; -- if the input table is not of type table return bool(false) for _, value in ipairs( tbl ) do if value == input_value then return true end; end; return false end --[[ ---------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------- ]] function refresh_properties() debug_log( 'refresh_properties() -- function variable names: ' ) return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function checkTimeString(str) debug_log( 'checkTimeString(' .. pre_dump(str) .. ') -- function variable names: str' ) -- Pattern to match the format '00:00:00' local pattern = "^%d%d:%d%d:%d%d$" -- Check if the string matches the pattern if string.match(str, pattern) then return true else return false end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function removeDuplicates( tbl ) debug_log( 'removeDuplicates(' .. pre_dump( tbl ) .. ') -- function variable names: tbl' ) if type( tbl ) ~= "table" or tbl == nil then return tbl end; -- if the input table is not of type table return input local seen = {} local result = {} for _, value in ipairs(tbl) do if not seen[value] then table.insert(result, value) seen[value] = true end end return result end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: A function named script_update will be called when settings are changed Credit: Modified: function: Called upon settings initialization and modification type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function convertToSeconds(timeString) debug_log( 'convertToSeconds(' .. pre_dump(timeString) .. ') -- function variable names: timeString' ) local hours, minutes, seconds = string.match(timeString, "(%d%d):(%d%d):(%d%d)") -- Convert hours, minutes, and seconds to integers hours = tonumber(hours) minutes = tonumber(minutes) seconds = tonumber(seconds) -- Calculate the total seconds local totalSeconds = (hours * 3600) + (minutes * 60) + seconds return totalSeconds end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: set source visibility Credit: midnight-studios, et al Modified: function: Update Text Source type: Support, Render input type: returns: bool ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function set_visible_OLD( source_name, visible ) debug_log( 'set_visible(' .. pre_dump(source_name) .. ", " .. pre_dump(visible) .. ') -- function variable names: source_name, visible ' ) visible = visible or false; local action_completed = false; if source_name == nil then return action_completed; end; if in_table( ignore_list, source_name ) then return action_completed; end; local scenes = obs.obs_frontend_get_scenes(); if scenes ~= nil then for i, scn in ipairs( scenes ) do local scene = obs.obs_scene_from_source( scn ); local sceneitem = obs.obs_scene_find_source_recursive( scene, source_name ); if sceneitem ~= nil then if visible and not obs.obs_sceneitem_visible( sceneitem ) then -- only set visible if not visible obs.obs_sceneitem_set_visible( sceneitem, visible ); end if not visible and obs.obs_sceneitem_visible( sceneitem ) then -- only hide if visible obs.obs_sceneitem_set_visible( sceneitem, visible ); end; action_completed = true; break; end; end; --end for obs.bfree( scn ); obs.source_list_release( scenes ); end; return action_completed; end local function set_visible( source_name, visible ) debug_log( 'set_visible(' .. pre_dump(source_name) .. ", " .. pre_dump(visible) .. ') -- function variable names: source_name, visible ' ) visible = visible or false; local action_completed = false; if source_name == nil then debug_log( 'set_visible: ' .. pre_dump(action_completed) ) return action_completed; end; if in_table( ignore_list, source_name ) then debug_log( 'set_visible: ' .. pre_dump(action_completed) ) return action_completed; end; debug_log( 'continue set_visible(' .. pre_dump(source_name) .. ", " .. pre_dump(visible) .. ') -- function variable names: source_name, visible ' ) local scenes = obs.obs_frontend_get_scenes(); if scenes ~= nil then for i, scn in ipairs( scenes ) do local scene = obs.obs_scene_from_source( scn ); local sceneitem = obs.obs_scene_find_source_recursive( scene, source_name ); if sceneitem ~= nil then obs.obs_sceneitem_set_visible( sceneitem, visible ); action_completed = true; break; end; end; --end for obs.bfree( scn ); obs.source_list_release( scenes ); end; return action_completed; end --[[ ---------------------------------------------------------- Description: check source visibility Credit: midnight-studios, et al Modified: function: Check source visibility state by name type: input type: source name (string) returns: boolean ---------------------------------------------------------- ]] local function is_visible( source_name ) debug_log( 'is_visible(' .. pre_dump(source_name) .. ') -- function variable names: source_name ' ) local isvisible = false; if source_name ~= nil then local scenes = obs.obs_frontend_get_scenes(); if scenes ~= nil then for i, scn in ipairs( scenes ) do local scene = obs.obs_scene_from_source( scn ); local sceneitem = obs.obs_scene_find_source_recursive( scene, source_name ); if sceneitem ~= nil then isvisible = obs.obs_sceneitem_visible( sceneitem ); break; end; end; --end for obs.bfree( scn ); obs.source_list_release( scenes ); end; --end scenes ~= nil end; debug_log( 'is_visible: ' .. pre_dump(isvisible) ) return isvisible; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Function to compare two time strings Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function compareTimeStrings( timeString1, timeString2 ) debug_log( 'compareTimeStrings(' .. pre_dump(timeString1) .. ", " .. pre_dump(timeString2) .. ') -- function variable names: timeString1, timeString2' ) -- Extract hours, minutes, and seconds from time strings local hours1, minutes1, seconds1 = string.match(timeString1, "(%d%d):(%d%d):(%d%d)") local hours2, minutes2, seconds2 = string.match(timeString2, "(%d%d):(%d%d):(%d%d)") -- Convert hours, minutes, and seconds to integers hours1 = tonumber(hours1) minutes1 = tonumber(minutes1) seconds1 = tonumber(seconds1) hours2 = tonumber(hours2) minutes2 = tonumber(minutes2) seconds2 = tonumber(seconds2) -- Calculate the total seconds for each time string local totalSeconds1 = (hours1 * 3600) + (minutes1 * 60) + seconds1 local totalSeconds2 = (hours2 * 3600) + (minutes2 * 60) + seconds2 -- Compare the total seconds return totalSeconds1 < totalSeconds2 end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Function to sort the table items Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function sortTimeTable( tbl ) debug_log( 'sortTimeTable(' .. pre_dump(tbl) .. ') -- function variable names: tbl' ) if type( tbl ) ~= "table" or tbl == nil then return tbl end; -- if the input table is not of type table return input table.sort(tbl, compareTimeStrings) end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: custom function we use this to get a count of all sources Credit: Modified: function: type: input type: returns: interger ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function count_sources() debug_log( 'count_sources() -- function variable names: ' ) local sources = obs.obs_enum_sources(); local i = 0; if sources ~= nil then for _, source in ipairs( sources ) do -- ipairs cycles auto incrimented items i = i + 1; end obs.bfree(source); -- free memory, release source as it is no longer needed end; obs.source_list_release( sources ); -- free memory, release total_sources = i; return i; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function getNextItemMatchingTime( tbl, currentTime ) debug_log( 'getNextItemMatchingTime(' .. pre_dump(tbl) .. ", " .. pre_dump(currentTime) .. ') -- function variable names: tbl, currentTime ' ) if type( tbl ) ~= "table" or tbl == nil then return nil end; -- if the input table is not of type table return input local nextItem = nil local currentTimeString = string.format( "%02d:%02d:%02d", math.floor( currentTime / 3600 ), math.floor( ( currentTime % 3600 ) / 60 ), currentTime % 60 ) for _, value in ipairs( tbl ) do if value > currentTimeString then nextItem = value break end end if tbl[1] ~= nil and nextItem == nil then -- loop to first item -- nextItem = tbl[1] end return nextItem end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function getPreviousItemMatchingTime( table, currentTime ) debug_log( 'getPreviousItemMatchingTime(' .. pre_dump(table) .. ", " .. pre_dump(currentTime) .. ') -- function variable names: table, currentTime ' ) local previousItem = nil local currentTimeString = string.format( "%02d:%02d:%02d", math.floor( currentTime / 3600 ), math.floor( ( currentTime % 3600 ) / 60 ), currentTime % 60 ) for _, value in ipairs(table) do if value < currentTimeString then previousItem = value else break end end if table[#table] ~= nil and previousItem == nil then -- loop to last item --previousItem = table[#table] end return previousItem end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Function to convert OBS data array to table obs_data_array_to_table( settings, "reference" ) Description: Grab OBS data array and return in a table Credit: midnight-studios Modified: function: data array to table type: Support input type: Settings, property reference returns: table ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function obs_data_array_to_table( set, item ) debug_log( 'obs_data_array_to_table(' .. pre_dump(set) .. ", " .. pre_dump(item) .. ') -- function variable names: set, item ' ) local array = obs.obs_data_get_array( set, item ); local count = obs.obs_data_array_count( array ); local list = {}; for i = 1, count do local array_item = obs.obs_data_array_item( array, i-1 ); local value = obs.obs_data_get_string( array_item, "value" ); table.insert( list, value ) end; obs.obs_data_array_release( array ); return list; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function getMarkerTime( ref, currentTime ) debug_log( 'getMarkerTime(' .. pre_dump(ref) .. ", " .. pre_dump(currentTime) .. ') -- function variable names: ref, currentTime ' ) --[[ Create a table for a list ]] local result = nil local i = 0; -- create interger variable local list = {}; -- create temporary table variable local data_list = obs_data_array_to_table( ctx.propsSet, "text_arr_" .. ref ); -- fetch obs userdata from property settings and return in table --[[ Build a cycle list We only include valid string formats '00:00:00' any other format will be excluded ]] for key, value in pairs( data_list ) do if value ~= nil and value ~= "" then if checkTimeString( value ) then i = i + 1; table.insert( list, value ) end end end list = removeDuplicates( list ); -- Ensure there are no duplicates sortTimeTable( list ); -- Time runs linear, so order the list to be linear local count = tablelength( list ); -- get how many items are listed if #list >= 1 then media["activated_media_".. ref] = false; -- Signal media has not yet been activated because we now have a new media item. end if current_count_direction == "UP" then result = getNextItemMatchingTime( list, currentTime ); -- basically, see what will be the next marker time else result = getPreviousItemMatchingTime( list, currentTime ); -- basically, see what will be the next marker time end return result end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Function to cycle through a table list containing strings Credit: Modified: function: type: input type: string returns: nothing ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function source_exists_REMOVE_2( source_name ) debug_log( 'source_exists_alt(' .. pre_dump(source_name) .. ') -- function variable names: source_name ' ) local sourceFound = false local sources = obs.obs_enum_sources(); local i = 0; if sources ~= nil then for _, source in ipairs( sources ) do -- ipairs cycles auto incrimented items local name = obs.obs_source_get_name( source ); if name == source_name then sourceFound = true end end obs.bfree(source); -- free memory, release source as it is no longer needed end; obs.source_list_release( sources ); -- free memory, release return sourceFound end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Function to cycle through a table list containing strings Credit: Modified: function: type: input type: string returns: nothing ---------------------------------------------------------------------------------------------------------------------------------------- ]] function source_exists( source_name ) debug_log( 'source_exists(' .. pre_dump(source_name) .. ') -- function variable names: source_name ' ) -- Get all sources local sources = obs.obs_enum_sources() if sources ~= nil then for i, source in ipairs(sources) do local name = obs.obs_source_get_name(source) -- Check if the source name matches the name you're looking for if source_name == source_name then obs.source_list_release(sources) debug_log( 'source ' .. source_name .. ' exists: true' ) return true end end obs.source_list_release(sources) end debug_log( 'source ' .. source_name .. ' exists: false' ) return false end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Function to cycle through a table list containing strings Credit: Modified: function: type: input type: string returns: nothing ---------------------------------------------------------------------------------------------------------------------------------------- ]] function source_exists_REMOVE( source_name ) debug_log( 'source_exists(' .. pre_dump(source_name) .. ') -- function variable names: source_name ' ) local sourceFound = false local source = obs.obs_get_source_by_name( timer_source ) if source ~= nil then -- continue if we have a source sourceFound = true end obs.obs_source_release( source ); -- release source return sourceFound end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Check source type of media if the media is set to loop The source is referenced by name. Credit: Modified: function: type: input type: reference (string) returns: bool ---------------------------------------------------------------------------------------------------------------------------------------- ]] --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Check source type of media if the media is set to loop The source is referenced by name. Credit: Modified: function: type: input type: reference (string) returns: bool ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function is_valid_media_source( source_name ) debug_log( 'is_valid_media_source(' .. pre_dump(source_name) .. ') -- function variable names: source_name ' ) local is_valid = false; if source_name ~= nil and source_name ~= "" then --[[ Increments the source reference counter, use obs_source_release() to release it when complete. we got a source name, let's see if it exist... ]] local source = obs.obs_get_source_by_name( source_name ); -- get source by name debug_log( 'is_valid_media_source get source: ' ) if source ~= nil then -- continue if we have a source debug_log( 'is_valid_media_source we have a source: ' ) local source_id = obs.obs_source_get_unversioned_id( source ); -- get source id debug_log( 'is_valid_media_source check if source_id is matching: ' .. pre_dump(source_id) ) if source_id == "ffmpeg_source" then -- check if source id match that of type we need to focus on is_valid = true end; end; obs.obs_source_release( source ); -- release source end debug_log( 'is_valid_media_source END result: ' .. pre_dump(is_valid) ) return is_valid; -- bool end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: A single function, named 'assign_audio_source_name(property_name, ref)', is responsible for performing a task on two different types of lists. This function utilizes two String Type variables: 'property_name' and 'ref'. The 'property_name' variable indicates the specific list object that will be required, while 'ref' indicates the location where the function is applied. Within the function, there is a method called 'obs_data_array_to_table(ctx.propsSet, property_name)'. This method retrieves the referenced list object and converts it into a table format that is assigned to the variable 'data_list', which is then used within the function for further processing. Credit: Modified: function: type: input type: string returns: nothing ---------------------------------------------------------------------------------------------------------------------------------------- ]] function assign_audio_source_name( property_name, ref ) debug_log( 'assign_audio_source_name(' .. pre_dump(property_name) .. ", " .. pre_dump(ref) .. ') -- function variable names: property_name, ref ' ) -- Convert data array to table local data_list = obs_data_array_to_table( ctx.propsSet, property_name ) -- Create a table to hold the purified data list local audioTable = {} -- Iterate over the data list and validate each source -- Use pairs instead of ipairs because ipairs assumes 1-based array for i, source_name in pairs( data_list ) do if is_valid_media_source( source_name ) then table.insert( audioTable, source_name ) end end -- Get current index of last assigned item local current_index = media["cycle_index_".. ref] -- signal there was a change since the last orientation if count_orientation ~= last_orientation then current_index = count_orientation == "NORMAL" and current_index + 1 or current_index - 1; end last_orientation = count_orientation if current_count_direction == "DOWN" and current_index < 1 then current_index = #audioTable end if current_count_direction == "UP" and current_index > #audioTable then current_index = 1 end if count_orientation == "NORMAL" then -- count_orientation == "NORMAL" or "INVERTED" media["source_name_audio_".. ref] = audioTable[current_index] -- '+1' because audioTable is 1-based end -- Depending on the 'count_orientation', set the next index. if count_orientation == "NORMAL" then -- count_orientation == "NORMAL" or "INVERTED" current_index = ( current_index % #audioTable ) + 1 else current_index = ( current_index - 2 + #audioTable ) % #audioTable + 1 end -- Store the selected index and source_name in global 'media' table media["cycle_index_".. ref] = current_index if count_orientation == "INVERTED" then -- count_orientation == "NORMAL" or "INVERTED" media["source_name_audio_".. ref] = audioTable[current_index] end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Function to cycle through a table list containing strings Credit: Modified: function: type: input type: string returns: nothing ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function reset_audio_source_visibility( property_name, ref ) debug_log( 'reset_audio_source_visibility(' .. pre_dump(property_name) .. ", " .. pre_dump(ref) .. ') -- function variable names: property_name, ref ' ) local data_list = obs_data_array_to_table( ctx.propsSet, property_name ); -- fetch obs userdata from property settings and return in table if data_list ~= nil then for key, value in pairs( data_list ) do if is_valid_media_source( value ) then set_visible( value, false ); end end end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function reset_audio_marker_arr() debug_log( 'reset_audio_marker_arr() -- function variable names: ' ) audio_marker_a = obs.obs_data_get_string( ctx.propsSet, "audio_marker_a" ); audio_marker_b = obs.obs_data_get_string( ctx.propsSet, "audio_marker_b" ); if audio_marker_a == "list" then reset_audio_source_visibility( "audio_marker_a_arr", "marker_a" ); end if audio_marker_b == "list" then reset_audio_source_visibility( "audio_marker_b_arr", "marker_b" ); end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: This function asigns a variable to a global Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function update_time_markers( currentTime ) debug_log( 'update_time_markers(' .. pre_dump(currentTime) .. ') -- function variable names: currentTime ' ) media["text_marker_a"] = getMarkerTime( "marker_a", currentTime ); media["text_marker_b"] = getMarkerTime( "marker_b", currentTime ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function update_media_names( position_reset ) debug_log( 'update_media_names(' .. pre_dump(position_reset) .. ') -- function variable names: position_reset ' ) -- If position_reset is not provided, set it to false position_reset = position_reset or false --[[ If we do not force a complete reset when required, then media names will cycle each time the function is called during the startup or reset stages ]] if position_reset then media["cycle_index_marker_a"] = 1; -- index from 1-based table media["cycle_index_marker_b"] = 1; -- index from 1-based table end --[[ If Marker Audio is set to "Allow Multiple Selections" then it will cycle any strings listed in the Marker Audio List ]] if audio_marker_a == "list" then assign_audio_source_name( "audio_marker_a_arr", "marker_a" ); end if audio_marker_b == "list" then assign_audio_source_name( "audio_marker_b_arr", "marker_b" ); end local temp_ref = "marker_a" debug_log( temp_ref .. ": ".. "["..(count_orientation == "NORMAL" and "Descending" or "Acending").."] Loaded Media:[" .. tostring(media["source_name_audio_" .. temp_ref]) .. "]" .. " Time ["..(count_orientation == "NORMAL" and "Next" or "Previous").."] " .. tostring(media["text_" .. temp_ref]) .. " Index: " .. tostring(media["cycle_index_" .. temp_ref]) ) temp_ref = "marker_b" debug_log( temp_ref .. ": ".. "["..(count_orientation == "NORMAL" and "Descending" or "Acending").."] Loaded Media:[" .. tostring(media["source_name_audio_" .. temp_ref]) .. "]" .. " Time ["..(count_orientation == "NORMAL" and "Next" or "Previous").."] " .. tostring(media["text_" .. temp_ref]) .. " Index: " .. tostring(media["cycle_index_" .. temp_ref]) ) end --[[ ---------------------------------------------------------- Description: This is basically obs.obs_enum_sources() but "Nested Scenes" are not listed in "obs.obs_enum_sources()" Credit: midnight-studios, et al Modified: Author function: Used to build a list from OBS source names into a table type: Support input type: "id", "unversioned_id", "display_name", "source_name" returns: table default with "source_name" or define return_ref: "id" or "unversioned_id" or "display_name" or "source_name" ---------------------------------------------------------- ]] function get_source_list( return_ref ) debug_log( 'get_source_list(' .. pre_dump(return_ref) .. ') -- function variable names: return_ref ' ) local scenes = obs.obs_frontend_get_scenes(); local source_list = {}; local list = {}; local sub = {}; --[[ ]] if scenes ~= nil then --[[ ]] for key_scenesource, value_scenesource in pairs( scenes ) do local scenename = obs.obs_source_get_name( value_scenesource ); local scene = obs.obs_scene_from_source( value_scenesource ); local sceneitems = obs.obs_scene_enum_items( scene ); --[[ ]] local index = 0; for key_sceneitem, value_sceneitem in pairs( sceneitems ) do index = index + 1; sub = {}; local source = obs.obs_sceneitem_get_source( value_sceneitem ); local source_name_parent = obs.obs_source_get_name( source ); local group = obs.obs_group_from_source( source ); local id_parent = obs.obs_source_get_id( source ); local unversioned_id_parent = obs.obs_source_get_unversioned_id( source ); local display_name_parent = obs.obs_source_get_display_name( id_parent ); sub["id"] = id_parent; sub["unversioned_id"] = unversioned_id_parent; sub["display_name"] = display_name_parent; sub["source_name"] = source_name_parent; table.insert( list, sub ) source_list[source_name_parent] = source_name_parent; -- will return this by default if return_ref not defined as the name is a unique id if group ~= nil then local groupitems = obs.obs_scene_enum_items( group ); if groupitems ~= nil then for key_groupitem, value_groupitem in pairs( groupitems ) do index = index + 1; sub = {}; local groupitemsource = obs.obs_sceneitem_get_source( value_groupitem ); local source_name_group = obs.obs_source_get_name( groupitemsource ); local id_group = obs.obs_source_get_id( groupitemsource ); local unversioned_id_group = obs.obs_source_get_unversioned_id( groupitemsource ); local display_name_group = obs.obs_source_get_display_name( id_group ); sub["id"] = id_group; sub["unversioned_id"] = unversioned_id_group; sub["display_name"] = display_name_group; sub["source_name"] = source_name_group; table.insert( list, sub ) source_list[source_name_group] = source_name_group; -- will return this by default if return_ref not defined as the name is a unique id end -- end for obs.sceneitem_list_release( groupitems ); end end end -- end for in pairs( sceneitems ) obs.sceneitem_list_release( sceneitems ); end -- end for in pairs( scenes ) --[[ ]] obs.source_list_release( scenes ); end; -- scenes ~= nil --[[ is "return_ref" defined and a valid (existing) reference? ]] local tmp_list = {}; local found = false; if return_ref ~= nil then for key, value in pairs( list ) do if value ~= nil and key ~= nil then local itm = list[key] if type( itm ) == "table" and itm ~= nil then if tableHasKey( itm, return_ref ) then found = true; tmp_list[itm["source_name"]] = itm[return_ref]; end; end; end end; end; if found then source_list = tmp_list end; return source_list; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: List files Credit: midnight-studios Modified: midnight-studios, et al function: Used to list files with target extension type: directory path, file extension input type: returns: table ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function get_filenames( path, file_extension ) debug_log( 'get_filenames(' .. pre_dump(path) .. ", " .. pre_dump(file_extension) .. ') -- function variable names: path, file_extension ' ) local filenames = {}; local dir = obs.os_opendir( path ); local entry; repeat entry = obs.os_readdir( dir ); if entry then local ext = obs.os_get_path_extension( entry.d_name ); if ext == file_extension then local filename = string.gsub( entry.d_name, ext, "" ); table.insert( filenames, filename ); end end until not entry; obs.os_closedir( dir ); return filenames; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Convert data to json Credit: midnight-studios, et al Modified: Yes, custom params to suit targeted need function: type: Support input type: OBS data (Settings) returns: json file ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function write_to_json( data ) debug_log( 'write_to_json(' .. pre_dump(data) .. ') -- function variable names: data ' ) output_folder = backup_folder; -- convert Windows path to UNIX path local file_name = filename() .. output_file_name:gsub("$date_stamp", os.date("%Y-%m-%d-%H%M")); -- set output path as the script path by default local script_path = script_path(); local output_path = script_path .. file_name; -- if specified output path exists, then set this as the new output path if (output_folder ~= "") then output_path = output_folder .. "/" .. file_name; else output_path = script_path .. file_name; end output_path = output_path:gsub([[\]], "/"); obs.obs_data_erase( data, "backup_folder" ); obs.obs_data_erase( data, "backup_mode" ); return obs.obs_data_save_json( data, output_path ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Assign a default Frequency based on the Frame Rate video_info.base_width video_info.base_height video_info.fps_den video_info.output_width video_info.output_height video_info.range video_info.colorspace Credit: midnight-studios Modified: function: Get obs user defined video frame rate type: Support input type: none returns: double ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function assign_default_frequency() debug_log( 'assign_default_frequency() -- function variable names: ' ) local fps = 60; -- 60 is the maximum supported frame rate local video_info = obs.obs_video_info(); if obs.obs_get_video_info(video_info) then fps = video_info.fps_num; end; time_frequency = ( 1/fps ); debug_log( 'assign default frequency: ' .. pre_dump(time_frequency) ) end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Local variables format_hour, format_minutes, format_seconds, format_mili are initialized to the strings that define the format of hour, minutes, seconds and mili based on whether they are present or not. "time" variable is initialized with the formatted string using string.format and the values of format_hour, format_minutes, format_seconds, format_mili and the input arguments hour, minutes, seconds, and mili. If show_mili is false, then time is re-initialized with the formatted string using string.format and values of format_hour, format_minutes, format_seconds and input arguments hour, minutes, and seconds. Return time. Take the time segments: Hours, Minutes, Seconds, Millisieconds Configure to standard format: HH:MM:SS:FF $function status: in service Credit: midnight-studios Modified: function: Dependency / Support type: input type: 4 variables - "HH" "MM" "SS" "FF" returns: formatted time string: time stamp 00:00:00,00 (HH:MM:SS,FF) ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function config_time( hour, minutes, seconds, mili ) debug_log( 'config_time(' .. pre_dump(hour) .. ", " .. pre_dump(minutes) .. ", " .. pre_dump(seconds) .. ", " .. pre_dump(mili) .. ') -- function variable names: hour, minutes, seconds, mili ' ) local format_hour, format_minutes, format_seconds, format_mili = ( hour and "%02d" or "" ), ( minutes and ":%02d" or "" ), ( seconds and ":%02d" or "" ), ( mili and ",%02d" or "" ); local time = string.format( format_hour..format_minutes..format_seconds..format_mili, hour, minutes, seconds, mili ); --[[ configure for SHOW or Hide millisonds ]] if not show_mili then time = string.format( format_hour..format_minutes..format_seconds, hour, minutes, seconds ); end; debug_log( 'config time: ' .. pre_dump(time) ) return time; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Convert Seconds to hours:minutes:seconds:miliseconds $function status: in service Local variables hour, minutes, seconds, and mili are initialized to 0. If time is greater than 86399 (23:59:59), c_time is calculated as the nearest multiple of 86400 that is less than time and time is updated by subtracting c_time from it. Hour is calculated as the floor division of time by 3600. If hour is less than 10 and trim is true, hour is updated with a leading zero. Minutes are calculated based on whether the custom_time_format has a value of 90. If minutes are greater than or equal to 60, minutes are updated as the remainder after dividing by 90. If minutes are less than 10 and trim is true, minutes are updated with a leading zero. Seconds are calculated as the floor value of time minus the product of hour and 3600 and the product of minutes and 60. If seconds are less than 10 and trim is true, seconds are updated with a leading zero. Miliseconds are calculated as the floor value of time minus the product of hour and 3600 and the product of minutes and 60 and the value of seconds. If miliseconds are less than 10 and trim is true, miliseconds are updated with a leading zero. Local variable "output" is initialized to an empty string. If simplify is true, "output" is updated with the result of calling config_time with hour, minutes, seconds and nil. "output" is updated with the result of calling config_time with hour, minutes, seconds, and miliseconds. Return "output". Credit: Modified: function: type: input type: Double (Seconds / Split Seconds) returns: time stamp 00:00:00,00 (HH:MM:SS,FF) ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function raw_time( time, simplify ) debug_log( 'raw_time(' .. pre_dump(time) .. ", " .. pre_dump(simplify) .. ') -- function variable names: time, simplify ' ) local hour, minutes, seconds, mili = 0, 0, 0, 0; --[[ If there is more than 24 hours in the time value we need to remove the additional time value to leave only a 23:59:59 value. We will do this by calculating days ]] -- If there is more than 24 hours, remove 23:59:59 as it will be in the clock if time > 86399 then -- 23:59:59 local c_time = ( math.floor( ( time ) / 86400 ) * 86400 ); time = time - c_time; end; --[[ ]] hour = math.floor( time/3600 ); if hour < 10 and trim then hour = "0"..hour; end; --[[ check flag: custom_time_format if flag assign a 90 minute unit then apply it, else use default 60 minute unit If there is a use case, this could potentially be expanded here but we will have to make sure the code checks out. ]] if minute_format ~= nil then minutes = math.floor( ( ( time/3600 ) * 3600 ) / 60 ); minutes = minutes % minute_format; else minutes = math.floor( ( time - math.floor( time/3600 )*3600 )/60 ); end if minutes < 10 and trim then minutes = "0"..minutes; end; --[[ ]] seconds = math.floor( time - math.floor( time/3600 )*3600 - math.floor( ( time - math.floor( time/3600 )*3600 )/60 )*60 ); if seconds < 10 and trim then seconds = "0"..seconds; end; --[[ ]] mili = math.floor( ( time - math.floor( time/3600 )*3600 - math.floor( ( time - math.floor( time/3600 )*3600 )/60 )*60 - math.floor( time - math.floor( time/3600 )*3600 - math.floor( ( time - math.floor( time/3600 )*3600 )/60 )*60 ) )*100 ); if mili < 10 and trim then mili = "0"..mili; end --[[ Use this to see if the time stamp matches certain criteria This looks at HH:MM:SS only and is used to match the timer's current time stamp against a user defined time mark that will for example activate Mark A or Mark B ]] local output = ""; if simplify then output = config_time( hour, minutes, seconds, nil ); else output = config_time( hour, minutes, seconds, mili ); end; debug_log( 'raw time: ' .. pre_dump(output) ) return output; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Used this in testing to measure accuracy The Text Source and the Log should produce the same value The Text source is updated by the time function while the debug uses start and end time stamps to get a value Credit: midnight-studios Modified: function: calculate time difference between two points in time type: Support input type: none returns: double ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function get_time_lapsed() debug_log( 'get_time_lapsed() -- function variable names: ' ) local ns = obs.os_gettime_ns(); local delta = ( ns/1000000000.0 ) - ( orig_time/1000000000.0 ); return raw_time( delta ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: The true frequency between cycles varies due to script and system task processing, therefore a static frequency will produce inaccuarte results over time. Start with a default frequency of 1 second devided by the assigned active fps and then update the frequency calculated from the difference between cycles for the previous and current cycle using high-precision system time, in nanoseconds. It should be noted, the frequency is based on the script defined cycle time, which in this case is 10 miliseconds. Based on testing 10 Miliseconds is the fastest cycle supported in OBS lua. Credit: midnight-studios Modified: function: determine the correct fraction of the split second based on frame rate type: Support input type: double returns: double ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function get_frequency( previous ) debug_log( 'get_frequency(' .. pre_dump(previous) .. ') -- function variable names: previous ' ) local ns = obs.os_gettime_ns(); ns_last = ns; local f = ( ns/1000000000.0 ) - ( previous/1000000000.0 ); if f > 1 then f = time_frequency end; debug_log( 'frequency: ' .. pre_dump(f) ) return f; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: midnight-studios Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function timer_remove( input ) debug_log( 'timer_remove(' .. pre_dump(input) .. ') -- function variable names: input' ) if type( input ) ~= "table" then if input ~= nil and type( input ) == "function" then obs.timer_remove( input ); debug_log( 'removed timer (' .. pre_dump(input) .. ')' ) else debug_log( 'requested timer for removal does not exist. Aborting.' ) end else debug_log( 'timer remove items: (' .. pre_dump(#input) .. ')' ) for key, value in pairs( input ) do if value ~= nil and type( input ) == "function" then obs.timer_remove( value ); debug_log( 'removed timer (' .. pre_dump(value) .. ')' ) else debug_log( 'requested timer for removal does not exist. Aborting.' ) end end; end; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: midnight-studios Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function timer_add( input, ms ) debug_log( 'timer_add(' .. pre_dump(input) .. ", " .. pre_dump(ms) .. ') -- function variable names: input' ) obs.timer_add( input, ms ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: midnight-studios Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function remove_all_timers() debug_log( 'remove_all_timers() -- function variable names: ' ) local timerNames = {} table.insert( timerNames, timer_callback ); table.insert( timerNames, sal_timer_callback ); table.insert( timerNames, ssl_timer_callback ); table.insert( timerNames, timer_end_media_end_callback ); table.insert( timerNames, frontend_recording_start_callback ); table.insert( timerNames, reset_text_a_colour_timer_callback ); table.insert( timerNames, reset_text_b_colour_timer_callback ); table.insert( timerNames, marker_a_media_end_callback ); table.insert( timerNames, marker_b_media_end_callback ); timer_remove( timerNames ) end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: This was developed because some tasks were not completing Credit: midnight-studios Modified: function: delayed recording task to allow other tasks to complete type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function frontend_recording_start_callback( ) debug_log( 'frontend_recording_start_callback() -- function variable names: ' ) if not record_timer_set then return end; if not obs.obs_frontend_recording_active() then obs.obs_frontend_recording_start(); end; timer_remove( frontend_recording_start_callback ); record_timer_set = false; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: "Timer Expires" = 1 "Marker A Time" = 2 "Marker B Time" = 3 "Timer Visible" = 4 "Timer Start" = 5 Credit: midnight-studios Modified: function: Start obs call obs_frontend_recording_start() type: input type: reference, milliseconds returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function record( mark, ms ) debug_log( 'record(' .. pre_dump(mark) .. ", " .. pre_dump(ms) .. ') -- function variable names: mark, ms ' ) if obs.obs_frontend_recording_active() then -- if already recording, remove and reset timer frontend_recording_start_callback( ); return; end; if timer_mode ~= 2 or obs.obs_frontend_recording_active() then return end; -- if not countdown or timer active, then exit if start_recording == 1 and mark == recording_type then if not record_timer_set then timer_add( frontend_recording_start_callback, ms ); --< milliseconds record_timer_set = true; end; end; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Convert hours:minutes:seconds to Seconds When the user defines the Hours, Minutes & Seconds we need to convert it to seconds as the timer works on the value "seconds" $function status: in service Credit: midnight-studios Modified: function: convert date, hour, minutes and secods to seconds type: input type: interger for date, time returns: interger (seconds) ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function delta_time( year, month, day, hour, minute, second ) debug_log( 'delta_time(' .. pre_dump(year) .. ", " .. pre_dump(month) .. ", " .. pre_dump(day) .. ", " .. pre_dump(hour) .. ", " .. pre_dump(minute) .. ", " .. pre_dump(second) .. ') -- function variable names: year, month, day, hour, minute, second ' ) local now = os.time(); if ( year == -1 ) then year = os.date( "%Y", now ); end; if ( month == -1 ) then month = os.date( "%m", now ); end; if ( day == -1 ) then day = os.date( "%d", now ); end; local future = os.time{year=year, month=month, day=day, hour=hour, min=minute, sec=second}; local seconds = os.difftime( future, now ); if ( seconds < 0 ) then seconds = 0; end; return seconds; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Take the raw time format "HH:MM:SS:FF" and allow the user to define a custom format. $function status: in service Credit: Modified: midnight-studios function: The timestamp is what we put in, the format is what we want this little princess to be transformed into type: input type: 00:00:00,00 returns: Whatever the format incleded: $T $D $H $M $S $F and anything inbetween ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function format_time_old( timestamp, format ) --[[ table 1, break time stamp in pieces by character reference ":" input: DD:HH:MM:SS,FF result: [DD], [HH], [MM], [SS,FF] ]] local table1 = explode( timestamp, ":" ); -- reference ":" 4 parts if table1 == nil then return timestamp end; -- have result or return input local c = tablelength( table1 ); -- measure table parts (there should be 4: [DD], [HH], [MM], [SS,FF]) --[[ This does something fancy that is needed ]] local _, d = timestamp:gsub(":",""); local _, t = format:gsub("$T",""); local day, hour, minute, seconds, mili = 0, 0, 0, 0, 0; -- start some blank variables that we will need if d == 3 then -- it should be 3 parts by default if the user uses a standard timestamp if tableHasKey( table1, 1 ) then -- day day = table1[1]; end; if tableHasKey( table1, 2 ) then -- hour hour = table1[2]; end if tableHasKey( table1, 3 ) then -- minute minute = table1[3]; end; if tableHasKey( table1, 4 ) then -- seconds seconds = table1[4]; local table2 = explode( table1[4], "," ); if tableHasKey( table2, 1 ) and tableHasKey( table2, 2 ) then -- milliseconds seconds = table2[1]; mili = table2[2]; end; end; end; if d == 2 then -- okay, the user is doing something fancy now and requested a non-standard timestamp if tableHasKey( table1, 1 ) then -- hour hour = table1[1]; end; if tableHasKey( table1, 2 ) then -- minute minute = table1[2]; end; if tableHasKey( table1, 3 ) then -- seconds seconds = table1[3]; local table2 = explode( table1[3], "," ); if tableHasKey( table2, 1 ) and tableHasKey( table2, 2 ) then -- milliseconds seconds = table2[1]; mili = table2[2]; end; end; end; if d == 1 then -- okay, the user is doing something fancy now and requested a non-standard timestamp if tableHasKey( table1, 1 ) then -- minute minute = table1[1]; end; if tableHasKey( table1, 2 ) then -- seconds seconds = table1[2]; local table2 = explode( table1[2], "," ); if tableHasKey( table2, 1 ) and tableHasKey( table2, 2 ) then -- milliseconds seconds = table2[1]; mili = table2[2]; end ; end; end; if tonumber(day) < 10 then day = "0"..day; end; --[[ Athis stage we have some groups to work with: TRIM: identified by $T, if this is found it will remove, trim or otherwise zap all zeros DD: identified by (will replace) $D HH: identified by (will replace) $H MM: identified by (will replace) $M SS: identified by (will replace) $S FF: identified by (will replace) $F ]] timestamp = format:gsub("$T", ""):gsub("$D", day):gsub( "$H", hour):gsub("$M", minute):gsub("$S", seconds):gsub("$F", mili); if not show_mili then format = format:gsub(",$F", ""):gsub("$F", ""); -- remove these if by default timestamp = format:gsub("$T", ""):gsub("$D", day):gsub( "$H", hour):gsub("$M", minute):gsub("$S", seconds); end --[[ If the user wants leading zeros trimmed ]] if t ~= 0 and current_seconds > 0.01 then --local reg = "^[0]+[:]?[0]+[:]?[0]+[:]?[0]?" local reg = "^[0:,]*" -- close, but misses 1 instance timestamp = timestamp:gsub(reg, ""); end --[[ If the user wants end time stamp displayed ]] if current_seconds < 0.01 and ( timer_display == 1 and timer_mode ~= 1 ) then if not in_table({1,5}, timer_format ) then timestamp = "0" end; -- the user wants the timer to end with a reminder that it is Game Over end; if current_seconds < 0.01 and timer_display == 2 then -- else it will show 00:00:00 timestamp = ""; -- the user wants the timer to disapear end ; return timestamp; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Take the raw time format "HH:MM:SS:FF" and allow the user to define a custom format. $function status: in service Credit: Modified: midnight-studios function: The timestamp is what we put in, the format is what we want this little princess to be transformed into type: input type: 00:00:00,00 returns: Whatever the format incleded: $T $D $H $M $S $F and anything inbetween ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function format_time( timestamp, format ) local days, hours, minutes, seconds, mili = "00", "00", "00", "00", "00"; local timeTable = explode( timestamp, ":" ) or {timestamp}; -- reference ":" 4 parts local trim = format:find("$T"); local replacements = {} if #timeTable == 4 then days, hours, minutes, seconds = unpack(timeTable) seconds, mili = unpack(explode(seconds, ",")) elseif #timeTable == 3 then hours, minutes, seconds = unpack(timeTable) seconds, mili = unpack(explode(seconds, ",")) elseif #timeTable == 2 then minutes, seconds = unpack(timeTable) seconds, mili = unpack(explode(seconds, ",")) elseif #timeTable == 1 then seconds = unpack(timeTable) seconds, mili = unpack(explode(seconds, ",")) end if tonumber(days) < 10 then days = "0"..days; end; local replacements = { ["$D"]=days, ["$H"]=hours, ["$M"]=minutes, ["$S"]=seconds } timestamp = format:gsub("$T", "") if show_mili then replacements["$F"]=mili else timestamp = timestamp:gsub("$F", "") end for k, v in pairs(replacements) do timestamp = timestamp:gsub(k, v) end if trim then local reg = "^[0:,]*" -- close, but misses 1 instance timestamp = timestamp:gsub(reg, ""); end local epsilon = 0.0001 -- tolerance value if math.abs( current_seconds ) < epsilon and timestamp:find("^0") then timestamp = timestamp:gsub("^0+:", "") end if math.abs( current_seconds ) < epsilon and ( timer_display == 1 and timer_mode ~= 1 ) then if not in_table( {1,5}, timer_format ) then timestamp = "0" end; -- the user wants the timer to end with a reminder that it is Game Over end; if math.abs( current_seconds) < epsilon and timer_display == 2 then -- else it will show 00:00:00 timestamp = ""; -- the user wants the timer to disapear end; return timestamp end local function format_time_REMOVE(timestamp, format) debug_log( 'format_time(' .. pre_dump(timestamp) .. ", " .. pre_dump(format) .. ') -- function variable names: timestamp, format ' ) --[[ table 1, break time stamp in pieces by character reference ":" input: DD:HH:MM:SS,FF result: [DD], [HH], [MM], [SS,FF] ]] local timeTable = explode( timestamp, ":" ) or {timestamp} -- reference ":" 4 parts if timeTable == nil then return timestamp end; -- have result or return input --[[ This does something fancy that is needed ]] local unpack = table.unpack or unpack -- for compatibility between Lua 5.1 and later versions local days, hours, minutes, seconds, mili = "00", "00", "00", "00", "00" local _, divs = timestamp:gsub(":",""); local _, t = format:gsub("$T",""); --local divs = #table1 - 1 if divs >= 3 then days, hours, minutes, seconds = unpack(timeTable) seconds, mili = unpack(explode(seconds, ",")) elseif divs == 2 then hours, minutes, seconds = unpack(timeTable) seconds, mili = unpack(explode(seconds, ",")) elseif divs == 1 then minute, seconds = unpack(timeTable) seconds, mili = unpack(explode(seconds, ",")) end if tonumber(days) < 10 then days = "0"..days; end; timestamp = format:gsub("$T", "") local replacements = { ["$D"]=days, ["$H"]=hours, ["$M"]=minutes, ["$S"]=seconds, ["$F"]=mili } for k, v in pairs(replacements) do timestamp = timestamp:gsub(k, v) end if not show_mili then timestamp = timestamp:gsub(",?$F", "") end local epsilon = 0.0001 -- tolerance value if math.abs( current_seconds ) < epsilon and timestamp:find("^0") then timestamp = timestamp:gsub("^0+:", "") end if math.abs( current_seconds ) < epsilon and ( timer_display == 1 and timer_mode ~= 1 ) then if not in_table( {1,5}, timer_format ) then timestamp = "0" end; -- the user wants the timer to end with a reminder that it is Game Over end; if math.abs( current_seconds) < epsilon and timer_display == 2 then -- else it will show 00:00:00 timestamp = ""; -- the user wants the timer to disapear end; return timestamp end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function long_time( time ) debug_log( 'long_time(' .. pre_dump(time) .. ') -- function variable names: time ' ) local c_time = time; -- If there is more than 24 hours, remove 23:59:59 as it will be in the clock if time > 86399 then -- 23:59:59 c_time = math.floor( ( time ) / 86400 ); end; if time < 86400 then c_time = 0; end; return c_time; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: This function checks if a string contains the characters "{" and "}" and the character "M" followed by a numeric value If the string meets these conditions, the function returns the numeric value Check to see if a user defined a custom time format and if the format defined a minute allocation. If the user need the minute clock to be for example 90 minutes instead of 60 then the user could add the expression as followes: {M90} will assign a 90 minute value Note, the M90 must be inside brackets to be considered. Credit: Modified: Asking if miliseconds property must be shown or hidden and this is for back end UI function: yer, no type: Support, UI input type: properties, settings returns: bool ---------------------------------------------------------------------------------------------------------------------------------------- ]] function get_minutes_allocation( str ) debug_log( 'get_minutes_allocation(' .. pre_dump(str) .. ') -- function variable names: str ' ) -- Find the first occurrence of a balanced pair of braces in the string local start_index, end_index = string.find(str, "%b{}") -- If no balanced pair of braces is found, return nil if start_index == nil then return nil end -- Extract the substring between the braces local inside = string.sub(str, start_index + 1, end_index - 1) -- Find the first occurrence of the letter "M" followed by one or more digits in the substring local m_index, _ = string.find(inside, "M%d+") -- If the letter "M" and numeric value is not found, return nil if m_index == nil then return nil end -- Convert the numeric value following the letter "M" to a number and return the result return tonumber(string.sub(inside, m_index + 1)) end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: This function uses the string.find function to find the first occurrence of a balanced pair of braces (%b{}) in the input string str. If no such pair is found, the function returns the input string as is. If a pair of braces is found, the function uses string.sub to extract the substrings of str before and after the pair of braces, concatenates them using the .. operator, and returns the result. Credit: Modified: Asking if miliseconds property must be shown or hidden and this is for back end UI function: yer, no type: Support, UI input type: properties, settings returns: bool ---------------------------------------------------------------------------------------------------------------------------------------- ]] function removeBrackets(str) debug_log( 'removeBrackets(' .. pre_dump(str) .. ') -- function variable names: str' ) local start_index, end_index = string.find(str, "%b{}") if start_index == nil then return str end return string.sub(str, 1, start_index - 1) .. string.sub(str, end_index + 1) end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Change color of font for text source Credit: et al Modified: function: Update Text Source (timer text source) type: Support, Render input type: Integer returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function set_timer_text_color( int ) debug_log( 'set_timer_text_color(' .. pre_dump(int) .. ') -- function variable names: int ' ) if in_table( {"Select", "select"}, timer_source ) then return end; -- if timer_source not defined, then return local source = obs.obs_get_source_by_name( timer_source ); -- get source by name if source ~= nil then -- continue if we have a source local settings = obs.obs_source_get_settings( source ); -- get source settings obs.obs_data_set_int( settings, "color", int ); -- update source settings end obs.obs_source_update( source, settings ); -- save source new settings obs.obs_data_release( settings ); -- release settings obs.obs_source_release( source ); -- release source end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Add timer here so we have a global setting Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function reset_text_a_colour_timer_callback() debug_log( 'reset_text_a_colour_timer_callback() -- function variable names: ' ) set_timer_text_color( media["color_normal"] ); timer_remove( reset_text_a_colour_timer_callback ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Remove timer here so we have a global setting Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function reset_text_a_colour_end_timer() debug_log( 'reset_text_a_colour_end_timer() -- function variable names: ' ) timer_remove( reset_text_a_colour_timer_callback ); -- Removing the callback stops the timer end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Add timer here so we have a global setting Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function reset_text_a_colour_start_timer( int ) debug_log( 'reset_text_a_colour_start_timer(' .. pre_dump(int) .. ') -- function variable names: int ' ) if int ~= 0 then timer_add( reset_text_a_colour_timer_callback, int ); --<- milliseconds end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Add timer here so we have a global setting Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function reset_text_b_colour_timer_callback() debug_log( 'reset_text_b_colour_timer_callback() -- function variable names: ' ) set_timer_text_color( media["color_normal"] ); timer_remove( reset_text_b_colour_timer_callback ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Remove timer here so we have a global setting Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function reset_text_b_colour_end_timer() debug_log( 'reset_text_b_colour_end_timer() -- function variable names: ' ) timer_remove( reset_text_b_colour_timer_callback ); -- Removing the callback stops the timer end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Add timer here so we have a global setting Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function reset_text_b_colour_start_timer( int ) debug_log( 'reset_text_b_colour_start_timer(' .. pre_dump(int) .. ') -- function variable names: int ' ) if int ~= 0 then timer_add( reset_text_b_colour_timer_callback, int ); --<- milliseconds end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Callback on properties modification Show/Hide a field in the properties based on a some criteria. In this case, show or hide the field "Toggle Milliseconds" only when required. Credit: Modified: Asking if miliseconds property must be shown or hidden and this is for back end UI function: yer, no type: Support, UI input type: properties, settings returns: bool ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function show_split( props, settings ) debug_log( 'show_split(' .. pre_dump(props) .. ", " .. pre_dump(settings) .. ') -- function variable names: props, settings ' ) local config_value = obs.obs_data_get_int( settings, "config" ); local mode = obs.obs_data_get_int( settings, "timer_mode" ); local shw = false; shw = ( config_value == 2 and mode == 2 and in_table( {1, 2}, timer_format ) ); if ( timer_format == 5 and config_value == 2 and mode == 2 ) then if ( string.find( custom_time_format, "$F" ) ~= nil ) then shw = true; else shw = false; end; end; return shw; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Function to set the source text Credit: et al Modified: function: Update Text Source type: Support, Render input type: target source by name, contents to be added to text contents returns: nothing ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function set_text( source_name, text ) debug_log( 'set_text(' .. pre_dump(source_name) .. ", " .. pre_dump(text) .. ') -- function variable names: source_name, text ' ) if source_name == "Select" or source_name == "select" then return; end; --[[ Increments the source reference counter, use obs_source_release() to release it when complete. ]] local source = obs.obs_get_source_by_name( source_name ); if source ~= nil then local settings = obs.obs_source_get_settings( source ); obs.obs_data_set_string( settings, "text", text ); end; obs.obs_source_update( source, settings ); obs.obs_data_release( settings ); obs.obs_source_release( source ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Set source visibility to hidden Credit: Modified: function: a callback for a timer used to set a source visibility to hidden type: input type: none returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function marker_a_media_end_callback( ) debug_log( 'marker_a_media_end_callback() -- function variable names: ' ) set_visible( media["source_name_audio_marker_a"], false ); timer_remove( marker_a_media_end_callback ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Set source visibility to hidden Credit: Modified: function: a callback for a timer used to set a source visibility to hidden type: input type: none returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function marker_b_media_end_callback( ) debug_log( 'marker_b_media_end_callback() -- function variable names: ' ) set_visible( media["source_name_audio_marker_b"], false ); timer_remove( marker_b_media_end_callback ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function signal_media_ended( cd ) debug_log( 'signal_media_ended(' .. pre_dump(cd) .. ') -- function variable names: cd ' ) --[[ Get source from CallData ]] local source = obs.calldata_source( cd, "source" ); --[[ Found Source? ]] if source ~= nil then local name = obs.obs_source_get_name( source ); --[[ Set Source Visibility to Hidden ]] set_visible( name, false ); end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: OBS, Source Signals, https://obsproject.com/docs/reference-sources.html?highlight=media_ended Modified: function: type: Support input type: ref returns: signal_media_ended ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function disconnect_after_media_end( ref ) debug_log( 'disconnect_after_media_end(' .. pre_dump(ref) .. ') -- function variable names: ref ' ) if source_name == nil then debug_log( 'disconnect_after_media_end source_name nil ' ) return end local source_name = media["source_name_audio_".. ref]; local source = obs.obs_get_source_by_name( source_name ); -- Increments the source reference counter, use obs_source_release() to release it when complete. --[[ Found Source? ]] if source ~= nil then local source_id = obs.obs_source_get_unversioned_id( source ); -- get source id if source_id == "ffmpeg_source" then -- check if source id match that of type we need to focus on --[[ Create a signal handler for the source ]] local sh = obs.obs_source_get_signal_handler( source ); --[[ https://obsproject.com/docs/reference-sources.html?highlight=media_started attach event listener callback [source_signal]: Called when media has ended. ]] obs.signal_handler_connect( sh, "media_ended", signal_media_ended ); end; end; obs.obs_source_release( source ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function start_media_action( source_name, ref ) debug_log( 'start_media_action(' .. pre_dump(source_name) .. ", " .. pre_dump(ref) .. ') -- function variable names: source_name, ref ' ) if in_table( ignore_list, source_name ) then return end; if not timer_active then return end; -- only start_media_action if the timer is active if not media["activated_media_".. ref] then media["current_seconds_".. ref] = math.ceil( current_seconds ); set_visible( source_name, true ); --[[ connect signal handler to ensure we reset the source if the media ended. ]] disconnect_after_media_end( ref ); media["activated_media_".. ref] = true; end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function start_media( source_name, ref ) debug_log( 'start_media(' .. pre_dump(source_name) .. ", " .. pre_dump(ref) .. ') -- function variable names: source_name, ref ' ) start_media_action( source_name, ref ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Change color of font for text source Credit: et al Modified: function: Update Text Source type: Support, Render input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function set_text_note_color( ref ) debug_log( 'set_text_note_color(' .. pre_dump(ref) .. ') -- function variable names: ref ' ) if media["note_source_" .. ref] == "Select" then return end; -- if source not defined, then return local source = obs.obs_get_source_by_name( media["note_source_" .. ref] ); -- get source by name if source ~= nil then -- continue if we have a source local settings = obs.obs_source_get_settings( source ) -- get source settings obs.obs_data_set_string( settings, "text", media["note_".. ref] ); -- update source settings obs.obs_data_set_int( settings, "color", media["color_".. ref] ); -- update source settings end; obs.obs_source_update( source, settings ); -- save source new settings obs.obs_data_release( settings ); -- release settings obs.obs_source_release( source ); -- release source end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function roundSeconds() debug_log( 'roundSeconds() -- function variable names: ' ) local round_seconds = math.ceil( current_seconds ); -- round to nearset upper value --[[ if not Countdown so target Stopwatch, or if the count direction changed and the count direction is positive ]] if current_count_direction == "UP" then round_seconds = math.floor( current_seconds ); -- round to nearset lower value end return round_seconds end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Comapre current time with a time mark reference. If the marker match, then complete required tasks. The tasks include changing the timer text source font colour as defined, setting linked text sources visible or/and hidden and changing linked text sources font colour. enable_marker_notes must be equal to 2 to be used. enable_marker_notes equal to 1 is disabled Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function time_mark_check( ref ) debug_log( 'time_mark_check(' .. pre_dump(ref) .. ') -- function variable names: ref ' ) if not timer_active then return end; -- only allow mark checks if the timer is active --[[ Make sure the trigger is as accurate as possible depending if the timer is counting up or down. ]] local round_seconds = roundSeconds(); local activation_time = media["activated_time_".. ref]; local text_marker = media["text_".. ref]; if activation_time == nil or text_marker == nil then --return; end; -- nothing to activate further if activation_time == nil or text_marker == nil then --return; end; -- nothing to activate further if ( current_count_direction == "UP" and activation_time < round_seconds ) or ( current_count_direction == "DOWN" and activation_time > round_seconds ) then -- a second or more passed media["activated_".. ref] = false; end if raw_time( round_seconds, true ) == text_marker and not media["activated_".. ref] then -- compare current time with marker --[[ Only do this stuff once when first activated, prevent wastage of resources ]] media["activated_".. ref] = true; -- signal already started media["activated_time_".. ref] = round_seconds; -- signal already started if ref == "marker_a" and media["reset_text_marker_a"] ~= nil and media["reset_text_marker_a"] ~= 0 then -- marker notes is enabled and the input reference matches reset_text_a_colour_start_timer( math.floor( media["reset_text_marker_a"] * 1000 ) ); end if ref == "marker_b" and media["reset_text_marker_b"] ~= nil and media["reset_text_marker_b"] ~= 0 then -- marker notes is enabled and the input reference matches reset_text_b_colour_start_timer( math.floor( media["reset_text_marker_b"] * 1000 ) ); end --[[ If Marker notes is enabled and the reference provided match to Marker A, complete some tasks ]] if enable_marker_notes ~= 1 and ref == "marker_a" then -- marker notes is enabled and the input reference matches debug_log( 'time_mark_check error (' .. pre_dump(media["note_source_" .. ref]) .. ')' ) set_visible( media["note_source_" .. ref], true ); -- Set visble the source for the note for marker a --set_visible( media["note_source_marker_b"], false ); -- Set hiden the source for the note for marker b (only show one note at a time) --set_text_note_color( ref ); -- Update the note text font to match the font colour defined for marker a end; --[[ If Marker notes is enabled and the reference provided match to Marker B, complete some tasks ]] if enable_marker_notes ~= 1 and ref == "marker_b" then -- marker notes is enabled and the input reference matches --set_visible( media["note_source_" .. ref], true ); -- Set visble the source for the note for marker b --set_visible( media["note_source_marker_a"], false ); -- Set hiden the source for the note for marker a (only show one note at a time) --set_text_note_color( ref ); -- Update the note text font to match the font colour defined for marker b end; --[[ Update the timer text source font colour to match the defined font colour for the referenced marker This will ensure that the timer text font matches the font colour of the currently displayed note. ]] set_timer_text_color( media["color_".. ref] ); --[[ ]] start_media( media["source_name_audio_".. ref], ref ); --[[ if the user wants OBS to start recording when the marker activates check the reference and activate the timer to initiate recording recording_type: 1 = "Timer Expires", 2 = "Marker A Time", 3 = "Marker B Time", 4 = "Timer Visible", 5 = "Timer Start" ]] --[[ There may be more markers, if so check if there are and upddate variables ]] update_time_markers( round_seconds ); update_media_names(); -- we don't reset because here the list must cycle if ref == "marker_a" then record( 2, 100 ) end; -- an integer reference used to compare with recording_type if ref == "marker_b" then record( 3, 100 ) end; -- an integer reference used to compare with recording_type end; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Check source type of media if the media is set to loop The source is referenced by name. Credit: Modified: function: type: input type: reference (string) returns: bool ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function get_source_looping( source_name ) debug_log( 'get_source_looping(' .. pre_dump(source_name) .. ') -- function variable names: source_name ' ) local property = "looping"; -- we want to check this property setting --[[ Increments the source reference counter, use obs_source_release() to release it when complete. we got a source name, let's see if it exist... ]] local source = obs.obs_get_source_by_name( source_name ); -- get source by name local enabled = false; if source ~= nil then -- continue if we have a source local source_id = obs.obs_source_get_unversioned_id( source ); -- get source id if source_id == "ffmpeg_source" then -- check if source id match that of type we need to focus on local settings = obs.obs_source_get_settings( source ); -- get source settings enabled = obs.obs_data_get_bool( settings, property ); -- check if media source has playback looping enabled end; end; obs.obs_data_release( settings ); -- release settings obs.obs_source_release( source ); -- release source return enabled; -- bool end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Check if the source state changed, if so, set source visble = false Credit: Modified: function: type: tasks input type: ref (string) returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function stop_media_action( ref ) debug_log( 'stop_media_action(' .. pre_dump(ref) .. ') -- function variable names: ref ' ) local source_name = media["source_name_audio_".. ref]; -- assign local variable if in_table( ignore_list, source_name ) and not media["media_ended_".. ref] then return end; -- if source not defined, then return --[[ ref is either Mark A or B Check if ref has duration set and check if media_playback_limit is enabled ]] if media["duration_".. ref] ~= 0 and media_playback_limit ~= 1 then --[[ Increments the source reference counter, use obs_source_release() to release it when complete. we got a source name, let's see if it exist... ]] local source = obs.obs_get_source_by_name( source_name ); if source ~= nil then -- source is valid local state = obs.obs_source_media_get_state( source ); -- get the current state for the source if media["last_state_".. ref] ~= state then -- The state has changed if state == obs.OBS_MEDIA_STATE_PLAYING then --[[ time remaining is calculated differently depending on the timer_mode (count is up or down) ]] local media_time_started = math.ceil( media["current_seconds_".. ref] ); -- round to nearset upper value local media_time_limit = math.floor( media["duration_".. ref] ); -- round to nearset lower value local time_currently = math.ceil( current_seconds ); -- round to nearset upper value local media_time_remaining = 0; -- set integer variable with default value local time_end = false; -- set bool variable with default value if timer_mode == 1 then -- count direction is positive media_time_remaining = media_time_started + media_time_limit; -- time media became active and add playback time limit time_end = ( time_currently >= media_time_remaining ); -- time is equal to or greater than the limit end if timer_mode == 2 then -- count direction is negative media_time_remaining = media_time_started - media_time_limit; -- time media became active and subtract playback time limit time_end = ( time_currently <= media_time_remaining ); -- time is equal to or less than the limit end if time_end then -- bool, has the time limit been reached? media["last_state_".. ref] = state; -- update the ref state global variable because we need to track it media["media_ended_".. ref] = true; -- update the ref media end global variable because we need to track it set_visible( source_name, false ); -- The source visibility must now be changed to hidden end end ; else -- The state has not changed media["last_state_".. ref] = state; -- update the ref state global variable because we need to track it -- the state is currently either stopped or completed, then reset source visibility to hidden if state == obs.OBS_MEDIA_STATE_STOPPED or state == obs.OBS_MEDIA_STATE_ENDED then -- state match? set_visible( source_name, false ); -- The source visibility must now be changed to hidden end; end; -- source state check end end; -- source ~= nil obs.obs_source_release( source ); -- release source from the reference counter end; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Stop Media Playback Credit: OBS, midnight-studios Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function stop_media_playback( source_name ) debug_log( 'stop_media_playback(' .. pre_dump(source_name) .. ') -- function variable names: source_name ' ) if in_table( ignore_list, source_name ) or not is_visible( source_name ) then return end; local source = obs.obs_get_source_by_name( source_name ); if source ~= nil then local source_id = obs.obs_source_get_unversioned_id( source ); -- unversioned_id will not be affected by language settings if source_id ~= "ffmpeg_source" then return end; -- apply this to media type sources only local state = obs.obs_source_media_get_state( source ) -- get the current state for the source if state == obs.OBS_MEDIA_STATE_PLAYING or obs.OBS_MEDIA_STATE_PAUSED then obs.obs_source_media_stop( source ); end; --obs.obs_source_media_get_duration( source ) --obs.obs_source_media_get_time( source ) --obs.obs_source_media_restart( source ) end; obs.obs_source_release( source ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Play / Pause Media Credit: OBS, midnight-studios Modified: function: type: input type: returns: none, play /pause media source ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function pause_play_media( source_name, play ) debug_log( 'pause_play_media(' .. pre_dump(source_name) .. ", " .. pre_dump(play) .. ') -- function variable names: source_name, play ' ) if in_table( ignore_list, source_name ) or not is_visible( source_name ) then return end; local source = obs.obs_get_source_by_name( source_name ); if source ~= nil then local source_id = obs.obs_source_get_unversioned_id( source ); -- unversioned_id will not be affected by language settings if source_id ~= "ffmpeg_source" then return end; -- apply this to media type sources only obs.obs_source_media_play_pause( source, play ); --obs.obs_source_media_get_duration( source ) --obs.obs_source_media_get_time( source ) --obs.obs_source_media_restart( source ) --local state = obs.obs_source_media_get_state( source ) -- get the current state for the source --if state == obs.OBS_MEDIA_STATE_PLAYING then --end --if state == obs.OBS_MEDIA_STATE_PAUSED then --end end obs.obs_source_release( source ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Stop Media is designed to reset the source to its starting state. In other words, make the source invisible again. This sould only happen if the media ended, or if it is looped, then end the media after a defined time. Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function stop_looping_media( ref ) debug_log( 'stop_looping_media(' .. pre_dump(ref) .. ') -- function variable names: ref ' ) local source_name = media["source_name_audio_".. ref]; if get_source_looping( source_name ) then stop_media_playback( source_name ); --[[ We don't need this because we have already attached a source signal hanlder for 'media_ended' that will hide the source if it is ended ]] --set_visible( source_name, false ) -- Set the media source state to hidden. end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Stop Media is designed to reset the source to its starting state. In other words, make the source invisible again. This sould only happen if the media ended, or if it is looped, then end the media after a defined time. Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function stop_media( ref, bypass ) debug_log( 'stop_media(' .. pre_dump(ref) .. ", " .. pre_dump(bypass) .. ') -- function variable names: ref, bypass ' ) if bypass == nil then bypass = false end; if bypass then -- No checks, just stop it set_visible( media["source_name_audio_".. ref], false ); -- Set the media source state to hidden else -- do some checks stop_media_action( ref ); -- handle this request elsewhere end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Function to cycle through a list for sources or scenes Credit: Modified: function: type: input type: string returns: nothing ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function cycle_source_list_by_source_type( source_type ) debug_log( 'cycle_source_list_by_source_type(' .. pre_dump(source_type) .. ') -- function variable names: source_type ' ) --[[ Create a table for a list ]] local i = 0; -- create interger variable local list = {}; -- create temporary table variable local item_list = {}; -- create temporary table variable local data_list = obs_data_array_to_table( ctx.propsSet, "cycle_list" ); -- fetch obs userdata from property settings and return in table local direction = 1; -- create interger variable --[[ handle scenes ]] if source_type ~= "source" then -- string is not equal to "source" as it should be "scene" direction = 1; -- Descend Ascend change direction to 1 or 2 local scenes = obs.obs_frontend_get_scene_names(); -- List Scenes names if scenes ~= nil then for _, scene in ipairs( scenes ) do -- cycle through list items one at a time item_list[scene] = scene; -- add scene name (string) to the table end obs.bfree( scene ); -- free memory, release source as it is no longer needed end else -- List Source names direction = 1;-- Descend Ascend change direction to 1 or 2 local sources = get_source_list(); -- "id" or "unversioned_id" or "display_name" or "source_name" for key, value in pairsByKeys( sources ) do item_list[value] = value; end end --[[ Build a cycle list ]] for key, value in pairs( data_list ) do if in_table( item_list, value ) then i = i + 1; table.insert( list, value ) end end local count = tablelength( list ); if cycle_index > count then cycle_index = 1; end local index = 0; if cycle_direction ~= direction then index = 1; else index = count; end for i = 1, count do local index_match = ( index == cycle_index ); --[[ The list contains all available sources. The value targets the sources requested. Check if the requested source is available ]] if list[i] ~= nil then --[[ Type is "Scene" ]] if source_type ~= "source" then -- if index_match then local scene_source = obs.obs_frontend_get_current_scene(); local name = obs.obs_source_get_name( scene_source ); obs.obs_source_release( scene_source ); local source = obs.obs_get_source_by_name( list[i] ); if source ~= nil then obs.obs_frontend_set_current_scene( source ); end obs.obs_source_release( source ); end --[[ Type is "Source" ]] else set_visible( list[i], index_match ); end if index_match then --[[ force the visibility state of this source. ]] if active_source_force_visible then set_visible( active_source, true ) end; set_text( active_source, list[i] ); end; end; if cycle_direction ~= direction then index = index + 1; else index = index - 1; end; end; cycle_index = cycle_index + 1; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Check if a scene with a specific name has a source with a specific name Credit: midnight-studios, et al Modified: function: check true or false type: Dependency / Support input type: string, string returns: bool ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function scene_name_has_source_name_OLD( scene_name, source_name ) debug_log( 'scene_name_has_source_name(' .. pre_dump(scene_name) .. ", " .. pre_dump(source_name) .. ') -- function variable names: scene_name, source_name ' ) scene_source = obs.obs_get_source_by_name( scene_name ); local scenename = obs.obs_source_get_name( scene_source ); local scene = obs.obs_scene_from_source( scene_source ); local sceneitems = obs.obs_scene_enum_items( scene ); local result = false; for key_sceneitem, value_sceneitem in pairs( sceneitems ) do local source = obs.obs_sceneitem_get_source( value_sceneitem ); local source_name_parent = obs.obs_source_get_name( source ); local group = obs.obs_group_from_source( source ); if source_name_parent == source_name then result = true; break; end if group ~= nil then local groupitems = obs.obs_scene_enum_items( group ); if groupitems ~= nil then for key_groupitem, value_groupitem in pairs( groupitems ) do local groupitemsource = obs.obs_sceneitem_get_source( value_groupitem ); local source_name_group = obs.obs_source_get_name( groupitemsource ); if source_name_group == source_name then result = true; break; end; end; -- end for obs.sceneitem_list_release( groupitems ); end; end; end; -- end for in pairs( sceneitems ) obs.sceneitem_list_release( sceneitems ); obs.obs_source_release( scene_source ); return result; end local function scene_name_has_source_name(scene_name, source_name) debug_log('scene_name_has_source_name(' .. pre_dump(scene_name) .. ", " .. pre_dump(source_name) .. ') -- function variable names: scene_name, source_name ') local scene_source = obs.obs_get_source_by_name(scene_name) local scene = obs.obs_scene_from_source(scene_source) local scene_items = obs.obs_scene_enum_items(scene) for _, scene_item in pairs(scene_items) do local item_source = obs.obs_sceneitem_get_source(scene_item) local item_source_name = obs.obs_source_get_name(item_source) if item_source_name == source_name then obs.sceneitem_list_release(scene_items) obs.obs_source_release(scene_source) return true end local group = obs.obs_group_from_source(item_source) if group then local group_items = obs.obs_scene_enum_items(group) for _, group_item in pairs(group_items) do local group_item_source_name = obs.obs_source_get_name(obs.obs_sceneitem_get_source(group_item)) if group_item_source_name == source_name then obs.sceneitem_list_release(group_items) obs.sceneitem_list_release(scene_items) obs.obs_source_release(scene_source) return true end end obs.sceneitem_list_release(group_items) end end obs.sceneitem_list_release(scene_items) obs.obs_source_release(scene_source) return false end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Called when a scene is activated/deactivated Credit: midnight-studios, et al Modified: function: make a source visible type: Dependency / Support input type: source, bool, start_on_scene_active (global), scene_name_has_source_name() returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function activate_timer_on_scene( source, activating ) debug_log( 'activate_timer_on_scene(' .. pre_dump(source) .. ", " .. pre_dump(activating) .. ') -- function variable names: source, activating ' ) --[[ Reset to starting point if, start_on_scene_active then set to visible ]] if start_on_scene_active and activating then local source_id = obs.obs_source_get_id( source ); local current_scene_source = obs.obs_frontend_get_current_scene(); local current_scene_name = obs.obs_source_get_name( current_scene_source ); obs.obs_source_release( current_scene_source ); if source_id == "scene" then if scene_name_has_source_name( current_scene_name, timer_source ) then if not is_visible( timer_source ) then set_visible( timer_source, true ); end; end; end; end; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Update Properties Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function update_properties_setting_int( reference, value ) debug_log( 'update_properties_setting_int(' .. pre_dump(reference) .. ", " .. pre_dump(value) .. ') -- function variable names: reference, value ' ) --[[ When this is updated it will trigger a callback "property_onchange", let's disable that for a moment. ]] --prevent_callback = true; obs.obs_data_set_int( ctx.propsSet, reference, value ); obs.obs_properties_apply_settings( ctx.propsDef, ctx.propsSet ); --[[ When this is updated it will trigger a callback "property_onchange", let's enable it again ]] --prevent_callback = false; return true end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Update Properties Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function update_prop_settings_current_seconds( value ) debug_log( 'update_prop_settings_current_seconds(' .. pre_dump(value) .. ') -- function variable names: value ' ) --[[ When this is updated it will trigger a callback "property_onchange", let's disable that for a moment. ]] prevent_callback = true; obs.obs_data_set_double( ctx.propsSet, "sw_current_seconds", value ); sw_current_seconds = value; obs.obs_properties_apply_settings( ctx.propsDef, ctx.propsSet ); --[[ When this is updated it will trigger a callback "property_onchange", let's enable it again ]] prevent_callback = false; return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Everytime the timer value is updated, it will happen here Credit: Modified: function: update the timer value type: Dependency / Support input type: double returns: current_seconds ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function timer_value( value ) debug_log( 'timer_value(' .. pre_dump(value) .. ') -- function variable names: value ' ) current_seconds = value; return current_seconds; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Assign the correct frequency value to the timer incriment depending on if timer is counting up or down Credit: Modified: function: update the timer value type: Dependency / Support input type: returns: calls timer_value() ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function set_time_direction( ) debug_log( 'set_time_direction() -- function variable names: ' ) --[[ ]] local t = 0; --[[ ]] if direction_changed then -- normal function suspended if current_count_direction == "UP" then t = ( current_seconds + time_frequency ); -- value else t = ( current_seconds - time_frequency ); -- value end else -- normal function active --[[ ]] if timer_mode ~= 2 then t = ( current_seconds + time_frequency ); -- value else t = ( current_seconds - time_frequency ); -- value end end --[[ update the timer value ]] timer_value( t ); -- value end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Function to set the defined time text source value Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function update_timer_display( source_name, text ) debug_log( 'update_timer_display(' .. pre_dump(source_name) .. ", " .. pre_dump(text) .. ') -- function variable names: source_name, text ' ) --[[ Increments the source reference counter, use obs_source_release() to release it when complete. ]] local source = obs.obs_get_source_by_name( source_name ); if source ~= nil then local settings = obs.obs_source_get_settings( source ); if not media["activated_media_marker_a"] and not media["activated_media_marker_b"] and not color_normal_updated then obs.obs_data_set_int( settings, "color", media["color_normal"] ); color_normal_updated = true; end time_mark_check( "marker_a" ); time_mark_check( "marker_b" ); obs.obs_data_set_string( settings, "text", text ); end obs.obs_source_update( source, settings ); obs.obs_data_release( settings ); obs.obs_source_release( source ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Function to toggle milliseconds Credit: Modified: function: mili_toggle type: input type: globals: toggle_mili_trigger, timer_mode, mili_toggle_triggered, raw_time() returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function toggle_mili() debug_log( 'toggle_mili() -- function variable names: ' ) --[[ This feature will only activate if "Trigger Value" is defined and if "Trigger Value" matches "Current Time" and if "Timer Type" is "Countdown" ]] if toggle_mili_trigger ~= "" and timer_mode == 2 and not mili_toggle_triggered then local time_offset = 1; -- offset by 1 second to allow user to achieve accurate setting if raw_time( ( current_seconds + time_offset ), true ) == toggle_mili_trigger then --[[ The action trigger a toggle, so if the active state at the time of the trigger is "Show" the toggle will "Hide" and Vicas Versa. Should we force a state? To force define: show_mili = false ]] mili( true ); mili_toggle_triggered = true; end; end; --return true end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Function to set the defined time text source value Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function set_time_text( source_name ) debug_log( 'set_time_text(' .. pre_dump(source_name) .. ') -- function variable names: source_name ' ) --[[ First Check we have a source reference ]] if source_name == nil then return end; --[[ Force absolute zero at this point ]] if current_seconds <= 0.01 and ( timer_mode ~= 1 or ( direction_changed and current_count_direction == "DOWN" and prevent_negative_time ) ) then timer_value( 0 ); -- value, update_settings end; toggle_mili(); local l_time = long_time( current_seconds ); local t_time = raw_time( current_seconds ); --[[ Timer Format Type: Full Format ]] local text = tostring( raw_time( current_seconds ) ); --[[ Timer Format Type: Remove Leading Zeros ]] if timer_format == 2 then local system_time_format = "$T$H:$M:$S,$F"; text = format_time( ( l_time ~= 0 ) and string.format( "%s:%s", l_time, t_time ) or string.format( "%s", t_time ), system_time_format ); end --[[ Timer Format Type: No Leading Zeros, No Split Seconds ]] if timer_format == 3 then local system_time_format = "$T$H:$M:$S"; text = format_time( ( l_time ~= 0 ) and string.format( "%s:%s", l_time, t_time ) or string.format( "%s", t_time ), system_time_format ); end --[[ Timer Format Type: No Leading Zeros, No Split Seconds ]] if timer_format == 4 then local system_time_format = "$H:$M:$S"; text = format_time( ( l_time ~= 0 ) and string.format( "%s:%s", l_time, t_time ) or string.format( "%s", t_time ), system_time_format ); end if timer_format ~= 5 then --[[ Format the Text "Day/Days" ]] if timer_mode == 2 and countdown_type == 1 and current_seconds ~= 0 then local longtimetext = longtimetext_p; if math.floor( current_seconds / 86400 ) <= 1 then longtimetext = longtimetext_s; end if math.floor( current_seconds / 86400 ) <= 0 then longtimetext = longtimetext_p; end text = string.gsub(longtimetext .. text, "[#]", long_time( current_seconds )); end end --[[ Timer Format Type: Custom Time Format ]] if timer_format == 5 then text = format_time( ( l_time ~= 0 ) and string.format( "%s:%s", l_time, t_time ) or string.format( "%s", t_time ), removeBrackets(custom_time_format) ); end if timer_mode ~= 2 then --text_prefix = "" -- text_suffix = "" end text = text_prefix .. text .. text_suffix; if text ~= last_text then update_timer_display( timer_source, text ); end --[[ Check is media is playing and stop if required ]] stop_media( "marker_a" ); stop_media( "marker_b" ); last_text = text; --[[ Timer Ended ]] local epsilon = 0.0001 -- tolerance value if math.abs(current_seconds) < epsilon and ( timer_mode ~= 1 or ( direction_changed and current_count_direction == "DOWN" and prevent_negative_time ) ) then --[[ Timer is shutting down, this would be a good time to reset some items. ]]-- if enable_marker_notes ~= 1 then set_visible( media["note_source_marker_a"], false ); set_visible( media["note_source_marker_b"], false ); end --[[ Timer was started and now has EXPIRED execute any final instructions that the user require on TIMER END ]]-- if timer_active then timer_ended( source_name ) end; --[[ Now remove the timer callback "timer_ended()" offer options to restart the timer and may define a value to "current_seconds". Final check, if current_seconds == 0 then deactivate (end/remove) the timer callback This is a fallback but should not be needed as the timer callback may be removed by timer_ended() if needed ]]-- if math.abs(current_seconds) < epsilon then timer_expired = true end; end --return true end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: This captures the split times and unpack it in the correct format. The text source only permits linebreaks ( "\n" ) this limitation affects how the data can be formated ): Credit: Modified: function: split time type: Dependency / Support input type: globals returns: updates global split_data ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function split_unpack() debug_log( 'split_unpack() -- function variable names: ' ) local data = nil; local c = table.getn( split_itm ); local text = ""; local title = ""; local subtitle = ""; local line = "______________________________"; for i = 1, c do local mark = split_itm[i]; local interval = mark; if i > 1 then local j = i - 1; interval = split_itm[i] - split_itm[j]; end --[[ "Interval" = 1 "Mark" = 2 "Mark Interval" = 3 "Interval Mark" = 4 ]] if split_type == 1 then title = "Interval"; --subtitle = "" text = tostring( raw_time( interval ) ); elseif split_type == 2 then title = "Mark"; --subtitle = "" text = tostring( raw_time( mark ) ); elseif split_type == 3 then title = "Mark "; subtitle = "Interval"; text = tostring( raw_time( mark ).." "..raw_time( interval ) ); else -- "Interval Mark" title = "Interval "; subtitle = "Mark"; text = tostring( raw_time( interval ).." "..raw_time( mark ) ); end; -- data collection here local n = i --formatting the index number if i < 10 then n = "0"..tostring( i ) end; if data ~= nil then data = data .. "\n" .. n.." ) "..text; else data = "# "..title..subtitle.."\n"..line.."\n\n"..n.." ) "..text; end end -- end for split_data = data; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Decide if current_seconds needs to reset to default_seconds Credit: Modified: function: Check if current_seconds needs to reset to default_seconds type: check input type: next_scene returns: bool ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function update_default_time() debug_log( 'update_default_time() -- function variable names: ' ) if next_scene == "Source List" or next_scene == "Scene List" then return true; end return false; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Function to set the split time text Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function set_split_text( source_name ) debug_log( 'set_split_text(' .. pre_dump(source_name) .. ') -- function variable names: source_name ' ) if in_table( ignore_list, source_name ) then return end; local text = split_data; if text ~= last_split_data then set_text( source_name, text ); end last_split_data = text; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Used when we need to set some gloabsl for the timer to default state Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function default_timer_globals( set_to_default ) debug_log( 'default_timer_globals(' .. pre_dump(set_to_default) .. ') -- function variable names: set_to_default ' ) --[[ if set_to_default == true and timer_mode == 2 (Countdown) ]] if set_to_default then --[[ Used for source cycling default_seconds: Default Seconds the default timer state This is the state of the timer that will set or reset the time ( current_seconds ) If the timer expires because current_seconds == 0, then the time ( current_seconds ) will be be restarted from default_seconds for another function such as source cycling. Every instance that a timer time is defined, we must record it to default_seconds THIS WILL UPDATE current_seconds to the value current_seconds ]] --if set_to_default and timer_mode == 2 and update_default_time() then --timer_value( default_seconds ) -- value, update_settings --end --[[ If timer is Stopwatch type and user pressed "reset" the timer must always be reset to zero ]] if timer_mode == 1 and reset_activated then timer_value( 0 ); update_prop_settings_current_seconds( 0 ); end color_normal_updated = false; media["activated_media_marker_a"] = false; media["activated_media_marker_b"] = false; media["activated_marker_a"] = false; media["activated_marker_b"] = false; media["activated_time_marker_a"] = 0; media["activated_time_marker_b"] = 0; end timer_expired = true; completed_cycles = 0; split_count = 0; split_itm = {}; split_data = nil; set_split_text( split_source ); orig_time = obs.os_gettime_ns(); set_visible( media["note_source_marker_a"], false ); set_visible( media["note_source_marker_b"], false ); mili_toggle_triggered = false; media["last_state_marker_a"] = obs.OBS_MEDIA_STATE_NONE; media["last_state_marker_b"] = obs.OBS_MEDIA_STATE_NONE; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: toggle_mili_trigger, set_time_text, timer_mode returns: show_mili (bool) ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function reset_mili( ) debug_log( 'reset_mili() -- function variable names: ' ) if timer_mode ~= 2 then return true; end --[[ Is the "Trigger Value" defined and is "Timer Type" set to "Countdown" ]] if toggle_mili_trigger ~= "" then show_mili = false; else show_mili = true; end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Called if the timer setting change and needsto be updated. default_seconds is used for source cycling default_seconds: Default Seconds the default timer state This is the state of the timer that will set or reset the time ( current_seconds ) If the timer expires because current_seconds == 0, then the time ( current_seconds ) will be be restarted from default_seconds for another function such as source cycling. Every instance that a timer time is defined, we must record it to default_seconds Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function update_timer_settings( set_to_default, new_settings ) -- optional inputs: set_to_default(bool), new_settings(obs_property_data/obs_userdata) debug_log( 'update_timer_settings(' .. pre_dump(set_to_default) .. ", " .. pre_dump(new_settings) .. ') -- function variable names: set_to_default, new_settings ' ) local update_timer_display = false; if set_to_default == nil then set_to_default = false end; -- incase not defined, set default if new_settings == nil then new_settings = ctx.propsSet end; -- incase not defined, set default --[[ ]] reset_mili( ); --[[ STOPWATCH We will look at some tasks if the timer mode is set to stopwatch Stopwatch must always be reset to zero unless if the time is loaded from a previous session ]] if timer_mode == 1 then update_timer_display = true; --[[ In case the mode is switched, alwasy start with a reset of the timer time ]] if timer_mode_changed or force_reset_on_visible or force_reset_on_scene_active then timer_value( 0 ) end; -- value, update_settings --[[ We may require timer globals to be reset to defaults ]] default_timer_globals( set_to_default ); end --[[ COUNTDOWN We will look at some tasks if the timer mode is set to countdown ]] if timer_mode == 2 then --[[ In case the mode is switched, alwasy start with a reset of the timer time ]] if timer_mode_changed then timer_value( 0 ) end; -- value, update_settings update_timer_display = true; --[[ Countdown and a specific date. The specific date will be converted to seconds. ]] local calculated_time = 0; if countdown_type == 1 then calculated_time = ( delta_time( timer_year, timer_month, timer_day, timer_hours, timer_minutes, timer_seconds ) ); timer_value( calculated_time ); else calculated_time = ( ( obs.obs_data_get_int( new_settings, "hours" )*60*60 ) + ( obs.obs_data_get_int( new_settings, "minutes" )*60 ) + obs.obs_data_get_int( new_settings, "seconds" ) ); end --[[ This setting must only be applied in the following conditions: 1) When the timer is resting and the user is setting a new time. 2) The timer completed a cycle and needs to be rest to start the next cycle. This will only be required if the "next scene" is set to 'source list' or 'scene list' ]] local do_update = ( timer_expired and not timer_active and not activated and not set_to_default or in_table( {"Source List", "Scene List"}, next_scene ) or timer_mode_changed ); --[[ ]] if do_update then --[[ update timer time ]] timer_value( calculated_time ); -- --[[ Used for countdown only NB: This must always be called last in this routine so that current_seconds can be updated first Used for source cycling default_seconds: Default Seconds the default timer state This is the state of the timer that will set or reset the time ( current_seconds ) If the timer expires because current_seconds == 0, then the time ( current_seconds ) will be be restarted from default_seconds for another function such as source cycling. Every instance that a timer time is defined, we must record it to default_seconds In this instance a Setting may be updated, so update default_seconds ]] default_seconds = current_seconds; end --[[ *Used when timer has been reset *Used for source cycling default_seconds: Default Seconds the default timer state This is the state of the timer that will set or reset the time ( current_seconds ) If the timer expires because current_seconds == 0, then the time ( current_seconds ) will be be restarted from default_seconds for another function such as source cycling. Every instance that a timer time is defined, we must record it to default_seconds THIS WILL UPDATE current_seconds to the value current_seconds ]] if set_to_default and update_default_time() or reset_activated or ( force_reset_on_visible or force_reset_on_scene_active and set_to_default ) then --[[ update timer time ]] timer_value( default_seconds ); -- value end --[[ We may require timer globals to be reset to defaults ]] default_timer_globals( set_to_default ); end --[[ At this stage the timer display may need to be update ]] if update_timer_display then --[[ update timer display ]] set_time_text( timer_source ); end update_timer_display = false; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Add timer here so we have a global setting Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function timer_callback() debug_log( 'timer_callback() -- function variable names: ' ) time_frequency = get_frequency( ns_last ); set_time_direction( ); completed_cycles = completed_cycles + 1; -- we just keeping track of these for debugging, if needed. --[[ We need to update the timer display here to create the timer animation, technically the timer updates the text source every x seconds to create the animation effect. ]] set_time_text( timer_source ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Remove timer here so we have a global setting Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function end_timer() debug_log( 'end_timer() -- function variable names: ' ) timer_active = false; timer_remove( timer_callback ); -- Removing the callback stops the timer end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Add timer here so we have a global setting Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function start_timer() debug_log( 'start_timer() -- function variable names: ' ) record( 5, 100 ); -- wait 100 miliseconds timer_active = true; timer_add( timer_callback, timer_cycle ); --<- milliseconds end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function activate( activating ) debug_log( 'activate(' .. pre_dump(activating) .. ') -- function variable names: activating ' ) --[[ We skip/cancel anything requested if the Script was disabled by the user. ]] if disable_script then return; end if timer_active and not activating then --[[ moved from function pause( pressed ) debug_log( 'pause(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) more information needed ]] split_unpack(); --[[ moved from function pause( pressed ) debug_log( 'pause(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) more information needed ]] set_split_text( split_source ); end --[[ Pass the activating state to a globle variable, as this wil be used in other instances. ]] activated = activating; --[[ activating will be TRUE or FALSE if TRUE create a TIMER (Callback) ]] if activating then --[[ if timer is activating, it has not expired ]] timer_expired = false; --[[ Once the timer started, this setting will default ]] set_timer_activated = false; start_timer(); -- Timer now initiated --[[ STOP TIMER REQUIRED ]] else --[[ Timer ENDED (EXPIRED) or PAUSED ]] reset_text_a_colour_end_timer(); reset_text_b_colour_end_timer(); if timer_expired and timer_active then end_timer() -- Removing the callback stops the timer set_visible( media["source_name_audio_marker_a"], false ); -- The timer expired, reset media. set_visible( media["source_name_audio_marker_b"], false ); -- The timer expired, reset media. else end_timer(); -- Removing the callback stops the timer end --[[ update_timer_settings: timer_mode: countdown only timer_active: timer not running define > set_to_default: (false) * we are receiving new settings, so we do not want to set the settings to default. define > new_settings: not required here because we will use the global (ctx.propsSet) purpose: User is changing the countdown time settings and we need to provide instant feedback output to the timer display (timer text source) ]] if not timer_active then -- update timer display when the timer settings changed --update_timer_settings( false ) -- optional inputs: set_to_default(bool), new_settings(obs_property_data/obs_userdata) < BUG end; end; debug_log( 'END of function activate()') -- function variable names: activating ' ) end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Usually called by a button or hotkey press Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function pause( pressed ) debug_log( 'pause(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) if not pressed then return; end --[[ Set timer source visibility to visible ]] set_visible( timer_source, true ); if timer_active then activate( false ); else if activated == false then activate( true ); end; end; pause_play_media( media["source_name_audio_marker_a"], not timer_active ); pause_play_media( media["source_name_audio_marker_b"], not timer_active ); return; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: reset timer type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function hotkey_send_pause( pressed ) debug_log( 'hotkey_send_pause(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) --[[ For hotkeys: This is called on key down & key up. A bool check: pressed = true (key down) pressed = false (key up) When a hotkeys is pressed the callback checks if the key state is currently pressed 'true' or 'false' (released) so a hotkey key press has a dual function: key down, key up ]] if pressed then -- key is currently down --return -- uncomment 'return' to ignore the call while key is down else -- key was released return; -- uncomment 'return' to ignore the call when key is released end; pause( pressed ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Take the values from the properties and assign it to the timer Credit: Modified: function: Used to mannually set stopwatch type: Dependency / Support input type: none returns: calls set_time_text() ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function set_stopwatch() debug_log( 'set_stopwatch() -- function variable names: ' ) time_frequency = get_frequency( ns_last ); local hh = ( sw_hours_saved * 3600 ); local mm = 0; if minute_format ~= nil then mm = ( sw_minutes_saved * minute_format ); else mm = ( sw_minutes_saved * 60 ); end mm = ( sw_minutes_saved * 60 ); local ss = ( sw_seconds_saved ); local ff = ( sw_milliseconds_saved / ( 99 + time_frequency ) ); local time = ( hh + mm + ss + ff ); timer_value( time ); -- Update the last saved time here set_time_text( timer_source ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: A leap year comes once in four years, in which February month has 29 days. With this additional day in February, a year becomes a Leap year. on every year that is evenly divisible by 4 except every year that is evenly divisible by 100 unless the year is also evenly divisible by 400 Credit: et al Modified: function: A leap year is a year with 366 days instead of 365 type: Dependency / Support, UI input type: integer returns: bool ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function is_leap_year( year ) debug_log( 'is_leap_year(' .. pre_dump(year) .. ') -- function variable names: year ' ) if year % 4 == 0 then -- A leap year comes once in four years if year % 100 == 0 then if year % 400 == 0 then return true; else return false; end; else return true; end; else return false; end; --return year%4==0 and (year%100~=0 or year%400==0) -- shorthand end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Callback for button press ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function mili_button_clicked( props, p, settings ) debug_log( 'mili_button_clicked(' .. pre_dump(props) .. ", " .. pre_dump(p) .. ", " .. pre_dump(settings) .. ') -- function variable names: props, p, settings ' ) mili( true ); return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Callback for button press Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function update_button_label( props ) debug_log( 'update_button_label(' .. pre_dump(props) .. ') -- function variable names: props ' ) --[[ A button has it's own callback so we can not action anything on the button press through the Properties Callback, instead we will action it on the button Callback directly. ]] local mode = obs.obs_data_get_int( ctx.propsSet, "timer_mode" ); local direction_button_prop = obs.obs_properties_get( props, "direction_button" ); --[[ We are only setting the button label depending if the timer is active and on the timer type. ]] if current_count_direction == "DOWN" then obs.obs_property_set_description( direction_button_prop, "Count Up" ); else obs.obs_property_set_description( direction_button_prop, "Count Down" ); end return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Callback for button press ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function direction_button_clicked( props, p, settings ) debug_log( 'direction_button_clicked(' .. pre_dump(props) .. ", " .. pre_dump(p) .. ", " .. pre_dump(settings) .. ') -- function variable names: props, p, settings ' ) --[[ Only allow if enable ]] if not enable_direction_toggle then return; end update_timer_direction( true ); update_button_label( props ); return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function update_timer_direction( pressed ) debug_log( 'update_timer_direction(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) if not pressed and not enable_direction_toggle then return; end --[[ Signal that the feature is in use, this will indicate that 'normal operation' is suspended. It will be reset if 'Reset' is called. ]] --[[ Change direction each time the feature is activated ]] current_count_direction = ( current_count_direction == "UP" and "DOWN" or "UP") --[[ Make sure the trigger is as accurate as possible depending if the timer is counting up or down. ]] if timer_mode == 1 then count_orientation = current_count_direction == "UP" and "NORMAL" or "INVERTED" end if timer_mode == 2 then count_orientation = current_count_direction == "DOWN" and "NORMAL" or "INVERTED" end if count_orientation == "INVERTED" then direction_changed = true; else direction_changed = false; end local round_seconds = roundSeconds() update_time_markers( round_seconds ) update_media_names(); -- we don't reset because here the list must cycle return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function mili( pressed ) debug_log( 'mili(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) if not pressed then return; end if show_mili then show_mili = false; else show_mili = true; end --[[ The timer text will update if the timer is active but not if it is paused. Lets update it if in the paused (inactive) state. ]] if not timer_active then set_time_text( timer_source ); end --[[ A button has it's own callback so we can not action anything on the button press through the Properties Callback, instead we will action it on the button Callback directly. ]] local mode = obs.obs_data_get_int( ctx.propsSet, "timer_mode" ); local mili_button_prop = obs.obs_properties_get( props, "mili_button" ); -- should it not be ctx.propsDef --[[ We are only setting the button label depending if the timer"s milliseconds value visibility state. ]] if show_mili then obs.obs_property_set_description( mili_button_prop, "Hide Milliseconds" ); else obs.obs_property_set_description( mili_button_prop, "Show Milliseconds" ); end return true end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function hotkey_send_mili( pressed ) debug_log( 'hotkey_send_mili(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) --[[ For hotkeys: This is called on key down & key up. A bool check: pressed = true (key down) pressed = false (key up) When a hotkeys is pressed the callback checks if the key state is currently pressed 'true' or 'false' (released) so a hotkey key press has a dual function: key down, key up ]] if pressed then -- key is currently down --return -- uncomment 'return' to ignore the call while key is down else -- key was released return -- uncomment 'return' to ignore the call when key is released end mili( pressed ) end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function hotkey_send_direction( pressed ) debug_log( 'hotkey_send_direction(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) --[[ For hotkeys: This is called on key down & key up. A bool check: pressed = true (key down) pressed = false (key up) When a hotkeys is pressed the callback checks if the key state is currently pressed 'true' or 'false' (released) so a hotkey key press has a dual function: key down, key up ]] if pressed then -- key is currently down --return -- uncomment 'return' to ignore the call while key is down else -- key was released return -- uncomment 'return' to ignore the call when key is released end update_timer_direction( true ) end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] function sal_timer_callback() debug_log( 'sal_timer_callback() -- function variable names: ' ) if timer_manipulation == 3 then return end; set_visible( add_limit_note_source, false ); timer_remove( sal_timer_callback ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] function start_sal_timer() debug_log( 'start_sal_timer() -- function variable names: ' ) if timer_manipulation == 3 then return end; set_visible( add_limit_note_source, true ); if add_limit_note_source_visible == 0 then return end; timer_add( sal_timer_callback, add_limit_note_source_visible ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function sal_check( ) debug_log( 'sal_check() -- function variable names: ' ) if timer_manipulation == 3 then return end; if sec_add_limit == 0 then return end; if sec_add_limit_used >= sec_add_limit then start_sal_timer(); else set_visible( add_limit_note_source, false ); end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function sec_add_1_update( ) debug_log( 'sec_add_1_update() -- function variable names: ' ) if timer_manipulation == 3 then return end; if sec_add_limit ~= 0 and sec_add_limit_used >= sec_add_limit then -- do nothing else timer_value( current_seconds + sec_add_1 ); local update_value = sec_add_limit_used + 1; sec_add_limit_used = update_value; update_properties_setting_int( "sec_add_limit_used", update_value ); end; sal_check( ); return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function sec_add_2_update( ) debug_log( 'sec_add_2_update() -- function variable names: ' ) if timer_manipulation == 3 then return end; if sec_add_limit ~= 0 and sec_add_limit_used >= sec_add_limit then -- do nothing else timer_value( current_seconds + sec_add_2 ); local update_value = sec_add_limit_used + 1; sec_add_limit_used = update_value; update_properties_setting_int( "sec_add_limit_used", update_value ); end; sal_check( ); return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function sec_add_3_update( ) debug_log( 'sec_add_3_update() -- function variable names: ' ) if timer_manipulation == 3 then return end; if sec_add_limit ~= 0 and sec_add_limit_used >= sec_add_limit then -- do nothing else timer_value( current_seconds + sec_add_3 ); local update_value = sec_add_limit_used + 1; sec_add_limit_used = update_value; update_properties_setting_int( "sec_add_limit_used", update_value ); end; sal_check( ); return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] function ssl_timer_callback() debug_log( 'ssl_timer_callback() -- function variable names: ' ) if timer_manipulation == 3 then return end; set_visible( sub_limit_note_source, false ); timer_remove( ssl_timer_callback ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] function start_ssl_timer() debug_log( 'start_ssl_timer() -- function variable names: ' ) if timer_manipulation == 3 then return end; set_visible( sub_limit_note_source, true ); if sub_limit_note_source_visible == 0 then return end; timer_add( ssl_timer_callback, sub_limit_note_source_visible ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function ssl_check( ) debug_log( 'ssl_check() -- function variable names: ' ) if timer_manipulation == 3 then return end; if sec_sub_limit == 0 then return end; if sec_sub_limit_used >= sec_sub_limit then start_ssl_timer(); else set_visible( sub_limit_note_source, false ); end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function sec_sub_1_update( ) debug_log( 'sec_sub_1_update() -- function variable names: ' ) if timer_manipulation == 3 then return end; if sec_sub_limit ~= 0 and sec_sub_limit_used >= sec_sub_limit then -- do nothing else timer_value( current_seconds - sec_sub_1 ); local update_value = sec_sub_limit_used + 1; sec_sub_limit_used = update_value; update_properties_setting_int( "sec_sub_limit_used", update_value ); end; ssl_check( ); return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function sec_sub_2_update( ) debug_log( 'sec_sub_2_update() -- function variable names: ' ) if timer_manipulation == 3 then return end; if sec_sub_limit ~= 0 and sec_sub_limit_used >= sec_sub_limit then -- do nothing else timer_value( current_seconds - sec_sub_2 ); local update_value = sec_sub_limit_used + 1; sec_sub_limit_used = update_value; update_properties_setting_int( "sec_sub_limit_used", update_value ); end; ssl_check( ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function sec_sub_3_update( ) debug_log( 'sec_sub_3_update() -- function variable names: ' ) if timer_manipulation == 3 then return end; if sec_sub_limit ~= 0 and sec_sub_limit_used >= sec_sub_limit then -- do nothing else timer_value( current_seconds - sec_sub_3 ); local update_value = sec_sub_limit_used + 1; sec_sub_limit_used = update_value; update_properties_setting_int( "sec_sub_limit_used", update_value ); end; ssl_check( ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function hotkey_send_sec_add_1( pressed ) debug_log( 'hotkey_send_sec_add_1(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) --[[ For hotkeys: This is called on key down & key up. A bool check: pressed = true (key down) pressed = false (key up) When a hotkeys is pressed the callback checks if the key state is currently pressed 'true' or 'false' (released) so a hotkey key press has a dual function: key down, key up ]] if pressed then -- key is currently down --return -- uncomment 'return' to ignore the call while key is down else -- key was released return; -- uncomment 'return' to ignore the call when key is released end; sec_add_1_update( ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function hotkey_send_sec_add_2( pressed ) debug_log( 'hotkey_send_sec_add_2(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) --[[ For hotkeys: This is called on key down & key up. A bool check: pressed = true (key down) pressed = false (key up) When a hotkeys is pressed the callback checks if the key state is currently pressed 'true' or 'false' (released) so a hotkey key press has a dual function: key down, key up ]] if pressed then -- key is currently down --return -- uncomment 'return' to ignore the call while key is down else -- key was released return; -- uncomment 'return' to ignore the call when key is released end; sec_add_2_update( ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function hotkey_send_sec_add_3( pressed ) debug_log( 'hotkey_send_sec_add_3(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) --[[ For hotkeys: This is called on key down & key up. A bool check: pressed = true (key down) pressed = false (key up) When a hotkeys is pressed the callback checks if the key state is currently pressed 'true' or 'false' (released) so a hotkey key press has a dual function: key down, key up ]] if pressed then -- key is currently down --return -- uncomment 'return' to ignore the call while key is down else -- key was released return; -- uncomment 'return' to ignore the call when key is released end; sec_add_3_update( ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function hotkey_send_sec_sub_1( pressed ) debug_log( 'hotkey_send_sec_sub_1(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) --[[ For hotkeys: This is called on key down & key up. A bool check: pressed = true (key down) pressed = false (key up) When a hotkeys is pressed the callback checks if the key state is currently pressed 'true' or 'false' (released) so a hotkey key press has a dual function: key down, key up ]] if pressed then -- key is currently down --return -- uncomment 'return' to ignore the call while key is down else -- key was released return; -- uncomment 'return' to ignore the call when key is released end; sec_sub_1_update( ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function hotkey_send_sec_sub_2( pressed ) debug_log( 'hotkey_send_sec_sub_2(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) --[[ For hotkeys: This is called on key down & key up. A bool check: pressed = true (key down) pressed = false (key up) When a hotkeys is pressed the callback checks if the key state is currently pressed 'true' or 'false' (released) so a hotkey key press has a dual function: key down, key up ]] if pressed then -- key is currently down --return -- uncomment 'return' to ignore the call while key is down else -- key was released return; -- uncomment 'return' to ignore the call when key is released end sec_sub_2_update( ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function hotkey_send_sec_sub_3( pressed ) debug_log( 'hotkey_send_sec_sub_3(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) --[[ For hotkeys: This is called on key down & key up. A bool check: pressed = true (key down) pressed = false (key up) When a hotkeys is pressed the callback checks if the key state is currently pressed 'true' or 'false' (released) so a hotkey key press has a dual function: key down, key up ]] if pressed then -- key is currently down --return -- uncomment 'return' to ignore the call while key is down else -- key was released return; -- uncomment 'return' to ignore the call when key is released end; sec_sub_3_update( ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Called when a source is activated/deactivated Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function activate_signal( cd, connected ) debug_log( 'activate_signal(' .. pre_dump(cd) .. ", " .. pre_dump(connected) .. ') -- function variable names: cd, connected ' ) --[[ Capture / Retrieve Callback Data ]] local source = obs.calldata_source( cd, "source" ); --[[ Do we have a Source? ]] if source ~= nil then local name = obs.obs_source_get_name( source ); -- Get Source NAME --[[ ]] activate_timer_on_scene( source, connected ); --[[ Check if scene has source ]] if ( name == timer_source ) then if connected then record( 4, 300 ) end; if start_on_visible or start_on_scene_active then --[[ Source visibility was set to hidden ]] if not connected then pause_play_media( media["source_name_audio_marker_a"], true ); pause_play_media( media["source_name_audio_marker_b"], true ); end --[[ timer_mode: either timer_active: timer not running define > set_to_default: (true) * we are starting the timer from the default position and therfore want to set the settings to default. define > new_settings: not required here because we will use the global (ctx.propsSet) purpose: User requires the timer to intiate. ]] if not set_timer_activated and ( force_reset_on_visible or force_reset_on_scene_active ) then update_timer_settings( true ); -- optional inputs: set_to_default(bool), new_settings(obs_property_data/obs_userdata) end if connected then activate( true ); else activate( false ); end; end; end; end; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: reset timer type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function reset( pressed ) debug_log( 'reset(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) if not script_ready then return end reset_activated = true; -- notify timer settings a reset call is in process --[[ For hotkeys: This is called on key down & key up. A bool check: pressed = true (key down) pressed = false (key up) When a hotkeys is pressed the callback checks if the key state is currently pressed 'true' or 'false' (released) so a hotkey key press has a dual function: key down, key up ]] if pressed then -- key is currently down --return -- uncomment 'return' to ignore the call while key is down else -- key was released --return -- uncomment 'return' to ignore the call when key is released end --[[ Set visibility to first position ]] set_visible( media["source_name_audio_marker_a"], false ); set_visible( media["source_name_audio_marker_b"], false ); set_visible( media["source_name_audio_marker_end"], false ); media["cycle_index_marker_a"] = 1; -- index from 1-based table media["cycle_index_marker_b"] = 1; -- index from 1-based table media["cycle_direction_marker_a"] = 2; media["cycle_direction_marker_b"] = 2; cycle_index = 1; set_visible( add_limit_note_source, false ); set_visible( sub_limit_note_source, false ); obs.obs_data_set_int( ctx.propsSet, "sec_add_limit_used", 0 ); obs.obs_data_set_int( ctx.propsSet, "sec_sub_limit_used", 0 ); obs.obs_properties_apply_settings( ctx.propsDef, ctx.propsSet ); sec_add_limit_used = 0; sec_sub_limit_used = 0; --[[ Set bool to first position ]] media["media_ended_marker_a"] = false; media["media_ended_marker_b"] = false; color_normal_updated = false; set_timer_activated = false; direction_changed = false; media["activated_media_marker_a"] = false; media["activated_media_marker_b"] = false; media["activated_marker_a"] = false; media["activated_marker_b"] = false; media["activated_time_marker_a"] = 0; media["activated_time_marker_b"] = 0; --[[ Reset the current_count_direction If the timer is in Stopwatch mode the timer is counting up ]] count_orientation = "NORMAL" last_orientation = "NORMAL" if timer_mode == 1 then current_count_direction = "UP"; else current_count_direction = "DOWN"; end --[[ force text update by changing last_text ]] last_text = tostring( obs.os_gettime_ns() ); --[[ timer_mode: eiher timer_active: either define > set_to_default: (true) * User requested a reset, so we do not want to set the settings to default. define > new_settings: not required here because we will use the global (ctx.propsSet) purpose: User is resetting the timer settings and we need to provide instant feedback output to the timer display (timer text source) ]] update_timer_settings( true ); -- optional inputs: set_to_default(bool), new_settings(obs_property_data/obs_userdata) -- based on UI Settings --[[ Handle Timer Callback ]] activate( false ); -- start or stop the timer, in this case, stop the timer and get it ready start again --[[ Reset timer split seconds text ]] set_split_text( split_source ); --[[ Reset "Active Source" text (if defined) ]] if active_source ~= "Select" then set_text( active_source, "" ); --[[ force the visibility state of this source. ]] if active_source_force_visible then set_visible( active_source, false ) end; end --[[ Make sure the trigger is as accurate as possible depending if the timer is counting up or down. ]] reset_audio_marker_arr(); local round_seconds = roundSeconds(); update_time_markers( round_seconds ); update_media_names( true ); reset_activated = false; -- notify timer settings a reset call ended debug_log( 'END of function reset()') -- function variable names: activating ' ) end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: reset timer type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function activate_reset( pressed ) debug_log( 'activate_reset(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) --[[ For hotkeys: This is called on key down & key up. A bool check: pressed = true (key down) pressed = false (key up) When a hotkeys is pressed the callback checks if the key state is currently pressed 'true' or 'false' (released) so a hotkey key press has a dual function: key down, key up ]] if pressed then -- key is currently down --return -- uncomment 'return' to ignore the call while key is down else -- key was released return; -- uncomment 'return' to ignore the call when key is released end reset( pressed ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: reset timer type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function hotkey_send_reset( pressed ) debug_log( 'hotkey_send_reset(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) --[[ For hotkeys: This is called on key down & key up. A bool check: pressed = true (key down) pressed = false (key up) When a hotkeys is pressed the callback checks if the key state is currently pressed 'true' or 'false' (released) so a hotkey key press has a dual function: key down, key up ]] if pressed then -- key is currently down --return -- uncomment 'return' to ignore the call while key is down else -- key was released return; -- uncomment 'return' to ignore the call when key is released end activate_reset( pressed ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Set the titles / labels of the Start / Pause Button Credit: Modified: function: Update start/puase button label type: input type: mode, timer_active returns: props ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function property_button_update_start( props ) debug_log( 'property_button_update_start(' .. pre_dump(props) .. ') -- function variable names: props ' ) --[[ A button has it's own callback so we can not action anything on the button press through the Properties Callback, instead we will action it on the button Callback directly. ]] local mode = obs.obs_data_get_int( ctx.propsSet, "timer_mode" ); local pause_button_prop = obs.obs_properties_get( props, "pause_button" ); local direction_button_prop = obs.obs_properties_get( props, "direction_button" ); --[[ We are only setting the button label depending if the timer is active and on the timer type. ]] if mode == 2 then obs.obs_property_set_description( direction_button_prop, "Count Up" ); if timer_active then obs.obs_property_set_description( pause_button_prop, "Start/Pause Countdown" ); else obs.obs_property_set_description( pause_button_prop, "Start Countdown" ); end; else obs.obs_property_set_description( direction_button_prop, "Count Down" ); if timer_active then obs.obs_property_set_description( pause_button_prop, "Start/Pause Stopwatch" ); else obs.obs_property_set_description( pause_button_prop, "Start Stopwatch" ); end; end; return props; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Callback for button press Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function pause_button_clicked( props, p ) debug_log( 'pause_button_clicked(' .. pre_dump(props) .. ", " .. pre_dump(p) .. ') -- function variable names: props, p ' ) pause( true ); property_button_update_start( props ); return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Callback for button press Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function sec_add_1_button_clicked( props, p ) debug_log( 'sec_add_1_button_clicked(' .. pre_dump(props) .. ", " .. pre_dump(p) .. ') -- function variable names: props, p ' ) sec_add_1_update( ); return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Callback for button press Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function sec_add_2_button_clicked( props, p ) debug_log( 'sec_add_2_button_clicked(' .. pre_dump(props) .. ", " .. pre_dump(p) .. ') -- function variable names: props, p ' ) sec_add_2_update( ); return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Callback for button press Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function sec_add_3_button_clicked( props, p ) debug_log( 'sec_add_3_button_clicked(' .. pre_dump(props) .. ", " .. pre_dump(p) .. ') -- function variable names: props, p ' ) sec_add_3_update( ); return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Callback for button press Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function sec_sub_1_button_clicked( props, p ) debug_log( 'sec_sub_1_button_clicked(' .. pre_dump(props) .. ", " .. pre_dump(p) .. ') -- function variable names: props, p ' ) sec_sub_1_update( ); return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Callback for button press Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function sec_sub_2_button_clicked( props, p ) debug_log( 'sec_sub_2_button_clicked(' .. pre_dump(props) .. ", " .. pre_dump(p) .. ') -- function variable names: props, p ' ) sec_sub_2_update( ); return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Callback for button press Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function sec_sub_3_button_clicked( props, p ) debug_log( 'sec_sub_3_button_clicked(' .. pre_dump(props) .. ", " .. pre_dump(p) .. ') -- function variable names: props, p ' ) sec_sub_3_update( ); return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Usually called by a button or hotkey press Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function export( pressed ) debug_log( 'export(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) if not pressed then return; end; local file_exported = write_to_json( ctx.propsSet ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Callback for button press Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function export_button_clicked( props, p ) debug_log( 'export_button_clicked(' .. pre_dump(props) .. ", " .. pre_dump(p) .. ') -- function variable names: props, p ' ) export( true ); return false; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Callback for button press Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function import_button_clicked( props, p, settings ) debug_log( 'import_button_clicked(' .. pre_dump(props) .. ", " .. pre_dump(p) .. ", " .. pre_dump(settings) .. ') -- function variable names: props, p, settings ' ) return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Callback for button press Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function sw_saved_button_clicked( props, p, settings ) debug_log( 'sw_saved_button_clicked(' .. pre_dump(props) .. ", " .. pre_dump(p) .. ", " .. pre_dump(settings) .. ') -- function variable names: props, p, settings ' ) if timer_mode == 1 then set_stopwatch(); set_timer_activated = true; end; return false; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Callback for button press Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function reset_button_clicked( props, p, settings ) debug_log( 'reset_button_clicked(' .. pre_dump(props) .. ", " .. pre_dump(p) .. ", " .. pre_dump(settings) .. ') -- function variable names: props, p, settings ' ) activate_reset( true ); return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Usually called by a button or hotkey press Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function split_time( pressed ) debug_log( 'split_time(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) if not pressed then return; end; if timer_active then split_count = split_count + 1; split_itm[split_count] = current_seconds; split_unpack(); set_split_text( split_source ); end; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: reset timer type: input type: returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function hotkey_send_split( pressed ) debug_log( 'hotkey_send_split(' .. pre_dump(pressed) .. ') -- function variable names: pressed ' ) --[[ For hotkeys: This is called on key down & key up. A bool check: pressed = true (key down) pressed = false (key up) When a hotkeys is pressed the callback checks if the key state is currently pressed 'true' or 'false' (released) so a hotkey key press has a dual function: key down, key up ]] if pressed then -- key is currently down --return -- uncomment 'return' to ignore the call while key is down else -- key was released return; -- uncomment 'return' to ignore the call when key is released end split_time( pressed ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Callback for button press Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function split_button_clicked( props, p ) debug_log( 'split_button_clicked(' .. pre_dump(props) .. ", " .. pre_dump(p) .. ') -- function variable names: props, p ' ) split_time( true ); return false; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function load_settings_globals( settings ) debug_log( 'load_settings_globals(' .. pre_dump(settings) .. ') -- function variable names: settings ' ) last_timer_mode = timer_mode; timer_day = obs.obs_data_get_int( settings, "day" ); timer_year = obs.obs_data_get_int( settings, "year" ); timer_hours = obs.obs_data_get_int( settings, "hours" ); timer_month = obs.obs_data_get_int( settings, "month" ) - 1; timer_manipulation = obs.obs_data_get_int( settings, "timer_manipulation" ); sec_add_1 = obs.obs_data_get_int( settings, "sec_add_1" ); sec_add_2 = obs.obs_data_get_int( settings, "sec_add_2" ); sec_add_3 = obs.obs_data_get_int( settings, "sec_add_3" ); sec_sub_1 = obs.obs_data_get_int( settings, "sec_sub_1" ); sec_sub_2 = obs.obs_data_get_int( settings, "sec_sub_2" ); sec_sub_3 = obs.obs_data_get_int( settings, "sec_sub_3" ); sec_add_limit = obs.obs_data_get_int( settings, "sec_add_limit" ); sec_add_limit_used = obs.obs_data_get_int( settings, "sec_add_limit_used" ); sec_sub_limit = obs.obs_data_get_int( settings, "sec_sub_limit" ); sec_sub_limit_used = obs.obs_data_get_int( settings, "sec_sub_limit_used" ); timer_mode = obs.obs_data_get_int( settings, "timer_mode" ); timer_minutes = obs.obs_data_get_int( settings, "minutes" ); timer_seconds = obs.obs_data_get_int( settings, "seconds" ); add_limit_note_source = obs.obs_data_get_string( settings, "add_limit_note_source" ); sub_limit_note_source = obs.obs_data_get_string( settings, "sub_limit_note_source" ); add_limit_note_source_visible = obs.obs_data_get_int( settings, "add_limit_note_source_visible" ); sub_limit_note_source_visible = obs.obs_data_get_int( settings, "sub_limit_note_source_visible" ); enable_marker_notes = obs.obs_data_get_int( settings, "enable_marker_notes" ); timer_format = obs.obs_data_get_int( settings, "timer_format" ); timer_source = obs.obs_data_get_string( settings, "timer_source" ); timer_display = obs.obs_data_get_int( settings, "timer_display" ); countdown_type = obs.obs_data_get_int( settings, "countdown_type" ); sw_hours_saved = obs.obs_data_get_int( settings, "sw_hours_saved" ); cycle_direction = obs.obs_data_get_int( settings, "cycle_direction" ); sw_current_seconds = obs.obs_data_get_double( settings, "sw_current_seconds" ); load_saved_time = obs.obs_data_get_bool( settings, "load_saved_time" ); sw_minutes_saved = obs.obs_data_get_int( settings, "sw_minutes_saved" ); sw_seconds_saved = obs.obs_data_get_int( settings, "sw_seconds_saved" ); custom_time_format = obs.obs_data_get_string( settings, "custom_time_format" ); toggle_mili_trigger = obs.obs_data_get_string( settings, "toggle_mili_trigger" ); sw_milliseconds_saved = obs.obs_data_get_int( settings, "sw_milliseconds_saved" ); prevent_negative_time = obs.obs_data_get_bool( settings, "prevent_negative_time" ); media["note_source_marker_a"] = obs.obs_data_get_string( settings, "note_source_marker_a" ); media["note_source_marker_b"] = obs.obs_data_get_string( settings, "note_source_marker_b" ); note_source_marker_a = obs.obs_data_get_string( settings, "note_source_marker_a" ); note_source_marker_b = obs.obs_data_get_string( settings, "note_source_marker_b" ); longtimetext_s = string.gsub(obs.obs_data_get_string( settings, "day_text" ), "\\([n])", {n="\n"}); longtimetext_p = string.gsub(obs.obs_data_get_string( settings, "days_text" ), "\\([n])", {n="\n"}); media["note_marker_a"] = string.gsub(obs.obs_data_get_string( settings, "note_marker_a" ), "\\([n])", {n="\n"}); media["note_marker_b"] = string.gsub(obs.obs_data_get_string( settings, "note_marker_b" ), "\\([n])", {n="\n"}); script_ready = obs.obs_data_get_bool( settings, "script_ready" );-- -- Retrieves property value from reference debug_log( 'load_settings_globals get script_ready (' .. pre_dump(script_ready) .. ') ' ) --[[ Used for source cycling default_seconds: Default Seconds the default timer state This is the state of the timer that will set or reset the time ( current_seconds ) If the timer expires because current_seconds == 0, then the time ( current_seconds ) will be be restarted from default_seconds for another function such as source cycling. Every instance that a timer time is defined, we must record it to default_seconds ]] default_seconds = current_seconds; split_type = obs.obs_data_get_int( settings, "split_type" ); stop_text = obs.obs_data_get_string( settings, "stop_text" ); next_scene = obs.obs_data_get_string( settings, "next_scene" ); import_list = obs.obs_data_get_string( settings, "import_list" ); split_source = obs.obs_data_get_string( settings, "split_source" ); disable_script = obs.obs_data_get_bool( settings, "disable_script" ); enable_direction_toggle = obs.obs_data_get_bool( settings, "enable_direction_toggle" ); recording_type = obs.obs_data_get_int( settings, "recording_type" ); backup_folder = obs.obs_data_get_string( settings, "backup_folder" ); active_source = obs.obs_data_get_string( settings, "active_source" ); start_recording = obs.obs_data_get_int( settings, "start_recording" ); load_saved_time = obs.obs_data_get_bool( settings, "load_saved_time" ); start_on_visible = obs.obs_data_get_bool( settings, "start_on_visible" ); media["color_normal"] = obs.obs_data_get_int( settings, "color_normal" ); media["color_marker_b"] = obs.obs_data_get_int( settings, "color_marker_b" ); media["color_marker_a"] = obs.obs_data_get_int( settings, "color_marker_a" ); audio_marker_a = obs.obs_data_get_string( ctx.propsSet, "audio_marker_a" ); audio_marker_b = obs.obs_data_get_string( ctx.propsSet, "audio_marker_b" ); if audio_marker_a ~= "list" then media["source_name_audio_marker_a"] = audio_marker_a end if audio_marker_a ~= "list" then media["source_name_audio_marker_b"] = audio_marker_b end media_playback_limit = obs.obs_data_get_int( settings, "media_playback_limit" ); start_on_scene_active = obs.obs_data_get_bool( settings, "start_on_scene_active" ); force_reset_on_visible = obs.obs_data_get_bool( settings, "force_reset_on_visible" ); force_reset_on_scene_active = obs.obs_data_get_bool( settings, "force_reset_on_scene_active" ); active_source_force_visible = obs.obs_data_get_bool( settings, "active_source_force_visible" ); media["source_name_audio_marker_end"] = obs.obs_data_get_string( settings, "audio_marker_end" ); text_prefix = string.gsub(obs.obs_data_get_string( settings, "text_prefix" ), "\\([n])", {n="\n"}); text_suffix = string.gsub(obs.obs_data_get_string( settings, "text_suffix" ), "\\([n])", {n="\n"}); media["reset_text_marker_a"] = obs.obs_data_get_int( settings, "reset_text_marker_a" ); media["reset_text_marker_b"] = obs.obs_data_get_int( settings, "reset_text_marker_b" ); if media_playback_limit ~= 1 then media["duration_marker_a"] = obs.obs_data_get_int( settings, "duration_marker_a" ); media["duration_marker_b"] = obs.obs_data_get_int( settings, "duration_marker_b" ); media["duration_marker_end"] = obs.obs_data_get_int( settings, "duration_marker_end" ); else media["duration_marker_a"] = 0; media["duration_marker_b"] = 0; media["duration_marker_end"] = 0; end; if not load_saved_time then sw_current_seconds = 0 end; timer_mode_changed = ( last_timer_mode ~= timer_mode ); update_source_list(); --[[ Make sure the trigger is as accurate as possible depending if the timer is counting up or down. ]] end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: custom function we use this to get a signal handler for a specific source once it is loaded to ensure it is connected when OBS starts up Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function loaded( cd ) debug_log( 'loaded(' .. pre_dump(cd) .. ') -- function variable names: cd ' ) obs.obs_data_set_bool( settings, "script_ready", false ); -- set false to signal that the script is not yet ready. debug_log( 'loaded set script_ready (' .. pre_dump(false) .. ') ' ) local all_sources_loaded = false; --[[ Get source from CallData ]] local source = obs.calldata_source( cd, "source" ); --[[ Found Source: ]] if source ~= nil then local name = obs.obs_source_get_name( source ); -- Get the source name, this will be a unique identifier debug_log( 'loaded source: (' .. pre_dump(name) .. ')' ) --[[ Does the name match the property value? ]] if ( name == timer_source ) then if start_on_visible and not start_on_scene_active then set_visible( timer_source, false ); -- set hidden as a starting point, the user can start the timer by setting it to visible end; end; if obs.obs_source_get_id( source ) ~= "scene" then sources_loaded = sources_loaded + 1; -- add this to the loaded sopurce tally end; end; --[[ Check if all sources are loaded ]] local curent_source_count = count_sources() all_sources_loaded = ( sources_loaded == curent_source_count ); debug_log( 'all_sources_loaded source: (' .. pre_dump(all_sources_loaded) .. ' loaded: ' .. pre_dump(sources_loaded) .. " of " .. pre_dump(curent_source_count) .. ')' ) --[[ Some of the functions included requires the source to be loaded, so check that all sources are loaded before continuing ]] if all_sources_loaded then debug_log( 'all_sources_loaded are now loaded!' ) obs.obs_data_set_bool( ctx.propsSet, "script_ready", true ); -- set to signal ready state. debug_log( 'loaded set script_ready (' .. pre_dump(true) .. ') ' ) --[[ load any property values available to globals ]] load_settings_globals( ctx.propsSet ); set_visible( media["source_name_audio_marker_a"], false ); -- set hidden just in case set_visible( media["source_name_audio_marker_b"], false ); -- set hidden just in case set_visible( media["source_name_audio_marker_end"], false ); -- set hidden just in case set_visible( add_limit_note_source, false ); -- set hidden just in case set_visible( sub_limit_note_source, false ); -- set hidden just in case if start_on_visible and not start_on_scene_active then set_visible( timer_source, false ) -- set hidden as a starting point, the user can start the timer by setting it to visible end if timer_mode == 1 then if load_saved_time then timer_value( sw_current_seconds ); -- value else timer_value( 0 ); reset( true ); -- 1 then obs.obs_property_int_set_suffix( sec_add_limit_prop, " Calls" ); end if sec_add_limit == 1 then obs.obs_property_int_set_suffix( sec_add_limit_prop, " Call" ); end if sec_add_limit == 0 then obs.obs_property_int_set_suffix( sec_add_limit_prop, " Zero allow infinite calls" ); end if sec_sub_limit > 1 then obs.obs_property_int_set_suffix( sec_sub_limit_prop, " Calls" ); end if sec_sub_limit == 1 then obs.obs_property_int_set_suffix( sec_sub_limit_prop, " Call" ); end if sec_sub_limit == 0 then obs.obs_property_int_set_suffix( sec_sub_limit_prop, " Zero allow infinite calls" ); end --[[ Rebuild backup file list ]] local filenames = get_filenames( backup_folder, ".json" ); local has_file_list = ( table.getn( filenames ) > 0 ); if obs.os_file_exists( backup_folder ) then obs.obs_property_list_clear( import_list_prop ); obs.obs_property_list_add_string( import_list_prop, "Select", "select" ); if has_file_list then for i,v in pairs( filenames ) do obs.obs_property_list_add_string( import_list_prop, v, v ); end; end; end; -- IMPORTANT: returns true to trigger refresh of the properties return true; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] local function cycle_source_list( source_type ) debug_log( 'cycle_source_list(' .. pre_dump(source_type) .. ') -- function variable names: source_type ' ) reset( true ); -- Reset the timer cycle_source_list_by_source_type( source_type ); --[[ Set timer_active for it to self-start ]] --timer_active = false pause( true ); -- Restart the timer end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function timer_end_media_end_callback( ) debug_log( 'timer_end_media_end_callback() -- function variable names: ' ) set_visible( media['source_name_audio_marker_end'], false ); timer_remove( timer_end_media_end_callback ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: Only used in Countdown mode Credit: Modified: function: type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function timer_ended( source_name ) debug_log( 'timer_ended(' .. pre_dump(source_name) .. ') -- function variable names: source_name ' ) --[[ stop the timer ]] activate( false ); -- This will remove the callback. --[[ If user wants recording to start when timer ended, now is a good timer to initiate recording NOTE: Recording will only be started, if it is not already recording ]] record( 1, 100 ); --[[ Force media playback to end, if the media is looping ]] stop_looping_media( "marker_a" ); stop_looping_media( "marker_b" ); --[[ Timer expired and a scene change is requested. ]] if not in_table( {"TIMER END TEXT", "Source List", "Scene List", "Select"}, next_scene ) then --[[ The timer expired and the timer will not restart. ]] set_visible( timer_source, false ); -- last thing, set visibility of timer to hide because we are changing to another scene --[[ Increments the source reference counter, use obs_source_release() to release it when complete. ]] local source = obs.obs_get_source_by_name( next_scene ); if source ~= nil then obs.obs_frontend_set_current_scene( source ); obs.obs_source_release( source ); --[[ timer_mode: countdown only timer_active: timer not running define > set_to_default: (true) * Timer time expired and the timer time needs to be set to the default state, so we do want to set the settings to default. define > new_settings: not required here because we will use the global (ctx.propsSet) purpose: The timer is completed and we need to provide instant feedback output to the timer display (timer text source) ]] --[[ used to be fresh_start set_to_default: true false timer_active: true false activated: true false timer_expired: false true update_timer_settings ---> set_time_text ---> timer_ended ---> update_timer_settings -- death loop ]] update_timer_settings( true ); -- optional inputs: set_to_default(bool), new_settings(obs_property_data/obs_userdata) end --[[ The timer expired and the timer will not restart. ]] timer_expired = true; timer_active = false; end if next_scene == "Source List" then cycle_source_list( "source" ); end if next_scene == "Scene List" then cycle_source_list( "scene" ); end if next_scene == "TIMER END TEXT" then local text = stop_text; set_text( source_name, text ); end --[[ for now, only limit the timer end media playback for these settings: ]] if in_table( {"TIMER END TEXT", "Select"}, next_scene ) then --[[ if a Media source has not been defined, then ignore ]] if not in_table( ignore_list, media["source_name_audio_marker_end"] ) then set_visible( media["source_name_audio_marker_end"], true ); -- play the media disconnect_after_media_end( "marker_end" ); -- attach a signal handler to set source hidden when media playback ends --[[ If the user wants the media to stop after defined duration we need to add this timer callback ]] if media["duration_marker_end"] ~= 0 and media_playback_limit ~= 1 then timer_add( timer_end_media_end_callback, math.floor( media["duration_marker_end"] * 1000 ) ); --<- milliseconds end; end; end; --[[ TODO> We want to update the timer start button description at this point. ]] end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: A function named script_properties defines the properties that the user can change for the entire script module itself. The stacking order of properties detirmine the order and position of the items on the UI Credit: Modified: function: Creates UI type: Properties input type: none returns: props ---------------------------------------------------------------------------------------------------------------------------------------- ]] function script_properties() debug_log( 'script_properties() -- function variable names: ' ) props = obs.obs_properties_create(); -- save original object ctx.propsDef = props; -- this becomes a data object --[[ Option list: User select type of timer required. This property is referenced to trigger an onchange event listener. ]] local p_1 = obs.obs_properties_add_list( ctx.propsDef, "timer_mode", "Timer Type", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT ); t_type = {"Stopwatch", "Countdown"}; -- Add options to the list for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_1, v, i ) end -- This list is auto indexed thus have an interger reference containing a string value --[[ Option list: User select to access Basic or Advanced Features. This provides UI layout options to enhance the user experience. Changing this setting does not impact any function or result. This property is referenced to trigger an onchange event listener. ]] local p_2 = obs.obs_properties_add_list( ctx.propsDef, "config", "Configuration", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT ); t_type = {"Basic", "Advanced"}; -- Adds options to the list for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_2, v, i ) end -- This list is auto indexed thus have an interger reference containing a string value --[[ Option list: User select to access show or hide available features. This provides UI layout options to enhance the user experience. Changing this setting does not impact any function or result. This property is referenced to trigger an onchange event listener. ]] local p_3 = obs.obs_properties_add_list( ctx.propsDef, "timer_options", "Timer Settings", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT ); t_type = {"Hidden", "Expanded"}; -- Add options to the list for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_3, v, i ) end -- This list is auto indexed thus have an interger reference containing a string value obs.obs_property_set_long_description( p_3, "\nExpand or hide additional timer options.\n" ); -- User Tip --[[ Returns an array of reference-incremented sources. Release with source_list_release(). ]] local sources = obs.obs_enum_sources(); --[[ Option list: User select to be used as visual feedback indicating a time stamp. This provides function options that will impact on visual feedback to the user. Changing this setting will impact on the function or end result. ]] local p_4 = obs.obs_properties_add_list( ctx.propsDef, "timer_source", "Timer Source", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING ); obs.obs_property_list_add_string( p_4, "Select", "select" ); -- Adds a default option to the list. First (top-most) list item. If selected the option is ignored. local list = {}; -- Define a table variable to be used to build lists that will be passed to the property list if sources ~= nil then for _, source in ipairs( sources ) do -- ipairs cycles auto incrimented items source_id = obs.obs_source_get_unversioned_id( source ); -- unversioned_id will not be affected by language settings if source_id == "text_gdiplus" or source_id == "text_ft2_source" then -- identify text type sources only local name = obs.obs_source_get_name( source ); -- Get the source name, this will be a unique identifier --[[ boolean check, is the source (name) already selected for another item? This will help to limit conflicts to prevent using a single source for two sperate functions ]] local reference = "timer_source"; if not source_selected( name, reference ) then -- boolean check --[[ add it to the (temp) table (list) ]] list[name] = name; else -- continue to skip potential conflicts end end end obs.bfree(source); -- free memory, release source as it is no longer needed --[[ This property list will be a type of string referenced items with a string value. The string reference must be unique or it will/may be overriden. Being string referenced the list will be compiled chronologically, thus the list names (values) may appear unordered and random. To reorganise and arrange the list alphabetically we will use pairsByKeys(). This will make it easier for the user to review and select the desired item from the list. ]] for key, value in pairsByKeys( list ) do --[[ add item to property list ]] obs.obs_property_list_add_string( p_4, value, value ); -- add the item to the property list using a string reference with a string value end end --[[ Option list: User select a reference to be used as control to switch the timer between countup and countdown. This provides function options that will impact operation and behaviour. Changing this setting will impact on the function or end result. This property is referenced to trigger an onchange event listener. ]] local p_5 = obs.obs_properties_add_list( ctx.propsDef, "countdown_type", "Countdown Type", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT ); t_type = {"Specific Date & Time", "Hours, Minutes, Seconds"}; -- Add options to the list for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_5, v, i ) end; -- This list is auto indexed thus have an interger reference containing a string value --[[ Text Field ]] local p_6 = obs.obs_properties_add_text( ctx.propsDef, "day_text", "Day Text Format", obs.OBS_TEXT_DEFAULT ); obs.obs_property_set_long_description( p_6, "\nUsed to distinguish between singular and plural days format. Use this for singular.\n" ); -- User Tip --[[ Text Field ]] local p_7 = obs.obs_properties_add_text( ctx.propsDef, "days_text", "Days Text Format", obs.OBS_TEXT_DEFAULT ); obs.obs_property_set_long_description( p_7, "\nUsed to distinguish between singular and plural days format. Use this for plural.\n" ); -- User Tip --[[ Option list: User select month reference to be used as control to set a timer for a future date. This provides function options that will impact on function and operation of the timer. Changing this setting will impact on the function or end result. This property is referenced to trigger an onchange event listener. ]] local p_8 = obs.obs_properties_add_list( ctx.propsDef, "month", "Month", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT ); t_type = {"Select", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; -- Add options to the list for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_8, v, i ) end; -- This list is auto indexed thus have an interger reference containing a string value --[[ Inerger Field This property is referenced to trigger an onchange event listener. ]] local p_9 = obs.obs_properties_add_int( ctx.propsDef, "year", "Year", 2022, 212021221, 1 ); --[[ Inerger Field ]] local p_10 = obs.obs_properties_add_int( ctx.propsDef, "day", "Day", 1, 31, 1 ); --[[ Inerger Field ]] local p_11 = obs.obs_properties_add_int( ctx.propsDef, "hours", "Hours", 0, 23, 1 ); obs.obs_property_int_set_suffix( p_11, " Hours" ); --[[ Inerger Field ]] local p_12 = obs.obs_properties_add_int( ctx.propsDef, "minutes", "Minutes", 0, 59, 1 ); obs.obs_property_int_set_suffix( p_12, " Minutes" ); --[[ Inerger Field ]] local p_13 = obs.obs_properties_add_int( ctx.propsDef, "seconds", "Seconds", 0, 59, 1 ); obs.obs_property_int_set_suffix( p_13, " Seconds" ); --[[ Option list: User select a reference to be used as control format the timer timestamp. This provides function options that will impact on visual feedback and does not impact any function and operation of the timer. Changing this setting will impact on visual feedback and not function. This property is referenced to trigger an onchange event listener. ]] local p_14 = obs.obs_properties_add_list( ctx.propsDef, "timer_format", "Timer Format", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT ); t_type = {"Display full format", "No leading zeros", "No leading zeros, no split seconds", "No split seconds", "Custom Time Format"}; -- Add options to the list for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_14, v, i ) end; -- This list is auto indexed thus have an interger reference containing a string value --[[ Text Field This property is referenced to trigger an onchange event listener. ]] local p_15 = obs.obs_properties_add_text( ctx.propsDef, "custom_time_format", "Time Format", obs.OBS_TEXT_DEFAULT ); obs.obs_property_set_long_description( p_15, "\n Timestamp is represented by $D = day, $H = hour, $M = minute, $S = second, $F = split second. \n\n Add 'M' and number enclosed in '{}' to adjust minute format: {M90} will display 90 minutes units.\n\n To trim leading zeros, include $T = truncate leading zeros. This will ONLY affect a format matching '$D:$H:$M:$S,$F' (00:00:00:00,00)\n" ); -- User Tip --[[ Text Field This property is referenced to trigger an onchange event listener. ]] local p_16 = obs.obs_properties_add_text( ctx.propsDef, "toggle_mili_trigger", "Toggle Milliseconds", obs.OBS_TEXT_DEFAULT ); obs.obs_property_set_long_description( p_16, "\nUse format 00:00:00 ( hoursa:minutes:seconds )\n" ); -- User Tip --[[ Option list: User select a reference to be used as control to show or hide the time stamp when the timer expire. This provides function options that will impact visual feedback but will not impact operation and behaviour. Changing this setting will not impact on the function or end result. This property is referenced to trigger an onchange event listener. ]] local p_17 = obs.obs_properties_add_list( ctx.propsDef, "timer_display", "Display", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT ); t_type = {"Show expired time stamp", "Remove expired time stamp"}; -- Add options to the list for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_17, v, i ) end; -- This list is auto indexed thus have an interger reference containing a string value --[[ Option list: User select to be used as visual feedback indicating a snip of the time stamp. This provides function options that will impact on visual feedback to the user. Changing this setting will not impact on the function or end result. ]] local p_18 = obs.obs_properties_add_list( ctx.propsDef, "split_source", "Split Source", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING ); obs.obs_property_list_add_string( p_18, "Select", "select" ); -- Adds a default option to the list. First (top-most) list item. If selected the option is ignored. list = {} -- Reset / clear a defined table variable to be used to build lists that will be passed to the property list if sources ~= nil then for _, source in ipairs( sources ) do source_id = obs.obs_source_get_unversioned_id( source ); -- unversioned_id will not be affected by language settings if source_id == "text_gdiplus" or source_id == "text_ft2_source" then -- identify text type sources only local name = obs.obs_source_get_name( source ); local reference = "split_source"; if not source_selected( name, reference ) then --[[ add it to list so that it can be reordered ]] list[name] = name; else --continue end end end obs.bfree(source); -- free memory, release source as it is no longer needed --[[ This property list will be a type of string referenced items with a string value. The string reference must be unique or it will/may be overriden. Being string referenced the list will be compiled chronologically, thus the list names (values) may appear unordered and random. To reorganise and arrange the list alphabetically we will use pairsByKeys(). This will make it easier for the user to review and select the desired item from the list. ]] for key, value in pairsByKeys(list) do --[[ add item to property list ]] obs.obs_property_list_add_string( p_18, value, value ); end end local p_19 = obs.obs_properties_add_list( ctx.propsDef, "split_type", "Split Type", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT ); t_type = {"Interval", "Mark", "Mark Interval", "Interval Mark"}; -- Add options to the list for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_19, v, i ) end; -- This list is auto indexed thus have an interger reference containing a string value obs.obs_property_set_long_description( p_19, "\nInterval = Time between current and previous split.\n\nMark = Time of split\n" ); -- User Tip --[[ Option list: User select to show or hide available features. This provides UI layout options to enhance the user experience. Changing this setting does not impact any function or result. This property is referenced to trigger an onchange event listener. ]] local p_20 = obs.obs_properties_add_list( ctx.propsDef, "trigger_options", "Marker Settings", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT ); t_type = {"Hidden", "Expanded"}; -- Add options to the list for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_20, v, i ) end; -- This list is auto indexed thus have an interger reference containing a string value obs.obs_property_set_long_description( p_20, "\nExpand or hide additional options triggered by time stamps.\n" ); -- User Tip --[[ Text Field ]] -- upgraded local p_21 = obs.obs_properties_add_text( ctx.propsDef, "text_marker_a", "Marker A Time", obs.OBS_TEXT_DEFAULT ); -- upgraded obs.obs_property_set_long_description( p_21, "\nUse format 00:00:00 ( hoursa:minutes:seconds )\n" ); -- User Tip --[[ Editable Option list: User adds a text time stamp that will trigger time Marker functions. This provides function options to change feature behaviour. Changing this setting will impact on feature behaviour. ]] local p_21 = obs.obs_properties_add_editable_list( ctx.propsDef, "text_arr_marker_a", "Marker A Time", obs.OBS_EDITABLE_LIST_TYPE_STRINGS, nil, nil ); --[[ Text Field ]] -- upgraded local p_22 = obs.obs_properties_add_text( ctx.propsDef, "text_marker_b", "Marker B Time", obs.OBS_TEXT_DEFAULT ); -- upgraded obs.obs_property_set_long_description( p_22, "\nUse format 00:00:00 ( hoursa:minutes:seconds )\n" ); -- User Tip --[[ Editable Option list: User adds a text time stamp that will trigger time Marker functions. This provides function options to change feature behaviour. Changing this setting will impact on feature behaviour. ]] local p_22 = obs.obs_properties_add_editable_list( ctx.propsDef, "text_arr_marker_b", "Marker B Time", obs.OBS_EDITABLE_LIST_TYPE_STRINGS, nil, nil ); --*props, *name, *description, min, max, step obs.obs_properties_add_int_slider( ctx.propsDef, "reset_text_marker_a", "Reset Marker A Text", 0, 10800, 1 ); obs.obs_properties_add_int_slider( ctx.propsDef, "reset_text_marker_b", "Reset Marker A Text", 0, 10800, 1 ); --[[ Option list: User select to be used as audio visual at a defined time. This provides function options that will impact on audio visual feedback to the user. Changing this setting will impact on a function or end result. ]] local p_23 = obs.obs_properties_add_list( ctx.propsDef, "audio_marker_a", "Marker A Audio", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING ); obs.obs_property_set_long_description( p_23, "\nSelect available media source to activate on defined time stamp.\n" ); -- User Tip obs.obs_property_list_add_string( p_23, "None", "none" ); -- Add options to the list obs.obs_property_list_add_string( p_23, "Allow Multiple Selections", "list" ); -- Add options to the list if sources ~= nil then for _, source in ipairs( sources ) do source_id = obs.obs_source_get_unversioned_id( source ); -- unversioned_id will not be affected by language settings if source_id == "ffmpeg_source" then -- identify media type sources only local name = obs.obs_source_get_name( source ); obs.obs_property_list_add_string( p_23, name, name ); end end obs.bfree(source); -- free memory, release source as it is no longer needed end --[[ ]] local p_23a = obs.obs_properties_add_editable_list( ctx.propsDef, "audio_marker_a_arr", "Marker A Audio List", obs.OBS_EDITABLE_LIST_TYPE_STRINGS, nil, nil ); --[[ Option list: User select to be used as audio visual at a defined time. This provides function options that will impact on audio visual feedback to the user. Changing this setting will impact on a function or end result. ]] local p_24 = obs.obs_properties_add_list( ctx.propsDef, "audio_marker_b", "Marker B Audio", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING ); obs.obs_property_set_long_description( p_24, "\nSelect available media source to activate on defined time stamp.\n" ); -- User Tip obs.obs_property_list_add_string( p_24, "None", "none" ); -- Add options to the list obs.obs_property_list_add_string( p_24, "Allow Multiple Selections", "list" ); -- Add options to the list if sources ~= nil then for _, source in ipairs( sources ) do source_id = obs.obs_source_get_unversioned_id( source ); -- unversioned_id will not be affected by language settings if source_id == "ffmpeg_source" then -- identify media type sources only local name = obs.obs_source_get_name( source ); obs.obs_property_list_add_string( p_24, name, name ); end end obs.bfree(source); -- free memory, release source as it is no longer needed end --[[ ]] local p_24a = obs.obs_properties_add_editable_list( ctx.propsDef, "audio_marker_b_arr", "Marker B Audio List", obs.OBS_EDITABLE_LIST_TYPE_STRINGS, nil, nil ); --[[ Option list: User select to be used as audio visual at a defined time. This provides function options that will impact on audio visual feedback to the user. Changing this setting will impact on a function or end result. ]] local p_25 = obs.obs_properties_add_list( ctx.propsDef, "audio_marker_end", "Timer Expired Audio", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING ) obs.obs_property_set_long_description( p_25, "\nSelect available media source to activate when the timer expired.\n" ) -- User Tip obs.obs_property_list_add_string( p_25, "None", "none" ) -- Add options to the list if sources ~= nil then for _, source in ipairs( sources ) do source_id = obs.obs_source_get_unversioned_id( source ) -- unversioned_id will not be affected by language settings if source_id == "ffmpeg_source" then -- identify media type sources only local name = obs.obs_source_get_name( source ) obs.obs_property_list_add_string( p_25, name, name ) end end obs.bfree(source) -- free memory, release source as it is no longer needed end --[[ Option list: User select a reference to be used as control to enable a time limit for media playback. This provides function options that will impact on audio visual feedback and does impact a function and operation of the timer. Changing this setting will impact on audio visual feedback and function. This property is referenced to trigger an onchange event listener. ]] local p_26 = obs.obs_properties_add_list( ctx.propsDef, "media_playback_limit", "Media Playback Limit", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT ); t_type = {"Disabled", "Enabled"}; -- Add options to the list for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_26, v, i ) end; -- This list is auto indexed thus have an interger reference containing a string value obs.obs_property_set_long_description( p_26, "\nSet a maximum time limit for media playback.\n" ); -- User Tip --*props, *name, *description, min, max, step obs.obs_properties_add_int_slider( ctx.propsDef, "duration_marker_a", "Marker A Duration", 0, 10800, 1 ); obs.obs_properties_add_int_slider( ctx.propsDef, "duration_marker_b", "Marker B Duration", 0, 10800, 1 ); obs.obs_properties_add_int_slider( ctx.propsDef, "duration_marker_end", "End Audio Duration", 0, 10800, 1 ); obs.obs_properties_add_color( ctx.propsDef, "color_normal", "Normal Color" ); obs.obs_properties_add_color( ctx.propsDef, "color_marker_a", "Marker A Color" ); obs.obs_properties_add_color( ctx.propsDef, "color_marker_b", "Marker B Color" ); --[[ Option list: User select a reference to be used as control to enable text notice for the two time triggers. This provides UI layout options to expand feature options. Changing this setting will impact on UI and feature options. This property is referenced to trigger an onchange event listener. ]] local p_27 = obs.obs_properties_add_list( ctx.propsDef, "enable_marker_notes", "Marker Notes", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT ); t_type = {"Disabled", "Enabled"}; -- Add options to the list for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_27, v, i ) end; -- This list is auto indexed thus have an interger reference containing a string value obs.obs_property_set_long_description( p_27, "\nAllows a note to be displayed when the timer match Marker A and Marker B timestamps.\n" ); -- User Tip --[[ Option list: User select to be used as visual feedback indicating a message at a determined time of the timer. This provides function options that will impact on visual feedback to the user. Changing this setting will not impact on the function or end result. ]] local p_28 = obs.obs_properties_add_list( ctx.propsDef, "note_source_marker_a", "Marker A Note Source", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING ); obs.obs_property_list_add_string( p_28, "Select", "select" ); -- Adds a default option to the list. First (top-most) list item. If selected the option is ignored. list = {} -- Reset / clear a defined table variable to be used to build lists that will be passed to the property list if sources ~= nil then for _, source in ipairs( sources ) do source_id = obs.obs_source_get_unversioned_id( source ); -- unversioned_id will not be affected by language settings if source_id == "text_gdiplus" or source_id == "text_ft2_source" then -- identify text type sources only local name = obs.obs_source_get_name( source ); local reference = "note_source_marker_a"; if not source_selected( name, reference ) then --[[ add it to list so that it can be reordered ]] list[name] = name; else --continue end end end obs.bfree(source); -- free memory, release source as it is no longer needed --[[ This property list will be a type of string referenced items with a string value. The string reference must be unique or it will/may be overriden. Being string referenced the list will be compiled chronologically, thus the list names (values) may appear unordered and random. To reorganise and arrange the list alphabetically we will use pairsByKeys(). This will make it easier for the user to review and select the desired item from the list. ]] for key, value in pairsByKeys( list ) do --[[ add item to property list ]] obs.obs_property_list_add_string( p_28, value, value ); end end --[[ Option list: User select to be used as visual feedback indicating a message at a determined time of the timer. This provides function options that will impact on visual feedback to the user. Changing this setting will not impact on the function or end result. ]] local p_29 = obs.obs_properties_add_list( ctx.propsDef, "note_source_marker_b", "Marker B Note Source", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING ); obs.obs_property_list_add_string( p_29, "Select", "select" ); -- Adds a default option to the list. First (top-most) list item. If selected the option is ignored. list = {}; -- Reset / clear a defined table variable to be used to build lists that will be passed to the property list if sources ~= nil then for _, source in ipairs( sources ) do source_id = obs.obs_source_get_unversioned_id( source ); -- unversioned_id will not be affected by language settings if source_id == "text_gdiplus" or source_id == "text_ft2_source" then -- identify text type sources only local name = obs.obs_source_get_name( source ); local reference = "note_source_marker_b"; if not source_selected( name, reference ) then --[[ add it to list so that it can be reordered ]] list[name] = name; else --continue end; end; end; obs.bfree( source ); -- free memory, release source as it is no longer needed --[[ This property list will be a type of string referenced items with a string value. The string reference must be unique or it will/may be overriden. Being string referenced the list will be compiled chronologically, thus the list names (values) may appear unordered and random. To reorganise and arrange the list alphabetically we will use pairsByKeys(). This will make it easier for the user to review and select the desired item from the list. ]] for key, value in pairsByKeys( list ) do --[[ add item to property list ]] obs.obs_property_list_add_string( p_29, value, value ); -- Add options to the list end end --[[ Text Field ]] local p_30 = obs.obs_properties_add_text( ctx.propsDef, "note_marker_a", "Marker A Note", obs.OBS_TEXT_DEFAULT ); obs.obs_property_set_long_description( p_30, "\nDisplay a note when Marker A is Activated.\n" ); -- User Tip --[[ Text Field ]] local p_31 = obs.obs_properties_add_text( ctx.propsDef, "note_marker_b", "Marker B Note", obs.OBS_TEXT_DEFAULT ); obs.obs_property_set_long_description( p_31, "\nDisplay a note when Marker B is Activated.\n" ); -- User Tip --[[ Option list: User select a reference to be used as control to enable an auto start recording feature. This provides function options to change a function behaviour. Changing this setting will impact on features and operation. This property is referenced to trigger an onchange event listener. ]] local p_32 = obs.obs_properties_add_list( ctx.propsDef, "start_recording", "Auto Recording", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT ); obs.obs_property_set_long_description( p_32, "\nEnable recording options\n" ); -- User Tip t_type = {"Yes", "No"}; -- Add options to the list for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_32, v, i ) end; -- This list is auto indexed thus have an interger reference containing a string value -- Combo list filled with the options from _type local p_33 = obs.obs_properties_add_list( ctx.propsDef, "recording_type", "Recording", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT ); obs.obs_property_set_long_description( p_33, "\nSelect whne to start recording\n" ); -- User Tip t_type = {"Timer Expires", "Marker A Time", "Marker B Time", "Timer Visible", "Timer Start"}; -- Add options to the list for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_33, v, i ) end; -- This list is auto indexed thus have an interger reference containing a string value --[[ Text Field ]] local p_34 = obs.obs_properties_add_text( ctx.propsDef, "text_prefix", "Timer Prefix", obs.OBS_TEXT_DEFAULT ); obs.obs_property_set_long_description( p_34, "\nDefine text placed before the Timer\n" ); -- User Tip --[[ Text Field ]] local p_35 = obs.obs_properties_add_text( ctx.propsDef, "text_suffix", "Timer Suffix", obs.OBS_TEXT_DEFAULT ); obs.obs_property_set_long_description( p_35, "\nDefine text placed after the Timer\n" ); -- User Tip --[[ Option list: User select to be used as visual feedback indicating a source name that is targeted. This provides function options that will impact on visual feedback to the user. Changing this setting will impact on the function or end result. ]] local p_36 = obs.obs_properties_add_list( ctx.propsDef, "active_source", "Active Source", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING ); obs.obs_property_set_long_description( p_36, "\nSelect a text source, that will be used to show the name for the current active Source or Scene\n" ); -- User Tip obs.obs_property_list_add_string( p_36, "Select", "select" ); -- Adds a default option to the list. First (top-most) list item. If selected the option is ignored. list = {} -- Reset / clear a defined table variable to be used to build lists that will be passed to the property list if sources ~= nil then for _, source in ipairs( sources ) do source_id = obs.obs_source_get_unversioned_id( source ); -- unversioned_id will not be affected by language settings if source_id == "text_gdiplus" or source_id == "text_ft2_source" then -- identify text type sources only local name = obs.obs_source_get_name( source ); local reference = "active_source"; if not source_selected( name, reference ) then --[[ add it to list so that it can be reordered ]] list[name] = name; else --continue end; end; end; obs.bfree(source); -- free memory, release source as it is no longer needed --[[ This property list will be a type of string referenced items with a string value. The string reference must be unique or it will/may be overriden. Being string referenced the list will be compiled chronologically, thus the list names (values) may appear unordered and random. To reorganise and arrange the list alphabetically we will use pairsByKeys(). This will make it easier for the user to review and select the desired item from the list. ]] for key, value in pairsByKeys( list ) do --[[ add item to property list ]] obs.obs_property_list_add_string( p_36, value, value ); -- Add options to the list end; end; --[[ Option list: User select a reference to be used as control to enable an action que task once the timer expires. This provides function options to change a function behaviour. Changing this setting will impact on features and operation. This property is referenced to trigger an onchange event listener. ]] local p_37 = obs.obs_properties_add_list( ctx.propsDef, "next_scene", "Next Scene", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING ); obs.obs_property_set_long_description( p_37, "\nDefine what happens afater timer ends\n" ); -- User Tip t_type = {"Select", "TIMER END TEXT", "Source List", "Scene List"}; -- Add options to the list for i,v in ipairs( t_type ) do obs.obs_property_list_add_string( p_37, v, v ); -- Add options to the list end; --[[ Adding a scene to become active once the timer expires is a feature included This will collect available scene names and list them for the user to choose from. The scene names will be added into the existing option list. ]] local scenes = obs.obs_frontend_get_scene_names(); if scenes ~= nil then for i, scene in ipairs( scenes ) do obs.obs_property_list_add_string( p_37, scene, scene ); -- Add options to the list end; obs.bfree( scene ); -- free memory, release source as it is no longer needed end; --[[ Text Field ]] local p_38 = obs.obs_properties_add_text( ctx.propsDef, "stop_text", "Timer End Text", obs.OBS_TEXT_DEFAULT ); obs.obs_property_set_long_description( p_38, "\nDefine text displayed when timer ended\n" ); -- User Tip --[[ Editable Option list: User adds a text name as an entry used as a reference to be used as inditification of a source. This provides function options to change feature behaviour. Changing this setting will impact on feature options and behaviour. ]] local p_39 = obs.obs_properties_add_editable_list( ctx.propsDef, "cycle_list", "Cycle List", obs.OBS_EDITABLE_LIST_TYPE_STRINGS, nil, nil ); --[[ Option list: User select a reference to be used as control to determine a cycling direction. This provides function options to change feature behaviour. Changing this setting will impact on feature options. ]] local p_40 = obs.obs_properties_add_list( ctx.propsDef, "cycle_direction", "Cycle Direction", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT ); t_type = {"Ascending", "Descending"}; -- Add options to the list for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_40, v, i ) end; -- This list is auto indexed thus have an interger reference containing a string value obs.obs_property_set_long_description( p_40, "\nSelect the rotation direction of lists.\n" ); -- User Tip --[[ Option list: User select to show or hide available features. This provides UI layout options to enhance the user experience. Changing this setting does not impact any function or result. This property is referenced to trigger an onchange event listener. ]] local p_ab = obs.obs_properties_add_list( ctx.propsDef, "timer_manipulation", "Timer Manipulation", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT ); t_type = {"Hidden", "Expanded", "Disabled"}; -- Add options to the list for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_ab, v, i ) end; -- This list is auto indexed thus have an interger reference containing a string value obs.obs_property_set_long_description( p_ab, "\nExpand or hide additional options for time adjustments.\n" ); -- User Tip --[[ Wrap and group properties together. Thise group provides options to the user to define a custom time value used as a start point for a stopwatch to continue from ]] local group_props_2 = obs.obs_properties_create(); obs.obs_properties_add_group( ctx.propsDef, "_group_2", "Add Seconds to time (Initiate with Hotkey)", obs.OBS_GROUP_NORMAL, group_props_2 ); -- obs_properties_t *props, const char *name, const char *description, int min, int max, int step local p_a1 = obs.obs_properties_add_int( group_props_2, "sec_add_1", "Set 1", 5, 259200, 15 ); obs.obs_property_int_set_suffix( p_a1, " Seconds added to time per call" ); local p_a2 = obs.obs_properties_add_int( group_props_2, "sec_add_2", "Set 2", 15, 259200, 15 ); obs.obs_property_int_set_suffix( p_a2, " Seconds added to time per call" ); local p_a3 = obs.obs_properties_add_int( group_props_2, "sec_add_3", "Set 3", 30, 259200, 15 ); obs.obs_property_int_set_suffix( p_a3, " Seconds added to time per call" ); local p_a4 = obs.obs_properties_add_int( group_props_2, "sec_add_limit", "Limit", 0, 1000, 1 ); obs.obs_property_int_set_suffix( p_a4, " Zero allow infinite calls" ); local p_a5 = obs.obs_properties_add_int( group_props_2, "sec_add_limit_used", "Used", 0, 1000, 1 ); obs.obs_property_int_set_suffix( p_a5, " Refesh to update form" ); obs.obs_property_set_long_description( p_a5, "\nClick the refresh button the update and show the used limits.\n" ); -- User Tip local p_a6 = obs.obs_properties_add_list( group_props_2, "add_limit_note_source", "Subtract Note", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING ); obs.obs_property_set_long_description( p_a6, "\nSelect a text source, that will be used to show a message when the limit was reached.\n" ); -- User Tip obs.obs_property_list_add_string( p_a6, "Select", "select" ) -- Adds a default option to the list. First (top-most) list item. If selected the option is ignored. list = {} -- Reset / clear a defined table variable to be used to build lists that will be passed to the property list if sources ~= nil then for _, source in ipairs( sources ) do source_id = obs.obs_source_get_unversioned_id( source ); -- unversioned_id will not be affected by language settings if source_id == "text_gdiplus" or source_id == "text_ft2_source" then -- identify text type sources only local name = obs.obs_source_get_name( source ); local reference = "add_limit_note_source"; if not source_selected( name, reference ) then --[[ add it to list so that it can be reordered ]] list[name] = name; else --continue end; end; end; obs.bfree(source); -- free memory, release source as it is no longer needed --[[ This property list will be a type of string referenced items with a string value. The string reference must be unique or it will/may be overriden. Being string referenced the list will be compiled chronologically, thus the list names (values) may appear unordered and random. To reorganise and arrange the list alphabetically we will use pairsByKeys(). This will make it easier for the user to review and select the desired item from the list. ]] for key, value in pairsByKeys( list ) do --[[ add item to property list ]] obs.obs_property_list_add_string( p_a5, value, value ); -- Add options to the list end; end; local p_b7 = obs.obs_properties_add_int( group_props_2, "add_limit_note_source_visible", "Note visible", 0, 36000, 1 ); obs.obs_property_int_set_suffix( p_b7, " milliseconds visible, 0 is infinite" ); local p_a8 = obs.obs_properties_add_button( group_props_2, "sec_add_refresh", "Refresh to update form", refresh_properties ); --[[ Wrap and group properties together. Thise group provides options to the user to define a custom time value used as a start point for a stopwatch to continue from ]] local group_props_3 = obs.obs_properties_create(); obs.obs_properties_add_group( ctx.propsDef, "_group_3", "Subtract Seconds from time (Initiate with Hotkey)", obs.OBS_GROUP_NORMAL, group_props_3 ); -- obs_properties_t *props, const char *name, const char *description, int min, int max, int step local p_b1 = obs.obs_properties_add_int( group_props_3, "sec_sub_1", "Set 1", 5, 259200, 15 ); obs.obs_property_int_set_suffix( p_b1, " Seconds subtracted from time per call" ); local p_b2 = obs.obs_properties_add_int( group_props_3, "sec_sub_2", "Set 2", 15, 259200, 15 ); obs.obs_property_int_set_suffix( p_b2, " Seconds subtracted from time per call" ); local p_b3 = obs.obs_properties_add_int( group_props_3, "sec_sub_3", "Set 3", 30, 259200, 15 ); obs.obs_property_int_set_suffix( p_b3, " Seconds subtracted from time per call" ); local p_b4 = obs.obs_properties_add_int( group_props_3, "sec_sub_limit", "Limit", 0, 1000, 1 ); obs.obs_property_int_set_suffix( p_b4, " Zero allow infinite calls" ); local p_b5 = obs.obs_properties_add_int( group_props_3, "sec_sub_limit_used", "Used", 0, 1000, 1 ); obs.obs_property_int_set_suffix( p_b5, " Refesh to update form" ); obs.obs_property_set_long_description( p_b5, "\nClick the refresh button the update and show the used limits.\n" ); -- User Tip local p_b6 = obs.obs_properties_add_list( group_props_3, "sub_limit_note_source", "Subtract Note", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING ); obs.obs_property_set_long_description( p_b6, "\nSelect a text source, that will be used to show a message when the limit was reached.\n" ); -- User Tip obs.obs_property_list_add_string( p_b6, "Select", "select" ) -- Adds a default option to the list. First (top-most) list item. If selected the option is ignored. list = {} -- Reset / clear a defined table variable to be used to build lists that will be passed to the property list if sources ~= nil then for _, source in ipairs( sources ) do source_id = obs.obs_source_get_unversioned_id( source ); -- unversioned_id will not be affected by language settings if source_id == "text_gdiplus" or source_id == "text_ft2_source" then -- identify text type sources only local name = obs.obs_source_get_name( source ); local reference = "sub_limit_note_source"; if not source_selected( name, reference ) then --[[ add it to list so that it can be reordered ]] list[name] = name; else --continue end; end; end; obs.bfree(source); -- free memory, release source as it is no longer needed --[[ This property list will be a type of string referenced items with a string value. The string reference must be unique or it will/may be overriden. Being string referenced the list will be compiled chronologically, thus the list names (values) may appear unordered and random. To reorganise and arrange the list alphabetically we will use pairsByKeys(). This will make it easier for the user to review and select the desired item from the list. ]] for key, value in pairsByKeys( list ) do --[[ add item to property list ]] obs.obs_property_list_add_string( p_b5, value, value ); -- Add options to the list end; end; local p_b7 = obs.obs_properties_add_int( group_props_3, "sub_limit_note_source_visible", "Note visible", 0, 36000, 1 ); obs.obs_property_int_set_suffix( p_b7, " milliseconds visible, 0 is infinite" ); local p_b8 = obs.obs_properties_add_button( group_props_3, "sec_sub_refresh", "Refresh to update form", refresh_properties ); --[[ Wrap and group properties together. Thise group provides options to the user to define a custom time value used as a start point for a stopwatch to continue from ]] local group_props_1 = obs.obs_properties_create(); obs.obs_properties_add_group( ctx.propsDef, "_group_1", "Stopwatch Start Point", obs.OBS_GROUP_NORMAL, group_props_1 ); local p_41 = obs.obs_properties_add_int( group_props_1, "sw_hours_saved", "HH", 0, 23, 1); local p_42 = obs.obs_properties_add_int( group_props_1, "sw_minutes_saved", "MM", 0, 59, 1); local p_43 = obs.obs_properties_add_int( group_props_1, "sw_seconds_saved", "SS", 0, 59, 1); local p_44 = obs.obs_properties_add_int( group_props_1, "sw_milliseconds_saved", "FF", 0, 99, 1); local p_45 = obs.obs_properties_add_bool( group_props_1, "load_saved_time", "Autoload last time stamp when OBS starts" ); local p_46 = obs.obs_properties_add_button( group_props_1, "sw_button", "Set", sw_saved_button_clicked ); --[[ Hidden Value We save last count in the properties for when OBS shuts down and starts again ]] local p_47 = obs.obs_properties_add_float( group_props_1, "sw_current_seconds", "Saved Seconds", 0, 3600000000, 0.1); obs.obs_property_set_visible( p_47 , false ); --[[ Property Button: User interaction that will start, pause or stop a timer. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] local p_48 = obs.obs_properties_add_button( ctx.propsDef, "pause_button", "Start", pause_button_clicked ); --[[ Property Button: User interaction that will mark a timer timestamp. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] obs.obs_properties_add_button( ctx.propsDef, "split_button", "Split Time", split_button_clicked ); --[[ Property Button: User interaction that will start, pause or stop a timer. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. This property is referenced to trigger an onchange event listener. ]] local p_49 = obs.obs_properties_add_button( ctx.propsDef, "mili_button", "Show Milliseconds", mili_button_clicked ); --[[ Property Button: User interaction that will reset a timer. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] local p_50 = obs.obs_properties_add_button( ctx.propsDef, "direction_button", "Count Up/Down", direction_button_clicked ); --[[ Property Button: User interaction that will reset a timer. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] local p_51 = obs.obs_properties_add_button( ctx.propsDef, "reset_button", "Reset Stopwatch", reset_button_clicked ); --[[ Property Checkbox: User interaction that enable setting a custom stopwatch start timestamp. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. This property is referenced to trigger an onchange event listener. ]] local p_52 = obs.obs_properties_add_bool( ctx.propsDef, "set_stopwatch", "Set Stopwatch" ); --[[ Property Checkbox: User interaction that will start timer if timer source becomes visible. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] local p_53 = obs.obs_properties_add_bool( ctx.propsDef, "start_on_visible", "Start Timer on Source Visible" ); --[[ Property Checkbox: User interaction that will disable the plugin. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] obs.obs_properties_add_bool( ctx.propsDef, "force_reset_on_visible", "Reset Timer on Source Visible" ); --[[ Property Checkbox: User interaction that toggle active source visibility. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] obs.obs_properties_add_bool( ctx.propsDef, "active_source_force_visible", "Toggle Active Source Visibility" ); --[[ Property Checkbox: User interaction that will start timer if scene with timer source becomes active. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] local p_54 = obs.obs_properties_add_bool( ctx.propsDef, "start_on_scene_active", "Start Timer on Scene Active" ); --[[ Property Checkbox: User interaction that will disable the plugin. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] obs.obs_properties_add_bool( ctx.propsDef, "force_reset_on_scene_active", "Reset timer on Scene Active" ); --[[ Property Checkbox: User interaction that will disable the plugin. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] obs.obs_properties_add_bool( ctx.propsDef, "disable_script", "Disable Script" ); --[[ Property Checkbox: User interaction that will disable the plugin. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] local p_55 = obs.obs_properties_add_bool( ctx.propsDef, "enable_direction_toggle", "Enable Timer Direction Toggle" ); --[[ Property Checkbox: User interaction that will disable the plugin. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] obs.obs_properties_add_bool( ctx.propsDef, "prevent_negative_time", "Prevent Negative Time Value" ); --[[ Property Checkbox: User interaction that will enable backup options. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. This property is referenced to trigger an onchange event listener. ]] local p_56 = obs.obs_properties_add_bool( ctx.propsDef, "backup_mode", "Backup Mode" ); --[[ Property Directory Path: User interaction that select a directory path. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. This property is referenced to trigger an onchange event listener. ]] local p_57 = obs.obs_properties_add_path( ctx.propsDef, "backup_folder", "Backup Folder", obs.OBS_PATH_DIRECTORY, nil, nil); --[[ Property list: User interaction that will execute an import feature. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. This property is referenced to trigger an onchange event listener. ]] local p_58 = obs.obs_properties_add_list( ctx.propsDef, "import_list", "Load Settings", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING ); obs.obs_property_list_add_string( p_58, "Select ", "select" ); -- Adds a default option to the list. First (top-most) list item. If selected the option is ignored. obs.obs_property_set_long_description( p_58, "\nSelect the Settings file to import.\n" ); -- User Tip local filenames = get_filenames( path, ".json" ); -- list all files of type if table.getn( filenames ) > 0 then for i,v in pairs( filenames ) do obs.obs_property_list_add_string( p_58, v, v ); -- Add options to the list end; end; --[[ Property Button: User interaction that will export all property settings to a json file. This provides function interaction to change feature behaviour. Interacting with this property will complete a feature task. ]] local p_59 = obs.obs_properties_add_button( ctx.propsDef, "export_button", "Export Settings", export_button_clicked ); --[[ Property Button: User interaction that will import available property settings from a json file and apply them to the properties. This provides function interaction to change feature behaviour. Interacting with this property will complete a feature task. This property is referenced to trigger an onchange event listener. ]] local p_60 = obs.obs_properties_add_button( ctx.propsDef, "import_button", "Import Settings", import_button_clicked ); --[[ Property Button: User interaction that will add defined time to the timer's current time. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] --local p_60 = obs.obs_properties_add_button( ctx.propsDef, "sec_add_1_button", "Add Set 1", sec_add_1_button_clicked ); --[[ Property Button: User interaction that will add defined time to the timer's current time. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] --local p_61 = obs.obs_properties_add_button( ctx.propsDef, "sec_add_2_button", "Add Set 2", sec_add_2_button_clicked ); --[[ Property Button: User interaction that will add defined time to the timer's current time. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] --local p_62 = obs.obs_properties_add_button( ctx.propsDef, "sec_add_3_button", "Add Set 3", sec_add_3_button_clicked ); --[[ Property Button: User interaction that will add defined time to the timer's current time. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] --local p_63 = obs.obs_properties_add_button( ctx.propsDef, "sec_sub_1_button", "Add Set 1", sec_sub_1_button_clicked ); --[[ Property Button: User interaction that will add defined time to the timer's current time. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] --local p_64 = obs.obs_properties_add_button( ctx.propsDef, "sec_sub_2_button", "Add Set 2", sec_sub_2_button_clicked ); --[[ Property Button: User interaction that will add defined time to the timer's current time. This provides function interaction to change feature behaviour. Interacting with this property will impact on feature options and behaviour. ]] --local p_65 = obs.obs_properties_add_button( ctx.propsDef, "sec_sub_3_button", "Add Set 3", sec_sub_3_button_clicked ); --[[ SCRIPT READY MUST BE HIDDEN ]] local p_61 = obs.obs_properties_add_bool( ctx.propsDef, "script_ready", "Script Ready" ); obs.obs_property_set_visible( p_61 , false ); obs.source_list_release( sources ); -- free memory, release sources as it is no longer needed --[[ Callback definitions used to check for user interaction or property changes. Event Listener Each entry provides a callback to a referenced proeprty along with a target callback handler timer_source, split_source, active_source, note_source_marker_a, note_source_marker_b, sub_limit_note_source, add_limit_note_source ]]-- obs.obs_property_set_modified_callback( p_ab, property_onchange ); -- timer_manipulation obs.obs_property_set_modified_callback( p_a4, property_onchange ); -- sec_add_limit obs.obs_property_set_modified_callback( p_b4, property_onchange ); -- sec_sub_limit obs.obs_property_set_modified_callback( p_a6, property_onchange ); -- add_limit_note_source obs.obs_property_set_modified_callback( p_b6, property_onchange ); -- sub_limit_note_source obs.obs_property_set_modified_callback( p_1, property_onchange ); -- timer_mode obs.obs_property_set_modified_callback( p_2, property_onchange ); -- config obs.obs_property_set_modified_callback( p_3, property_onchange ); -- timer_options obs.obs_property_set_modified_callback( p_4, property_onchange ); -- timer_source obs.obs_property_set_modified_callback( p_5, property_onchange ); -- countdown_type obs.obs_property_set_modified_callback( p_8, property_onchange ); -- month obs.obs_property_set_modified_callback( p_9, property_onchange ); -- year obs.obs_property_set_modified_callback( p_11, property_onchange ); -- hours obs.obs_property_set_modified_callback( p_12, property_onchange ); -- minutes obs.obs_property_set_modified_callback( p_13, property_onchange ); -- seconds obs.obs_property_set_modified_callback( p_14, property_onchange ); -- timer_format obs.obs_property_set_modified_callback( p_15, property_onchange ); -- custom_time_format obs.obs_property_set_modified_callback( p_16, property_onchange ); -- toggle_mili_trigger obs.obs_property_set_modified_callback( p_17, property_onchange ); -- timer_display obs.obs_property_set_modified_callback( p_18, property_onchange ); -- split_source obs.obs_property_set_modified_callback( p_20, property_onchange ); -- trigger_options obs.obs_property_set_modified_callback( p_23, property_onchange ); -- audio_marker_a obs.obs_property_set_modified_callback( p_24, property_onchange ); -- audio_marker_b obs.obs_property_set_modified_callback( p_26, property_onchange ); -- media_playback_limit obs.obs_property_set_modified_callback( p_27, property_onchange ); -- enable_marker_notes obs.obs_property_set_modified_callback( p_28, property_onchange ); -- note_source_marker_a obs.obs_property_set_modified_callback( p_29, property_onchange ); -- note_source_marker_b obs.obs_property_set_modified_callback( p_32, property_onchange ); -- start_recording obs.obs_property_set_modified_callback( p_36, property_onchange ); -- active_source obs.obs_property_set_modified_callback( p_37, property_onchange ); -- next_scene obs.obs_property_set_modified_callback( p_52, property_onchange ); -- set_stopwatch obs.obs_property_set_modified_callback( p_45, property_onchange ); -- load_saved_time obs.obs_property_set_modified_callback( p_53, property_onchange ); -- start_on_visible --obs.obs_property_set_modified_callback( p_, property_onchange ); -- force_reset_on_visible obs.obs_property_set_modified_callback( p_54, property_onchange ); -- start_on_scene_active obs.obs_property_set_modified_callback( p_55, property_onchange ); -- enable_direction_toggle obs.obs_property_set_modified_callback( p_56, property_onchange ); -- backup_mode obs.obs_property_set_modified_callback( p_57, property_onchange ); -- backup_folder obs.obs_property_set_modified_callback( p_58, property_onchange ); -- import_list obs.obs_property_set_modified_callback( p_60, import_properties ); -- import_button -- Calls the callback once to set-up current visibility obs.obs_properties_apply_settings( ctx.propsDef, ctx.propsSet ); return ctx.propsDef; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: We use this to keep track of what text type sources was assigned/selected in properties Credit: Modified: function: Called upon settings initialization and modification type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function update_source_list() debug_log( 'update_source_list() -- function variable names: ' ) selected_source_list["timer_source"] = timer_source; selected_source_list["split_source"] = split_source; selected_source_list["active_source"] = active_source; selected_source_list["note_source_marker_a"] = note_source_marker_a; selected_source_list["note_source_marker_b"] = note_source_marker_b; selected_source_list["sub_limit_note_source"] = sub_limit_note_source; selected_source_list["add_limit_note_source"] = add_limit_note_source; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: We use this to keep track of what text type sources was assigned/selected in properties Credit: Modified: function: Called upon settings initialization and modification type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function source_selected( input_value, reference ) debug_log( 'source_selected(' .. pre_dump(input_value) .. ", " .. pre_dump(reference) .. ') -- function variable names: input_value, reference ' ) if in_table( {'Select, select'}, input_value ) or selected_source_list[reference] == input_value then return false end; return in_table( selected_source_list, input_value ); end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: A function named script_update will be called when settings are changed Credit: Modified: function: Called upon settings initialization and modification type: input type: returns: ---------------------------------------------------------------------------------------------------------------------------------------- ]] function script_update( settings ) debug_log( 'script_update(' .. pre_dump(settings) .. ') -- function variable names: settings ' ) --[[ something changed, remove all timers. ]] remove_all_timers(); ctx.propsSet = settings;-- Keep track of current settings --[[ Update a gloabl in case something changed. ]] count_sources(); --[[ Get the correct frequency for splitseconds when the script loads. ]] assign_default_frequency(); --[[ load any property values available to globals ]] load_settings_globals( settings ); -- load all property settings to globals reset_mili( ); -- ensure mili hide/show settings are updated reset( true ); -- anything could have changed so reset everything --[[ If setting changed, update timer ]] update_timer_settings( false ); -- optional inputs: set_to_default(bool), new_settings(obs_property_data/obs_userdata) minute_format = get_minutes_allocation( custom_time_format ); --[[ Make sure the trigger is as accurate as possible depending if the timer is counting up or down. ]] end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: A function named script_defaults will be called to set the default settings Credit: Modified: function: Called when the script is started for the first time or when the script settings is set to default type: settings input type: Called when the Script is loaded thr first time or returns: None ---------------------------------------------------------------------------------------------------------------------------------------- ]] function script_defaults( settings ) debug_log( 'script_defaults(' .. pre_dump(settings) .. ') -- function variable names: settings ' ) --[[ Get the correct frequency for splitseconds when the script loads. ]] assign_default_frequency(); --[[ Set property value by provided property reference. Set property INTEGER TYPES. ]] obs.obs_data_set_default_int( settings, "sec_add_limit", 0 ); obs.obs_data_set_default_int( settings, "sec_add_limit_used", 0 ); obs.obs_data_set_default_int( settings, "sec_sub_limit", 0 ); obs.obs_data_set_default_int( settings, "sec_sub_limit_used", 0 ); obs.obs_data_set_default_int( settings, "hours", 0 ); obs.obs_data_set_default_int( settings, "config", 1 ); obs.obs_data_set_default_int( settings, "minutes", 0 ); obs.obs_data_set_default_int( settings, "seconds", 0 ); obs.obs_data_set_default_int( settings, "sec_add_1", 5 ); obs.obs_data_set_default_int( settings, "sec_add_2", 15 ); obs.obs_data_set_default_int( settings, "sec_add_3", 30 ); obs.obs_data_set_default_int( settings, "sec_sub_1", 5 ); obs.obs_data_set_default_int( settings, "sec_sub_2", 15 ); obs.obs_data_set_default_int( settings, "sec_sub_3", 30 ); obs.obs_data_set_default_int( settings, "split_type", 2 ); obs.obs_data_set_default_int( settings, "timer_mode", 1 ); obs.obs_data_set_default_int( settings, "timer_format", 1 ); obs.obs_data_set_default_int( settings, "timer_display", 1 ); obs.obs_data_set_default_int( settings, "timer_options", 1 ); obs.obs_data_set_default_int( settings, "sw_hours_saved", 0 ); obs.obs_data_set_default_int( settings, "countdown_type", 2 ); obs.obs_data_set_default_int( settings, "recording_type", 5 ); obs.obs_data_set_default_int( settings, "sw_minutes_saved", 0 ); obs.obs_data_set_default_int( settings, "sw_seconds_saved", 0 ); obs.obs_data_set_default_int( settings, "cycle_direction", 1 ); obs.obs_data_set_default_int( settings, "start_recording", 2 ); obs.obs_data_set_default_int( settings, "reset_text_marker_a", 3 ); obs.obs_data_set_default_int( settings, "reset_text_marker_b", 3 ); obs.obs_data_set_default_int( settings, "duration_marker_a", 5 ); obs.obs_data_set_default_int( settings, "duration_marker_b", 5 ); obs.obs_data_set_default_int( settings, "timer_manipulation", 3 ); obs.obs_data_set_default_int( settings, "duration_marker_end", 5 ); obs.obs_data_set_default_int( settings, "enable_marker_notes", 1 ); obs.obs_data_set_default_int( settings, "media_playback_limit", 1 ); obs.obs_data_set_default_int( settings, "sw_milliseconds_saved", 0 ); obs.obs_data_set_default_int( settings, "year", os.date("%Y", os.time()) ); obs.obs_data_set_default_int( settings, "add_limit_note_source_visible", 0 ) obs.obs_data_set_default_int( settings, "sub_limit_note_source_visible", 0 ) obs.obs_data_set_default_int( settings, "color_normal", media["color_normal"] ); obs.obs_data_set_default_int( settings, "color_marker_a", media["color_marker_a"] ); obs.obs_data_set_default_int( settings, "color_marker_b", media["color_marker_b"] ); --[[ Set property STRING TYPES. ]] obs.obs_data_set_default_string( settings, "stop_text", "" ); obs.obs_data_set_default_string( settings, "text_prefix", "" ); obs.obs_data_set_default_string( settings, "text_suffix", "" ); obs.obs_data_set_default_string( settings, "note_marker_a", "" ); obs.obs_data_set_default_string( settings, "note_marker_b", "" ); -- upgraded obs.obs_data_set_default_string( settings, "text_marker_a", "" ); -- upgraded obs.obs_data_set_default_string( settings, "text_marker_b", "" ); obs.obs_data_set_default_string( settings, "next_scene", "select" ); obs.obs_data_set_default_string( settings, "day_text", "# Day \n" ); obs.obs_data_set_default_string( settings, "audio_marker_b", "None" ); obs.obs_data_set_default_string( settings, "audio_marker_a", "None" ); obs.obs_data_set_default_string( settings, "audio_marker_end", "None" ); obs.obs_data_set_default_string( settings, "days_text", "# Days \n" ); obs.obs_data_set_default_string( settings, "split_source", "select" ); obs.obs_data_set_default_string( settings, "timer_source", "select" ); obs.obs_data_set_default_string( settings, "active_source", "select" ); obs.obs_data_set_default_string( settings, "toggle_mili_trigger", "" ); obs.obs_data_set_default_string( settings, "sub_limit_note_source", "select" ); obs.obs_data_set_default_string( settings, "add_limit_note_source", "select" ); obs.obs_data_set_default_string( settings, "note_source_marker_a", "select" ); obs.obs_data_set_default_string( settings, "note_source_marker_b", "select" ); obs.obs_data_set_default_string( settings, "custom_time_format", "$T$D:$H:$M:$S,$F" ); --[[ Set property BOOL TYPES. ]] obs.obs_data_set_default_bool( settings, "backup_mode", false ); obs.obs_data_set_default_bool( settings, "set_stopwatch", false ); obs.obs_data_set_default_bool( settings, "disable_script", false ); obs.obs_data_set_default_bool( settings, "load_saved_time", false ); obs.obs_data_set_default_bool( settings, "start_on_visible", false ); obs.obs_data_set_default_bool( settings, "start_on_scene_active", false ); obs.obs_data_set_default_bool( settings, "force_reset_on_visible", false ); obs.obs_data_set_default_bool( settings, "enable_direction_toggle", false ); obs.obs_data_set_default_bool( settings, "force_reset_on_scene_active", false ); obs.obs_data_set_default_bool( settings, "prevent_negative_time", false ); obs.obs_data_set_default_bool( settings, "active_source_force_visible", false ); obs.obs_data_set_default_bool( settings, "script_ready", true ); -- Assume it is ready debug_log( 'loaded get default script_ready (' .. pre_dump(true) .. ') ' ) -- Keep track of current settings ctx.propsSet = settings; end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: 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. Credit: Modified: function: Called when script is saved type: OBS input type: settings returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] function script_save( settings ) debug_log( 'script_save(' .. pre_dump(settings) .. ') -- function variable names: settings ' ) --[[ script save Reset (Timer) ]] local hotkey_save_array_reset = obs.obs_hotkey_save( hotkey_id_reset ); obs.obs_data_set_array( settings, "reset_hotkey", hotkey_save_array_reset ); obs.obs_data_array_release( hotkey_save_array_reset ); --[[ script save Pause (Start/Pause Timer) ]] local hotkey_save_array_pause = obs.obs_hotkey_save( hotkey_id_pause ); obs.obs_data_set_array( settings, "pause_hotkey", hotkey_save_array_pause ); obs.obs_data_array_release( hotkey_save_array_pause ); --[[ script save Split (available for stopwatch only) ]] local hotkey_save_array_split = obs.obs_hotkey_save( hotkey_id_split ); obs.obs_data_set_array( settings, "split_hotkey", hotkey_save_array_split ); obs.obs_data_array_release( hotkey_save_array_split ); --[[ script is loading. register and assign hotkeys script save ]] local hotkey_save_array_mili = obs.obs_hotkey_save( hotkey_id_mili ); obs.obs_data_set_array( settings, "mili_hotkey", hotkey_save_array_mili ); obs.obs_data_array_release( hotkey_save_array_mili ); --[[ ]] local hotkey_save_array_direction = obs.obs_hotkey_save( hotkey_id_direction ); obs.obs_data_set_array( settings, "direction_hotkey", hotkey_save_array_direction ); obs.obs_data_array_release( hotkey_save_array_direction ); --[[ ]] local hotkey_save_array_sec_add_1 = obs.obs_hotkey_save( hotkey_id_sec_add_1 ); obs.obs_data_set_array( settings, "sec_add_1_hotkey", hotkey_save_array_sec_add_1 ); obs.obs_data_array_release( hotkey_save_array_sec_add_1 ); --[[ ]] local hotkey_save_array_sec_add_2 = obs.obs_hotkey_save( hotkey_id_sec_add_2 ); obs.obs_data_set_array( settings, "sec_add_2_hotkey", hotkey_save_array_sec_add_2 ); obs.obs_data_array_release( hotkey_save_array_sec_add_2 ); --[[ ]] local hotkey_save_array_sec_add_3 = obs.obs_hotkey_save( hotkey_id_sec_add_3 ); obs.obs_data_set_array( settings, "sec_add_3_hotkey", hotkey_save_array_sec_add_3 ); obs.obs_data_array_release( hotkey_save_array_sec_add_3 ); --[[ ]] local hotkey_save_array_sec_sub_1 = obs.obs_hotkey_save( hotkey_id_sec_sub_1 ); obs.obs_data_set_array( settings, "sec_sub_1_hotkey", hotkey_save_array_sec_sub_1 ); obs.obs_data_array_release( hotkey_save_array_sec_sub_1 ); --[[ ]] local hotkey_save_array_sec_sub_2 = obs.obs_hotkey_save( hotkey_id_sec_sub_2 ); obs.obs_data_set_array( settings, "sec_sub_2_hotkey", hotkey_save_array_sec_sub_2 ); obs.obs_data_array_release( hotkey_save_array_sec_sub_2 ); --[[ ]] local hotkey_save_array_sec_sub_3 = obs.obs_hotkey_save( hotkey_id_sec_sub_3 ); obs.obs_data_set_array( settings, "sec_sub_3_hotkey", hotkey_save_array_sec_sub_3 ); obs.obs_data_array_release( hotkey_save_array_sec_sub_3 ); --[[ It is really important that this the last item in this routine ]] --obs.obs_properties_set_flags( ctx.propsDef, obs.obs_properties_get_flags( ctx.propsDef ) or obs.OBS_PROPERTIES_DEFER_UPDATE) if load_saved_time then --update_prop_settings_current_seconds( current_seconds ) -- update current time to last time in properties obs.obs_data_set_double( ctx.propsSet, "sw_current_seconds", current_seconds ); obslua.obs_data_apply( settings, ctx.propsSet ); end end --[[ ---------------------------------------------------------------------------------------------------------------------------------------- Description: a function named script_load will be called on startup Connect hotkey and activation/deactivation signal callbacks -- NOTE: These particular script callbacks do not necessarily have to be disconnected, as callbacks will automatically destroy themselves if the script is unloaded. So there"s no real need to manually disconnect callbacks that are intended to last until the script is unloaded. Credit: Modified: function: Called when OBS is launched or the script is added type: OBS input type: settings returns: none ---------------------------------------------------------------------------------------------------------------------------------------- ]] function script_load( settings ) debug_log( 'script_load(' .. pre_dump(settings) .. ') -- function variable names: settings ' ) local status = count_sources() debug_log( 'script_load sources ready (' .. pre_dump(status) .. ') ' ) status = ( status ~= 0) obs.obs_data_set_bool( ctx.propsSet, "script_ready", status ); -- set to signal ready state. debug_log( 'script_load set script_ready (' .. pre_dump(status) .. ') ' ) --[[ Get the correct frequency for splitseconds when the script loads. ]] assign_default_frequency(); local sh = obs.obs_get_signal_handler(); --[[ attach event listener callback [loaded] for when a source is done loading. ]] obs.signal_handler_connect( sh, "source_load", loaded ); -- monitor for source load completed obs.signal_handler_connect( sh, "source_deactivate", source_deactivated ); -- monitor source deactivates signal_handler_disconnect obs.signal_handler_connect( sh, "source_activate", source_activated ); -- monitor source activates local hotkey_name = ""; --[[ %s: This is a special sequence which matches all whitespace characters. %c: This is a special sequence which matches all control characters (\n, \t, \r, ...). %p: This is a special sequence which matches all punctuation characters (!, ?, &, ...). %w: This is a special sequence which matches all alphanumeric characters (A-Z, a-z, 0-9). see also: https://riptutorial.com/lua/example/20315/lua-pattern-matching ]] --[[ script is loading. register and assign hotkeys Reset (Timer) ]] hotkey_name = "timer_reset_" .. filename():lower():gsub("[%W%p%c%s]", ""); hotkey_id_reset = obs.obs_hotkey_register_frontend( hotkey_name, "Reset " .. filename(), hotkey_send_reset ); local hotkey_save_array_reset = obs.obs_data_get_array( settings, "reset_hotkey" ); obs.obs_hotkey_load( hotkey_id_reset, hotkey_save_array_reset ); obs.obs_data_array_release( hotkey_save_array_reset ); --[[ script is loading. register and assign hotkeys Pause (Start/Pause Timer) ]] hotkey_name = "timer_pause_" .. filename():lower():gsub("[%W%p%c%s]", ""); hotkey_id_pause = obs.obs_hotkey_register_frontend( hotkey_name, "Start/Pause " .. filename(), hotkey_send_pause ); local hotkey_save_array_pause = obs.obs_data_get_array( settings, "pause_hotkey" ); obs.obs_hotkey_load( hotkey_id_pause, hotkey_save_array_pause ); obs.obs_data_array_release( hotkey_save_array_pause ); --[[ script is loading. register and assign hotkeys Split (available for stopwatch only) ]] hotkey_name = "timer_split_" .. filename():lower():gsub("[%W%p%c%s]", ""); hotkey_id_split = obs.obs_hotkey_register_frontend( hotkey_name, "Split Time " .. filename(), hotkey_send_split ); local hotkey_save_array_split = obs.obs_data_get_array( settings, "split_hotkey" ); obs.obs_hotkey_load( hotkey_id_split, hotkey_save_array_split ); obs.obs_data_array_release( hotkey_save_array_split ); --[[ script is loading. register and assign hotkeys Milliseconds (Show/Hide Timer Milliseconds if in Timer format) ]] hotkey_name = "timer_mili_" .. filename():lower():gsub("[%W%p%c%s]", ""); hotkey_id_mili = obs.obs_hotkey_register_frontend( hotkey_name, "Milliseconds Toggle " .. filename(), hotkey_send_mili ); local hotkey_save_array_mili = obs.obs_data_get_array( settings, "mili_hotkey" ); obs.obs_hotkey_load( hotkey_id_mili, hotkey_save_array_mili ); obs.obs_data_array_release( hotkey_save_array_mili ); --[[ script is loading. register and assign hotkeys Change Timer Count Direction (Show/Hide Timer Milliseconds if in Timer format) ]] hotkey_name = "direction_change_" .. filename():lower():gsub("[%W%p%c%s]", ""); hotkey_id_direction = obs.obs_hotkey_register_frontend( hotkey_name, "Change Timer Direction " .. filename(), hotkey_send_direction ); local hotkey_save_array_direction = obs.obs_data_get_array( settings, "direction_hotkey" ); obs.obs_hotkey_load( hotkey_id_direction, hotkey_save_array_direction ); obs.obs_data_array_release( hotkey_save_array_direction ); --[[ script is loading. register and assign hotkeys Add Seconds to Timer ]] hotkey_name = "sec_add_1_" .. filename():lower():gsub("[%W%p%c%s]", ""); hotkey_id_sec_add_1 = obs.obs_hotkey_register_frontend( hotkey_name, "Add Seconds Set 1 " .. filename(), hotkey_send_sec_add_1 ); local hotkey_save_array_sec_add_1 = obs.obs_data_get_array( settings, "sec_add_1_hotkey" ); obs.obs_hotkey_load( hotkey_id_sec_add_1, hotkey_save_array_sec_add_1 ); obs.obs_data_array_release( hotkey_save_array_sec_add_1 ); --[[ script is loading. register and assign hotkeys Add Seconds to Timer ]] hotkey_name = "sec_add_2_" .. filename():lower():gsub("[%W%p%c%s]", ""); hotkey_id_sec_add_2 = obs.obs_hotkey_register_frontend( hotkey_name, "Add Seconds Set 2 " .. filename(), hotkey_send_sec_add_2 ); local hotkey_save_array_sec_add_2 = obs.obs_data_get_array( settings, "sec_add_2_hotkey" ); obs.obs_hotkey_load( hotkey_id_sec_add_2, hotkey_save_array_sec_add_2 ); obs.obs_data_array_release( hotkey_save_array_sec_add_2 ); --[[ script is loading. register and assign hotkeys Add Seconds to Timer ]] hotkey_name = "sec_add_3_" .. filename():lower():gsub("[%W%p%c%s]", ""); hotkey_id_sec_add_3 = obs.obs_hotkey_register_frontend( hotkey_name, "Add Seconds Set 3 " .. filename(), hotkey_send_sec_add_3 ); local hotkey_save_array_sec_add_3 = obs.obs_data_get_array( settings, "sec_add_3_hotkey" ); obs.obs_hotkey_load( hotkey_id_sec_add_3, hotkey_save_array_sec_add_3 ); obs.obs_data_array_release( hotkey_save_array_sec_add_3 ); --[[ script is loading. register and assign hotkeys Add Seconds to Timer ]] hotkey_name = "sec_sub_1_" .. filename():lower():gsub("[%W%p%c%s]", ""); hotkey_id_sec_sub_1 = obs.obs_hotkey_register_frontend( hotkey_name, "Add Seconds Set 1 " .. filename(), hotkey_send_sec_sub_1 ); local hotkey_save_array_sec_sub_1 = obs.obs_data_get_array( settings, "sec_sub_1_hotkey" ); obs.obs_hotkey_load( hotkey_id_sec_sub_1, hotkey_save_array_sec_sub_1 ); obs.obs_data_array_release( hotkey_save_array_sec_sub_1 ); --[[ script is loading. register and assign hotkeys Add Seconds to Timer ]] hotkey_name = "sec_sub_2_" .. filename():lower():gsub("[%W%p%c%s]", ""); hotkey_id_sec_sub_2 = obs.obs_hotkey_register_frontend( hotkey_name, "Add Seconds Set 2 " .. filename(), hotkey_send_sec_sub_2 ); local hotkey_save_array_sec_sub_2 = obs.obs_data_get_array( settings, "sec_sub_2_hotkey" ); obs.obs_hotkey_load( hotkey_id_sec_sub_2, hotkey_save_array_sec_sub_2 ); obs.obs_data_array_release( hotkey_save_array_sec_sub_2 ); --[[ script is loading. register and assign hotkeys Add Seconds to Timer ]] hotkey_name = "sec_sub_3_" .. filename():lower():gsub("[%W%p%c%s]", ""); hotkey_id_sec_sub_3 = obs.obs_hotkey_register_frontend( hotkey_name, "Add Seconds Set 3 " .. filename(), hotkey_send_sec_sub_3 ); local hotkey_save_array_sec_sub_3 = obs.obs_data_get_array( settings, "sec_sub_3_hotkey" ); obs.obs_hotkey_load( hotkey_id_sec_sub_3, hotkey_save_array_sec_sub_3 ); obs.obs_data_array_release( hotkey_save_array_sec_sub_3 ); --[[ If the user is not loading saved time, clear it ]] if not load_saved_time then update_prop_settings_current_seconds( 0 ) -- update current time to last time in properties end end