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:
141
effects/datamosh.py
Normal file
141
effects/datamosh.py
Normal file
@@ -0,0 +1,141 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = ["numpy"]
|
||||
# ///
|
||||
"""
|
||||
@effect datamosh
|
||||
@version 1.0.0
|
||||
@author artdag
|
||||
|
||||
@description
|
||||
Digital corruption / glitch block effect. Randomly corrupts rectangular
|
||||
blocks by shifting, swapping, or duplicating from previous frames.
|
||||
Simulates video compression artifacts.
|
||||
|
||||
@param block_size int
|
||||
@range 8 128
|
||||
@default 32
|
||||
Size of corruption blocks in pixels.
|
||||
|
||||
@param corruption float
|
||||
@range 0 1
|
||||
@default 0.3
|
||||
Probability of corrupting each block. Bind to energy for reactive glitch.
|
||||
|
||||
@param max_offset int
|
||||
@range 0 200
|
||||
@default 50
|
||||
Maximum pixel offset when shifting blocks.
|
||||
|
||||
@param color_corrupt bool
|
||||
@default true
|
||||
Also apply color channel shifts to blocks.
|
||||
|
||||
@param seed int
|
||||
@default 42
|
||||
Random seed for deterministic glitch patterns.
|
||||
|
||||
@state previous_frame ndarray
|
||||
Stores previous frame for frame-blending corruption.
|
||||
|
||||
@state rng DeterministicRNG
|
||||
Random number generator for reproducible results.
|
||||
|
||||
@example
|
||||
(effect datamosh :corruption 0.4)
|
||||
|
||||
@example
|
||||
;; Heavy glitch on energy peaks
|
||||
(effect datamosh :corruption (bind energy :range [0 0.8]) :block_size 16)
|
||||
|
||||
@example
|
||||
;; Reproducible glitch with seed
|
||||
(effect datamosh :corruption 0.5 :seed 12345)
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
# Import DeterministicRNG from same directory
|
||||
_effects_dir = Path(__file__).parent
|
||||
if str(_effects_dir) not in sys.path:
|
||||
sys.path.insert(0, str(_effects_dir))
|
||||
from random import DeterministicRNG
|
||||
|
||||
|
||||
def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple:
|
||||
"""
|
||||
Apply datamosh/glitch block effect to a video frame.
|
||||
|
||||
Args:
|
||||
frame: Input frame as numpy array (H, W, 3) RGB uint8
|
||||
params: Effect parameters
|
||||
- block_size: corruption block size (default 32)
|
||||
- corruption: probability 0-1 (default 0.3)
|
||||
- max_offset: max shift in pixels (default 50)
|
||||
- color_corrupt: apply color shifts (default True)
|
||||
state: Persistent state dict
|
||||
- previous_frame: last frame for duplication effect
|
||||
|
||||
Returns:
|
||||
Tuple of (processed_frame, new_state)
|
||||
"""
|
||||
block_size = max(8, min(int(params.get("block_size", 32)), 128))
|
||||
corruption = max(0, min(params.get("corruption", 0.3), 1))
|
||||
max_offset = int(params.get("max_offset", 50))
|
||||
color_corrupt = params.get("color_corrupt", True)
|
||||
seed = int(params.get("seed", 42))
|
||||
|
||||
if state is None:
|
||||
state = {}
|
||||
|
||||
# Initialize RNG
|
||||
if "rng" not in state:
|
||||
state["rng"] = DeterministicRNG(seed)
|
||||
rng = state["rng"]
|
||||
|
||||
if corruption == 0:
|
||||
state["previous_frame"] = frame.copy()
|
||||
return frame, state
|
||||
|
||||
h, w = frame.shape[:2]
|
||||
result = frame.copy()
|
||||
prev_frame = state.get("previous_frame")
|
||||
|
||||
# Process blocks
|
||||
for by in range(0, h, block_size):
|
||||
for bx in range(0, w, block_size):
|
||||
bh = min(block_size, h - by)
|
||||
bw = min(block_size, w - bx)
|
||||
|
||||
if rng.uniform() < corruption:
|
||||
corruption_type = rng.choice(["shift", "duplicate", "color", "swap"])
|
||||
|
||||
if corruption_type == "shift" and max_offset > 0:
|
||||
ox = rng.randint(-max_offset, max_offset)
|
||||
oy = rng.randint(-max_offset, max_offset)
|
||||
src_x = max(0, min(bx + ox, w - bw))
|
||||
src_y = max(0, min(by + oy, h - bh))
|
||||
result[by:by+bh, bx:bx+bw] = frame[src_y:src_y+bh, src_x:src_x+bw]
|
||||
|
||||
elif corruption_type == "duplicate" and prev_frame is not None:
|
||||
if prev_frame.shape == frame.shape:
|
||||
result[by:by+bh, bx:bx+bw] = prev_frame[by:by+bh, bx:bx+bw]
|
||||
|
||||
elif corruption_type == "color" and color_corrupt:
|
||||
block = result[by:by+bh, bx:bx+bw].copy()
|
||||
shift = rng.randint(1, 3)
|
||||
channel = rng.randint(0, 2)
|
||||
block[:, :, channel] = np.roll(block[:, :, channel], shift, axis=0)
|
||||
result[by:by+bh, bx:bx+bw] = block
|
||||
|
||||
elif corruption_type == "swap":
|
||||
other_bx = rng.randint(0, max(0, w - bw - 1))
|
||||
other_by = rng.randint(0, max(0, h - bh - 1))
|
||||
temp = result[by:by+bh, bx:bx+bw].copy()
|
||||
result[by:by+bh, bx:bx+bw] = frame[other_by:other_by+bh, other_bx:other_bx+bw]
|
||||
result[other_by:other_by+bh, other_bx:other_bx+bw] = temp
|
||||
|
||||
state["previous_frame"] = frame.copy()
|
||||
return result, state
|
||||
Reference in New Issue
Block a user