'Extra replay buffer' hotkeys clearing when I close OBS

JM92

New Member
So I used this script to add extra replay buffer lengths, ran into all kinds of issues, ended up getting a new code from chatGPT (with 6 extra lengths instead of 3) because I don't know s*** about coding. To my surprise it worked except it clears the hotkeys I have set for them all every time I close & reopen OBS. Here's the code I have:

Code:
import obspython as obs
import os, sys, subprocess, time
from time import sleep
from importlib import util

# Global variables tracking system states
enabled = True
debug_mode = False
saved_settings = None

# Track all 6 hotkey pointer references safely inside an array
hotkey_references = [obs.OBS_INVALID_HOTKEY_ID] * 6

# Store internal properties mapping
replay_configs = []
for i in range(1, 7):
    replay_configs.append({
        "seconds": 15 if i==1 else (30 if i==2 else (60 if i==3 else (180 if i==4 else (300 if i==5 else 600)))),
        "path": "",
        "remove": True,
        "from_end": False
    })

def file_in_use(fpath):
    if os.path.exists(fpath):
        try:
            os.rename(fpath, fpath)
            return False
        except:
            return True
    return False
   
def ffmpeg_extract_subclip(filename, t1, t2, targetname=None):
    if "get_setting" not in sys.modules:
        from moviepy.config import get_setting
    if "get_setting" not in sys.modules:
        from moviepy.tools import subprocess_call
       
    name, ext = os.path.splitext(filename)
    if not targetname:
        T1, T2 = [int(1000*t) for t in [t1, t2]]
        targetname = "%sSUB%d_%d.%s" % (name, T1, T2, ext)
   
    cmd = [get_setting("FFMPEG_BINARY"), "-y",
           "-ss", "%0.2f"%t1,
           "-i", filename,
           "-t", "%0.2f"%(t2-t1),
           "-vcodec", "copy", "-acodec", "copy", targetname]
   
    if debug_mode:
        subprocess_call(cmd)
    else:
        subprocess_call(cmd, None)

def save_and_get_last_replay():
    timestamp = time.time()
    obs.obs_frontend_replay_buffer_save()
    path = get_last_replay()
    if path is None:
        sleep(2)
        path = get_last_replay()
    if not os.path.exists(path):
        sleep(2)
        path = get_last_replay()
    for i in range(20):
        file_timestamp = os.path.getctime(path)
        if file_timestamp < timestamp:
            sleep(1)
            path = get_last_replay()
        else:
            break
   
    file_timestamp = os.path.getctime(path)
    if file_timestamp < timestamp:
        return None
    else:
        return path
   
def get_last_replay():
    replay_buffer = obs.obs_frontend_get_replay_buffer_output()
    if not replay_buffer:
        return None
    cd = obs.calldata_create()
    ph = obs.obs_output_get_proc_handler(replay_buffer)
    obs.proc_handler_call(ph, "get_last_replay", cd)
    path = obs.calldata_string(cd, "path")
    obs.calldata_destroy(cd)
    obs.obs_output_release(replay_buffer)
    return path

# Unique callback entry handlers mapping directly to target configuration indexes
def trigger_replay1(pressed):
    if pressed and enabled: execute_clipping_sequence(0)
def trigger_replay2(pressed):
    if pressed and enabled: execute_clipping_sequence(1)
def trigger_replay3(pressed):
    if pressed and enabled: execute_clipping_sequence(2)
def trigger_replay4(pressed):
    if pressed and enabled: execute_clipping_sequence(3)
def trigger_replay5(pressed):
    if pressed and enabled: execute_clipping_sequence(4)
def trigger_replay6(pressed):
    if pressed and enabled: execute_clipping_sequence(5)

def execute_clipping_sequence(idx):
    if obs.obs_frontend_replay_buffer_active() is False:
        return
       
    config = replay_configs[idx]
    seconds = config["seconds"]
    new_path = config["path"]
    remove_source = config["remove"]
    from_end = config["from_end"]
   
    if seconds <= 0:
        return

    if "VideoFileClip" not in sys.modules:
        from moviepy.editor import VideoFileClip

    last_replay = save_and_get_last_replay()
    if last_replay is not None and len(last_replay) > 0:
        last_replay_folder = os.path.dirname(os.path.abspath(last_replay))
        last_replay_name, last_replay_type = os.path.splitext(os.path.basename(last_replay))
       
        if len(new_path) <= 0 or not os.path.exists(new_path):
            new_path = last_replay_folder
           
        new_replay = os.path.normpath(os.path.join(new_path, last_replay_name + "_" + str(seconds) + "s" + last_replay_type))
        src_replay = os.path.normpath(last_replay)

        if src_replay.lower() == new_replay.lower():
            new_replay = os.path.normpath(os.path.join(new_path, last_replay_name + "_" + str(seconds) + "s_clip" + last_replay_type))

        clip = VideoFileClip(src_replay)
        duration = clip.duration
       
        if duration > seconds:
            if from_end:
                ffmpeg_extract_subclip(src_replay, duration - seconds, duration, targetname=new_replay)
            else:
                ffmpeg_extract_subclip(src_replay, 0, seconds, targetname=new_replay)
        else:
            ffmpeg_extract_subclip(src_replay, 0, duration, targetname=new_replay)
           
        clip.reader.close()
        if clip.audio and clip.audio.reader:
            clip.audio.reader.close_proc()
        del clip.reader
        del clip

        if remove_source:
            sleep(1)
            for i in range(10):
                if not file_in_use(src_replay):
                    try:
                        os.remove(src_replay)
                        break
                    except:
                        pass
                sleep(1)

def script_description():
    return "Unified Management Panel for 6 Additional Custom ReplayBuffer lengths."

def script_update(settings):
    global enabled, debug_mode
    enabled = obs.obs_data_get_bool(settings, "enabled")
    debug_mode = obs.obs_data_get_bool(settings, "debug_mode")
   
    for i in range(1, 7):
        cfg = replay_configs[i-1]
        cfg["seconds"] = obs.obs_data_get_int(settings, f"replay{i}_seconds")
        cfg["path"] = obs.obs_data_get_string(settings, f"replay{i}_path")
        cfg["remove"] = obs.obs_data_get_bool(settings, f"replay{i}_remove")
        cfg["from_end"] = obs.obs_data_get_bool(settings, f"replay{i}_from_end")

def script_properties():
    props = obs.obs_properties_create()
    obs.obs_properties_add_bool(props, "enabled", "Enable Script")
    obs.obs_properties_add_bool(props, "debug_mode", "Debug Mode")
   
    for i in range(1, 7):
        obs.obs_properties_add_int(props, f"replay{i}_seconds", f"Replay {i} Duration (seconds)", 0, 2000, 1)
        obs.obs_properties_add_path(props, f"replay{i}_path", f"Replay {i} Output Path", obs.OBS_PATH_DIRECTORY, None, None)
        obs.obs_properties_add_bool(props, f"replay{i}_remove", f"Replay {i} Delete Original Buffer File")
        obs.obs_properties_add_bool(props, f"replay{i}_from_end", f"Replay {i} Cut From End (Instead of Start)")
       
    return props

def register_all_hotkeys():
    global hotkey_references, saved_settings
    if saved_settings is None:
        return
       
    callbacks = [trigger_replay1, trigger_replay2, trigger_replay3, trigger_replay4, trigger_replay5, trigger_replay6]
   
    for i in range(1, 7):
        idx = i - 1
        hk_id = f"mega_replay_slot_{i}_hk"
        label = f"Save Replay {i}"
       
        hotkey_references[idx] = obs.obs_hotkey_register_frontend(hk_id, label, callbacks[idx])
        hk_array = obs.obs_data_get_array(saved_settings, hk_id)
        obs.obs_hotkey_load(hotkey_references[idx], hk_array)
        obs.obs_data_array_release(hk_array)

def unified_frontend_event_callback(event):
    # Locks hotkey assignments safely into execution memory only after system arrays instantiate
    if event == obs.OBS_FRONTEND_EVENT_FINISHED_LOADING:
        register_all_hotkeys()

def script_load(settings):
    global saved_settings
    saved_settings = settings
   
    if not obs.obs_data_has_user_value(settings, "enabled"):
        obs.obs_data_set_default_bool(settings, "enabled", True)
       
    # Inject baseline defaults matching your structural choices
    defaults = [15, 30, 60, 180, 300, 600]
    for i in range(1, 7):
        if not obs.obs_data_has_user_value(settings, f"replay{i}_seconds"):
            obs.obs_data_set_default_int(settings, f"replay{i}_seconds", defaults[i-1])
        if not obs.obs_data_has_user_value(settings, f"replay{i}_remove"):
            obs.obs_data_set_default_bool(settings, f"replay{i}_remove", True)

    obs.obs_frontend_add_event_callback(unified_frontend_event_callback)
    register_all_hotkeys()

def script_save(settings):
    global hotkey_references
    for i in range(1, 7):
        idx = i - 1
        if hotkey_references[idx] == obs.OBS_INVALID_HOTKEY_ID:
            continue
        hk_id = f"mega_replay_slot_{i}_hk"
        hk_array = obs.obs_hotkey_save(hotkey_references[idx])
        obs.obs_data_set_array(settings, hk_id, hk_array)
        obs.obs_data_array_release(hk_array)

I also can't for the life me get OBS to auto launch (as admin) on boot, and enable replay buffer automatically. It used to work but then stopped working after I changed it to run as admin. It also never enabled replay buffer when closing & relaunching manually
 
Last edited:
Back
Top