- 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>
68 lines
2.4 KiB
Common Lisp
68 lines
2.4 KiB
Common Lisp
;; ASCII with Alternating Rotation Directions
|
|
;;
|
|
;; Checkerboard pattern: even cells rotate clockwise, odd cells rotate counter-clockwise
|
|
;; Rotation amount scaled by energy and position (more at top-right)
|
|
|
|
(recipe "ascii_alternating_rotate"
|
|
:version "1.0"
|
|
:description "ASCII art with alternating rotation directions per cell"
|
|
:minimal-primitives true
|
|
:encoding (:codec "libx264" :crf 20 :preset "medium" :audio-codec "aac" :fps 30)
|
|
|
|
:params (
|
|
(cols :type int :default 50 :range [20 100]
|
|
:desc "Number of character columns")
|
|
(rotation_scale :type float :default 60 :range [0 180]
|
|
:desc "Max rotation in degrees")
|
|
(duration :type float :default 10 :range [1 300]
|
|
:desc "Clip duration in seconds")
|
|
)
|
|
|
|
;; Registry
|
|
(effect ascii_fx_zone :path "../sexp_effects/effects/ascii_fx_zone.sexp")
|
|
;; Effects used in cell_effect lambda
|
|
(effect rotate :path "../sexp_effects/effects/rotate.sexp")
|
|
(analyzer energy :path "../../artdag-analyzers/energy/analyzer.py")
|
|
|
|
;; Source files
|
|
(def video (source :path "../monday.webm"))
|
|
(def audio (source :path "../dizzy.mp3"))
|
|
|
|
;; Stage 1: Analysis
|
|
(stage :analyze
|
|
:outputs [energy-data]
|
|
(def audio-clip (-> audio (segment :start 60 :duration duration)))
|
|
(def energy-data (-> audio-clip (analyze energy))))
|
|
|
|
;; Stage 2: Process
|
|
(stage :process
|
|
:requires [:analyze]
|
|
:inputs [energy-data]
|
|
:outputs [result audio-clip]
|
|
(def clip (-> video (segment :start 0 :duration duration)))
|
|
(def audio-clip (-> audio (segment :start 60 :duration duration)))
|
|
|
|
(def result (-> clip
|
|
(effect ascii_fx_zone
|
|
:cols cols
|
|
:char_size (bind energy-data values :range [10 20])
|
|
:color_mode "color"
|
|
:background "black"
|
|
:energy (bind energy-data values :range [0 1])
|
|
:rotation_scale rotation_scale
|
|
;; Alternating rotation: even cells clockwise, odd cells counter-clockwise
|
|
;; Scaled by energy * position (more at top-right)
|
|
:cell_effect (lambda [cell zone]
|
|
(rotate cell
|
|
(* (if (= (mod (+ (get zone "row") (get zone "col")) 2) 0) 1 -1)
|
|
(* (get zone "energy")
|
|
(get zone "rotation_scale")
|
|
(* 1.5 (+ (get zone "col-norm")
|
|
(- 1 (get zone "row-norm"))))))))))))
|
|
|
|
;; Stage 3: Output
|
|
(stage :output
|
|
:requires [:process]
|
|
:inputs [result audio-clip]
|
|
(mux result audio-clip)))
|