Automatically rename the latest Recording / Replay Buffer in OBS to a consistent pattern based on the scene name and timestamp, keeping captures organized by context and time.
<Scene Name> YYYY-MM-DD HH-MM-SS.ext
<Scene Name>-Replay YYYY-MM-DD HH-MM-SS.ext
local active_scene_name = "Scene"
local rec_started_time = nil
local pending_rec_rename = false
local pending_replay_rename = false
local last_replay_path = nil
local last_replay_time = nil
Tracks scene-at-start and timing for recordings; once flags to avoid duplicate renames; last replay path/time captured at save.
sanitize_name(name)
local function sanitize_name(name)
name = name or "Scene"
name = name:gsub("[\\/:*?\"<>|]", "_")
name = name:gsub("%c", "_")
name = name:match("^%s*(.-)%s*$") or name
if name == "" then name = "Scene" end
return name
end
Makes a filesystem-safe scene string: removes illegal/control chars, trims whitespace, and falls back to Scene
.
get_current_scene_name()
local function get_current_scene_name()
local src = obs.obs_frontend_get_current_scene()
if src ~= nil then
local n = obs.obs_source_get_name(src)
obs.obs_source_release(src)
return n or "Scene"
end
return "Scene"
end
Retrieves the current scene name from OBS, releasing the handle afterward.
build_name(scene, tstamp, ext, is_replay)
local function build_name(scene, tstamp, ext, is_replay)
local dt = os.date("%Y-%m-%d %H-%M-%S", tstamp or os.time())
local s = sanitize_name(scene)
local core = is_replay and string.format("%s-Replay %s", s, dt) or string.format("%s %s", s, dt)
if ext ~= nil and ext ~= "" then
return string.format("%s.%s", core, ext)
else
return core
end
end
Builds the final filename string; adds -Replay
for replay saves and preserves file extension.
unique_path(path)
local function unique_path(path)
if not file_exists(path) then return path end
local dir, file = path:match("^(.*[\\/])(.-)$")
dir = dir or ""
file = file or path
local base, ext = file:match("^(.*)%.([^%.]*)$")
if not base then base, ext = file, "" end
local n = 2
while true do
local candidate
if ext ~= "" then
candidate = string.format("%s%s (%d).%s", dir, base, n, ext)
else
candidate = string.format("%s%s (%d)", dir, base, n)
end
if not file_exists(candidate) then return candidate end
n = n + 1
end
end
Prevents overwrites by appending (2)
, (3)
, … until an unused name is found.
do_rename_recording_once()
local function do_rename_recording_once()
obs.timer_remove(do_rename_recording_once)
if not pending_rec_rename then return end
pending_rec_rename = false
local last = obs.obs_frontend_get_last_recording()
if not last or last == "" then
obs.script_log(obs.LOG_WARNING, "[scene_in_filename.lua] No last recording path.]
return
end
local dir, _, ext = split_path(last)
local new_name = build_name(active_scene_name, rec_started_time, ext, false)
local new_full = unique_path(dir .. new_name)
if not file_exists(last) then
obs.script_log(obs.LOG_INFO, "[scene_in_filename.lua] Recording already renamed or missing.")
return
end
local ok, err = os.rename(last, new_full)
if ok then
obs.script_log(obs.LOG_INFO, "[scene_in_filename.lua] Renamed recording to: " .. new_full)
else
obs.script_log(obs.LOG_ERROR, "[scene_in_filename.lua] Recording rename failed: " .. tostring(err))
end
end
One rename after recording stops: uses the scene/time captured at start.
do_rename_replay_once()
local function do_rename_replay_once()
obs.timer_remove(do_rename_replay_once)
if not pending_replay_rename then return end
pending_replay_rename = false
local last = last_replay_path or obs.obs_frontend_get_last_replay()
if not last or last == "" then
obs.script_log(obs.LOG_WARNING, "[scene_in_filename.lua] No last replay path.")
return
end
local dir, _, ext = split_path(last)
local scene_at_save = get_current_scene_name() -- use scene at save time
local new_name = build_name(scene_at_save, last_replay_time or os.time(), ext, true)
local new_full = unique_path(dir .. new_name)
if not file_exists(last) then
obs.script_log(obs.LOG_INFO, "[scene_in_filename.lua] Replay already renamed or missing.")
return
end
local ok, err = os.rename(last, new_full)
if ok then
obs.script_log(obs.LOG_INFO, "[scene_in_filename.lua] Renamed replay to: " .. new_full)
else
obs.script_log(obs.LOG_ERROR, "[scene_in_filename.lua] Replay rename failed: " .. tostring(err))
end
end
One rename per replay save: uses the scene active at save time and adds -Replay
.
on_event(event)
local function on_event(event)
if event == obs.OBS_FRONTEND_EVENT_RECORDING_STARTING
or event == obs.OBS_FRONTEND_EVENT_RECORDING_STARTED then
active_scene_name = get_current_scene_name()
rec_started_time = os.time()
elseif event == obs.OBS_FRONTEND_EVENT_RECORDING_STOPPED then
pending_rec_rename = true
obs.timer_add(do_rename_recording_once, 250)
elseif event == obs.OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED then
-- Grab the path/time and schedule the rename once (each save triggers once)
last_replay_path = obs.obs_frontend_get_last_replay()
last_replay_time = os.time()
pending_replay_rename = true
obs.timer_add(do_rename_replay_once, 200)
end
end
Wires OBS frontend events to capture context and schedule safe, single-run rename operations.
function script_description()
return [[
Renames both Recordings and Replay Buffer saves to:
YYYY-MM-DD HH-MM-SS.ext
- Recording uses the scene at start time.
- Replay uses the scene at save time.
]]
end
function script_load(s)
obs.obs_frontend_add_event_callback(on_event)
end
Shows the description in the Scripts UI and registers the event callback on load.