video-flow-toon/src/routes/storyboard/batchSuperScoreImage.ts
2026-02-06 14:18:30 +08:00

101 lines
3.3 KiB
TypeScript

import express from "express";
import u from "@/utils";
import { error, success } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware";
import { z } from "zod";
import { v4 } from "uuid";
import axios from "axios";
const router = express.Router();
// url转base64
async function urlToBase64(imageUrl: string): Promise<string> {
const response = await axios.get(imageUrl, { responseType: "arraybuffer" });
const contentType = response.headers["content-type"] || "image/png";
const base64 = Buffer.from(response.data, "binary").toString("base64");
return `data:${contentType};base64,${base64}`;
}
// 超分并保存到 oss
async function superResolutionAndSave(
src: string,
projectId: number,
videoRatio: string,
): Promise<{ ossPath: string; base64: string }> {
const contentStr = await u.ai.image({
aspectRatio: videoRatio,
size: "1K",
resType: "b64",
systemPrompt: "你的核心任务是将所给的图片超分到 1K ,不改变图片任何内容,仅改变分辨率",
prompt: "你的核心任务是将所给的图片超分到 1K ,不改变图片任何内容,仅改变分辨率",
imageBase64: [await urlToBase64(src)],
});
const match = contentStr.match(/base64,([A-Za-z0-9+/=]+)/);
const base64Str = match ? match[1] : contentStr;
const buffer = Buffer.from(base64Str, "base64");
const ossPath = `/${projectId}/chat/${v4()}.jpg`;
await u.oss.writeFile(ossPath, buffer);
return { ossPath, base64: `data:image/jpg;base64,${base64Str}` };
}
export default router.post(
"/",
validateFields({
projectId: z.number(),
scriptId: z.number().nullable(),
imageList: z.array(
z.object({
cells: z.array(
z.object({
id: z.string(),
prompt: z.string().optional(),
src: z.string(),
})
),
})
),
}),
async (req, res) => {
const { projectId, scriptId, imageList } = req.body;
const scriptData = await u.db("t_script").where("id", scriptId).select("content").first();
if (!scriptData) return res.status(500).send(error("剧本不存在"));
const projectData = await u.db("t_project").where({ id: +projectId }).select("artStyle", "videoRatio").first();
if (!projectData) return res.status(500).send(error("项目不存在"));
// 遍历处理每个分镜段
const processSegment = async (
segment: { cells: { id: string; src: string }[] }
) => {
// 超分所有 cell
const cellsWithSuperscore = await Promise.all(
segment.cells.map(async (cell) => {
const { ossPath } = await superResolutionAndSave(cell.src, projectId, projectData.videoRatio!);
return {
id: cell.id,
projectId,
scriptId,
filePath: ossPath, // oss 路径(未签名)
src: cell.src,
type: "分镜"
};
})
);
return cellsWithSuperscore;
};
// 处理每个段
const results = await Promise.allSettled(imageList.map(processSegment));
// 展开放回并签名 filePath
const flatList = await Promise.all(
results.flatMap((item: any) =>
(item.value as any[]).map(async (cell) => ({
...cell,
filePath: await u.oss.getFileUrl(cell.filePath ?? ""),
}))
)
);
res.status(200).send(success(flatList));
}
);