OBS Lua Rename Script — Full Guide

Creator: Kvothera

Script Goal

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.

Output Pattern

Full Script

scene_in_filename.lua

Step-by-Step Explanation

Key Variables

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.

Helper: 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.

Helper: 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.

Helper: 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.

Helper: 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.

Recording Rename: 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.

Replay Rename: 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.

Events: 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.

Script Hooks

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.