Files
test/recipe-all-effects.sexp
gilesb 406cc7c0c7 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>
2026-01-19 12:34:45 +00:00

173 lines
11 KiB
Common Lisp

;; All Effects Showcase
;; Cycles through every sexp effect on beat, using entire audio
(recipe "all-effects-showcase"
:version "1.0"
:encoding (:codec "libx264" :crf 20 :preset "medium" :audio-codec "aac" :fps 30)
;; Analyzers
(analyzer beats :path "../artdag-analyzers/beats/analyzer.py")
(analyzer bass :path "../artdag-analyzers/bass/analyzer.py")
(analyzer energy :path "../artdag-analyzers/energy/analyzer.py")
;; All sexp effects
(effect ascii_art :path "sexp_effects/effects/ascii_art.sexp")
(effect ascii_zones :path "sexp_effects/effects/ascii_zones.sexp")
(effect datamosh :path "sexp_effects/effects/datamosh.sexp")
(effect pixelsort :path "sexp_effects/effects/pixelsort.sexp")
(effect bloom :path "sexp_effects/effects/bloom.sexp")
(effect blur :path "sexp_effects/effects/blur.sexp")
(effect brightness :path "sexp_effects/effects/brightness.sexp")
(effect color-adjust :path "sexp_effects/effects/color-adjust.sexp")
(effect color_cycle :path "sexp_effects/effects/color_cycle.sexp")
(effect contrast :path "sexp_effects/effects/contrast.sexp")
(effect crt :path "sexp_effects/effects/crt.sexp")
(effect echo :path "sexp_effects/effects/echo.sexp")
(effect edge_detect :path "sexp_effects/effects/edge_detect.sexp")
(effect emboss :path "sexp_effects/effects/emboss.sexp")
(effect film_grain :path "sexp_effects/effects/film_grain.sexp")
(effect fisheye :path "sexp_effects/effects/fisheye.sexp")
(effect flip :path "sexp_effects/effects/flip.sexp")
(effect grayscale :path "sexp_effects/effects/grayscale.sexp")
(effect hue_shift :path "sexp_effects/effects/hue_shift.sexp")
(effect invert :path "sexp_effects/effects/invert.sexp")
(effect kaleidoscope :path "sexp_effects/effects/kaleidoscope.sexp")
(effect mirror :path "sexp_effects/effects/mirror.sexp")
(effect neon_glow :path "sexp_effects/effects/neon_glow.sexp")
(effect noise :path "sexp_effects/effects/noise.sexp")
(effect outline :path "sexp_effects/effects/outline.sexp")
(effect pixelate :path "sexp_effects/effects/pixelate.sexp")
(effect posterize :path "sexp_effects/effects/posterize.sexp")
(effect rgb_split :path "sexp_effects/effects/rgb_split.sexp")
(effect ripple :path "sexp_effects/effects/ripple.sexp")
(effect rotate :path "sexp_effects/effects/rotate.sexp")
(effect saturation :path "sexp_effects/effects/saturation.sexp")
(effect scanlines :path "sexp_effects/effects/scanlines.sexp")
(effect sepia :path "sexp_effects/effects/sepia.sexp")
(effect sharpen :path "sexp_effects/effects/sharpen.sexp")
(effect strobe :path "sexp_effects/effects/strobe.sexp")
(effect swirl :path "sexp_effects/effects/swirl.sexp")
(effect threshold :path "sexp_effects/effects/threshold.sexp")
(effect tile_grid :path "sexp_effects/effects/tile_grid.sexp")
(effect trails :path "sexp_effects/effects/trails.sexp")
(effect vignette :path "sexp_effects/effects/vignette.sexp")
(effect wave :path "sexp_effects/effects/wave.sexp")
(effect zoom :path "sexp_effects/effects/zoom.sexp")
;; Constructs
(construct slice-every-n :path "constructs/slice-every-n.sexp")
;; Sources with durations (seconds)
(def video-a (source :path "monday.webm"))
(def video-a-duration 30) ;; adjust to actual duration
(def video-b (source :path "new.webm"))
(def video-b-duration 60) ;; adjust to actual duration
(def video-c (source :path "ecstacy.mp4"))
(def video-c-duration 45) ;; adjust to actual duration
;; Video list with durations for easy lookup
(def videos (list video-a video-b video-c))
(def video-durations (list video-a-duration video-b-duration video-c-duration))
;; Audio - entire file
(def audio (source :path "dizzy.mp3"))
;; Analysis
(def beats-data (-> audio (analyze beats)))
(def bass-data (-> audio (analyze bass)))
(def energy-data (-> audio (analyze energy)))
;; Group every 21 beats into one segment (~42 segments for this track)
(def beats-per-seg 21)
;; Slice into segments, one effect each
;; Wraps video start time; skips if segment longer than all videos
(def segments (slice-every-n beats-data beats-per-seg
:init 0
:reducer (fn [acc i start end]
(let [seg-duration (- end start)
;; Try preferred video first, then others
vid-idx (mod acc 3)
;; Find a video long enough for this segment
valid-vid-idx (cond
(>= (nth video-durations vid-idx) seg-duration) vid-idx
(>= (nth video-durations (mod (+ vid-idx 1) 3)) seg-duration) (mod (+ vid-idx 1) 3)
(>= (nth video-durations (mod (+ vid-idx 2) 3)) seg-duration) (mod (+ vid-idx 2) 3)
:else nil)]
;; Skip if no video is long enough
(if (= valid-vid-idx nil)
{:skip true :acc (inc acc)}
(let [src (nth videos valid-vid-idx)
src-duration (nth video-durations valid-vid-idx)
;; Wrap start time within video duration
wrapped-start (mod start src-duration)
effect-idx (mod acc 42)
fx (cond
;; Color effects 0-9 - DRAMATIC ranges for visible music reactivity
(= effect-idx 0) {:effect invert}
(= effect-idx 1) {:effect grayscale}
(= effect-idx 2) {:effect sepia}
(= effect-idx 3) {:effect brightness :amount (bind bass values :range [-80 80])}
(= effect-idx 4) {:effect contrast :amount (bind energy values :range [0.5 2.5])}
(= effect-idx 5) {:effect saturation :amount (bind bass values :range [0.2 3.0])}
(= effect-idx 6) {:effect hue_shift :degrees (bind energy values :range [0 360])}
(= effect-idx 7) {:effect color_cycle :speed 2}
(= effect-idx 8) {:effect threshold :level 128}
(= effect-idx 9) {:effect posterize :levels 6}
;; Blur/sharpen 10-13 - wider ranges
(= effect-idx 10) {:effect blur :radius (bind bass values :range [1 30])}
(= effect-idx 11) {:effect sharpen :amount (bind energy values :range [0.5 4])}
(= effect-idx 12) {:effect bloom :intensity 0.6 :radius 20}
(= effect-idx 13) {:effect color-adjust :brightness 20 :contrast 1.2}
;; Distortion 14-21 - much more dramatic
(= effect-idx 14) {:effect swirl :strength (bind bass values :range [-6 6])}
(= effect-idx 15) {:effect fisheye :strength (bind bass values :range [-0.5 0.8])}
(= effect-idx 16) {:effect wave :amplitude (bind bass values :range [10 60]) :wavelength 60}
(= effect-idx 17) {:effect ripple :amplitude (bind bass values :range [10 40]) :frequency 6}
(= effect-idx 18) {:effect kaleidoscope :segments 6 :rotation_speed 30}
(= effect-idx 19) {:effect zoom :factor (bind bass values :range [0.8 1.5])}
(= effect-idx 20) {:effect rotate :angle (bind energy values :range [-30 30])}
(= effect-idx 21) {:effect mirror :direction "horizontal"}
;; Stylization 22-28 - more variation
(= effect-idx 22) {:effect pixelate :block_size (bind bass values :range [4 32])}
(= effect-idx 23) {:effect ascii_art :char_size 8 :color_mode "color"}
(= effect-idx 24) {:effect ascii_zones :char_size 10}
(= effect-idx 25) {:effect edge_detect :low 50 :high 150}
(= effect-idx 26) {:effect emboss :strength 1.5}
(= effect-idx 27) {:effect outline :thickness 2}
(= effect-idx 28) {:effect neon_glow :glow_radius 20 :glow_intensity 2}
;; Retro/film 29-33
(= effect-idx 29) {:effect crt :line_spacing 3 :vignette_amount 0.3}
(= effect-idx 30) {:effect scanlines :spacing 3 :intensity 0.4}
(= effect-idx 31) {:effect film_grain :intensity 0.25}
(= effect-idx 32) {:effect vignette :strength 0.6}
(= effect-idx 33) {:effect noise :amount (bind bass values :range [10 80])}
;; Chromatic 34 - bigger split
(= effect-idx 34) {:effect rgb_split :offset_x (bind bass values :range [5 40])}
;; Temporal 35-37
(= effect-idx 35) {:effect echo :num_echoes 4 :decay 0.5}
(= effect-idx 36) {:effect trails :persistence 0.7}
(= effect-idx 37) {:effect strobe :frequency 4}
;; Geometric 38-39
(= effect-idx 38) {:effect flip :direction "horizontal"}
(= effect-idx 39) {:effect tile_grid :rows 2 :cols 2}
;; Glitch 40-41 - more glitchy
(= effect-idx 40) {:effect pixelsort :threshold_low 30 :threshold_high 220}
(= effect-idx 41) {:effect datamosh :corruption (bind bass values :range [0.2 0.8]) :block_size 24}
;; Default fallback
:else {:effect invert})]
{:source src
:start wrapped-start
:duration seg-duration
:effects (list fx)
:acc (inc acc)}))))))
;; Error if no segments were created (all videos too short)
(assert (> (len segments) 0) "No segments created - all videos too short for segment durations")
;; Sequence all segments
(def showcase (-> segments
(sequence :resize-mode :fit :priority :width)))
;; Output with original audio
(mux showcase audio))