← back to NationalPaperHangers

lib/segment-image.js

90 lines

// Per-installer deterministic public-domain image picker.
// Strategy: pick the installer's primary market_segment, fall through to
// related segments, and finally to 'generic'. Index = installer.id mod
// pool_size so the same studio always gets the same photo across renders.
// PD-only — every entry in manifest.json is Wikimedia Commons public-domain
// or CC0 (the fetcher rejects anything else).

const path = require('path');
const fs = require('fs');

let _manifest = null;
let _byPath = null;
const FALLBACK = {
  silk:               ['hand_painted', 'mural', 'luxury_residential', 'generic'],
  hospitality:        ['luxury_residential', 'museum', 'generic'],
  museum:             ['luxury_residential', 'hand_painted', 'generic'],
  hand_painted:       ['mural', 'luxury_residential', 'generic'],
  grasscloth:         ['hand_painted', 'luxury_residential', 'generic'],
  mural:              ['hand_painted', 'luxury_residential', 'generic'],
  luxury_residential: ['hand_painted', 'mural', 'museum', 'generic'],
  retail:             ['hospitality', 'luxury_residential', 'generic'],
  yacht:              ['luxury_residential', 'hospitality', 'generic'],
  generic:            []
};

function loadManifest() {
  if (_manifest) return _manifest;
  const p = path.join(__dirname, '..', 'public', 'img', 'segments', 'manifest.json');
  try {
    const txt = fs.readFileSync(p, 'utf8');
    _manifest = JSON.parse(txt);
    _byPath = {};
    for (const it of (_manifest.items || [])) _byPath[it.file] = it;
  } catch {
    _manifest = { items: [] };
    _byPath = {};
  }
  return _manifest;
}

function imagesIn(segment) {
  const m = loadManifest();
  return (m.items || []).filter(it => it.segment === segment);
}

function pickSegmentImage(installer) {
  if (!installer) return null;
  const segs = Array.isArray(installer.market_segments) ? installer.market_segments : [];
  const candidates = [];
  if (segs.length) candidates.push(segs[0]);
  if (segs[0] && FALLBACK[segs[0]]) candidates.push(...FALLBACK[segs[0]]);
  if (!candidates.length) candidates.push('generic');
  // Always end with 'generic' as final fallback.
  if (!candidates.includes('generic')) candidates.push('generic');

  for (const seg of candidates) {
    const pool = imagesIn(seg);
    if (pool.length) {
      const idx = Math.abs(Number(installer.id) || 0) % pool.length;
      return pool[idx];
    }
  }
  return null;
}

// Public-facing license/attribution string for a manifest item.
// Cooper Hewitt + Met + Brooklyn Museum entries usually carry a creator;
// generic site dumps may not. We render this minimally: "Photo: <creator> ·
// <license> · Wikimedia Commons" — link to source_url for users who want
// to verify.
function attributionFor(file) {
  const m = loadManifest();
  if (!_byPath) return null;
  const it = _byPath[file];
  if (!it) return null;
  return {
    creator: it.creator || null,
    license: it.license || 'Public domain',
    source_url: it.source_url || null,
    source: 'Wikimedia Commons'
  };
}

module.exports = {
  loadManifest,
  imagesIn,
  pickSegmentImage,
  attributionFor
};