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:
139
effects/color_grade.py
Normal file
139
effects/color_grade.py
Normal file
@@ -0,0 +1,139 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = ["numpy", "opencv-python"]
|
||||
# ///
|
||||
"""
|
||||
@effect color_grade
|
||||
@version 1.0.0
|
||||
@author artdag
|
||||
|
||||
@description
|
||||
Color grading effect. Applies cinematic color adjustments including
|
||||
shadows/midtones/highlights tinting, lift/gamma/gain, and temperature.
|
||||
|
||||
@param shadows list
|
||||
@default [0, 0, 0]
|
||||
RGB tint for dark areas.
|
||||
|
||||
@param midtones list
|
||||
@default [0, 0, 0]
|
||||
RGB tint for middle tones.
|
||||
|
||||
@param highlights list
|
||||
@default [0, 0, 0]
|
||||
RGB tint for bright areas.
|
||||
|
||||
@param lift float
|
||||
@range -0.5 0.5
|
||||
@default 0
|
||||
Raise/lower shadow levels.
|
||||
|
||||
@param gamma float
|
||||
@range 0.5 2
|
||||
@default 1
|
||||
Midtone brightness curve.
|
||||
|
||||
@param gain float
|
||||
@range 0.5 2
|
||||
@default 1
|
||||
Highlight intensity.
|
||||
|
||||
@param temperature float
|
||||
@range -100 100
|
||||
@default 0
|
||||
Color temperature (-100 = cool/blue, +100 = warm/orange).
|
||||
|
||||
@param tint float
|
||||
@range -100 100
|
||||
@default 0
|
||||
Green/magenta tint (-100 = green, +100 = magenta).
|
||||
|
||||
@example
|
||||
(effect color_grade :temperature 30 :shadows [0 0 20])
|
||||
|
||||
@example
|
||||
;; Cinematic teal-orange look
|
||||
(effect color_grade :shadows [0 10 20] :highlights [20 10 0])
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import cv2
|
||||
|
||||
|
||||
def process_frame(frame: np.ndarray, params: dict, state: dict) -> tuple:
|
||||
"""
|
||||
Apply color grading to a video frame.
|
||||
|
||||
Args:
|
||||
frame: Input frame as numpy array (H, W, 3) RGB uint8
|
||||
params: Effect parameters
|
||||
state: Persistent state dict
|
||||
|
||||
Returns:
|
||||
Tuple of (processed_frame, new_state)
|
||||
"""
|
||||
shadows = params.get("shadows", [0, 0, 0])
|
||||
midtones = params.get("midtones", [0, 0, 0])
|
||||
highlights = params.get("highlights", [0, 0, 0])
|
||||
lift = params.get("lift", 0)
|
||||
gamma = max(0.5, min(params.get("gamma", 1), 2))
|
||||
gain = max(0.5, min(params.get("gain", 1), 2))
|
||||
temperature = params.get("temperature", 0)
|
||||
tint = params.get("tint", 0)
|
||||
|
||||
if state is None:
|
||||
state = {}
|
||||
|
||||
result = frame.astype(np.float32) / 255.0
|
||||
|
||||
# Apply lift (shadows)
|
||||
result = result + lift
|
||||
|
||||
# Apply gamma (midtones)
|
||||
result = np.power(np.clip(result, 0.001, 1), 1 / gamma)
|
||||
|
||||
# Apply gain (highlights)
|
||||
result = result * gain
|
||||
|
||||
# Convert tints to float
|
||||
if isinstance(shadows, (list, tuple)) and len(shadows) >= 3:
|
||||
shadows = np.array(shadows[:3], dtype=np.float32) / 255.0
|
||||
else:
|
||||
shadows = np.zeros(3, dtype=np.float32)
|
||||
|
||||
if isinstance(midtones, (list, tuple)) and len(midtones) >= 3:
|
||||
midtones = np.array(midtones[:3], dtype=np.float32) / 255.0
|
||||
else:
|
||||
midtones = np.zeros(3, dtype=np.float32)
|
||||
|
||||
if isinstance(highlights, (list, tuple)) and len(highlights) >= 3:
|
||||
highlights = np.array(highlights[:3], dtype=np.float32) / 255.0
|
||||
else:
|
||||
highlights = np.zeros(3, dtype=np.float32)
|
||||
|
||||
# Calculate luminance for zone-based grading
|
||||
lum = 0.299 * result[:, :, 0] + 0.587 * result[:, :, 1] + 0.114 * result[:, :, 2]
|
||||
|
||||
# Create zone masks
|
||||
shadow_mask = np.clip(1 - lum * 3, 0, 1)[:, :, np.newaxis]
|
||||
highlight_mask = np.clip((lum - 0.67) * 3, 0, 1)[:, :, np.newaxis]
|
||||
midtone_mask = 1 - shadow_mask - highlight_mask
|
||||
|
||||
# Apply zone tints
|
||||
for c in range(3):
|
||||
result[:, :, c] += shadows[c] * shadow_mask[:, :, 0]
|
||||
result[:, :, c] += midtones[c] * midtone_mask[:, :, 0]
|
||||
result[:, :, c] += highlights[c] * highlight_mask[:, :, 0]
|
||||
|
||||
# Apply temperature (blue <-> orange)
|
||||
if temperature != 0:
|
||||
temp_shift = temperature / 100.0
|
||||
result[:, :, 0] += temp_shift * 0.3 # Red
|
||||
result[:, :, 2] -= temp_shift * 0.3 # Blue
|
||||
|
||||
# Apply tint (green <-> magenta)
|
||||
if tint != 0:
|
||||
tint_shift = tint / 100.0
|
||||
result[:, :, 1] -= tint_shift * 0.2 # Green
|
||||
|
||||
return (np.clip(result, 0, 1) * 255).astype(np.uint8), state
|
||||
Reference in New Issue
Block a user