@pixerate/flair 0.35.0 → 0.35.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @pixerate/flair
2
2
 
3
+ ## 0.35.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 66e52bd: Add swiftshader ANGLE flag for headless Linux Chrome, evaluate render stall timeout dynamically, and capture Chrome stderr for diagnostic logs on exit or timeout
8
+
9
+ ## 0.35.1
10
+
11
+ ### Patch Changes
12
+
13
+ - ac354c4: fix: add container-friendly flags and configurable stall timeout in recorder
14
+
3
15
  ## 0.35.0
4
16
 
5
17
  ### Minor Changes
@@ -5,7 +5,7 @@ import {
5
5
  pageControls,
6
6
  planOutput,
7
7
  runRender
8
- } from "./chunk-7KYQ4R76.js";
8
+ } from "./chunk-Z5EETGR4.js";
9
9
  import {
10
10
  svgAnimatedMarkup
11
11
  } from "./chunk-N5HXMQOK.js";
@@ -411,4 +411,4 @@ export {
411
411
  effectQueryFromFlags,
412
412
  describeEffect
413
413
  };
414
- //# sourceMappingURL=chunk-UU4PHRVF.js.map
414
+ //# sourceMappingURL=chunk-O5QKC2L7.js.map
@@ -485,10 +485,13 @@ async function gifSink(plan, width, height, fps) {
485
485
  function chromeArgs(userDataDir, width, height, url) {
486
486
  return [
487
487
  "--headless=new",
488
+ "--no-sandbox",
489
+ "--disable-setuid-sandbox",
490
+ "--disable-dev-shm-usage",
488
491
  `--user-data-dir=${userDataDir}`,
489
492
  `--window-size=${Math.max(64, width)},${Math.max(64, height)}`,
490
493
  "--use-gl=angle",
491
- ...process.platform === "darwin" ? ["--use-angle=metal"] : [],
494
+ ...process.platform === "darwin" ? ["--use-angle=metal"] : ["--use-angle=swiftshader"],
492
495
  "--enable-webgl",
493
496
  "--enable-unsafe-swiftshader",
494
497
  "--ignore-gpu-blocklist",
@@ -503,7 +506,7 @@ function chromeArgs(userDataDir, width, height, url) {
503
506
  url
504
507
  ];
505
508
  }
506
- var STALL_MS = 6e4;
509
+ var DEFAULT_STALL_MS = 12e4;
507
510
  async function runRender(job) {
508
511
  const { page, plan, fps, onProgress } = job;
509
512
  const total = page.frames;
@@ -521,6 +524,8 @@ async function runRender(job) {
521
524
  let written = 0;
522
525
  let stall;
523
526
  const sockets = /* @__PURE__ */ new Set();
527
+ const stallTimeoutMs = Number(process.env.FLAIR_STALL_TIMEOUT_MS) || DEFAULT_STALL_MS;
528
+ let chromeStderr = "";
524
529
  const cleanup = () => {
525
530
  clearTimeout(stall);
526
531
  try {
@@ -547,7 +552,12 @@ async function runRender(job) {
547
552
  };
548
553
  const watchdog = () => {
549
554
  clearTimeout(stall);
550
- stall = setTimeout(() => fail(new Error(`The render stalled: no frame for ${STALL_MS / 1e3} s after frame ${written} of ${total}. Is WebGL2 available to headless Chrome?`)), STALL_MS);
555
+ stall = setTimeout(() => {
556
+ const diag = chromeStderr.trim() ? `
557
+ Chrome stderr:
558
+ ${chromeStderr.trim()}` : "";
559
+ fail(new Error(`The render stalled: no frame for ${stallTimeoutMs / 1e3} s after frame ${written} of ${total}. Is WebGL2 available to headless Chrome?${diag}`));
560
+ }, stallTimeoutMs);
551
561
  };
552
562
  const server = http.createServer((req, res) => {
553
563
  const url = new URL(req.url || "/", "http://127.0.0.1");
@@ -624,10 +634,25 @@ async function runRender(job) {
624
634
  });
625
635
  server.listen(0, "127.0.0.1", () => {
626
636
  const { port } = server.address();
627
- chrome = spawn(chromeBin, chromeArgs(userDataDir, page.width, page.height, `http://127.0.0.1:${port}/`), { stdio: "ignore" });
628
- chrome.on("error", (err) => fail(new Error(`Could not launch Chrome at ${chromeBin}: ${err.message}`)));
637
+ chrome = spawn(chromeBin, chromeArgs(userDataDir, page.width, page.height, `http://127.0.0.1:${port}/`), {
638
+ stdio: ["ignore", "ignore", "pipe"]
639
+ });
640
+ chrome.stderr?.on("data", (chunk) => {
641
+ chromeStderr = (chromeStderr + chunk.toString()).slice(-4e3);
642
+ });
643
+ chrome.on("error", (err) => {
644
+ const diag = chromeStderr.trim() ? `
645
+ Chrome stderr:
646
+ ${chromeStderr.trim()}` : "";
647
+ fail(new Error(`Could not launch Chrome at ${chromeBin}: ${err.message}${diag}`));
648
+ });
629
649
  chrome.on("exit", (code) => {
630
- if (!finished && written < total) fail(new Error(`Chrome quit (exit ${code}) after ${written} of ${total} frames.`));
650
+ if (!finished && written < total) {
651
+ const diag = chromeStderr.trim() ? `
652
+ Chrome stderr:
653
+ ${chromeStderr.trim()}` : "";
654
+ fail(new Error(`Chrome quit (exit ${code}) after ${written} of ${total} frames.${diag}`));
655
+ }
631
656
  });
632
657
  watchdog();
633
658
  });
@@ -721,4 +746,4 @@ export {
721
746
  frameStates,
722
747
  exportMotionGraphic
723
748
  };
724
- //# sourceMappingURL=chunk-7KYQ4R76.js.map
749
+ //# sourceMappingURL=chunk-Z5EETGR4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/export/recorder.ts"],"sourcesContent":["// gifenc ships without types; the reference brings them along.\n/// <reference path=\"../shader/gifenc.d.ts\" />\nimport http from 'node:http';\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { controlValue, parseControls, type ControlValue, type TextureSettings } from '../shader/controls.js';\nimport { MAX_CLICKS, MAX_TRAIL, PointerTracker, TRAIL_SECONDS } from '../shader/pointer.js';\nimport { VERTEX_SOURCE, wrapSource } from '../shader/program.js';\nimport { SVG_EDGE } from '../shader/textures.js';\n\n// ==========================================\n// Tools: Chrome and ffmpeg\n// ==========================================\n\nconst CHROME_CANDIDATES = [\n '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n '/Applications/Chromium.app/Contents/MacOS/Chromium',\n 'C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n '/usr/bin/google-chrome',\n '/usr/bin/google-chrome-stable',\n '/usr/bin/chromium',\n '/usr/bin/chromium-browser',\n];\n\n/** Chromium builds Playwright downloaded (`npx playwright install chromium`), newest first. */\nfunction playwrightChromes(): string[] {\n const roots = [\n process.env.PLAYWRIGHT_BROWSERS_PATH,\n path.join(os.homedir(), 'Library/Caches/ms-playwright'),\n path.join(os.homedir(), '.cache/ms-playwright'),\n ].filter((r): r is string => Boolean(r));\n const found: string[] = [];\n for (const root of roots) {\n let dirs: string[] = [];\n try {\n dirs = fs.readdirSync(root).filter((d) => /^chromium-\\d+$/.test(d));\n } catch {\n continue;\n }\n dirs.sort((a, b) => Number(b.split('-')[1]) - Number(a.split('-')[1]));\n for (const dir of dirs) {\n for (const bin of [\n 'chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing',\n 'chrome-mac/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing',\n 'chrome-mac/Chromium.app/Contents/MacOS/Chromium',\n 'chrome-linux/chrome',\n 'chrome-linux64/chrome',\n ]) found.push(path.join(root, dir, bin));\n }\n }\n return found;\n}\n\n/**\n * Finds a local Chrome or Chromium executable, or `null`: `CHROME_PATH` (or\n * `CHROME_BIN`), then the usual install places, then a Chromium Playwright\n * downloaded.\n */\nexport function findChrome(): string | null {\n const env = process.env.CHROME_PATH || process.env.CHROME_BIN;\n if (env) return env;\n return [...CHROME_CANDIDATES, ...playwrightChromes()].find((candidate) => fs.existsSync(candidate)) ?? null;\n}\n\n/** The ffmpeg to run: `FFMPEG_PATH`, else `ffmpeg` on the `PATH`. */\nexport function ffmpegPath(): string {\n return process.env.FFMPEG_PATH || 'ffmpeg';\n}\n\nconst NO_FFMPEG =\n 'ffmpeg is not installed (or not on the PATH). Install it (brew install ffmpeg, apt install ffmpeg), or set FFMPEG_PATH; GIF, PNG and SVG output do not need it.';\n\n// ==========================================\n// Output formats\n// ==========================================\n\n/** A file type the exporter writes, from the output path. */\nexport type OutputFormat = 'mp4' | 'webm' | 'mov' | 'gif' | 'png' | 'svg';\n\n/** What to write and how. */\nexport interface OutputPlan {\n format: OutputFormat;\n /** The output path, resolved. For a PNG sequence, a printf pattern (`…/frame_%04d.png`). */\n path: string;\n /** Whether the frames keep their alpha. */\n alpha: boolean;\n /** ffmpeg's arguments after the input (for mp4, webm and mov). */\n ffmpegArgs?: string[];\n}\n\n/**\n * Works out the format from `outputPath`'s extension and what alpha it can\n * keep. A path with no extension, or ending in a slash, is a folder of PNG\n * frames; a `.png` path with a `%d` pattern (`out_%04d.png`) names each\n * frame, and one without gets `_%04d` added (unless there is one frame).\n * Throws a sentence for formats it cannot write, and for alpha in MP4.\n */\nexport function planOutput(outputPath: string, options: { alpha?: boolean; frames?: number } = {}): OutputPlan {\n const resolved = path.resolve(process.cwd(), outputPath);\n const trailingSlash = /[\\\\/]$/.test(outputPath);\n const ext = trailingSlash ? '' : path.extname(resolved).toLowerCase();\n const alpha = Boolean(options.alpha);\n switch (ext) {\n case '': {\n return { format: 'png', path: path.join(resolved, 'frame_%04d.png'), alpha };\n }\n case '.png': {\n if (/%0?\\d*d/.test(resolved) || (options.frames ?? 2) === 1) return { format: 'png', path: resolved, alpha };\n return { format: 'png', path: resolved.replace(/\\.png$/i, '_%04d.png'), alpha };\n }\n case '.gif':\n return { format: 'gif', path: resolved, alpha };\n case '.svg':\n return { format: 'svg', path: resolved, alpha };\n case '.mp4':\n case '.m4v': {\n if (alpha) throw new Error('MP4 (H.264) cannot hold alpha; write .webm, .mov, .gif or .png frames for a transparent render, or drop --alpha.');\n return {\n format: 'mp4',\n path: resolved,\n alpha,\n ffmpegArgs: [\n // yuv420p needs even sizes; sRGB pixels go to BT.709, tagged so players show the same colours.\n '-vf', 'pad=ceil(iw/2)*2:ceil(ih/2)*2,scale=out_color_matrix=bt709:out_range=tv,format=yuv420p',\n '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-crf', '18', '-preset', 'medium',\n '-colorspace', 'bt709', '-color_primaries', 'bt709', '-color_trc', 'bt709', '-color_range', 'tv',\n '-movflags', '+faststart',\n ],\n };\n }\n case '.webm':\n return {\n format: 'webm',\n path: resolved,\n alpha,\n ffmpegArgs: alpha\n ? ['-c:v', 'libvpx-vp9', '-pix_fmt', 'yuva420p', '-b:v', '0', '-crf', '30', '-auto-alt-ref', '0', '-row-mt', '1']\n : ['-vf', 'scale=out_color_matrix=bt709:out_range=tv,format=yuv420p', '-c:v', 'libvpx-vp9', '-pix_fmt', 'yuv420p', '-b:v', '0', '-crf', '30', '-row-mt', '1',\n '-colorspace', 'bt709', '-color_primaries', 'bt709', '-color_trc', 'bt709'],\n };\n case '.mov':\n return {\n format: 'mov',\n path: resolved,\n alpha,\n ffmpegArgs: [\n '-c:v', 'prores_ks', '-profile:v', '4444', '-vendor', 'apl0',\n '-pix_fmt', alpha ? 'yuva444p10le' : 'yuv444p10le',\n ...(alpha ? ['-alpha_bits', '16'] : []),\n '-colorspace', 'bt709', '-color_primaries', 'bt709', '-color_trc', 'bt709',\n ],\n };\n default:\n throw new Error(`Flair cannot write \"${ext}\" files; write .mp4, .webm, .mov, .gif, .svg, or .png frames (a folder, or a pattern like out_%04d.png).`);\n }\n}\n\n/** A frame number put into a PNG sequence's printf pattern (`%04d`, `%d`). */\nexport function sequencePath(pattern: string, frame: number): string {\n return pattern.replace(/%(0?)(\\d*)d/, (_, zero: string, width: string) => String(frame).padStart(Number(width || 0), zero ? '0' : ' '));\n}\n\n// ==========================================\n// The pointer, scripted\n// ==========================================\n\n/** Pointer uniforms for one frame. */\nexport interface PointerFrame {\n mouse: [number, number, number, number];\n pointer: [number, number, number, number];\n /** `iClicks`, MAX_CLICKS × 4 numbers. */\n clicks: number[];\n /** `iTrail`, MAX_TRAIL × 4 numbers. */\n trail: number[];\n}\n\n/**\n * A pointer that shows off an interactive shader in a recording: it hovers\n * over the canvas from the start, wanders a figure-of-eight (so trails have\n * a path), and clicks every `interval` seconds, holding each press for a\n * moment. Returns the uniforms for each frame time, in order.\n */\nexport function scriptedPointer(width: number, height: number, times: readonly number[], options: { interval?: number; period?: number } = {}): PointerFrame[] {\n const interval = options.interval ?? 0.8;\n const period = Math.max(2, options.period ?? (times[times.length - 1] ?? 2));\n const at = (t: number): [number, number] => [\n width * (0.5 + 0.3 * Math.sin((2 * Math.PI * t) / period)),\n height * (0.5 + 0.22 * Math.sin((4 * Math.PI * t) / period)),\n ];\n const press = 0.25;\n const tracker = new PointerTracker();\n tracker.hover(true, 0);\n tracker.move(...at(0), 0);\n let clock = 0;\n let nextClick = 0.25;\n let release = Infinity;\n let mouse: [number, number, number, number] = [0, 0, 0, 0];\n const frames: PointerFrame[] = [];\n for (const time of times) {\n // Walk the script up to `time` in trail-sized steps, so the trail and clicks land where they would live.\n while (clock < time) {\n const next = Math.min(time, clock + TRAIL_SECONDS, nextClick, release);\n clock = next;\n const [x, y] = at(clock);\n tracker.move(x, y, clock);\n if (mouse[2] > 0) mouse = [x, y, mouse[2], mouse[3]];\n if (clock >= release) {\n tracker.up(clock);\n mouse = [mouse[0], mouse[1], -mouse[2], -Math.abs(mouse[3])];\n release = Infinity;\n }\n if (clock >= nextClick) {\n tracker.down(x, y, clock);\n mouse = [x, y, x, y];\n release = clock + press;\n nextClick = clock + interval;\n }\n }\n frames.push({\n mouse: [...mouse],\n pointer: tracker.pointer(time),\n clicks: [...tracker.clicksAt(time)],\n trail: [...tracker.trailAt(time)],\n });\n }\n return frames;\n}\n\n// ==========================================\n// The page\n// ==========================================\n\n/** A shader control as the page sets it. */\nexport interface PageControl {\n name: string;\n type: string;\n value: ControlValue;\n texture?: TextureSettings;\n}\n\n/** What the render page draws. */\nexport interface PageConfig {\n width: number;\n height: number;\n frames: number;\n /** Keep alpha in the frames (else they are composited over `background`). */\n alpha: boolean;\n /** CSS colour under transparent pixels when `alpha` is false. */\n background: string;\n /** How frames go back: PNG files, or raw RGBA (rows top down). */\n send: 'png' | 'rgba';\n /** A shader to run, or (when absent) SVG frame markup from `/state`. */\n shader?: {\n /** The full GLSL ES 3.00 fragment shader (see {@link fragmentSource}). */\n fragment: string;\n controls: PageControl[];\n /** Draws its frame opaque, as `ShaderRenderer` does without `alpha`: its own alpha is ignored. */\n opaque: boolean;\n };\n}\n\n/** JSON that is safe inside a `<script>`. */\nconst scriptJson = (value: unknown) => JSON.stringify(value).replace(/</g, '\\\\u003c');\n\n/**\n * The page headless Chrome runs: it compiles the shader with the same\n * wrapper as `ShaderRenderer` (`wrapSource`, so every built-in, `iPointer`,\n * `iClicks`, `iTrail`, `iAudio`, `iSpectrum`, `iBeat` and `iScroll` included, is\n * declared; the audio and scroll ones stay 0, so those effects play their idle\n * demos), sets each control by its\n * type (`int` and `bool` as integers), loads image controls the way\n * `loadImage` does (SVGs sized to their long edge first) before the first\n * frame, and flips them as the renderer does. Each frame asks `/state` for\n * its time, pointer, video frames or SVG markup, draws, and posts the\n * pixels to `/frame`.\n */\nexport function renderPageHtml(config: PageConfig): string {\n return `<!doctype html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<style>html,body{margin:0;background:transparent;overflow:hidden}</style>\n</head>\n<body>\n<script>\nconst CONFIG = ${scriptJson(config)};\nconst VERTEX = ${scriptJson(VERTEX_SOURCE)};\nconst W = CONFIG.width, H = CONFIG.height;\nconst report = (message) => fetch(\"/error\", { method: \"POST\", body: String(message) });\naddEventListener(\"error\", (e) => report(\"The render page failed: \" + e.message));\naddEventListener(\"unhandledrejection\", (e) => report((e.reason && e.reason.message) || String(e.reason)));\n\n// SVGs decode at their own (often icon) size: give them ${SVG_EDGE} px on the long edge, and a viewBox.\nfunction sizeSvg(markup, w0, h0) {\n const doc = new DOMParser().parseFromString(markup, \"image/svg+xml\");\n const svg = doc.documentElement;\n if (svg.localName !== \"svg\" || doc.getElementsByTagName(\"parsererror\").length) throw new Error(\"Not an SVG document.\");\n const len = (n) => { const m = (svg.getAttribute(n) || \"\").trim().match(/^([\\\\d.]+)(px)?$/); return m && +m[1] > 0 ? +m[1] : 0; };\n const box = (svg.getAttribute(\"viewBox\") || \"\").trim().split(/[\\\\s,]+/).map(Number);\n const hasBox = box.length === 4 && box[2] > 0 && box[3] > 0;\n const w = len(\"width\") || (hasBox ? box[2] : 300), h = len(\"height\") || (hasBox ? box[3] : 150);\n if (!hasBox) svg.setAttribute(\"viewBox\", \"0 0 \" + w + \" \" + h);\n const scale = w0 ? 1 : ${SVG_EDGE} / Math.max(w, h);\n svg.setAttribute(\"width\", w0 || Math.max(1, Math.round(w * scale)));\n svg.setAttribute(\"height\", h0 || Math.max(1, Math.round(h * scale)));\n return new XMLSerializer().serializeToString(svg);\n}\nconst isSvg = (url) => /^data:image\\\\/svg\\\\+xml[;,]/i.test(url) || /^(?!data:)[^?#]*\\\\.svg([?#]|$)/i.test(url);\nasync function decode(src, name) {\n const img = new Image();\n img.src = src;\n try { await img.decode(); } catch (e) { throw new Error(\"Could not load the image \" + name + \".\"); }\n return img;\n}\nasync function loadImage(url, name) {\n if (!isSvg(url)) return decode(url, name);\n const text = await (await fetch(url)).text();\n const blob = URL.createObjectURL(new Blob([sizeSvg(text)], { type: \"image/svg+xml\" }));\n try { return await decode(blob, name); } finally { URL.revokeObjectURL(blob); }\n}\n\nconst out = document.createElement(\"canvas\");\nout.width = W; out.height = H;\nconst ctx = out.getContext(\"2d\", { willReadFrequently: CONFIG.send === \"rgba\" });\nlet gl = null, glCanvas = null, u = null;\nconst textures = {};\n\nfunction setupShader() {\n glCanvas = document.createElement(\"canvas\");\n glCanvas.width = W; glCanvas.height = H;\n // As createContext: no antialiasing, depth or stencil; unpremultiplied alpha so pixels read back exactly.\n gl = glCanvas.getContext(\"webgl2\", { alpha: true, premultipliedAlpha: false, preserveDrawingBuffer: true, antialias: false, depth: false, stencil: false, powerPreference: \"high-performance\" });\n if (!gl) throw new Error(\"WebGL2 is not available in headless Chrome.\");\n const compile = (type, src) => {\n const s = gl.createShader(type);\n gl.shaderSource(s, src);\n gl.compileShader(s);\n if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(\"The shader does not compile: \" + gl.getShaderInfoLog(s));\n return s;\n };\n const program = gl.createProgram();\n gl.attachShader(program, compile(gl.VERTEX_SHADER, VERTEX));\n gl.attachShader(program, compile(gl.FRAGMENT_SHADER, CONFIG.shader.fragment));\n gl.linkProgram(program);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error(\"The shader does not link: \" + gl.getProgramInfoLog(program));\n gl.useProgram(program);\n u = (n) => gl.getUniformLocation(program, n);\n gl.viewport(0, 0, W, H);\n if (CONFIG.shader.opaque) {\n // An opaque canvas, as ShaderRenderer has without alpha: alpha stays 1 whatever the shader writes.\n gl.clearColor(0, 0, 0, 1);\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.colorMask(true, true, true, false);\n }\n}\n\nfunction makeTexture(unit, texture) {\n const t = gl.createTexture();\n const settings = texture || { filter: \"mipmap\", wrap: \"repeat\" };\n gl.activeTexture(gl.TEXTURE0 + unit);\n gl.bindTexture(gl.TEXTURE_2D, t);\n // Black until (or unless) a picture arrives, as TextureCache does.\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 255]));\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, { mipmap: gl.LINEAR_MIPMAP_LINEAR, linear: gl.LINEAR, nearest: gl.NEAREST }[settings.filter]);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, settings.filter === \"nearest\" ? gl.NEAREST : gl.LINEAR);\n const wrap = { repeat: gl.REPEAT, clamp: gl.CLAMP_TO_EDGE, mirror: gl.MIRRORED_REPEAT }[settings.wrap];\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);\n return { unit, texture: t };\n}\n\nfunction upload(name, img) {\n const slot = textures[name];\n if (!slot) return;\n gl.activeTexture(gl.TEXTURE0 + slot.unit);\n gl.bindTexture(gl.TEXTURE_2D, slot.texture);\n // Rows bottom up, as the renderer's TextureCache uploads them.\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n gl.generateMipmap(gl.TEXTURE_2D);\n}\n\nasync function setControls() {\n let unit = 0;\n const loads = [];\n for (const c of CONFIG.shader.controls) {\n const loc = u(c.name);\n const v = c.value;\n if (c.type === \"sampler2D\") {\n textures[c.name] = makeTexture(unit, c.texture);\n if (loc) gl.uniform1i(loc, unit);\n unit++;\n if (v) loads.push(loadImage(v, \"for \" + c.name).then((img) => upload(c.name, img)));\n continue;\n }\n if (!loc) continue;\n if (c.type === \"float\") gl.uniform1f(loc, v);\n else if (c.type === \"int\") gl.uniform1i(loc, Math.round(v));\n else if (c.type === \"bool\") gl.uniform1i(loc, v ? 1 : 0);\n else if (Array.isArray(v)) gl[\"uniform\" + v.length + \"fv\"](loc, v);\n }\n await Promise.all(loads);\n}\n\nfunction drawShader(s) {\n gl.viewport(0, 0, W, H);\n const set = (n, f) => { const l = u(n); if (l) f(l); };\n set(\"iResolution\", (l) => gl.uniform3f(l, W, H, 1));\n set(\"iTime\", (l) => gl.uniform1f(l, s.time));\n set(\"iTimeDelta\", (l) => gl.uniform1f(l, s.delta));\n set(\"iFrame\", (l) => gl.uniform1i(l, s.frame));\n set(\"iMouse\", (l) => gl.uniform4fv(l, s.mouse));\n set(\"iPointer\", (l) => gl.uniform4fv(l, s.pointer));\n set(\"iClicks\", (l) => gl.uniform4fv(l, new Float32Array(s.clicks)));\n set(\"iTrail\", (l) => gl.uniform4fv(l, new Float32Array(s.trail)));\n // glslsandbox-style names, for complete programs.\n set(\"u_resolution\", (l) => gl.uniform2f(l, W, H));\n set(\"u_time\", (l) => gl.uniform1f(l, s.time));\n set(\"u_mouse\", (l) => gl.uniform2f(l, s.pointer[0], s.pointer[1]));\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n}\n\nasync function frameImage(s) {\n if (CONFIG.shader) {\n for (const [name, url] of Object.entries(s.video || {})) upload(name, await decode(url, \"of the video frame\"));\n drawShader(s);\n return glCanvas;\n }\n const markup = sizeSvg(s.svg, W, H);\n const url = URL.createObjectURL(new Blob([markup], { type: \"image/svg+xml\" }));\n try { return await decode(url, \"of the SVG frame\"); } finally { URL.revokeObjectURL(url); }\n}\n\nasync function capture(src) {\n // The WebGL canvas reads back exactly when nothing is composited under it.\n const direct = CONFIG.shader && (CONFIG.alpha || CONFIG.shader.opaque);\n if (direct) {\n if (CONFIG.send === \"png\") return new Promise((r) => glCanvas.toBlob(r, \"image/png\"));\n const px = new Uint8Array(W * H * 4);\n gl.readPixels(0, 0, W, H, gl.RGBA, gl.UNSIGNED_BYTE, px);\n const flipped = new Uint8Array(px.length), row = W * 4;\n for (let y = 0; y < H; y++) flipped.set(px.subarray((H - 1 - y) * row, (H - y) * row), y * row);\n return flipped;\n }\n ctx.clearRect(0, 0, W, H);\n if (!CONFIG.alpha) { ctx.fillStyle = CONFIG.background; ctx.fillRect(0, 0, W, H); }\n ctx.drawImage(src, 0, 0, W, H);\n if (CONFIG.send === \"png\") return new Promise((r) => out.toBlob(r, \"image/png\"));\n return ctx.getImageData(0, 0, W, H).data;\n}\n\n(async () => {\n if (CONFIG.shader) { setupShader(); await setControls(); }\n for (let i = 0; i < CONFIG.frames; i++) {\n const s = await (await fetch(\"/state?i=\" + i)).json();\n const body = await capture(await frameImage(s));\n const r = await fetch(\"/frame?i=\" + i, { method: \"POST\", body });\n if (!r.ok) return;\n }\n})().catch((e) => report((e && e.message) || String(e)));\n</script>\n</body>\n</html>\n`;\n}\n\n// ==========================================\n// The render loop\n// ==========================================\n\n/** One frame's state, as the page asks for it. */\nexport interface FrameState {\n time: number;\n delta: number;\n frame: number;\n mouse: [number, number, number, number];\n pointer: [number, number, number, number];\n clicks: number[];\n trail: number[];\n /** SVG markup to draw (vector frames). */\n svg?: string;\n /** Image control → local file to show this frame (video frames). */\n video?: Record<string, string>;\n}\n\n/** A render: the page to run, each frame's state, and where it goes. */\nexport interface RenderJob {\n page: PageConfig;\n state: (frame: number) => FrameState;\n plan: OutputPlan;\n fps: number;\n onProgress?: (frame: number, total: number) => void;\n}\n\n/** Frames written somewhere: a sink takes them in order and finishes. */\ninterface Sink {\n write(frame: number, data: Buffer): Promise<void>;\n finish(): Promise<void>;\n abort(): void;\n}\n\nfunction ffmpegSink(plan: OutputPlan, fps: number): Sink {\n const args = ['-y', '-hide_banner', '-loglevel', 'error', '-f', 'image2pipe', '-framerate', String(fps), '-c:v', 'png', '-i', '-', ...plan.ffmpegArgs!, '-r', String(fps), plan.path];\n const proc = spawn(ffmpegPath(), args, { stdio: ['pipe', 'ignore', 'pipe'] });\n let stderr = '';\n let failed: Error | null = null;\n proc.stderr!.on('data', (d) => (stderr += d.toString()));\n const done = new Promise<void>((resolve, reject) => {\n proc.on('error', (err: NodeJS.ErrnoException) => {\n failed = err.code === 'ENOENT' ? new Error(NO_FFMPEG) : new Error(`ffmpeg could not start: ${err.message}`);\n reject(failed);\n });\n proc.on('close', (code) => {\n if (code === 0) resolve();\n else reject((failed ??= new Error(`ffmpeg failed (exit ${code}): ${stderr.trim() || 'no message'}`)));\n });\n });\n done.catch(() => {});\n proc.stdin!.on('error', () => {});\n return {\n async write(_frame, data) {\n if (failed) throw failed;\n if (!proc.stdin!.write(data)) await new Promise((r) => proc.stdin!.once('drain', r));\n },\n async finish() {\n proc.stdin!.end();\n await done;\n },\n abort() {\n proc.kill('SIGKILL');\n },\n };\n}\n\nfunction pngSink(plan: OutputPlan): Sink {\n fs.mkdirSync(path.dirname(plan.path), { recursive: true });\n return {\n async write(frame, data) {\n await fs.promises.writeFile(plan.path.includes('%') ? sequencePath(plan.path, frame) : plan.path, data);\n },\n async finish() {},\n abort() {},\n };\n}\n\nasync function gifSink(plan: OutputPlan, width: number, height: number, fps: number): Promise<Sink> {\n // gifenc is CommonJS in Node: its functions may only be on the default export.\n const mod = (await import('gifenc')) as Partial<typeof import('gifenc')> & { default?: typeof import('gifenc') };\n const { GIFEncoder, quantize, applyPalette } = (typeof mod.GIFEncoder === 'function' ? mod : mod.default!) as typeof import('gifenc');\n const gif = GIFEncoder();\n const delay = 1000 / fps;\n return {\n async write(frame, data) {\n const rgba = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n const first = frame === 0 ? { repeat: 0 } : {};\n if (plan.alpha) {\n // One-bit alpha: each frame replaces the last (dispose 2), with a transparent palette entry.\n const palette = quantize(rgba, 256, { format: 'rgba4444', oneBitAlpha: true });\n const transparentIndex = palette.findIndex((c) => c[3] === 0);\n gif.writeFrame(applyPalette(rgba, palette, 'rgba4444'), width, height, {\n palette,\n delay,\n dispose: 2,\n ...(transparentIndex >= 0 ? { transparent: true, transparentIndex } : {}),\n ...first,\n });\n } else {\n const palette = quantize(rgba, 256);\n gif.writeFrame(applyPalette(rgba, palette), width, height, { palette, delay, ...first });\n }\n },\n async finish() {\n gif.finish();\n fs.mkdirSync(path.dirname(plan.path), { recursive: true });\n await fs.promises.writeFile(plan.path, gif.bytes());\n },\n abort() {},\n };\n}\n\n/** Chrome's flags: GPU WebGL where there is one (Metal on macOS), SwiftShader otherwise. */\nfunction chromeArgs(userDataDir: string, width: number, height: number, url: string): string[] {\n return [\n '--headless=new',\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-dev-shm-usage',\n `--user-data-dir=${userDataDir}`,\n `--window-size=${Math.max(64, width)},${Math.max(64, height)}`,\n '--use-gl=angle',\n ...(process.platform === 'darwin' ? ['--use-angle=metal'] : ['--use-angle=swiftshader']),\n '--enable-webgl',\n '--enable-unsafe-swiftshader',\n '--ignore-gpu-blocklist',\n '--disable-gpu-sandbox',\n '--force-color-profile=srgb',\n '--no-first-run',\n '--no-default-browser-check',\n '--disable-background-networking',\n '--disable-background-timer-throttling',\n '--disable-renderer-backgrounding',\n '--disable-extensions',\n url,\n ];\n}\n\n/** Default stall timeout in milliseconds if not overridden via FLAIR_STALL_TIMEOUT_MS. */\nconst DEFAULT_STALL_MS = 120_000;\n\n/**\n * Runs a render: serves the page to headless Chrome on 127.0.0.1, hands it\n * each frame's state, and writes the frames it posts back. Resolves with the\n * output path (a PNG sequence's pattern).\n */\nexport async function runRender(job: RenderJob): Promise<string> {\n const { page, plan, fps, onProgress } = job;\n const total = page.frames;\n const chromeBin = findChrome();\n if (!chromeBin) {\n throw new Error('Chrome or Chromium is needed to render, and none was found. Install Google Chrome, run \"npx playwright install chromium\", or set CHROME_PATH.');\n }\n fs.mkdirSync(path.dirname(plan.path), { recursive: true });\n const sink = plan.format === 'gif' ? await gifSink(plan, page.width, page.height, fps) : plan.format === 'png' ? pngSink(plan) : ffmpegSink(plan, fps);\n const html = renderPageHtml(page);\n const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'flair-chrome-'));\n\n return new Promise<string>((resolve, reject) => {\n let chrome: ChildProcess | null = null;\n let finished = false;\n let written = 0;\n let stall: NodeJS.Timeout | undefined;\n const sockets = new Set<import('node:net').Socket>();\n\n const stallTimeoutMs = Number(process.env.FLAIR_STALL_TIMEOUT_MS) || DEFAULT_STALL_MS;\n let chromeStderr = '';\n\n const cleanup = () => {\n clearTimeout(stall);\n try {\n chrome?.kill('SIGKILL');\n } catch {}\n for (const s of sockets) s.destroy();\n server.close();\n fs.rm(userDataDir, { recursive: true, force: true }, () => {});\n };\n const fail = (err: Error) => {\n if (finished) return;\n finished = true;\n sink.abort();\n cleanup();\n reject(err);\n };\n const succeed = () => {\n if (finished) return;\n finished = true;\n cleanup();\n resolve(plan.path);\n };\n const watchdog = () => {\n clearTimeout(stall);\n stall = setTimeout(() => {\n const diag = chromeStderr.trim() ? `\\nChrome stderr:\\n${chromeStderr.trim()}` : '';\n fail(new Error(`The render stalled: no frame for ${stallTimeoutMs / 1000} s after frame ${written} of ${total}. Is WebGL2 available to headless Chrome?${diag}`));\n }, stallTimeoutMs);\n };\n\n const server = http.createServer((req, res) => {\n const url = new URL(req.url || '/', 'http://127.0.0.1');\n if (url.pathname === '/' && req.method === 'GET') {\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });\n res.end(html);\n return;\n }\n if (url.pathname === '/state') {\n const i = Number(url.searchParams.get('i'));\n try {\n const s = job.state(i);\n const body = { ...s, ...(s.video ? { video: Object.fromEntries(Object.keys(s.video).map((k) => [k, `/video?i=${i}&name=${encodeURIComponent(k)}`])) } : {}) };\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(body));\n } catch (e) {\n res.writeHead(500);\n res.end();\n fail(e as Error);\n }\n return;\n }\n if (url.pathname === '/video') {\n const i = Number(url.searchParams.get('i'));\n const file = job.state(i).video?.[url.searchParams.get('name') ?? ''];\n if (!file) {\n res.writeHead(404);\n res.end();\n return;\n }\n res.writeHead(200, { 'Content-Type': 'image/png' });\n fs.createReadStream(file).pipe(res);\n return;\n }\n if (url.pathname === '/frame' && req.method === 'POST') {\n const i = Number(url.searchParams.get('i'));\n const chunks: Buffer[] = [];\n req.on('data', (c: Buffer) => chunks.push(c));\n req.on('end', () => {\n sink\n .write(i, Buffer.concat(chunks))\n .then(async () => {\n written++;\n watchdog();\n onProgress?.(written, total);\n res.writeHead(200);\n res.end('ok');\n if (written >= total) {\n await sink.finish();\n succeed();\n }\n })\n .catch((e: Error) => {\n res.writeHead(500);\n res.end();\n fail(e);\n });\n });\n return;\n }\n if (url.pathname === '/error' && req.method === 'POST') {\n let message = '';\n req.on('data', (c) => (message += c));\n req.on('end', () => {\n res.writeHead(200);\n res.end();\n fail(new Error(message || 'The render page failed.'));\n });\n return;\n }\n res.writeHead(404);\n res.end();\n });\n server.on('connection', (s) => {\n sockets.add(s);\n s.on('close', () => sockets.delete(s));\n });\n\n server.listen(0, '127.0.0.1', () => {\n const { port } = server.address() as import('node:net').AddressInfo;\n chrome = spawn(chromeBin, chromeArgs(userDataDir, page.width, page.height, `http://127.0.0.1:${port}/`), {\n stdio: ['ignore', 'ignore', 'pipe'],\n });\n chrome.stderr?.on('data', (chunk) => {\n chromeStderr = (chromeStderr + chunk.toString()).slice(-4000);\n });\n chrome.on('error', (err) => {\n const diag = chromeStderr.trim() ? `\\nChrome stderr:\\n${chromeStderr.trim()}` : '';\n fail(new Error(`Could not launch Chrome at ${chromeBin}: ${err.message}${diag}`));\n });\n chrome.on('exit', (code) => {\n if (!finished && written < total) {\n const diag = chromeStderr.trim() ? `\\nChrome stderr:\\n${chromeStderr.trim()}` : '';\n fail(new Error(`Chrome quit (exit ${code}) after ${written} of ${total} frames.${diag}`));\n }\n });\n watchdog();\n });\n });\n}\n\n// ==========================================\n// Shader export\n// ==========================================\n\n/** Where the pointer is in a recording: nowhere (`none`), or the scripted pointer of {@link scriptedPointer} (`auto`). */\nexport type PointerMode = 'none' | 'auto';\n\nexport interface ExportOptions {\n /** GLSL fragment shader source (Shadertoy-style `mainImage`). */\n source: string;\n /**\n * Control values over the declared defaults. An image control takes a\n * URL, a `data:` URL, or a local file path (read into a `data:` URL).\n */\n values?: Record<string, ControlValue>;\n /** Destination: .mp4, .webm, .mov, .gif, or PNG frames (a folder or `out_%04d.png`). */\n outputPath: string;\n width?: number;\n height?: number;\n fps?: number;\n /** Seconds (default 3). */\n duration?: number;\n /** Keep the shader's alpha (WebM, MOV, GIF and PNG; MP4 is always opaque). */\n alpha?: boolean;\n /**\n * The shader draws over other content (`fragColor.a` is coverage): without\n * `alpha` it is composited over `background`. Otherwise its alpha is\n * ignored without `alpha`, as on an opaque canvas.\n */\n transparent?: boolean;\n /** CSS colour under transparent pixels when `alpha` is off (default black). */\n background?: string;\n /** The pointer: `none` (default) or `auto`, a scripted pointer that hovers, wanders and clicks. */\n pointer?: PointerMode;\n onProgress?: (frame: number, total: number) => void;\n}\n\nconst MIME: Record<string, string> = {\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.bmp': 'image/bmp',\n '.svg': 'image/svg+xml',\n};\n\n/**\n * An image for the page, as a `data:` URL: a local file is read, an\n * `http(s):` URL fetched (so CORS does not matter); `data:` URLs stay.\n */\nexport async function imageDataUrl(source: string, cwd = process.cwd()): Promise<string> {\n if (/^data:/i.test(source) || source === '') return source;\n if (/^https?:/i.test(source)) {\n const res = await fetch(source);\n if (!res.ok) throw new Error(`Could not download the image at ${source} (HTTP ${res.status}).`);\n const type = res.headers.get('content-type')?.split(';')[0] || MIME[path.extname(new URL(source).pathname).toLowerCase()] || 'application/octet-stream';\n return `data:${type};base64,${Buffer.from(await res.arrayBuffer()).toString('base64')}`;\n }\n const file = path.resolve(cwd, source.replace(/^file:\\/\\//, ''));\n if (!fs.existsSync(file)) throw new Error(`There is no image at ${file}.`);\n const type = MIME[path.extname(file).toLowerCase()];\n if (!type) throw new Error(`${path.basename(file)} is not an image Flair can show; give it a PNG, JPEG, GIF, WebP, AVIF, BMP or SVG.`);\n return `data:${type};base64,${fs.readFileSync(file).toString('base64')}`;\n}\n\n/**\n * The fragment shader the page compiles: a Shadertoy-style `mainImage`\n * source goes in `wrapSource`, as `ShaderRenderer` compiles it. A complete\n * program (one with its own `void main()`, glslsandbox-style, reading\n * `u_time` and `u_resolution`) is kept as written, with `#version 300 es`\n * added when it has none.\n */\nexport function fragmentSource(source: string): string {\n if (!/\\bvoid\\s+main\\s*\\(\\s*\\)/.test(source)) return wrapSource(source);\n return /^\\s*#version\\b/.test(source) ? source : `#version 300 es\\n#line 1\\n${source}`;\n}\n\n/** Shader controls with their values, images as `data:` URLs, ready for the page. */\nexport async function pageControls(source: string, values: Record<string, ControlValue> = {}): Promise<PageControl[]> {\n return Promise.all(\n parseControls(source).map(async (c) => {\n let value = controlValue(c, values);\n if (c.type === 'sampler2D' && typeof value === 'string') value = await imageDataUrl(value);\n return { name: c.name, type: c.type, value, ...(c.texture ? { texture: c.texture } : {}) };\n }),\n );\n}\n\n/** Frame times and pointer uniforms for `frames` frames at `fps`. */\nexport function frameStates(frames: number, fps: number, width: number, height: number, pointer: PointerMode): FrameState[] {\n const times = Array.from({ length: frames }, (_, i) => i / fps);\n const scripted = pointer === 'auto' ? scriptedPointer(width, height, times) : undefined;\n const none = { mouse: [0, 0, 0, 0] as [number, number, number, number], pointer: [0, 0, 0, 0] as [number, number, number, number], clicks: new Array(MAX_CLICKS * 4).fill(0), trail: new Array(MAX_TRAIL * 4).fill(0) };\n return times.map((time, i) => ({ time, delta: i === 0 ? 0 : 1 / fps, frame: i, ...(scripted?.[i] ?? none) }));\n}\n\n/**\n * Renders a shader animation frame by frame with WebGL2 in headless Chrome,\n * the way `ShaderRenderer` draws it (the same GLSL wrapper and built-ins,\n * images, int and bool controls), and writes MP4 (H.264), WebM (VP9, with\n * alpha when asked), MOV (ProRes 4444), GIF or PNG frames. MP4, WebM and\n * MOV need ffmpeg; GIF and PNG do not.\n */\nexport async function exportMotionGraphic(options: ExportOptions): Promise<string> {\n const { source, values = {}, outputPath, fps = 30, duration = 3, onProgress } = options;\n const width = Math.max(1, Math.round(options.width ?? 720));\n const height = Math.max(1, Math.round(options.height ?? 720));\n const frames = Math.max(1, Math.round(duration * fps));\n const plan = planOutput(outputPath, { alpha: options.alpha, frames });\n if (plan.format === 'svg') throw new Error('A shader cannot be written as SVG; write .mp4, .webm, .mov, .gif or .png frames.');\n const states = frameStates(frames, fps, width, height, options.pointer ?? 'none');\n return runRender({\n page: {\n width,\n height,\n frames,\n alpha: plan.alpha,\n background: options.background ?? '#000000',\n send: plan.format === 'gif' ? 'rgba' : 'png',\n shader: {\n fragment: fragmentSource(source),\n controls: await pageControls(source, values),\n opaque: !plan.alpha && !options.transparent,\n },\n },\n state: (i) => states[i]!,\n plan,\n fps,\n onProgress,\n });\n}\n"],"mappings":"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","names":["type"]}
package/dist/cli.js CHANGED
@@ -154,7 +154,7 @@ program.command("export <targetOrShader> [extraPath] [outputPath]").description(
154
154
  const duration = parseFloat(options.duration);
155
155
  console.log(`Rendering ${duration}s (${duration * fps} frames at ${width}x${height}, ${fps}fps) to ${options.out}...`);
156
156
  try {
157
- const { exportMotionGraphic } = await import("./recorder-TYMEZHOT.js");
157
+ const { exportMotionGraphic } = await import("./recorder-YXZL6SXM.js");
158
158
  const savedPath = await exportMotionGraphic({
159
159
  source,
160
160
  values,
@@ -183,7 +183,7 @@ var collect = (value, previous) => [...previous, value];
183
183
  program.command("effects").description("List catalog effects with what they take (image, video, svg, text, mask, path) and how they answer the pointer").option("--accepts <inputs>", "Only effects that take all of these (image, video, svg, text, mask, path; comma-separated or repeated)", collect, []).option("--interaction <ways>", "Only effects that answer the pointer in one of these ways (hover, press, click, trail, pointer)", collect, []).option("--category <category>", "Only this category").option("--kind <kind>", "shader or vector").option("--search <words>", "Words to look for in the id, name, description and tags").option("--json", "Output JSON (id, name, kind, category, description, inputs, presets)", false).action(async (options) => {
184
184
  try {
185
185
  const { findEffects, effectInputs } = await import("./catalog/index.js");
186
- const { effectQueryFromFlags, describeEffect } = await import("./effect-6VQS2CA5.js");
186
+ const { effectQueryFromFlags, describeEffect } = await import("./effect-HL4XF4FD.js");
187
187
  const effects = findEffects(effectQueryFromFlags(options));
188
188
  if (options.json) {
189
189
  console.log(JSON.stringify(effects.map((e) => ({ id: e.id, name: e.name, kind: e.kind, category: e.category, description: e.description, inputs: effectInputs(e), presets: e.presets?.map((p) => p.name) ?? [] })), null, 2));
@@ -204,7 +204,7 @@ ${effects.length} effect${effects.length === 1 ? "" : "s"}. Render one with: fla
204
204
  program.command("render <effect>").description("Render a catalog effect, applied to a subject, to .mp4, .webm, .mov, .gif, .svg (vector effects) or .png frames").option("--image <path>", "A picture (file path or URL); repeat for effects that take two", collect, []).option("--svg <path>", "An SVG (file path, URL or markup)", collect, []).option("--text <text>", "Words, for a text effect or a text mask", collect, []).option("--font <family>", "CSS font family for --text drawn into a shader").option("--weight <weight>", "Font weight for --text drawn into a shader").option("--path <d>", "SVG path data, for a shape control", collect, []).option("--video <path>", "A video file, decoded to frames with ffmpeg", collect, []).option("--preset <name>", "A preset by name").option("--set <name=value>", "A control value (repeatable): --set uSpeed=2 --set uTint=#ff0080", collect, []).option("--size <WxH>", "Pixels, like 1280x720").option("--fps <fps>", "Frames per second", "30").option("--duration <seconds>", "Seconds (default: the effect's loop or play-through)").option("--alpha", "Keep transparency (webm, mov, gif, png, svg)", false).option("--background <css>", "Colour under transparent pixels without --alpha").option("--no-pointer", "Leave the pointer still (interactive effects otherwise get a scripted pointer)").option("-o, --out <outputPath>", "Output: .mp4, .webm, .mov, .gif, .svg, a .png pattern (out_%04d.png) or a folder").action(async (effectId, options) => {
205
205
  try {
206
206
  if (!options.out) throw new Error("Say where to write it: -o out.mp4 (or .webm, .mov, .gif, .svg, out_%04d.png, a folder/).");
207
- const { exportEffect, parseSets, parseSize, resolveEffect, subjectsFromFlags } = await import("./effect-6VQS2CA5.js");
207
+ const { exportEffect, parseSets, parseSize, resolveEffect, subjectsFromFlags } = await import("./effect-HL4XF4FD.js");
208
208
  const effect = resolveEffect(effectId);
209
209
  const subjects = subjectsFromFlags(options);
210
210
  const size = options.size ? parseSize(options.size) : void 0;
@@ -14,8 +14,8 @@ import {
14
14
  subjectsFromFlags,
15
15
  vectorTimeline,
16
16
  videoSeconds
17
- } from "./chunk-UU4PHRVF.js";
18
- import "./chunk-7KYQ4R76.js";
17
+ } from "./chunk-O5QKC2L7.js";
18
+ import "./chunk-Z5EETGR4.js";
19
19
  import "./chunk-N5HXMQOK.js";
20
20
  import "./chunk-MOLJPWME.js";
21
21
  import "./chunk-2O3RMSOE.js";
@@ -44,4 +44,4 @@ export {
44
44
  vectorTimeline,
45
45
  videoSeconds
46
46
  };
47
- //# sourceMappingURL=effect-6VQS2CA5.js.map
47
+ //# sourceMappingURL=effect-HL4XF4FD.js.map
package/dist/node.js CHANGED
@@ -2,13 +2,13 @@ import {
2
2
  exportEffect,
3
3
  nodeSubjectValues,
4
4
  vectorTimeline
5
- } from "./chunk-UU4PHRVF.js";
5
+ } from "./chunk-O5QKC2L7.js";
6
6
  import {
7
7
  exportMotionGraphic,
8
8
  ffmpegPath,
9
9
  findChrome,
10
10
  planOutput
11
- } from "./chunk-7KYQ4R76.js";
11
+ } from "./chunk-Z5EETGR4.js";
12
12
  import "./chunk-N5HXMQOK.js";
13
13
  import "./chunk-MOLJPWME.js";
14
14
  import "./chunk-2O3RMSOE.js";
@@ -11,7 +11,7 @@ import {
11
11
  runRender,
12
12
  scriptedPointer,
13
13
  sequencePath
14
- } from "./chunk-7KYQ4R76.js";
14
+ } from "./chunk-Z5EETGR4.js";
15
15
  import "./chunk-HDYBY3PQ.js";
16
16
  import "./chunk-VYOMXE6I.js";
17
17
  export {
@@ -28,4 +28,4 @@ export {
28
28
  scriptedPointer,
29
29
  sequencePath
30
30
  };
31
- //# sourceMappingURL=recorder-TYMEZHOT.js.map
31
+ //# sourceMappingURL=recorder-YXZL6SXM.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/flair",
3
- "version": "0.35.0",
3
+ "version": "0.35.2",
4
4
  "description": "Web visual effects and motion graphics: WebGL2 shader effects, animated vector effects, a catalog of ready-made effects, and a UI-agnostic, agent-ready effect editor.",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/export/recorder.ts"],"sourcesContent":["// gifenc ships without types; the reference brings them along.\n/// <reference path=\"../shader/gifenc.d.ts\" />\nimport http from 'node:http';\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { controlValue, parseControls, type ControlValue, type TextureSettings } from '../shader/controls.js';\nimport { MAX_CLICKS, MAX_TRAIL, PointerTracker, TRAIL_SECONDS } from '../shader/pointer.js';\nimport { VERTEX_SOURCE, wrapSource } from '../shader/program.js';\nimport { SVG_EDGE } from '../shader/textures.js';\n\n// ==========================================\n// Tools: Chrome and ffmpeg\n// ==========================================\n\nconst CHROME_CANDIDATES = [\n '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n '/Applications/Chromium.app/Contents/MacOS/Chromium',\n 'C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n '/usr/bin/google-chrome',\n '/usr/bin/google-chrome-stable',\n '/usr/bin/chromium',\n '/usr/bin/chromium-browser',\n];\n\n/** Chromium builds Playwright downloaded (`npx playwright install chromium`), newest first. */\nfunction playwrightChromes(): string[] {\n const roots = [\n process.env.PLAYWRIGHT_BROWSERS_PATH,\n path.join(os.homedir(), 'Library/Caches/ms-playwright'),\n path.join(os.homedir(), '.cache/ms-playwright'),\n ].filter((r): r is string => Boolean(r));\n const found: string[] = [];\n for (const root of roots) {\n let dirs: string[] = [];\n try {\n dirs = fs.readdirSync(root).filter((d) => /^chromium-\\d+$/.test(d));\n } catch {\n continue;\n }\n dirs.sort((a, b) => Number(b.split('-')[1]) - Number(a.split('-')[1]));\n for (const dir of dirs) {\n for (const bin of [\n 'chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing',\n 'chrome-mac/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing',\n 'chrome-mac/Chromium.app/Contents/MacOS/Chromium',\n 'chrome-linux/chrome',\n 'chrome-linux64/chrome',\n ]) found.push(path.join(root, dir, bin));\n }\n }\n return found;\n}\n\n/**\n * Finds a local Chrome or Chromium executable, or `null`: `CHROME_PATH` (or\n * `CHROME_BIN`), then the usual install places, then a Chromium Playwright\n * downloaded.\n */\nexport function findChrome(): string | null {\n const env = process.env.CHROME_PATH || process.env.CHROME_BIN;\n if (env) return env;\n return [...CHROME_CANDIDATES, ...playwrightChromes()].find((candidate) => fs.existsSync(candidate)) ?? null;\n}\n\n/** The ffmpeg to run: `FFMPEG_PATH`, else `ffmpeg` on the `PATH`. */\nexport function ffmpegPath(): string {\n return process.env.FFMPEG_PATH || 'ffmpeg';\n}\n\nconst NO_FFMPEG =\n 'ffmpeg is not installed (or not on the PATH). Install it (brew install ffmpeg, apt install ffmpeg), or set FFMPEG_PATH; GIF, PNG and SVG output do not need it.';\n\n// ==========================================\n// Output formats\n// ==========================================\n\n/** A file type the exporter writes, from the output path. */\nexport type OutputFormat = 'mp4' | 'webm' | 'mov' | 'gif' | 'png' | 'svg';\n\n/** What to write and how. */\nexport interface OutputPlan {\n format: OutputFormat;\n /** The output path, resolved. For a PNG sequence, a printf pattern (`…/frame_%04d.png`). */\n path: string;\n /** Whether the frames keep their alpha. */\n alpha: boolean;\n /** ffmpeg's arguments after the input (for mp4, webm and mov). */\n ffmpegArgs?: string[];\n}\n\n/**\n * Works out the format from `outputPath`'s extension and what alpha it can\n * keep. A path with no extension, or ending in a slash, is a folder of PNG\n * frames; a `.png` path with a `%d` pattern (`out_%04d.png`) names each\n * frame, and one without gets `_%04d` added (unless there is one frame).\n * Throws a sentence for formats it cannot write, and for alpha in MP4.\n */\nexport function planOutput(outputPath: string, options: { alpha?: boolean; frames?: number } = {}): OutputPlan {\n const resolved = path.resolve(process.cwd(), outputPath);\n const trailingSlash = /[\\\\/]$/.test(outputPath);\n const ext = trailingSlash ? '' : path.extname(resolved).toLowerCase();\n const alpha = Boolean(options.alpha);\n switch (ext) {\n case '': {\n return { format: 'png', path: path.join(resolved, 'frame_%04d.png'), alpha };\n }\n case '.png': {\n if (/%0?\\d*d/.test(resolved) || (options.frames ?? 2) === 1) return { format: 'png', path: resolved, alpha };\n return { format: 'png', path: resolved.replace(/\\.png$/i, '_%04d.png'), alpha };\n }\n case '.gif':\n return { format: 'gif', path: resolved, alpha };\n case '.svg':\n return { format: 'svg', path: resolved, alpha };\n case '.mp4':\n case '.m4v': {\n if (alpha) throw new Error('MP4 (H.264) cannot hold alpha; write .webm, .mov, .gif or .png frames for a transparent render, or drop --alpha.');\n return {\n format: 'mp4',\n path: resolved,\n alpha,\n ffmpegArgs: [\n // yuv420p needs even sizes; sRGB pixels go to BT.709, tagged so players show the same colours.\n '-vf', 'pad=ceil(iw/2)*2:ceil(ih/2)*2,scale=out_color_matrix=bt709:out_range=tv,format=yuv420p',\n '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-crf', '18', '-preset', 'medium',\n '-colorspace', 'bt709', '-color_primaries', 'bt709', '-color_trc', 'bt709', '-color_range', 'tv',\n '-movflags', '+faststart',\n ],\n };\n }\n case '.webm':\n return {\n format: 'webm',\n path: resolved,\n alpha,\n ffmpegArgs: alpha\n ? ['-c:v', 'libvpx-vp9', '-pix_fmt', 'yuva420p', '-b:v', '0', '-crf', '30', '-auto-alt-ref', '0', '-row-mt', '1']\n : ['-vf', 'scale=out_color_matrix=bt709:out_range=tv,format=yuv420p', '-c:v', 'libvpx-vp9', '-pix_fmt', 'yuv420p', '-b:v', '0', '-crf', '30', '-row-mt', '1',\n '-colorspace', 'bt709', '-color_primaries', 'bt709', '-color_trc', 'bt709'],\n };\n case '.mov':\n return {\n format: 'mov',\n path: resolved,\n alpha,\n ffmpegArgs: [\n '-c:v', 'prores_ks', '-profile:v', '4444', '-vendor', 'apl0',\n '-pix_fmt', alpha ? 'yuva444p10le' : 'yuv444p10le',\n ...(alpha ? ['-alpha_bits', '16'] : []),\n '-colorspace', 'bt709', '-color_primaries', 'bt709', '-color_trc', 'bt709',\n ],\n };\n default:\n throw new Error(`Flair cannot write \"${ext}\" files; write .mp4, .webm, .mov, .gif, .svg, or .png frames (a folder, or a pattern like out_%04d.png).`);\n }\n}\n\n/** A frame number put into a PNG sequence's printf pattern (`%04d`, `%d`). */\nexport function sequencePath(pattern: string, frame: number): string {\n return pattern.replace(/%(0?)(\\d*)d/, (_, zero: string, width: string) => String(frame).padStart(Number(width || 0), zero ? '0' : ' '));\n}\n\n// ==========================================\n// The pointer, scripted\n// ==========================================\n\n/** Pointer uniforms for one frame. */\nexport interface PointerFrame {\n mouse: [number, number, number, number];\n pointer: [number, number, number, number];\n /** `iClicks`, MAX_CLICKS × 4 numbers. */\n clicks: number[];\n /** `iTrail`, MAX_TRAIL × 4 numbers. */\n trail: number[];\n}\n\n/**\n * A pointer that shows off an interactive shader in a recording: it hovers\n * over the canvas from the start, wanders a figure-of-eight (so trails have\n * a path), and clicks every `interval` seconds, holding each press for a\n * moment. Returns the uniforms for each frame time, in order.\n */\nexport function scriptedPointer(width: number, height: number, times: readonly number[], options: { interval?: number; period?: number } = {}): PointerFrame[] {\n const interval = options.interval ?? 0.8;\n const period = Math.max(2, options.period ?? (times[times.length - 1] ?? 2));\n const at = (t: number): [number, number] => [\n width * (0.5 + 0.3 * Math.sin((2 * Math.PI * t) / period)),\n height * (0.5 + 0.22 * Math.sin((4 * Math.PI * t) / period)),\n ];\n const press = 0.25;\n const tracker = new PointerTracker();\n tracker.hover(true, 0);\n tracker.move(...at(0), 0);\n let clock = 0;\n let nextClick = 0.25;\n let release = Infinity;\n let mouse: [number, number, number, number] = [0, 0, 0, 0];\n const frames: PointerFrame[] = [];\n for (const time of times) {\n // Walk the script up to `time` in trail-sized steps, so the trail and clicks land where they would live.\n while (clock < time) {\n const next = Math.min(time, clock + TRAIL_SECONDS, nextClick, release);\n clock = next;\n const [x, y] = at(clock);\n tracker.move(x, y, clock);\n if (mouse[2] > 0) mouse = [x, y, mouse[2], mouse[3]];\n if (clock >= release) {\n tracker.up(clock);\n mouse = [mouse[0], mouse[1], -mouse[2], -Math.abs(mouse[3])];\n release = Infinity;\n }\n if (clock >= nextClick) {\n tracker.down(x, y, clock);\n mouse = [x, y, x, y];\n release = clock + press;\n nextClick = clock + interval;\n }\n }\n frames.push({\n mouse: [...mouse],\n pointer: tracker.pointer(time),\n clicks: [...tracker.clicksAt(time)],\n trail: [...tracker.trailAt(time)],\n });\n }\n return frames;\n}\n\n// ==========================================\n// The page\n// ==========================================\n\n/** A shader control as the page sets it. */\nexport interface PageControl {\n name: string;\n type: string;\n value: ControlValue;\n texture?: TextureSettings;\n}\n\n/** What the render page draws. */\nexport interface PageConfig {\n width: number;\n height: number;\n frames: number;\n /** Keep alpha in the frames (else they are composited over `background`). */\n alpha: boolean;\n /** CSS colour under transparent pixels when `alpha` is false. */\n background: string;\n /** How frames go back: PNG files, or raw RGBA (rows top down). */\n send: 'png' | 'rgba';\n /** A shader to run, or (when absent) SVG frame markup from `/state`. */\n shader?: {\n /** The full GLSL ES 3.00 fragment shader (see {@link fragmentSource}). */\n fragment: string;\n controls: PageControl[];\n /** Draws its frame opaque, as `ShaderRenderer` does without `alpha`: its own alpha is ignored. */\n opaque: boolean;\n };\n}\n\n/** JSON that is safe inside a `<script>`. */\nconst scriptJson = (value: unknown) => JSON.stringify(value).replace(/</g, '\\\\u003c');\n\n/**\n * The page headless Chrome runs: it compiles the shader with the same\n * wrapper as `ShaderRenderer` (`wrapSource`, so every built-in, `iPointer`,\n * `iClicks`, `iTrail`, `iAudio`, `iSpectrum`, `iBeat` and `iScroll` included, is\n * declared; the audio and scroll ones stay 0, so those effects play their idle\n * demos), sets each control by its\n * type (`int` and `bool` as integers), loads image controls the way\n * `loadImage` does (SVGs sized to their long edge first) before the first\n * frame, and flips them as the renderer does. Each frame asks `/state` for\n * its time, pointer, video frames or SVG markup, draws, and posts the\n * pixels to `/frame`.\n */\nexport function renderPageHtml(config: PageConfig): string {\n return `<!doctype html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<style>html,body{margin:0;background:transparent;overflow:hidden}</style>\n</head>\n<body>\n<script>\nconst CONFIG = ${scriptJson(config)};\nconst VERTEX = ${scriptJson(VERTEX_SOURCE)};\nconst W = CONFIG.width, H = CONFIG.height;\nconst report = (message) => fetch(\"/error\", { method: \"POST\", body: String(message) });\naddEventListener(\"error\", (e) => report(\"The render page failed: \" + e.message));\naddEventListener(\"unhandledrejection\", (e) => report((e.reason && e.reason.message) || String(e.reason)));\n\n// SVGs decode at their own (often icon) size: give them ${SVG_EDGE} px on the long edge, and a viewBox.\nfunction sizeSvg(markup, w0, h0) {\n const doc = new DOMParser().parseFromString(markup, \"image/svg+xml\");\n const svg = doc.documentElement;\n if (svg.localName !== \"svg\" || doc.getElementsByTagName(\"parsererror\").length) throw new Error(\"Not an SVG document.\");\n const len = (n) => { const m = (svg.getAttribute(n) || \"\").trim().match(/^([\\\\d.]+)(px)?$/); return m && +m[1] > 0 ? +m[1] : 0; };\n const box = (svg.getAttribute(\"viewBox\") || \"\").trim().split(/[\\\\s,]+/).map(Number);\n const hasBox = box.length === 4 && box[2] > 0 && box[3] > 0;\n const w = len(\"width\") || (hasBox ? box[2] : 300), h = len(\"height\") || (hasBox ? box[3] : 150);\n if (!hasBox) svg.setAttribute(\"viewBox\", \"0 0 \" + w + \" \" + h);\n const scale = w0 ? 1 : ${SVG_EDGE} / Math.max(w, h);\n svg.setAttribute(\"width\", w0 || Math.max(1, Math.round(w * scale)));\n svg.setAttribute(\"height\", h0 || Math.max(1, Math.round(h * scale)));\n return new XMLSerializer().serializeToString(svg);\n}\nconst isSvg = (url) => /^data:image\\\\/svg\\\\+xml[;,]/i.test(url) || /^(?!data:)[^?#]*\\\\.svg([?#]|$)/i.test(url);\nasync function decode(src, name) {\n const img = new Image();\n img.src = src;\n try { await img.decode(); } catch (e) { throw new Error(\"Could not load the image \" + name + \".\"); }\n return img;\n}\nasync function loadImage(url, name) {\n if (!isSvg(url)) return decode(url, name);\n const text = await (await fetch(url)).text();\n const blob = URL.createObjectURL(new Blob([sizeSvg(text)], { type: \"image/svg+xml\" }));\n try { return await decode(blob, name); } finally { URL.revokeObjectURL(blob); }\n}\n\nconst out = document.createElement(\"canvas\");\nout.width = W; out.height = H;\nconst ctx = out.getContext(\"2d\", { willReadFrequently: CONFIG.send === \"rgba\" });\nlet gl = null, glCanvas = null, u = null;\nconst textures = {};\n\nfunction setupShader() {\n glCanvas = document.createElement(\"canvas\");\n glCanvas.width = W; glCanvas.height = H;\n // As createContext: no antialiasing, depth or stencil; unpremultiplied alpha so pixels read back exactly.\n gl = glCanvas.getContext(\"webgl2\", { alpha: true, premultipliedAlpha: false, preserveDrawingBuffer: true, antialias: false, depth: false, stencil: false, powerPreference: \"high-performance\" });\n if (!gl) throw new Error(\"WebGL2 is not available in headless Chrome.\");\n const compile = (type, src) => {\n const s = gl.createShader(type);\n gl.shaderSource(s, src);\n gl.compileShader(s);\n if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(\"The shader does not compile: \" + gl.getShaderInfoLog(s));\n return s;\n };\n const program = gl.createProgram();\n gl.attachShader(program, compile(gl.VERTEX_SHADER, VERTEX));\n gl.attachShader(program, compile(gl.FRAGMENT_SHADER, CONFIG.shader.fragment));\n gl.linkProgram(program);\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error(\"The shader does not link: \" + gl.getProgramInfoLog(program));\n gl.useProgram(program);\n u = (n) => gl.getUniformLocation(program, n);\n gl.viewport(0, 0, W, H);\n if (CONFIG.shader.opaque) {\n // An opaque canvas, as ShaderRenderer has without alpha: alpha stays 1 whatever the shader writes.\n gl.clearColor(0, 0, 0, 1);\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.colorMask(true, true, true, false);\n }\n}\n\nfunction makeTexture(unit, texture) {\n const t = gl.createTexture();\n const settings = texture || { filter: \"mipmap\", wrap: \"repeat\" };\n gl.activeTexture(gl.TEXTURE0 + unit);\n gl.bindTexture(gl.TEXTURE_2D, t);\n // Black until (or unless) a picture arrives, as TextureCache does.\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 255]));\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, { mipmap: gl.LINEAR_MIPMAP_LINEAR, linear: gl.LINEAR, nearest: gl.NEAREST }[settings.filter]);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, settings.filter === \"nearest\" ? gl.NEAREST : gl.LINEAR);\n const wrap = { repeat: gl.REPEAT, clamp: gl.CLAMP_TO_EDGE, mirror: gl.MIRRORED_REPEAT }[settings.wrap];\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);\n return { unit, texture: t };\n}\n\nfunction upload(name, img) {\n const slot = textures[name];\n if (!slot) return;\n gl.activeTexture(gl.TEXTURE0 + slot.unit);\n gl.bindTexture(gl.TEXTURE_2D, slot.texture);\n // Rows bottom up, as the renderer's TextureCache uploads them.\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);\n gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);\n gl.generateMipmap(gl.TEXTURE_2D);\n}\n\nasync function setControls() {\n let unit = 0;\n const loads = [];\n for (const c of CONFIG.shader.controls) {\n const loc = u(c.name);\n const v = c.value;\n if (c.type === \"sampler2D\") {\n textures[c.name] = makeTexture(unit, c.texture);\n if (loc) gl.uniform1i(loc, unit);\n unit++;\n if (v) loads.push(loadImage(v, \"for \" + c.name).then((img) => upload(c.name, img)));\n continue;\n }\n if (!loc) continue;\n if (c.type === \"float\") gl.uniform1f(loc, v);\n else if (c.type === \"int\") gl.uniform1i(loc, Math.round(v));\n else if (c.type === \"bool\") gl.uniform1i(loc, v ? 1 : 0);\n else if (Array.isArray(v)) gl[\"uniform\" + v.length + \"fv\"](loc, v);\n }\n await Promise.all(loads);\n}\n\nfunction drawShader(s) {\n gl.viewport(0, 0, W, H);\n const set = (n, f) => { const l = u(n); if (l) f(l); };\n set(\"iResolution\", (l) => gl.uniform3f(l, W, H, 1));\n set(\"iTime\", (l) => gl.uniform1f(l, s.time));\n set(\"iTimeDelta\", (l) => gl.uniform1f(l, s.delta));\n set(\"iFrame\", (l) => gl.uniform1i(l, s.frame));\n set(\"iMouse\", (l) => gl.uniform4fv(l, s.mouse));\n set(\"iPointer\", (l) => gl.uniform4fv(l, s.pointer));\n set(\"iClicks\", (l) => gl.uniform4fv(l, new Float32Array(s.clicks)));\n set(\"iTrail\", (l) => gl.uniform4fv(l, new Float32Array(s.trail)));\n // glslsandbox-style names, for complete programs.\n set(\"u_resolution\", (l) => gl.uniform2f(l, W, H));\n set(\"u_time\", (l) => gl.uniform1f(l, s.time));\n set(\"u_mouse\", (l) => gl.uniform2f(l, s.pointer[0], s.pointer[1]));\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n}\n\nasync function frameImage(s) {\n if (CONFIG.shader) {\n for (const [name, url] of Object.entries(s.video || {})) upload(name, await decode(url, \"of the video frame\"));\n drawShader(s);\n return glCanvas;\n }\n const markup = sizeSvg(s.svg, W, H);\n const url = URL.createObjectURL(new Blob([markup], { type: \"image/svg+xml\" }));\n try { return await decode(url, \"of the SVG frame\"); } finally { URL.revokeObjectURL(url); }\n}\n\nasync function capture(src) {\n // The WebGL canvas reads back exactly when nothing is composited under it.\n const direct = CONFIG.shader && (CONFIG.alpha || CONFIG.shader.opaque);\n if (direct) {\n if (CONFIG.send === \"png\") return new Promise((r) => glCanvas.toBlob(r, \"image/png\"));\n const px = new Uint8Array(W * H * 4);\n gl.readPixels(0, 0, W, H, gl.RGBA, gl.UNSIGNED_BYTE, px);\n const flipped = new Uint8Array(px.length), row = W * 4;\n for (let y = 0; y < H; y++) flipped.set(px.subarray((H - 1 - y) * row, (H - y) * row), y * row);\n return flipped;\n }\n ctx.clearRect(0, 0, W, H);\n if (!CONFIG.alpha) { ctx.fillStyle = CONFIG.background; ctx.fillRect(0, 0, W, H); }\n ctx.drawImage(src, 0, 0, W, H);\n if (CONFIG.send === \"png\") return new Promise((r) => out.toBlob(r, \"image/png\"));\n return ctx.getImageData(0, 0, W, H).data;\n}\n\n(async () => {\n if (CONFIG.shader) { setupShader(); await setControls(); }\n for (let i = 0; i < CONFIG.frames; i++) {\n const s = await (await fetch(\"/state?i=\" + i)).json();\n const body = await capture(await frameImage(s));\n const r = await fetch(\"/frame?i=\" + i, { method: \"POST\", body });\n if (!r.ok) return;\n }\n})().catch((e) => report((e && e.message) || String(e)));\n</script>\n</body>\n</html>\n`;\n}\n\n// ==========================================\n// The render loop\n// ==========================================\n\n/** One frame's state, as the page asks for it. */\nexport interface FrameState {\n time: number;\n delta: number;\n frame: number;\n mouse: [number, number, number, number];\n pointer: [number, number, number, number];\n clicks: number[];\n trail: number[];\n /** SVG markup to draw (vector frames). */\n svg?: string;\n /** Image control → local file to show this frame (video frames). */\n video?: Record<string, string>;\n}\n\n/** A render: the page to run, each frame's state, and where it goes. */\nexport interface RenderJob {\n page: PageConfig;\n state: (frame: number) => FrameState;\n plan: OutputPlan;\n fps: number;\n onProgress?: (frame: number, total: number) => void;\n}\n\n/** Frames written somewhere: a sink takes them in order and finishes. */\ninterface Sink {\n write(frame: number, data: Buffer): Promise<void>;\n finish(): Promise<void>;\n abort(): void;\n}\n\nfunction ffmpegSink(plan: OutputPlan, fps: number): Sink {\n const args = ['-y', '-hide_banner', '-loglevel', 'error', '-f', 'image2pipe', '-framerate', String(fps), '-c:v', 'png', '-i', '-', ...plan.ffmpegArgs!, '-r', String(fps), plan.path];\n const proc = spawn(ffmpegPath(), args, { stdio: ['pipe', 'ignore', 'pipe'] });\n let stderr = '';\n let failed: Error | null = null;\n proc.stderr!.on('data', (d) => (stderr += d.toString()));\n const done = new Promise<void>((resolve, reject) => {\n proc.on('error', (err: NodeJS.ErrnoException) => {\n failed = err.code === 'ENOENT' ? new Error(NO_FFMPEG) : new Error(`ffmpeg could not start: ${err.message}`);\n reject(failed);\n });\n proc.on('close', (code) => {\n if (code === 0) resolve();\n else reject((failed ??= new Error(`ffmpeg failed (exit ${code}): ${stderr.trim() || 'no message'}`)));\n });\n });\n done.catch(() => {});\n proc.stdin!.on('error', () => {});\n return {\n async write(_frame, data) {\n if (failed) throw failed;\n if (!proc.stdin!.write(data)) await new Promise((r) => proc.stdin!.once('drain', r));\n },\n async finish() {\n proc.stdin!.end();\n await done;\n },\n abort() {\n proc.kill('SIGKILL');\n },\n };\n}\n\nfunction pngSink(plan: OutputPlan): Sink {\n fs.mkdirSync(path.dirname(plan.path), { recursive: true });\n return {\n async write(frame, data) {\n await fs.promises.writeFile(plan.path.includes('%') ? sequencePath(plan.path, frame) : plan.path, data);\n },\n async finish() {},\n abort() {},\n };\n}\n\nasync function gifSink(plan: OutputPlan, width: number, height: number, fps: number): Promise<Sink> {\n // gifenc is CommonJS in Node: its functions may only be on the default export.\n const mod = (await import('gifenc')) as Partial<typeof import('gifenc')> & { default?: typeof import('gifenc') };\n const { GIFEncoder, quantize, applyPalette } = (typeof mod.GIFEncoder === 'function' ? mod : mod.default!) as typeof import('gifenc');\n const gif = GIFEncoder();\n const delay = 1000 / fps;\n return {\n async write(frame, data) {\n const rgba = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n const first = frame === 0 ? { repeat: 0 } : {};\n if (plan.alpha) {\n // One-bit alpha: each frame replaces the last (dispose 2), with a transparent palette entry.\n const palette = quantize(rgba, 256, { format: 'rgba4444', oneBitAlpha: true });\n const transparentIndex = palette.findIndex((c) => c[3] === 0);\n gif.writeFrame(applyPalette(rgba, palette, 'rgba4444'), width, height, {\n palette,\n delay,\n dispose: 2,\n ...(transparentIndex >= 0 ? { transparent: true, transparentIndex } : {}),\n ...first,\n });\n } else {\n const palette = quantize(rgba, 256);\n gif.writeFrame(applyPalette(rgba, palette), width, height, { palette, delay, ...first });\n }\n },\n async finish() {\n gif.finish();\n fs.mkdirSync(path.dirname(plan.path), { recursive: true });\n await fs.promises.writeFile(plan.path, gif.bytes());\n },\n abort() {},\n };\n}\n\n/** Chrome's flags: GPU WebGL where there is one (Metal on macOS), SwiftShader otherwise. */\nfunction chromeArgs(userDataDir: string, width: number, height: number, url: string): string[] {\n return [\n '--headless=new',\n `--user-data-dir=${userDataDir}`,\n `--window-size=${Math.max(64, width)},${Math.max(64, height)}`,\n '--use-gl=angle',\n ...(process.platform === 'darwin' ? ['--use-angle=metal'] : []),\n '--enable-webgl',\n '--enable-unsafe-swiftshader',\n '--ignore-gpu-blocklist',\n '--disable-gpu-sandbox',\n '--force-color-profile=srgb',\n '--no-first-run',\n '--no-default-browser-check',\n '--disable-background-networking',\n '--disable-background-timer-throttling',\n '--disable-renderer-backgrounding',\n '--disable-extensions',\n url,\n ];\n}\n\nconst STALL_MS = 60_000;\n\n/**\n * Runs a render: serves the page to headless Chrome on 127.0.0.1, hands it\n * each frame's state, and writes the frames it posts back. Resolves with the\n * output path (a PNG sequence's pattern).\n */\nexport async function runRender(job: RenderJob): Promise<string> {\n const { page, plan, fps, onProgress } = job;\n const total = page.frames;\n const chromeBin = findChrome();\n if (!chromeBin) {\n throw new Error('Chrome or Chromium is needed to render, and none was found. Install Google Chrome, run \"npx playwright install chromium\", or set CHROME_PATH.');\n }\n fs.mkdirSync(path.dirname(plan.path), { recursive: true });\n const sink = plan.format === 'gif' ? await gifSink(plan, page.width, page.height, fps) : plan.format === 'png' ? pngSink(plan) : ffmpegSink(plan, fps);\n const html = renderPageHtml(page);\n const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'flair-chrome-'));\n\n return new Promise<string>((resolve, reject) => {\n let chrome: ChildProcess | null = null;\n let finished = false;\n let written = 0;\n let stall: NodeJS.Timeout | undefined;\n const sockets = new Set<import('node:net').Socket>();\n\n const cleanup = () => {\n clearTimeout(stall);\n try {\n chrome?.kill('SIGKILL');\n } catch {}\n for (const s of sockets) s.destroy();\n server.close();\n fs.rm(userDataDir, { recursive: true, force: true }, () => {});\n };\n const fail = (err: Error) => {\n if (finished) return;\n finished = true;\n sink.abort();\n cleanup();\n reject(err);\n };\n const succeed = () => {\n if (finished) return;\n finished = true;\n cleanup();\n resolve(plan.path);\n };\n const watchdog = () => {\n clearTimeout(stall);\n stall = setTimeout(() => fail(new Error(`The render stalled: no frame for ${STALL_MS / 1000} s after frame ${written} of ${total}. Is WebGL2 available to headless Chrome?`)), STALL_MS);\n };\n\n const server = http.createServer((req, res) => {\n const url = new URL(req.url || '/', 'http://127.0.0.1');\n if (url.pathname === '/' && req.method === 'GET') {\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' });\n res.end(html);\n return;\n }\n if (url.pathname === '/state') {\n const i = Number(url.searchParams.get('i'));\n try {\n const s = job.state(i);\n const body = { ...s, ...(s.video ? { video: Object.fromEntries(Object.keys(s.video).map((k) => [k, `/video?i=${i}&name=${encodeURIComponent(k)}`])) } : {}) };\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(body));\n } catch (e) {\n res.writeHead(500);\n res.end();\n fail(e as Error);\n }\n return;\n }\n if (url.pathname === '/video') {\n const i = Number(url.searchParams.get('i'));\n const file = job.state(i).video?.[url.searchParams.get('name') ?? ''];\n if (!file) {\n res.writeHead(404);\n res.end();\n return;\n }\n res.writeHead(200, { 'Content-Type': 'image/png' });\n fs.createReadStream(file).pipe(res);\n return;\n }\n if (url.pathname === '/frame' && req.method === 'POST') {\n const i = Number(url.searchParams.get('i'));\n const chunks: Buffer[] = [];\n req.on('data', (c: Buffer) => chunks.push(c));\n req.on('end', () => {\n sink\n .write(i, Buffer.concat(chunks))\n .then(async () => {\n written++;\n watchdog();\n onProgress?.(written, total);\n res.writeHead(200);\n res.end('ok');\n if (written >= total) {\n await sink.finish();\n succeed();\n }\n })\n .catch((e: Error) => {\n res.writeHead(500);\n res.end();\n fail(e);\n });\n });\n return;\n }\n if (url.pathname === '/error' && req.method === 'POST') {\n let message = '';\n req.on('data', (c) => (message += c));\n req.on('end', () => {\n res.writeHead(200);\n res.end();\n fail(new Error(message || 'The render page failed.'));\n });\n return;\n }\n res.writeHead(404);\n res.end();\n });\n server.on('connection', (s) => {\n sockets.add(s);\n s.on('close', () => sockets.delete(s));\n });\n\n server.listen(0, '127.0.0.1', () => {\n const { port } = server.address() as import('node:net').AddressInfo;\n chrome = spawn(chromeBin, chromeArgs(userDataDir, page.width, page.height, `http://127.0.0.1:${port}/`), { stdio: 'ignore' });\n chrome.on('error', (err) => fail(new Error(`Could not launch Chrome at ${chromeBin}: ${err.message}`)));\n chrome.on('exit', (code) => {\n if (!finished && written < total) fail(new Error(`Chrome quit (exit ${code}) after ${written} of ${total} frames.`));\n });\n watchdog();\n });\n });\n}\n\n// ==========================================\n// Shader export\n// ==========================================\n\n/** Where the pointer is in a recording: nowhere (`none`), or the scripted pointer of {@link scriptedPointer} (`auto`). */\nexport type PointerMode = 'none' | 'auto';\n\nexport interface ExportOptions {\n /** GLSL fragment shader source (Shadertoy-style `mainImage`). */\n source: string;\n /**\n * Control values over the declared defaults. An image control takes a\n * URL, a `data:` URL, or a local file path (read into a `data:` URL).\n */\n values?: Record<string, ControlValue>;\n /** Destination: .mp4, .webm, .mov, .gif, or PNG frames (a folder or `out_%04d.png`). */\n outputPath: string;\n width?: number;\n height?: number;\n fps?: number;\n /** Seconds (default 3). */\n duration?: number;\n /** Keep the shader's alpha (WebM, MOV, GIF and PNG; MP4 is always opaque). */\n alpha?: boolean;\n /**\n * The shader draws over other content (`fragColor.a` is coverage): without\n * `alpha` it is composited over `background`. Otherwise its alpha is\n * ignored without `alpha`, as on an opaque canvas.\n */\n transparent?: boolean;\n /** CSS colour under transparent pixels when `alpha` is off (default black). */\n background?: string;\n /** The pointer: `none` (default) or `auto`, a scripted pointer that hovers, wanders and clicks. */\n pointer?: PointerMode;\n onProgress?: (frame: number, total: number) => void;\n}\n\nconst MIME: Record<string, string> = {\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.bmp': 'image/bmp',\n '.svg': 'image/svg+xml',\n};\n\n/**\n * An image for the page, as a `data:` URL: a local file is read, an\n * `http(s):` URL fetched (so CORS does not matter); `data:` URLs stay.\n */\nexport async function imageDataUrl(source: string, cwd = process.cwd()): Promise<string> {\n if (/^data:/i.test(source) || source === '') return source;\n if (/^https?:/i.test(source)) {\n const res = await fetch(source);\n if (!res.ok) throw new Error(`Could not download the image at ${source} (HTTP ${res.status}).`);\n const type = res.headers.get('content-type')?.split(';')[0] || MIME[path.extname(new URL(source).pathname).toLowerCase()] || 'application/octet-stream';\n return `data:${type};base64,${Buffer.from(await res.arrayBuffer()).toString('base64')}`;\n }\n const file = path.resolve(cwd, source.replace(/^file:\\/\\//, ''));\n if (!fs.existsSync(file)) throw new Error(`There is no image at ${file}.`);\n const type = MIME[path.extname(file).toLowerCase()];\n if (!type) throw new Error(`${path.basename(file)} is not an image Flair can show; give it a PNG, JPEG, GIF, WebP, AVIF, BMP or SVG.`);\n return `data:${type};base64,${fs.readFileSync(file).toString('base64')}`;\n}\n\n/**\n * The fragment shader the page compiles: a Shadertoy-style `mainImage`\n * source goes in `wrapSource`, as `ShaderRenderer` compiles it. A complete\n * program (one with its own `void main()`, glslsandbox-style, reading\n * `u_time` and `u_resolution`) is kept as written, with `#version 300 es`\n * added when it has none.\n */\nexport function fragmentSource(source: string): string {\n if (!/\\bvoid\\s+main\\s*\\(\\s*\\)/.test(source)) return wrapSource(source);\n return /^\\s*#version\\b/.test(source) ? source : `#version 300 es\\n#line 1\\n${source}`;\n}\n\n/** Shader controls with their values, images as `data:` URLs, ready for the page. */\nexport async function pageControls(source: string, values: Record<string, ControlValue> = {}): Promise<PageControl[]> {\n return Promise.all(\n parseControls(source).map(async (c) => {\n let value = controlValue(c, values);\n if (c.type === 'sampler2D' && typeof value === 'string') value = await imageDataUrl(value);\n return { name: c.name, type: c.type, value, ...(c.texture ? { texture: c.texture } : {}) };\n }),\n );\n}\n\n/** Frame times and pointer uniforms for `frames` frames at `fps`. */\nexport function frameStates(frames: number, fps: number, width: number, height: number, pointer: PointerMode): FrameState[] {\n const times = Array.from({ length: frames }, (_, i) => i / fps);\n const scripted = pointer === 'auto' ? scriptedPointer(width, height, times) : undefined;\n const none = { mouse: [0, 0, 0, 0] as [number, number, number, number], pointer: [0, 0, 0, 0] as [number, number, number, number], clicks: new Array(MAX_CLICKS * 4).fill(0), trail: new Array(MAX_TRAIL * 4).fill(0) };\n return times.map((time, i) => ({ time, delta: i === 0 ? 0 : 1 / fps, frame: i, ...(scripted?.[i] ?? none) }));\n}\n\n/**\n * Renders a shader animation frame by frame with WebGL2 in headless Chrome,\n * the way `ShaderRenderer` draws it (the same GLSL wrapper and built-ins,\n * images, int and bool controls), and writes MP4 (H.264), WebM (VP9, with\n * alpha when asked), MOV (ProRes 4444), GIF or PNG frames. MP4, WebM and\n * MOV need ffmpeg; GIF and PNG do not.\n */\nexport async function exportMotionGraphic(options: ExportOptions): Promise<string> {\n const { source, values = {}, outputPath, fps = 30, duration = 3, onProgress } = options;\n const width = Math.max(1, Math.round(options.width ?? 720));\n const height = Math.max(1, Math.round(options.height ?? 720));\n const frames = Math.max(1, Math.round(duration * fps));\n const plan = planOutput(outputPath, { alpha: options.alpha, frames });\n if (plan.format === 'svg') throw new Error('A shader cannot be written as SVG; write .mp4, .webm, .mov, .gif or .png frames.');\n const states = frameStates(frames, fps, width, height, options.pointer ?? 'none');\n return runRender({\n page: {\n width,\n height,\n frames,\n alpha: plan.alpha,\n background: options.background ?? '#000000',\n send: plan.format === 'gif' ? 'rgba' : 'png',\n shader: {\n fragment: fragmentSource(source),\n controls: await pageControls(source, values),\n opaque: !plan.alpha && !options.transparent,\n },\n },\n state: (i) => states[i]!,\n plan,\n fps,\n onProgress,\n });\n}\n"],"mappings":"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","names":["type"]}