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>
92 lines
2.2 KiB
Python
92 lines
2.2 KiB
Python
# /// script
|
|
# requires-python = ">=3.10"
|
|
# dependencies = ["numpy", "opencv-python"]
|
|
# ///
|
|
"""
|
|
@effect zoom
|
|
@version 1.0.0
|
|
@author artdag
|
|
|
|
@description
|
|
Zooms into the center of the frame. Values > 1 zoom in (magnify),
|
|
values < 1 zoom out (shrink with black borders).
|
|
|
|
@param factor float
|
|
@range 0.1 5
|
|
@default 1.0
|
|
Zoom factor. 1 = unchanged, 2 = 2x magnification, 0.5 = half size.
|
|
|
|
@param center_x float
|
|
@range 0 1
|
|
@default 0.5
|
|
Horizontal center of zoom (0 = left, 1 = right).
|
|
|
|
@param center_y float
|
|
@range 0 1
|
|
@default 0.5
|
|
Vertical center of zoom (0 = top, 1 = bottom).
|
|
|
|
@example
|
|
(effect zoom :factor 1.5)
|
|
|
|
@example
|
|
;; Pulse zoom on bass
|
|
(effect zoom :factor (bind bass :range [1.0 1.5] :transform sqrt))
|
|
|
|
@example
|
|
;; Zoom to corner
|
|
(effect zoom :factor 2 :center_x 0 :center_y 0)
|
|
"""
|
|
|
|
import numpy as np
|
|
import cv2
|
|
|
|
|
|
def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple:
|
|
"""
|
|
Zoom a video frame.
|
|
|
|
Args:
|
|
frame: Input frame as numpy array (H, W, 3) RGB uint8
|
|
params: Effect parameters
|
|
- factor: zoom multiplier (default 1.0)
|
|
- center_x: horizontal center 0-1 (default 0.5)
|
|
- center_y: vertical center 0-1 (default 0.5)
|
|
state: Persistent state dict (unused)
|
|
|
|
Returns:
|
|
Tuple of (processed_frame, new_state)
|
|
"""
|
|
factor = params.get("factor", 1.0)
|
|
center_x = params.get("center_x", 0.5)
|
|
center_y = params.get("center_y", 0.5)
|
|
|
|
if factor is None or factor <= 0.01:
|
|
factor = 1.0
|
|
|
|
if factor == 1.0:
|
|
return frame, state
|
|
|
|
h, w = frame.shape[:2]
|
|
|
|
# Calculate crop region for zoom in
|
|
new_w = int(w / factor)
|
|
new_h = int(h / factor)
|
|
|
|
if new_w <= 0 or new_h <= 0:
|
|
return frame, state
|
|
|
|
# Calculate crop offset based on center
|
|
x_start = int((w - new_w) * center_x)
|
|
y_start = int((h - new_h) * center_y)
|
|
|
|
# Clamp to valid range
|
|
x_start = max(0, min(x_start, w - new_w))
|
|
y_start = max(0, min(y_start, h - new_h))
|
|
|
|
# Crop and resize back to original dimensions
|
|
cropped = frame[y_start:y_start + new_h, x_start:x_start + new_w]
|
|
result = cv2.resize(cropped, (w, h), interpolation=cv2.INTER_LINEAR)
|
|
|
|
return result, state
|