Add ascii_dual_blend recipe and fix blend/layer effects
- Add ascii_dual_blend.sexp: blends two ASCII-processed videos synced to audio - Fix blend.sexp: add require-primitives, fix params syntax - Fix layer.sexp: add require-primitives - Use consistent (effect blend ...) syntax instead of special form Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
101
effects/ascii_dual_blend.sexp
Normal file
101
effects/ascii_dual_blend.sexp
Normal file
@@ -0,0 +1,101 @@
|
||||
;; 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")
|
||||
(audio_start :type float :default 60
|
||||
:desc "Start time in audio (seconds)")
|
||||
(duration :type float :default 10
|
||||
:desc "Duration (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 audio_start :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 audio_start :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)))
|
||||
@@ -3,17 +3,19 @@
|
||||
;; Params:
|
||||
;; mode - blend mode (add, multiply, screen, overlay, difference, lighten, darken, alpha)
|
||||
;; opacity - blend amount (0-1)
|
||||
;; resize-mode - how to resize frame-b to match frame-a (fit, crop, stretch)
|
||||
;; resize_mode - how to resize frame-b to match frame-a (fit, crop, stretch)
|
||||
;; priority - which dimension takes priority (width, height)
|
||||
;; pad-color - color for padding in fit mode [r g b]
|
||||
;; pad_color - color for padding in fit mode [r g b]
|
||||
|
||||
(require-primitives "image" "blending")
|
||||
|
||||
(define-effect blend
|
||||
:params (
|
||||
(mode :type string :default "overlay")
|
||||
(opacity :type float :default 0.5)
|
||||
(resize_mode :type string :default "fit")
|
||||
(priority :type string :default "width")
|
||||
(list :type string :default 0 0 0)
|
||||
)
|
||||
(pad_color :type list :default [0 0 0])
|
||||
)
|
||||
(let [a frame-a
|
||||
a-w (width a)
|
||||
@@ -24,31 +26,31 @@
|
||||
;; Calculate scale based on resize mode and priority
|
||||
scale-w (/ a-w b-w)
|
||||
scale-h (/ a-h b-h)
|
||||
scale (if (= resize-mode "stretch")
|
||||
scale (if (= resize_mode "stretch")
|
||||
1 ;; Will use explicit dimensions
|
||||
(if (= resize-mode "crop")
|
||||
(if (= resize_mode "crop")
|
||||
(max scale-w scale-h) ;; Scale to cover, then crop
|
||||
(if (= priority "width")
|
||||
scale-w
|
||||
scale-h)))
|
||||
;; For stretch, use target dimensions directly
|
||||
new-w (if (= resize-mode "stretch") a-w (round (* b-w scale)))
|
||||
new-h (if (= resize-mode "stretch") a-h (round (* b-h scale)))
|
||||
new-w (if (= resize_mode "stretch") a-w (round (* b-w scale)))
|
||||
new-h (if (= resize_mode "stretch") a-h (round (* b-h scale)))
|
||||
;; Resize b
|
||||
b-resized (resize b-raw new-w new-h "linear")
|
||||
;; Handle fit (pad) or crop to exact size
|
||||
b (if (= resize-mode "crop")
|
||||
b (if (= resize_mode "crop")
|
||||
;; Crop to center
|
||||
(let [cx (/ (- new-w a-w) 2)
|
||||
cy (/ (- new-h a-h) 2)]
|
||||
(crop b-resized cx cy a-w a-h))
|
||||
(if (and (= resize-mode "fit") (or (!= new-w a-w) (!= new-h a-h)))
|
||||
(if (and (= resize_mode "fit") (or (!= new-w a-w) (!= new-h a-h)))
|
||||
;; Pad to center
|
||||
(let [pad-x (/ (- a-w new-w) 2)
|
||||
pad-y (/ (- a-h new-h) 2)
|
||||
canvas (make-image a-w a-h pad-color)]
|
||||
canvas (make-image a-w a-h pad_color)]
|
||||
(paste canvas b-resized pad-x pad-y))
|
||||
b-resized))]
|
||||
(if (= mode "alpha")
|
||||
(blend-images a b opacity)
|
||||
(blend-images a (blend-mode a b mode) opacity)))
|
||||
(blend-images a (blend-mode a b mode) opacity))))
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
;; Multi-input effect: uses frame-a (background) and frame-b (overlay)
|
||||
;; Params: x, y (position), opacity (0-1), mode (blend mode)
|
||||
|
||||
(require-primitives "image" "blending")
|
||||
|
||||
(define-effect layer
|
||||
:params (
|
||||
(x :type int :default 0)
|
||||
|
||||
Reference in New Issue
Block a user