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>
This commit is contained in:
gilesb
2026-01-19 12:34:45 +00:00
commit 406cc7c0c7
171 changed files with 13406 additions and 0 deletions

63
libs/all-effects.sexp Normal file
View File

@@ -0,0 +1,63 @@
;; All 42 Sexp Effects
;; Include with: (include :path "libs/all-effects.sexp")
;; Or from cache: (include :cid "bafy...")
;; Color effects
(effect invert :path "sexp_effects/effects/invert.sexp")
(effect grayscale :path "sexp_effects/effects/grayscale.sexp")
(effect sepia :path "sexp_effects/effects/sepia.sexp")
(effect brightness :path "sexp_effects/effects/brightness.sexp")
(effect contrast :path "sexp_effects/effects/contrast.sexp")
(effect saturation :path "sexp_effects/effects/saturation.sexp")
(effect hue_shift :path "sexp_effects/effects/hue_shift.sexp")
(effect color_cycle :path "sexp_effects/effects/color_cycle.sexp")
(effect threshold :path "sexp_effects/effects/threshold.sexp")
(effect posterize :path "sexp_effects/effects/posterize.sexp")
;; Blur/sharpen
(effect blur :path "sexp_effects/effects/blur.sexp")
(effect sharpen :path "sexp_effects/effects/sharpen.sexp")
(effect bloom :path "sexp_effects/effects/bloom.sexp")
(effect color-adjust :path "sexp_effects/effects/color-adjust.sexp")
;; Distortion
(effect swirl :path "sexp_effects/effects/swirl.sexp")
(effect fisheye :path "sexp_effects/effects/fisheye.sexp")
(effect wave :path "sexp_effects/effects/wave.sexp")
(effect ripple :path "sexp_effects/effects/ripple.sexp")
(effect kaleidoscope :path "sexp_effects/effects/kaleidoscope.sexp")
(effect zoom :path "sexp_effects/effects/zoom.sexp")
(effect rotate :path "sexp_effects/effects/rotate.sexp")
(effect mirror :path "sexp_effects/effects/mirror.sexp")
;; Stylization
(effect pixelate :path "sexp_effects/effects/pixelate.sexp")
(effect ascii_art :path "sexp_effects/effects/ascii_art.sexp")
(effect ascii_zones :path "sexp_effects/effects/ascii_zones.sexp")
(effect edge_detect :path "sexp_effects/effects/edge_detect.sexp")
(effect emboss :path "sexp_effects/effects/emboss.sexp")
(effect outline :path "sexp_effects/effects/outline.sexp")
(effect neon_glow :path "sexp_effects/effects/neon_glow.sexp")
;; Retro/film
(effect crt :path "sexp_effects/effects/crt.sexp")
(effect scanlines :path "sexp_effects/effects/scanlines.sexp")
(effect film_grain :path "sexp_effects/effects/film_grain.sexp")
(effect vignette :path "sexp_effects/effects/vignette.sexp")
(effect noise :path "sexp_effects/effects/noise.sexp")
;; Chromatic
(effect rgb_split :path "sexp_effects/effects/rgb_split.sexp")
;; Temporal
(effect echo :path "sexp_effects/effects/echo.sexp")
(effect trails :path "sexp_effects/effects/trails.sexp")
(effect strobe :path "sexp_effects/effects/strobe.sexp")
;; Geometric
(effect flip :path "sexp_effects/effects/flip.sexp")
(effect tile_grid :path "sexp_effects/effects/tile_grid.sexp")
;; Glitch
(effect pixelsort :path "sexp_effects/effects/pixelsort.sexp")
(effect datamosh :path "sexp_effects/effects/datamosh.sexp")

68
libs/plan Normal file
View File

@@ -0,0 +1,68 @@
Exactly. You're describing a DAG of pipelines that can branch and merge:
audio-a ─→ analyze ─→ plan-a ─┐
├─→ combine ─→ final
audio-b ─→ analyze ─→ plan-b ─┘
videos ─→ analyze ─────────────┴─→ (shared by both plans)
Each node is independently cacheable. Parallel branches run in tandem.
A clean syntax might be:
(recipe "multi-track-video"
:encoding (...)
;; Sources (stage 0 - always available)
(def audio-a (source "track1.mp3"))
(def audio-b (source "track2.mp3"))
(def videos (source-glob "videos/*.mp4"))
;; Analysis stages (run in parallel, cached by input hash)
(stage :analyze-a
(def beats-a (-> audio-a (analyze beats))))
(stage :analyze-b
(def beats-b (-> audio-b (analyze beats))))
(stage :analyze-videos
(def video-infos (-> videos (analyze-each video-info))))
;; Planning stages (depend on analysis, explicit deps)
(stage :plan-a :requires [:analyze-a :analyze-videos]
(def segments-a (make-segments :beats beats-a :video-infos video-infos)))
(stage :plan-b :requires [:analyze-b :analyze-videos]
(def segments-b (make-segments :beats beats-b :video-infos video-infos)))
;; Render stages (can parallelize)
(stage :render-a :requires [:plan-a]
(def rendered-a (-> segments-a (sequence))))
(stage :render-b :requires [:plan-b]
(def rendered-b (-> segments-b (sequence))))
;; Final combine
(stage :output :requires [:render-a :render-b]
(-> (list rendered-a rendered-b)
(concat)
(crossfade :duration 2)
(mux audio-a audio-b))))
What this gives you:
1. Explicit data availability - :requires declares what's available
2. Parallel execution - :analyze-a and :analyze-b run simultaneously
3. Granular caching - each stage output cached by its inputs' hashes
4. Flexible composition - add more tracks, branches, merge points as needed
5. Clear errors - referencing beats-a before :analyze-a is a compile error
Changes needed to sexp system:
1. stage form with :requires dependency declaration
2. Stage scheduler that builds execution DAG
3. Cache layer keyed by stage + input hashes
4. Dict iteration (keys, for-each) for generic constructs
Want to prototype this direction?

View File

@@ -0,0 +1,11 @@
;; Standard Analyzers (Audio + Video)
;; Include with: (include :path "libs/standard-analyzers.sexp")
;; Or from cache: (include :cid "bafy...")
;; Audio 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")
;; Video analyzers
(analyzer video-info :path "../artdag-analyzers/video-info/analyzer.py")

View File

@@ -0,0 +1,6 @@
;; Standard Constructs
;; Include with: (include :path "libs/standard-constructs.sexp")
;; Or from cache: (include :cid "bafy...")
(construct slice-every-n :path "constructs/slice-every-n.sexp")
(construct cycle-effects-preset :path "constructs/cycle-effects-preset.sexp")