"...because the geometry of the abyss demands it"
This commit is contained in:
parent
3ca50a0001
commit
e33aae5ea9
2 changed files with 303 additions and 0 deletions
60
README.org
Normal file
60
README.org
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
|
||||||
|
* time, space, stacking, slicing, scanning, etc
|
||||||
|
|
||||||
|
"Slitscan imaging techniques are used to create static images of time-based phenomena. In traditional film photography, slit scan images are created by exposing film as it slides past a slit-shaped aperture. In the digital realm, thin slices are extracted from a sequence of video frames, and concatenated into a new image."
|
||||||
|
|
||||||
|
[[http://www.flong.com/texts/lists/slit_scan/][An Informal Catalogue of Slit-Scan Video Artworks and Research]] Compiled by Golan Levin.
|
||||||
|
|
||||||
|
#+ATTR_HTML: :style margin-left: auto; margin-right: auto;
|
||||||
|
#+name: unfolding
|
||||||
|
#+caption: Egbert Mittelstaedt. Unfolding (2002)
|
||||||
|
[[http://www.flong.com/storage/images/texts/slit_scan/34_egbert_mittelstaedt_665px.jpg]]
|
||||||
|
|
||||||
|
This particular slitscan provides some slightly configurable scaffolding around ffmpeg (based on a blogpost from [[http://oioiiooixiii.blogspot.com/2017/11/ffmpeg-temporal-slice-stacking-effect.html][oioiiooixiii]]) and is intended for non-realtime rendering at various resolutions. it's neither first, nor last. it's not especially interesting, educational or efficient. just another cloudform in the endless space of possible implementation...
|
||||||
|
|
||||||
|
* installation, configuration, operation, etc
|
||||||
|
|
||||||
|
install (mac)...
|
||||||
|
#+BEGIN_SRC shell
|
||||||
|
brew install ffmpeg drracket
|
||||||
|
git clone https://github.com/zzkt/slitscan.git
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
install (debian/ubuntu)...
|
||||||
|
#+BEGIN_SRC shell
|
||||||
|
sudo apt install ffmpeg racket
|
||||||
|
git clone https://github.com/zzkt/slitscan.git
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
simple transform...
|
||||||
|
#+BEGIN_SRC shell
|
||||||
|
racket slitscan.rkt -v -i <input.mp4>
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
transform at 1080p and delete working files, with minimal command line noise...
|
||||||
|
#+BEGIN_SRC shell
|
||||||
|
racket slitscan.rkt --width 1920 --height 1080 --loglevel quiet --cleanup -i <input.mp4>
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
options...
|
||||||
|
#+BEGIN_SRC shell
|
||||||
|
slitscan [ <option> ... ]
|
||||||
|
where <option> is one of
|
||||||
|
-v, --verbose : various verbose messages
|
||||||
|
-l <level>, --loglevel <level> : loglevel for ffmpeg e.g. quiet, error, warning, info, debug
|
||||||
|
-c, --cleanup : Clean up temporary/working files
|
||||||
|
--horizontal : output a horizontal video only (default: both)
|
||||||
|
--vertical : output a vertical video only (default: both)
|
||||||
|
--width <pixels> : width of output output video
|
||||||
|
--height <pixels> : height of output output video
|
||||||
|
-i <video>, --input <video> : input file
|
||||||
|
-f <folder>, --folder <folder> : input folder
|
||||||
|
--help, -h : Show this help
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
|
* further reading
|
||||||
|
|
||||||
|
- The Fourth Dimension and How to Get There by Rudy Rucker
|
||||||
|
- Muybridge's Complete Human and Animal Locomotion, Vol. I
|
||||||
|
- Baroness Elsa, by Irene Gammel
|
243
slitscan.rkt
Executable file
243
slitscan.rkt
Executable file
|
@ -0,0 +1,243 @@
|
||||||
|
#! /usr/bin/env racket
|
||||||
|
#lang racket
|
||||||
|
|
||||||
|
;;; Temporal slice-stacking with FFmpeg (aka slitscan, etc+)
|
||||||
|
;;;
|
||||||
|
;;; Copyright (C) 2019 FoAM
|
||||||
|
;;;
|
||||||
|
;;; This program is free software: you can redistribute it and/or modify
|
||||||
|
;;; it under the terms of the GNU General Public License as published by
|
||||||
|
;;; the Free Software Foundation, either version 3 of the License, or
|
||||||
|
;;; (at your option) any later version.
|
||||||
|
;;;
|
||||||
|
;;; This program is distributed in the hope that it will be useful,
|
||||||
|
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
;;; GNU General Public License for more details.
|
||||||
|
;;;
|
||||||
|
;;; You should have received a copy of the GNU General Public License
|
||||||
|
;;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
;;;
|
||||||
|
;;;
|
||||||
|
;;; Authors
|
||||||
|
;;;
|
||||||
|
;;; nik gaffney <nik@fo.am>
|
||||||
|
;;;
|
||||||
|
;;; Requirements
|
||||||
|
;;;
|
||||||
|
;;; Racket, ffmpeg and zsh or bash (probably)
|
||||||
|
;;;
|
||||||
|
;;; Commentary
|
||||||
|
;;;
|
||||||
|
;;; Mostly scaffolding around ffmpeg, based on a shell script from oioiiooixiii
|
||||||
|
;;; http://oioiiooixiii.blogspot.com/2017/11/ffmpeg-temporal-slice-stacking-effect.html
|
||||||
|
;;;
|
||||||
|
;;; Further details, instructions and digression can be found at
|
||||||
|
;;; https://github.com/zzkt/slitscan/
|
||||||
|
;;;
|
||||||
|
;;; Revision history
|
||||||
|
;;;
|
||||||
|
;;; - 2017-10-01 - shell script from oioiiooixiii
|
||||||
|
;;; - 2019-08-08 - cmprzs, convert, reconstruct
|
||||||
|
;;; - 2019-08-10 - racket translation, info
|
||||||
|
|
||||||
|
|
||||||
|
;; cli options
|
||||||
|
(define verbose? (make-parameter #f))
|
||||||
|
(define cleanup? (make-parameter #f))
|
||||||
|
(define filename (make-parameter "untitled"))
|
||||||
|
(define input-folder (make-parameter ""))
|
||||||
|
(define loglevel (make-parameter "")) ;; leave unset by default
|
||||||
|
|
||||||
|
;; output options horiz/vert/both
|
||||||
|
(define hout-only? (make-parameter #f))
|
||||||
|
(define vout-only? (make-parameter #f))
|
||||||
|
|
||||||
|
(define output-width (make-parameter 640))
|
||||||
|
(define output-height (make-parameter 360))
|
||||||
|
|
||||||
|
;; parse command line
|
||||||
|
(define getopt
|
||||||
|
(command-line
|
||||||
|
#:program "slitscan"
|
||||||
|
#:once-each
|
||||||
|
(("-v" "--verbose") "various verbose messages" (verbose? #t))
|
||||||
|
(("-l" "--loglevel") level "loglevel for ffmpeg e.g. quiet, error, warning, info, debug" (loglevel level))
|
||||||
|
(("-c" "--cleanup") "Clean up temporary/working files" (cleanup? #t))
|
||||||
|
(("--horizontal") "output a horizontal video only (default: both)" (hout-only? #t))
|
||||||
|
(("--vertical") "output a vertical video only (default: both)" (vout-only? #t))
|
||||||
|
(("--width") pixels "width of transform video" (output-width pixels))
|
||||||
|
(("--height") pixels "height of tansform video" (output-height pixels))
|
||||||
|
(("-i" "--input") video "input file" (filename video))
|
||||||
|
(("-f" "--folder") folder "input folder" (input-folder folder))
|
||||||
|
#:args () ; rest of the args?
|
||||||
|
(printf "slitscanning: ~a\n" (filename))))
|
||||||
|
|
||||||
|
;; emulation of $(...) shell syntax
|
||||||
|
(define-syntax zout
|
||||||
|
(syntax-rules ()
|
||||||
|
((_ cmd) (string-trim (with-output-to-string (lambda () (system cmd)))))))
|
||||||
|
|
||||||
|
;; echoing verbosity
|
||||||
|
(define-syntax vecho
|
||||||
|
(syntax-rules ()
|
||||||
|
((_ str ...) (when (verbose?) (printf str ...)))))
|
||||||
|
|
||||||
|
(cond ((and (verbose?) (string=? "" (loglevel))) (loglevel "info"))
|
||||||
|
((and (not (verbose?)) (string=? "" (loglevel))) (loglevel "error")))
|
||||||
|
|
||||||
|
;; variables/various
|
||||||
|
(define folder (if (not (string=? "" (input-folder)))
|
||||||
|
(input-folder)
|
||||||
|
(zout "mktemp -d")))
|
||||||
|
|
||||||
|
(vecho "using folder: ~a\n" folder)
|
||||||
|
|
||||||
|
;(define fffeature "ffprobe -v quiet -select_streams v -of default=noprint_wrappers=1:nokey=1 -show_entries stream=")
|
||||||
|
|
||||||
|
(define fffeature "ffprobe -v error -of default=noprint_wrappers=1:nokey=1 -show_entries format=")
|
||||||
|
|
||||||
|
;; assume most/all codecs/formats/streams have a duration
|
||||||
|
(define duration (let ((f (string->number
|
||||||
|
(zout (format
|
||||||
|
"~aduration \"~a\""
|
||||||
|
fffeature (filename))))))
|
||||||
|
(if f f 0)))
|
||||||
|
|
||||||
|
(vecho "duration: ~a seconds\n" duration)
|
||||||
|
|
||||||
|
;; determine fps from input file or default to 24
|
||||||
|
(define fps (let ((f (string->number
|
||||||
|
(zout (format
|
||||||
|
"~ar_frame_rate \"~a\""
|
||||||
|
fffeature (filename))))))
|
||||||
|
(if f f 24)))
|
||||||
|
|
||||||
|
(vecho "fps: ~a\n" fps)
|
||||||
|
|
||||||
|
;; number of frames. estimate for codecs/formats/streams without nb_frames
|
||||||
|
(define frames (let ((f (string->number
|
||||||
|
(zout (format
|
||||||
|
"~anb_frames \"~a\""
|
||||||
|
fffeature (filename))))))
|
||||||
|
(if f f (ceiling (* duration fps)))))
|
||||||
|
|
||||||
|
(vecho "frames: ~a\n" frames)
|
||||||
|
|
||||||
|
;; output dimensions. change if/as required via opts...
|
||||||
|
(define w (output-width))
|
||||||
|
(define h (output-height))
|
||||||
|
|
||||||
|
(vecho "transform dimensions: ~ax~a\n" w h)
|
||||||
|
|
||||||
|
;; FFmpeg Filterchains
|
||||||
|
(define stemStart "select=gte(n\\,")
|
||||||
|
(define stemEnd "),format=yuv444p,split[horz][vert]")
|
||||||
|
(define horz (format "[horz]crop=in_w:1:0:n,tile=1x~a[horz]" h))
|
||||||
|
(define vert (format "[vert]crop=1:in_h:n:0,tile=~aX1[vert]" w))
|
||||||
|
(define merge "[0:v]null[horz];[1:v]null[vert]")
|
||||||
|
(define scale (format "scale=~a:~a" w h))
|
||||||
|
|
||||||
|
;; check resume?
|
||||||
|
|
||||||
|
;; rezise video
|
||||||
|
(define resized-video (string-append folder "/resized.mkv"))
|
||||||
|
|
||||||
|
(define (resize)
|
||||||
|
(define ffmpeg-r1
|
||||||
|
(format "ffmpeg -loglevel ~a -i \"~a\" -vf ~a -crf 10 \"~a\" 2>&1 | grep 'frame=' | tr \\n \\r; echo"
|
||||||
|
(loglevel) (filename) scale resized-video))
|
||||||
|
(vecho "resizing video: ~a\n" resized-video)
|
||||||
|
(system ffmpeg-r1))
|
||||||
|
|
||||||
|
|
||||||
|
;; slice/crop/loop
|
||||||
|
(define (slice)
|
||||||
|
(for ((i (range frames)))
|
||||||
|
(vecho "Frame: ~a of ~a\r" i frames)
|
||||||
|
(define ffmpeg-s1
|
||||||
|
(format
|
||||||
|
"ffmpeg -loglevel ~a -i \"~a\" \
|
||||||
|
-filter_complex \"~a~a~a;~a;~a\" \
|
||||||
|
-map '[horz]' \
|
||||||
|
-vframes 1 ~a/horz_frame~a.png \
|
||||||
|
-map '[vert]' \
|
||||||
|
-vframes 1 ~a/vert_frame~a.png"
|
||||||
|
(loglevel) resized-video
|
||||||
|
stemStart i stemEnd horz vert
|
||||||
|
folder i
|
||||||
|
folder i))
|
||||||
|
(system ffmpeg-s1))
|
||||||
|
(vecho "\n"))
|
||||||
|
|
||||||
|
|
||||||
|
;; Join images (optional sharpening, upscale, etc. via 'merge' variable)
|
||||||
|
(define (assemble)
|
||||||
|
(vecho "Creating output videos\n")
|
||||||
|
|
||||||
|
(define ffmpeg-a0 ;; horizontal & vertical videos (from .sh)
|
||||||
|
(format
|
||||||
|
"ffmpeg -loglevel ~a \
|
||||||
|
-r ~a -i ~a/horz_frame%d.png \
|
||||||
|
-r ~a -i ~a/vert_frame%d.png \
|
||||||
|
-filter_complex \"~a\" \
|
||||||
|
-map '[horz]' \
|
||||||
|
-r ~a \
|
||||||
|
-crf 10 \
|
||||||
|
\"~a_horizontal-smear.mkv\" \
|
||||||
|
-map '[vert]' \
|
||||||
|
-r ~a \
|
||||||
|
-crf 10 \
|
||||||
|
\"~a_vertical-smear.mkv\""
|
||||||
|
(loglevel)
|
||||||
|
fps folder
|
||||||
|
fps folder
|
||||||
|
merge
|
||||||
|
fps (filename)
|
||||||
|
fps (filename)))
|
||||||
|
|
||||||
|
(define ffmpeg-a1 ;; horizontal frame stack
|
||||||
|
(format
|
||||||
|
"ffmpeg -loglevel ~a -r ~a -i ~a/horz_frame%d.png \"~a_horizontal-smear.mkv\""
|
||||||
|
(loglevel) fps folder (filename)))
|
||||||
|
|
||||||
|
(define ffmpeg-a2 ;; vertical frame stack
|
||||||
|
(format
|
||||||
|
"ffmpeg -loglevel ~a -r ~a -i ~a/vert_frame%d.png \"~a_vertical-smear.mkv\""
|
||||||
|
(loglevel) fps folder (filename)))
|
||||||
|
|
||||||
|
;; assemble
|
||||||
|
(cond ((hout-only?) (system ffmpeg-a1))
|
||||||
|
((vout-only?) (system ffmpeg-a2))
|
||||||
|
(else (system ffmpeg-a1)
|
||||||
|
(system ffmpeg-a2))))
|
||||||
|
|
||||||
|
;; spatial extents (frames -> image) summary
|
||||||
|
(define (framecat start offset)
|
||||||
|
(define montage-h1
|
||||||
|
(format "montage ~a/horz_frame~a.png ~a/horz_frame~a.png ~a/horz_frame~a.png ~a/horz_frame~a.png ~a/horz_frame~a.png -tile x1 -mode concatenate ~a-slitscan-s~a.o~a.jpg"
|
||||||
|
folder start
|
||||||
|
folder (+ start offset)
|
||||||
|
folder (+ start (* offset 2))
|
||||||
|
folder (+ start (* offset 3))
|
||||||
|
folder (+ start (* offset 4))
|
||||||
|
(filename) start offset))
|
||||||
|
(system montage-h1))
|
||||||
|
|
||||||
|
(define (image) (framecat 1 (/ frames 5)))
|
||||||
|
|
||||||
|
;; delete working files (or not)
|
||||||
|
(define (cleanup)
|
||||||
|
(if (cleanup?)
|
||||||
|
(begin (system (format "rm -rf ~a" folder))
|
||||||
|
(printf "Removed temp files at ~a\n" folder))
|
||||||
|
(vecho "leaving working files at ~a\n" folder)))
|
||||||
|
|
||||||
|
;; output in 3 (or 4 (or 5)) steps
|
||||||
|
|
||||||
|
(when (resize) (printf "resized\n"))
|
||||||
|
(when (slice) (printf "slicwd\n"))
|
||||||
|
(when (assemble) (printf "assembled\n"))
|
||||||
|
(when (cleanup) (printf "done\n"))
|
||||||
|
|
||||||
|
;;; FIN
|
Loading…
Reference in a new issue