Help updating text file read rate to be faster?

door_casts

New Member
I'm working on a project where I'd like to use a quickly or constantly updated text file to display information and the 1 second refresh rate that's default is simply too slow for what I'm doing.

I found this thread: https://obsproject.com/forum/threads/read-from-text-file-refresh-rate.73589/

from a while ago detailing how to do it but the files they offer up are too old and while I can find the variable that is necessary to change in the obs-text.cpp file on the GitHub I can't figure out how to compile the source code myself into the .dll necessary after changing it.

Is there another workaround or can anybody explain to me how to compile that .dll properly?
 

Wallwin

New Member
Hi guys,

After looking into the python Scripting API I managed to create a python script that will refresh a specified text source from a specified Text File at a specified time interval, down to milliseconds. Its based of the one of the default python scripts that comes pre-installed with OBS, url-text.py. I have it running in OBS 30.0.2.

Import Note: I have noticed that running this script at a really low interval <50ms can greatly increase the time required to render frames in OBS. You might want to set the update interval a little bit higher if you are experiencing frames being missed due to rendering lag (check your View > Stats for information on this)

You'll have to have python installed (I used Python v3.8, not sure if it will work with other versions) and make sure that the python path is specified in the Python Settings tab (See Screenshot below). You might want to look into how to use Python Scripts in OBS in order to install it correctly if you aren't sure how to do this.

1708304317395.png


Here is the python script:


Python:
import obspython as obs
import os

file_path = ""
interval = 1000
source_name = ""

# ------------------------------------------------------------

def update_text():
    global file_path
    global source_name

    source = obs.obs_get_source_by_name(source_name)
    if source is not None:
        try:
            with open(file_path, 'r') as file:
                text = file.read()

                settings = obs.obs_data_create()
                obs.obs_data_set_string(settings, "text", text)
                obs.obs_source_update(source, settings)
                obs.obs_data_release(settings)

        except FileNotFoundError:
            obs.script_log(obs.LOG_WARNING, f"File not found at path: {file_path}")

        obs.obs_source_release(source)

def refresh_pressed(props, prop):
    update_text()

# ------------------------------------------------------------

def script_description():
    return "Updates a text source with text retrieved from a text file at specified intervals."

def script_update(settings):
    global file_path
    global interval
    global source_name

    file_path = obs.obs_data_get_string(settings, "file_path")
    interval = obs.obs_data_get_int(settings, "interval")
    source_name = obs.obs_data_get_string(settings, "source")

    if file_path != "" and source_name != "":
        obs.timer_add(update_text, interval)

def script_defaults(settings):
    obs.obs_data_set_default_int(settings, "interval", 1000)

def script_properties():
    props = obs.obs_properties_create()

    obs.obs_properties_add_text(props, "file_path", "File Path", obs.OBS_TEXT_DEFAULT)
    obs.obs_properties_add_int(props, "interval", "Update Interval (Milliseconds)", 1, 3600, 1)

    p = obs.obs_properties_add_list(props, "source", "Text Source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)
    sources = obs.obs_enum_sources()
    if sources is not None:
        for source in sources:
            source_id = obs.obs_source_get_unversioned_id(source)
            if source_id in ["text_gdiplus", "text_ft2_source"]:
                name = obs.obs_source_get_name(source)
                obs.obs_property_list_add_string(p, name, name)

        obs.source_list_release(sources)

    obs.obs_properties_add_button(props, "button", "Refresh", refresh_pressed)
    return props

Just copy it into a .py file and then upload that to your OBS Scripts.

Hopefully this helps some of you!
 

Wallwin

New Member
Hi All,

I just want to mention, I added a siginicant optimization to the code mentioned above. Instead of updating the contents of the text source every time the interval occurs, it will only update the text source on the specified interval when the text file has actually changed. This small change has significantly reduced the time required to render frames, down from 15ms to almost 3ms. This was necessary for my project as the overhead from all my other sources was pushing the time to render a single frame to concerning levels, almost to the point of skipping frames.

Please see the updated python file below and ignore the one mentioned above!


Python:
import obspython as obs
import os

file_path = ""
interval = 1000
source_name = ""
previous_content = ""

# ------------------------------------------------------------

def update_text():
    global file_path
    global source_name
    global previous_content

    source = obs.obs_get_source_by_name(source_name)
    if source is not None:
        try:
            with open(file_path, 'r') as file:
                text = file.read()

                # Only update the source if the content has changed
                if previous_content != text:
                    settings = obs.obs_data_create()
                    obs.obs_data_set_string(settings, "text", text)
                    obs.obs_source_update(source, settings)
                    obs.obs_data_release(settings)

                    # Update the previous content
                    previous_content = text

        except FileNotFoundError:
            obs.script_log(obs.LOG_WARNING, f"File not found at path: {file_path}")

        obs.obs_source_release(source)

def refresh_pressed(props, prop):
    update_text()

# ------------------------------------------------------------

def script_description():
    return "Updates a text source with text retrieved from a text file at specified intervals."

def script_update(settings):
    global file_path
    global interval
    global source_name

    file_path = obs.obs_data_get_string(settings, "file_path")
    interval = obs.obs_data_get_int(settings, "interval")
    source_name = obs.obs_data_get_string(settings, "source")

    if file_path != "" and source_name != "":
        obs.timer_add(update_text, interval)

def script_defaults(settings):
    obs.obs_data_set_default_int(settings, "interval", 1000)

def script_properties():
    props = obs.obs_properties_create()

    obs.obs_properties_add_text(props, "file_path", "File Path", obs.OBS_TEXT_DEFAULT)
    obs.obs_properties_add_int(props, "interval", "Update Interval (Milliseconds)", 1, 3600, 1)

    p = obs.obs_properties_add_list(props, "source", "Text Source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)
    sources = obs.obs_enum_sources()
    if sources is not None:
        for source in sources:
            source_id = obs.obs_source_get_unversioned_id(source)
            if source_id in ["text_gdiplus", "text_ft2_source"]:
                name = obs.obs_source_get_name(source)
                obs.obs_property_list_add_string(p, name, name)

        obs.source_list_release(sources)

    obs.obs_properties_add_button(props, "button", "Refresh", refresh_pressed)
    return props

Thanks everyone!!
 

Chewt

New Member
Hi to those in this thread, I came across this script and adapted it to my needs. My needs being able to capture the output of a python script instead of reading a file. Feel free to use the modified version of the script if you find it useful.

It expects the python script to have a `run` function that returns some string to be displayed. For example you could have a script like so:

Python:
def run():
    return "This is my output text"

Here is the script:

Python:
import obspython as obs
import os
import importlib.util

file_path = ""
interval = 1000
source_name = ""
previous_content = ""
format_string = "{s}"
stopped = True

# ------------------------------------------------------------

def update_text():
    global file_path
    global source_name
    global previous_content

    source = obs.obs_get_source_by_name(source_name)
    if source is not None:
        try:
            spec = importlib.util.spec_from_file_location(file_path.split('.py')[0], file_path)
            module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(module)
            text = str(module.run())
            # Only update the source if the content has changed
            if previous_content != text:
                settings = obs.obs_data_create()
                obs.obs_data_set_string(settings, "text", format_string.format(s=text))
                obs.obs_source_update(source, settings)
                obs.obs_data_release(settings)

                # Update the previous content
                previous_content = text

        except ModuleNotFoundError:
            obs.script_log(obs.LOG_WARNING, f"File not found at path: {file_path}")

        obs.obs_source_release(source)

def refresh_pressed(props, prop):
    update_text()

def start_refresh(props, prop):
    global stopped
    global file_path
    global interval
    global source_name
    stopped = False
    if file_path != "" and source_name != "":
        obs.timer_add(update_text, interval)
    return True

def stop_refresh(props, prop):
    global stopped
    global file_path
    global interval
    global source_name
    stopped = True
    obs.timer_remove(update_text)
    return True

# ------------------------------------------------------------

def script_description():
    return "Updates a text source with output retrieved from a python script at specified intervals."

def script_update(settings):
    global file_path
    global interval
    global source_name
    global stopped
    global format_string

    file_path = obs.obs_data_get_string(settings, "script_path")
    interval = obs.obs_data_get_int(settings, "interval")
    source_name = obs.obs_data_get_string(settings, "source")
    format_string = obs.obs_data_get_string(settings, "format_text")

def script_defaults(settings):
    obs.obs_data_set_default_int(settings, "interval", 1000)
    obs.obs_data_set_default_string(settings, "format_text", format_string)

def script_properties():
    props = obs.obs_properties_create()

    obs.obs_properties_add_path(props, "script_path", "Script Path", obs.OBS_PATH_FILE, "Python (*.py)", None)
    obs.obs_properties_add_int(props, "interval", "Update Interval (Milliseconds)", 1, 3600000, 1)

    p = obs.obs_properties_add_list(props, "source", "Text Source", obs.OBS_COMBO_TYPE_EDITABLE, obs.OBS_COMBO_FORMAT_STRING)
    sources = obs.obs_enum_sources()
    if sources is not None:
        for source in sources:
            source_id = obs.obs_source_get_unversioned_id(source)
            if source_id in ["text_gdiplus", "text_ft2_source"]:
                name = obs.obs_source_get_name(source)
                obs.obs_property_list_add_string(p, name, name)

        obs.source_list_release(sources)


    obs.obs_properties_add_text(props, "format_text", "Format String", obs.OBS_TEXT_DEFAULT)
    obs.obs_properties_add_button(props, "start_button", "Start", start_refresh)
    obs.obs_properties_add_button(props, "stop_button", "Stop", stop_refresh)
    obs.obs_properties_add_button(props, "button", "Refresh", refresh_pressed)
    return props
 
Top