// Backfill cover-art for every station missing a local image. // // node server/scripts/download-images.js # only stations missing a local file // node server/scripts/download-images.js --all # re-download even cached stations // node server/scripts/download-images.js --no-scrape # skip homepage scrape fallback // // Strategy per station: // 1. Try downloading the existing image_url. // 2. On failure (404, HTML, dead host, ...), call scrapeIcon() to find a // fresh icon from Radio-Browser / the station homepage, persist the new // URL, and try again. // // Safe to re-run. import 'dotenv/config'; import { initDb } from '../db/index.js'; import { listStations, updateStation } from '../stations.js'; import { saveStationImageFromUrl, ensureImageDirs } from '../media/images.js'; import { scrapeIcon } from '../sources/iconScraper.js'; const FORCE = process.argv.includes('--all'); const NO_SCRAPE = process.argv.includes('--no-scrape'); const CONCURRENCY = 4; initDb(process.env.DB_PATH || './data/db/oradio.sqlite'); ensureImageDirs(); const all = listStations({ enabled: null }); const todo = all.filter((s) => FORCE || !s.image_path); console.log(`[images] ${todo.length} of ${all.length} stations to fetch (force=${FORCE}, scrape=${!NO_SCRAPE})`); let i = 0, ok = 0, scraped = 0, fail = 0; async function worker() { while (i < todo.length) { const idx = i++; const s = todo[idx]; const label = `[${idx + 1}/${todo.length}] ${s.name}`; // 1. Try the existing remote URL. let rel = s.image_url ? await saveStationImageFromUrl(s.id, s.image_url, { source: s.image_source || 'remote' }) : null; // 2. Fallback: scrape a fresh icon from the homepage / Radio-Browser. if (!rel && !NO_SCRAPE) { const found = await scrapeIcon(s); if (found && found !== s.image_url) { updateStation(s.id, { image_url: found }); rel = await saveStationImageFromUrl(s.id, found, { source: 'scraped' }); if (rel) scraped++; } } if (rel) { ok++; console.log(` ${label} -> ${rel}`); } else { fail++; console.log(` ${label} ✗ (${s.image_url || 'no url'})`); } } } await Promise.all(Array.from({ length: CONCURRENCY }, worker)); console.log(`[images] done. ok=${ok} (scraped=${scraped}) fail=${fail}`); process.exit(0);