@labcat2020/p5.audioreactive-capture-cli
v0.1.2
Published
Headless capture CLI — Playwright + explicit viewport → per-frame R2 upload (additive over p5.capture.js ZIP fallback)
Maintainers
Readme
@labcat2020/p5.audioreactive-capture-cli
Headless capture CLI for LABCAT audio-reactive sketches — additive over the existing p5.capture.js ZIP fallback.
- Opens the local/dev sketch URL in headless Chromium (Playwright)
- Sets explicit viewport for portrait vs landscape
- Triggers the existing deterministic capture loop (audio seek via
_lastPos, MIDI cues, FFT from buffer,draw()per frame @ 60 fps, CSS-background compositing) — no?exporting=truegate, no Socket.io, notoDataURLover the wire, noframeRate(2)live loop - Uploads each frame to R2 as written (or small concurrency batches); no ZIP required for automation. Local
toBlob→ PNG → R2 (putObject). CSS background is included where sketches usecaptureCSSBackground/compositeDomCaptureExtension. - Local in-browser PNG blobs → ZIP download fallback remains untouched (Chrome multi-download mitigation still applies there).
Borrowed plumbing from Pelletier-Auger p5.js template: headless trigger + off-browser delivery + explicit viewport only. Intentionally not adopting Socket.io/server.js, toDataURL transport, or throttled live-loop export.
Install
From the lab repo (sibling of series repos):
pnpm install
# Playwright browser (once)
npx playwright install chromiumSeries repos consume p5.audioreactive-capture@^0.1.3 (adds headless hook) from npm. Local pnpm workspace links when the lab sibling is present.
CLI
labcat-capture --repo <path> --sketch <name> --frames 100 --orient portrait|landscape [options]
# also available as `capture`| Option | Description |
|--------|-------------|
| --repo <path> | Path to checked-out series repo (e.g. ../triangles). Required unless --url given |
| --sketch <name> | Sketch name or route id (TrianglesNo1, TrianglesNo1.js, number-1). Resolved via src/sketchMetadata.js |
| --frames <n> | Frames to capture (default: full audio duration @60fps; test: 100) |
| --orient <portrait\|landscape> | Explicit viewport (landscape 1920×1080, portrait 1080×1920). Override with --width/--height |
| --width <n> --height <n> | Explicit viewport override |
| --r2-prefix <prefix> | R2 key prefix (captures/<sketch>/<orient>/<timestamp> is default; e.g. captures-test/2026-09-06/TrianglesNo1-portrait) |
| --r2-bucket <bucket> | R2 bucket (also R2_BUCKET / CAPTURE_R2_BUCKET) |
| --r2-account-id <id> | R2 account id (also R2_ACCOUNT_ID / CLOUDFLARE_ACCOUNT_ID) |
| --out-dir <dir> | Local output dir (dry-run or mirror) — also enables gallery index.html |
| --url <url> | Capture a fully-qualified sketch URL directly (skips repo resolution + dev server) |
| --port <n> | Dev server port when auto-spawning (default 4321) |
| --no-server | Assume dev server already running |
| --dry-run | Force local disk even if R2 creds are set |
| --concurrency <n> | Upload concurrency (default 4) |
| --verbose | Stream page console.log verbatim |
R2 credentials (when uploading): R2_ACCESS_KEY_ID / CAPTURE_R2_ACCESS_KEY_ID + R2_SECRET_ACCESS_KEY / CAPTURE_R2_SECRET_ACCESS_KEY plus bucket/account above. If absent, CLI falls back to local --out-dir / ./capture-out/....
Workflow (test → approve → full)
# 1) Test ~100 frames to temp R2 (or dry-run if no secrets in CI)
pnpm exec labcat-capture --repo ../triangles --sketch TrianglesNo1 --frames 100 --orient portrait --r2-prefix captures-test/$(date +%F)/TrianglesNo1-portrait
# or dry-run locally:
pnpm exec labcat-capture --repo ../triangles --sketch TrianglesNo1 --frames 100 --orient portrait --dry-run
# 2) Founder reviews (between test and full run) — R2 prefix or simple gallery:
wrangler r2 object list <bucket> --prefix "captures-test/2026-09-06/TrianglesNo1-portrait/"
# if --dry-run: open capture-out/TrianglesNo1-portrait-*/index.html
# 3) Full run after approval (portrait + landscape are separate runs)
pnpm exec labcat-capture --repo ../triangles --sketch TrianglesNo1 --orient portrait --r2-prefix captures/TrianglesNo1
pnpm exec labcat-capture --repo ../triangles --sketch TrianglesNo1 --orient landscape --r2-prefix captures/TrianglesNo1
# frames defaults to full audio length; pass --frames <n> to cap
# 4) Downstream: ffmpeg/DaVinci mux is a follow-up (not this CLI). Pull frames from R2:
# wrangler r2 object get ... or rclone, then ffmpeg -framerate 60 -i 'TrianglesNo1_%05d.png' ...Bucket / prefix layout
r2://<bucket>/<r2Prefix>/<sketch>/<orient>/<filename>
# default r2Prefix = captures/<sketch>/<orient>/<timestamp>
# example captures/TrianglesNo1/portrait/20260906-143200/TrianglesNo1_00000.pngcaptures-test/<date>/…for test runs; delete after approval if desired (wrangler r2 object deleteor dashboard).- Keep portrait and landscape under distinct orient segments so both can be rendered to ProRes later.
- Local dry-run mirrors the same filename scheme under
--out-dir/capture-out/<sketch>-<orient>-<ts>/.
In-browser capture retained
- Sketches keep
initCapture(p, { prefix, enabled, captureCSSBackground, extension })config-driven; no query-string export mode (?exporting=true) is introduced. - Clicking the canvas when
captureEnabled(or devtoolsp.startCapture()) still produces local ZIPs viajszip(<prefix>_frames_part1_<ts>.zip+ffmpeg_command.txt), throttled withMS_BETWEEN_ZIP_DOWNLOADSto avoid Chrome blocking. That path is unchanged and verified as fallback. - Headless path sets
p.captureSkipDownload = trueand uses the additivecaptureOnFrame/window.__audioReactOnFrameBlobhook (alias__labcatOnFrameBlobkept) to upload per-frame PNGs viatoBlob(preferred overtoDataURL), preserving CSS-background compositing (compositeDomCaptureExtension/createDomLayerCaptureBackground). If sketch has noinitCapture(e.g.TrianglesNo2), CLI auto-injects it via file patch (prefix + composite background) before dev server start and restores after — no manual sketch edit required (disable with--no-auto-init).
Verification
- CI / secrets-unavailable:
labcat-capture --dry-run --frames 5writes PNGs + gallery tocapture-out/and asserts no ZIP is required; existing ZIP fallback still works in a headed browser. - With secrets: short test capture against one sketch (e.g.
TrianglesNo1 --frames 100) confirms viewport dimensions, frame count, per-frame R2 keys, and gallery.
Out of scope (intentionally not in this CLI)
?exporting=truequery flag- Socket.io + local frame server /
toDataURLwire - Real-time throttled live-loop export (
frameRate(2)) - ffmpeg / DaVinci mux automation (follow-up)
- Auto Instagram post
- Removing local ZIP fallback
