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:
108
effects/wave.py
Normal file
108
effects/wave.py
Normal file
@@ -0,0 +1,108 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = ["numpy", "opencv-python"]
|
||||
# ///
|
||||
"""
|
||||
@effect wave
|
||||
@version 1.0.0
|
||||
@author artdag
|
||||
|
||||
@description
|
||||
Sine wave displacement distortion. Creates wavy, liquid-like warping.
|
||||
Great for psychedelic and underwater effects synced to music.
|
||||
|
||||
@param amplitude float
|
||||
@range 0 100
|
||||
@default 10
|
||||
Wave height in pixels. Bind to bass for punchy distortion.
|
||||
|
||||
@param wavelength float
|
||||
@range 10 500
|
||||
@default 50
|
||||
Distance between wave peaks in pixels.
|
||||
|
||||
@param speed float
|
||||
@range 0 10
|
||||
@default 1
|
||||
Wave animation speed. Uses state to track phase over time.
|
||||
|
||||
@param direction string
|
||||
@enum horizontal vertical both
|
||||
@default horizontal
|
||||
Wave direction:
|
||||
- horizontal: waves move left-right
|
||||
- vertical: waves move up-down
|
||||
- both: waves in both directions
|
||||
|
||||
@state phase float
|
||||
Tracks wave animation phase across frames.
|
||||
|
||||
@example
|
||||
(effect wave :amplitude 20 :wavelength 100)
|
||||
|
||||
@example
|
||||
;; Bass-reactive waves
|
||||
(effect wave :amplitude (bind bass :range [0 50] :transform sqrt))
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
|
||||
def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple:
|
||||
"""
|
||||
Apply wave distortion to a video frame.
|
||||
|
||||
Args:
|
||||
frame: Input frame as numpy array (H, W, 3) RGB uint8
|
||||
params: Effect parameters
|
||||
- amplitude: wave height in pixels (default 10)
|
||||
- wavelength: distance between peaks (default 50)
|
||||
- speed: animation speed (default 1)
|
||||
- direction: horizontal/vertical/both (default horizontal)
|
||||
state: Persistent state dict
|
||||
- phase: current wave phase
|
||||
|
||||
Returns:
|
||||
Tuple of (processed_frame, new_state)
|
||||
"""
|
||||
amplitude = params.get("amplitude", 10)
|
||||
wavelength = params.get("wavelength", 50)
|
||||
speed = params.get("speed", 1)
|
||||
direction = params.get("direction", "horizontal")
|
||||
|
||||
if state is None:
|
||||
state = {}
|
||||
|
||||
if amplitude == 0:
|
||||
return frame, state
|
||||
|
||||
h, w = frame.shape[:2]
|
||||
|
||||
# Update phase for animation
|
||||
phase = state.get("phase", 0)
|
||||
phase += speed * 0.1
|
||||
state["phase"] = phase
|
||||
|
||||
# Create coordinate maps
|
||||
map_x = np.tile(np.arange(w, dtype=np.float32), (h, 1))
|
||||
map_y = np.tile(np.arange(h, dtype=np.float32).reshape(-1, 1), (1, w))
|
||||
|
||||
if direction in ("horizontal", "both"):
|
||||
# Horizontal waves: displace X based on Y
|
||||
wave = np.sin(2 * np.pi * map_y / wavelength + phase) * amplitude
|
||||
map_x = map_x + wave
|
||||
|
||||
if direction in ("vertical", "both"):
|
||||
# Vertical waves: displace Y based on X
|
||||
wave = np.sin(2 * np.pi * map_x / wavelength + phase) * amplitude
|
||||
map_y = map_y + wave
|
||||
|
||||
# Apply distortion
|
||||
result = cv2.remap(
|
||||
frame, map_x, map_y,
|
||||
cv2.INTER_LINEAR,
|
||||
borderMode=cv2.BORDER_REFLECT
|
||||
)
|
||||
|
||||
return result, state
|
||||
Reference in New Issue
Block a user