Files
radio-explorer/server/scripts/download-images.js
Marco Mooren b86dcfbb8d Add master display UI with audio output management and styling
- Implement main.js for the master display functionality, including WebSocket connection, audio output management, and state handling.
- Create style.css for the master display's visual design, ensuring a cohesive look and feel with a dark theme and responsive layout.
- Integrate device management with a fallback for non-Electron environments, allowing users to select audio outputs.
- Add features for managing favorites, including toggling favorites and filtering by genre.
- Enhance user experience with a responsive favorites grid and drag-to-scroll functionality.
2026-05-11 17:55:09 +02:00

66 lines
2.4 KiB
JavaScript

// 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);