← 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}`);
})();