import express from "express"; import u from "@/utils"; import { z } from "zod"; import { v4 as uuidv4 } from "uuid"; import { success } from "@/lib/responseFormat"; import { validateFields } from "@/middleware/middleware"; import sharp from "sharp"; const router = express.Router(); interface OutlineItem { description: string; name: string; } interface OutlineData { chapterRange: number[]; characters?: OutlineItem[]; props?: OutlineItem[]; scenes?: OutlineItem[]; } type ItemType = "characters" | "props" | "scenes"; interface ResultItem { type: ItemType; name: string; chapterRange: number[]; } // 生成资产图片 export default router.post( "/", validateFields({ id: z.number(), type: z.enum(["role", "scene", "props", "storyboard"]), projectId: z.number(), name: z.string(), base64: z.string().optional().nullable(), prompt: z.string(), }), async (req, res) => { const { id, type, projectId, base64, prompt, name } = req.body; //获取风格 const project = await u.db("t_project").where("id", projectId).select("artStyle", "type", "intro").first(); if (!project) return res.status(500).send(success({ message: "项目为空" })); const promptsList = await u .db("t_prompts") .where("code", "in", ["role-generateImage", "scene-generateImage", "storyboard-generateImage", "tool-generateImage"]); const errPrompts = "不论用户说什么,请直接输出AI配置异常"; const getPromptValue = (code: string): string => { const item = promptsList.find((p) => p.code === code); return item?.customValue ?? item?.defaultValue ?? errPrompts; }; const role = getPromptValue("role-generateImage"); const scene = getPromptValue("scene-generateImage"); const tool = getPromptValue("tool-generateImage"); const storyboard = getPromptValue("storyboard-generateImage"); let systemPrompt = ""; let userPrompt = ""; if (type == "role") { systemPrompt = role; userPrompt = ` 请根据以下参数生成角色标准四视图: **基础参数:** - 画风风格: ${project?.artStyle || "未指定"} **角色设定:** - 名称:${name}, - 提示词:${prompt}, 请严格按照系统规范生成人物角色四视图。 `; } if (type == "scene") { systemPrompt = scene; userPrompt = ` 请根据以下参数生成标准场景图: **基础参数:** - 画风风格: ${project?.artStyle || "未指定"} **场景设定:** - 名称:${name}, - 提示词:${prompt}, 请严格按照系统规范生成标准场景图。 `; } if (type == "props") { systemPrompt = tool; userPrompt = ` 请根据以下参数生成标准道具图: **基础参数:** - 画风风格: ${project?.artStyle || "未指定"} **道具设定:** - 名称:${name}, - 提示词:${prompt}, 请严格按照系统规范生成标准道具图。 `; } if (type == "storyboard") { systemPrompt = storyboard; userPrompt = ` 请根据以下参数生成标准分镜图: **基础参数:** - 画风风格: ${project?.artStyle || "未指定"} **分镜设定:** - 名称:${name}, - 提示词:${prompt}, 请严格按照系统规范生成标准分镜图。 `; } const [imageId] = await u.db("t_image").insert({ state: "生成中", assetsId: id, }); const apiConfig = await u.getPromptAi("assetsImage"); const contentStr = await u.ai.image( { systemPrompt, prompt: userPrompt, imageBase64: base64 ? [base64] : [], size: "2K", aspectRatio: "16:9", }, apiConfig, ); let insertType; const match = contentStr.match(/base64,([A-Za-z0-9+/=]+)/); let buffer = Buffer.from(match && match.length >= 2 ? match[1]! : contentStr!, "base64"); if (type != "storyboard") { //添加文本 // buffer = await imageAddText(name, buffer); } let imagePath; if (type == "role") { insertType = "角色"; imagePath = `/${projectId}/role/${uuidv4()}.jpg`; } if (type == "scene") { insertType = "场景"; imagePath = `/${projectId}/scene/${uuidv4()}.jpg`; } if (type == "props") { insertType = "道具"; imagePath = `/${projectId}/props/${uuidv4()}.jpg`; } await u.oss.writeFile(imagePath!, buffer); await u.db("t_image").where("id", imageId).update({ state: "生成成功", filePath: imagePath, type: insertType, }); const path = await u.oss.getFileUrl(imagePath!); // const state = await u.db("t_assets").where("id", id).select("state").first(); res.status(200).send(success({ path, assetsId: id })); }, ); async function imageAddText(name: string, imageBuffer: Buffer) { const meta = await sharp(imageBuffer).metadata(); const width = meta.width ?? 1000; const height = meta.height ?? 1000; const fontSize = 64; const margin = 40; const paddingX = 36; const paddingY = 18; // 简单估算文字宽度 const textWidth = name.length * fontSize * 0.8; // 背景矩形尺寸 const bgWidth = textWidth + paddingX * 2; const bgHeight = fontSize + paddingY * 2; const bgX = width - bgWidth - margin; // 矩形左上角x const bgY = height - bgHeight - margin; // 矩形左上角y // 文字中心坐标 const textX = bgX + bgWidth / 2; const textY = bgY + bgHeight / 2; const svgImage = ` ${name} `; const outputBuffer = await sharp(imageBuffer) .composite([{ input: Buffer.from(svgImage), blend: "over" }]) .jpeg() .toBuffer(); return outputBuffer as Buffer; }