Sync all skills and memories 2026-04-14 07:27

This commit is contained in:
2026-04-14 07:27:20 +09:00
parent 516bb44fe6
commit 1eba2bca95
386 changed files with 167655 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
#!/usr/bin/env node
/**
* p5.js Skill — Headless Frame Export
*
* Captures frames from a p5.js sketch using Puppeteer (headless Chrome).
* Uses noLoop() + redraw() for DETERMINISTIC frame-by-frame control.
*
* IMPORTANT: Your sketch must call noLoop() in setup() and set
* window._p5Ready = true when initialized. This script calls redraw()
* for each frame capture, ensuring exact 1:1 correspondence between
* frameCount and captured frames.
*
* If the sketch does NOT set window._p5Ready, the script falls back to
* a timed capture mode (less precise, may drop/duplicate frames).
*
* Usage:
* node export-frames.js sketch.html [options]
*
* Options:
* --output <dir> Output directory (default: ./frames)
* --width <px> Canvas width (default: 1920)
* --height <px> Canvas height (default: 1080)
* --frames <n> Number of frames to capture (default: 1)
* --fps <n> Target FPS for timed fallback mode (default: 30)
* --wait <ms> Wait before first capture (default: 2000)
* --selector <sel> Canvas CSS selector (default: canvas)
*
* Examples:
* node export-frames.js sketch.html --frames 1 # single PNG
* node export-frames.js sketch.html --frames 300 --fps 30 # 10s at 30fps
* node export-frames.js sketch.html --width 3840 --height 2160 # 4K still
*
* Sketch template for deterministic capture:
* function setup() {
* createCanvas(1920, 1080);
* pixelDensity(1);
* noLoop(); // REQUIRED for deterministic capture
* window._p5Ready = true; // REQUIRED to signal readiness
* }
* function draw() { ... }
*/
const puppeteer = require('puppeteer');
const path = require('path');
const fs = require('fs');
// Parse CLI arguments
function parseArgs() {
const args = process.argv.slice(2);
const opts = {
input: null,
output: './frames',
width: 1920,
height: 1080,
frames: 1,
fps: 30,
wait: 2000,
selector: 'canvas',
};
for (let i = 0; i < args.length; i++) {
if (args[i].startsWith('--')) {
const key = args[i].slice(2);
const val = args[i + 1];
if (key in opts && val !== undefined) {
opts[key] = isNaN(Number(val)) ? val : Number(val);
i++;
}
} else if (!opts.input) {
opts.input = args[i];
}
}
if (!opts.input) {
console.error('Usage: node export-frames.js <sketch.html> [options]');
process.exit(1);
}
return opts;
}
async function main() {
const opts = parseArgs();
const inputPath = path.resolve(opts.input);
if (!fs.existsSync(inputPath)) {
console.error(`File not found: ${inputPath}`);
process.exit(1);
}
// Create output directory
fs.mkdirSync(opts.output, { recursive: true });
console.log(`Capturing ${opts.frames} frame(s) from ${opts.input}`);
console.log(`Resolution: ${opts.width}x${opts.height}`);
console.log(`Output: ${opts.output}/`);
const browser = await puppeteer.launch({
headless: 'new',
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-gpu',
'--disable-dev-shm-usage',
'--disable-web-security',
'--allow-file-access-from-files',
],
});
const page = await browser.newPage();
await page.setViewport({
width: opts.width,
height: opts.height,
deviceScaleFactor: 1,
});
// Navigate to sketch
const fileUrl = `file://${inputPath}`;
await page.goto(fileUrl, { waitUntil: 'networkidle0', timeout: 30000 });
// Wait for canvas to appear
await page.waitForSelector(opts.selector, { timeout: 10000 });
// Detect capture mode: deterministic (noLoop+redraw) vs timed (fallback)
let deterministic = false;
try {
await page.waitForFunction('window._p5Ready === true', { timeout: 5000 });
deterministic = true;
console.log(`Mode: deterministic (noLoop + redraw)`);
} catch {
console.log(`Mode: timed fallback (sketch does not set window._p5Ready)`);
console.log(` For frame-perfect capture, add noLoop() and window._p5Ready=true to setup()`);
await new Promise(r => setTimeout(r, opts.wait));
}
const startTime = Date.now();
for (let i = 0; i < opts.frames; i++) {
if (deterministic) {
// Advance exactly one frame
await page.evaluate(() => { redraw(); });
// Brief settle time for render to complete
await new Promise(r => setTimeout(r, 20));
}
const frameName = `frame-${String(i).padStart(4, '0')}.png`;
const framePath = path.join(opts.output, frameName);
// Capture the canvas element
const canvas = await page.$(opts.selector);
if (!canvas) {
console.error('Canvas element not found');
break;
}
await canvas.screenshot({ path: framePath, type: 'png' });
// Progress
if (i % 30 === 0 || i === opts.frames - 1) {
const pct = ((i + 1) / opts.frames * 100).toFixed(1);
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
process.stdout.write(`\r Frame ${i + 1}/${opts.frames} (${pct}%) — ${elapsed}s`);
}
// In timed mode, wait between frames
if (!deterministic && i < opts.frames - 1) {
await new Promise(r => setTimeout(r, 1000 / opts.fps));
}
}
console.log('\n Done.');
await browser.close();
}
main().catch(err => {
console.error('Error:', err.message);
process.exit(1);
});

View File

@@ -0,0 +1,108 @@
#!/bin/bash
# p5.js Skill — Headless Render Pipeline
# Renders a p5.js sketch to MP4 video via Puppeteer + ffmpeg
#
# Usage:
# bash scripts/render.sh sketch.html output.mp4 [options]
#
# Options:
# --width Canvas width (default: 1920)
# --height Canvas height (default: 1080)
# --fps Frames per second (default: 30)
# --duration Duration in seconds (default: 10)
# --quality CRF value 0-51 (default: 18, lower = better)
# --frames-only Only export frames, skip MP4 encoding
#
# Examples:
# bash scripts/render.sh sketch.html output.mp4
# bash scripts/render.sh sketch.html output.mp4 --duration 30 --fps 60
# bash scripts/render.sh sketch.html output.mp4 --width 3840 --height 2160
set -euo pipefail
# Defaults
WIDTH=1920
HEIGHT=1080
FPS=30
DURATION=10
CRF=18
FRAMES_ONLY=false
# Parse arguments
INPUT="${1:?Usage: render.sh <input.html> <output.mp4> [options]}"
OUTPUT="${2:?Usage: render.sh <input.html> <output.mp4> [options]}"
shift 2
while [[ $# -gt 0 ]]; do
case $1 in
--width) WIDTH="$2"; shift 2 ;;
--height) HEIGHT="$2"; shift 2 ;;
--fps) FPS="$2"; shift 2 ;;
--duration) DURATION="$2"; shift 2 ;;
--quality) CRF="$2"; shift 2 ;;
--frames-only) FRAMES_ONLY=true; shift ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
TOTAL_FRAMES=$((FPS * DURATION))
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
FRAME_DIR=$(mktemp -d)
echo "=== p5.js Render Pipeline ==="
echo "Input: $INPUT"
echo "Output: $OUTPUT"
echo "Resolution: ${WIDTH}x${HEIGHT}"
echo "FPS: $FPS"
echo "Duration: ${DURATION}s (${TOTAL_FRAMES} frames)"
echo "Quality: CRF $CRF"
echo "Frame dir: $FRAME_DIR"
echo ""
# Check dependencies
command -v node >/dev/null 2>&1 || { echo "Error: Node.js required"; exit 1; }
if [ "$FRAMES_ONLY" = false ]; then
command -v ffmpeg >/dev/null 2>&1 || { echo "Error: ffmpeg required for MP4"; exit 1; }
fi
# Step 1: Capture frames via Puppeteer
echo "Step 1/2: Capturing ${TOTAL_FRAMES} frames..."
node "$SCRIPT_DIR/export-frames.js" \
"$INPUT" \
--output "$FRAME_DIR" \
--width "$WIDTH" \
--height "$HEIGHT" \
--frames "$TOTAL_FRAMES" \
--fps "$FPS"
echo "Frames captured to $FRAME_DIR"
if [ "$FRAMES_ONLY" = true ]; then
echo "Frames saved to: $FRAME_DIR"
echo "To encode manually:"
echo " ffmpeg -framerate $FPS -i $FRAME_DIR/frame-%04d.png -c:v libx264 -crf $CRF -pix_fmt yuv420p $OUTPUT"
exit 0
fi
# Step 2: Encode to MP4
echo "Step 2/2: Encoding MP4..."
ffmpeg -y \
-framerate "$FPS" \
-i "$FRAME_DIR/frame-%04d.png" \
-c:v libx264 \
-preset slow \
-crf "$CRF" \
-pix_fmt yuv420p \
-movflags +faststart \
"$OUTPUT" \
2>"$FRAME_DIR/ffmpeg.log"
# Cleanup
rm -rf "$FRAME_DIR"
# Report
FILE_SIZE=$(ls -lh "$OUTPUT" | awk '{print $5}')
echo ""
echo "=== Done ==="
echo "Output: $OUTPUT ($FILE_SIZE)"
echo "Duration: ${DURATION}s at ${FPS}fps, ${WIDTH}x${HEIGHT}"

View File

@@ -0,0 +1,28 @@
#!/bin/bash
# p5.js Skill — Local Development Server
# Serves the current directory over HTTP for loading local assets (fonts, images)
#
# Usage:
# bash scripts/serve.sh [port] [directory]
#
# Examples:
# bash scripts/serve.sh # serve CWD on port 8080
# bash scripts/serve.sh 3000 # serve CWD on port 3000
# bash scripts/serve.sh 8080 ./my-project # serve specific directory
PORT="${1:-8080}"
DIR="${2:-.}"
echo "=== p5.js Dev Server ==="
echo "Serving: $(cd "$DIR" && pwd)"
echo "URL: http://localhost:$PORT"
echo "Press Ctrl+C to stop"
echo ""
cd "$DIR" && python3 -m http.server "$PORT" 2>/dev/null || {
echo "Python3 not found. Trying Node.js..."
npx serve -l "$PORT" "$DIR" 2>/dev/null || {
echo "Error: Need python3 or npx (Node.js) for local server"
exit 1
}
}

View File

@@ -0,0 +1,87 @@
#!/bin/bash
# p5.js Skill — Dependency Verification
# Run: bash skills/creative/p5js/scripts/setup.sh
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
ok() { echo -e "${GREEN}[OK]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
fail() { echo -e "${RED}[FAIL]${NC} $1"; }
echo "=== p5.js Skill — Setup Check ==="
echo ""
# Required: Node.js (for Puppeteer headless export)
if command -v node &>/dev/null; then
NODE_VER=$(node -v)
ok "Node.js $NODE_VER"
else
warn "Node.js not found — optional, needed for headless export"
echo " Install: https://nodejs.org/ or 'brew install node'"
fi
# Required: npm (for Puppeteer install)
if command -v npm &>/dev/null; then
NPM_VER=$(npm -v)
ok "npm $NPM_VER"
else
warn "npm not found — optional, needed for headless export"
fi
# Optional: Puppeteer
if node -e "require('puppeteer')" 2>/dev/null; then
ok "Puppeteer installed"
else
warn "Puppeteer not installed — needed for headless export"
echo " Install: npm install puppeteer"
fi
# Optional: ffmpeg (for MP4 encoding from frame sequences)
if command -v ffmpeg &>/dev/null; then
FFMPEG_VER=$(ffmpeg -version 2>&1 | head -1 | awk '{print $3}')
ok "ffmpeg $FFMPEG_VER"
else
warn "ffmpeg not found — needed for MP4 export"
echo " Install: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)"
fi
# Optional: Python3 (for local server)
if command -v python3 &>/dev/null; then
PY_VER=$(python3 --version 2>&1 | awk '{print $2}')
ok "Python $PY_VER (for local server: python3 -m http.server)"
else
warn "Python3 not found — needed for local file serving"
fi
# Browser check (macOS)
if [[ "$(uname)" == "Darwin" ]]; then
if open -Ra "Google Chrome" 2>/dev/null; then
ok "Google Chrome found"
elif open -Ra "Safari" 2>/dev/null; then
ok "Safari found"
else
warn "No browser detected"
fi
fi
echo ""
echo "=== Core Requirements ==="
echo " A modern browser (Chrome/Firefox/Safari/Edge)"
echo " p5.js loaded via CDN — no local install needed"
echo ""
echo "=== Optional (for export) ==="
echo " Node.js + Puppeteer — headless frame capture"
echo " ffmpeg — frame sequence to MP4"
echo " Python3 — local development server"
echo ""
echo "=== Quick Start ==="
echo " 1. Create an HTML file with inline p5.js sketch"
echo " 2. Open in browser: open sketch.html"
echo " 3. Press 's' to save PNG, 'g' to save GIF"
echo ""
echo "Setup check complete."