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:
gilesb
2026-01-19 12:34:45 +00:00
commit 406cc7c0c7
171 changed files with 13406 additions and 0 deletions

110
effects/rotate.py Normal file
View File

@@ -0,0 +1,110 @@
# /// script
# requires-python = ">=3.10"
# dependencies = ["numpy", "opencv-python"]
# ///
"""
@effect rotate
@version 1.0.0
@author artdag
@description
Rotates the frame by a specified angle. Supports two modes:
- Static: rotate by fixed angle
- Continuous: rotate by speed (degrees per frame), accumulating over time
The effect uses state to track cumulative rotation for smooth continuous motion.
@param angle float
@range -360 360
@default 0
Rotation angle in degrees. Positive = counter-clockwise.
@param speed float
@range -30 30
@default 0
Rotation speed in degrees per frame (for continuous rotation).
When non-zero, angle is ignored and rotation accumulates.
@param auto_zoom bool
@default false
If true, automatically zooms to crop out black corners.
@state cumulative_angle float
Tracks total rotation for continuous mode. Persists across frames.
@example
(effect rotate :angle 45)
@example
;; Continuous spin at 5 degrees per frame
(effect rotate :speed 5)
@example
;; Beat-reactive rotation
(effect rotate :angle (bind bass :range [0 90]))
"""
import numpy as np
import cv2
import math
def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple:
"""
Rotate a video frame.
Args:
frame: Input frame as numpy array (H, W, 3) RGB uint8
params: Effect parameters
- angle: rotation angle in degrees (default 0)
- speed: rotation speed per frame for continuous mode (default 0)
- auto_zoom: zoom to hide black corners (default False)
state: Persistent state dict
- cumulative_angle: total rotation for continuous mode
Returns:
Tuple of (processed_frame, new_state)
"""
angle = params.get("angle", 0)
speed = params.get("speed", 0)
auto_zoom = params.get("auto_zoom", False)
# Initialize state
if state is None:
state = {}
# Handle continuous rotation mode
if speed != 0:
cumulative = state.get("cumulative_angle", 0)
cumulative += speed
state["cumulative_angle"] = cumulative
angle = cumulative
if angle == 0:
return frame, state
h, w = frame.shape[:2]
center = (w / 2, h / 2)
# Calculate zoom factor to hide black corners
scale = 1.0
if auto_zoom:
angle_rad = math.radians(abs(angle) % 90)
if w >= h:
scale = math.cos(angle_rad) + (w / h) * math.sin(angle_rad)
else:
scale = math.cos(angle_rad) + (h / w) * math.sin(angle_rad)
scale = max(1.0, scale)
# Get rotation matrix
rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale)
# Apply rotation
result = cv2.warpAffine(
frame, rotation_matrix, (w, h),
flags=cv2.INTER_LINEAR,
borderMode=cv2.BORDER_CONSTANT,
borderValue=(0, 0, 0)
)
return result, state