Add S-expression based video effects pipeline with modular effect definitions, constructs, and recipe files. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
131 lines
3.5 KiB
Python
131 lines
3.5 KiB
Python
# /// script
|
|
# requires-python = ">=3.10"
|
|
# dependencies = ["numpy", "opencv-python"]
|
|
# ///
|
|
"""
|
|
@effect neon_glow
|
|
@version 1.0.0
|
|
@author artdag
|
|
|
|
@description
|
|
Neon edge glow effect. Detects edges and applies a glowing colored outline.
|
|
Great for cyberpunk/synthwave aesthetics synced to music.
|
|
|
|
@param glow_radius float
|
|
@range 0 50
|
|
@default 15
|
|
Blur radius for the glow. Bind to bass for pulsing glow.
|
|
|
|
@param glow_intensity float
|
|
@range 0.5 5
|
|
@default 2.0
|
|
Brightness multiplier for the glow.
|
|
|
|
@param edge_low float
|
|
@range 10 200
|
|
@default 50
|
|
Lower threshold for edge detection.
|
|
|
|
@param edge_high float
|
|
@range 50 300
|
|
@default 150
|
|
Upper threshold for edge detection.
|
|
|
|
@param color_r int
|
|
@range 0 255
|
|
@default 0
|
|
Red component of glow color.
|
|
|
|
@param color_g int
|
|
@range 0 255
|
|
@default 255
|
|
Green component of glow color.
|
|
|
|
@param color_b int
|
|
@range 0 255
|
|
@default 255
|
|
Blue component of glow color.
|
|
|
|
@param background float
|
|
@range 0 1
|
|
@default 0.3
|
|
How much of the original image shows through (0 = glow only).
|
|
|
|
@example
|
|
(effect neon_glow :glow_radius 20 :color_r 255 :color_g 0 :color_b 255)
|
|
|
|
@example
|
|
;; Pulsing cyan glow on bass
|
|
(effect neon_glow :glow_radius (bind bass :range [5 30] :transform sqrt))
|
|
"""
|
|
|
|
import numpy as np
|
|
import cv2
|
|
|
|
|
|
def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple:
|
|
"""
|
|
Apply neon glow effect to a video frame.
|
|
|
|
Args:
|
|
frame: Input frame as numpy array (H, W, 3) RGB uint8
|
|
params: Effect parameters
|
|
- glow_radius: blur radius (default 15)
|
|
- glow_intensity: brightness (default 2.0)
|
|
- edge_low: canny low threshold (default 50)
|
|
- edge_high: canny high threshold (default 150)
|
|
- color_r/g/b: glow color (default cyan 0,255,255)
|
|
- background: original visibility (default 0.3)
|
|
state: Persistent state dict (unused)
|
|
|
|
Returns:
|
|
Tuple of (processed_frame, new_state)
|
|
"""
|
|
glow_radius = int(params.get("glow_radius", 15))
|
|
glow_intensity = params.get("glow_intensity", 2.0)
|
|
edge_low = int(params.get("edge_low", 50))
|
|
edge_high = int(params.get("edge_high", 150))
|
|
color_r = int(params.get("color_r", 0))
|
|
color_g = int(params.get("color_g", 255))
|
|
color_b = int(params.get("color_b", 255))
|
|
background = params.get("background", 0.3)
|
|
|
|
h, w = frame.shape[:2]
|
|
color = np.array([color_r, color_g, color_b], dtype=np.float32)
|
|
|
|
# Edge detection
|
|
gray = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
|
|
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
|
|
edges = cv2.Canny(blurred, edge_low, edge_high)
|
|
|
|
# Create colored edge image
|
|
glow_base = np.zeros((h, w, 3), dtype=np.float32)
|
|
for c in range(3):
|
|
glow_base[:, :, c] = edges.astype(np.float32) * (color[c] / 255.0)
|
|
|
|
# Apply blur for glow
|
|
if glow_radius > 0:
|
|
ksize = glow_radius * 2 + 1
|
|
glow = cv2.GaussianBlur(glow_base, (ksize, ksize), 0)
|
|
else:
|
|
glow = glow_base
|
|
|
|
# Intensify
|
|
glow = glow * glow_intensity
|
|
|
|
# Add sharp edges on top
|
|
edge_layer = np.zeros((h, w, 3), dtype=np.float32)
|
|
for c in range(3):
|
|
edge_layer[:, :, c] = edges.astype(np.float32) * (color[c] / 255.0) * 255
|
|
glow = np.maximum(glow, edge_layer)
|
|
|
|
# Blend with original
|
|
if background > 0:
|
|
a = frame.astype(np.float32) / 255.0 * background
|
|
b = glow / 255.0
|
|
result = (1 - (1 - a) * (1 - b)) * 255 # Screen blend
|
|
else:
|
|
result = glow
|
|
|
|
return np.clip(result, 0, 255).astype(np.uint8), state
|