Initial commit: video effects processing system
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>
This commit is contained in:
85
effects/threshold.py
Normal file
85
effects/threshold.py
Normal file
@@ -0,0 +1,85 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = ["numpy"]
|
||||
# ///
|
||||
"""
|
||||
@effect threshold
|
||||
@version 1.0.0
|
||||
@author artdag
|
||||
|
||||
@description
|
||||
Threshold effect. Converts to high-contrast black and white.
|
||||
Creates stark, graphic look by converting grayscale to pure
|
||||
black/white based on a threshold value.
|
||||
|
||||
@param level int
|
||||
@range 0 255
|
||||
@default 128
|
||||
Threshold level. Pixels above = white, below = black.
|
||||
|
||||
@param invert bool
|
||||
@default false
|
||||
Swap black and white.
|
||||
|
||||
@param color_mode string
|
||||
@enum bw color
|
||||
@default bw
|
||||
Output mode:
|
||||
- bw: pure black and white
|
||||
- color: keep original colors where above threshold
|
||||
|
||||
@example
|
||||
(effect threshold :level 100)
|
||||
|
||||
@example
|
||||
;; Beat-reactive threshold
|
||||
(effect threshold :level (bind bass :range [80 180]) :invert true)
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple:
|
||||
"""
|
||||
Apply threshold effect to a video frame.
|
||||
|
||||
Args:
|
||||
frame: Input frame as numpy array (H, W, 3) RGB uint8
|
||||
params: Effect parameters
|
||||
- level: threshold 0-255 (default 128)
|
||||
- invert: swap black/white (default False)
|
||||
- color_mode: bw or color (default bw)
|
||||
state: Persistent state dict
|
||||
|
||||
Returns:
|
||||
Tuple of (processed_frame, new_state)
|
||||
"""
|
||||
level = int(np.clip(params.get("level", 128), 0, 255))
|
||||
invert = params.get("invert", False)
|
||||
color_mode = params.get("color_mode", "bw")
|
||||
|
||||
if state is None:
|
||||
state = {}
|
||||
|
||||
# Convert to grayscale for threshold comparison
|
||||
if len(frame.shape) == 3:
|
||||
gray = np.mean(frame, axis=2)
|
||||
else:
|
||||
gray = frame
|
||||
|
||||
# Apply threshold
|
||||
mask = gray > level
|
||||
|
||||
if invert:
|
||||
mask = ~mask
|
||||
|
||||
if color_mode == "bw":
|
||||
# Pure black and white
|
||||
result = np.where(mask[:, :, np.newaxis], 255, 0).astype(np.uint8)
|
||||
if len(frame.shape) == 3:
|
||||
result = np.repeat(result, frame.shape[2], axis=2)
|
||||
else:
|
||||
# Keep original colors where above threshold
|
||||
result = np.where(mask[:, :, np.newaxis], frame, 0).astype(np.uint8)
|
||||
|
||||
return result, state
|
||||
Reference in New Issue
Block a user