- 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>
66 lines
2.7 KiB
Common Lisp
66 lines
2.7 KiB
Common Lisp
;; cycle-crossfade template
|
|
;;
|
|
;; Generalized cycling zoom-crossfade for any number of video layers.
|
|
;; Cycles through videos with smooth zoom-based crossfade transitions.
|
|
;;
|
|
;; Parameters:
|
|
;; beat-data - beat analysis node (drives timing)
|
|
;; input-videos - list of video nodes to cycle through
|
|
;; init-clen - initial cycle length in beats
|
|
;;
|
|
;; NOTE: The parameter is named "input-videos" (not "videos") because
|
|
;; template substitution replaces param names everywhere in the AST.
|
|
;; The planner's _expand_slice_on injects env["videos"] at plan time,
|
|
;; so (len videos) inside the lambda references that injected value.
|
|
|
|
(deftemplate cycle-crossfade
|
|
(beat-data input-videos init-clen)
|
|
|
|
(slice-on beat-data
|
|
:videos input-videos
|
|
:init {:cycle 0 :beat 0 :clen init-clen}
|
|
:fn (lambda [acc i start end]
|
|
(let [beat (get acc "beat")
|
|
clen (get acc "clen")
|
|
active (get acc "cycle")
|
|
n (len videos)
|
|
phase3 (* beat 3)
|
|
wt (lambda [p]
|
|
(let [prev (mod (+ p (- n 1)) n)]
|
|
(if (= active p)
|
|
(if (< phase3 clen) 1.0
|
|
(if (< phase3 (* clen 2))
|
|
(- 1.0 (* (/ (- phase3 clen) clen) 1.0))
|
|
0.0))
|
|
(if (= active prev)
|
|
(if (< phase3 clen) 0.0
|
|
(if (< phase3 (* clen 2))
|
|
(* (/ (- phase3 clen) clen) 1.0)
|
|
1.0))
|
|
0.0))))
|
|
zm (lambda [p]
|
|
(let [prev (mod (+ p (- n 1)) n)]
|
|
(if (= active p)
|
|
;; Active video: normal -> zoom out during transition -> tiny
|
|
(if (< phase3 clen) 1.0
|
|
(if (< phase3 (* clen 2))
|
|
(+ 1.0 (* (/ (- phase3 clen) clen) 1.0))
|
|
0.1))
|
|
(if (= active prev)
|
|
;; Incoming video: tiny -> zoom in during transition -> normal
|
|
(if (< phase3 clen) 0.1
|
|
(if (< phase3 (* clen 2))
|
|
(+ 0.1 (* (/ (- phase3 clen) clen) 0.9))
|
|
1.0))
|
|
0.1))))
|
|
new-acc (if (< (+ beat 1) clen)
|
|
(dict :cycle active :beat (+ beat 1) :clen clen)
|
|
(dict :cycle (mod (+ active 1) n) :beat 0
|
|
:clen (+ 40 (mod (* i 7) 41))))]
|
|
{:layers (map (lambda [p]
|
|
{:video p :effects [{:effect zoom :amount (zm p)}]})
|
|
(range 0 n))
|
|
:compose {:effect blend_multi :mode "alpha"
|
|
:weights (map (lambda [p] (wt p)) (range 0 n))}
|
|
:acc new-acc}))))
|