Add streaming video compositor with sexp interpreter
- New streaming/ module for real-time video processing: - compositor.py: Main streaming compositor with cycle-crossfade - sexp_executor.py: Executes compiled sexp recipes in real-time - sexp_interp.py: Full S-expression interpreter for SLICE_ON Lambda - recipe_adapter.py: Bridges recipes to streaming compositor - sources.py: Video source with ffmpeg streaming - audio.py: Real-time audio analysis (energy, beats) - output.py: Preview (mpv) and file output with audio muxing - New templates/: - cycle-crossfade.sexp: Smooth zoom-based video cycling - process-pair.sexp: Dual-clip processing with effects - Key features: - Videos cycle in input-videos order (not definition order) - Cumulative whole-spin rotation - Zero-weight sources skip processing - Live audio-reactive effects - New effects: blend_multi for weighted layer compositing - Updated primitives and interpreter for streaming compatibility Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,32 @@ def prim_mod(a, b):
|
||||
return a % b
|
||||
|
||||
|
||||
def prim_abs(x):
|
||||
return abs(x)
|
||||
|
||||
|
||||
def prim_min(*args):
|
||||
return min(args)
|
||||
|
||||
|
||||
def prim_max(*args):
|
||||
return max(args)
|
||||
|
||||
|
||||
def prim_round(x):
|
||||
return round(x)
|
||||
|
||||
|
||||
def prim_floor(x):
|
||||
import math
|
||||
return math.floor(x)
|
||||
|
||||
|
||||
def prim_ceil(x):
|
||||
import math
|
||||
return math.ceil(x)
|
||||
|
||||
|
||||
# Comparison
|
||||
def prim_lt(a, b):
|
||||
return a < b
|
||||
@@ -98,6 +124,17 @@ def prim_get(obj, key, default=None):
|
||||
return default
|
||||
|
||||
|
||||
def prim_nth(seq, i):
|
||||
i = int(i)
|
||||
if 0 <= i < len(seq):
|
||||
return seq[i]
|
||||
return None
|
||||
|
||||
|
||||
def prim_first(seq):
|
||||
return seq[0] if seq else None
|
||||
|
||||
|
||||
def prim_length(seq):
|
||||
return len(seq)
|
||||
|
||||
@@ -127,6 +164,31 @@ def prim_is_nil(x):
|
||||
return x is None
|
||||
|
||||
|
||||
# Higher-order / iteration
|
||||
def prim_reduce(seq, init, fn):
|
||||
"""(reduce seq init fn) — fold left: fn(fn(fn(init, s0), s1), s2) ..."""
|
||||
acc = init
|
||||
for item in seq:
|
||||
acc = fn(acc, item)
|
||||
return acc
|
||||
|
||||
|
||||
def prim_map(seq, fn):
|
||||
"""(map seq fn) — apply fn to each element, return new list."""
|
||||
return [fn(item) for item in seq]
|
||||
|
||||
|
||||
def prim_range(*args):
|
||||
"""(range end), (range start end), or (range start end step) — integer range."""
|
||||
if len(args) == 1:
|
||||
return list(range(int(args[0])))
|
||||
elif len(args) == 2:
|
||||
return list(range(int(args[0]), int(args[1])))
|
||||
elif len(args) >= 3:
|
||||
return list(range(int(args[0]), int(args[1]), int(args[2])))
|
||||
return []
|
||||
|
||||
|
||||
# Core primitives dict
|
||||
PRIMITIVES = {
|
||||
# Arithmetic
|
||||
@@ -135,6 +197,12 @@ PRIMITIVES = {
|
||||
'*': prim_mul,
|
||||
'/': prim_div,
|
||||
'mod': prim_mod,
|
||||
'abs': prim_abs,
|
||||
'min': prim_min,
|
||||
'max': prim_max,
|
||||
'round': prim_round,
|
||||
'floor': prim_floor,
|
||||
'ceil': prim_ceil,
|
||||
|
||||
# Comparison
|
||||
'<': prim_lt,
|
||||
@@ -151,6 +219,8 @@ PRIMITIVES = {
|
||||
|
||||
# Data access
|
||||
'get': prim_get,
|
||||
'nth': prim_nth,
|
||||
'first': prim_first,
|
||||
'length': prim_length,
|
||||
'len': prim_length,
|
||||
'list': prim_list,
|
||||
@@ -161,4 +231,10 @@ PRIMITIVES = {
|
||||
'list?': prim_is_list,
|
||||
'dict?': prim_is_dict,
|
||||
'nil?': prim_is_nil,
|
||||
|
||||
# Higher-order / iteration
|
||||
'reduce': prim_reduce,
|
||||
'fold': prim_reduce,
|
||||
'map': prim_map,
|
||||
'range': prim_range,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user