Permalink
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
obs-lua/StopWatch.lua
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Fixed an issue when user checked the checkbox for timer in Stopwatch mode setting reset on "source visible" or "scene active" .
7003 lines (5995 sloc)
264 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
---------------------------------------------------------------------------------------------------------------------------------------- | |
Open Broadcaster Software®️ | |
OBS > Tools > Scripts | |
@midnight-studios | |
Stopwatch | |
*************************************************************************************************************************************** | |
Version 4.10 | |
Published / Released: 2023-02-09 10:00 | |
NEW FEATURES | |
- | |
OPTIMIZATION | |
- | |
USER EXPERIENCE & FEATURE ENHANCEMENTS | |
- | |
BUGS | |
- Fixed an issue 'Reset Timer on Source Visible'. | |
- Fixed an issue 'Reset Timer on Scene Active'. | |
*************************************************************************************************************************************** | |
Version 4.9 | |
Published / Released: 2023-02-09 10:00 | |
NEW FEATURES | |
- | |
OPTIMIZATION | |
- | |
USER EXPERIENCE & FEATURE ENHANCEMENTS | |
- | |
BUGS | |
- Fixed an issue that caused the time marker function to break. | |
*************************************************************************************************************************************** | |
Version 4.8 | |
Published / Released: 2023-02-02 10:45 | |
NEW FEATURES | |
- Custom Minute Format that supports minute formats of any length. To use open Scipt settings go to 'Time Format' and set to 'Custom Time Format' and define the time stamp you require. To adjust the minute format you need to add the following syntax: '{M90}' Add 'M' and number enclosed in '{}' to adjust minute format: {M90} will display 90 minutes units. The number value following the 'M' will be assigned to the Minute Format | |
OPTIMIZATION | |
- | |
USER EXPERIENCE & FEATURE ENHANCEMENTS | |
- | |
BUGS | |
- | |
*************************************************************************************************************************************** | |
*************************************************************************************************************************************** | |
Version 4.7 | |
Published / Released: 2022-12.29 18:56 | |
NEW FEATURES | |
- Expanded Add / Subtract seconds to time. | |
- Alow this feature to be hidden or disabled | |
- This feature now allows a limit for updating the timer (Zero = infinite) | |
- A source text note can be defined to notify the user when the limit was reached | |
- User can define how long the note is displayed (Zero = disables the hide feature) | |
OPTIMIZATION | |
- some back end improvements or changes | |
USER EXPERIENCE & FEATURE ENHANCEMENTS | |
- Some improvements with conflict prevention for text source selection | |
- Renamed 'Autoload last time stamp on OBS start' | |
BUGS | |
- Fixed some bugs introduced during the previous release | |
- Fixed an issue that caused timer to crash when OBS shuts down if the script was duplicated (used multiple times) [@Xagika] | |
- Fixed a bug that would reset the timer if user enables 'auto load end timsestamp' | |
*************************************************************************************************************************************** | |
Version 4.6 | |
Published / Released: 2022-12.20 12:48 | |
NEW FEATURES | |
- Allow Timer reset if scene becomes activated | |
OPTIMIZATION | |
- | |
USER EXPERIENCE & FEATURE ENHANCEMENTS | |
- Start on Scene active improvements | |
BUGS | |
- Fixed an issue that caused the stinger transition to break when the timer is in countdown mode. | |
*************************************************************************************************************************************** | |
Version 4.5 | |
Published / Released: 2022-12.03 01:56 | |
NEW FEATURES | |
- Add / Subtract seconds to time (3 sets available, configurable up to 72 hours or 259200 seconds) | |
OPTIMIZATION | |
- | |
USER EXPERIENCE & FEATURE ENHANCEMENTS | |
- | |
BUGS | |
- | |
*************************************************************************************************************************************** | |
Version 4.4 | |
Published / Released: 2022-11.04 19:25 | |
NEW FEATURES | |
- Change counter direction | |
- Prevent Negative Time Value (Opt-In) | |
- Enable / Disable new feature: Change counter direction (Opt-In) | |
- Hotkey and Button to change counter direction (Supported in Stopwatch and Countdown mode) | |
OPTIMIZATION | |
- | |
USER EXPERIENCE & FEATURE ENHANCEMENTS | |
- | |
BUGS | |
- | |
*************************************************************************************************************************************** | |
Version 4.3 | |
Published / Released: 2022-10.20 11:51 | |
NEW FEATURES | |
- | |
OPTIMIZATION | |
- | |
USER EXPERIENCE & FEATURE ENHANCEMENTS | |
- | |
BUGS | |
- Fixed the expired timestamp for 'custom time format' | |
*************************************************************************************************************************************** | |
Version 4.2 | |
Published / Released: 2022-10.20 21:23 | |
NEW FEATURES | |
- Add Media Playback for Timer end | |
OPTIMIZATION | |
- | |
USER EXPERIENCE & FEATURE ENHANCEMENTS | |
- Changed Property Setting label 'Trigger Text' to 'Marker Notes' | |
BUGS | |
- Fixed an infinate timer callback loop | |
- Reverted and applied new Fix for timer Reset issue | |
- Fixed an issue for recording call when recording is already active | |
- Fixed switching "split type" does not reset the timer display | |
- Fixed changing "Mark Time" does not reset the timer display | |
- Fixed timer display color reset after Mark A & B conditions were met | |
- Fixed stopping Media playback if media is set to loop when the timer expires | |
- Fixed a routine issue on sources loaded | |
- Fixed a timer reset issue (for activate timer on visible) | |
- Fixed a load 'last saved time' issue (for activate timer on visible) | |
- Fixed a 'set stopwatch' issue (for activate timer on visible) | |
*************************************************************************************************************************************** | |
Version 4.1 | |
Published / Released: 2022-10.06 15:23 | |
NEW FEATURES | |
- | |
OPTIMIZATION | |
- | |
USER EXPERIENCE & FEATURE ENHANCEMENTS | |
- | |
BUGS | |
- Fixed timer Reset issue | |
*************************************************************************************************************************************** | |
Version 4.0 | |
Published / Released: 2022-10.06 12:26 | |
NEW FEATURES | |
- | |
OPTIMIZATION | |
- Backend updates | |
USER EXPERIENCE & FEATURE ENHANCEMENTS | |
- | |
BUGS | |
- Fixed a signal handler crash due to an unloaded source | |
- Fixed a bug that triggered the Media when the timer was not active | |
*************************************************************************************************************************************** | |
Version 3.9 | |
Published / Released: 2022-10.10 15:15 | |
NEW FEATURES | |
- Added button Show / Hide Milliseconds | |
- Added Time Stamp trigger for Show / Hide Milliseconds | |
- Added Timer text output options (Still not convinvinced this makes sense) | |
- Added Hotkey for Show / Hide Milliseconds | |
- Pause Media if timer is paused | |
- Added option to force visibility of the "Active Source" | |
OPTIMIZATION | |
- OBS 28 Supported | |
- Rebuild timer text options | |
- Prevented unnecessary Callback Events | |
- Localized functions to reduce Globals clutter | |
- Renamed some functions to something more sensible | |
- Added and updated developer code comments | |
- Realigned function stacking order | |
- Renamed Hotkey references | |
USER EXPERIENCE & FEATURE ENHANCEMENTS | |
- Added tooltip for Next Scene list | |
- Added tooltip for Recording list | |
- Items listed in Cycle Sources list will ignore incorrectly referenced sources. | |
- User can now define the direction of Cycle Sources | |
- Cycle Sources (and toggle source visibility) with timer is now fully automated without third party plugins/scripts | |
- Cycle Sources now support Nested Scenes | |
- Added new Toggles for settings properties to hide or show certain settings | |
BUGS | |
- Fixed a Stack Overflow Crash produced by the Next Scene process. | |
- Fixed an issue that caused a timer feature to break. | |
- Fixed a critical memory leak for media sources that caused OBS to crash | |
- Fixed Media Playback time limit | |
- Fixed Split time outputs for Stopwatch | |
---------------------------------------------------------------------------------------------------------------------------------------- | |
]] | |
--Globals | |
obs = obslua; | |
gversion = "4.10"; | |
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 = | |
[[ | |
<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> | |
<center><a href="https://obsproject.com/forum/resources/]] .. obsurl ..[[updates">]] .. patch_notes ..[[</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/> | |
]]; | |
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 = ""; | |
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; | |
current_count_direction = 1; | |
timer_cycle = 10; --milliseconds | |
split_itm = {}; | |
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; | |
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, | |
current_seconds_marker_a = 0, | |
current_seconds_marker_b = 0, | |
duration_marker_a = 0, | |
duration_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() | |
return string.format( desc, tostring( gversion ) ); | |
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 | |
local str = "{ "; | |
for key, value in pairs( input ) do | |
if type( key ) ~= "number" then key = "'" .. key .. "'" end; | |
str = str .. "[" .. key .. "] = " .. pre_dump( value ) .. ","; | |
end; | |
return str .. "} "; | |
else | |
return tostring( input ); | |
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: 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, 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: 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 ) | |
if type( tbl ) ~= "table" then return tbl end; -- if the input table is not of type table return input | |
local temp_tbl = {}; -- build temporary table of the keys | |
for items in pairs( tbl ) do table.insert( temp_tbl, items ) end; | |
table.sort( temp_tbl, input_function ); -- sort using supplied function, if any | |
local i = 0 -- iterator variable | |
local iter = function () -- iterator function | |
i = i + 1; | |
if temp_tbl[i] == nil then return nil; | |
else return temp_tbl[i], tbl[temp_tbl[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 ) | |
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 ) | |
if type( tbl ) ~= "table" 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 ) | |
if type( tbl ) ~= "table" then return false end; -- if the input table is not of type table return bool(false) | |
local found = false; -- set result default bool (not found) | |
for key, value in pairs( tbl ) do | |
if value == input_value then -- compare search value against table value | |
found = true; -- found, update result bool | |
break; -- found, end and exit here | |
end; | |
end; | |
return found; -- return bool | |
end | |
--[[ | |
---------------------------------------------------------- | |
Description: | |
Credit: | |
Modified: | |
function: | |
type: | |
input type: | |
returns: | |
---------------------------------------------------------- | |
]] | |
function refresh_properties() | |
return true; | |
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 | |
returns: table | |
---------------------------------------------------------- | |
]] | |
local function remove_duplicates( tbl ) | |
if type( tbl ) ~= "table" then return table end; -- if the input table is not of type table return input | |
local hash = {}; | |
local clean_tbl = {}; | |
for _, value in pairsByKeys( tbl ) do | |
if ( not hash[value] ) then | |
clean_tbl[#clean_tbl+1] = value; -- you could print here instead of saving to result table if you wanted | |
hash[value] = true; | |
end; | |
end; | |
return clean_tbl; -- return final result | |
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 ) | |
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; | |
list[index] = 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; | |
list[index] = 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 type( list[key] ) == "table" then | |
if tableHasKey( list[key], return_ref ) then | |
found = true; | |
tmp_list[list[key]["source_name"]] = list[key][return_ref]; | |
end; | |
end; | |
end; | |
end; | |
if found then source_list = tmp_list end; | |
return source_list; | |
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 ) | |
local array = obs.obs_data_get_array( set, item ); | |
local count = obs.obs_data_array_count( array ); | |
local list = {}; | |
for i = 0, count do | |
local array_item = obs.obs_data_array_item( array, i ); | |
local value = obs.obs_data_get_string( array_item, "value" ); | |
list[i] = value; | |
end; | |
obs.obs_data_array_release( array ); | |
return list; | |
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: 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 ) | |
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 ) | |
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() | |
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 | |
--[[ | |
---------------------------------------------------------------------------------------------------------------------------------------- | |
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 ) | |
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; | |
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 ) | |
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; | |
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() | |
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 ) | |
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 | |
--[[ | |
---------------------------------------------------------------------------------------------------------------------------------------- | |
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( ) | |
if not record_timer_set then return end; | |
if not obs.obs_frontend_recording_active() then | |
obs.obs_frontend_recording_start(); | |
end; | |
obs.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 ) | |
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 | |
obs.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 ) | |
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( 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: | |
Credit: | |
Modified: | |
function: | |
type: | |
input type: | |
returns: | |
---------------------------------------------------------------------------------------------------------------------------------------- | |
]] | |
local function long_time( 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 ) | |
-- 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) | |
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: 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 ) | |
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 ) | |
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 | |
Credit: midnight-studios, et al | |
Modified: | |
function: Update Text Source | |
type: Support, Render | |
input type: | |
returns: bool | |
---------------------------------------------------------------------------------------------------------------------------------------- | |
]] | |
local function set_visible( source_name, visible ) | |
if visible == nil then visible = true end; | |
local action_completed = false; | |
if in_table( {"","None", "Select","none", "select"}, 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 | |
--[[ | |
---------------------------------------------------------- | |
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 ) | |
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, 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 | |
return isvisible; | |
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( ) | |
set_visible( media["source_name_audio_marker_a"], false ); | |
obs.remove_current_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( ) | |
set_visible( media["source_name_audio_marker_b"], false ); | |
obs.remove_current_callback(); | |
end | |
--[[ | |
---------------------------------------------------------------------------------------------------------------------------------------- | |
Description: | |
Credit: | |
Modified: | |
function: | |
type: | |
input type: | |
returns: | |
---------------------------------------------------------------------------------------------------------------------------------------- | |
]] | |
local 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 Source Visibility to Hidden | |
]] | |
set_visible( name, false ); | |
obs.remove_current_callback(); | |
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 ) | |
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 ) | |
if in_table( {"","None", "Select","none", "select"}, source_name ) then return end; | |
if not media["activated_".. 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_".. ref] = true; | |
end | |
end | |
--[[ | |
---------------------------------------------------------------------------------------------------------------------------------------- | |
Description: | |
Credit: | |
Modified: | |
function: | |
type: | |
input type: | |
returns: | |
---------------------------------------------------------------------------------------------------------------------------------------- | |
]] | |
local function start_media( 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 (timer text source) | |
type: Support, Render | |
input type: Integer | |
returns: none | |
---------------------------------------------------------------------------------------------------------------------------------------- | |
]] | |
local function set_text_timer_color( 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: 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 ) | |
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: 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 ) | |
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 = 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 timer_mode ~= 2 and not direction_changed or ( direction_changed and current_count_direction == 2 ) then | |
round_seconds = math.floor( current_seconds ); -- round to nearset lower value | |
end | |
if raw_time( round_seconds, true ) == media["text_".. ref] then -- compare current time with marker | |
--[[ | |
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 | |
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_text_timer_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" | |
]] | |
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 ) | |
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 ) | |
local source_name = media["source_name_audio_".. ref]; -- assign local variable | |
if in_table( {nil, "","None", "Select","none", "select"}, 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 | |
end; | |
obs.obs_source_release( source ); -- release source from the reference counter | |
end | |
--[[ | |
---------------------------------------------------------------------------------------------------------------------------------------- | |
Description: Stop Media Playback | |
Credit: OBS, midnight-studios | |
Modified: | |
function: | |
type: | |
input type: | |
returns: none | |
---------------------------------------------------------------------------------------------------------------------------------------- | |
]] | |
local function stop_media_playback( source_name ) | |
if in_table( {"","None", "Select","none", "select"}, 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 ) | |
if in_table( {"","None", "Select","none", "select"}, 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 ) | |
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 ) | |
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 ) | |
--[[ | |
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" | |
--sources = remove_duplicates( sources ) | |
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; | |
list[i] = 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( 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 | |
--[[ | |
---------------------------------------------------------------------------------------------------------------------------------------- | |
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 ) | |
--[[ | |
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 ) | |
--[[ | |
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 ) | |
--[[ | |
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 ) | |
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( ) | |
--[[ | |
]] | |
local t = 0; | |
--[[ | |
]] | |
if direction_changed then -- normal function suspended | |
if current_count_direction == 1 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 ) | |
--[[ | |
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_marker_a"] and not media["activated_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() | |
--[[ | |
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 ) | |
--[[ | |
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 == 1 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 | |
]] | |
if current_seconds <= 0.01 and ( timer_mode ~= 1 or ( direction_changed and current_count_direction == 1 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 current_seconds == 0 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() | |
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() | |
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 ) | |
if in_table( {"","None", "Select","none", "select"}, 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 ) | |
--[[ | |
if set_to_default == true | |
and timer_mode == 2 (Countdown) | |
]] | |