- Shorten quote 03 from 'Can I split it across a few months?' to 'Can I pay monthly?' for column symmetry - Add nbsp between 'money' and 'arriving' to prevent orphan line break
110 lines
13 KiB
TypeScript
110 lines
13 KiB
TypeScript
/**
|
|
* Retry remaining 19 product photos — sequential with 8s delays
|
|
* Run: npx tsx scripts/generate-brand-photos-4-retry.ts
|
|
*/
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import dotenv from "dotenv";
|
|
|
|
dotenv.config();
|
|
|
|
const API_KEY = process.env.GEMINI_API_KEY;
|
|
if (!API_KEY) { console.error("Missing GEMINI_API_KEY"); process.exit(1); }
|
|
|
|
const MODEL = "gemini-3-pro-image-preview";
|
|
const ENDPOINT = `https://generativelanguage.googleapis.com/v1beta/models/${MODEL}:generateContent?key=${API_KEY}`;
|
|
const OUTPUT_DIR = path.join(process.cwd(), "public/images/brand");
|
|
|
|
const STYLE = `Photorealistic documentary photography. Sony A7III, shallow depth of field, available light. Candid fly-on-the-wall. Nobody looks at camera. No stock aesthetic. No staged poses. No alcohol or wine ever. No visible watermarks. Young modern British-Muslim community. South Asian and Arab features.`;
|
|
|
|
interface Photo { filename: string; prompt: string; }
|
|
|
|
const ALL_PHOTOS: Photo[] = [
|
|
{ filename: "product-pledge-05-confirmation-smile.jpg", prompt: `3:4 portrait. A young British-Muslim man at a charity dinner, looking at his phone with a small satisfied smile. He's just completed something on his phone — putting it down on the table. His expression is quiet contentment, not performance. A QR tent card visible on the table near his plate. Other guests are chatting around him, unaware. The private satisfaction of having made a pledge. Warm ambient light. 85mm f/1.4. ${STYLE}` },
|
|
{ filename: "product-pledge-06-multiple-phones.jpg", prompt: `16:9 landscape. Wide shot of a charity dinner table where multiple guests are simultaneously looking at their phones. Five or six people at a round table, at least three of them have phones out, scanning or tapping. A QR tent card sits in the centre of the table. The speaker at the far end of the hall is mid-appeal (blurred). The viral moment when one person scans and everyone follows. Energy and momentum. 35mm f/2.0. ${STYLE}` },
|
|
{ filename: "product-pledge-07-outdoor-event.jpg", prompt: `16:9 landscape. An outdoor charity fundraising event — a marquee or gazebo in a park. A young British-Muslim woman is holding her phone up to a QR code on a printed poster attached to a display board. She's in casual clothes — denim jacket, colourful hijab. Behind her: families on the grass, a bouncy castle, charity stalls. Daytime, overcast. Pledging doesn't just happen at dinners — it happens anywhere. 50mm f/2.0. ${STYLE}` },
|
|
{ filename: "product-pledge-08-mosque-friday.jpg", prompt: `16:9 landscape. Just outside a mosque after Friday prayer. A printed A3 poster on a noticeboard near the exit shows a QR code with a headline (text blurred). A young British-Muslim man in a thobe has stopped to scan it with his phone on his way out. Other congregants are filing past him. Afternoon daylight through the entrance. The mosque foyer has a shoe rack, coats on hooks, and community notices. Pledging in the flow of daily religious life. 35mm f/2.0. ${STYLE}` },
|
|
{ filename: "product-dashboard-02-team-review.jpg", prompt: `16:9 landscape. A small charity office. Three young British-Muslim team members — two men and a woman in hijab — gathered around a single laptop on a desk. One is pointing at the screen, another is writing numbers on a notepad, the third has her hand on her chin looking pleased. The laptop screen (NOT visible to camera, shot from behind) shows data. Tea mugs and biscuits on the desk. The team debrief — reviewing pledge numbers after an event. 35mm f/2.0. ${STYLE}` },
|
|
{ filename: "product-dashboard-03-phone-notification.jpg", prompt: `3:4 portrait. A young British-Muslim woman's hand holding her phone, which shows a notification banner on the lock screen (notification content blurred but the banner shape is clear — a white rounded rectangle at the top of the screen with an app icon). She's on a London bus, the window shows a rainy street. Her other hand holds the pole. A pledge payment just came through — the system works even when you're not thinking about it. 85mm f/1.4. ${STYLE}` },
|
|
{ filename: "product-dashboard-04-whatsapp-reminder.jpg", prompt: `16:9 landscape. Split focus shot. In the foreground (sharp): a phone lying on a desk showing a WhatsApp message thread with a green chat bubble (content blurred). In the background (slightly soft): a young British-Muslim charity worker at the desk, reaching for the phone with a curious expression. A gentle follow-up reminder has arrived for a donor — the automated system nudging at the right time. Natural desk lamp light. 50mm f/2.8. ${STYLE}` },
|
|
{ filename: "product-dashboard-05-spreadsheet-vs-app.jpg", prompt: `16:9 landscape. A charity office desk showing the "before and after" of pledge tracking. On the left side of the desk: a messy stack of paper pledge forms, a calculator, scribbled Post-it notes, and a stressed-looking pen. On the right side: a clean laptop with a calm screen glow. A young British-Muslim woman's hands are pushing the paper pile to the side, turning toward the laptop. The transition from chaos to system. Overhead fluorescent. 35mm f/2.8. ${STYLE}` },
|
|
{ filename: "product-dashboard-06-treasurer-reconciling.jpg", prompt: `16:9 landscape. An older British-Muslim man (50s, glasses, trimmed beard) sitting at a home office desk with a laptop, a bank statement printout, and a cup of chai. He's running his finger down the printed statement, cross-referencing with the laptop screen (not visible). His expression is focused but not stressed — things are matching up. A desk lamp, family photos on the shelf behind. The charity treasurer's monthly reconciliation made simple. 50mm f/2.0. ${STYLE}` },
|
|
{ filename: "product-donor-01-reminder-commute.jpg", prompt: `16:9 landscape. A young British-Muslim man sitting on a London Underground tube train, looking at his phone with recognition — "oh right, I pledged at that dinner." He's in work clothes — shirt, no tie, messenger bag on lap. The tube carriage has other commuters reading, on phones. Through the dark window: tunnel reflections. A reminder notification has just arrived. The gentle nudge that converts a pledge to a payment. 50mm f/2.0. ${STYLE}` },
|
|
{ filename: "product-donor-02-paying-sofa.jpg", prompt: `16:9 landscape. A young British-Muslim woman in hijab sitting on a sofa at home, casually completing a payment on her phone. She's relaxed — legs tucked under her, a blanket, the TV on in the background (blurred). Her phone is in one hand, her other hand holds a cup of tea. She's tapping the screen with her thumb. Warm living room lamp light. Paying a pledge in her own time, on her own sofa, no pressure. The ease of delayed giving. 50mm f/2.0. ${STYLE}` },
|
|
{ filename: "product-donor-03-payday-payment.jpg", prompt: `3:4 portrait. Close-up of a young British-Muslim man's hands holding his phone over a kitchen counter. Next to the phone on the counter: his car keys, a wallet, and a pay slip (partially visible, blurred). He's completing a transaction on his phone (screen blurred, but a confirmation-style layout with a button is visible). Payday — the day he can finally honour his pledge from last month. Morning light. The integrity of following through. 85mm f/1.8. ${STYLE}` },
|
|
{ filename: "product-donor-04-installment-calendar.jpg", prompt: `16:9 landscape. A young British-Muslim woman at a desk with a physical wall calendar visible behind her. Some dates on the calendar have small coloured stickers on them — marking her pledge payment installments. She's on her phone (screen not visible), relaxed, chin in hand. The calendar shows this is a planned, manageable commitment spread over months, not a single painful payment. Soft afternoon light. Financial flexibility for generous hearts. 50mm f/2.0. ${STYLE}` },
|
|
{ filename: "product-donor-05-confirmation-peace.jpg", prompt: `3:4 portrait. A young British-Muslim man sitting alone on a park bench, phone in his lap, looking up at the sky with a peaceful half-smile. He's just completed his final pledge payment. The phone screen is dark/off. Trees in the background, late afternoon golden light. A private moment of completion — promise kept, obligation fulfilled. No fanfare, just quiet peace. 85mm f/1.4. ${STYLE}` },
|
|
{ filename: "product-donor-06-gift-aid-tick.jpg", prompt: `3:4 portrait. Extreme close-up of a phone screen showing a simple checkbox or toggle being tapped by a thumb. The checkbox area is in focus with a clean white interface and blue accent colour (specific text NOT readable). The background behind the phone is blurred — looks like a dinner table setting with warm light. The small action of ticking Gift Aid that adds 25% to a donation at no cost. The detail that charities need. 85mm macro f/1.4. ${STYLE}` },
|
|
{ filename: "product-celebrate-01-target-hit.jpg", prompt: `16:9 landscape. A small charity office. A young British-Muslim man jumps up from his desk chair, arms raised, fists clenched in celebration. His laptop is open in front of him. A young woman in hijab at the next desk is turning to look at him with a surprised laugh, mid-sip of tea. Papers on desks, charity posters on walls. Fluorescent light. The moment the pledge collection target is hit — the dashboard showed 100%. Genuine, unguarded joy. 35mm f/2.0. ${STYLE}` },
|
|
{ filename: "product-celebrate-02-showing-phone.jpg", prompt: `16:9 landscape. Two young British-Muslim charity volunteers at a post-event cleanup. One is holding his phone out to show the other something on the screen (phone screen facing away from camera, NOT visible). The one looking has wide eyes and is covering her mouth with her hand in disbelief. They're standing in a half-cleared community hall — stacked chairs, folded tablecloths. The total pledged amount exceeding expectations. 50mm f/1.8. ${STYLE}` },
|
|
{ filename: "product-celebrate-03-announcing-total.jpg", prompt: `16:9 landscape. A charity fundraising dinner. The MC/speaker at the front podium holds up a piece of paper and announces the total to the room. The audience is mid-applause — hands up, some standing, faces lit with pride. Round tables with dinner remnants. A projection screen behind the speaker shows a large number (blurred). Fairy lights. The climactic announcement powered by real-time pledge data. The room erupts. 24mm f/2.8. ${STYLE}` },
|
|
{ filename: "product-celebrate-04-thank-you-text.jpg", prompt: `3:4 portrait. A young British-Muslim woman in hijab sitting at a desk, typing on her phone with both thumbs, a warm smile on her face. She's sending thank-you messages to donors. On the desk beside her: a laptop with the lid half-closed, a notepad with a list of names (some ticked off), and an empty mug. Warm desk lamp light, evening. The personal follow-up that turns a one-time donor into a lifelong supporter. 85mm f/1.4. ${STYLE}` },
|
|
];
|
|
|
|
async function generateOne(spec: Photo): Promise<boolean> {
|
|
const outPath = path.join(OUTPUT_DIR, spec.filename);
|
|
if (fs.existsSync(outPath)) {
|
|
console.log(` ⏭️ ${spec.filename} — already exists, skipping`);
|
|
return true;
|
|
}
|
|
const t0 = Date.now();
|
|
try {
|
|
const res = await fetch(ENDPOINT, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
contents: [{ parts: [{ text: `Generate a photorealistic photograph. ${spec.prompt}` }] }],
|
|
generationConfig: { responseModalities: ["IMAGE", "TEXT"] },
|
|
}),
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.text();
|
|
console.error(` ❌ ${spec.filename} — API ${res.status}: ${err.slice(0, 120)}`);
|
|
return false;
|
|
}
|
|
const data: any = await res.json();
|
|
const parts = data.candidates?.[0]?.content?.parts;
|
|
const imgPart = parts?.find((p: any) => p.inlineData?.mimeType?.startsWith("image/"));
|
|
if (!imgPart) {
|
|
console.error(` ❌ ${spec.filename} — No image in response`);
|
|
return false;
|
|
}
|
|
const buf = Buffer.from(imgPart.inlineData.data, "base64");
|
|
fs.writeFileSync(outPath, buf);
|
|
console.log(` ✅ ${spec.filename} — ${(buf.length / 1024).toFixed(0)}KB (${((Date.now() - t0) / 1000).toFixed(1)}s)`);
|
|
return true;
|
|
} catch (e: any) {
|
|
console.error(` ❌ ${spec.filename} — ${e.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
// Filter to only missing files
|
|
const missing = ALL_PHOTOS.filter(p => !fs.existsSync(path.join(OUTPUT_DIR, p.filename)));
|
|
console.log(`\n🔧 ${missing.length} photos still needed (${ALL_PHOTOS.length - missing.length} already exist)\n`);
|
|
|
|
if (missing.length === 0) {
|
|
console.log("All done!");
|
|
return;
|
|
}
|
|
|
|
// Sequential with 8s delays to respect rate limits
|
|
let success = 0;
|
|
const t0 = Date.now();
|
|
|
|
for (let i = 0; i < missing.length; i++) {
|
|
console.log(`[${i + 1}/${missing.length}]`);
|
|
const ok = await generateOne(missing[i]);
|
|
if (ok) success++;
|
|
if (i < missing.length - 1) {
|
|
await new Promise(r => setTimeout(r, 8000));
|
|
}
|
|
}
|
|
|
|
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
|
|
console.log(`\n═══ Done: ${success}/${missing.length} in ${elapsed}s ═══`);
|
|
}
|
|
|
|
main();
|