← back to Allnewsdaily
allnewsdaily Short: show each network's real logo on the source chip
54ada90c07f6fca4bfc6351fe5445fe3d52bca72 · 2026-09-09 23:26:45 -0700 · Steve Abrams
fetch-stock.mjs downloads one logo per story (domain-derived: Clearbit → DuckDuckGo icon
proxy → Google favicons). render-short.js composites the logo + outlet name into a white
lower-third chip, replacing the red VIA {OUTLET} text (which stays as fallback when a logo
misses). Nominative-use attribution logos, not article imagery. Verified 6/6 logos + legible.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M scripts/short/fetch-stock.mjsM scripts/short/render-short.js
Diff
commit 54ada90c07f6fca4bfc6351fe5445fe3d52bca72
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 9 23:26:45 2026 -0700
allnewsdaily Short: show each network's real logo on the source chip
fetch-stock.mjs downloads one logo per story (domain-derived: Clearbit → DuckDuckGo icon
proxy → Google favicons). render-short.js composites the logo + outlet name into a white
lower-third chip, replacing the red VIA {OUTLET} text (which stays as fallback when a logo
misses). Nominative-use attribution logos, not article imagery. Verified 6/6 logos + legible.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
scripts/short/fetch-stock.mjs | 53 +++++++++++++++++++++++++++++++++++--------
scripts/short/render-short.js | 33 ++++++++++++++++++++-------
2 files changed, 68 insertions(+), 18 deletions(-)
diff --git a/scripts/short/fetch-stock.mjs b/scripts/short/fetch-stock.mjs
index 391f94f..5891aa1 100644
--- a/scripts/short/fetch-stock.mjs
+++ b/scripts/short/fetch-stock.mjs
@@ -12,6 +12,7 @@ const DIR = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(DIR, '../..');
const DATA = path.join(ROOT, 'data', 'short');
const IMG = path.join(DATA, 'img');
+const LOGO = path.join(DATA, 'logo');
const OPENVERSE = 'https://api.openverse.org/v1/images/';
const STOP = new Set(('a,an,the,of,to,in,on,for,and,or,but,after,before,as,at,by,with,from,into,over,under,is,are,was,were,' +
@@ -56,6 +57,33 @@ async function downloadFirstUsable(results, outPath) {
const GENERIC = ['news studio broadcast', 'city skyline', 'government capitol building', 'world globe earth',
'newspaper headlines', 'crowd people street', 'flags government', 'stock market chart', 'satellite earth night'];
+function domainOf(link) { try { return new URL(link).hostname.replace(/^www\./, ''); } catch { return null; } }
+const EXT = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/x-icon': 'ico', 'image/vnd.microsoft.icon': 'ico', 'image/svg+xml': 'svg', 'image/webp': 'webp', 'image/gif': 'gif' };
+
+// Each network's real logo: Clearbit (best) → DuckDuckGo icon proxy → Google favicons (always returns something).
+async function grabLogo(domain, outBase) {
+ if (!domain) return { ok: false, note: 'no domain' };
+ const sources = [
+ `https://logo.clearbit.com/${domain}?size=256`,
+ `https://icons.duckduckgo.com/ip3/${domain}.ico`,
+ `https://www.google.com/s2/favicons?sz=256&domain=${domain}`,
+ ];
+ for (const src of sources) {
+ try {
+ const r = await tfetch(src, {}, 8000);
+ if (!r.ok) continue;
+ const ct = (r.headers.get('content-type') || '').split(';')[0].trim();
+ if (!/image\//.test(ct) || ct === 'image/svg+xml') continue; // skip svg (IM raster handling varies)
+ const buf = Buffer.from(await r.arrayBuffer());
+ if (buf.length < 400) continue;
+ const out = `${outBase}.${EXT[ct] || 'png'}`;
+ fs.writeFileSync(out, buf);
+ return { ok: true, domain, via: src.split('/')[2], bytes: buf.length };
+ } catch { /* next source */ }
+ }
+ return { ok: false, domain, note: 'no logo source hit' };
+}
+
async function grabWithFallback(primaryQuery, outPath, idx) {
let r = await grab(primaryQuery, outPath); if (r.ok) return { ...r, used: primaryQuery };
const t1 = primaryQuery.split(' ')[0];
@@ -78,17 +106,22 @@ async function grab(query, outPath) {
(async () => {
const storiesDoc = JSON.parse(fs.readFileSync(path.join(DATA, 'stories.json'), 'utf8'));
- fs.rmSync(IMG, { recursive: true, force: true });
- fs.mkdirSync(IMG, { recursive: true });
+ for (const dir of [IMG, LOGO]) { fs.rmSync(dir, { recursive: true, force: true }); fs.mkdirSync(dir, { recursive: true }); }
+
+ const imgJobs = [];
+ imgJobs.push(grabWithFallback('news studio broadcast', path.join(IMG, 'intro.jpg'), 0).then((r) => ['img:intro', r]));
+ storiesDoc.stories.forEach((s, i) => imgJobs.push(grabWithFallback(queryFor(s.headline), path.join(IMG, `${s.n}.jpg`), i + 1).then((r) => [`img:beat${s.n}`, r])));
+ imgJobs.push(grabWithFallback('world globe earth', path.join(IMG, 'outro.jpg'), 8).then((r) => ['img:outro', r]));
- const jobs = [];
- jobs.push(grabWithFallback('news studio broadcast', path.join(IMG, 'intro.jpg'), 0).then((r) => ['intro', r]));
- storiesDoc.stories.forEach((s, i) => jobs.push(grabWithFallback(queryFor(s.headline), path.join(IMG, `${s.n}.jpg`), i + 1).then((r) => [`beat${s.n}`, r])));
- jobs.push(grabWithFallback('world globe earth', path.join(IMG, 'outro.jpg'), 8).then((r) => ['outro', r]));
+ // one real network logo per story (Clearbit → DuckDuckGo → Google favicons)
+ const logoJobs = storiesDoc.stories.map((s) => grabLogo(domainOf(s.link), path.join(LOGO, String(s.n))).then((r) => [`logo:${s.outlet}`, r]));
- const results = await Promise.all(jobs);
- let ok = 0;
- for (const [tag, r] of results) { if (r.ok) ok++; console.log(` ${tag}: ${r.ok ? '✓ ' + (r.used || '') : '✗ ' + (r.note || 'fail')}`); }
- console.log(`[fetch-stock] ${ok}/${results.length} images downloaded (CC0/public-domain, commercial-safe).`);
+ const results = await Promise.all([...imgJobs, ...logoJobs]);
+ let img = 0, logo = 0;
+ for (const [tag, r] of results) {
+ if (r.ok) { if (tag.startsWith('logo')) logo++; else img++; }
+ console.log(` ${tag}: ${r.ok ? '✓ ' + (r.used || r.via || r.domain || '') : '✗ ' + (r.note || 'fail')}`);
+ }
+ console.log(`[fetch-stock] ${img} stock images (CC0/public-domain) + ${logo}/${storiesDoc.stories.length} network logos.`);
process.exit(0); // best-effort — never block the pipeline
})().catch((e) => { console.error('[fetch-stock] non-fatal:', e.message); process.exit(0); });
diff --git a/scripts/short/render-short.js b/scripts/short/render-short.js
index 951a255..5f0a294 100644
--- a/scripts/short/render-short.js
+++ b/scripts/short/render-short.js
@@ -24,6 +24,7 @@ const ROOT = path.resolve(__dirname, '..', '..'); // ~/Projects/allne
const DATA = path.join(ROOT, 'data', 'short');
const TMP = path.join(DATA, '.rtmp');
const IMG_DIR = path.join(DATA, 'img'); // stock backgrounds from fetch-stock.mjs (optional)
+const LOGO_DIR = path.join(DATA, 'logo'); // per-network source logos from fetch-stock.mjs (optional)
const STORIES_PATH = path.join(DATA, 'stories.json');
const SCRIPT_PATH = path.join(DATA, 'script.json');
@@ -149,13 +150,14 @@ if (target > HARD_CAP_SEC) {
function cleanIntro(t) { return String(t).replace(/^\s*All News Daily\.\s*/i, '').trim() || t; }
function imgPath(name) { const p = path.join(IMG_DIR, `${name}.jpg`); return fs.existsSync(p) ? p : null; }
+function logoPath(n) { try { const f = fs.readdirSync(LOGO_DIR).find((x) => x.startsWith(String(n) + '.')); return f ? path.join(LOGO_DIR, f) : null; } catch { return null; } }
const segments = [];
segments.push({ kind: 'intro', main: cleanIntro(script.intro.text), outlet: null,
est: Number(script.intro.estSec) || 3, mainSize: 62, tag: 'BRIEFING', img: imgPath('intro') });
for (const b of script.beats) {
segments.push({ kind: 'beat', main: b.headline || b.text, outlet: b.outlet || null,
- est: Number(b.estSec) || 5, mainSize: 74, n: b.n, img: imgPath(b.n) });
+ est: Number(b.estSec) || 5, mainSize: 74, n: b.n, img: imgPath(b.n), logo: logoPath(b.n) });
}
segments.push({ kind: 'outro', main: script.outro.text, outlet: null,
est: Number(script.outro.estSec) || 5, mainSize: 60, tag: 'ALLNEWSDAILY.COM', img: imgPath('outro') });
@@ -208,13 +210,28 @@ function buildCardPng(seg, idx) {
'-size', '1160x1040', '-gravity', 'center', `caption:@${hlFile}`, ')',
'-gravity', 'center', '-geometry', '+0+0', '-composite', png]);
- // 4) lower-third chip — outlet (beats) or a brand tag (intro/outro)
- const chipText = seg.outlet ? ('VIA ' + String(seg.outlet).toUpperCase()) : (seg.tag || null);
- if (chipText) {
- const chFile = writeRaw(`chip${idx}.txt`, chipText);
- run(MAGICK, [png, '(', '-background', HEX_RED, '-fill', 'white', '-font', FONT,
- '-pointsize', '46', `label:@${chFile}`, '-bordercolor', HEX_RED, '-border', '28x18', ')',
- '-gravity', 'North', '-geometry', '+0+1900', '-composite', png]);
+ // 4) lower-third source chip — the network's real LOGO + name (beats with a logo),
+ // else the red "VIA {OUTLET}" text chip (intro/outro, or if the logo fetch missed).
+ if (seg.logo && seg.outlet) {
+ const logoSq = path.join(TMP, `logosq${idx}.png`);
+ // [0] = first frame (multi-size .ico); flatten transparency onto white; square to 104px
+ run(MAGICK, [`${seg.logo}[0]`, '-background', 'white', '-alpha', 'remove', '-alpha', 'off',
+ '-resize', '104x104', '-gravity', 'center', '-extent', '104x104', logoSq]);
+ const nmFile = writeRaw(`nm${idx}.txt`, ` ${seg.outlet} `);
+ const nameImg = path.join(TMP, `name${idx}.png`);
+ run(MAGICK, ['-background', 'white', '-fill', '#0A0A0A', '-font', FONT, '-pointsize', '54',
+ `label:@${nmFile}`, '-gravity', 'center', '-background', 'white', '-extent', 'x104', nameImg]);
+ const chip = path.join(TMP, `chip${idx}.png`);
+ run(MAGICK, [logoSq, nameImg, '+append', '-bordercolor', 'white', '-border', '26x22', chip]);
+ run(MAGICK, [png, chip, '-gravity', 'North', '-geometry', '+0+1852', '-composite', png]);
+ } else {
+ const chipText = seg.outlet ? ('VIA ' + String(seg.outlet).toUpperCase()) : (seg.tag || null);
+ if (chipText) {
+ const chFile = writeRaw(`chip${idx}.txt`, chipText);
+ run(MAGICK, [png, '(', '-background', HEX_RED, '-fill', 'white', '-font', FONT,
+ '-pointsize', '46', `label:@${chFile}`, '-bordercolor', HEX_RED, '-border', '28x18', ')',
+ '-gravity', 'North', '-geometry', '+0+1900', '-composite', png]);
+ }
}
return png;
}
← c16abe8 allnewsdaily: cut outlet directory to top-20 most-popular gl
·
back to Allnewsdaily
·
allnewsdaily Short: up to 4x/day (6/11/16/21) + guard follow 944b120 →