← back to Ventura Corridor

scripts/ventura_films.cjs

100 lines

/**
 * Discover films & TV shows that mention Ventura Boulevard via Wikipedia.
 *
 * Method: full-text search Wikipedia for "Ventura Boulevard", then for each
 * hit fetch the categories — keep titles tagged with Films/Television/Music_video
 * categories. Saves to data/photos/ventura-films.json.
 *
 * No LLM. No paid API. Free Wikipedia API.
 */
const fs = require('fs');
const path = require('path');

const WP = 'https://en.wikipedia.org/w/api.php';
const OUT = path.join(__dirname, '../data/photos/ventura-films.json');

async function api(params) {
  const qs = new URLSearchParams({ format: 'json', formatversion: '2', origin: '*', ...params });
  const r = await fetch(WP + '?' + qs.toString(), {
    headers: { 'User-Agent': 'ventura-corridor/0.1 (steveabramsdesigns@gmail.com)' },
  });
  return r.json();
}

async function fullText(q) {
  const out = [];
  let off = 0;
  while (off < 300) {
    const data = await api({ action: 'query', list: 'search', srsearch: q, srlimit: 50, sroffset: off, srprop: 'snippet' });
    for (const m of data.query?.search || []) out.push({ title: m.title, snippet: (m.snippet || '').replace(/<[^>]+>/g, '') });
    if (!data.continue) break;
    off += 50;
  }
  return out;
}

async function categories(titles) {
  // 50 titles per request
  const out = {};
  for (let i = 0; i < titles.length; i += 50) {
    const batch = titles.slice(i, i + 50);
    const data = await api({
      action: 'query', titles: batch.join('|'),
      prop: 'categories', cllimit: 'max', clshow: '!hidden',
    });
    for (const p of data.query?.pages || []) {
      out[p.title] = (p.categories || []).map((c) => c.title);
    }
  }
  return out;
}

const FILM_CAT_PATTERNS = [
  /films?$/i,
  /films? (set|shot|filmed) in/i,
  /^Category:\d{4} films?$/i,
  /television series/i,
  /television shows? (set|filmed) in/i,
  /^Category:\d{4} American films?$/i,
  /music videos?/i,
  /^Category:Films directed by/i,
];

function isFilmOrTV(cats) {
  return cats.some((c) => FILM_CAT_PATTERNS.some((re) => re.test(c)));
}

(async () => {
  console.log('[films] searching Wikipedia for "Ventura Boulevard"…');
  const hits = await fullText('"Ventura Boulevard"');
  console.log(`[films] ${hits.length} initial hits`);
  const titles = hits.map((h) => h.title);
  console.log('[films] fetching categories for filtering…');
  const cats = await categories(titles);
  const films = [];
  for (const h of hits) {
    const cs = cats[h.title] || [];
    if (isFilmOrTV(cs)) {
      films.push({
        title: h.title,
        wikipedia_url: 'https://en.wikipedia.org/wiki/' + encodeURIComponent(h.title.replace(/ /g, '_')),
        snippet: h.snippet.slice(0, 240),
        kind: cs.find((c) => /television/i.test(c)) ? 'tv'
            : cs.find((c) => /music video/i.test(c)) ? 'music_video'
            : 'film',
        categories_film_or_tv: cs.filter((c) => FILM_CAT_PATTERNS.some((re) => re.test(c))).slice(0, 6),
      });
    }
  }
  // Sort films first, then TV, then music videos
  films.sort((a, b) => a.kind.localeCompare(b.kind) || a.title.localeCompare(b.title));
  fs.writeFileSync(OUT, JSON.stringify(films, null, 2));
  console.log(`[films] kept ${films.length} film/TV/music-video pages`);
  console.log(`[films] wrote ${OUT}`);
  const byKind = {};
  for (const f of films) byKind[f.kind] = (byKind[f.kind] || 0) + 1;
  for (const [k, v] of Object.entries(byKind)) console.log(`  ${k.padEnd(15)} ${v}`);
  console.log('\n[films] sample:');
  for (const f of films.slice(0, 12)) console.log(`  • ${f.kind.padEnd(13)} ${f.title}`);
})();