Files
project-noctivus/lib/pipeline/images.ts
2025-08-08 19:26:21 +02:00

71 lines
2.0 KiB
TypeScript

import OpenAI from "openai";
import * as fs from "fs";
import * as path from "path";
import { StoryConfig } from "./config";
let openaiClient: OpenAI | null = null;
function getOpenAI(): OpenAI {
if (!openaiClient) {
openaiClient = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
}
return openaiClient;
}
const allowedSizesValues = [
"256x256",
"512x512",
"1024x1024",
"1536x1024",
"1024x1536",
"1792x1024",
"1024x1792",
] as const;
type AllowedSize = (typeof allowedSizesValues)[number];
function pickImageSize(resolution?: string): AllowedSize {
if (!resolution) return "1024x1024";
const match = resolution.match(/^(\d+)x(\d+)$/);
if (!match) return "1024x1024";
const width = parseInt(match[1], 10);
const height = parseInt(match[2], 10);
if (!Number.isFinite(width) || !Number.isFinite(height)) return "1024x1024";
if (width === height) return "1024x1024";
const landscapeCandidates: AllowedSize[] = ["1536x1024", "1792x1024"];
const portraitCandidates: AllowedSize[] = ["1024x1536", "1024x1792"];
return width > height ? landscapeCandidates[0] : portraitCandidates[0];
}
export async function generateImage(
storyName: string,
storyConfig: StoryConfig,
chunk: string,
chunkIndex: number,
imageIndex: number
): Promise<string> {
const imagePath = path.join("stories", storyName, "images", `chunk_${chunkIndex}_img${imageIndex}.png`);
const prompt = `${(storyConfig.config.image_style_prompts || "").trim()}
Illustration for the following passage:
"${chunk.slice(0, 500)}"`;
const size = pickImageSize(storyConfig.config.export_settings?.resolution);
const response = await getOpenAI().images.generate({
model: "dall-e-3",
prompt,
n: 1,
size,
response_format: "b64_json",
});
if (!response.data?.[0].b64_json) {
throw new Error("Image data not found in response");
}
const imageBase64 = response.data[0].b64_json;
const imageBuffer = Buffer.from(imageBase64, "base64");
fs.writeFileSync(imagePath, imageBuffer);
return imagePath;
}