-- Requires OBS v27.2.x and up
----------------------------------------------------------
-- I didn't found the way to free the variable allocated by the obs_frontend_get_current_record_output_path(),
-- and thus can't get 0 counter for memory leaks in OBS.
----------------------------------------------------------
local obs = obslua
local rec_counter = 0
local filename_old = ""
local filename_formatted = ""
local filename_new = ""
local attempts = 0
local attempts_max = 5
local time_between_attempts_ms = 1000
-- Recordings path = "C:/Temp/"
local rec_path
local source_name = ""
-- Full list of the available hotkeys (OBS v29.1.2)
-- https://github.com/obsproject/obs-studio/blob/cb391a595d45aea0d710680c143eb90efe22998b/libobs/obs-hotkeys.h
-- Hotkey on Media Source update
local end_key = "OBS_KEY_NUM7"
local end_key_modifires = {control=true} ---- {shift=true, alt=true, control=true, command=true}
----------------------------------------------------------
-- This function triggers OBS hotkey event
function send_hotkey(hotkey_id_name, key_modifiers)
shift_mod = key_modifiers.shift or false
control_mod = key_modifiers.control or false
alt_mod = key_modifiers.alt or false
command_mod = key_modifiers.command or false
modifiers = 0
if shift_mod then
modifiers = bit.bor(modifiers, obs.INTERACT_SHIFT_KEY )
end
if control_mod then
modifiers = bit.bor(modifiers, obs.INTERACT_CONTROL_KEY )
end
if alt_mod then
modifiers = bit.bor(modifiers, obs.INTERACT_ALT_KEY )
end
if command_mod then
modifiers = bit.bor(modifiers, obs.INTERACT_COMMAND_KEY )
end
local combo = obs.obs_key_combination()
combo.modifiers = modifiers
combo.key = obs.obs_key_from_name(hotkey_id_name)
obs.obs_hotkey_inject_event(combo, false)
obs.obs_hotkey_inject_event(combo, true)
obs.obs_hotkey_inject_event(combo, false)
end
----------------------------------------------------------
-- Update Media Source with new media file
function file_to_media_source(path)
local source = obs.obs_get_source_by_name(source_name)
if source ~= nil then
local settings = obs.obs_data_create()
source_id = obs.obs_source_get_id(source)
if source_id == "ffmpeg_source" then
obs.obs_data_set_string(settings, "local_file", path)
obs.obs_data_set_bool(settings, "is_local_file", true)
-- Updating will automatically cause the source to
-- refresh if the source is currently active
obs.obs_source_update(source, settings)
elseif source_id == "vlc_source" then
-- "playlist"
local array = obs.obs_data_array_create()
local item = obs.obs_data_create()
obs.obs_data_set_string(item, "value", path)
obs.obs_data_array_push_back(array, item)
obs.obs_data_set_array(settings, "playlist", array)
-- Updating will automatically cause the source to
-- refresh if the source is currently active
obs.obs_source_update(source, settings)
obs.obs_data_release(item)
obs.obs_data_array_release(array)
end
obs.obs_data_release(settings)
obs.obs_source_release(source)
-- Send hotkey to OBS
send_hotkey(end_key, end_key_modifires)
end
end
----------------------------------------------------------
-- Timed callback
function recording_rename()
local oldname = rec_path .. filename_old
local newname = rec_path .. filename_new
print("From: " .. oldname)
print(" to: " .. newname)
-- !!! Unicode symbols in the file name not supported in Windows !!! Test oldname = "C:/τέστ.txt"
local res = os.rename(oldname, newname)
if res == nil then
print("renaming FAILS !!!")
attempts = attempts + 1
else
-- All is OK, try no more
attempts = attempts_max
-- Update Media Source with recently renamed file
file_to_media_source(newname)
end
if attempts >= attempts_max then
obs.remove_current_callback()
end
end
function generate_new_name()
filename_new = string.format(filename_formatted, rec_counter)
end
----------------------------------------------------------
function show_new_name_button_clicked(props, p)
generate_new_name()
p = obs.obs_properties_get(props, "renamed_info")
obs.obs_property_set_description(p, filename_new)
return true
end
-- A function named script_update will be called when settings are changed
function script_update(settings)
rec_counter = obs.obs_data_get_int(settings, "rec_counter")
filename_old = obs.obs_data_get_string(settings, "filename_old")
filename_formatted = obs.obs_data_get_string(settings, "filename_formatted")
source_name = obs.obs_data_get_string(settings, "source")
end
-- A function named script_defaults will be called to set the default settings,
-- if stored values not found this will be used instead.
function script_defaults(settings)
obs.obs_data_set_default_string(settings, "filename_old", "rec.mkv")
obs.obs_data_set_default_string(settings, "filename_formatted", "rec_%04d.mkv")
obs.obs_data_set_default_int(settings, "rec_counter", 0)
end
-- A function named script_properties defines the properties that the user
-- can change for the entire script module itself
function script_properties()
local props = obs.obs_properties_create()
obs.obs_properties_add_text(props, "filename_old", "Original file name", obs.OBS_TEXT_DEFAULT)
obs.obs_properties_add_text(props, "filename_formatted", "New (formatted name)", obs.OBS_TEXT_DEFAULT)
obs.obs_properties_add_int(props, "rec_counter", "Last number", 0, 1000000, 1)
obs.obs_properties_add_button(props, "check_button", "Check new name", show_new_name_button_clicked)
obs.obs_properties_add_text(props, "renamed_info", " ", obs.OBS_TEXT_INFO)
-- Media Source selection, later in this source last renamed recording file will be added
local p = obs.obs_properties_add_list(props, "source", "Media Source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)
local sources = obs.obs_enum_sources()
if sources ~= nil then
for _, source in ipairs(sources) do
source_id = obs.obs_source_get_id(source)
if source_id == "ffmpeg_source" then
local name = obs.obs_source_get_name(source)
obs.obs_property_list_add_string(p, name, name)
elseif source_id == "vlc_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)
return props
end
function on_event(event)
if event == obs.OBS_FRONTEND_EVENT_RECORDING_STARTED then
rec_counter = rec_counter + 1
generate_new_name()
attempts = 0
-- By calling obs_frontend_get_current_record_output_path() OBS allocates memory that I was unable to free from the script.
-- I have not found %newobject in SWIG or other ways to prevent char* conversion to sting here.
-- Each memory leakage is small - just few bytes (depends on length of the returned path),
-- and convenience of using the function instead of typing the paths manually was a decisive factor.
rec_path = obs.obs_frontend_get_current_record_output_path()
-- Add "/" to the end if not present
if rec_path:sub(#rec_path, #rec_path) ~= "/" then
rec_path = rec_path .. "/"
end
end
if event == obs.OBS_FRONTEND_EVENT_RECORDING_STOPPED then
obs.timer_add(recording_rename, time_between_attempts_ms)
end
end
function script_description()
local ctrl_end = end_key_modifires.control or false
local alt_end = end_key_modifires.alt or false
local shift_end = end_key_modifires.shift or false
local command_end = end_key_modifires.command or false
local modifiers_end = ""
if ctrl_end then
modifiers_end = modifiers_end .. "CTRL + "
end
if alt_end then
modifiers_end = modifiers_end .. "ALT + "
end
if shift_end then
modifiers_end = modifiers_end .. "SHIFT + "
end
if command_end then
modifiers_end = modifiers_end .. "COMMAND + "
end
return "Renames recording file to the numbered name on record stop.\n\nIn the end fires hotkey " .. modifiers_end .. end_key ..
"\nTo get recent number of recordings and update UI click 'Check new name' button or reload the script."
end
-- A function named script_save will be called when the script is saved.
-- Settings set by user via the properties UI will be saved automatically.
function script_save(settings)
obs.obs_data_set_int(settings, "rec_counter", rec_counter)
-- Other:
-- filename_old
-- filename_formatted
-- source
-- will be modified by user, thus saved by itself on modification
end
function script_load(settings)
obs.obs_frontend_add_event_callback(on_event)
end