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
Specific 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 aprops
data type that maps to an object in C/C++ for OBS.Passing
props
intoobs.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-facingText Source
that maps its values to"source"
.
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_gdiplus"
or "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:
obs.source_list_release(sources)
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 props
:
return props
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)