I often record my D&D sessions with my friends who are all remote via OBS. Open Broadcaster Software (aka OBS) is a: popular, free, and open-source cross-platform streaming and recording program.
I have text that is a timestamp of the day we play D&D on the screen so that whenever we review the footage, we always know from which session it was:
But I didn't want to continue to rewrite the date manually every time.
Sometimes I would forget and the footage would have the incorrect date and it would affect the order of the videos. So I set out to figure out how to write a script in OBS that would set today's date for me.
As a bonus, I wanted to see if I could add a prefix and suffix options to the date as well, just in case I wanted to add additional text before or after the date.
OBS is written in C, C++. But for the purposes of this post is to showcase one of OBS' best features which is that since version 21.0+, anyone can write a
script which can use "hooks" that are available in to effectively do anything.
OBS scripting documentation is located here and there are two important notes. You can write your scripts in either Python 3 or Luajit 2 (Lua 5.2) which hook into the C/C++ methods that run OBS.
Despite knowing Python more than Lua; this small task which has a fast iterative cycle seemed like the best time to learn how Lua works.
Figuring out the OBS hooks for writing plugins:
OBS' main hooks are contained in this document but I focused on the following hooks:
script_load script_update script_defaults script_properties
script_load in the "lifecycle" of OBS, is called whenever a new source is created or source is activated. Sources are a collection of inputs (audio/video/moving/static) that map to a specific scene.
script_update is called whenever the user has modified the script that is in use.
script_defaults is called whenever the script is initialized.
script_properties is called to load all the possible options for the script.
Reverse Engineering a Lua Script
I followed a script that comes preloaded with OBS called
countdown.lua located within
frontend-tools/scripts/ and dissected the following things:
To import the OBS library
obs = obslua
One must set-up global variables
source_name = "" prefix = "" suffix = ""
Lua is functional, everything else is a function from here on out. To hook into
script_properties, one must make it into a function:
function script_properties() ... end
obs functions must be called to register the source that we are modifying and the options that that would modify the source. Looking at
script_properties again, I will showcase the script I wrote for this hook:
obs = obslua source_name = "" prefix = "" suffix = "" function script_properties() local props = obs.obs_properties_create() local p = obs.obs_properties_add_list(props, "source", "Text Source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING) local sources = obs.obs_enum_sources() -- As long as the sources are not empty, then if sources ~= nil then -- iterate over all the sources for _, source in ipairs(sources) do source_id = obs.obs_source_get_id(source) if source_id == "text_gdiplus" or source_id == "text_ft2_source" then local name = obs.obs_source_get_name(source) obs.obs_property_list_add_string(p, name, name) end end end obs.source_list_release(sources) obs.obs_properties_add_text(props, "prefix", "Prefix Text", obs.OBS_TEXT_DEFAULT) obs.obs_properties_add_text(props, "suffix", "Suffix Text", obs.OBS_TEXT_DEFAULT) return props end
obs.obs_properties_create()is used to create an instance of a
propsdata type that maps to an object in C/C++ for OBS.
obs.obs_properties_add_list(props, "source", "Text Source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)creates a dropdown list that has a user-facing
Text Sourcethat maps its values to
The loop that starts here:
local sources = obs.obs_enum_sources() -- As long as the sources are not empty, then if sources ~= nil then -- iterate over all the sources for _, source in ipairs(sources) do
is called by loading all the sources that the user has, making sure at least one exists, and then iterating over them. I only want to target sources for which the source is a Text source only. That's what is happening here:
for _, source in ipairs(sources) do source_id = obs.obs_source_get_id(source) if source_id == "text_gdiplus" or source_id == "text_ft2_source" then local name = obs.obs_source_get_name(source) obs.obs_property_list_add_string(p, name, name) end end
If the source has an internal id of
"text_ft2_source", we grab the name of that source and modify the properties of the list from earlier and add the
name of that source into the dropdown.
Important note, you are still writing in C/C++
Despite the fact that we can write these scripts in Lua or Python, these scripts are linked to the C/C++ internals of OBS and thus follow the same rules of needing to release memory whenever we no longer need the resource.
That means if we do:
local sources = obs.obs_enum_sources(), after we are done with messing with the
sources, we need to release this from memory by doing the following:
Just because we are using higher level languages in this script, that doesn't mean we can forget about memory management.
The last part is needing to add an option for the prefix and suffix and have it available as a free-text field for the user to fill in:
obs.obs_properties_add_text(props, "prefix", "Prefix Text", obs.OBS_TEXT_DEFAULT) obs.obs_properties_add_text(props, "suffix", "Suffix Text", obs.OBS_TEXT_DEFAULT)
Once we are done with setting up the configuration for the script, we need to return the
The other 3 "hooks" for this implementation can be viewed at my Github page where I've made this code available for anyone else to use:
Top comments (0)