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>
173 lines
11 KiB
Common Lisp
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))
|