Take screenshots with a timer

wtechgo

New Member
The user can configure a target directory and an interval duration, to take a screenshot of a selected source or scene.

Once we have the pictures we've accomplished a lot, however, most will want to make a video out of it. Can we harness the OBS built-in ffmpeg to run a command to do just that?

Fyi, I posted on reddit to accomplish the above with a lua script, though if the feature would be part of OBS already, that would be preferable.

P.S. I wanted to post in OBS forum in the scripting section, but I'm not allowed to. To post a feature request, same story. I wonder how one gets access to post there.
 

Bluscream

New Member
@wtechgo Actually i managed to create python OBS script that serves a snapshot of the current output on a included http server

Unfortunately there is no way to take a screenshot or fetch a frame directly through OBS'es scripting API, but if you can run https://github.com/iamscottxu/obs-rtspserver at the same time it can be used to fetch a frame and serve it via http

it is awful barely working code; but it works here and runs alongside obs (YOU HAVE TO KILL OBS.exe manually after closing obs tho!)

to install copy the code below, paste it in a file like `C:\Program Files\obs-studio\data\obs-plugins\frontend-tools\scripts\preview.py` and add that script via OBS -> "Tools" -> "Scripts" -> "+".
Then you can visit http://localhost:8000/ and get a frame of the rtsp stream

Python:
import signal
# import obspython as obs
import os
import subprocess
import http.server
import socketserver
import os
import threading

temp_dir = os.environ.get('TMPDIR', '/tmp') if os.name == 'posix' else os.environ.get('TMP', 'C:\\Windows\\Temp')

# region Settings
HTTP_SERVER_PORT = 8000
RTSP_SERVER_URI = "rtsp://127.0.0.1:554/live"
SNAPSHOT_PATH = os.path.join(temp_dir, 'obs-snapshot.jpg')
# endregion Settings

def capture_frame_rtsp():
    image_path = SNAPSHOT_PATH # os.path.join(os.path.dirname(__file__), 'output.jpg')
    print(image_path)
    # cmd = ["ffmpeg", "-rtsp_transport", "tcp", "-i", "rtsp://127.0.0.1:554/live", "-frames:v", "1", '"'+image_path+'"']
    cmd = ["ffmpeg","-y","-i",RTSP_SERVER_URI,"-vframes","1",'"'+image_path+'"']
    print(" ".join(cmd))
    subprocess.run(cmd)
    return image_path
# obs.timer_add(capture_frame_rtsp, 10000) # Capture a frame every 1000 milliseconds (1 second)

# def capture_screenshot():
#     obs.obs_frontend_trigger_hotkey_by_name("Screenshot") # Simulate pressing the 'S' key to trigger a screenshot capture
# obs.timer_add(capture_screenshot, 10000)

class Handler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        print(f"GET {self.path}")
        if self.path == "/":
            image_path = capture_frame_rtsp()
            if not os.path.exists(image_path):
                self.send_error(404, "File not found")
                return
            # Serve the image file
            with open(image_path, 'rb') as f:
                self.send_response(200)
                self.send_header('Content-type', 'image/jpeg')
                self.end_headers()
                self.wfile.write(f.read())
            #     self.path = screenshot_path
            # else:
            #     self.path = 'screenshot.png' # Default file if no screenshot exists
        elif self.path == "/stop":
            exit(0)
        return http.server.SimpleHTTPRequestHandler.do_GET(self)

class GracefulTCPServer(socketserver.TCPServer):
    def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
        super().__init__(server_address, RequestHandlerClass, bind_and_activate)
        self.shutdown_request = self.custom_shutdown_request

    def custom_shutdown_request(self, request):
        # This method is called for each request to be shut down
        # Here you can add any custom behavior before shutting down
        pass

    def server_close(self):
        # Override server_close to implement custom shutdown logic
        self.shutdown()
        super().server_close()

def start_http_server():
    with GracefulTCPServer(("", HTTP_SERVER_PORT), Handler) as httpd:
        print("Serving at port", HTTP_SERVER_PORT)
        httpd.serve_forever()

# Signal handling
def signal_handler(sig, frame):
    print("Signal received, shutting down server...")
    # Here you can add any cleanup logic before shutting down
    exit(0)

# Register signal handlers
signal.signal(signal.SIGINT, signal_handler)

# Start the server in a separate thread
server_thread = threading.Thread(target=start_http_server)
server_thread.start()
 

Suslik V

Active Member
With "Advanced Scene Switcher" plugin same task can be done easier. Plugin has Timer trigger (condition) and Screenshot action.
 

upgradeQ

Member
@Bluscream to make it gracefully shutdown, instead of manually killing it in task manager, you should write script_unload callback, here is the example code
Python:
import obspython as S
import threading
from time import sleep

def hook(obs_htk_id, htk_id, callback):
    json_data = '{"%s":[{"key":"%s"}]}' % (htk_id, obs_htk_id)
    s = S.obs_data_create_from_json(json_data)
    a = S.obs_data_get_array(s, htk_id)
    h = S.obs_hotkey_register_frontend(htk_id, obs_htk_id, callback)
    S.obs_hotkey_load(h, a)
    S.obs_data_array_release(a)
    S.obs_data_release(s)

data = lambda: ...
data.thread_paused = True
data.status = "empty"
data.shutdown = False

def toggle_thread():
    data.thread_paused = not data.thread_paused

def callback(pressed):
    if pressed:
        toggle_thread()

def busy_thread():
    while True:
        if not data.thread_paused:
            sleep(0.3)
            data.status = "active"
            # print to stdoud crashes OBS on exit
        else:
            sleep(0.5)
            data.status = "inactive"
        if data.shutdown:
            raise KeyboardInterrupt
def script_load(settings):
    print('Press the "~" to toggle on/off')
    hook("OBS_KEY_ASCIITILDE", "id_", callback)
    S.timer_add(lambda: print(data.status), 500)
    t = threading.Thread(target=busy_thread)
    t.start()

def script_unload():
    data.shutdown = True

"Unfortunately there is no way to take a screenshot or fetch a frame directly through OBS'es scripting API" - This is false, you can access a raw frame via ctypes ffi, see

Also you might try to rewrite your script using built in WHIP and WEBRTC with python library - aiortc

@wtechgo "P.S. I wanted to post in OBS forum in the scripting section, but I'm not allowed to. " - This is resource only section, meaning you must contribute something in order to "post".
Now, for your idea, there is popular obs-screenshot-plugin which has many features you might like, it is currently in low maintenance, but I found a recent fork with OBS Studio 30.0.0+ support:
 
Top