Is an Extension for OBS Studio built on top of its scripting facilities, utilising built-in embedded LuaJIT interpreter, filter UI and function environment from Lua 5.1, it works on all major versions of OBS Studio such as: 32, 28, 27
- Less boilerplate code: an environment provided with simple UI, functions and special variables
source- source reference thatConsoleinstance attached tot.pressed- hotkey state which you can bindsleep(seconds)- command to pause executionobsffi- accessed viaobsffi- native linked libraryt.raw_image- D3D11_MAPPED_SUBRESOURCE, see example belowbk_obs_api_interactions_functions- more code to interact with OS and OBS API checkconsole.luat.tasks- asynchronous event loop
- Browser source interaction:
- real keyboard and mouse interaction functions
- snippet for auto refresh
- patch to inject and run arbitrary javascript without browser refreshing
Win64
obs-websocketsinteraction support to run any code or execute existing one, seeraw_websockets_interaction.py- Hotkeys support for each
Consoleinstance - Send, pause, resume, switch, recompile
Consoleinstances via global(per OBS Studio instance) multi actions pipes - Auto run code when OBS starts, load from file, Hot reload expressions
- Patches: run code before registering all of
Consoleinstance sources
Download source code, unpack/unzip
Add console.lua to OBS Studio via Tools > Scripts > "+" button
Tip
Make a backup of your scene collection.
You can rename the script name from console.lua to something else if crashing on start.
Left click on any source, add Console filter to it
Open Script Log to view Console output
Type some code into the text area
Press Execute!
Each Console instance has it's own namespace t and custom environment, you can access source which Console is attached to. e.g:
print(obs_source_get_name(source)) To access global the state of script do it via _G, when you write x = 5, only that instance of Console will have it
Note
There might be exceptions in your code, it is recommended to add print('start') and print('end') statements to debug code in Console
There are 2 types of hotkeys
First, can be found in settings with prefixed 0; - it will execute code in text area
Second, prefixed with 1;, 2;, 3; - it will mutate t.pressed, t.pressed2, t.pressed3 states
On/off sceneitem every 2.5 seconds - source must be a scene
Loop media source between start and end via hotkey - adds two hotkeys to set and clear loop (1; and 2;)
Update browser every 15 minutes - attach to browser source
Overwrite maximum render delay limit - attach to the source that has filter "Render Delay"
print_settings(source) - shows all settings
print_settings_new(source) - uses obs_data_get_json_pretty_with_defaults
print_settings2(source, filter_name) - shows all settings for a filter on that source
set_settings2(source, filter_name, opts) - sets one setting for filter of a source
set_settings52(source, opts) - sets just one setting for a source
set_settings3(source, filter_name, json_string) - sets settings for a filter of a source
set_settings4(source, json_string) - sets settings for source
set_settings2(source, "Color Correction", {_type ="double", _field= "gamma", _value= 0})local my_json_string = [==[
{"brightness":0.0,"color_add":0,"color_multiply":16777215,
"contrast":0.0,"gamma":0.0,"hue_shift":0.0,"opacity":1.0,"saturation":0.0}
]==]
set_settings3(source, "Color Correction", my_json_string)stash "Retro Effects"Click Execute! to save filter state of settings into the stash, click again to restore it
Duplicate Console filter if you want another stash
Note: Built-in filters work differently and you may want to press Defaults first, then restore from stash
settings1 = obs_source_get_private_settings(source)
obs_data_set_int(settings1,"__private__", 7)
obs_apply_private_data(settings1)
obs_data_release(settings1)Those settings are global for a source, in the next Console filter
settings2 = obs_source_get_private_settings(source)
local xc = obs_data_get_int(settings2,"__private__")
print(xc)
obs_data_release(settings2)Read the source code to know exactly how they work in the section bk_obs_api_interactions_functions
execute(command_line, current_directory) - executes command line command without console blinking WINDOWS ONLY
if execute[["C:\full\path\to\python.exe" "C:\Users\YOUR_USERNAME\path\to\program.py" ]] then
error('done') else error('not done') endpp_execute - works roughly same as above, based on util.h from libobs see also
sname(source) - returns source name as string
sceneitem = get_scene_sceneitem(scene_name, scene_item_name) - gets scene item object
click_property(source, property_name) - used for browser source like this: click_property(source, "refreshnocache")
click_property_filter_ffi(source, filter_name, prop_name) - this code will press Execute! button click_property_filter_ffi(source, "Console", "button1") on Console filter that is attached to some source
get the current time (in milliseconds) of the media with get_timing(), length - get_duration()
repeat
play_once(21012,23528)
play_once(13012,15528)
play_once(40012,43528)
play_once(33012,37528)
until falseGet image data of any source as 512x288px, scaled. Enable it first in Show/Hide in properties. Windows, DirectX only. It may hit FPS, check stats
dx_screenshot "Scene 2"
local img = c_u8_p(t.raw_image)
local n = MAX_LEN
print(table.concat({img[0], img[1], img[2], img[3]}, ' '))
print(table.concat({img[n-4], img[n-3], img[n-2], img[n-1]}, ' '))repeat sleep(1)
send_mouse_move_tbs(source, 12, 125)
local get_t = function() return math.random(125, 140) end
for i=12, 200, 6 do
sleep(0.03)
send_mouse_move_tbs(source, i, get_t())
end
until false2 consoles are sending mouse move events into browser sources:

repeat sleep(1)
--send_mouse_move_tbs(source, 95, 80) -- 300x300 browser source
_opts = {x=95, y=80, button_type=MOUSE_LEFT, mouse_up=false, click_count=0}
send_mouse_click_tbs(source, _opts)
-- here might be delay which specifies how long mouse is pressed
_opts.mouse_up, _opts.click_count = true, 2
send_mouse_click_tbs(source, _opts)
until false-- Send tab
send_hotkey_tbs1(source, "OBS_KEY_TAB", false)
send_hotkey_tbs1(source, "OBS_KEY_TAB", true)
-- Send tab with shift modifier
send_hotkey_tbs1(source, "OBS_KEY_TAB", false, {shift=true})
send_hotkey_tbs1(source, "OBS_KEY_TAB", true, {shift=true})
send_hotkey_tbs1(source, "OBS_KEY_RETURN", false)
send_hotkey_tbs1(source, "OBS_KEY_RETURN", true)
-- char_to_obskey (ASCII only)
send_hotkey_tbs1(source, char_to_obskey('j'), false, {shift=true})
send_hotkey_tbs1(source, char_to_obskey('j'), true, {shift=true})
-- or use
send_hotkey_tbs1(source, c2o('j'), false)
send_hotkey_tbs1(source, c2o('j'), true)
-- might work with unicode input
send_hotkey_tbs2(source, 'q', false)
send_hotkey_tbs2(source, 'Đą', false)Caution
This will rewrite all CSS on all browser sources.
patch_bs_js() must be written in the GLOBAL code config.
Restart the program or reload the script, when adding new browser source
In version 4.1.2 patch_bs_js(1) accepts numerical index in the offsets table, defaults to last index when calling without arguments patch_bs_js()
In version 4.2.0+ patch_bs_js(999) now performs a memory scan for the offset
send_js "document.documentElement.style.filter='grayscale(100%)'"
sleep(1.3)
send_js [[document.documentElement.style.filter='invert(100%)']]
sleep(0.8)
send_js [==[
document.body.innerHTML = `
<style>body{margin:0;padding:0}canvas{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}</style>
<canvas id="cvs" width="100" height="100"></canvas> `;
let r = Math.random;
const c = document.getElementById('cvs').getContext('2d');
c.beginPath();c.moveTo(50, 0);c.lineTo(0, 100);c.lineTo(100, 100);
c.fillStyle=`rgb(${r()*256|0},${r()*256|0},${r()*256|0})`;c.fill();
]==]Note
It is possible to patch window.obsstudio.setCurrentScene and establish two way connection between browser source JS context and Lua environment. See also PR439
If you check Auto run then code from this console will be executed automatically when OBS starts
To load from file you need first select which one to load from properties, see "Settings for internal use", then paste this template into text area:
local f = loadfile(t.p1, "t",getfenv(1))
success, result = pcall(f)
if not success then print(result) endprint('restarted') -- expression print_source_name(source)
local delay = 0.5
while true do
local f=load( t.hotreload)
setfenv(f,getfenv(1))
success, result = pcall(f)
if not success then print(result) end
sleep(delay)
endConsole instance with this entries in first and second text area
okay("pipe1")
print('exposing pipe 1')Actual code, write it in second text area in each instance of Console
print(os.time()) print(' start 11111') ; sleep (0.5) ; print(os.time())
print_source_name(source) ; sleep(2) print('done 11111')Another Console instance with same code first text area but different in second
okay("pipe2")
print('exposing pipe 2')And in multiaction text area add this
print(os.time()) print('start ss22222ssssss2ss') ; sleep (2.5 ) ; print(os.time())
print_source_name(source) ; sleep(2) print('done 2222')Main Console instance. This will start pipe1 then after sec pipe2
offer('pipe1')
sleep(1)
offer('pipe2')
okay- exposes actionsoffer- starts actionsstall- pauseforward- continueswitch- pause/continuerecompile- restarts actions
Here is the stuff that is rarely used, presented as API interaction examples.
Toggle visibility of collapsed markdown text
set hotkey for 1; of Audio Input source
repeat
sleep(0.0)
if t.pressed
then obs_source_set_volume(source,0.5)
else
sleep(0.8) obs_source_set_volume(source,0.0)
end
until false local sceneitem = get_scene_sceneitem("Scene 2", sname(source))
repeat
sleep(0.01)
if sceneitem then
obs_sceneitem_set_rot(sceneitem, math.sin(math.random() * 100))
end
until false- Auto run
while true do
sleep(0.03)
obs_source_set_enabled(source, true)
sleep(0.03)
obs_source_set_enabled(source, false)
endrepeat
sleep(0.1)
if t.pressed then print_source_name(source) end
until false (using code from wiki)
Paste into Console or load from file this code:
local source_name = obs_source_get_name(source)
local _name = "YOUR CURRENT SCENE NAME YOU ARE ON"
local sceneitem = get_scene_sceneitem(_name, return_source_name(source))
local amplitude , shaken_sceneitem_angle , frequency = 10, 0, 2
local pos = vec2()
local function update_text(source, text)
local settings = obs_data_create()
obs_data_set_string(settings, "text", text)
obs_source_update(source, settings)
obs_data_release(settings)
end
local function get_position(opts)
return "pos x: " .. opts.x .. " y: " .. opts.y
end
repeat
sleep(0) -- sometimes obs freezes if sceneitem is double clicked
local angle = shaken_sceneitem_angle + amplitude*math.sin(os.clock()*frequency*2*math.pi)
obs_sceneitem_set_rot(sceneitem, angle)
obs_sceneitem_get_pos(sceneitem, pos)
local result = get_position { x = pos.x, y = pos.y }
update_text(source, result)
until falsePrint a source name every second while also print current filters attached to
source in t.tasks, shutdown this task after 10 seconds
function print_filters()
repeat
local filters_list = obs_source_enum_filters(source)
for _, fs in pairs(filters_list) do
print_source_name(fs)
end
source_list_release(filters_list)
sleep(math.random())
until false
end
t.tasks[1] = run(print_filters)
function shutdown_all()
for task, _coro in pairs(t.tasks) do
t.tasks[task] = nil
end
end
t.tasks[2] = run(function()
sleep(10)
shutdown_all()
end)
repeat
sleep(1)
print_source_name(source)
until falseUsing move-transition plugin with its move-audio filter, redirect to t.mv2, then show value of t.mv2 in Script Log
repeat
sleep(0.3)
print(t.mv2)
until falselocal description = 'OBSBasic.StartVirtualCam'
trigger_from_hotkey_callback(description)send_hotkey('OBS_KEY_2', {shift=true})hook_mouse_buttons()
repeat
sleep(0.1)
print(tostring(LMB))
print(tostring(RMB))
until falseRoute audio move value filter from obs-move-transition to change console settings
Attach console to image source, add images to directory with console.lua
In audio move set Input Peak Sample, select Move value[0, 100] 1 base value 1, factor 100
function update_image(state)
local settings = obs_data_create()
obs_data_set_string(settings, "file", script_path() .. state)
obs_source_update(source, settings)
obs_data_release(settings)
end
local skip, scream, normal, silent = false, 30, 20, 20
while true do ::continue::
sleep(0.03)
if t.mv2 > scream then update_image("scream.png") skip = false
sleep(0.5) goto continue end
if t.mv2 > normal then update_image("normal.png") skip = false
sleep(0.3) goto continue
end -- pause for a moment then goto start
if t.mv2 < silent then if skip then goto continue end
update_image("silent.png")
skip = true -- do not update afterwards
end
endexec_py(
[=[def print_hello():
print('hello world')
a = [ x for x in range(10) ][0]
return a
print_hello()
]=])register_on_show(function()
print('on show')
sleep(3)
print('on show exit')
end)- Source code to read - https://github.com/upgradeQ/libre-macros/blob/master/console.lua
- Advanced scene switcher plugin
- Examples & Cheatsheet (python)
- https://lua.org/ , https://luajit.org/
The libre-macros is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. That means that IF users interacting with it remotely(through a network) - they are entitled to source code. And if it is not modified, then you can direct them here, but if you had modified it, you simply have to publish your modifications. The easiest way to do this is to have a public GitHub repository of your fork or create a PR upstream. Otherwise, you will be in violation of the license. The relevant part of the license is under section 13 of the AGPLv3.
