diff --git a/configs/obs-studio/basic/scenes/Webcam_Controls.json b/configs/obs-studio/basic/scenes/Webcam_Controls.json index 1ead003..cbf47bd 100644 --- a/configs/obs-studio/basic/scenes/Webcam_Controls.json +++ b/configs/obs-studio/basic/scenes/Webcam_Controls.json @@ -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, diff --git a/configs/obs-studio/scripts/filter-fish-lens.lua b/configs/obs-studio/scripts/filter-fish-lens.lua new file mode 100644 index 0000000..82f72c6 --- /dev/null +++ b/configs/obs-studio/scripts/filter-fish-lens.lua @@ -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); + } +} +]] diff --git a/configs/obs-studio/scripts/filter-gaussian-blur.effect b/configs/obs-studio/scripts/filter-gaussian-blur.effect new file mode 100644 index 0000000..b0ff241 --- /dev/null +++ b/configs/obs-studio/scripts/filter-gaussian-blur.effect @@ -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); + } +} diff --git a/configs/obs-studio/scripts/filter-gaussian-blur.lua b/configs/obs-studio/scripts/filter-gaussian-blur.lua new file mode 100644 index 0000000..b206658 --- /dev/null +++ b/configs/obs-studio/scripts/filter-gaussian-blur.lua @@ -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) diff --git a/install-linux.sh b/install-linux.sh index b313158..2006613 100644 --- a/install-linux.sh +++ b/install-linux.sh @@ -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