diff --git a/README.md b/README.md
index eff619b..e3cb3dd 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,6 @@
# Description
**obs-libre-macros** 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.2
-# Screenshot
-
-![img](https://i.imgur.com/10IrnOu.png)
# Features
- Attach `Console` to **any** source in real-time
@@ -18,11 +15,11 @@ utilising built-in embedded LuaJIT interpreter, filter UI and function environme
- `sleep(seconds)` - command to pause execution
- `t.tasks` - asynchronous event loop
- `obsffi` - accessed via `obsffi` - native linked library
- - View **all** settings for source, and for filter in that source
- - Send, pause, resume, switch `Console` instances via GLOBAL multi actions pipes
+ - View and change **all** settings for source, and for filter in that source
+ - Send, pause, resume, switch, recompile `Console` instances via GLOBAL multi actions pipes
- Read and write private data, execute Python from Lua, and Lua from Python
+ - Create hollow gaps
- Crossplatform, works offline.
-- View output of `print` in `Script Log`.
```diff
+Browser source keyboard and mouse interaction+
```
@@ -38,6 +35,7 @@ utilising built-in embedded LuaJIT interpreter, filter UI and function environme
- Type some code into the text area.
- Press `Execute!`.
- Sample code: `print(obs_frontend_get_current_scene_collection())`
+- [Examples & Cheatsheet (python)](https://github.com/upgradeQ/OBS-Studio-Python-Scripting-Cheatsheet-obspython-Examples-of-API)
# REPL usage
@@ -78,7 +76,7 @@ if not success then print(result) end
# Hotkeys usage
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;`- it will mutate `t.pressed` state
+ - Second, prefixed with `1;`, `2;`, `3;` - it will mutate `t.pressed`, `t.pressed2`, `t.pressed3` states
# Examples
High frequency blinking source:
@@ -170,7 +168,6 @@ print_source_name(source)
until false
```
Using [move-transition plugin](https://obsproject.com/forum/resources/move-transition.913/) with its move-audio filter, redirect to `t.mv2`, then show value of `t.mv2` in `Script Log`
-- [x] Headphones on
```lua
repeat
sleep(0.3)
@@ -178,14 +175,13 @@ print(t.mv2)
until false
```
-Attach volmeter to source with sound(same as above, but without plugin):
+Attach volmeter to source with sound.Note: this is unstable, and tends to crash.
```lua
volume_level(return_source_name(source))
repeat
sleep(1)
-print(LVL)
-print(NOISE)
+print(t.noise)
until false
```
@@ -275,7 +271,7 @@ _opts.mouse_up, _opts.click_count = true, 2
send_mouse_click_tbs(source, _opts)
until false
```
-## Wheel does not work
+## Wheel does not work with default CSS
```lua
repeat sleep(1)
--send_mouse_move_tbs(source, 95, 80) -- 300x300 browser source
@@ -327,43 +323,79 @@ print('on show exit')
end)
```
## Run multiactions
-`Console` instance with this entry in first and second text area.
+### Example
+`Console` instance with this entries in first and second text area.
```lua
okay("pipe1")
print('exposing pipe 1')
```
Actual code
```lua
- print(os.time()) print('test') ; sleep (3.5 ) ; print(os.time())
-; print('done'); print_source_name(source) ; sleep(2) print(3) print(os.time())
+print(os.time()) print('start') ; sleep (2.5 ) ; print(os.time())
+print_source_name(source) ; sleep(2) print('done'); print(os.time())
```
-Another `Console` instance with same code in second text area
+Another `Console` instance with same code fisrt text area but different in second
```lua
okay("pipe2")
print('exposing pipe 2')
```
-Main `Console` instance, has this code, it will start `pipe1` then after sec `pipe2`
+Main `Console` instance. This will start `pipe1` then after sec `pipe2`
```
offer('pipe1')
sleep(1)
offer('pipe2')
```
-`okay` - exposes actions
-`offer` - starts actions
-`stall` - pause
-`forward` - continue
-`switch` - pause/continue
+- `okay` - exposes actions
+- `offer` - starts actions
+- `stall` - pause
+- `forward` - continue
+- `switch` - pause/continue
+- `recompile` - restarts actions
+
+# Gaps sources
+***Only usable through attaching via filter to scene (not groups)***
+
+- Add gap:
+```lua
+add_gap {x=300,y=500, width = 100, height = 100}
+```
+- Add outer gaps - `add_outer_gap(100)`
+- Resize outer gaps - `resize_outer_gaps(30)`
+- Delete all gaps on scene - `delete_all_gaps()`
+
+# View and set settings
+- `print_settings(source)` - shows all settings
+- `print_settings2(source, filter_name)` - shows all settings for a filter on that source
+- `set_settings2(source, filter_name, opts)` - sets one settings
+- `set_settings3(source, filter_name, json_string)` - sets one settings
+- `set_settings4(source, json_string)` - sets settings for source
+
+Examples:
+
+```lua
+set_settings2(source, "Color Correction", {_type ="double", _field= "gamma", _value= 0})
+```
+
+```lua
+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)
+```
# Contribute
Contributions are welcome!
+
# On the Roadmap
-- Inject custom shader/effect and custom rendering for filter and for source
+- Inject custom shader/effect and custom rendering for filter and for source. There is pure Lua custom shader loader [here](https://github.com/ps0ares/CustomShaders).
- Hook keyboard, hook mouse position for winapi and x11 using cdefs
-- Add predefined templates with examples and multiple text areas to take code from
+- Add predefined templates with examples
+- Add multiple text areas which execute every _n_ seconds and has switch to turn on/off
# License
-The **obs-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, so: If it is **not** modified, then you can direct them [here](https://github.com/upgradeQ/obs-libre-macros), 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.
+The **obs-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](https://github.com/upgradeQ/obs-libre-macros), 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.
diff --git a/console.lua b/console.lua
index ba406af..0ffb91a 100644
--- a/console.lua
+++ b/console.lua
@@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
]]
print(copyleft)
-obs_libre_macros_version = "2.0.0"
+obs_libre_macros_version = "2.1.0"
function script_description()
return copyleft:sub(1, 163) .. 'Version: ' .. obs_libre_macros_version ..
@@ -93,6 +93,7 @@ _OFFER = 111;function offer(pipe_name) SUB[pipe_name] = _OFFER end
_STALL = 222;function stall(pipe_name) SUB[pipe_name] = _STALL end
_FORWARD = 333;function forward(pipe_name) SUB[pipe_name] = _FORWARD end
_SWITCH = 444;function switch(pipe_name) SUB[pipe_name] = _SWITCH end
+_RECOMPILE = 555;function recompile(pipe_name) SUB[pipe_name] = _RECOMPILE end
function executor(ctx, code, loc, name) -- args defined automatically as local
local _ENV = _G
@@ -144,6 +145,8 @@ function SourceDef:create(source)
instance.hotkeys = {}
instance.hk = {}
instance.pressed = false
+ instance.pressed2 = false
+ instance.pressed3 = false
instance.created_hotkeys = false
instance.button_dispatch = false
@@ -231,6 +234,9 @@ function SourceDef:_event_loop(seconds)
elseif num_code == _SWITCH and self.pipe_name == name then
self.is_action_paused = not self.is_action_paused
SUB[name] = 999
+ elseif num_code == _RECOMPILE and self.pipe_name == name then
+ executor(self, self.action_code, "exec_action_code", "actions entry recompiled")
+ SUB[name] = 999
end
end
if self.actions_dispatch then
@@ -332,20 +338,34 @@ function SourceDef:_reg_htk(settings)
self.hotkeys["1;" .. source_name .. ";" .. filter_name] = function(pressed)
self.pressed = pressed
end
+ self.hotkeys["2;" .. source_name .. ";" .. filter_name] = function(pressed)
+ self.pressed2 = pressed
+ end
+ self.hotkeys["3;" .. source_name .. ";" .. filter_name] = function(pressed)
+ self.pressed3 = pressed
+ end
for k, v in pairs(self.hotkeys) do
self.hk[k] = OBS_INVALID_HOTKEY_ID
end
+ function reroute_hotkey_state(k)
+ self.hk[k] = obs_hotkey_register_frontend(k, k, function(pressed)
+ if pressed then
+ self.hotkeys[k](true)
+ else
+ self.hotkeys[k](false)
+ end
+ end)
+ end
+
for k, v in pairs(self.hotkeys) do
if k:sub(1, 1) == "1" then -- starts with 1 symbol
- self.hk[k] = obs_hotkey_register_frontend(k, k, function(pressed)
- if pressed then
- self.hotkeys[k](true)
- else
- self.hotkeys[k](false)
- end
- end)
+ reroute_hotkey_state(k)
+ elseif k:sub(1, 1) == "2" then
+ reroute_hotkey_state(k)
+ elseif k:sub(1, 1) == "3" then
+ reroute_hotkey_state(k)
else
self.hk[k] = obs_hotkey_register_frontend(k, k, function(pressed)
if pressed then
@@ -455,6 +475,34 @@ as_custom_source.output_flags = bit.bor(OBS_SOURCE_VIDEO, OBS_SOURCE_CUSTOM_DRAW
obs_register_source(as_custom_source)
+as_gap_source = {}
+function as_gap_source:create(source)
+ local instance = {}
+ as_gap_source.update(instance, self) -- self = settings and this shows it on screen
+ return instance
+end
+function as_gap_source:get_name() return "Gap source" end
+function as_gap_source:update(settings)
+ self.height = obs_data_get_double(settings, "_height")
+ self.width = obs_data_get_double(settings, "_width")
+end
+function as_gap_source:get_properties()
+ local props = obs_properties_create()
+ obs_properties_add_int_slider(props, "_width", "Width ", 1, 9999, 1)
+ obs_properties_add_int_slider(props, "_height", "Height ", 1, 9999, 1)
+ return props
+end
+function as_gap_source:load(settings)
+ self.height = obs_data_get_double(settings, "_height")
+ self.width = obs_data_get_double(settings, "_width")
+end
+function as_gap_source:get_height() return self.height end
+function as_gap_source:get_width() return self.width end
+as_gap_source.id = "_gap_source"
+as_gap_source.type = OBS_SOURCE_TYPE_SOURCE
+as_gap_source.output_flags = bit.bor(OBS_SOURCE_VIDEO, OBS_SOURCE_CUSTOM_DRAW)
+obs_register_source(as_gap_source)
+
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
-- id - obs keyboard id , c - character , cs - character with shift pressed
qwerty_minimal_keyboard_layout = {
@@ -560,6 +608,38 @@ function print_settings2(source, filter_name)
source_list_release(result)
end
+
+function set_settings2(source, filter_name, opts)
+ local result = obs_source_enum_filters(source)
+ local settings = obs_data_create()
+ for _, f in pairs(result) do
+ if return_source_name(f) == filter_name then
+ _G[("obs_data_set_%s"):format(opts._type)](settings, opts._field, opts._value)
+ obs_source_update(f, settings)
+ obs_data_release(settings)
+ end
+ end
+ source_list_release(result)
+end
+
+function set_settings3(source, filter_name, json_string)
+ local result = obs_source_enum_filters(source)
+ local settings = obs_data_create_from_json(json_string)
+ for _, f in pairs(result) do
+ if return_source_name(f) == filter_name then
+ obs_source_update(f, settings)
+ obs_data_release(settings)
+ end
+ end
+ source_list_release(result)
+end
+
+function set_settings4(source, json_string)
+ local settings = obs_data_create_from_json(json_string)
+ obs_source_update(f, settings)
+ obs_data_release(settings)
+end
+
LMB, RMB, MOUSE_HOOKED = false, false, false
function htk_1_cb(pressed) LMB = pressed end
function htk_2_cb(pressed) RMB = pressed end
@@ -703,60 +783,6 @@ function trigger_from_hotkey_callback(description)
end
end
-ffi.cdef[[
-typedef struct obs_source obs_source_t;
-obs_source_t *obs_get_source_by_name(const char *name);
-void obs_source_release(obs_source_t *source);
-
-enum obs_fader_type {
- OBS_FADER_CUBIC,
- OBS_FADER_IEC,
- OBS_FADER_LOG
-};
-
-typedef struct obs_volmeter obs_volmeter_t;
-
-bool obs_volmeter_attach_source(obs_volmeter_t *volmeter,
- obs_source_t *source);
-
-int MAX_AUDIO_CHANNELS;
-
-obs_volmeter_t *obs_volmeter_create(enum obs_fader_type type);
-
-typedef void (*obs_volmeter_updated_t)(
- void *param, const float magnitude[MAX_AUDIO_CHANNELS],
- const float peak[MAX_AUDIO_CHANNELS],
- const float input_peak[MAX_AUDIO_CHANNELS]);
-
-void obs_volmeter_add_callback(obs_volmeter_t *volmeter,
- obs_volmeter_updated_t callback,
- void *param);
-
-void obs_volmeter_set_peak_meter_type(obs_volmeter_t *volmeter,
- enum obs_peak_meter_type peak_meter_type);
-]]
-
-LVL, NOISE, LOCK = "?", 0, false
-function callback_meter(data, mag, peak, input)
- LVL = 'Volume lvl is :' .. tostring(tonumber(peak[0]))
- NOISE = tonumber(peak[0])
-end
-
-
-jit.off(callback_meter)
-
-function volume_level(source_name)
- if LOCK then return error("cannot attach to more than 1 source") end
- local source = obsffi.obs_get_source_by_name(source_name)
- local volmeter = obsffi.obs_volmeter_create(obsffi.OBS_FADER_LOG)
- -- https://github.com/WarmUpTill/SceneSwitcher/blob/214821b69f5ade803a4919dc9386f6351583faca/src/switch-audio.cpp#L194-L207
- local cb = ffi.cast("obs_volmeter_updated_t", callback_meter)
- obsffi.obs_volmeter_add_callback(volmeter, cb, nil)
- obsffi.obs_volmeter_attach_source(volmeter, source)
- obsffi.obs_source_release(source)
- LOCK = true
-end
-
function read_private_data(data_type, field)
local s = obs_get_private_data()
local result = _G[("obs_data_get_%s"):format(data_type)](s, field)
@@ -813,7 +839,36 @@ function get_code(address)
return proceed, string_, handshake
end
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
---
+
+ffi.cdef[[
+typedef struct obs_source obs_source_t;
+obs_source_t *obs_get_source_by_name(const char *name);
+void obs_source_release(obs_source_t *source);
+
+enum obs_fader_type {
+ OBS_FADER_CUBIC,
+ OBS_FADER_IEC,
+ OBS_FADER_LOG
+};
+typedef struct obs_volmeter obs_volmeter_t;
+bool obs_volmeter_attach_source(obs_volmeter_t *volmeter,
+ obs_source_t *source);
+
+int MAX_AUDIO_CHANNELS;
+
+obs_volmeter_t *obs_volmeter_create(enum obs_fader_type type);
+
+typedef void (*obs_volmeter_updated_t)(
+ void *param, const float magnitude[MAX_AUDIO_CHANNELS],
+ const float peak[MAX_AUDIO_CHANNELS],
+ const float input_peak[MAX_AUDIO_CHANNELS]);
+
+void obs_volmeter_add_callback(obs_volmeter_t *volmeter,
+ obs_volmeter_updated_t callback,
+ void *param);
+]]
+
+
CODE_STORAGE = [===[
local t = __t;
local source = t.source
@@ -865,6 +920,134 @@ end
local function okay(name) t.pipe_name = name end
+local function callback_meter(data, mag, peak, input)
+ t.noise = tonumber(peak[0])
+end
+
+jit.off(callback_meter)
+
+local function volume_level(source_name)
+ if t.volmeter_lock then return error("cannot attach more than one") end
+ local source = obsffi.obs_get_source_by_name(source_name)
+ local volmeter = obsffi.obs_volmeter_create(obsffi.OBS_FADER_LOG)
+ -- https://github.com/WarmUpTill/SceneSwitcher/blob/214821b69f5ade803a4919dc9386f6351583faca/src/switch-audio.cpp#L194-L207
+ local cb = ffi.cast("obs_volmeter_updated_t", callback_meter);cb:set(callback_meter)
+ obsffi.obs_volmeter_add_callback(volmeter, cb, nil)
+ obsffi.obs_volmeter_attach_source(volmeter, source)
+ obsffi.obs_source_release(source)
+ t.volmeter_lock = true
+end
+
+local function get_gap_source(opts)
+ local gap, settings;
+ local w = opts.w
+ local h = opts.h
+ local n = opts.n
+ settings = obs_data_create()
+ obs_data_set_double(settings, "_width", w)
+ obs_data_set_double(settings, "_height", h)
+ gap = obs_source_create("_gap_source", n, settings, nil)
+ return gap, settings
+end
+
+local function __c(source, settings)
+ -- clear current context
+ obs_source_release(source)
+ obs_data_release(settings)
+end
+
+
+local function add_outer_gap(size)
+ size = size or 15
+ if not t.__scene then -- otherwise its crashes
+ t.__scene = obs_scene_from_source(source)
+ end
+ local width = obs_source_get_base_width(source)
+ local height = obs_source_get_base_height(source)
+
+ local rgap, rsettings = get_gap_source({w=size, h=height, n="_right_gap"});
+ local lgap, lsettings = get_gap_source({w=size, h=height, n="_left_gap"});
+ local ugap, usettings = get_gap_source({w=width, h=size, n="_up_gap"});
+ local dgap, dsettings = get_gap_source({w=width, h=size, n="_down_gap"});
+ local rpos, lpos, upos, dpos = vec2(), vec2(), vec2(), vec2()
+ local r = obs_scene_add(t.__scene, rgap); __c(rgap, rsettings)
+ local l = obs_scene_add(t.__scene, lgap); __c(lgap, lsettings)
+ local u = obs_scene_add(t.__scene, ugap); __c(ugap, usettings)
+ local d = obs_scene_add(t.__scene, dgap); __c(dgap, dsettings)
+ lpos.x, lpos.y = 0, 0; obs_sceneitem_set_pos(l, lpos)
+ rpos.x, rpos.y = width - size, 0; obs_sceneitem_set_pos(r, rpos)
+ upos.x, upos.y = 0, 0; obs_sceneitem_set_pos(u, upos)
+ dpos.x, dpos.y = 0, height - size; obs_sceneitem_set_pos(d, dpos)
+end
+
+local function delete_all_gaps()
+ if not t.__scene then -- otherwise its crashes
+ t.__scene = obs_scene_from_source(source)
+ end
+ local items = obs_scene_enum_items(t.__scene)
+ for _, i in pairs(items) do
+ if obs_source_get_unversioned_id(obs_sceneitem_get_source(i)) == '_gap_source' then
+ obs_sceneitem_remove(i)
+ end
+ end
+ sceneitem_list_release(items)
+end
+
+local function _update_gap_base(gs, opts)
+ local settings = obs_source_get_settings(gs)
+ obs_data_set_double(settings, "_width", opts.w)
+ obs_data_set_double(settings, "_height", opts.h)
+ obs_source_update(gs, settings)
+ obs_data_release(settings)
+end
+
+local function _set_gap(gs, gi, size, width, height)
+ local pos = vec2()
+ local name = obs_source_get_name(gs)
+
+ if name == '_right_gap' then
+ _update_gap_base(gs, {w = size, h = height})
+ pos.x, pos.y = width - size, 0; obs_sceneitem_set_pos(gi, pos)
+ elseif name =='_left_gap' then
+ _update_gap_base(gs, {w = size, h = height})
+ pos.x, pos.y = 0, 0; obs_sceneitem_set_pos(gi, pos)
+ elseif name =='_up_gap' then
+ _update_gap_base(gs, {w = width, h = size})
+ pos.x, pos.y = 0, 0; obs_sceneitem_set_pos(gi, pos)
+ elseif name =='_down_gap' then
+ _update_gap_base(gs, {w = width, h = size})
+ pos.x, pos.y = 0, height - size; obs_sceneitem_set_pos(gi, pos)
+ end
+end
+
+local function resize_outer_gaps(size)
+ size = size or 15
+ if not t.__scene then -- otherwise its crashes
+ t.__scene = obs_scene_from_source(source)
+ end
+ local items = obs_scene_enum_items(t.__scene)
+ local width = obs_source_get_base_width(source)
+ local height = obs_source_get_base_height(source)
+ for _, i in pairs(items) do
+ local s = obs_sceneitem_get_source(i)
+ if obs_source_get_unversioned_id(s) == '_gap_source' then
+ _set_gap(s, i, size, width, height)
+ end
+ end
+ sceneitem_list_release(items)
+end
+
+local function add_gap(opts)
+ -- add_gap {x=300,y=500, width = 100, height = 100}
+ if not t.__scene then -- otherwise its crashes
+ t.__scene = obs_scene_from_source(source)
+ end
+ local gap,settings = get_gap_source({w=opts.width, h=opts.height, n="_unnamed_gap"});
+ local item = obs_scene_add(t.__scene, gap); __c(gap, settings)
+ local pos = vec2(); pos.x, pos.y = opts.x, opts.y
+ obs_sceneitem_set_pos(item, pos)
+end
+
-- leave empty new line with 2 spaces
]===]