--[[
----------------------------------------------------------------------------------------------------------------------------------------
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