Files
annu-kute-ced/scripts/fetch-podcast-cover.mjs
sucupira 6c2df9b1cd Optimise les images distantes au build et fiabilise la QA
- scripts/fetch-podcast-cover.mjs : pochettes Castopod (flux + épisodes)
  téléchargées et converties en WebP 512px au prebuild (2,3 Mo → 59 Ko)
- scripts/optimize-remote-images.mjs : vignettes PeerTube (640px), avatars
  (96px) et médias Mastodon (800px) optimisés en WebP + mapping
  src/lib/server/image-map.json consommé par mapImage()
- a11y : liens footer soulignés dans les paragraphes, cibles tactiles ≥24px
  sur les liens des posts Mastodon
- image-map.ts : import JSON statique (le createRequire pointait sur le bundle)
- doc : étape prebuild documentée (dépendance ImageMagick)

Lighthouse mobile : 97/100/100/100 — LCP 2,4s, TBT 50ms, CLS 0,
poids accueil 900 Ko (budgets du playbook respectés)
2026-07-23 03:37:03 -04:00

52 lines
1.8 KiB
JavaScript

import { writeFileSync, statSync, mkdirSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
import { join, dirname, basename } from 'node:path';
import { fileURLToPath } from 'node:url';
const FEED_URL = 'https://kute.o-k-i.net/@annu_kute_cedric/feed';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
const EPISODES_DIR = join(ROOT, 'static', 'images', 'episodes');
const CHANNEL_COVER = join(ROOT, 'static', 'images', 'podcast-cover.webp');
mkdirSync(EPISODES_DIR, { recursive: true });
const xml = await (await fetch(FEED_URL)).text();
const urls = new Set();
const channel = xml.split('<item')[0] ?? '';
const channelMatch =
channel.match(/<itunes:image[^>]*href="([^"]+)"/i) ?? channel.match(/<url>([^<]+)<\/url>/i);
if (channelMatch) urls.add(channelMatch[1]);
for (const item of xml.split(/<item[\s>]/i).slice(1)) {
const match = item.match(/<itunes:image[^>]*href="([^"]+)"/i);
if (match) urls.add(match[1]);
}
let total = 0;
for (const url of urls) {
const isChannel = url === channelMatch?.[1];
const out = isChannel
? CHANNEL_COVER
: join(EPISODES_DIR, basename(url).replace(/\.(png|jpe?g|gif|webp)$/i, '.webp'));
try {
const res = await fetch(url);
if (!res.ok) {
console.warn(`[covers] HTTP ${res.status} pour ${url}`);
continue;
}
const source = Buffer.from(await res.arrayBuffer());
const tmp = `/tmp/opencode/cover-${Date.now()}`;
writeFileSync(tmp, source);
execFileSync('convert', [tmp, '-resize', '512x512>', '-quality', '82', '-define', 'webp:method=6', out]);
const size = statSync(out).size;
total += size;
console.log(`[covers] ${basename(out)} : ${Math.round(source.length / 1024)} Ko → ${Math.round(size / 1024)} Ko`);
} catch (err) {
console.warn(`[covers] échec pour ${url}:`, err.message);
}
}
console.log(`[covers] total optimisé : ${Math.round(total / 1024)} Ko`);