Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
5.5 kB · 132 lines
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133#!/usr/bin/env node// cornerfive/bin/gen-illy.mjs — generate the cornerfive album cover via// gpt-image-2, then embed it into the rendered mp3 as ID3 art.//// cornerfive is the whistlegraph "five in the corner x2" remix — kick +// sine-bell sister line + the whistle. The cover draws that idea: a// whistle made visible as a luminous sine-line sinking the orange 5-ball// in the corner pocket, twice. Same identity-ref path as the other /pop// gen-illy scripts (momboba, marimba).//// Prompt: pop/cornerfive/cornerfive.illy.txt (lowercase fragments — papers voice)// Output: pop/cornerfive/out/cornerfive-cover.png// Embed: pop/cornerfive/out/cornerfive.mp3 (ID3v2 attached picture)//// Usage:// node pop/cornerfive/bin/gen-illy.mjs # cached if cover exists// node pop/cornerfive/bin/gen-illy.mjs --force # regenerate// node pop/cornerfive/bin/gen-illy.mjs --embed-only # skip gen, just embed
import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync } from "node:fs";import { resolve, dirname } from "node:path";import { fileURLToPath } from "node:url";import { spawnSync } from "node:child_process";
const HERE = dirname(fileURLToPath(import.meta.url));const LANE = resolve(HERE, "..");const REPO = resolve(LANE, "..", "..");
const flags = {};for (let i = 2; i < process.argv.length; i++) { if (process.argv[i].startsWith("--")) flags[process.argv[i].slice(2)] = true;}const FORCE = flags.force === true;const SIZE = "1024x1024";
const _af = (k, d) => { const i = process.argv.indexOf(k); return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : d; };const PROMPT_PATH = resolve(process.cwd(), _af("--prompt", `${LANE}/cornerfive.illy.txt`));const OUT_PATH = resolve(process.cwd(), _af("--cover", `${LANE}/out/cornerfive-cover.png`));const MP3_PATH = resolve(process.cwd(), _af("--mp3", `${LANE}/out/cornerfive.mp3`));mkdirSync(`${LANE}/out`, { recursive: true });
// ── identity refs (mirrors momboba/bin/gen-illy.mjs) ─────────────────────const SHOOT_DIR = `${REPO}/portraits/jeffrey/corpus/shoot-2k`;const ARCHIVE_DIR = `${REPO}/portraits/jeffrey/ig-archive/whistlegraph`;const REFS = [ `${SHOOT_DIR}/jeffery-av--07.jpg`, `${SHOOT_DIR}/jeffery-av--01.jpg`, `${SHOOT_DIR}/jeffery-av--04.jpg`, `${ARCHIVE_DIR}/2018-12-02_Bq4ckGFFNtW.jpg`, `${ARCHIVE_DIR}/2020-09-02_CEpxlO2FOvD.jpg`, `${ARCHIVE_DIR}/2021-07-10_CRI095Vl7AO_1.jpg`, `${ARCHIVE_DIR}/2025-01-25_DFQ2lHPzN_W.jpg`,].filter((p) => { if (existsSync(p)) return true; console.warn(` ⚠ ref missing, dropping: ${p}`); return false;});
function loadOpenAIKey() { if (process.env.OPENAI_API_KEY) return process.env.OPENAI_API_KEY; const vault = `${REPO}/aesthetic-computer-vault/.devcontainer/envs/devcontainer.env`; if (existsSync(vault)) { for (const line of readFileSync(vault, "utf8").split("\n")) { if (line.startsWith("OPENAI_API_KEY=")) { return line.slice("OPENAI_API_KEY=".length).trim().replace(/^['"]|['"]$/g, ""); } } } throw new Error("OPENAI_API_KEY not set and not found in vault");}
// ── embed the cover into the mp3 as ID3v2 attached picture ───────────────function embedCover() { if (!existsSync(OUT_PATH) || !existsSync(MP3_PATH)) { console.warn(` ⚠ skip embed — need both ${OUT_PATH} and ${MP3_PATH}`); return; } const tmp = `${MP3_PATH}.embed.mp3`; const r = spawnSync("ffmpeg", ["-hide_banner", "-y", "-loglevel", "error", "-i", MP3_PATH, "-i", OUT_PATH, "-map", "0:a", "-map", "1:v", "-c", "copy", "-id3v2_version", "3", "-metadata:s:v", "title=cover", "-metadata:s:v", "comment=Cover (front)", "-disposition:v", "attached_pic", tmp], { stdio: "inherit" }); if (r.status !== 0 || !existsSync(tmp)) { console.error("✗ embed failed"); return; } renameSync(tmp, MP3_PATH); console.log(`✓ cover embedded → ${MP3_PATH.replace(REPO + "/", "")}`);}
if (flags["embed-only"]) { embedCover(); process.exit(0); }
if (existsSync(OUT_PATH) && !FORCE) { console.log(`✓ cached cover → ${OUT_PATH.replace(REPO + "/", "")} (use --force to regen)`); embedCover(); process.exit(0);}
const apiKey = loadOpenAIKey();const prompt = readFileSync(PROMPT_PATH, "utf8").trim();console.log(`▸ cornerfive cover · ${SIZE} · ${REFS.length} refs`);const t0 = Date.now();
const fd = new FormData();fd.append("model", "gpt-image-2");fd.append("prompt", prompt);fd.append("size", SIZE);fd.append("quality", "high");fd.append("n", "1");for (const ref of REFS) { const buf = readFileSync(ref); const ext = ref.toLowerCase().endsWith(".png") ? "png" : ref.toLowerCase().endsWith(".webp") ? "webp" : "jpeg"; fd.append("image[]", new Blob([buf], { type: `image/${ext}` }), ref.split("/").pop());}const res = await fetch("https://api.openai.com/v1/images/edits", { method: "POST", headers: { Authorization: `Bearer ${apiKey}` }, body: fd,});if (!res.ok) { console.error(`✗ OpenAI ${res.status}: ${(await res.text()).slice(0, 600)}`); process.exit(1);}const json = await res.json();const b64 = json.data?.[0]?.b64_json;if (!b64) { console.error(`✗ no image: ${JSON.stringify(json).slice(0, 280)}`); process.exit(1);}writeFileSync(OUT_PATH, Buffer.from(b64, "base64"));console.log(`✓ ${((Date.now() - t0) / 1000).toFixed(1)}s → ${OUT_PATH.replace(REPO + "/", "")}`);
embedCover();