obs = obslua my_settings = nil Input_Table = {} Output_String = "" max_fields = 100 -- Safety catch. Input loop will stop after attempting to read this many lines. If more text objects are used, increase only as necessary. activeId = 0 activated = false oops_count = 0 stop_me = false -- Causes text to be updated during OBS startup. function source_activated(cd) obs.remove_current_callback() if Update_Table() then Update_Text() end obs.script_log(obs.LOG_INFO, string.format("Initialization completed (1)")) obs.timer_add(startup_safety_callback, 250) end -- Occassionally, problems occur during startup routine. The following forces a second attempt to load and set data. function startup_safety_callback() obs.timer_remove(startup_safety_callback) Input_Table = {} if Update_Table() then Update_Text() end obs.script_log(obs.LOG_INFO, string.format("Initialization completed (2)")) end local function checkFile(id) -- if the script has reloaded then stop any old timers if id < activeId then obs.remove_current_callback() return end if obs.obs_data_get_bool(my_settings, "debug") then obs.script_log(obs.LOG_INFO, string.format("(%d) Checking text file...(%d)", id, obs.obs_data_get_int(my_settings, "interval"))) end if Update_Table() then Update_Text() end end -- Loads file into a data table. Returns true if any updated (or new) values have been added to the text file. function Update_Table() local changed = false -- Check for and open existing file. local filename = obs.obs_data_get_string(my_settings, "load_Filename") local f = io.open(filename, "r") if f == nil then print("Error: File does not exist or could not open file for reading.") return false end -- Convert file contents to paired list local content = f:read("*a") io.close(f) local name, value local count = 0 while string.len(content) > 0 do count = count + 1 name = "" value = "" local findTB = string.find(content, "\t") local findNL = string.find(content, "\n") if not findNL then findNL = string.len(content) end -- If no tab character on line, skip the line if not findTB or findTB > findNL then content = string.sub(content,findNL+1) -- Found a tab, get the value else name = string.sub(content,0,findTB-1) value = string.sub(content,findTB+1,findNL-1) content = string.sub(content,findNL+1) if string.len(name) > 0 then -- Data doesn't exist yet if not Input_Table[name] then Input_Table[name] = value changed = true -- Data changed elseif Input_Table[name] ~= value then Input_Table[name] = value changed = true end end end -- Warning: Script does not check for data fields REMOVED from the text file while running. -- If data is removed, the field will continue to be set back to the original value if changed. -- Error Catch: If file read goes haywire, stop at N-th loop. if count > max_fields then content = "" end end count = 0 return changed end -- Inserts the values loaded from the text file into OBS text fields. function Update_Text() for name, value in pairs(Input_Table) do local source = obs.obs_get_source_by_name(name) if not source then -- Catch event where no OBS field has the specified name print("Error: Could not find source: " .. name) else -- Update the text value of the OBS field matching the specified name. -- Warning: This could go wrong if the name matches up with something that does not have a text value. local settings = obs.obs_data_create() obs.obs_data_set_string(settings, "text", value) obs.obs_source_update(source, settings) obs.obs_data_release(settings) obs.obs_source_release(source) end end end -- Creates an input file based on the current OBS text fields. If file already exists, nothing is saved. function Generate_File() -- Generate output string Output_String = "" local sources = obs.obs_enum_sources() if sources ~= nil then for _, source in ipairs(sources) do source_id = obs.obs_source_get_unversioned_id(source) if source_id == "text_gdiplus" or source_id == "text_ft2_source" then local name = obs.obs_source_get_name(source) local settings = obs.obs_source_get_settings(source) local text = obs.obs_data_get_string(settings, "text") obs.obs_data_release(settings) Output_String = Output_String .. name .. "\t" .. text .. "\n" end end end obs.source_list_release(sources) -- Check for existing file before overwriting. local filename = obs.obs_data_get_string(my_settings, "load_Filename") local f = io.open(filename, "r") if f ~= nil then io.close(f) print("Error: File already exists! Overwriting is not supported.") return end -- Open and output to a new file f = assert(io.open(filename, "w")) f:write(Output_String) f:close() end -- Force the file to reload and update immediately. Table previously loaded is dumped, and table is completely reloaded. function update_button_clicked(props, p) -- force reload of entire table. only applies with manual update button. Input_Table = {} if Update_Table() then Update_Text() end return false end function generate_button_clicked(props, p) Generate_File() return false end ------------------------------------------------------------------------------------------------- function script_properties() local props = obs.obs_properties_create() -- Data Filename obs.obs_properties_add_text(props, "load_Filename", "Data Filename", obs.OBS_TEXT_DEFAULT) -- File Monitor local m = obs.obs_properties_add_bool(props, "file_monitor", "Monitor File for Changes") obs.obs_property_set_modified_callback(m, trigger_mode_changed) obs.obs_properties_add_int(props, "interval", "Interval (ms)", 1000, 30000, 500) -- Update Now Button obs.obs_properties_add_button(props, "update_button", "Update Now", update_button_clicked) -- Generate File Button obs.obs_properties_add_button(props, "generate_button", "Generate File", generate_button_clicked) -- Debug obs.obs_properties_add_bool(props, "debug", "Debug") obslua.obs_properties_apply_settings(props, my_settings) return props end function trigger_mode_changed(props, property, settings) local monitor = obs.obs_data_get_bool(settings, "file_monitor") -- Set visibility for interval property obs.obs_property_set_visible(obslua.obs_properties_get(props, "interval"), monitor) -- Stop file monitor timer if not monitor then activeId = activeId + 1 end return true end function script_description() return [[

Text Loader

Information will be loaded from a text file and placed into OBS text objects matching names found in the file.

Updates will be applied as follows:

Information is stored in a tab-delimited file. The text before the first tab should match a text object name in OBS. Text after the tab and up to the end of that line, will be placed into the text object. For a sample file format, choose a filename for a new file and click the 'Generate File' button.


]] end function script_update(settings) my_settings = settings if not obs.obs_data_get_string(settings, "load_Filename") then obs.script_log(obs.LOG_INFO, string.format("Text monitor stopped")) return nil end if not obs.obs_data_get_bool(settings, "file_monitor") then obs.script_log(obs.LOG_INFO, string.format("Text monitor stopped")) return nil end activeId = activeId + 1 local id = activeId obs.timer_add(function() checkFile(id) end, obs.obs_data_get_int(settings, "interval")) obs.script_log(obs.LOG_INFO, string.format("Text monitor started")) end function script_defaults(settings) obs.obs_data_set_default_string(settings, "load_Filename", "") obs.obs_data_set_default_bool(settings, "file_monitor", false) obs.obs_data_set_default_int(settings, "interval", 10000) obs.obs_data_set_default_bool(settings, "debug", false) end function script_load(settings) local sh = obs.obs_get_signal_handler() obs.signal_handler_connect(sh, "source_activate", source_activated) end