242 lines
7.5 KiB
JavaScript
242 lines
7.5 KiB
JavaScript
import OpenAI from 'openai';
|
|
import fs from 'fs/promises';
|
|
import path from 'path';
|
|
import dotenv from 'dotenv';
|
|
import crypto from 'crypto';
|
|
|
|
dotenv.config();
|
|
|
|
// Helper til at generere en konsistent hash af en prompt
|
|
function hashPrompt(prompt) {
|
|
return crypto.createHash('md5').update(prompt).digest('hex');
|
|
}
|
|
|
|
// Cache struktur til at gemme prompts og deres resultater
|
|
async function loadCache() {
|
|
try {
|
|
const cache = await fs.readFile('scripts/image-cache.json', 'utf8');
|
|
return JSON.parse(cache);
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
async function saveCache(cache) {
|
|
await fs.writeFile('scripts/image-cache.json', JSON.stringify(cache, null, 2));
|
|
}
|
|
|
|
const openai = new OpenAI({
|
|
apiKey: process.env.OPENAI_KEY
|
|
});
|
|
|
|
const prompts = [
|
|
{
|
|
name: 'klassisk',
|
|
prompt: `High-end food photography of an elegant plated dish:
|
|
- Hot-smoked salmon with cream cheese, fresh dill, and thinly sliced radishes
|
|
- Beautifully arranged on a modern white ceramic plate
|
|
- Natural, directional lighting emphasizing textures
|
|
- Shallow depth of field focusing on the salmon
|
|
- Professional restaurant-quality presentation
|
|
- Garnished with micro herbs and edible flowers
|
|
- 4K quality, shot with a high-end camera`
|
|
},
|
|
{
|
|
name: 'veg',
|
|
prompt: `Professional food photography of a colorful vegetarian main course:
|
|
- Grilled asparagus arranged artfully
|
|
- Creamy mushroom risotto with parmesan
|
|
- Edible flowers and micro greens as garnish
|
|
- Shot from above at a slight angle
|
|
- Natural window lighting
|
|
- White ceramic plate on a light wooden surface
|
|
- Vibrant colors and appealing textures
|
|
- High-resolution, restaurant quality presentation`
|
|
},
|
|
{
|
|
name: 'grill',
|
|
prompt: `Dramatic food photography of grilled dishes:
|
|
- Perfectly grilled medium-rare flank steak with visible grill marks
|
|
- Colorful grilled vegetables alongside
|
|
- Visible steam or smoke
|
|
- Rich, warm lighting emphasizing caramelization
|
|
- Shot at a 45-degree angle
|
|
- Dark moody background
|
|
- Professional food styling with herbs and sea salt
|
|
- High-end restaurant presentation quality`
|
|
},
|
|
{
|
|
name: 'nordisk',
|
|
prompt: `Nordic cuisine presentation:
|
|
- Modern interpretation of traditional Nordic dishes
|
|
- Cold-smoked salmon and pickled herring
|
|
- Fresh rye bread and traditional accompaniments
|
|
- Clean, minimal styling on matte black plates
|
|
- Bright natural lighting
|
|
- Garnished with fresh dill and edible flowers
|
|
- Shot from above with some height
|
|
- Professional restaurant quality presentation`
|
|
},
|
|
{
|
|
name: 'tapas',
|
|
prompt: `Spanish tapas spread photography:
|
|
- Beautifully arranged selection of Spanish tapas
|
|
- Serrano ham, manchego cheese, and olives
|
|
- Patatas bravas with vibrant sauce
|
|
- Traditional ceramic plates and wooden boards
|
|
- Warm, Mediterranean-inspired lighting
|
|
- Garnished with fresh herbs and olive oil
|
|
- Rustic yet elegant presentation
|
|
- Shot from a 45-degree angle`
|
|
},
|
|
{
|
|
name: 'brunch',
|
|
prompt: `Luxury brunch setting photography:
|
|
- Elegant brunch spread with fresh pastries
|
|
- Perfectly cooked scrambled eggs with herbs
|
|
- Fresh fruits and berries
|
|
- Artisanal bread selection
|
|
- Bright, morning lighting
|
|
- White linens and premium tableware
|
|
- Fresh flowers as decoration
|
|
- High-end restaurant quality presentation`
|
|
},
|
|
{
|
|
name: 'italiensk',
|
|
prompt: `Italian cuisine photography:
|
|
- Authentic Italian antipasti spread
|
|
- Fresh bruschetta with vibrant tomatoes
|
|
- Selection of Italian cured meats
|
|
- Artfully arranged on rustic wooden board
|
|
- Warm, directional lighting
|
|
- Fresh basil and olive oil garnish
|
|
- Rustic Italian restaurant atmosphere
|
|
- Professional food styling`
|
|
},
|
|
{
|
|
name: 'gourmet',
|
|
prompt: `High-end gourmet dish photography:
|
|
- Elegant beef tenderloin dish with truffle
|
|
- Modern plating on dark ceramic plate
|
|
- Artistic sauce presentation
|
|
- Micro herbs and edible flowers
|
|
- Dramatic lighting emphasizing textures
|
|
- Shallow depth of field
|
|
- Michelin-star quality presentation
|
|
- Shot with professional camera setup`
|
|
},
|
|
{
|
|
name: 'fransk',
|
|
prompt: `French haute cuisine photography:
|
|
- Elegant plating of foie gras and brioche
|
|
- Duck confit with pommes sarladaises
|
|
- Sophisticated sauce work and garnishes
|
|
- Premium porcelain plate with gold rim
|
|
- Professional studio lighting
|
|
- Shallow depth of field
|
|
- Michelin-star presentation style
|
|
- Garnished with edible flowers and micro herbs`
|
|
},
|
|
{
|
|
name: 'jul',
|
|
prompt: `Nordic Christmas dinner photography:
|
|
- Elegant presentation of traditional Danish Christmas dishes
|
|
- Beautifully arranged confited duck leg
|
|
- Caramelized potatoes and red cabbage
|
|
- Classic white porcelain plate
|
|
- Warm, festive lighting with candle glow
|
|
- Garnished with fresh herbs
|
|
- Holiday atmosphere but modern presentation
|
|
- Professional food photography style`
|
|
},
|
|
{
|
|
name: 'fusion',
|
|
prompt: `Modern Asian fusion food photography:
|
|
- Contemporary Asian fusion dish
|
|
- Korean style short ribs with modern plating
|
|
- Colorful Asian vegetables and garnishes
|
|
- Dramatic black ceramic plate
|
|
- Professional studio lighting
|
|
- Garnished with micro herbs and sesame
|
|
- Artistic sauce presentation
|
|
- High-end restaurant quality styling`
|
|
}
|
|
];async function generateImages() {
|
|
const cache = await loadCache();
|
|
|
|
for (const { name, prompt } of prompts) {
|
|
const promptHash = hashPrompt(prompt);
|
|
const imagePath = path.join(process.cwd(), 'public', 'img', `${name}.jpg`);
|
|
|
|
try {
|
|
// Tjek om billedet allerede eksisterer
|
|
try {
|
|
await fs.access(imagePath);
|
|
// Hvis billedet findes og prompten er cached, spring over
|
|
if (cache[promptHash]) {
|
|
console.log(`✓ Skipping ${name}.jpg (already exists with same prompt)`);
|
|
continue;
|
|
}
|
|
} catch {
|
|
// Billedet findes ikke, vi skal generere det
|
|
}
|
|
|
|
console.log(`Generating image for ${name}...`);
|
|
|
|
const response = await openai.images.generate({
|
|
model: "dall-e-3",
|
|
prompt: prompt,
|
|
n: 1,
|
|
size: "1024x1024",
|
|
quality: "hd",
|
|
style: "natural"
|
|
});
|
|
|
|
const imageUrl = response.data[0].url;
|
|
|
|
// Download billedet
|
|
const imageResponse = await fetch(imageUrl);
|
|
const buffer = await imageResponse.arrayBuffer();
|
|
|
|
// Gem billedet
|
|
await fs.mkdir(path.join(process.cwd(), 'public', 'img'), { recursive: true });
|
|
await fs.writeFile(imagePath, Buffer.from(buffer));
|
|
|
|
// Gem prompt i cache
|
|
cache[promptHash] = {
|
|
name,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
await saveCache(cache);
|
|
|
|
console.log(`✅ Generated and saved ${name}.jpg`);
|
|
} catch (error) {
|
|
console.error(`❌ Error generating ${name}:`, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Håndter afbrydelse af scriptet
|
|
async function cleanup() {
|
|
console.log('\nSaving cache and cleaning up...');
|
|
try {
|
|
await saveCache(await loadCache());
|
|
console.log('Cache saved successfully');
|
|
} catch (error) {
|
|
console.error('Error saving cache:', error);
|
|
}
|
|
process.exit(0);
|
|
}
|
|
|
|
// Lyt efter SIGINT (Ctrl+C)
|
|
process.on('SIGINT', cleanup);
|
|
process.on('SIGTERM', cleanup);
|
|
|
|
// Run the script
|
|
generateImages().then(() => {
|
|
console.log('All done!');
|
|
process.exit(0);
|
|
}).catch(error => {
|
|
console.error('Error:', error);
|
|
process.exit(1);
|
|
}); |