增加新的处理脚本

This commit is contained in:
evan.liu 2026-09-24 08:04:14 +08:00
parent 5cb2875ba6
commit 12764a1a22
2 changed files with 521 additions and 55 deletions

View File

@ -1,6 +1,9 @@
-- HUD Overlay Color Script for OBS (separate Create / Update buttons) -- HUD Overlay Color Script for OBS (separate Create / Update buttons)
-- Green-screen style keying: keeps only the green symbols and removes all other colors (including black) + custom symbol color -- Black-to-transparent keying: the black background becomes fully transparent and
-- Principle: greenness = G - max(R, B); green symbols are positive, black/gray/white backgrounds are ~0 -- symbol pixels are drawn with a pure preset color (default pure green).
-- Principle: subtract a black level (kills the codec noise floor), use max(R, G, B) as
-- alpha (x gain) so anti-aliased edges survive as partial alpha instead of being eroded,
-- then output the preset color x alpha (premultiplied).
-- Requires: obs-shaderfilter plugin (installed) -- Requires: obs-shaderfilter plugin (installed)
-- Install: OBS -> Tools -> Scripts -> + -> select this file -> pick a color -- Install: OBS -> Tools -> Scripts -> + -> select this file -> pick a color
-- "Create Scene" : builds the scene from scratch (deletes and recreates both media sources) -- "Create Scene" : builds the scene from scratch (deletes and recreates both media sources)
@ -8,59 +11,60 @@
obs = obslua obs = obslua
local SETTING_FRONT_PATH = "front_path" local SETTING_FRONT_PATH = "front_path"
local SETTING_BACK_PATH = "back_path" local SETTING_BACK_PATH = "back_path"
local SETTING_COLOR = "hud_color" -- preset color index local SETTING_COLOR = "hud_color" -- preset color index (1 = keep original green)
local SETTING_KEY_LOW = "key_low" local SETTING_BLACK_LEVEL = "black_level"
local SETTING_KEY_HIGH = "key_high" local SETTING_GAIN = "alpha_gain"
local SETTING_OPACITY = "opacity" local SETTING_OPACITY = "opacity"
local SCENE_NAME = "HUD Overlay Color" local SCENE_NAME = "HUD Overlay Color"
local FRONT_NAME = "HUD - Front Color" local FRONT_NAME = "HUD - Front Color"
local BACK_NAME = "Center IG - Back Color" local BACK_NAME = "Center IG - Back Color"
local FILTER_NAME = "HUD Colorize" local FILTER_NAME = "HUD Colorize"
-- Preset colors (ordered from strongest to weakest contrast against a teal background) -- Preset colors; index 1 is pure green (the original HUD color)
local COLOR_PRESETS = { local COLOR_PRESETS = {
{ name = "Red (recommended, complementary to teal)", r = 1.0, g = 0.0, b = 0.0 }, { name = "Green (original HUD color)", r = 0.0, g = 1.0, b = 0.0 },
{ name = "Magenta", r = 1.0, g = 0.0, b = 1.0 }, { name = "Red (recommended, complementary to teal)", r = 1.0, g = 0.0, b = 0.0 },
{ name = "Orange", r = 1.0, g = 0.5, b = 0.0 }, { name = "Magenta", r = 1.0, g = 0.0, b = 1.0 },
{ name = "Yellow", r = 1.0, g = 1.0, b = 0.0 }, { name = "Orange", r = 1.0, g = 0.5, b = 0.0 },
{ name = "White", r = 1.0, g = 1.0, b = 1.0 }, { name = "Yellow", r = 1.0, g = 1.0, b = 0.0 },
{ name = "Green (original color)", r = 0.0, g = 1.0, b = 0.0 }, { name = "White", r = 1.0, g = 1.0, b = 1.0 },
{ name = "Bright green (neon green)", r = 0.25, g = 1.0, b = 0.1 }, { name = "Bright green (neon green)", r = 0.25, g = 1.0, b = 0.1 },
{ name = "Black (strong contrast on light backgrounds)", r = 0.0, g = 0.0, b = 0.0 }, { name = "Black (strong contrast on light backgrounds)", r = 0.0, g = 0.0, b = 0.0 },
} }
local front_path = "" local front_path = ""
local back_path = "" local back_path = ""
local color_index = 1 local color_index = 1
local key_low = 0.02 local black_level = 0.055 -- 14/255, measured noise floor of the HUD capture
local key_high = 0.20 local alpha_gain = 1.35
local opacity = 1.0 local opacity = 1.0
local fit_retries = 0 local fit_retries = 0
-- Green-preserving keying + recolor shader: -- Black-key shader:
-- greenness = G - max(R, B); only greenish pixels are positive -- h = max(px - black_level, 0) -> drop the noise floor, pure black becomes 0
-- alpha = smoothstep(key_low, key_high, greenness); soft edges preserve the anti-aliased transition -- k = max(h.r, h.g, h.b) -> brightness of the symbol at this pixel
-- Color is forced to the specified HUD color (premultiplied output) -- a = clamp(k * alpha_gain) -> soft alpha; anti-aliased edges keep partial alpha
-- Output is premultiplied: preset color x alpha (symbol pixels are pure HUD color)
local SHADER_TEXT = [==[ local SHADER_TEXT = [==[
uniform float key_low < uniform float black_level <
string label = "Key low"; string label = "Black level";
string widget_type = "slider"; string widget_type = "slider";
float minimum = 0.0; float minimum = 0.0;
float maximum = 0.5; float maximum = 0.3;
float step = 0.001; float step = 0.001;
> = 0.02; > = 0.055;
uniform float key_high < uniform float alpha_gain <
string label = "Key high"; string label = "Alpha gain";
string widget_type = "slider"; string widget_type = "slider";
float minimum = 0.01; float minimum = 0.5;
float maximum = 1.0; float maximum = 4.0;
float step = 0.005; float step = 0.01;
> = 0.20; > = 1.35;
uniform float hud_r < uniform float hud_r <
string label = "HUD color R"; string label = "HUD color R";
@ -68,7 +72,7 @@ uniform float hud_r <
float minimum = 0.0; float minimum = 0.0;
float maximum = 1.0; float maximum = 1.0;
float step = 0.01; float step = 0.01;
> = 1.0; > = 0.0;
uniform float hud_g < uniform float hud_g <
string label = "HUD color G"; string label = "HUD color G";
@ -76,7 +80,7 @@ uniform float hud_g <
float minimum = 0.0; float minimum = 0.0;
float maximum = 1.0; float maximum = 1.0;
float step = 0.01; float step = 0.01;
> = 0.0; > = 1.0;
uniform float hud_b < uniform float hud_b <
string label = "HUD color B"; string label = "HUD color B";
@ -101,13 +105,16 @@ float4 mainImage(VertData v_in) : TARGET
// The OBS filter chain input is premultiplied; un-premultiply first (media sources have alpha=1, so usually no effect) // The OBS filter chain input is premultiplied; un-premultiply first (media sources have alpha=1, so usually no effect)
px.rgb *= (px.a > 0.0) ? (1.0 / px.a) : 0.0; px.rgb *= (px.a > 0.0) ? (1.0 / px.a) : 0.0;
// Greenness: only green symbols are positive; black/gray/white backgrounds are ~0 // Subtract the black level: pure black (and the codec noise floor) becomes exactly 0
float greenness = px.g - max(px.r, px.b); float3 h = max(px.rgb - black_level, 0.0);
// Soft-threshold keying: fully transparent below key_low, fully opaque above key_high // Brightness key: 0 for black, ~1.0 for pure green symbols
float a = smoothstep(key_low, max(key_high, key_low + 0.001), greenness) * opacity; float k = max(h.r, max(h.g, h.b));
// Premultiplied output: specified color x alpha // Soft alpha with gain: thin strokes become more solid without eroding their edges
float a = clamp(k * alpha_gain, 0.0, 1.0) * opacity;
// Premultiplied output: pure HUD color x alpha
return float4(float3(hud_r, hud_g, hud_b) * a, a); return float4(float3(hud_r, hud_g, hud_b) * a, a);
} }
]==] ]==]
@ -156,8 +163,8 @@ local function apply_key_filter(src)
local fs = obs.obs_data_create() local fs = obs.obs_data_create()
obs.obs_data_set_bool(fs, "from_file", false) obs.obs_data_set_bool(fs, "from_file", false)
obs.obs_data_set_string(fs, "shader_text", SHADER_TEXT) obs.obs_data_set_string(fs, "shader_text", SHADER_TEXT)
obs.obs_data_set_double(fs, "key_low", key_low) obs.obs_data_set_double(fs, "black_level", black_level)
obs.obs_data_set_double(fs, "key_high", key_high) obs.obs_data_set_double(fs, "alpha_gain", alpha_gain)
obs.obs_data_set_double(fs, "hud_r", preset.r) obs.obs_data_set_double(fs, "hud_r", preset.r)
obs.obs_data_set_double(fs, "hud_g", preset.g) obs.obs_data_set_double(fs, "hud_g", preset.g)
obs.obs_data_set_double(fs, "hud_b", preset.b) obs.obs_data_set_double(fs, "hud_b", preset.b)
@ -339,18 +346,19 @@ end
------------------------------------------------------------ ------------------------------------------------------------
function script_description() function script_description()
return "Green-screen style keying: keeps only the green symbols and removes all other colors (including black).\n" .. return "Black-to-transparent keying: the black background becomes fully transparent and symbol pixels are drawn with a pure preset color (default pure green).\n" ..
"Principle: greenness = G - max(R, B), which is less likely to pick up background noise than the old 'use the G channel as alpha' approach.\n" .. "alpha = max(R, G, B) after subtracting a black level, so anti-aliased edges survive as partial alpha instead of being eroded.\n" ..
"Compared with the old greenness keying, thin lines and small text no longer fade or break apart.\n" ..
"\"Create Scene\": builds the scene from scratch (deletes and recreates both media sources).\n" .. "\"Create Scene\": builds the scene from scratch (deletes and recreates both media sources).\n" ..
"\"Update Scene\": updates the existing scene in place (video paths, symbol color, keying parameters).\n" .. "\"Update Scene\": updates the existing scene in place (video paths, symbol color, keying parameters).\n" ..
"The filter panel also has HUD color R/G/B sliders for fine-tuning to any color." "The filter panel also has Black level / Alpha gain / HUD color R/G/B sliders for fine-tuning."
end end
function script_properties() function script_properties()
local props = obs.obs_properties_create() local props = obs.obs_properties_create()
obs.obs_properties_add_path(props, SETTING_FRONT_PATH, obs.obs_properties_add_path(props, SETTING_FRONT_PATH,
"Foreground video (HUD, green symbols)", obs.OBS_PATH_FILE, "Foreground video (HUD, green symbols on black)", obs.OBS_PATH_FILE,
"Video files (*.avi *.mp4 *.mov *.mkv *.webm);;All files (*.*)", "") "Video files (*.avi *.mp4 *.mov *.mkv *.webm);;All files (*.*)", "")
obs.obs_properties_add_path(props, SETTING_BACK_PATH, obs.obs_properties_add_path(props, SETTING_BACK_PATH,
@ -363,10 +371,10 @@ function script_properties()
obs.obs_property_list_add_int(clist, c.name, i) obs.obs_property_list_add_int(clist, c.name, i)
end end
obs.obs_properties_add_float_slider(props, SETTING_KEY_LOW, obs.obs_properties_add_float_slider(props, SETTING_BLACK_LEVEL,
"Green lower bound (increase if noise/color artifacts remain)", 0.0, 0.5, 0.001) "Black level (increase if a dark halo/noise remains around the symbols)", 0.0, 0.3, 0.001)
obs.obs_properties_add_float_slider(props, SETTING_KEY_HIGH, obs.obs_properties_add_float_slider(props, SETTING_GAIN,
"Green upper bound (decrease if thin lines fade or edges get too soft)", 0.01, 1.0, 0.005) "Alpha gain (increase if the symbols look too transparent over bright sky)", 0.5, 4.0, 0.01)
obs.obs_properties_add_float_slider(props, SETTING_OPACITY, obs.obs_properties_add_float_slider(props, SETTING_OPACITY,
"Overall opacity", 0.0, 1.0, 0.01) "Overall opacity", 0.0, 1.0, 0.01)
@ -382,8 +390,8 @@ function script_defaults(settings)
obs.obs_data_set_default_string(settings, SETTING_BACK_PATH, obs.obs_data_set_default_string(settings, SETTING_BACK_PATH,
"D:\\Test Pictures + Video\\Center IG - Back Videos.avi") "D:\\Test Pictures + Video\\Center IG - Back Videos.avi")
obs.obs_data_set_default_int(settings, SETTING_COLOR, 1) obs.obs_data_set_default_int(settings, SETTING_COLOR, 1)
obs.obs_data_set_default_double(settings, SETTING_KEY_LOW, 0.02) obs.obs_data_set_default_double(settings, SETTING_BLACK_LEVEL, 0.055)
obs.obs_data_set_default_double(settings, SETTING_KEY_HIGH, 0.20) obs.obs_data_set_default_double(settings, SETTING_GAIN, 1.35)
obs.obs_data_set_default_double(settings, SETTING_OPACITY, 1.0) obs.obs_data_set_default_double(settings, SETTING_OPACITY, 1.0)
end end
@ -391,7 +399,7 @@ function script_update(settings)
front_path = obs.obs_data_get_string(settings, SETTING_FRONT_PATH) front_path = obs.obs_data_get_string(settings, SETTING_FRONT_PATH)
back_path = obs.obs_data_get_string(settings, SETTING_BACK_PATH) back_path = obs.obs_data_get_string(settings, SETTING_BACK_PATH)
color_index = obs.obs_data_get_int(settings, SETTING_COLOR) color_index = obs.obs_data_get_int(settings, SETTING_COLOR)
key_low = obs.obs_data_get_double(settings, SETTING_KEY_LOW) black_level = obs.obs_data_get_double(settings, SETTING_BLACK_LEVEL)
key_high = obs.obs_data_get_double(settings, SETTING_KEY_HIGH) alpha_gain = obs.obs_data_get_double(settings, SETTING_GAIN)
opacity = obs.obs_data_get_double(settings, SETTING_OPACITY) opacity = obs.obs_data_get_double(settings, SETTING_OPACITY)
end end

View File

@ -0,0 +1,458 @@
-- HUD Overlay Outline Script for OBS (separate Create / Update buttons)
-- New approach: detect the green symbols first, then draw them in pure green with a
-- dark green outline, so they stay readable over ANY background (bright sky, green terrain, clouds).
-- Principle:
-- 1. symbol detection: subtract a black level, alpha = max(R, G, B) x gain,
-- multiplied by a greenness factor (G - max(R, B)) to reject gray/white noise
-- 2. outline: sample the symbol alpha in a ring around each pixel; pixels that are
-- near a symbol but not part of it get a dark outline underneath the green fill
-- 3. premultiplied output: pure green x symbol alpha + outline color x outline alpha
-- Requires: obs-shaderfilter plugin (installed)
-- Install: OBS -> Tools -> Scripts -> + -> select this file
-- "Create Scene" : builds the scene from scratch (deletes and recreates both media sources)
-- "Update Scene" : updates the existing scene in place (video paths, color, keying parameters)
obs = obslua
local SETTING_FRONT_PATH = "front_path"
local SETTING_BACK_PATH = "back_path"
local SETTING_BLACK_LEVEL = "black_level"
local SETTING_GAIN = "alpha_gain"
local SETTING_GREEN_THRESH = "green_thresh"
local SETTING_OUT_WIDTH = "outline_width"
local SETTING_OUT_ALPHA = "outline_alpha"
local SETTING_OPACITY = "opacity"
local SCENE_NAME = "HUD Overlay Outline"
local FRONT_NAME = "HUD - Front Outline"
local BACK_NAME = "Center IG - Back Outline"
local FILTER_NAME = "HUD Outline"
local front_path = ""
local back_path = ""
local black_level = 0.055 -- 14/255, measured noise floor of the HUD capture
local alpha_gain = 1.35
local green_thresh = 0.10 -- greenness (G - max(R,B)) needed to count as a green symbol
local outline_width = 2.0 -- outline thickness in source pixels
local outline_alpha = 0.9
local opacity = 1.0
local fit_retries = 0
-- Symbol-detect + outline shader:
-- sym_alpha(uv): black level subtraction -> brightness key x gain -> greenness gate
-- main: symbol = pure green x sym_alpha; ring of neighbor sym_alpha forms the outline
local SHADER_TEXT = [==[
uniform float black_level <
string label = "Black level";
string widget_type = "slider";
float minimum = 0.0;
float maximum = 0.3;
float step = 0.001;
> = 0.055;
uniform float alpha_gain <
string label = "Alpha gain";
string widget_type = "slider";
float minimum = 0.5;
float maximum = 4.0;
float step = 0.01;
> = 1.35;
uniform float green_thresh <
string label = "Green threshold";
string widget_type = "slider";
float minimum = 0.01;
float maximum = 0.5;
float step = 0.005;
> = 0.10;
uniform float outline_width <
string label = "Outline width (px)";
string widget_type = "slider";
float minimum = 0.0;
float maximum = 8.0;
float step = 0.5;
> = 2.0;
uniform float outline_alpha <
string label = "Outline strength";
string widget_type = "slider";
float minimum = 0.0;
float maximum = 1.0;
float step = 0.01;
> = 0.9;
uniform float hud_r <
string label = "Symbol color R";
string widget_type = "slider";
float minimum = 0.0;
float maximum = 1.0;
float step = 0.01;
> = 0.0;
uniform float hud_g <
string label = "Symbol color G";
string widget_type = "slider";
float minimum = 0.0;
float maximum = 1.0;
float step = 0.01;
> = 1.0;
uniform float hud_b <
string label = "Symbol color B";
string widget_type = "slider";
float minimum = 0.0;
float maximum = 1.0;
float step = 0.01;
> = 0.0;
uniform float outline_brightness <
string label = "Outline brightness";
string widget_type = "slider";
float minimum = 0.0;
float maximum = 1.0;
float step = 0.01;
> = 0.35;
uniform float opacity <
string label = "Opacity";
string widget_type = "slider";
float minimum = 0.0;
float maximum = 1.0;
float step = 0.01;
> = 1.0;
// Symbol alpha at a given UV: 1 = solid green symbol, 0 = background/noise
float sym_alpha(float2 uv)
{
float4 px = image.Sample(textureSampler, uv);
px.rgb *= (px.a > 0.0) ? (1.0 / px.a) : 0.0;
// drop the codec noise floor; pure black becomes exactly 0
float3 h = max(px.rgb - black_level, 0.0);
// brightness key
float k = max(h.r, max(h.g, h.b));
// greenness gate: green symbols pass, gray/white noise is rejected
float gn = h.g - max(h.r, h.b);
float gf = clamp(gn / green_thresh, 0.0, 1.0);
return clamp(k * alpha_gain, 0.0, 1.0) * gf;
}
float4 mainImage(VertData v_in) : TARGET
{
float a_sym = sym_alpha(v_in.uv);
// Ring of 8 neighbor taps at outline_width distance -> dilated symbol alpha
float2 d = uv_pixel_interval * outline_width;
float2 dg = d * 0.7071;
float ring = 0.0;
ring = max(ring, sym_alpha(v_in.uv + float2( d.x, 0.0)));
ring = max(ring, sym_alpha(v_in.uv + float2(-d.x, 0.0)));
ring = max(ring, sym_alpha(v_in.uv + float2( 0.0, d.y)));
ring = max(ring, sym_alpha(v_in.uv + float2( 0.0, -d.y)));
ring = max(ring, sym_alpha(v_in.uv + float2( dg.x, dg.y)));
ring = max(ring, sym_alpha(v_in.uv + float2(-dg.x, dg.y)));
ring = max(ring, sym_alpha(v_in.uv + float2( dg.x, -dg.y)));
ring = max(ring, sym_alpha(v_in.uv + float2(-dg.x, -dg.y)));
// Outline only where the symbol itself is not already opaque
float a_out = min(ring * outline_alpha, 1.0 - a_sym);
float a_sym_o = a_sym * opacity;
float a_out_o = a_out * opacity;
// Premultiplied: pure symbol color over a dimmer pure-green outline
float3 rgb = float3(hud_r, hud_g, hud_b) * a_sym_o
+ float3(hud_r, hud_g, hud_b) * outline_brightness * a_out_o;
return float4(rgb, a_sym_o + a_out_o);
}
]==]
------------------------------------------------------------
-- Utility functions
------------------------------------------------------------
local function remove_source_by_name(name)
local s = obs.obs_get_source_by_name(name)
if s ~= nil then
obs.obs_source_remove(s)
obs.obs_source_release(s)
end
end
local function create_media_source(name, path)
local s = obs.obs_data_create()
obs.obs_data_set_string(s, "local_file", path)
obs.obs_data_set_bool(s, "is_local_file", true)
obs.obs_data_set_bool(s, "looping", true)
obs.obs_data_set_bool(s, "restart_on_activate", true)
obs.obs_data_set_bool(s, "close_when_inactive", false)
local src = obs.obs_source_create("ffmpeg_source", name, s, nil)
obs.obs_data_release(s)
return src
end
-- Point an existing media source at a new file without recreating it
local function update_media_source_path(src, path)
local s = obs.obs_source_get_settings(src)
obs.obs_data_set_string(s, "local_file", path)
obs.obs_source_update(src, s)
obs.obs_data_release(s)
end
local function apply_key_filter(src)
local old = obs.obs_source_get_filter_by_name(src, FILTER_NAME)
if old ~= nil then
obs.obs_source_filter_remove(src, old)
obs.obs_source_release(old)
end
local fs = obs.obs_data_create()
obs.obs_data_set_bool(fs, "from_file", false)
obs.obs_data_set_string(fs, "shader_text", SHADER_TEXT)
obs.obs_data_set_double(fs, "black_level", black_level)
obs.obs_data_set_double(fs, "alpha_gain", alpha_gain)
obs.obs_data_set_double(fs, "green_thresh", green_thresh)
obs.obs_data_set_double(fs, "outline_width", outline_width)
obs.obs_data_set_double(fs, "outline_alpha", outline_alpha)
obs.obs_data_set_double(fs, "opacity", opacity)
-- First check whether the obs-shaderfilter plugin is loaded
if obs.obs_source_get_display_name("shader_filter") == nil then
obs.obs_data_release(fs)
obs.script_log(obs.LOG_WARNING,
"HUD Overlay Outline: obs-shaderfilter plugin not detected; keying was not applied. Please fully quit OBS (including the system tray) and restart it.")
return
end
local f = obs.obs_source_create("shader_filter", FILTER_NAME, fs, nil)
obs.obs_data_release(fs)
if f ~= nil then
obs.obs_source_filter_add(src, f)
obs.obs_source_release(f)
end
end
local function fit_items_to_canvas()
local vi = obs.obs_video_info()
if obs.obs_get_video_info(vi) == false then
return false
end
local cw, ch = vi.base_width, vi.base_height
local scene_src = obs.obs_get_source_by_name(SCENE_NAME)
if scene_src == nil then
return false
end
local scene = obs.obs_scene_from_source(scene_src)
local items = obs.obs_scene_enum_items(scene)
local all_ready = true
for _, item in ipairs(items) do
local s = obs.obs_sceneitem_get_source(item)
local w = obs.obs_source_get_width(s)
local h = obs.obs_source_get_height(s)
if w > 0 and h > 0 then
local scale = math.min(cw / w, ch / h)
local sc = obs.vec2()
sc.x = scale
sc.y = scale
obs.obs_sceneitem_set_scale(item, sc)
local pos = obs.vec2()
pos.x = (cw - w * scale) / 2
pos.y = (ch - h * scale) / 2
obs.obs_sceneitem_set_pos(item, pos)
-- Use Lanczos when upscaling line-art footage; sharper than the default bilinear
if obs.obs_sceneitem_set_scale_filter ~= nil then
obs.obs_sceneitem_set_scale_filter(item, obs.OBS_SCALE_LANCZOS or 4)
end
else
all_ready = false
end
end
obs.sceneitem_list_release(items)
obs.obs_source_release(scene_src)
return all_ready
end
local function fit_retry()
fit_retries = fit_retries - 1
if fit_items_to_canvas() or fit_retries <= 0 then
obs.timer_remove(fit_retry)
end
end
local function schedule_fit()
fit_retries = 10
obs.timer_remove(fit_retry)
obs.timer_add(fit_retry, 500)
end
------------------------------------------------------------
-- Create scene (from scratch)
------------------------------------------------------------
local function create_scene()
if front_path == "" or back_path == "" then
obs.script_log(obs.LOG_WARNING, "HUD Overlay Outline: please set the foreground and background video paths first")
return
end
local scene = nil
local scene_src = obs.obs_get_source_by_name(SCENE_NAME)
if scene_src ~= nil then
scene = obs.obs_scene_from_source(scene_src)
else
scene = obs.obs_scene_create(SCENE_NAME)
scene_src = obs.obs_scene_get_source(scene)
end
remove_source_by_name(FRONT_NAME)
remove_source_by_name(BACK_NAME)
local back = create_media_source(BACK_NAME, back_path)
obs.obs_scene_add(scene, back)
local front = create_media_source(FRONT_NAME, front_path)
apply_key_filter(front)
obs.obs_scene_add(scene, front)
obs.obs_source_release(back)
obs.obs_source_release(front)
obs.obs_frontend_set_current_scene(scene_src)
obs.obs_source_release(scene_src)
schedule_fit()
obs.script_log(obs.LOG_INFO, "HUD Overlay Outline: scene created")
end
------------------------------------------------------------
-- Update scene (in place, without recreating sources)
------------------------------------------------------------
local function update_scene()
if front_path == "" or back_path == "" then
obs.script_log(obs.LOG_WARNING, "HUD Overlay Outline: please set the foreground and background video paths first")
return
end
local scene_src = obs.obs_get_source_by_name(SCENE_NAME)
if scene_src == nil then
obs.script_log(obs.LOG_WARNING,
"HUD Overlay Outline: scene \"" .. SCENE_NAME .. "\" does not exist yet; please use \"Create Scene\" first")
return
end
local back = obs.obs_get_source_by_name(BACK_NAME)
local front = obs.obs_get_source_by_name(FRONT_NAME)
if back == nil or front == nil then
if back ~= nil then obs.obs_source_release(back) end
if front ~= nil then obs.obs_source_release(front) end
obs.obs_source_release(scene_src)
obs.script_log(obs.LOG_WARNING,
"HUD Overlay Outline: media sources are missing; please use \"Create Scene\" to rebuild the scene")
return
end
-- Update video files in place
update_media_source_path(back, back_path)
update_media_source_path(front, front_path)
-- Re-apply the keying filter with the current keying parameters
apply_key_filter(front)
obs.obs_source_release(back)
obs.obs_source_release(front)
obs.obs_frontend_set_current_scene(scene_src)
obs.obs_source_release(scene_src)
schedule_fit()
obs.script_log(obs.LOG_INFO, "HUD Overlay Outline: scene updated")
end
local function on_create_clicked(props, p)
create_scene()
return true
end
local function on_update_clicked(props, p)
update_scene()
return true
end
------------------------------------------------------------
-- Script UI and settings
------------------------------------------------------------
function script_description()
return "Detects the green HUD symbols, then draws them in pure green with a dark green outline,\n" ..
"so they stay readable over any background (bright sky, green terrain, clouds).\n" ..
"Symbol detection: alpha = max(R, G, B) x gain after black-level subtraction, gated by greenness G - max(R, B) to reject noise.\n" ..
"\"Create Scene\": builds the scene from scratch (deletes and recreates both media sources).\n" ..
"\"Update Scene\": updates the existing scene in place (video paths, keying parameters).\n" ..
"The filter panel has sliders for symbol color, outline width, strength and brightness."
end
function script_properties()
local props = obs.obs_properties_create()
obs.obs_properties_add_path(props, SETTING_FRONT_PATH,
"Foreground video (HUD, green symbols on black)", obs.OBS_PATH_FILE,
"Video files (*.avi *.mp4 *.mov *.mkv *.webm);;All files (*.*)", "")
obs.obs_properties_add_path(props, SETTING_BACK_PATH,
"Background video (bottom layer)", obs.OBS_PATH_FILE,
"Video files (*.avi *.mp4 *.mov *.mkv *.webm);;All files (*.*)", "")
obs.obs_properties_add_float_slider(props, SETTING_BLACK_LEVEL,
"Black level (increase if a dark halo/noise remains)", 0.0, 0.3, 0.001)
obs.obs_properties_add_float_slider(props, SETTING_GAIN,
"Alpha gain (increase if symbols look too transparent)", 0.5, 4.0, 0.01)
obs.obs_properties_add_float_slider(props, SETTING_GREEN_THRESH,
"Green threshold (increase if non-green noise appears)", 0.01, 0.5, 0.005)
obs.obs_properties_add_float_slider(props, SETTING_OUT_WIDTH,
"Outline width (px)", 0.0, 8.0, 0.5)
obs.obs_properties_add_float_slider(props, SETTING_OUT_ALPHA,
"Outline strength", 0.0, 1.0, 0.01)
obs.obs_properties_add_float_slider(props, SETTING_OPACITY,
"Overall opacity", 0.0, 1.0, 0.01)
obs.obs_properties_add_button(props, "create_btn", "Create Scene", on_create_clicked)
obs.obs_properties_add_button(props, "update_btn", "Update Scene", on_update_clicked)
return props
end
function script_defaults(settings)
obs.obs_data_set_default_string(settings, SETTING_FRONT_PATH,
"D:\\Test Pictures + Video\\HUD - Front Video.avi")
obs.obs_data_set_default_string(settings, SETTING_BACK_PATH,
"D:\\Test Pictures + Video\\Center IG - Back Videos.avi")
obs.obs_data_set_default_double(settings, SETTING_BLACK_LEVEL, 0.055)
obs.obs_data_set_default_double(settings, SETTING_GAIN, 1.35)
obs.obs_data_set_default_double(settings, SETTING_GREEN_THRESH, 0.10)
obs.obs_data_set_default_double(settings, SETTING_OUT_WIDTH, 2.0)
obs.obs_data_set_default_double(settings, SETTING_OUT_ALPHA, 0.9)
obs.obs_data_set_default_double(settings, SETTING_OPACITY, 1.0)
end
function script_update(settings)
front_path = obs.obs_data_get_string(settings, SETTING_FRONT_PATH)
back_path = obs.obs_data_get_string(settings, SETTING_BACK_PATH)
black_level = obs.obs_data_get_double(settings, SETTING_BLACK_LEVEL)
alpha_gain = obs.obs_data_get_double(settings, SETTING_GAIN)
green_thresh = obs.obs_data_get_double(settings, SETTING_GREEN_THRESH)
outline_width = obs.obs_data_get_double(settings, SETTING_OUT_WIDTH)
outline_alpha = obs.obs_data_get_double(settings, SETTING_OUT_ALPHA)
opacity = obs.obs_data_get_double(settings, SETTING_OPACITY)
end