obspython
OBS Studio API.Obs scripts examples in/src
- Each obs script example mostly will operate on existsing text source
- It is possible to duplicate scripts and re-add them to OBS ( names must be different)
- Using classes
- with statement
- Passing arguments to callbacks
- UI
- Property modification
- Additional input
- obs_data
- save settings as json
- Source's and filters with identifier string
- Add source
- Move source
- Add filter to source
- Toggle source visibility
- Set current scene
- Get set order in scene
- Add scene with sources to current scene
- Events
- Program state
- Signals
- Timing (sequential primitives)
- Hotkeys
- Play sound
- Read and write private data from scripts or plugins
- Debug
- Docs and code examples
- Links
- Contribute
class Example:
def __init__(self,source_name=None):
self.source_name = source_name
def update_text(self):
source = obs.obs_get_source_by_name(self.source_name)
if source is not None:
data = str(next(datacycle))
settings = obs.obs_data_create()
obs.obs_data_set_string(settings, "text", data)
obs.obs_source_update(source, settings)
obs.obs_data_release(settings)
obs.obs_source_release(source)
Or more compact:
class _G:
source_name = ''
data = None
flag = False
G = _G()
def script_update(settings):
G.source_name = ...
if G.flag:
pass
Automatically release .
@contextmanager
def source_auto_release(source_name):
source = obs.obs_get_source_by_name(source_name)
try:
yield source
finally:
obs.obs_source_release(source)
...
# usage
with source_auto_release(self.source_name) as source:
if source is not None:
data = str(next(datacycle))
with data_auto_release() as settings:
obs.obs_data_set_string(settings, "text", data)
obs.obs_source_update(source, settings)
Full example
See also :
https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager
from functools import partial
...
flag = obs.obs_data_get_bool(settings,"_obs_bool")
eg.update_text = partial(eg.update_text,flag_func=flag)
...
See also :
https://obsproject.com/docs/reference-properties.html#property-object-functions
def callback(props, prop, *args, **kwargs): # pass settings implicitly
p = obs.obs_properties_get(props, "button")
n = next(counter)
obs.obs_property_set_description(p, f"refresh pressed {n} times")
return True
...
def script_properties():
props = obs.obs_properties_create()
b = obs.obs_properties_add_button(
props, "button", "refresh pressed 0 times", refresh_pressed
)
obs.obs_property_set_modified_callback(b, callback)
return props
Full example
See also :
https://obsproject.com/docs/reference-properties.html#property-modification-functions
def callback(props, prop, settings):
_number = obs.obs_data_get_int(settings, "_int")
_text_value = obs.obs_data_get_string(settings, "_text")
text_property = obs.obs_properties_get(props, "_text")
if _number > 50:
eg.data = _text_value + str(_number)
obs.obs_property_set_visible(text_property, True)
return True
else:
eg.data = ""
obs.obs_property_set_visible(text_property, False)
return True
...
def script_properties(): # ui
...
number = obs.obs_properties_add_int(props, "_int", "Number", 1, 100, 1)
text_value = obs.obs_properties_add_text(
props, "_text", "Additional input:", obs.OBS_TEXT_DEFAULT
)
obs.obs_property_set_visible(text_value, False)
obs.obs_property_set_modified_callback(number, callback)
...
Note: properties share similar structure , in Python, Lua, C.
Example C
See also :
https://obsproject.com/docs/reference-properties.html#property-modification-functions
obs_data_get_string
obs_data_get_int
obs_data_get_double
obs_data_get_bool
obs_data_get_obj
obs_data_get_array
p = Path(__file__).absolute() # current script path
file = p.parent / "saved_settings.json"
try:
content = obs.obs_data_get_json(Data._settings_)
with open(file, "w") as f:
f.write(content)
except Exception as e:
print(e, "cannot write to file")
Full example
See also :
https://obsproject.com/docs/reference-settings.html
https://obsproject.com/docs/scripting.html#getting-the-current-script-s-path
To identify with obs_source_get_unversioned_id
, or creating source/filter.
Name | Source type identifier string |
---|---|
Browser | browser_source |
Color Source | color_source |
Display Capture | monitor_capture |
Game Capture | game_capture |
Image | image_source |
Image Slide Show | slideshow |
Media Source | ffmpeg_source |
Text (GDI+) | text_gdiplus |
Window Capture | window_capture |
Name | Source type identifier string |
---|---|
Async Delay | async_delay_filter |
Chroma Key | chroma_key_filter |
Chroma Key V2 | chroma_key_filter_v2 |
Color Correction | color_filter |
Color Correction V2 | color_filter_v2 |
Color Key | color_key_filter |
Color Key V2 | color_key_filter_v2 |
Color Grade | color_grade_filter |
Compressor | compressor_filter |
Crop/Pad | crop_filter |
Expander | expander_filter |
Gain | gain_filter |
GPU Delay | gpu_delay_filter |
Image Mask/Blend | mask_filter |
Invert Polarity | invert_polarity_filter |
Limiter | limiter_filter |
Luma Key | luma_key_filter |
Luma Key V2 | luma_key_filter_v2 |
Mask | mask_filter |
Mask V2 | mask_filter_v2 |
Noise Gate | noise_gate_filter |
Noise Suppression | noise_suppress_filter |
Noise Suppression V2 | noise_suppress_filter_v2 |
Render Delay | gpu_delay |
Scaling/Aspect Ratio | scale_filter |
Scroll | scroll_filter |
Sharpen | sharpness_filter |
Sharpen V2 | sharpness_filter_v2 |
Video Delay (Async) | async_delay_filter |
VST 2.x Plug-in | vst_filter |
Create source and add it to current scene
obs.obs_data_set_string(settings, "text", "The quick brown fox jumps over the lazy dog")
source = obs.obs_source_create_private("text_gdiplus", "test_py", settings)
obs.obs_scene_add(scene, source)
Full example
See also :
https://obsproject.com/docs/reference-scenes.html
Get current scene , get source name, move source to location
def __init__(self):
pos = obs.vec2()
self.location = pos
...
def move_text_source(self):
current_scene = obs.obs_frontend_get_current_scene()
source = obs.obs_get_source_by_name("test_py")
scene = obs.obs_scene_from_source(current_scene)
scene_item = obs.obs_scene_find_source(scene, "test_py")
if scene_item:
dx, dy = 10, 10
print("old values", self.location.x)
obs.obs_sceneitem_get_pos(
scene_item, self.location
) # update to last position if its changed from OBS
self.location.x += dx
self.location.y += dy
print("new values", self.location.x)
obs.obs_sceneitem_set_pos(scene_item, self.location)
Filters are sources,they are not listed in obspython module, you need to know its id from obs_source_info
obs.obs_data_set_int(settings, "opacity", 50)
source_color = obs.obs_source_create_private(
"color_filter", "opacity to 50", settings
)
obs.obs_source_filter_add(source, source_color)
Full example
See also :
Color correction source
https://obsproject.com/docs/reference-sources.html
def toggle(self):
current_scene = obs.obs_scene_from_source(obs.obs_frontend_get_current_scene())
scene_item = obs.obs_scene_find_source(current_scene, self.source_name)
boolean = not obs.obs_sceneitem_visible(scene_item)
obs.obs_sceneitem_set_visible(scene_item, boolean)
def set_current_scene(self):
scenes = obs.obs_frontend_get_scenes()
for scene in scenes:
name = obs.obs_source_get_name(scene)
if name == self.scene_name:
obs.obs_frontend_set_current_scene(scene)
...
scenes = obs.obs_frontend_get_scenes() # Dropdown menu UI
for scene in scenes:
name = obs.obs_source_get_name(scene)
obs.obs_property_list_add_string(p, name, name)
def get_order(scene_items=None):
order = list()
for i, s in enumerate(scene_items):
source = obs.obs_sceneitem_get_source(s)
name = obs.obs_source_get_name(source)
order.append({"index": i, "name": name, "scene_item": s})
return order
def reorder():
current_scene = obs.obs_frontend_get_current_scene()
with scene_ar(current_scene) as scene:
with scene_enum(scene) as scene_items:
order = get_order(scene_items)
# change second index with pre last
order[1]["index"], order[-2]["index"] = (
order[-2]["index"],
order[1]["index"],
)
for s in sorted(order, key=lambda i: i["index"]):
obs.obs_sceneitem_set_order_position(s["scene_item"], s["index"])
def add_random_text_source(scene):
r = " random text # " + str(randint(0, 10))
with data_ar() as settings:
obs.obs_data_set_string(settings, "text", f"random text value {r}")
with source_create_ar("text_ft2_source", f"random text{r}", settings) as source:
pos = obs.vec2()
pos.x = randint(0, 1920)
pos.y = randint(0, 1080)
scene_item = obs.obs_scene_add(scene, source)
obs.obs_sceneitem_set_pos(scene_item, pos)
def add_scene_with_sources():
current_scene_source = obs.obs_frontend_get_current_scene()
with scene_from_source_ar(current_scene_source) as scene_source:
with scene_create_ar("_nested_scene") as _scene:
py_scene_source = obs.obs_scene_get_source(_scene)
with scene_from_source_ar(py_scene_source) as scene:
add_random_text_source(scene)
add_random_text_source(scene)
add_random_text_source(scene)
# add created scene to current scene ( nested scene)
_scene_source = obs.obs_scene_get_source(scene)
obs.obs_scene_add(scene_source, _scene_source)
Note: sometimes OBS crashes if one of such scenes has been deleted.
def on_event(event):
if event == obs.OBS_FRONTEND_EVENT_SCENE_CHANGED:
raise Exception("Triggered when the current scene has changed.")
def script_load(settings):
obs.obs_frontend_add_event_callback(on_event)
Full example
See also:
https://obsproject.com/docs/reference-frontend-api.html#structures-enumerations
Those functions return true or false :
obs.obs_frontend_preview_program_mode_active()
obs.obs_frontend_replay_buffer_active()
obs.obs_frontend_recording_active()
obs.obs_frontend_recording_paused()
obs.obs_frontend_streaming_active()
Signals , callbacks , differences from C
sh = obs.obs_get_signal_handler()
obs.signal_handler_connect(sh,"source_create",callback)
def callback(calldata):
source = obs.calldata_source(cd,"source")
print(obs.obs_source_get_name(source))
source_create, source_destroy, source_remove, source_save, source_load, source_activate, source_deactivate, source_show, source_hide, source_rename, source_volume, source_transition_start, source_transition_video_stop, source_transition_stop, channel_change, master_volume, hotkey_layout_change, hotkey_register, hotkey_unregister, hotkey_bindings_changed
https://obsproject.com/docs/reference-core.html#core-obs-signals
def connect_cur_scene():
source = obs.obs_frontend_get_current_scene()
sh = obs.obs_source_get_signal_handler(source)
obs.signal_handler_connect(sh, "item_add", callback)
obs.obs_source_release(source)
def callback(calldata):
scene_item = obs.calldata_sceneitem(calldata, "item")
#scene = obs.calldata_source(cd,"scene") # bad utf symbols
scene = obs.obs_sceneitem_get_scene(scene_item)
name = obs.obs_source_get_name
source = obs.obs_sceneitem_get_source
scene_source = obs.obs_scene_get_source
scene_name = name(scene_source(scene))
scene_item_name = name(source(scene_item))
print(f"item {scene_item_name} has been added to scene {scene_name}")
item_add, item_remove, reorder, refresh, item_visible, item_locked, item_select, item_deselect, item_transform
https://obsproject.com/docs/reference-scenes.html#scene-signals
sh = obs.obs_source_get_signal_handler(some_source)
obs.signal_handler_connect(sh,"show",callback)
def callback(calldata):
source = obs.calldata_source(cd,"source")
print("on source show",obs.obs_source_get_name(source))
destroy, remove, save, load, activate, deactivate, show, hide, mute, push_to_mute_changed, push_to_mute_delay, push_to_talk_changed, push_to_talk_delay, enable, rename, volume, update_properties, update_flags, audio_sync, audio_mixers, filter_add, filter_remove, reorder_filters, transition_start, transition_video_stop, transition_stop, media_started, media_ended, media_pause, media_play, media_restart, media_stopped, media_next, media_previous
https://obsproject.com/docs/reference-sources.html#source-signals
def connect_to_rec():
sh = obs.obs_output_get_signal_handler(obs.obs_frontend_get_recording_output())
obs.signal_handler_connect(sh, "pause", callback)
def callback(calldata):
#out = obs.calldata_ptr(calldata, "output") # bad type
print('output paused')
start, stop, pause, unpause, starting, stopping, activate, deactivate, reconnect, reconnect_success
https://obsproject.com/docs/reference-outputs.html#output-signals
def script_update(settings):
eg.source_name = obs.obs_data_get_string(settings, "source")
obs.timer_remove(eg.update_text)
if eg.source_name != "":
obs.timer_add(eg.update_text, 1 * 1000)
Full example
Note: each time script updated it's removed first
See also :
Version with globals and only one timer allowed.
https://obsproject.com/docs/scripting.html#script-timers
def callback(pressed):
if pressed:
toggle_thread()
def busy_thread():
while True:
if not data.thread_paused:
sleep(0.02)
data.status = "active"
# print to stdoud crashes OBS on exit
else:
sleep(0.5)
data.status = "inactive"
print('Press the "~" to toggle on/off')
hook("OBS_KEY_ASCIITILDE", "id_", callback)
obs.timer_add(lambda: print(data.status), 500)
t = threading.Thread(target=busy_thread)
t.start()
This hotkey example will create hotkeys in settings , but you need to bind it manually.
class Hotkey:
def __init__(self, callback, obs_settings, _id):
self.obs_data = obs_settings
self.hotkey_id = obs.OBS_INVALID_HOTKEY_ID
self.hotkey_saved_key = None
self.callback = callback
self._id = _id
self.load_hotkey()
self.register_hotkey()
self.save_hotkey()
...
class h:
htk_copy = None # this attribute will hold instance of Hotkey
...
h1 = h()
h2 = h()
...
def script_load(settings):
h1.htk_copy = Hotkey(cb1, settings, "h1_id")
h2.htk_copy = Hotkey(cb2, settings, "h2_id")
def script_save(settings):
h1.htk_copy.save_hotkey()
h2.htk_copy.save_hotkey()
This hotkey example will create hotkeys on fly from json settings , but you need to know internal id.
ID = "htk_id"
JSON_DATA = '{"%s":[{"key":"OBS_KEY_1"}]}' % ID
def on_obs_key_1(pressed):
if pressed:
raise Exception("hotkey 1 pressed")
def script_load(settings):
s = obs.obs_data_create_from_json(JSON_DATA)
a = obs.obs_data_get_array(s, ID)
h = obs.obs_hotkey_register_frontend(ID, ID, on_obs_key_1)
obs.obs_hotkey_load(h, a)
Here is how send hotkey to OBS
def send_hotkey(obs_htk_id, key_modifiers=None):
if key_modifiers:
shift = key_modifiers.get("shift")
control = key_modifiers.get("control")
alt = key_modifiers.get("alt")
command = key_modifiers.get("command")
...
combo = obs.obs_key_combination()
combo.modifiers = modifiers
combo.key = obs.obs_key_from_name(obs_htk_id)
...
obs.obs_hotkey_inject_event(combo, False)
obs.obs_hotkey_inject_event(combo, True)
obs.obs_hotkey_inject_event(combo, False)
See also:
https://github.com/obsproject/obs-studio/blob/master/libobs/obs-hotkeys.h
obsproject/obs-websocket#595
def play_sound():
...
mediaSource = obs.obs_source_create_private(
"ffmpeg_source", "Global Media Source", None
)
s = obs.obs_data_create()
obs.obs_data_set_string(s, "local_file", script_path() + "alert.mp3")
obs.obs_source_update(mediaSource, s)
obs.obs_source_set_monitoring_type(
mediaSource, obs.OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT
)
...
Write in one script
def send_to_private_data(data_type, field, result):
settings = obs.obs_data_create()
set = getattr(obs, f"obs_data_set_{data_type}")
set(settings, field, result)
obs.obs_apply_private_data(settings)
obs.obs_data_release(settings)
def write_private_data():
result = "private value from " + str(__file__) + " " + str(randint(1, 10))
send_to_private_data("string", "__private__", result)
Read from another
@contextmanager
def p_data_ar(data_type, field):
settings = obs.obs_get_private_data()
get = getattr(obs, f"obs_data_get_{data_type}")
try:
yield get(settings, field)
finally:
obs.obs_data_release(settings)
def print_private_data():
with p_data_ar("string", "__private__") as value:
print(value)
Lua is also supported
local obs = obslua
local settings = obs.obs_data_create()
obs.obs_data_set_int(settings,"__private__", 7)
obs.obs_apply_private_data(settings)
obs.obs_data_release(settings)
There is no stdin therefore you can't use pdb , options are:
- using
print
- using pycharm remote debugging (localhost)
- using vscode attach to the process:
- Load python extension
- open script file ,
pip install debugpy
, placedebugpy.breakpoint()
somewhere- Run (F5) select configuration ( Attach using Process ID)
- select obs (on windows
obs64.exe
) - View select Debug Console (ctrl+shift+y)
- Example debugpy obs
Generated export.md contains all variables and functions available in obspython
formatted with markdown. Table consist of links to appropriate search terms in OBS Studio repository, and obswebsocket,links to scripts in obspython
and obslua
with each script within github code search.gs_*
and matrix_*
functions exluded from that table.
Full example
Note
: starting from 2020.12.17 Github Code Search no longer works as it was, see also this thread
- Scripts forum , Github topic
obs-scripts
, Github topicobs-script
- OBS Studio Repo , obs-scripting-python.c
- Docs , Docs/scripting , Docs/plugins , Docs index
- obspython Gist , Github , grep.app
- obslua Gist , Github , grep.app
- Lua tips and tricks
- Python 3.6.8 , 64 bit installer
Contributions are welcome!