luv

Workshop wiki

capture-specification.lisp

hal/capture-specification.lisp

system luv · 33 definitions · on GitHub

Named, reproducible media recipes for wiki figures.

A recipe is source; its PNG or MP4 is a disposable build product. The protocol deliberately knows nothing about SSH hosts or artifact stores: ASDF performs it on whichever GPU machine invoked the operation. #IVRWI8

in-package#:luv
defclasscapture-specification
name:initarg:name:readercapture-specification-name
figure-id:initarg:figure-id:readercapture-specification-figure-id
kind:initarg:kind:readercapture-specification-kind
description:initarg:description:readercapture-specification-description
extension:initarg:extension:readercapture-specification-extension
layout:initarg:layout:initform:landscape:readercapture-specification-layout
renderer:initarg:renderer:readercapture-specification-renderer
:documentation

A named recipe for one generated wiki image or video.

The semantic metadata is inspectable and retained in source; renderer is the ordinary named function installed by define-capture. Rendered bytes live under the capture output directory and do not belong in Git. #IVRWI8

defvar*capture-specifications*
make-hash-table:test#'equal
defconstant+capture-web-image-width+768"The default intrinsic width of a showcase card image derivative."
defconstant+capture-web-poster-width+480"The maximum intrinsic width of a showcase video poster."
defparameter*capture-web-image-widths*'
480768
"The deterministic responsive widths generated for each larger still."
defunnormalize-capture-name
name
string-downcase
etypecasename
symbol
symbol-namename
stringname
defunnormalize-capture-figure-id
id
let
id
string-upcase
etypecaseid
symbol
symbol-nameid
stringid
unless
and
every#'alphanumericpid
error"Capture figure ID ~S is not six alphanumeric characters."id
id
defunnormalize-capture-extension
let
extension
string-downcase
stringextension
unless
and
every#'alphanumericpextension
error"Capture extension ~S is not a simple file extension."extension
extension
defunnormalize-capture-layout
unless
memberlayout'
:landscape:portrait
error"Capture layout ~S is not :LANDSCAPE or :PORTRAIT."layout
layout
defunregister-capture-specification
specification

Install specification by name, replacing a live redefinition in place.

check-typespecificationcapture-specification
let
name
capture-specification-namespecification
setfspecification
specification
defuncapture-specifications

Return every registered capture specification in definition order.

loopfornamein*capture-specification-order*forspecification=whenspecificationcollectspecification
defunfind-capture-specification
name&key
errorpt

Find the capture recipe name, accepting either a symbol or a string.

let
cond
specificationspecification
errorp
error"No capture specification named ~S."name
tnil
defmacrodefine-capture
name
&keyfigurekindextension
description""
layout:landscape
pathname
&bodybody

Define one inspectable wiki capture recipe.

name is its command-line identity. figure is the stable six-character wiki figure ID; KIND is :IMAGE or :video; extension is the generated file suffix. layout is :LANDSCAPE by default or :PORTRAIT for uncropped 9:16 presentation. body is an ordinary named renderer function body with pathname bound to its requested output. Re-evaluating the definition replaces the recipe without leaving stale closures in the registry.

let
renderer
intern
formatnil"RENDER-~A-CAPTURE"
symbol-namename
symbol-packagename
`
progn
defun,renderer
,pathname
,description,@body
defuncapture-output-pathname
specificationdirectory

The deterministic media pathname for specification under directory.

merge-pathnames
formatnil"~A-~A.~A"
capture-specification-figure-idspecification
capture-specification-namespecification
capture-specification-extensionspecification
uiop:ensure-directory-pathnamedirectory
defuncapture-derived-media-pathname
pathnamesuffixextension

Return pathname with suffix appended to its name and a new extension.

make-pathname:name
formatnil"~A~A"
pathname-namepathname
suffix
:typeextension:defaultspathname
defuncapture-responsive-image-pathname

The deterministic card-sized WebP beside an original capture pathname.

capture-derived-media-pathnamepathname
formatnil"-~Dw"width
"webp"
defuncapture-video-poster-pathname
pathname

The deterministic card-sized WebP poster beside a captured film.

defuncapture-media-dimensions
pathname

Return the first video stream's width and height using pinned FFprobe.

let*
output
uiop:run-program
list"ffprobe""-v""error""-select_streams""v:0""-show_entries""stream=width,height""-of""csv=s=x:p=0"
uiop:native-namestringpathname
:output:string:error-output:interactive
dimensions
string-trim'
#\Space#\Tab#\Newline#\Return
output
separator
position#\xdimensions
unlessseparator
error"FFprobe returned no dimensions for ~A: ~S."pathnamedimensions
values
parse-integerdimensions:endseparator
parse-integerdimensions:start
1+separator
defunwrite-capture-web-image
sourcedestinationwidth

Downsample source to width as a deterministic photographic WebP.

formatt"capture web image: writing ~A at ~Dpx wide...~%"destinationwidth
finish-output
uiop:run-program
list"ffmpeg""-nostdin""-hide_banner""-loglevel""error""-y""-i"
uiop:native-namestringsource
"-frames:v""1""-vf"
formatnil"scale=~D:-2:flags=lanczos"width
"-c:v""libwebp""-lossless""0""-preset""photo""-quality""82""-map_metadata""-1"
uiop:native-namestringdestination
:output:interactive:error-output:interactive
destination
defunprepare-capture-web-media
specificationpathname

Create the small public-index derivative for captured media at pathname.

Image originals get 480w and 768w WebPs whenever those are true downscales. Films get a card-sized WebP poster from their first frame. Originals remain untouched and retain their stable capture identity. #IVRWI8

multiple-value-bind
declare
ignoreheight
ecase
capture-specification-kindspecification
:image
dolist
when
>widthresponsive-width
write-capture-web-imagepathname
capture-responsive-image-pathnamepathnameresponsive-width
responsive-width
defuncapture-manifest-entry
specificationdirectory

Describe specification's original and any generated web derivative.

let
pathname
capture-output-pathnamespecificationdirectory
multiple-value-bind
let
entry
list:name
capture-specification-namespecification
:figure
capture-specification-figure-idspecification
:kind
capture-specification-kindspecification
:file
file-namestringpathname
:layout
capture-specification-layoutspecification
:widthwidth:heightheight
ecase
capture-specification-kindspecification
:image
let
variants
loopforexpected-widthin*capture-web-image-widths*forresponsive=
capture-responsive-image-pathnamepathnameexpected-width
when
probe-fileresponsive
collect
multiple-value-bind
responsive-widthresponsive-height
list:file
file-namestringresponsive
:type"image/webp":widthresponsive-width:heightresponsive-height
whenvariants
setfentry
appendentry
list:variantsvariants
:video
let
when
probe-fileposter
multiple-value-bind
poster-widthposter-height
setfentry
appendentry
list:poster
list:file
file-namestringposter
:type"image/webp":widthposter-width:heightposter-height
entry
defgenericrender-capture
specificationpathname
:documentation

Render specification to pathname and return pathname.

defmethodrender-capture
pathname
funcall
capture-specification-rendererspecification
pathname
pathname
defuncapture-source-revision
ignore-errors
let
root
asdf:system-source-directory:luv
string-trim'
#\Space#\Tab#\Newline#\Return
uiop:run-program
list"git""-C"
uiop:native-namestringroot
"rev-parse""HEAD"
:output:string
defunwrite-capture-manifest
directoryspecifications
let
pathname
merge-pathnames"manifest.sexp"directory
with-open-file
streampathname:direction:output:if-exists:supersede:if-does-not-exist:create
with-standard-io-syntax
let
*print-readably*nil
pprint`
:version1:source-revision,:captures,
mapcar
lambda
specification
capture-manifest-entryspecificationdirectory
specifications
stream
pathname
defunrender-capture-set
directory&keynames

Render names, or every registered recipe, under directory.

Each recipe announces its start and completion so a GPU build never becomes an unexplained silent process. A manifest records stable names, wiki figure IDs, media kinds, filenames, and the source revision that produced the set.

let*
directory
uiop:ensure-directory-pathnamedirectory
specifications
ensure-directories-existdirectory
dolist
specificationspecifications
let
pathname
capture-output-pathnamespecificationdirectory
formatt"~&capture ~A: rendering ~A for #~A...~%"
capture-specification-namespecification
capture-specification-kindspecification
capture-specification-figure-idspecification
finish-output
render-capturespecificationpathname
unless
probe-filepathname
error"Capture ~A returned without writing ~A."
capture-specification-namespecification
pathname
prepare-capture-web-mediaspecificationpathname
formatt"capture ~A: wrote ~A~%"
capture-specification-namespecification
pathname
finish-output
write-capture-manifestdirectoryspecifications
mapcar
lambda
specification
capture-output-pathnamespecificationdirectory
specifications

ASDF is the local executor. Host selection (for example, invoking this operation over SSH) stays outside the repository and the capture graph.

defmethodasdf:selfward-operation
operationcapture-op
declare
ignoreoperation
'asdf:load-op
defmethodasdf:operation-done-p
operationcapture-op
systemasdf:system
declare
ignoreoperationsystem

Explicit capture builds mean “make fresh evidence”, even when a previous artifact exists.

nil
defuncapture-operation-output-directory
system
let
configured
uiop:getenv"LUV_CAPTURE_OUTPUT_DIRECTORY"
if
andconfigured
plusp
lengthconfigured
uiop:ensure-directory-pathnameconfigured
asdf:system-relative-pathnamesystem"build/wiki/media/"
defmethodasdf:output-files
operationcapture-op
systemasdf:system
declare
ignoreoperation
values
list
merge-pathnames"manifest.sexp"
t
defmethodasdf:perform
operationcapture-op
systemasdf:system
declare
ignoreoperation