--[[
----------------------------------------------------------
Open Broadcaster Software®️
OBS > Tools > Scripts
@midnight-studios
Stopwatch
----------------------------------------------------------
]]
--Globals
obs           				= obslua
gversion 					= "3.4"
luafile						= "StopWatch.lua"
obsurl						= "simple-stopwatch.1364/"
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	    				= [[
<hr/><center><h2>Advanced Stopwatch</h2>( Version: %s )</center>
<br><center><img width=38 height=42 src=']] .. icon .. [['/></center>
<br><center><a href="https://github.com/midnight-studios/obs-lua/blob/main/]] .. luafile ..[[">Find it on GitHub</a></center>
<br><p>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.</p><p>Find help on the <a href="https://obsproject.com/forum/resources/]] .. obsurl ..[[">OBS Forum Thread</a>.</p><hr/>]]
last_text    				= ""
custom_time_format			= ""
font_normal					= "#ffffff"
font_dimmed					= "#bfbbbf"
timer_source   				= ""
countdown_type 				= ""
backup_folder 				= ""
import_list 				= ""
output_file_name 			= "-backup($date_stamp).json";
longtimetext_s 				= ""
longtimetext_p 				= ""
last_split_data 			= ""
split_type   				= ""
split_source   				= ""
active_source  				= ""
next_scene					= ""
stop_text					= ""
cur_seconds   				= 0
def_seconds   				= 0
split	     				= 0
timer_year	     			= 0
timer_month	     			= 0
timer_day	     			= 0
timer_hours	     			= 0
timer_minutes 	     		= 0
timer_seconds	     		= 0
timer_type   				= 0
start_recording				= 0
recording_type				= 0
trigger_text				= 1
orig_time     				= 0
time_frequency				= 0
completed_cycles			= 0
ns_last						= 0
cycle_index 				= 0
timer_cycle 				= 10 --milliseconds
split_itm					= {}
split_data					= nil
script_settings 			= nil
props 						= nil
activated     				= false
timer_active  				= false
reset_activated				= false
start_on_visible  			= false
disable_script   			= false
media = {
warning_text				= "",
caution_text				= "",
source_name_audio_warning	= "",
source_name_audio_caution	= "", 
caution_note_source 		= "",
warning_note_source 		= "",
caution_note 				= "",
warning_note 				= "",
warning_activated			= false,
caution_activated			= false, 
cur_seconds_caution			= 0,
cur_seconds_warning			= 0, 
caution_duration			= 0, 
warning_duration			= 0, 
normal_color				= 0xFFFFFFFF,
caution_color				= 0x40f3ed,
warning_color				= 0x05055a,
last_state_caution			= obs.OBS_MEDIA_STATE_NONE,
last_state_warning			= obs.OBS_MEDIA_STATE_NONE
}
hotkey_id_reset			= obs.OBS_INVALID_HOTKEY_ID
hotkey_id_pause			= obs.OBS_INVALID_HOTKEY_ID
hotkey_id_split			= obs.OBS_INVALID_HOTKEY_ID
--[[
----------------------------------------------------------
-- Use this to create a Script Log Output used in testing
----------------------------------------------------------
]]
function log( name, msg )
  if msg ~= nil then
    msg = " > " .. tostring( msg )
  else
    msg = ""
  end
  obs.script_log( obs.LOG_DEBUG, name .. msg )
end
--[[
----------------------------------------------------------
-- Get the name of this script
----------------------------------------------------------
]]
function filename() 
	local str = debug.getinfo(2).source:sub(2) 
	return str:match("^.*/(.*).lua$") or str 
end
--[[
----------------------------------------------------------
	A function named script_description returns the description shown to
	the user
----------------------------------------------------------
]]
function script_description()
	return string.format( desc, tostring( gversion ) )
end
--[[
----------------------------------------------------------
-- 
----------------------------------------------------------
]]
function get_filenames( path )
	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 == ".json" 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
--[[
----------------------------------------------------------
-- 
----------------------------------------------------------
]]
function write_to_json( 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
--[[
----------------------------------------------------------
	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
----------------------------------------------------------
]]
function assign_default_frequency()
	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 )
end	
--[[
----------------------------------------------------------
	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
----------------------------------------------------------
]]
function get_time_lapsed()
	local ns = obs.os_gettime_ns()
	local delta = ( ns/1000000000.0 ) - ( orig_time/1000000000.0 )
	return TimeFormat( delta )
end	
--[[
----------------------------------------------------------
	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.
----------------------------------------------------------
]]
function get_frequency( 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
	return f	
end

--[[
----------------------------------------------------------
	Convert Seconds to hours:minutes:seconds:miliseconds
----------------------------------------------------------
]]
function delta_time(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
--[[
----------------------------------------------------------
	
----------------------------------------------------------
]]
function trim_time( hour, minutes, seconds, mili, trim )
	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 )
	if trim then
		return time
	end	
	if hour == 0 then
		time = string.format( "%02d:%02d"..format_mili, minutes, seconds, mili )
	end
	if hour == 0 and minutes == 0 then
		time = string.format( "%02d"..format_mili, seconds, mili )
	end
	if hour == 0 and minutes == 0 and seconds == 0 then
		format_mili = ( mili and "%02d" or "" )
		time = string.format( format_mili, mili )
	end
	return trim_zero( time )
end
--[[
----------------------------------------------------------
	
----------------------------------------------------------
]]
function trim_zero( int )
   while true do 
      if int:sub( 1,1 ) == '0' then 
         int = int:sub( 2 )
      else
         break
      end
   end   
   return int
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function wait( ms )
	if ms ~= nil then
		local start = math.floor( ( obs.os_gettime_ns()/1000000 ) )
		repeat until ( math.floor( ( obs.os_gettime_ns()/1000000 ) )-start ) >= ms 
	end 
end
--[[
----------------------------------------------------------
	"Timer Expires" 	= 1 
	"Caution Time" 		= 2 
	"Warning Time" 		= 3 
	"Timer Visible" 	= 4 
	"Timer Start" 		= 5
----------------------------------------------------------
]]
function record( mark, ms )
	if timer_type ~= 2 then return end
	if start_recording == 1 and mark == recording_type then
		obs.obs_frontend_recording_start()
	end
	if ms ~= nil then wait( ms ) end 
end	
--[[
----------------------------------------------------------
	Only used in Countdown mode
----------------------------------------------------------
]] 
function timer_ended( source_name )
	delayed_recording()
	if next_scene == "" or next_scene == "Select" then
		return
	end	
	if next_scene ~= "TIMER END TEXT" and next_scene ~= "Source List"  and next_scene ~= "Scene List" then
		set_visible( timer_source, false ) -- last thing, set visibility of timer to hide
		--[[
			Increments the source reference counter, 
			use obs_source_release() to release it when complete.
		]]
		local source = obs.obs_get_source_by_name( next_scene )
		obs.obs_frontend_set_current_scene( source )
		obs.obs_source_release( source )
		fresh_start( true ) 
		--obs.remove_current_callback()
	end
	if next_scene == "Source List" then
		reset( true )
		cycle_list_activate( 'source' )
		on_pause( true )
	end	
	if next_scene == "Scene List" then
		reset( true )
		cycle_list_activate( 'scene' )
		on_pause( true )
	end	
	if next_scene == "TIMER END TEXT" then
		local text = stop_text
		set_text( source_name, text )
	end	
end	
--[[
----------------------------------------------------------
	Function  
----------------------------------------------------------
]]
function cycle_list_activate( source_type )
	local list = {}
	if source_type ~= "source" then
		local scenes = obs.obs_frontend_get_scene_names(  )

		if scenes ~= nil then
			for _, scene in pairs( scenes ) do
				if name ~= scene then
					list[scene] = scene
				end
			end
			obs.bfree( scene )
		end	
	else
		list = {}
		local sources = obs.obs_enum_sources()
		if sources ~= nil then
			for _, source in pairs( sources ) do
				local name = obs.obs_source_get_name( source )
				list[name] = name
			end
		end	
		obs.source_list_release( sources )		
	end
		cycle_list = obs.obs_data_get_array( script_settings, "cycle_list" )
		local count = obs.obs_data_array_count(cycle_list) - 1;
		for i = 0, count do 
			local item = obs.obs_data_array_item(cycle_list, cycle_index);
			local value = obs.obs_data_get_string(item, "value");
			cycle_index = cycle_index + 1
			if cycle_index > count then
				cycle_index = 0
			end	
			if list[value] ~= nil then
				if source_type ~= "source" 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 )
				
					if name == list[value] then 
						-- goto next			
					else
						local source = obs.obs_get_source_by_name( list[value] )
						if source ~= nil then
							obs.obs_frontend_set_current_scene( source )
						end
						obs.obs_source_release( source )
						set_text( active_source, list[value] )
						break	
					end			
				else
					if is_visible(list[value]) then 
						-- goto next			
					else
						set_visible( list[value], true )
						set_text( active_source, list[value] )
						break
					end
				end				
			end	
		end
		obs.obs_data_array_release(cycle_list)	
end	
--[[
----------------------------------------------------------
	Convert Seconds to hours:minutes:seconds:miliseconds
----------------------------------------------------------
]]
function TimeFormat( time, notrim )
	if time == nil then
		return
	end
	local trim = ( timer_trim == 1 )
	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
	minutes = math.floor( ( time - math.floor( time/3600 )*3600 )/60 )
	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 
	if notrim or ( timer_trim == 4 ) then
		return trim_time( hour, minutes, seconds, nil, true )		
	end	
	if ( timer_trim == 5 ) then
		return trim_time( hour, minutes, seconds, mili, true )		
	end	
	return trim_time( hour, minutes, seconds, ( ( timer_trim ~= 3 ) and mili or nil ), trim )
end
--[[
----------------------------------------------------------
	
----------------------------------------------------------
]]
function LongTimeFormat( 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


--[[
----------------------------------------------------------
	
----------------------------------------------------------
]]
function explode(str,delim)
   local t, ll
   t={}
   ll=0
   if(#str == 1) then
      return {str}
   end
   while true do
      l = string.find(str, delim, ll, true) -- find the next d in the string
      if l ~= nil then -- if "not not" found then..
         table.insert(t, string.sub(str,ll,l-1)) -- Save it in our array.
         ll = l + 1 -- save just after where we found it for searching next time.
      else
         table.insert(t, string.sub(str,ll)) -- Save what's left in our array.
         break -- Break at end, as it should be, according to the lua manual.
      end
   end
   return t
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function tablelength(T)
  local count = 0
  for _ in pairs(T) do count = count + 1 end
  return count
end
--[[
----------------------------------------------------------
	
----------------------------------------------------------
]]
function format_time_stamp( timestamp, format )
	local table1 = explode( timestamp, ':' )
	if table1 == nil then return timestamp end
	local c = tablelength( table1 )
	local _, d = timestamp:gsub(":","")
	local _, t = format:gsub("$T","")
	local day, hour, minute, seconds, mili = 0, 0, 0, 0, 0
	if d == 3 then
		if tableHasKey( table1, 1 ) then
			day = table1[1]
		end
		if tableHasKey( table1, 2 ) then
			hour = table1[2]
		end
		if tableHasKey( table1, 3 ) then
			minute = table1[3]
		end
		if tableHasKey( table1, 4 ) then
			seconds = table1[4]
			local table2 = explode( table1[4], ',' )
			if tableHasKey( table2, 1 ) and tableHasKey( table2, 2 ) then
				seconds = table2[1]
				mili = table2[2]
			end	
		end
	end
	if d == 2 then
		if tableHasKey( table1, 1 ) then
			hour = table1[1]
		end
		if tableHasKey( table1, 2 ) then
			minute = table1[2]
		end
		if tableHasKey( table1, 3 ) then
			seconds = table1[3]
			local table2 = explode( table1[3], ',' )
			if tableHasKey( table2, 1 ) and tableHasKey( table2, 2 ) then
				seconds = table2[1]
				mili = table2[2]
			end	
		end
	end
	if d == 1 then
		if tableHasKey( table1, 1 ) then
			minute = table1[1]
		end
		if tableHasKey( table1, 2 ) then
			seconds = table1[2]
			local table2 = explode( table1[2], ',' )
			if tableHasKey( table2, 1 ) and tableHasKey( table2, 2 ) then
				seconds = table2[1]
				mili = table2[2]
			end	
		end
	end
	if tonumber(day) < 10 then 
		day = "0"..day
	end
	timestamp = format:gsub("$T", ""):gsub("$D", day):gsub( "$H", hour):gsub("$M", minute):gsub("$S", seconds):gsub("$F", mili)
	if t ~= 0 and cur_seconds > 0.01 then
		--local reg = '^[0]+[:]?[0]+[:]?[0]+[:]?[0]?'
		local reg = "^[0:,]*" -- close, but misses 1 instance
		timestamp = timestamp:gsub(reg,"")
	end	
	if cur_seconds < 0.01 then -- else it will show 00:00:00
		timestamp = ""
	end	
	return timestamp
end
--[[
----------------------------------------------------------
	Function to set the time text
----------------------------------------------------------
]]
function set_time_text( source_name )
	if reset_activated then 
		reset_activated = false
		fresh_start( true ) 
	end	
	if cur_seconds <= 0.01 and timer_type ~= 1 then cur_seconds = 0 end
	local text = tostring( TimeFormat( cur_seconds ) )
	if timer_trim ~= 5 then
		--[[
			Format the Text 'Day/Days'
		]]
		if timer_type == 2 and countdown_type == 1 and cur_seconds ~= 0 then
			local longtimetext = longtimetext_p
			if math.floor( cur_seconds / 86400 ) <= 1 then
				longtimetext = longtimetext_s
			end
			if math.floor( cur_seconds / 86400 ) <= 0 then
				longtimetext = longtimetext_p
			end		
			text = string.gsub(longtimetext .. text, "[#]", LongTimeFormat( cur_seconds ))
		end		
	else	
		local l_time = LongTimeFormat( cur_seconds )
		local t_time = TimeFormat( cur_seconds )
		text = format_time_stamp( ( l_time ~= 0 ) and string.format("%s:%s", l_time, t_time ) or string.format("%s", t_time ), custom_time_format )
	end	
	if timer_type ~= 2 then
		--text_prefix = ""
		--   text_suffix = ""
	end
	text = text_prefix .. text .. text_suffix
	if text ~= last_text then
		--[[
			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 )
			if not media['caution_activated'] and not media['warning_activated'] then
				obs.obs_data_set_int( settings, "color", media['normal_color'] )
			end		
			media_activate( settings, 'caution' )
			media_activate( settings, 'warning' )
		end
			obs.obs_source_update( source, settings )
			obs.obs_data_release( settings )
			obs.obs_source_release( source )
	end
	stop_media( 'caution' )
	stop_media( 'warning' )
	last_text = text
	if cur_seconds <= 0.01 and timer_type ~= 1 then
	activate( false, true )
	--[[
	Timer Ended
	]]--
	if trigger_text ~= 1 then 
		set_visible( media["caution_note_source"], false )
		set_visible( media["warning_note_source"], false )  
	end		
	timer_ended( source_name )
	end	
end
--[[
----------------------------------------------------------
	Function to set the split time text
----------------------------------------------------------
]]
function set_split_text( source_name )
	if source_name == 'Select' then
		return
	end	
	if reset_activated then 
		reset_activated = false
		fresh_start( true ) 
	end	
	local text = split_data
	if text ~= last_split_data then
	set_text( source_name, text )
	end
	last_split_data = text
end
--[[
----------------------------------------------------------
	Function to set the source text
----------------------------------------------------------
]]
function set_text( 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
--[[
----------------------------------------------------------
	
----------------------------------------------------------
]]
function set_text_note_color( ref )
	if media[ref .. "_note_source"] == 'Select' then return end
	local source = obs.obs_get_source_by_name( media[ref .. "_note_source"] )
	if source ~= nil then
		local settings = obs.obs_source_get_settings( source )
			obs.obs_data_set_string( settings, "text", media[ref..'_note'] )
			obs.obs_data_set_int( settings, "color", media[ref..'_color'] )
	end
	obs.obs_source_update( source, settings )
	obs.obs_data_release( settings )
	obs.obs_source_release( source )
end	
--[[
----------------------------------------------------------
	
----------------------------------------------------------
]]
function media_activate( settings, ref )
	if TimeFormat( cur_seconds, true ) == media[ref..'_text'] then
		if trigger_text ~= 1 and ref == 'caution' then 
			set_visible( media[ref .. "_note_source"], true ) 
			set_visible( media["warning_note_source"], false )
			set_text_note_color( ref ) 
		end		
		if trigger_text ~= 1 and ref == 'warning' then 
			set_visible( media[ref .. "_note_source"], true ) 
			set_visible( media["caution_note_source"], false ) 
			set_text_note_color( ref ) 
		end			
		obs.obs_data_set_int( settings, "color", media[ref..'_color'] )
		media['cur_seconds_'..ref] = cur_seconds
		media[ref..'_activated'] = true
		start_media( media['source_name_audio_'..ref], ref )
		if ref == 'caution' then record( 2 ) end 
		if ref == 'warning' then record( 3 ) end 
	end		
	return settings
end
--[[
----------------------------------------------------------
	
----------------------------------------------------------
]]
function start_media( source_name, ref )
start_media_action( source_name, ref )
end
--[[
----------------------------------------------------------
	Set source visble = true
----------------------------------------------------------
]]
function start_media_action( source_name, ref )
	if source_name == nil or source_name  == "None" then
		return
	end		
	if media[ref..'_activated'] then set_visible( source_name, true ) end
end
--[[
----------------------------------------------------------
	Stop Media is designed to reset the Source to the
	starting state. In other words, make the source
	invisible again. This sould only happen if the media
	ended, or if it is looped, end the media after a
	defined time.
----------------------------------------------------------
]]
function stop_media( ref, bypass )
	if bypass then -- No checks, just stop it
		set_visible( media['source_name_audio_'..ref], false )
	else -- do some checks
		stop_media_action( ref )	
	end	
end
--[[
----------------------------------------------------------
	Check if the source state changed, 
	if so, set source visble = false
----------------------------------------------------------
]]
function stop_media_action( ref )
	local source_name = media['source_name_audio_'..ref]
	if source_name == nil or source_name  == "None" then
		return
	end	
	--[[
		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 get_source_looping( source_name ) then
				--log( 'is looped', source_name )
				if state == obs.OBS_MEDIA_STATE_PLAYING  then
					-- The source is looping, it will never stop
					if source_name == media['source_name_audio_'..ref] then
						local time_remaining = math.floor( media['cur_seconds_'..ref] ) + math.floor( media[ref..'_duration'] ) - math.floor( cur_seconds )
						local time_end = ( time_remaining <= 0 )
						if time_end then
							media['last_state_'..ref] = state
							set_visible( source_name, false )
						end 	
					end
				end
			else
				--log( 'not looped', source_name )
				media['last_state_'..ref] = state
				if state == obs.OBS_MEDIA_STATE_STOPPED or state == obs.OBS_MEDIA_STATE_ENDED then
					set_visible( source_name, false )
				end
			end	
		end	 
    end
    obs.obs_source_release( source )
end
--[[
----------------------------------------------------------
	
----------------------------------------------------------
]]
function get_source_looping( source_name )
	local property = "looping"
	--[[
		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 )	
	local enabled = false 
    if source ~= nil then
		local source_id = obs.obs_source_get_unversioned_id( source )
			if source_id == "ffmpeg_source" then
				local s = obs.obs_source_get_settings( source )
				--local prop = obs.obs_data_get_string( s, property )
				enabled = obs.obs_data_get_bool( s, property )
				obs.obs_data_release( s )
			end


	end
    obs.obs_source_release( source )
	return enabled
end	
--[[
----------------------------------------------------------
	set source visibility
----------------------------------------------------------
]]
function set_visible( target_name, visible )
	if in_table( {'','none', 'select'}, target_name ) then return 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, target_name )
			if sceneitem ~= nil then
				obs.obs_sceneitem_set_visible( sceneitem, visible )
				break	
			end	
		end --end for
		obs.bfree( scn )
		obs.source_list_release( scenes )		
	end
end
--[[
----------------------------------------------------------
	check source visibility
----------------------------------------------------------
]]
function is_visible( target_name )
	local isvisible = false
	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, target_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
	
	return isvisible
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function delayed_recording()
	obs.timer_add( recording_callback, 100 ) --<- milliseconds 
end	
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function recording_callback()
	obs.timer_remove( recording_callback )
	record( 1 )
end
--[[
----------------------------------------------------------
	Add timer here so we have a global setting
----------------------------------------------------------
]]
function start_timer()
	record( 5, 100 ) -- wait 100 miliseconds
	timer_active = true
	fresh_start( false )
	obs.timer_add( timer_callback, timer_cycle ) --<- milliseconds 
end		
--[[
----------------------------------------------------------
	Add timer here so we have a global setting
----------------------------------------------------------
]]
function timer_callback()
	time_frequency = get_frequency( ns_last )
	calculate()
	completed_cycles = completed_cycles + 1
	set_time_text( timer_source )
	--log( 'Applied frequency', time_frequency ) 
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function calculate()
	if timer_type ~= 2 then
		cur_seconds = cur_seconds + time_frequency
	else
		cur_seconds = cur_seconds - time_frequency
	end
end	
--[[
----------------------------------------------------------
	Called if the counter is starting fresh
----------------------------------------------------------
]]
function fresh_start( reset_curent )

	if timer_type == 2 and countdown_type == 1 then
		cur_seconds = delta_time( timer_year, timer_month, timer_day, timer_hours, timer_minutes, timer_seconds)
		def_seconds = cur_seconds
	end
	
	if reset_curent ~= nil then
		if reset_curent then
			cur_seconds = def_seconds
			completed_cycles = 0
			split = 0
			split_itm = {}
			split_data = nil
			media['caution_activated'] = false
			media['warning_activated'] = false
		end
	end
	orig_time = obs.os_gettime_ns()
	set_visible( media["caution_note_source"], false )
	set_visible( media["warning_note_source"], false ) 
end	
--[[
----------------------------------------------------------
----------------------------------------------------------
]]	
function timer_caution_media_end_callback()
	set_visible( media['source_name_audio_caution'], false )	
	obs.remove_current_callback()
end	
--[[
----------------------------------------------------------
----------------------------------------------------------
]]	
function timer_warning_media_end_callback()
	set_visible( media['source_name_audio_warning'], false )
	obs.remove_current_callback()
end	
--[[
----------------------------------------------------------
----------------------------------------------------------
]]	
function disconnect_after_media_end( ref )
	
	local source_name = media['source_name_audio_'..ref] 
	
	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 state = obs.obs_source_media_get_state( source ) -- get the current state for the source
				
			if get_source_looping( source_name ) then -- The source is looping, it will never stop
				
				if ref == "caution" then
					obs.timer_add( timer_caution_media_end_callback, math.floor( media[ref..'_duration'] * 1000 ) ) --<- milliseconds
				end
			
				if ref == "warning" then
					obs.timer_add( timer_warning_media_end_callback, math.floor( media[ref..'_duration'] * 1000 ) ) --<- milliseconds
				end
			 
			else
				--[[
				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
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]	
function signal_media_ended( 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_visible( name, false )
		obs.remove_current_callback()
	end	
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function activate( activating, timer_expired )
	if disable_script then
		return		
	end	
	activated = activating
	if activating then	
		--obs.obs_frontend_recording_start()
		start_timer()
	else
		if timer_expired then
			obs.timer_remove( timer_callback )
			disconnect_after_media_end( 'caution' )
			disconnect_after_media_end( 'warning' )
		else
			timer_active = false
			obs.timer_remove( timer_callback )
			stop_media( 'caution',true )
			stop_media( 'warning',true )	
		end
	end
end
--[[
----------------------------------------------------------
	Called when a source is activated/deactivated
----------------------------------------------------------
]]
function activate_signal( cd, activating )
	local source = obs.calldata_source( cd, "source" )
	if source ~= nil then
		local name = obs.obs_source_get_name( source )
		if ( name == timer_source ) then
			if activating then record( 4, 300 ) end
			if start_on_visible then
				fresh_start( true )
				activate( activating )
			end
		end
	end
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function source_activated( cd )
   if disable_script then
		return		
	end
	activate_signal( cd, true )
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function source_deactivated( cd )
	activate_signal( cd, false )
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function reset( pressed )
	if not pressed then
		return
	end
	reset_activated = true
	set_time_text( timer_source )
	activate( false )
	set_split_text( split_source )
	
	set_text( active_source, "" )
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function reset_button_clicked( props, p )
	reset( true )
	return false
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function pause_button_clicked( props, p )
	on_pause( true )
	return false
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function on_pause( pressed )
	if not pressed then
		return
	end
	
	set_visible( timer_source, true )
	
	if timer_active then
		timer_active = false
		activate( false )
		split_unpack()
		set_split_text( split_source )
		--log( 'OBS Video Frame Time', obs.obs_get_video_frame_time() )
		--log( completed_cycles..' Cycles', get_time_lapsed() )	
	else
		if activated == false then	
			activate( true )
		end	
	end
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function split_button_clicked( props, p )
	on_split( true )
	return false
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function export_button_clicked( props, p )
	print("export_button_clicked")
	on_export( true )
	return false
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function on_export( pressed )
	if not pressed then
		return
	end
	local file_exported = write_to_json( script_settings )
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function import_button_clicked( props, p, settings )
	return false
end

--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function import_properties( props, property, settings )
	local import_folder = backup_folder
	-- convert Windows path to UNIX path
	import_folder = import_folder .. "/" .. import_list .. ".json"
	import_folder = import_folder:gsub([[\]], "/");
	if obs.os_file_exists( import_folder ) then
		data = obs.obs_data_create_from_json_file( import_folder )
		if data ~= nil then
			obs.obs_data_apply( settings, data )
			obs.obs_data_set_string( settings, "import_list", 'select')
			script_settings = settings
		end	
	end
	return true
end
--[[
----------------------------------------------------------
----------------------------------------------------------
]]
function on_split( pressed )
	if not pressed then
		return
	end
	if timer_active then
		split = split + 1
		split_itm[split] = cur_seconds
		split_unpack()
		set_split_text( split_source )
	end
end
--[[
----------------------------------------------------------
	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  ):
----------------------------------------------------------
]]
function split_unpack()
	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
		if split_type == 'Interval' then
			title = 'Interval'
			--subtitle = ''
			text = tostring( TimeFormat( interval ) )
		elseif split_type == 'Mark' then
			title = 'Mark'
			--subtitle = ''
			text = tostring( TimeFormat( mark ) )
		elseif split_type == 'Mark Interval' then
			title = 'Mark                     '
			subtitle = 'Interval'
			text = tostring( TimeFormat( mark )..'          '..TimeFormat( interval ) )
		else	--	"Interval Mark"
			title = 'Interval                  '
			subtitle = 'Mark'
		text = tostring( TimeFormat( interval )..'          '..TimeFormat( 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
--[[
--------------------------------------------------------------------
--------------------------------------------------------------------
]]
function pairsByKeys( t, f )
	local a = {}
	for n in pairs( t ) do table.insert( a, n ) end
	table.sort( a, f )
	local i = 0      -- iterator variable
	local iter = function ()   -- iterator function
		i = i + 1
		if a[i] == nil then return nil
		else return a[i], t[a[i]]
		end
	end
	return iter
end
--[[
--------------------------------------------------------------------
 custom function: helper
----------------------------------------------------------
]]
function tableHasKey(table,key)
    return table[key] ~= nil
end
--[[
--------------------------------------------------------------------
 custom function: helper
----------------------------------------------------------
]]
function in_table( tbl, value )
	local found = false
	for k, v in pairs( tbl ) do
		if value == v then
			found = true
			break
		end	
	end
	return found
end
--[[
----------------------------------------------------------

----------------------------------------------------------
]]
function is_leap_year( year )
	if year % 4 == 0 then
		if year % 100 == 0 then
			if year % 400 == 0 then
			  return true
			else
			  return false
			end			
		else
		 return true
		end
	else
		return false
	end						
end	
--[[
----------------------------------------------------------
Callback on list modification
----------------------------------------------------------
]]
function property_onchange( props, property, settings )
	local filenames = get_filenames( backup_folder )
	local has_file_list = (table.getn( filenames ) > 0)
	-- Retrieves value selected in list
	backup_folder = obs.obs_data_get_string( settings, "backup_folder" )
	local custom_time_format = obs.obs_data_get_string( settings, "custom_time_format" )
	local timer_trim = obs.obs_data_get_int( settings, "timer_trim" )
	local import_list = obs.obs_data_get_string( settings, "import_list" )
	local backup_m = obs.obs_data_get_bool( settings, "backup_mode" )
	local config = obs.obs_data_get_int( settings, "config" )
	local mode = obs.obs_data_get_int( settings, "timer_type" )
	local rec = obs.obs_data_get_int( settings, "start_recording" )
	local scene = obs.obs_data_get_string( settings, "next_scene" )	
	local t_source = obs.obs_data_get_string( settings, "timer_source" )
	local s_source = obs.obs_data_get_string( settings, "split_source" )
	local a_source = obs.obs_data_get_string( settings, "active_source" )
	local mth = obs.obs_data_get_int( settings, "month" )
	local yr = obs.obs_data_get_int( settings, "year" )
	local d = obs.obs_data_get_int( settings, "day" )
	local c_type = obs.obs_data_get_int( settings, "countdown_type" )
	local t_text = obs.obs_data_get_int( settings, "trigger_text" )
	local cn_source = obs.obs_data_get_string( settings, "caution_note_source" )
	local wn_source = obs.obs_data_get_string( settings, "warning_note_source" )
	-- Retrieves property reference
	local import_list_prop = obs.obs_properties_get( props, "import_list" )
	local custom_time_format_prop = obs.obs_properties_get( props, "custom_time_format" )
	local t_text_prop = obs.obs_properties_get( props, "trigger_text" )
	local cn_source_prop = obs.obs_properties_get( props, "caution_note_source" )
	local wn_source_prop = obs.obs_properties_get( props, "warning_note_source" )
	local c_note_prop = obs.obs_properties_get( props, "caution_note" )
	local w_note_prop = obs.obs_properties_get( props, "warning_note" )
	local stop_text_prop = obs.obs_properties_get( props, "stop_text" )
	local text_prefix_prop = obs.obs_properties_get( props, "text_prefix" )
	local text_suffix_prop = obs.obs_properties_get( props, "text_suffix" )
	local recording_type_prop = obs.obs_properties_get( props, "recording_type" )
	local cycle_list_prop = obs.obs_properties_get( props, "cycle_list" )
	local active_source_prop = obs.obs_properties_get( props, "active_source" )
	local day_text_prop = obs.obs_properties_get( props, "day_text" )
	local days_text_prop = obs.obs_properties_get( props, "days_text" )
	local month_prop = obs.obs_properties_get( props, "month" )
	local day_prop = obs.obs_properties_get( props, "day" )
	local year_prop = obs.obs_properties_get( props, "year" )
	local countdown_type_prop = obs.obs_properties_get( props, "countdown_type" )
	local pause_button_prop = obs.obs_properties_get( props, "pause_button" )
	local reset_button_prop = obs.obs_properties_get( props, "reset_button" )
	local start_recording_prop = obs.obs_properties_get( props, "start_recording" )
	local next_scene_prop = obs.obs_properties_get( props, "next_scene" )
	local hours_prop = obs.obs_properties_get( props, "hours" )
	local minutes_prop = obs.obs_properties_get( props, "minutes" )
	local seconds_prop = obs.obs_properties_get( props, "seconds" )
	local split_button_prop = obs.obs_properties_get( props, "split_button" )
	local split_type_prop = obs.obs_properties_get( props, "split_type" )
	local split_source_prop = obs.obs_properties_get( props, "split_source" )
	local timer_trim_prop = obs.obs_properties_get( props, "timer_trim" )
	local audio_caution_prop = obs.obs_properties_get( props, "audio_caution" )
	local audio_warning_prop = obs.obs_properties_get( props, "audio_warning" )
	local caution_duration_prop = obs.obs_properties_get( props, "caution_duration" )
	local warning_duration_prop = obs.obs_properties_get( props, "warning_duration" )
	local normal_color_prop = obs.obs_properties_get( props, "normal_color" )
	local caution_color_prop = obs.obs_properties_get( props, "caution_color" )
	local warning_color_prop = obs.obs_properties_get( props, "warning_color" )
	local caution_text_prop = obs.obs_properties_get( props, "caution_text" )
	local warning_text_prop = obs.obs_properties_get( props, "warning_text" )
	local start_on_visible_prop = obs.obs_properties_get( props, "start_on_visible" )
	local disable_script_prop = obs.obs_properties_get( props, "disable_script" )
	local export_button_prop = obs.obs_properties_get( props, "export_button" )
	local import_button_prop = obs.obs_properties_get( props, "import_button" )
	local export_folder_prop = obs.obs_properties_get( props, "backup_folder" )
	if timer_trim ~= 5 then
		obs.obs_property_set_visible( custom_time_format_prop, false )
	else
		obs.obs_property_set_visible( custom_time_format_prop, true )
	end	
	if backup_m then 
		obs.obs_property_set_visible( import_list_prop, true )
		obs.obs_property_set_visible( export_button_prop, true )
		obs.obs_property_set_visible( import_button_prop, true )
		obs.obs_property_set_visible( export_folder_prop, true )
	else
		obs.obs_property_set_visible( import_list_prop, false )
		obs.obs_property_set_visible( export_button_prop, false )
		obs.obs_property_set_visible( import_button_prop, false )
		obs.obs_property_set_visible( export_folder_prop, false )
	end	
	if import_list ~= "select" then
		obs.obs_property_set_visible( import_button_prop, true )
	else
		obs.obs_property_set_visible( import_button_prop, false )
	end
	if ( in_table( {t_source, s_source, a_source, wn_source}, cn_source ) ) then
		obs.obs_data_set_string(settings, "caution_note_source", 'select') -- Don't allow timer and caution note text source to be the same
	end 
	if ( in_table( {t_source, s_source, a_source, cn_source}, wn_source ) ) then
		obs.obs_data_set_string(settings, "warning_note_source", 'select') -- Don't allow timer and warning note text source to be the same
	end 	
	if ( in_table( {t_source, s_source, cn_source, wn_source}, a_source ) ) then
		obs.obs_data_set_string(settings, "active_source", 'select') -- Don't allow timer and active text source to be the same
	end
	obs.obs_property_set_visible( t_text_prop, ( config == 2 ))
	obs.obs_property_set_visible( cn_source_prop, ( config == 2 and ( t_text ~= 1 ) ))
	obs.obs_property_set_visible( wn_source_prop, ( config == 2 and ( t_text ~= 1 ) ))
	obs.obs_property_set_visible( c_note_prop, ( config == 2 and ( t_text ~= 1 ) ))
	obs.obs_property_set_visible( w_note_prop, ( config == 2 and ( t_text ~= 1 ) ))
	obs.obs_property_set_visible( stop_text_prop, false )
	obs.obs_property_set_visible( text_prefix_prop, false )
	obs.obs_property_set_visible( text_suffix_prop, false )
	obs.obs_property_set_visible( recording_type_prop, false )
	obs.obs_property_set_visible( cycle_list_prop, ( (scene == 'Source List' or scene == 'Scene List') and config == 2 ) )
	obs.obs_property_set_visible( active_source_prop, ( (scene == 'Source List' or scene == 'Scene List') and config == 2 ) )
	obs.obs_property_set_visible( day_text_prop, ( c_type == 1 and config == 2 and mode == 2 and timer_trim ~= 5 ) )
	obs.obs_property_set_visible( days_text_prop, ( c_type == 1 and config == 2 and mode == 2 and timer_trim ~= 5 ) )
	obs.obs_property_set_visible( month_prop, ( c_type == 1 and config == 2 and mode == 2 ) )
	obs.obs_property_set_visible( day_prop, ( c_type == 1 and config == 2 and mode == 2 ) )
	obs.obs_property_set_visible( year_prop, ( c_type == 1 and config == 2 and mode == 2 ) )
	if mth ~= 1 then
	obs.obs_property_set_enabled( day_prop, true )
	obs.obs_property_set_enabled( year_prop, true )
	else
	obs.obs_property_set_enabled( day_prop, false )
	obs.obs_property_set_enabled( year_prop, false )
	end
	if c_type == 1 and mth ~= 1 and d == 0 then
		obs.obs_data_set_int( settings, "day", 1 ) -- set to at least 1, else the timer won't know it is at zero
	end	
	obs.obs_property_int_set_limits( day_prop, 1, 31, 1 )
	if ( mth == 5 or mth == 7 or mth == 10 or mth == 12 ) then
		obs.obs_property_int_set_limits( day_prop, 1, 30, 1 )
	elseif ( mth == 3 ) then
		local leap = 28
		if is_leap_year(yr) then
			 leap = 29
		else
			 leap = 28
		end	
		obs.obs_property_int_set_limits( day_prop, 1, leap, 1 )
	end
	obs.obs_property_set_visible( countdown_type_prop, false )
	obs.obs_property_set_visible( countdown_type_prop, (config == 2 and mode == 2 ) )
	if scene == 'TIMER END TEXT' and  mode == 2 then
		obs.obs_property_set_visible( stop_text_prop, true )
	end	
	-- Preset parameters
	if rec == 1 and mode == 2 then
	obs.obs_property_set_visible( recording_type_prop, config == 2 )
	end
	obs.obs_property_set_visible( text_prefix_prop, config == 2 )
	obs.obs_property_set_visible( text_suffix_prop, config == 2 )
	if mode == 2 then		
	obs.obs_property_set_visible( start_recording_prop, config == 2 )
	obs.obs_property_set_visible( next_scene_prop, config == 2 )
	obs.obs_property_set_visible( hours_prop, (config == 2 ) )
	obs.obs_property_set_visible( minutes_prop, (config == 2 ) )
	obs.obs_property_set_visible( seconds_prop, (config == 2 ) )
	obs.obs_property_set_description( pause_button_prop, "Start/Pause Countdown" )
	obs.obs_property_set_description( reset_button_prop, "Reset Countdown" )
	else	
	obs.obs_property_set_visible( start_recording_prop, false )
	obs.obs_property_set_visible( next_scene_prop, false )
	obs.obs_property_set_visible( hours_prop, false )
	obs.obs_property_set_visible( minutes_prop, false )
	obs.obs_property_set_visible( seconds_prop, false )
	obs.obs_property_set_description( pause_button_prop, "Start/Pause Stopwatch" )
	obs.obs_property_set_description( reset_button_prop, "Reset Stopwatch" )	
	end	
	obs.obs_property_set_visible( split_button_prop, mode==1 )
  	obs.obs_property_set_visible( split_type_prop, false )
	obs.obs_property_set_visible( split_source_prop, false )
	obs.obs_property_set_visible( timer_trim_prop, config==2 )
	obs.obs_property_set_visible( audio_caution_prop, config==2 )
	obs.obs_property_set_visible( audio_warning_prop, config==2 )
	obs.obs_property_set_visible( caution_duration_prop, config==2 )
	obs.obs_property_set_visible( warning_duration_prop, config==2 )

	obs.obs_property_set_visible( normal_color_prop, config==2 )
	obs.obs_property_set_visible( caution_color_prop, config==2 )
	obs.obs_property_set_visible( warning_color_prop, config==2 )
	obs.obs_property_set_visible( caution_text_prop, config==2 )
	obs.obs_property_set_visible( warning_text_prop, config==2 )
	obs.obs_property_set_visible( start_on_visible_prop, config==2 )
	obs.obs_property_set_visible( disable_script_prop, config==2 )
	if mode == 1 then
  		obs.obs_property_set_visible( split_type_prop, config == 2 )
		obs.obs_property_set_visible( split_source_prop, config == 2 )	
	end	
	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
--[[
----------------------------------------------------------
	A function named script_properties defines the properties that the user
	can change for the entire script module itself
----------------------------------------------------------
]]
function script_properties()
	props = obs.obs_properties_create()	
  	local p_1 = obs.obs_properties_add_list( props, "timer_type", "<b>Timer Type</b>", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT )
  	t_type = {"Stopwatch", "Countdown"}
  	for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_1, v, i ) end
  	local p_2 = obs.obs_properties_add_list( props, "config", "<font color=".. font_dimmed ..">Configuration</font>", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT )
  	t_type = {"Basic", "Advanced"}
  	for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_2, v, i ) end
	--[[
		Returns an array of reference-incremented sources. 
		Release with source_list_release().
	]]	
	local sources = obs.obs_enum_sources()
	local p_3 = obs.obs_properties_add_list( props, "timer_source", "<i>Timer Source</i>", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING )
	obs.obs_property_list_add_string( p_3, "Select", "select" )
	local list = {}
	if sources ~= nil then
		for _, source in ipairs( sources ) do
			source_id = obs.obs_source_get_unversioned_id( source )
			if source_id == "text_gdiplus" or source_id == "text_ft2_source" then
				local name = obs.obs_source_get_name( source )
				if not in_table( {split_source, active_source, media["caution_note_source"], media["warning_note_source"]}, name ) then
					--[[
						add it to list so that it can be reordered
					]]		
					list[name] = name
				else
					--continue 
				end
			end
		end
			obs.bfree(source)
		
		for key, value in pairsByKeys( list ) do
			--[[
				add item to property list
			]]	
			obs.obs_property_list_add_string( p_3, value, value )
		end
	end
	local p_4 = obs.obs_properties_add_list( props, "countdown_type", "<font color=".. font_dimmed ..">Countdown Type</font>", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT )
  	t_type = {"Specific Date & Time", "Hours, Minutes, Seconds"}
  	for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_4, v, i ) end
	local p_5 = obs.obs_properties_add_text( props, "day_text", "<font color=".. font_dimmed ..">Day Text Format</font>", obs.OBS_TEXT_DEFAULT )
	obs.obs_property_set_long_description( p_5, "\nUsed to distinguish between singular and plural days format. Use this for singular.\n" )	
	local p_6 = obs.obs_properties_add_text( props, "days_text", "<font color=".. font_dimmed ..">Days Text Format</font>", obs.OBS_TEXT_DEFAULT )
	obs.obs_property_set_long_description( p_6, "\nUsed to distinguish between singular and plural days format. Use this for plural.\n" )
  	local p_7 = obs.obs_properties_add_list( props, "month", "<font color=".. font_dimmed ..">Month</font>", 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"}
  	for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_7, v, i ) end
  	local p_8 = obs.obs_properties_add_int( props, "year", "<font color=".. font_dimmed ..">Year</font>", 2022, 212021221, 1 )
  	local p_9 = obs.obs_properties_add_int( props, "day", "<font color=".. font_dimmed ..">Day</font>", 1, 31, 1 )
	local p_10 = obs.obs_properties_add_int( props, "hours", "<font color=".. font_dimmed ..">Hours</font>", 0, 23, 1 )
	obs.obs_property_int_set_suffix( p_10, " Hours" )
	local p_11 = obs.obs_properties_add_int( props, "minutes", "<font color=".. font_dimmed ..">Minutes</font>", 0, 59, 1 )
	obs.obs_property_int_set_suffix( p_11, " Minutes" );
	local p_12 = obs.obs_properties_add_int( props, "seconds", "<font color=".. font_dimmed ..">Seconds</font>", 0, 59, 1 )
	obs.obs_property_int_set_suffix( p_12, " Seconds" );
	local p_13 = obs.obs_properties_add_list( props, "timer_trim", "Timer Format", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT )
	t_type = {"Display full format", "Remove leading zeros", "No leading zeros, no split seconds", "No split seconds", "Custom Time Format"}
	for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_13, v, i ) end
	local p_37 = obs.obs_properties_add_text( props, "custom_time_format", "<font color=".. font_dimmed ..">Time Format</font>", obs.OBS_TEXT_DEFAULT )
	obs.obs_property_set_long_description( p_37, "\n Timestamp is represented by $D = day, $H = hour, $M = minute, $S = second, $F = split second.\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" )	
	local p_14 = obs.obs_properties_add_list( props, "split_source", "<i>Split Source</i>", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING )
	obs.obs_property_list_add_string( p_14, "Select", "select" )
	list = {}
	if sources ~= nil then
		for _, source in ipairs( sources ) do
			source_id = obs.obs_source_get_unversioned_id( source )
			if source_id == "text_gdiplus" or source_id == "text_ft2_source" then
				local name = obs.obs_source_get_name( source )
			
				if not in_table( {timer_source, active_source, media["caution_note_source"], media["warning_note_source"]}, name ) then
					--[[
						add it to list so that it can be reordered
					]]		
					list[name] = name
				else
					--continue 
				end
			end
		end
			obs.bfree(source)
		
		for key, value in pairsByKeys(list) do
			--[[
				add item to property list
			]]	
			obs.obs_property_list_add_string( p_14, value, value )
		end
	end	
  	local p_15 = obs.obs_properties_add_list( props, "split_type", "Split Type", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT )
  	t_type = {"Interval", "Mark", "Mark Interval", "Interval Mark"}
  	for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_15, v, i ) end
	obs.obs_property_set_long_description( p_15, "\nInterval = Time between current and previous split.\n\nMark = Time of split\n" )
	local p_16 = obs.obs_properties_add_list( props, "audio_caution", "<font color=".. font_dimmed ..">Caution Audio</font>", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING )
	obs.obs_property_list_add_string( p_16, "None", "none" )
	if sources ~= nil then
		for _, source in ipairs( sources ) do
			source_id = obs.obs_source_get_unversioned_id( source )
			if source_id == "ffmpeg_source" then
				local name = obs.obs_source_get_name( source )
				obs.obs_property_list_add_string( p_16, name, name )
			end
		end
			obs.bfree(source)
	end	
	local p_17 = obs.obs_properties_add_list( props, "audio_warning", "<font color=".. font_dimmed ..">Warning Audio</font>", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING )
	obs.obs_property_list_add_string( p_17, "None", "none" )
	if sources ~= nil then
		for _, source in ipairs( sources ) do
			source_id = obs.obs_source_get_unversioned_id( source )
			if source_id == "ffmpeg_source" then
				local name = obs.obs_source_get_name( source )
				obs.obs_property_list_add_string( p_17, name, name )
			end
		end
			obs.bfree(source)
	end		
	obs.obs_properties_add_color( props, "normal_color", "Normal Color" )
	obs.obs_properties_add_color( props, "caution_color", "Caution Color" )
	obs.obs_properties_add_color( props, "warning_color", "Warning Color" )
	local p_18 = obs.obs_properties_add_text( props, "caution_text", "<font color=".. font_dimmed ..">Caution Time</font>", obs.OBS_TEXT_DEFAULT )
	obs.obs_property_set_long_description( p_18, "\nUse format 00:00:00 ( hoursa:minutes:seconds )\n" )
	local p_i = obs.obs_properties_add_text( props, "warning_text", "<font color=".. font_dimmed ..">Warning Time</font>", obs.OBS_TEXT_DEFAULT )
	obs.obs_property_set_long_description( p_i, "\nUse format 00:00:00 ( hoursa:minutes:seconds )\n" )
	local p_19 = obs.obs_properties_add_list( props, "trigger_text", "<i>Trigger Text</i>", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT )
	 t_type = {"Disabled", "Enabled"}
  	for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_19, v, i ) end
	obs.obs_property_set_long_description( p_19, "\nDisplay a note when the timer trigger warning and caution states.\n" )
	local p_20 = obs.obs_properties_add_list( props, "caution_note_source", "<i>Caution Note Source</i>", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING )
	obs.obs_property_list_add_string( p_20, "Select", "select" )
	list = {}
	if sources ~= nil then
		for _, source in ipairs( sources ) do
			source_id = obs.obs_source_get_unversioned_id( source )
			if source_id == "text_gdiplus" or source_id == "text_ft2_source" then
				local name = obs.obs_source_get_name( source )
				if not in_table( {timer_source, split_source, active_source, media["warning_note_source"]}, name ) then
					--[[
						add it to list so that it can be reordered
					]]		
					list[name] = name
				else
					--continue 
				end
			end
		end
			obs.bfree(source)
		
		for key, value in pairsByKeys( list ) do
			--[[
				add item to property list
			]]	
			obs.obs_property_list_add_string( p_20, value, value )
		end
	end
	local p_21 = obs.obs_properties_add_list( props, "warning_note_source", "<i>Warning Note Source</i>", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING )
	obs.obs_property_list_add_string( p_21, "Select", "select" )
	list = {}
	if sources ~= nil then
		for _, source in ipairs( sources ) do
			source_id = obs.obs_source_get_unversioned_id( source )
			if source_id == "text_gdiplus" or source_id == "text_ft2_source" then
				local name = obs.obs_source_get_name( source )
				if not in_table( {timer_source, split_source, active_source, media["caution_note_source"]}, name ) then
					--[[
						add it to list so that it can be reordered
					]]		
					list[name] = name
				else
					--continue 
				end
			end
		end
			obs.bfree( source )
		for key, value in pairsByKeys( list ) do
			--[[
				add item to property list
			]]	
			obs.obs_property_list_add_string( p_21, value, value )
		end
	end	
	local p_22 = obs.obs_properties_add_text( props, "caution_note", "<font color=".. font_dimmed ..">Caution Note</font>", obs.OBS_TEXT_DEFAULT )
	obs.obs_property_set_long_description( p_22, "\nDisplay a note when the caution trigger.\n" )
	local p_23 = obs.obs_properties_add_text( props, "warning_note", "<font color=".. font_dimmed ..">Warning Note</font>", obs.OBS_TEXT_DEFAULT )
	obs.obs_property_set_long_description( p_23, "\nDisplay a note when the warning trigger.\n" )
	--*props, *name, *description, min, max, step
	obs.obs_properties_add_int_slider( props, "caution_duration", "Caution Duration", 1, 100, 1 )
	obs.obs_properties_add_int_slider( props, "warning_duration", "Warning Duration", 1, 100, 1 )
	local p_24 = obs.obs_properties_add_list( props, "start_recording", "Auto Recording", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT )
	t_type = {"Yes", "No"}
	for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_24, v, i ) end
	-- Combo list filled with the options from _type
  	local p_25 = obs.obs_properties_add_list( props, "recording_type", "Recording", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_INT )
  	t_type = {"Timer Expires", "Caution Time", "Warning Time", "Timer Visible", "Timer Start"}
  	for i,v in ipairs( t_type ) do obs.obs_property_list_add_int( p_25, v, i ) end
	local p_26 = obs.obs_properties_add_list( props, "next_scene", "<i>Next Scene</i>", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING )
	t_type = {"Select", "TIMER END TEXT", "Source List", "Scene List"}
	for i,v in ipairs( t_type ) do obs.obs_property_list_add_string( p_26, v, v ) end
	local scene_source = obs.obs_frontend_get_current_scene()
	local name = obs.obs_source_get_name( scene_source )
	local scenes = obs.obs_frontend_get_scene_names()
	if scenes ~= nil then
		for i, scene in ipairs( scenes ) do
			if name ~= scene then
				obs.obs_property_list_add_string( p_26, scene, scene )
			end
		end
		obs.bfree( scene )
	end
    obs.obs_source_release( scene_source )
	local p_27 = obs.obs_properties_add_text( props, "text_prefix", "<font color=#fefceb>Timer Prefix</font>", obs.OBS_TEXT_DEFAULT )
	obs.obs_property_set_long_description( p_27, "\nDefine text placed before the Timer\n" )
	local p_28 = obs.obs_properties_add_text( props, "text_suffix", "<font color=#fefceb>Timer Suffix</font>", obs.OBS_TEXT_DEFAULT )
	obs.obs_property_set_long_description( p_28, "\nDefine text placed after the Timer\n" )
	local p_29 = obs.obs_properties_add_text( props, "stop_text", "<font color=#fef1eb>Timer End Text</font>", obs.OBS_TEXT_DEFAULT )
	obs.obs_property_set_long_description( p_29, "\nDefine text displayed when timer ended\n" )
   	local p_30 = obs.obs_properties_add_list( props, "active_source", "<i>Active Source</i>", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING )
	obs.obs_property_set_long_description( p_30, "\nSelect a text source, that will be used to show the name for the current active Source or Scene\n" )
	obs.obs_property_list_add_string( p_30, "Select", "select" )
	list = {}
	if sources ~= nil then
		for _, source in ipairs( sources ) do
			source_id = obs.obs_source_get_unversioned_id( source )
			if source_id == "text_gdiplus" or source_id == "text_ft2_source" then
				local name = obs.obs_source_get_name( source )
				if name ~= timer_source then
					--[[
						add it to list so that it can be reordered
					]]		
					list[name] = name
				else
					--continue 
				end
			end
		end
			obs.bfree(source)
		for key, value in pairsByKeys( list ) do
			--[[
				add item to property list
			]]	
			obs.obs_property_list_add_string( p_30, value, value )
		end
	end
	local p_31 = obs.obs_properties_add_editable_list(props, "cycle_list", "Cycle List",obs.OBS_EDITABLE_LIST_TYPE_STRINGS,nil,nil)
	obs.obs_properties_add_button( props, "reset_button", "Reset Stopwatch", reset_button_clicked )
	obs.obs_properties_add_button( props, "pause_button", "Start/Pause Stopwatch", pause_button_clicked )	
	obs.obs_properties_add_button( props, "split_button", "Split Time", split_button_clicked )
    obs.obs_properties_add_bool( props, "start_on_visible", "Start Timer on Source Visible" )
    obs.obs_properties_add_bool( props, "disable_script", "Disable Script" )
	
	local p_32 = obs.obs_properties_add_bool( props, "backup_mode", "Backup Mode" )
	local p_33 = obs.obs_properties_add_path( props, "backup_folder", "Backup Folder", obs.OBS_PATH_DIRECTORY, nil, nil)
	local p_34 = obs.obs_properties_add_list( props, "import_list", "<i>Load Settings</i>", obs.OBS_COMBO_TYPE_LIST, obs.OBS_COMBO_FORMAT_STRING )
	obs.obs_property_list_add_string( p_34, 'Select ', 'select' )
	obs.obs_property_set_long_description( p_34, "\nSelect the Settings file to import.\n" )
	local filenames = get_filenames( path )
	if table.getn( filenames ) > 0 then
  		for i,v in pairs( filenames ) do 
			obs.obs_property_list_add_string( p_34, v, v )
		end
	end
	local p_35 = obs.obs_properties_add_button( props, "export_button", "Export Settings", export_button_clicked )
	local p_36 = obs.obs_properties_add_button( props, "import_button", "Import Settings", import_button_clicked )
	obs.source_list_release( sources )
	--Sets callback upon modification of the list Basically an Event Listener
  	obs.obs_property_set_modified_callback( p_1, property_onchange )
  	obs.obs_property_set_modified_callback( p_2, property_onchange )
  	obs.obs_property_set_modified_callback( p_4, property_onchange )
  	obs.obs_property_set_modified_callback( p_7, property_onchange )
  	obs.obs_property_set_modified_callback( p_8, property_onchange )
  	obs.obs_property_set_modified_callback( p_13, property_onchange )
  	obs.obs_property_set_modified_callback( p_19, property_onchange )
  	obs.obs_property_set_modified_callback( p_24, property_onchange )
  	obs.obs_property_set_modified_callback( p_26, property_onchange )
  	obs.obs_property_set_modified_callback( p_30, property_onchange )
	obs.obs_property_set_modified_callback( p_32, property_onchange )
	obs.obs_property_set_modified_callback( p_33, property_onchange )
	obs.obs_property_set_modified_callback( p_34, property_onchange )
	obs.obs_property_set_modified_callback( p_36, import_properties )
	-- Calls the callback once to set-up current visibility
  	obs.obs_properties_apply_settings( props, script_settings )
	return props
end
--[[
----------------------------------------------------------
	A function named script_update will be called when settings are changed
----------------------------------------------------------
]]
-- Called upon settings initialization and modification
function script_update( settings )
	assign_default_frequency()
	activate( false )
	trigger_text = obs.obs_data_get_int( settings, "trigger_text" )
	media["caution_note_source"] = obs.obs_data_get_string( settings, "caution_note_source" )
	media["warning_note_source"] = obs.obs_data_get_string( settings, "warning_note_source" )
	media["caution_note"] = string.gsub(obs.obs_data_get_string( settings, "caution_note" ), "\\([n])", {n="\n"})
	media["warning_note"] = string.gsub(obs.obs_data_get_string( settings, "warning_note" ), "\\([n])", {n="\n"})
	timer_type = obs.obs_data_get_int( settings, "timer_type" )
	timer_source = obs.obs_data_get_string( settings, "timer_source" )
	countdown_type = obs.obs_data_get_int( settings, "countdown_type" )
	if timer_type == 2 then
		cur_seconds = 
		( obs.obs_data_get_int( settings, "hours" )*60*60 ) + 
		( obs.obs_data_get_int( settings, "minutes" )*60 ) + 
		obs.obs_data_get_int( settings, "seconds" )
	else
		cur_seconds = 0
	end	
	custom_time_format = obs.obs_data_get_string( settings, "custom_time_format" )
	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"})
	timer_year = obs.obs_data_get_int( settings, "year" )
	timer_month = obs.obs_data_get_int( settings, "month" ) - 1
	timer_day = obs.obs_data_get_int( settings, "day" )
	timer_hours = obs.obs_data_get_int( settings, "hours" )
	timer_minutes = obs.obs_data_get_int( settings, "minutes" )
	timer_seconds = obs.obs_data_get_int( settings, "seconds" )
	if timer_type == 2 and countdown_type == 1 then
		cur_seconds = delta_time( timer_year, timer_month, timer_day, timer_hours, timer_minutes, timer_seconds)
	end
	timer_trim = obs.obs_data_get_int( settings, "timer_trim" )
	def_seconds = cur_seconds 
	split_source = obs.obs_data_get_string( settings, "split_source" )
	active_source = obs.obs_data_get_string( settings, "active_source" )
	media['source_name_audio_warning'] = obs.obs_data_get_string( settings, "audio_warning" )
	media['source_name_audio_caution'] = obs.obs_data_get_string( settings, "audio_caution" )											
	media['normal_color'] = obs.obs_data_get_int( settings, "normal_color" )
	media['caution_color'] = obs.obs_data_get_int( settings, "caution_color" )
	media['warning_color'] = obs.obs_data_get_int( settings, "warning_color" )										
	media['warning_text'] = obs.obs_data_get_string( settings, "warning_text" )
	media['caution_text'] = obs.obs_data_get_string( settings, "caution_text" )
	media['caution_duration'] = obs.obs_data_get_int( settings, "caution_duration" )
	media['warning_duration'] = obs.obs_data_get_int( settings, "warning_duration" )
	split_type = obs.obs_data_get_string( settings, "split_type" )
	start_recording = obs.obs_data_get_int( settings, "start_recording" )
	recording_type = obs.obs_data_get_int( settings, "recording_type" )
	next_scene = obs.obs_data_get_string( settings, "next_scene" )
	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"})
	stop_text = obs.obs_data_get_string( settings, "stop_text" )
    start_on_visible = obs.obs_data_get_bool( settings,"start_on_visible" )
	backup_folder = obs.obs_data_get_string( settings, "backup_folder" )
    disable_script = obs.obs_data_get_bool( settings,"disable_script" )
	import_list = obs.obs_data_get_string( settings, "import_list" )
	reset( true )
	-- Keep track of current settings
  	script_settings = settings 
end
--[[
----------------------------------------------------------
A function named script_defaults will be called to set the default settings
----------------------------------------------------------
]]
function script_defaults( settings )
	assign_default_frequency()
	obs.obs_data_set_default_int( settings, "trigger_text", 1 )
	obs.obs_data_set_default_string( settings, "custom_time_format", "$T$D:$H:$M:$S,$F" )
	obs.obs_data_set_default_string( settings, "caution_note", "" )
	obs.obs_data_set_default_string( settings, "warning_note", "" )
	obs.obs_data_set_default_string( settings, "caution_note_source", "Select" )
	obs.obs_data_set_default_string( settings, "warning_note_source", "Select" )
	obs.obs_data_set_default_int( settings, "timer_type", 1 )
	obs.obs_data_set_default_int( settings, "config", 1 )
	obs.obs_data_set_default_string( settings, "timer_source", "Select" )
	obs.obs_data_set_default_int( settings, "countdown_type", 2 )
	obs.obs_data_set_default_int( settings, "year", os.date("%Y", os.time()) )
	obs.obs_data_set_default_int( settings, "hours", 0 )
	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, "timer_trim", 1 )
	obs.obs_data_set_default_string( settings, "day_text", "# Day \n" )
	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, "audio_warning", "None" )
	obs.obs_data_set_default_string( settings, "audio_caution", "None" )
	obs.obs_data_set_default_int( settings, "normal_color", media['normal_color'] )
	obs.obs_data_set_default_int( settings, "caution_color", media['caution_color'] )
	obs.obs_data_set_default_int( settings, "warning_color", media['warning_color'] )
	obs.obs_data_set_default_string( settings, "warning_text", "" )
	obs.obs_data_set_default_string( settings, "caution_text", "" )
	obs.obs_data_set_default_int( settings, "caution_duration", 5 )
	obs.obs_data_set_default_int( settings, "warning_duration", 5 )
	obs.obs_data_set_default_int( settings, "split_type", 2 )
	obs.obs_data_set_default_int( settings, "start_recording", 2 )
	obs.obs_data_set_default_int( settings, "recording_type", 5 )
	obs.obs_data_set_default_string( settings, "next_scene", "Select" )
	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, "stop_text", "" )
	obs.obs_data_set_default_bool( settings, "start_on_visible", false )
	obs.obs_data_set_default_bool( settings, "disable_script", false )
	obs.obs_data_set_default_bool( settings, "backup_mode", false )
	-- Keep track of current settings
  	script_settings = settings 
end
--[[
----------------------------------------------------------

	A function named script_save will be called when the script is saved
	NOTE: This function is usually used for saving extra data ( such as in this
	case, a hotkey's save data ).  Settings set via the properties are saved
	automatically.
----------------------------------------------------------
]]
function script_save( settings )
	local hotkey_save_array_reset = obs.obs_hotkey_save( hotkey_id_reset )
	local hotkey_save_array_pause = obs.obs_hotkey_save( hotkey_id_pause )
	local hotkey_save_array_split = obs.obs_hotkey_save( hotkey_id_split )
	obs.obs_data_set_array( settings, "reset_hotkey", hotkey_save_array_reset )
	obs.obs_data_set_array( settings, "pause_hotkey", hotkey_save_array_pause )
	obs.obs_data_set_array( settings, "split_hotkey", hotkey_save_array_split )
	obs.obs_data_array_release( hotkey_save_array_pause )
	obs.obs_data_array_release( hotkey_save_array_reset )
	obs.obs_data_array_release( hotkey_save_array_split )
end
--[[
----------------------------------------------------------
	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.
----------------------------------------------------------
]]
function script_load( settings )
	assign_default_frequency()
	local sh = obs.obs_get_signal_handler()
	obs.signal_handler_connect( sh, "source_activate", source_activated )
	obs.signal_handler_connect( sh, "source_deactivate", source_deactivated )
	hotkey_id_reset = obs.obs_hotkey_register_frontend( "reset_stopwatch_" .. filename():lower():gsub('[%W%p%c%s]', ''), "Reset " .. filename(), reset )
	hotkey_id_pause = obs.obs_hotkey_register_frontend( "pause_stopwatch_" .. filename():lower():gsub('[%W%p%c%s]', ''), "Start/Pause " .. filename(), on_pause )
	hotkey_id_split = obs.obs_hotkey_register_frontend( "split_stopwatch_" .. filename():lower():gsub('[%W%p%c%s]', ''), "Split Time " .. filename(), on_split )
	local hotkey_save_array_reset = obs.obs_data_get_array( settings, "reset_hotkey" )
	local hotkey_save_array_pause = obs.obs_data_get_array( settings, "pause_hotkey" )
	local hotkey_save_array_split = obs.obs_data_get_array( settings, "split_hotkey" )
	obs.obs_hotkey_load( hotkey_id_reset, hotkey_save_array_reset )
	obs.obs_hotkey_load( hotkey_id_pause, hotkey_save_array_pause )
	obs.obs_hotkey_load( hotkey_id_split, hotkey_save_array_split )
	obs.obs_data_array_release( hotkey_save_array_reset )
	obs.obs_data_array_release( hotkey_save_array_pause )
	obs.obs_data_array_release( hotkey_save_array_split )
end