Files
test/effects/ascii_dual_blend.sexp
gilesb d241e2a663 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>
2026-01-29 01:27:39 +00:00

100 lines
3.6 KiB
Common Lisp

;; ASCII Dual Blend
;;
;; Applies ASCII alternating rotation effect to two video sources,
;; blends them together, and muxes with audio.
;; All synced to the same audio analysis.
(recipe "ascii_dual_blend"
:version "1.0"
:description "Blend two ASCII-processed videos synced to audio"
: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")
(blend_opacity :type float :default 0.5 :range [0 1]
:desc "Blend opacity (0=video-a only, 1=video-b only)")
(blend_mode :type string :default "overlay"
:desc "Blend mode: alpha, add, multiply, screen, overlay, difference")
(duration :type float :default 10 :range [1 300]
:desc "Clip duration in seconds")
)
;; Registry - effects and analyzers
(effect ascii_fx_zone :path "../sexp_effects/effects/ascii_fx_zone.sexp")
(effect rotate :path "../sexp_effects/effects/rotate.sexp")
(effect blend :path "../sexp_effects/effects/blend.sexp")
(analyzer energy :path "../../artdag-analyzers/energy/analyzer.py")
;; Source files
(def video-a (source :path "../monday.webm"))
(def video-b (source :path "../new.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 both videos
(stage :process
:requires [:analyze]
:inputs [energy-data]
:outputs [blended audio-clip]
;; Get audio clip for final mux
(def audio-clip (-> audio (segment :start 60 :duration duration)))
;; Process video A with ASCII effect
(def clip-a (-> video-a (segment :start 0 :duration duration)))
(def ascii-a (-> clip-a
(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
: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")))))))))))
;; Process video B with ASCII effect
(def clip-b (-> video-b (segment :start 0 :duration duration)))
(def ascii-b (-> clip-b
(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
: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")))))))))))
;; Blend the two ASCII videos using consistent effect syntax
(def blended (-> ascii-a
(effect blend ascii-b
:mode blend_mode
:opacity blend_opacity
:resize_mode "fit"))))
;; Stage 3: Output
(stage :output
:requires [:process]
:inputs [blended audio-clip]
(mux blended audio-clip)))