Automatically load the latest video file in a folder and loop it

dotdotw

New Member
Hey guys, Sorry if this has already been discussed, but I'm struggling to find my exact use case, so thought I'd ask...

I want to be able to play a video with media source and make a recording of the media source playing within a scene. And then automatically load this newly recorded video back into the media source so that I can do the process again. Media Source loops playback. I can get some of the way there with Instant Replay buffer but it still requires me to manually load the video back in and the duration doesn't match the source video. I can also get some of the way there with Directory WatchMedia plugin with a VLC playlist source, but this then continues to other videos in the playlist. I'd ideally use Recording as the instant replay doesn't allow recording in uncompressed, prores etc. Any suggestions will be appreciated! Thanks :)
 

AaronD

Active Member
I've seen audio loopers that do what I think you want: instant start/stop, and immediately loop the part in between while optionally adding another recording to it. With just the one pedal, a musician can layer an entire song, live, all by himself.

You're looking to do that with video? If so, I think OBS is going to be too clunky for you. It works really well to set up what a scene is going to be and then play it, kinda like building a PowerPoint slide ahead of time, but it doesn't yank around live very well. You can do *some*, but probably not all that you want.
 

dotdotw

New Member
That's exactly what I am after, I've been using an audio looper for layered up beatboxing, and wanted to make multi layered video feedback loops to use as the visuals to go with it. I've gotten some of the way using Resolume, but the issue with that program was dropping frames and it was still not seamless with the recording matching the source. OBS records much better. Thanks for the advise about it not yanking around live very well, sounds like I'll be trying to push it in the wrong direction, it has crashed quite a while trying the different instant replay methods.
 

Suslik V

Active Member
Here is .lua script that renames file (this can be last recording file when it has static name) and loads this recently renamed file into the available Media Source/VLC Video Source of OBS, then the "Ctrl+Num7" key press event simulated. Save it as "rename_recordings_and_generation_loss.lua" or use similar name to be able to add it to the main menu Tools > Scripts

Script:
Lua:
-- 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

Static name:
obs_rename_recording_static_name01.png


Notes
  • Script doesn't supports Unicode symbols in path and names.
  • Script has no checks for existing files during renaming, thus silently fails if the file already exist.
  • Script makes 5 attempts (1 sec interval) to rename the file after OBS stopped recording.
  • Script has very small memory leakage of few bytes (<100) per recording - this is string (path to recording) that was allocated by OBS. I was unable to free it from inside of the script.

Usage logic:
  1. You need to set OBS recording to fixed (static) filename (main menu File > Settings > Advanced > Filename Formatting field).
  2. You loading this "rename_recordings_and_generation_loss.lua" script into OBS (main menu Tools > Scripts).
  3. You setting Original file name in the script's settings (same as in p1)
  4. Then you starting recording.
  5. You stopping your recording when you need to.
  6. Recorded file automatically will be renamed to given formatted name.
  7. This recently renamed file loaded into specified Media Source of OBS.
  8. Hotkey "Ctrl+Num7" press event simulated for internals of OBS (it can be assigned to scene switch or anything you need).
 
Last edited:

Dablu05

New Member
Hey guys, when I play the replay buffer.. it shows only the old recording. which I selected while setting the reply buffer. it does not change the currently recorded video... please help
 

Suslik V

Active Member
@Dablu05 for instant replay, try "instant-replay.lua" script that is shipped with any modern version of OBS. You can find it in OBS folder: data\obs-plugins\frontend-tools\scripts\.
 

Dablu05

New Member
@Suslik V hay bro many thanks for your valuable reply..... but till now I am facing the same problem. Firstly, I have deleted the exiting "instant-replay.lua" ... then I have to follow the path that you suggest. but unfortunately, I am facing the same problem. please help will be very appreciated.
 

Attachments

  • Capture.PNG
    Capture.PNG
    54.9 KB · Views: 39

Suslik V

Active Member
@Dablu05 it works for me just fine.
But the script "instant-replay.lua" has different logic.
For OBS "instant-replay.lua" script you need to specify special hotkey (that both saves replay file and loads replay file into specified Media Source of OBS). The hotkey itself is named "Instant Replay", look:

instant-replay-OBS-Studio-v30-0-2.png

Then, when you need to show to your viewers your most recent replay you hitting hotkey "Instant Replay" (assigned by you earlier). OBS saves replay and loads it into the Media Source (all in one action). Of course, the Replay Buffer should be already running.
 

Dablu05

New Member
@Suslik V Thank you soooooo much... I have choosing wrong hockey that's why not perform... After follow your instructions Now I have tested every thing fine... Once again thank you sooo much... It's help me lot... I am using instant replay for live cricket... So i thik you understand that how much important to me to play a replay...
 

beaz2013

New Member
I have all the replay parts working fine but I'm looking a script or plugin that will play the last 3 saved replays instead of the last one if that makes sence. At the moment I have a scene with VLC and I go in and select the last 3 manually and click OK and play that quickly which is a pain lol

So yeah I'm just wondering if there's a script that would do this or able to modify the instant replay.lua
 
Top