;; Quick Test Recipe ;; ;; Cycles between three video pairs (monday, duel, ecstacy) with smooth zoom-based crossfade. ;; Each pair is two copies of the same source with opposite rotations. ;; Each pair rotates in its own direction (per-pair rotation via template). ;; Cycle: active pair plays -> crossfade -> new pair plays -> advance and repeat. ;; Ripple drops on the final combined output only. (recipe "quick_test" :version "1.0" :description "Cycling crossfade between three video pairs" :minimal-primitives true :encoding (:codec "libx264" :crf 23 :preset "ultrafast" :audio-codec "aac" :fps 30) :params ( (audio_start :type float :default 60 :range [0 300] :desc "Audio start time in seconds") (audio_duration :type float :default nil :desc "Audio duration (nil = full remaining)") (blend_opacity :type float :default 0.5 :range [0 1] :desc "Blend opacity within each pair") (seed :type int :default 42 :desc "Master random seed") ) ;; Registry (effect rotate :path "../sexp_effects/effects/rotate.sexp") (effect zoom :path "../sexp_effects/effects/zoom.sexp") (effect blend :path "../sexp_effects/effects/blend.sexp") (effect invert :path "../sexp_effects/effects/invert.sexp") (effect hue_shift :path "../sexp_effects/effects/hue_shift.sexp") (effect ascii_art :path "../sexp_effects/effects/ascii_art.sexp") (effect ripple :path "../sexp_effects/effects/ripple.sexp") (effect blend_multi :path "../sexp_effects/effects/blend_multi.sexp") (analyzer energy :path "../../artdag-analyzers/energy/analyzer.py") (analyzer beats :path "../../artdag-analyzers/beats/analyzer.py") ;; Sources (def video-1 (source :path "../1.mp4")) (def video-2 (source :path "../2.webm")) (def video-4 (source :path "../4.mp4")) (def video-5 (source :path "../5.mp4")) (def video-a (source :path "../monday.webm")) (def video-b (source :path "../escher.webm")) (def video-c (source :path "../dopple.webm")) (def video-d (source :path "../disruptors.webm")) (def video-e (source :path "../ecstacy.mp4")) (def audio (source :path "../dizzy.mp3")) ;; Templates: reusable video-pair processor and cycle-crossfade (include :path "../templates/process-pair.sexp") (include :path "../templates/cycle-crossfade.sexp") ;; Unified RNG: auto-derives unique seeds for all scans (def rng (make-rng seed)) ;; Stage 1: Analysis - energy, beats, and global-level scans (stage :analyze :outputs [energy-data beat-data whole-spin ripple-gate ripple-cx ripple-cy] (def audio-clip (-> audio (segment :start audio_start :duration audio_duration))) (def energy-data (-> audio-clip (analyze energy))) (def beat-data (-> audio-clip (analyze beats))) ;; --- Whole-video continuous spin: cumulative rotation that reverses direction periodically --- (def whole-spin (scan beat-data :rng rng :init (dict :beat 0 :clen 25 :dir 1 :angle 0) :step (if (< (+ beat 1) clen) (dict :beat (+ beat 1) :clen clen :dir dir :angle (+ angle (* dir (/ 360 clen)))) (dict :beat 0 :clen (rand-int 20 30) :dir (* dir -1) :angle angle)) :emit angle)) ;; --- Ripple drops on final output --- (def ripple (scan beat-data :rng rng :init (dict :rem 0 :cx 0.5 :cy 0.5) :step (if (> rem 0) (dict :rem (- rem 1) :cx cx :cy cy) (if (< (rand) 0.05) (dict :rem (rand-int 1 20) :cx (rand-range 0.1 0.9) :cy (rand-range 0.1 0.9)) (dict :rem 0 :cx 0.5 :cy 0.5))) :emit {:gate (if (> rem 0) 1 0) :cx cx :cy cy}))) ;; Stage 2: Process videos via template ;; Per-pair scans (inv/hue/ascii triggers, pair-mix, pair-rot) are now ;; defined inside the process-pair template using seed offsets. (stage :process :requires [:analyze] :inputs [energy-data beat-data whole-spin ripple-gate ripple-cx ripple-cy] :outputs [final-video audio-clip] ;; Re-segment audio for final mux (def audio-clip (-> audio (segment :start audio_start :duration audio_duration))) ;; --- Process each pair via template --- (def monday-blend (process-pair :video video-a :energy energy-data :beat-data beat-data :rng rng :rot-dir -1 :rot-a [0 45] :rot-b [0 -45] :zoom-a [1 1.5] :zoom-b [1 0.5])) (def escher-blend (process-pair :video video-b :energy energy-data :beat-data beat-data :rng rng :rot-dir 1 :rot-a [0 45] :rot-b [0 -45] :zoom-a [1 1.5] :zoom-b [1 0.5])) (def duel-blend (process-pair :video video-d :energy energy-data :beat-data beat-data :rng rng :rot-dir -1 :rot-a [0 -45] :rot-b [0 45] :zoom-a [1 0.5] :zoom-b [1 1.5])) (def blend-2 (process-pair :video video-2 :energy energy-data :beat-data beat-data :rng rng :rot-dir 1 :rot-a [0 45] :rot-b [0 -45] :zoom-a [1 1.5] :zoom-b [1 0.5])) (def dopple-blend (process-pair :video video-c :energy energy-data :beat-data beat-data :rng rng :rot-dir -1 :rot-a [0 -45] :rot-b [0 45] :zoom-a [1 0.5] :zoom-b [1 1.5])) (def blend-4 (process-pair :video video-4 :energy energy-data :beat-data beat-data :rng rng :rot-dir -1 :rot-a [0 45] :rot-b [0 -45] :zoom-a [1 1.5] :zoom-b [1 0.5])) (def ext-blend (process-pair :video video-e :energy energy-data :beat-data beat-data :rng rng :rot-dir 1 :rot-a [0 30] :rot-b [0 -30] :zoom-a [1 1.3] :zoom-b [1 0.7])) (def blend-5 (process-pair :video video-5 :energy energy-data :beat-data beat-data :rng rng :rot-dir 1 :rot-a [0 45] :rot-b [0 -45] :zoom-a [1 1.5] :zoom-b [1 0.5])) ;; --- Cycle zoom + crossfade via template --- (def combined (cycle-crossfade :beat-data beat-data :input-videos [monday-blend escher-blend blend-2 duel-blend blend-4 ext-blend dopple-blend blend-5] :init-clen 60)) ;; --- Final output: sporadic spin + ripple --- (def final-video (-> combined (effect rotate :angle (bind whole-spin values)) (effect ripple :amplitude (* (bind ripple-gate values) (bind energy-data values :range [5 50])) :center_x (bind ripple-cx values) :center_y (bind ripple-cy values) :frequency 8 :decay 2 :speed 5)))) ;; Stage 3: Output (stage :output :requires [:process] :inputs [final-video audio-clip] (mux final-video audio-clip)))