;; 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)))