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:
90
effects/strobe.py
Normal file
90
effects/strobe.py
Normal file
@@ -0,0 +1,90 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = ["numpy"]
|
||||
# ///
|
||||
"""
|
||||
@effect strobe
|
||||
@version 1.0.0
|
||||
@author artdag
|
||||
|
||||
@description
|
||||
Strobe / Posterize Time effect. Locks video to a reduced frame rate,
|
||||
creating a choppy, stop-motion look. Also known as frame hold.
|
||||
|
||||
@param frame_rate float
|
||||
@range 1 60
|
||||
@default 12
|
||||
Target frame rate in fps. Lower = choppier.
|
||||
|
||||
@param sync_to_beat bool
|
||||
@default false
|
||||
If true, hold frames until next beat (overrides frame_rate).
|
||||
|
||||
@param beat_divisor int
|
||||
@range 1 8
|
||||
@default 1
|
||||
Hold for 1/N beats when sync_to_beat is true.
|
||||
|
||||
@state held_frame ndarray
|
||||
Currently held frame.
|
||||
|
||||
@state held_until float
|
||||
Time until which to hold the frame.
|
||||
|
||||
@example
|
||||
(effect strobe :frame_rate 8)
|
||||
|
||||
@example
|
||||
;; Very choppy at 4 fps
|
||||
(effect strobe :frame_rate 4)
|
||||
|
||||
@example
|
||||
;; Beat-synced frame hold
|
||||
(effect strobe :sync_to_beat true :beat_divisor 2)
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple:
|
||||
"""
|
||||
Apply strobe/posterize time effect to a video frame.
|
||||
|
||||
Args:
|
||||
frame: Input frame as numpy array (H, W, 3) RGB uint8
|
||||
params: Effect parameters
|
||||
- frame_rate: target fps 1-60 (default 12)
|
||||
- sync_to_beat: use beat timing (default False)
|
||||
- beat_divisor: beat fraction (default 1)
|
||||
state: Persistent state dict
|
||||
- held_frame: currently held frame
|
||||
- held_until: hold expiry time
|
||||
|
||||
Returns:
|
||||
Tuple of (processed_frame, new_state)
|
||||
"""
|
||||
target_fps = max(1, min(params.get("frame_rate", 12), 60))
|
||||
sync_to_beat = params.get("sync_to_beat", False)
|
||||
beat_divisor = max(1, int(params.get("beat_divisor", 1)))
|
||||
|
||||
# Get current time from params (executor should provide this)
|
||||
t = params.get("_time", 0)
|
||||
|
||||
if state is None:
|
||||
state = {}
|
||||
|
||||
# Initialize state
|
||||
if "held_frame" not in state:
|
||||
state["held_frame"] = None
|
||||
state["held_until"] = 0.0
|
||||
state["last_beat"] = -1
|
||||
|
||||
# Frame rate based hold
|
||||
frame_duration = 1.0 / target_fps
|
||||
|
||||
if t >= state["held_until"]:
|
||||
# Time for new frame
|
||||
state["held_frame"] = frame.copy()
|
||||
state["held_until"] = t + frame_duration
|
||||
|
||||
return state["held_frame"] if state["held_frame"] is not None else frame, state
|
||||
Reference in New Issue
Block a user