1

Add filter scripts

This commit is contained in:
Evan Pratten 2024-04-15 10:18:30 -04:00
parent 531c761ab9
commit f214ff72c6
5 changed files with 544 additions and 4 deletions

@ -129,6 +129,32 @@
"deinterlace_field_order": 0,
"monitoring_type": 0,
"private_settings": {}
},
{
"prev_ver": 503382018,
"name": "UnFish/Fish Lens",
"uuid": "9218e1a8-404f-4c23-8cc4-69f7cf5b7373",
"id": "filter-fish-lens",
"versioned_id": "filter-fish-lens",
"settings": {
"fish_power": -0.081000000000000003
},
"mixers": 0,
"sync": 0,
"flags": 0,
"volume": 1.0,
"balance": 0.5,
"enabled": true,
"muted": false,
"push-to-mute": false,
"push-to-mute-delay": 0,
"push-to-talk": false,
"push-to-talk-delay": 0,
"hotkeys": {},
"deinterlace_mode": 0,
"deinterlace_field_order": 0,
"monitoring_type": 0,
"private_settings": {}
}
]
},
@ -143,7 +169,7 @@
"id_counter": 1,
"items": [
{
"name": "Gopro",
"name": "GoPro Hero 3 Silver",
"source_uuid": "8a1155d1-82a8-4565-b8ac-7149e06f67ef",
"visible": true,
"locked": false,
@ -269,7 +295,16 @@
"scaling_off_x": 0.0,
"scaling_off_y": 0.0,
"modules": {
"scripts-tool": [],
"scripts-tool": [
{
"path": "/home/ewpratten/.config/ewconfig/configs/obs-studio/scripts/filter-fish-lens.lua",
"settings": {}
},
{
"path": "/home/ewpratten/.config/ewconfig/configs/obs-studio/scripts/filter-gaussian-blur.lua",
"settings": {}
}
],
"output-timer": {
"streamTimerHours": 0,
"streamTimerMinutes": 0,

@ -0,0 +1,186 @@
-- This is the 2022-05-21 version of "UnFish Lens" (https://obsproject.com/forum/resources/unfish-lens.1532/)
-- I use this script to correct for the distortion of my camera lenses.
-- Install by going to Tools -> Scripts and adding it.
-- Inspired by Corner Pin effect filter v1.1 by khaver
obs = obslua
bit = require("bit")
TEXT_FILTER_NAME = 'UnFish/Fish Lens'
TEXT_FISH_POWER = 'Strength'
SETTING_FISH_POWER = 'fish_power'
source_def = {}
source_def.id = 'filter-fish-lens'
source_def.type = obs.OBS_SOURCE_TYPE_FILTER
source_def.output_flags = bit.bor(obs.OBS_SOURCE_VIDEO)
function set_render_size(filter)
target = obs.obs_filter_get_target(filter.context)
local width, height
if target == nil then
width = 0
height = 0
else
width = obs.obs_source_get_base_width(target)
height = obs.obs_source_get_base_height(target)
end
filter.width = width
filter.height = height
end
source_def.get_name = function()
return TEXT_FILTER_NAME
end
source_def.create = function(settings, source)
filter = {}
filter.params = {}
filter.context = source
set_render_size(filter)
obs.obs_enter_graphics()
filter.effect = obs.gs_effect_create(shader, nil, nil)
if filter.effect ~= nil then
filter.params.fish_power = obs.gs_effect_get_param_by_name(filter.effect, 'fish_power')
filter.params.texture_width = obs.gs_effect_get_param_by_name(filter.effect, 'texture_width')
filter.params.texture_height = obs.gs_effect_get_param_by_name(filter.effect, 'texture_height')
end
obs.obs_leave_graphics()
if filter.effect == nil then
source_def.destroy(filter)
return nil
end
source_def.update(filter, settings)
return filter
end
source_def.destroy = function(filter)
if filter.effect ~= nil then
obs.obs_enter_graphics()
obs.gs_effect_destroy(filter.effect)
obs.obs_leave_graphics()
end
end
source_def.get_width = function(filter)
return filter.width
end
source_def.get_height = function(filter)
return filter.height
end
source_def.update = function(filter, settings)
filter.fish_power = obs.obs_data_get_double(settings, SETTING_FISH_POWER)
set_render_size(filter)
end
source_def.video_render = function(filter, effect)
if not obs.obs_source_process_filter_begin(filter.context, obs.GS_RGBA, obs.OBS_NO_DIRECT_RENDERING) then
return
end
obs.gs_effect_set_float(filter.params.fish_power, filter.fish_power)
obs.gs_effect_set_float(filter.params.texture_width, filter.width)
obs.gs_effect_set_float(filter.params.texture_height, filter.height)
obs.obs_source_process_filter_end(filter.context, filter.effect, filter.width, filter.height)
end
source_def.get_properties = function(settings)
props = obs.obs_properties_create()
obs.obs_properties_add_float_slider(props, SETTING_FISH_POWER, TEXT_FISH_POWER, -1.0, 2.0, 0.001)
return props
end
source_def.get_defaults = function(settings)
obs.obs_data_set_default_double(settings, SETTING_FISH_POWER, -0.18)
end
source_def.video_tick = function(filter, seconds)
set_render_size(filter)
end
function script_description()
return "Adds new video effect filter named '" .. TEXT_FILTER_NAME .. "' to imitate lens distortion"
end
function script_load(settings)
obs.obs_register_source(source_def)
end
shader = [[
// Adaptation by Suslik V
// Based on the Sharpness shader of OBS Studio v27.0.0,
// And the https://github.com/Oncorporation/obs-shaderfilter/
uniform float4x4 ViewProj;
uniform texture2d image;
uniform float fish_power;
uniform float texture_width;
uniform float texture_height;
sampler_state def_sampler {
Filter = Linear;
AddressU = Clamp;
AddressV = Clamp;
};
struct VertData {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
};
VertData VSDefault(VertData v_in)
{
VertData vert_out;
vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj);
vert_out.uv = v_in.uv;
return vert_out;
}
float4 PSDrawBare(VertData v_in) : TARGET
{
int center_x_percent = 50;
int center_y_percent = 50;
float power = fish_power;
float2 uv_pixel_interval;
uv_pixel_interval.x = 1.0 / texture_width;
uv_pixel_interval.y = 1.0 / texture_height;
float2 center_pos = float2(center_x_percent * .01, center_y_percent * .01);
float2 uv = v_in.uv;
if (power >= 0.0001) {
float b = sqrt(dot(center_pos, center_pos));
uv = center_pos + normalize(v_in.uv - center_pos) * tan(distance(center_pos, v_in.uv) * power) * b / tan( b * power);
} else if (power <= -0.0001) {
float b;
if (uv_pixel_interval.x < uv_pixel_interval.y){
b = center_pos.x;
} else {
b = center_pos.y;
}
uv = center_pos + normalize(v_in.uv - center_pos) * atan(distance(center_pos, v_in.uv) * -power * 10.0) * b / atan(-power * b * 10.0);
}
return image.Sample(def_sampler, uv);
}
technique Draw
{
pass
{
vertex_shader = VSDefault(v_in);
pixel_shader = PSDrawBare(v_in);
}
}
]]

@ -0,0 +1,101 @@
uniform float4x4 ViewProj;
uniform texture2d image;
uniform texture2d mask;
uniform bool use_mask;
uniform bool invert_mask;
uniform float2 pixel_size;
uniform float4 kernel0;
uniform float4 kernel1;
uniform float4 kernel2;
uniform float4 kernel3;
uniform int kernel_size;
sampler_state textureSampler {
Filter = Linear;
AddressU = Clamp;
AddressV = Clamp;
};
struct VertDataIn {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
};
struct VertDataOut {
float4 pos : POSITION;
float2 uv : TEXCOORD0;
};
VertDataOut VSDefault(VertDataIn v_in)
{
VertDataOut vert_out;
vert_out.pos = mul(float4(v_in.pos.xyz, 1.0), ViewProj);
vert_out.uv = v_in.uv;
return vert_out;
}
float get_kernel(int x)
{
int abs_x = abs(x);
if(abs_x >= kernel_size)
return 0.0;
int mod_x = abs_x % 4;
switch(abs_x / 4)
{
case 0:
return kernel0[mod_x];
case 1:
return kernel1[mod_x];
case 2:
return kernel2[mod_x];
case 3:
return kernel3[mod_x];
}
return 0.0;
}
float get_kernel_2D(int x, int y)
{
return get_kernel(x) * get_kernel(y);
}
float2 translate_pixel(float2 uv, int x, int y)
{
return uv + float2(x * pixel_size.x, y * pixel_size.y);
}
float4 PassThrough(VertDataOut v_in) : TARGET
{
float4 rgba = float4(0.0, 0.0, 0.0, 0.0);
for(int x = 1 - kernel_size; x < kernel_size; x++)
{
for(int y = 1 - kernel_size; y < kernel_size; y++)
{
rgba += get_kernel_2D(x, y) * image.Sample(textureSampler, translate_pixel(v_in.uv, x, y));
}
}
if(use_mask)
{
float4 original_color = image.Sample(textureSampler, v_in.uv);
float mask_alpha = mask.Sample(textureSampler, v_in.uv).a;
if(invert_mask)
mask_alpha = 1 - mask_alpha;
rgba = rgba * mask_alpha + original_color * (1 - mask_alpha);
}
return rgba;
}
technique Draw
{
pass
{
vertex_shader = VSDefault(v_in);
pixel_shader = PassThrough(v_in);
}
}

@ -0,0 +1,218 @@
-- A slightly modified version of the Gaussian Blur filter from "DarkLink's Script Pack" (https://github.com/WiiPlayer2/obs-scripts)
obs = obslua
bit = require("bit")
MAX_KERNEL_SIZE = 16 -- max.: 3 * MAX_SIGMA
MAX_SIGMA = 10.0
SETTING_KERNEL_SIZE = 'kernel_size'
SETTING_SIGMA = 'sigma'
SETTING_USE_MASK = 'use_mask'
SETTING_INVERT_MASK = 'invert_mask'
SETTING_MASK_IMAGE = 'mask_image'
SETTING_PIXEL_SKIP = 'pixel_skip'
TEXT_KERNEL_SIZE = 'Kernel Size'
TEXT_SIGMA = 'Sigma'
TEXT_USE_MASK = 'Use Blur Mask'
TEXT_INVERT_MASK = 'Invert Blur Mask'
TEXT_MASK = 'Blur Mask Image (Alpha)'
TEXT_PIXEL_SKIP = 'Pixel Skip Factor'
IMAGE_FILTER = 'Images (*.bmp *.jpg *.jpeg *.tga *.gif *.png);; All Files (*.*)'
source_def = {}
source_def.id = 'filter-gaussian-blur'
source_def.type = obs.OBS_SOURCE_TYPE_FILTER
source_def.output_flags = bit.bor(obs.OBS_SOURCE_VIDEO)
function gaussian(sigma, x)
factor = 1.0 / math.sqrt(2 * math.pi * sigma * sigma)
exponent = -1 * (x * x) / (2 * sigma * sigma);
return factor * math.exp(exponent)
end
function set_render_size(filter)
target = obs.obs_filter_get_target(filter.context)
local width, height
if target == nil then
width = 0
height = 0
else
width = obs.obs_source_get_base_width(target)
height = obs.obs_source_get_base_height(target)
end
filter.render_width = width
filter.render_height = height
if width == 0 then
width = 1
end
if height == 0 then
height = 1
end
filter.pixel_size.x = filter.pixel_skip / width
filter.pixel_size.y = filter.pixel_skip / height
end
source_def.get_name = function()
return 'Gaussian Blur'
end
source_def.destroy = function(filter)
if filter.effect ~= nil then
obs.obs_enter_graphics()
obs.gs_effect_destroy(filter.effect)
obs.obs_leave_graphics()
end
end
source_def.update = function(filter, settings)
local kernel_size = obs.obs_data_get_int(settings, SETTING_KERNEL_SIZE)
filter.kernel_size = math.ceil(kernel_size / 2)
filter.sigma = obs.obs_data_get_double(settings, SETTING_SIGMA)
filter.use_mask = obs.obs_data_get_bool(settings, SETTING_USE_MASK)
filter.invert_mask = obs.obs_data_get_bool(settings, SETTING_INVERT_MASK)
filter.pixel_skip = obs.obs_data_get_int(settings, SETTING_PIXEL_SKIP)
local mask_image_path = obs.obs_data_get_string(settings, SETTING_MASK_IMAGE)
local kernel = {}
local sum = 0.0
for i = 1, filter.kernel_size do
kernel[i] = gaussian(filter.sigma, i - 1)
sum = sum + kernel[i] + kernel[i]
end
for i = filter.kernel_size + 1, MAX_KERNEL_SIZE do
kernel[i] = 0.0
end
sum = sum - kernel[1]
local norm = 1.0 / sum
for i = 1, MAX_KERNEL_SIZE do
kernel[i] = kernel[i] * norm
end
obs.vec4_set(filter.kernel0, kernel[1], kernel[2], kernel[3], kernel[4])
obs.vec4_set(filter.kernel1, kernel[5], kernel[6], kernel[7], kernel[8])
obs.vec4_set(filter.kernel2, kernel[9], kernel[10], kernel[11], kernel[12])
obs.vec4_set(filter.kernel3, kernel[13], kernel[14], kernel[15], kernel[16])
if filter.use_mask then
obs.obs_enter_graphics()
obs.gs_image_file_free(filter.mask_image)
obs.obs_leave_graphics()
obs.gs_image_file_init(filter.mask_image, mask_image_path)
obs.obs_enter_graphics()
obs.gs_image_file_init_texture(filter.mask_image)
obs.obs_leave_graphics()
if not filter.mask_image.loaded then
print("failed to load texture " .. mask_image_path);
end
end
set_render_size(filter)
end
source_def.create = function(settings, source)
filter = {}
effect_path = script_path() .. 'filter-gaussian-blur.effect'
filter.context = source
filter.mask_image = obs.gs_image_file()
filter.pixel_size = obs.vec2()
filter.pixel_skip = 1
filter.kernel_size = 4
filter.kernel0 = obs.vec4()
filter.kernel1 = obs.vec4()
filter.kernel2 = obs.vec4()
filter.kernel3 = obs.vec4()
set_render_size(filter)
obs.obs_enter_graphics()
filter.effect = obs.gs_effect_create_from_file(effect_path, nil)
if filter.effect ~= nil then
filter.mask_param = obs.gs_effect_get_param_by_name(filter.effect, 'mask')
filter.use_mask_param = obs.gs_effect_get_param_by_name(filter.effect, 'use_mask')
filter.invert_mask_param = obs.gs_effect_get_param_by_name(filter.effect, 'invert_mask')
filter.pixel_size_param = obs.gs_effect_get_param_by_name(filter.effect, 'pixel_size')
filter.kernel0_param = obs.gs_effect_get_param_by_name(filter.effect, 'kernel0')
filter.kernel1_param = obs.gs_effect_get_param_by_name(filter.effect, 'kernel1')
filter.kernel2_param = obs.gs_effect_get_param_by_name(filter.effect, 'kernel2')
filter.kernel3_param = obs.gs_effect_get_param_by_name(filter.effect, 'kernel3')
filter.kernel_size_param = obs.gs_effect_get_param_by_name(filter.effect, 'kernel_size')
end
obs.obs_leave_graphics()
if filter.effect == nil then
source_def.destroy(filter)
return nil
end
source_def.update(filter, settings)
return filter
end
source_def.get_width = function(filter)
return filter.render_width
end
source_def.get_height = function(filter)
return filter.render_height
end
source_def.video_render = function(filter, effect)
obs.obs_source_process_filter_begin(filter.context, obs.GS_RGBA, obs.OBS_NO_DIRECT_RENDERING)
obs.gs_effect_set_texture(filter.mask_param, filter.mask_image.texture)
obs.gs_effect_set_bool(filter.use_mask_param, filter.use_mask)
obs.gs_effect_set_bool(filter.invert_mask_param, filter.invert_mask)
obs.gs_effect_set_vec2(filter.pixel_size_param, filter.pixel_size)
obs.gs_effect_set_vec4(filter.kernel0_param, filter.kernel0)
obs.gs_effect_set_vec4(filter.kernel1_param, filter.kernel1)
obs.gs_effect_set_vec4(filter.kernel2_param, filter.kernel2)
obs.gs_effect_set_vec4(filter.kernel3_param, filter.kernel3)
obs.gs_effect_set_int(filter.kernel_size_param, filter.kernel_size)
obs.obs_source_process_filter_end(filter.context, filter.effect, filter.render_width, filter.render_height)
end
source_def.get_properties = function(settings)
props = obs.obs_properties_create()
obs.obs_properties_add_int_slider(props, SETTING_KERNEL_SIZE, TEXT_KERNEL_SIZE, 1, MAX_KERNEL_SIZE * 2 - 1, 2)
obs.obs_properties_add_float_slider(props, SETTING_SIGMA, TEXT_SIGMA, 1.0, MAX_SIGMA, 0.01)
obs.obs_properties_add_int_slider(props, SETTING_PIXEL_SKIP, TEXT_PIXEL_SKIP, 1, 128, 1)
obs.obs_properties_add_bool(props, SETTING_USE_MASK, TEXT_USE_MASK)
obs.obs_properties_add_bool(props, SETTING_INVERT_MASK, TEXT_INVERT_MASK)
obs.obs_properties_add_path(props, SETTING_MASK_IMAGE, TEXT_MASK_IMAGE, obs.OBS_PATH_FILE, IMAGE_FILTER, nil)
return props
end
source_def.get_defaults = function(settings)
obs.obs_data_set_default_int(settings, SETTING_KERNEL_SIZE, 3)
obs.obs_data_set_default_double(settings, SETTING_SIGMA, 1.0)
obs.obs_data_set_default_bool(settings, SETTING_USE_MASK, false)
obs.obs_data_set_default_bool(settings, SETTING_INVERT_MASK, false)
obs.obs_data_set_default_int(settings, SETTING_PIXEL_SKIP, 1)
end
source_def.video_tick = function(filter, seconds)
set_render_size(filter)
end
obs.obs_register_source(source_def)

@ -126,8 +126,8 @@ ln -sf $EWCONFIG_ROOT/configs/tmux/.tmux.conf ~/.tmux.conf
# OBS Studio
if [ -d ~/.var/app/com.obsproject.Studio ]; then
# NOTE: OBS Flatpak needs a hardlink to the config file
ln $EWCONFIG_ROOT/configs/obs-studio/basic/scenes/Webcam_Controls.json ~/.var/app/com.obsproject.Studio/config/obs-studio/basic/scenes/Webcam_Controls.json
# NOTE: OBS Flatpak can't work with symlinks
cp $EWCONFIG_ROOT/configs/obs-studio/basic/scenes/Webcam_Controls.json ~/.var/app/com.obsproject.Studio/config/obs-studio/basic/scenes/Webcam_Controls.json
fi
if [ -d ~/.config/obs-studio ]; then
ln -sf $EWCONFIG_ROOT/configs/obs-studio/basic/scenes/Webcam_Controls.json ~/.config/obs-studio/basic/scenes/Webcam_Controls.json