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:
134
effects/tile_grid.py
Normal file
134
effects/tile_grid.py
Normal file
@@ -0,0 +1,134 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = ["numpy", "opencv-python"]
|
||||
# ///
|
||||
"""
|
||||
@effect tile_grid
|
||||
@version 1.0.0
|
||||
@author artdag
|
||||
|
||||
@description
|
||||
Tile Grid effect. Repeats the frame in a grid pattern creating
|
||||
a mosaic by tiling scaled-down copies. Great for psychedelic visuals.
|
||||
|
||||
@param rows int
|
||||
@range 1 10
|
||||
@default 2
|
||||
Number of rows in grid.
|
||||
|
||||
@param cols int
|
||||
@range 1 10
|
||||
@default 2
|
||||
Number of columns in grid.
|
||||
|
||||
@param gap int
|
||||
@range 0 50
|
||||
@default 0
|
||||
Gap between tiles in pixels.
|
||||
|
||||
@param gap_color list
|
||||
@default [0, 0, 0]
|
||||
RGB color for gaps.
|
||||
|
||||
@param rotation_per_tile float
|
||||
@range -180 180
|
||||
@default 0
|
||||
Rotation increment per tile in degrees.
|
||||
|
||||
@param alternate_flip bool
|
||||
@default false
|
||||
Flip alternating tiles horizontally.
|
||||
|
||||
@example
|
||||
(effect tile_grid :rows 3 :cols 3)
|
||||
|
||||
@example
|
||||
;; Rotating tiles
|
||||
(effect tile_grid :rows 2 :cols 2 :rotation_per_tile 90)
|
||||
|
||||
@example
|
||||
;; Beat-reactive grid
|
||||
(effect tile_grid :rows (bind bass :range [2 6]) :cols (bind bass :range [2 6]))
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
|
||||
def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple:
|
||||
"""
|
||||
Apply tile grid effect to a video frame.
|
||||
|
||||
Args:
|
||||
frame: Input frame as numpy array (H, W, 3) RGB uint8
|
||||
params: Effect parameters
|
||||
- rows: number of rows (default 2)
|
||||
- cols: number of columns (default 2)
|
||||
- gap: gap between tiles (default 0)
|
||||
- gap_color: RGB tuple (default [0,0,0])
|
||||
- rotation_per_tile: rotation increment (default 0)
|
||||
- alternate_flip: flip alternating tiles (default False)
|
||||
state: Persistent state dict
|
||||
|
||||
Returns:
|
||||
Tuple of (processed_frame, new_state)
|
||||
"""
|
||||
rows = max(1, min(int(params.get("rows", 2)), 10))
|
||||
cols = max(1, min(int(params.get("cols", 2)), 10))
|
||||
gap = max(0, int(params.get("gap", 0)))
|
||||
gap_color = params.get("gap_color", [0, 0, 0])
|
||||
rotation_per_tile = params.get("rotation_per_tile", 0)
|
||||
alternate_flip = params.get("alternate_flip", False)
|
||||
|
||||
if state is None:
|
||||
state = {}
|
||||
|
||||
h, w = frame.shape[:2]
|
||||
|
||||
# Calculate tile size
|
||||
tile_w = (w - gap * (cols - 1)) // cols
|
||||
tile_h = (h - gap * (rows - 1)) // rows
|
||||
|
||||
if tile_w <= 0 or tile_h <= 0:
|
||||
return frame, state
|
||||
|
||||
# Scale down the original frame to tile size
|
||||
tile = cv2.resize(frame, (tile_w, tile_h), interpolation=cv2.INTER_LINEAR)
|
||||
|
||||
# Create result with gap color
|
||||
if isinstance(gap_color, (list, tuple)) and len(gap_color) >= 3:
|
||||
result = np.full((h, w, 3), gap_color[:3], dtype=np.uint8)
|
||||
else:
|
||||
result = np.zeros((h, w, 3), dtype=np.uint8)
|
||||
|
||||
# Place tiles
|
||||
tile_idx = 0
|
||||
for row in range(rows):
|
||||
for col in range(cols):
|
||||
y = row * (tile_h + gap)
|
||||
x = col * (tile_w + gap)
|
||||
|
||||
current_tile = tile.copy()
|
||||
|
||||
# Apply rotation if specified
|
||||
if rotation_per_tile != 0:
|
||||
angle = rotation_per_tile * tile_idx
|
||||
center = (tile_w // 2, tile_h // 2)
|
||||
M = cv2.getRotationMatrix2D(center, angle, 1.0)
|
||||
current_tile = cv2.warpAffine(current_tile, M, (tile_w, tile_h),
|
||||
borderMode=cv2.BORDER_REFLECT)
|
||||
|
||||
# Apply flip for alternating tiles
|
||||
if alternate_flip and (row + col) % 2 == 1:
|
||||
current_tile = cv2.flip(current_tile, 1)
|
||||
|
||||
# Place tile
|
||||
y_end = min(y + tile_h, h)
|
||||
x_end = min(x + tile_w, w)
|
||||
tile_crop_h = y_end - y
|
||||
tile_crop_w = x_end - x
|
||||
result[y:y_end, x:x_end] = current_tile[:tile_crop_h, :tile_crop_w]
|
||||
|
||||
tile_idx += 1
|
||||
|
||||
return result, state
|
||||
Reference in New Issue
Block a user