← back to Wallco Ai

scripts/archive-svg-5400.js

53 lines

#!/usr/bin/env node
/**
 * Render full-size 150-DPI archives (5400×5400 = 36" tile) for HOT geometric SVG
 * tessellations, straight from each tile's .svg master (vector → crisp, no LANCZOS).
 * Saves a 2-ink LZW-compressed TIF to data/tif/ and records tif_path/bytes/dims.
 * Targets every svg-tessellate tile that was HOT'd (published OR curator-loved).
 *
 * Usage: node scripts/archive-svg-5400.js [--size 5400] [--ids 1,2,3]
 */
const fs = require('fs');
const path = require('path');
const { spawnSync } = require('child_process');

const ROOT = path.join(__dirname, '..');
const TIF_DIR = path.join(ROOT, 'data', 'tif');
const VENV_PY = path.join(ROOT, 'scripts', 'vectorize', '.venv', 'bin', 'python3');
const SNAP = path.join(ROOT, 'scripts', 'svg_raster_snap.py');
const DB = process.env.WALLCO_DB || 'dw_unified';
const flag = (k, d) => { const m = process.argv.find(a => a.startsWith('--' + k + '=')); return m ? m.split('=')[1] : d; };
const SIZE = parseInt(flag('size', '5400'), 10);
const IDS = flag('ids', '');

fs.mkdirSync(TIF_DIR, { recursive: true });
const psql = sql => {
  const r = spawnSync('psql', ['-d', DB, '-tAc', sql], { encoding: 'utf8' });
  if (r.status !== 0) throw new Error('psql: ' + (r.stderr || '').slice(0, 300));
  return (r.stdout || '').trim();
};

const where = IDS
  ? `id IN (${IDS.split(',').map(n => parseInt(n, 10)).filter(Number.isFinite).join(',')})`
  : `generator='svg-tessellate' AND (is_published=TRUE OR 'curator-loved'=ANY(tags)) AND tif_path IS NULL`;
const rows = psql(
  `SELECT id || '|' || local_path || '|' || COALESCE(palette->0->>'hex','#16203c') || '|' || COALESCE(palette->1->>'hex','#f2e9d3') ` +
  `FROM all_designs WHERE ${where} ORDER BY id;`
).split('\n').filter(Boolean);

console.log(`Archiving ${rows.length} HOT tiles → ${SIZE}px TIF\n`);
let ok = 0;
for (const row of rows) {
  const [id, png, ink, paper] = row.split('|');
  const svg = png.replace(/\.png$/, '.svg');
  if (!fs.existsSync(svg)) { console.log(`  ✗ #${id}: no svg master (${path.basename(svg)})`); continue; }
  const tif = path.join(TIF_DIR, path.basename(png).replace(/\.png$/, `_${SIZE}.tif`));
  const r = spawnSync(VENV_PY, [SNAP, svg, tif, String(SIZE), ink, paper], { encoding: 'utf8' });
  if (r.status !== 0 || !fs.existsSync(tif)) { console.log(`  ✗ #${id}: ${(r.stderr || r.stdout || 'raster failed').slice(0, 140)}`); continue; }
  const bytes = fs.statSync(tif).size;
  psql(`UPDATE all_designs SET tif_path='${tif.replace(/'/g, "''")}', tif_bytes=${bytes}, tif_w_px=${SIZE}, tif_h_px=${SIZE} WHERE id=${id};`);
  ok++;
  console.log(`  ✓ #${id}  ${(bytes/1024/1024).toFixed(2)} MB  ${path.basename(tif)}`);
}
console.log(`\nDone: ${ok}/${rows.length} archives rendered → ${TIF_DIR}`);