生成视频

This commit is contained in:
小帅 2026-03-21 16:24:10 +08:00
parent 5dd533e563
commit 9501db016c
6 changed files with 254 additions and 120 deletions

View File

@ -307,6 +307,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
name: "o_storyboard", name: "o_storyboard",
builder: (table) => { builder: (table) => {
table.integer("id").notNullable(); table.integer("id").notNullable();
table.integer("scriptId");
table.text("name"); table.text("name");
table.text("detail"); table.text("detail");
table.text("prompt"); table.text("prompt");
@ -334,11 +335,7 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
name: "o_video", name: "o_video",
builder: (table) => { builder: (table) => {
table.integer("id").notNullable(); table.integer("id").notNullable();
table.text("resolution");
table.text("prompt");
table.text("filePath"); table.text("filePath");
table.text("model");
table.text("mode");
table.text("errorReason"); table.text("errorReason");
table.integer("time"); table.integer("time");
table.text("state"); table.text("state");
@ -348,15 +345,15 @@ export default async (knex: Knex, forceInit: boolean = false): Promise<void> =>
table.unique(["id"]); table.unique(["id"]);
}, },
}, },
//视频配置
{ {
name: "o_videoConfig", name: "o_videoConfig",
builder: (table) => { builder: (table) => {
table.integer("id").notNullable(); table.integer("id").notNullable();
table.integer("videoId"); //视频Id table.integer("storyboardId");
table.integer("audio"); //声音 table.integer("videoId");
table.text("model"); //模型 table.integer("audio"); // 声音
table.text("mode"); // 模式startEnd/multi/single table.text("model"); // 模型
table.text("mode"); // 模式:
table.text("data"); // 所选数据集图片 JSON table.text("data"); // 所选数据集图片 JSON
table.text("resolution"); // 分辨率 table.text("resolution"); // 分辨率
table.integer("duration"); // 时长 table.integer("duration"); // 时长

View File

@ -1,4 +1,4 @@
// @routes-hash beeb358ee056d0414c7a2ca88bc9b566 // @routes-hash a3ee46a441a84f41e515f5b26b9ccaa9
import { Express } from "express"; import { Express } from "express";
import route1 from "./routes/agents/clearMemory"; import route1 from "./routes/agents/clearMemory";
@ -34,35 +34,39 @@ import route30 from "./routes/novel/getNovel";
import route31 from "./routes/novel/updateNovel"; import route31 from "./routes/novel/updateNovel";
import route32 from "./routes/other/deleteAllData"; import route32 from "./routes/other/deleteAllData";
import route33 from "./routes/other/getCaptcha"; import route33 from "./routes/other/getCaptcha";
import route34 from "./routes/production/getProductionData"; import route34 from "./routes/production/editStoryboard/generateStoryboardImage";
import route35 from "./routes/production/workbench/generateVideo"; import route35 from "./routes/production/editStoryboard/getStoryboardFlow";
import route36 from "./routes/production/workbench/getVideoModelDetail"; import route36 from "./routes/production/editStoryboard/saveStoryboardFlow";
import route37 from "./routes/project/addProject"; import route37 from "./routes/production/editStoryboard/updateStoryboardFlow";
import route38 from "./routes/project/delProject"; import route38 from "./routes/production/getProductionData";
import route39 from "./routes/project/editProject"; import route39 from "./routes/production/workbench/generateVideo";
import route40 from "./routes/project/getProject"; import route40 from "./routes/production/workbench/getVideoModelDetail";
import route41 from "./routes/script/addScript"; import route41 from "./routes/project/addProject";
import route42 from "./routes/script/delScript"; import route42 from "./routes/project/delProject";
import route43 from "./routes/script/getScrptApi"; import route43 from "./routes/project/editProject";
import route44 from "./routes/script/updateScript"; import route44 from "./routes/project/getProject";
import route45 from "./routes/setting/agentDeploy/deployAgentModel"; import route45 from "./routes/script/addScript";
import route46 from "./routes/setting/agentDeploy/getAgentDeploy"; import route46 from "./routes/script/delScript";
import route47 from "./routes/setting/agentDeploy/updateKey"; import route47 from "./routes/script/getScrptApi";
import route48 from "./routes/setting/dbConfig/clearData"; import route48 from "./routes/script/updateScript";
import route49 from "./routes/setting/getTextModel"; import route49 from "./routes/setting/agentDeploy/deployAgentModel";
import route50 from "./routes/setting/loginConfig/getUser"; import route50 from "./routes/setting/agentDeploy/getAgentDeploy";
import route51 from "./routes/setting/loginConfig/updateUserPwd"; import route51 from "./routes/setting/agentDeploy/updateKey";
import route52 from "./routes/setting/memoryConfig/getMemory"; import route52 from "./routes/setting/dbConfig/clearData";
import route53 from "./routes/setting/memoryConfig/sureMemory"; import route53 from "./routes/setting/getTextModel";
import route54 from "./routes/setting/vendorConfig/addVendor"; import route54 from "./routes/setting/loginConfig/getUser";
import route55 from "./routes/setting/vendorConfig/deleteVendor"; import route55 from "./routes/setting/loginConfig/updateUserPwd";
import route56 from "./routes/setting/vendorConfig/getVendorList"; import route56 from "./routes/setting/memoryConfig/getMemory";
import route57 from "./routes/setting/vendorConfig/modelTest"; import route57 from "./routes/setting/memoryConfig/sureMemory";
import route58 from "./routes/setting/vendorConfig/updateVendor"; import route58 from "./routes/setting/vendorConfig/addVendor";
import route59 from "./routes/task/getMyTaskApi"; import route59 from "./routes/setting/vendorConfig/deleteVendor";
import route60 from "./routes/task/getTaskCategories"; import route60 from "./routes/setting/vendorConfig/getVendorList";
import route61 from "./routes/task/taskDetails"; import route61 from "./routes/setting/vendorConfig/modelTest";
import route62 from "./routes/test/test"; import route62 from "./routes/setting/vendorConfig/updateVendor";
import route63 from "./routes/task/getMyTaskApi";
import route64 from "./routes/task/getTaskCategories";
import route65 from "./routes/task/taskDetails";
import route66 from "./routes/test/test";
export default async (app: Express) => { export default async (app: Express) => {
app.use("/api/agents/clearMemory", route1); app.use("/api/agents/clearMemory", route1);
@ -98,33 +102,37 @@ export default async (app: Express) => {
app.use("/api/novel/updateNovel", route31); app.use("/api/novel/updateNovel", route31);
app.use("/api/other/deleteAllData", route32); app.use("/api/other/deleteAllData", route32);
app.use("/api/other/getCaptcha", route33); app.use("/api/other/getCaptcha", route33);
app.use("/api/production/getProductionData", route34); app.use("/api/production/editStoryboard/generateStoryboardImage", route34);
app.use("/api/production/workbench/generateVideo", route35); app.use("/api/production/editStoryboard/getStoryboardFlow", route35);
app.use("/api/production/workbench/getVideoModelDetail", route36); app.use("/api/production/editStoryboard/saveStoryboardFlow", route36);
app.use("/api/project/addProject", route37); app.use("/api/production/editStoryboard/updateStoryboardFlow", route37);
app.use("/api/project/delProject", route38); app.use("/api/production/getProductionData", route38);
app.use("/api/project/editProject", route39); app.use("/api/production/workbench/generateVideo", route39);
app.use("/api/project/getProject", route40); app.use("/api/production/workbench/getVideoModelDetail", route40);
app.use("/api/script/addScript", route41); app.use("/api/project/addProject", route41);
app.use("/api/script/delScript", route42); app.use("/api/project/delProject", route42);
app.use("/api/script/getScrptApi", route43); app.use("/api/project/editProject", route43);
app.use("/api/script/updateScript", route44); app.use("/api/project/getProject", route44);
app.use("/api/setting/agentDeploy/deployAgentModel", route45); app.use("/api/script/addScript", route45);
app.use("/api/setting/agentDeploy/getAgentDeploy", route46); app.use("/api/script/delScript", route46);
app.use("/api/setting/agentDeploy/updateKey", route47); app.use("/api/script/getScrptApi", route47);
app.use("/api/setting/dbConfig/clearData", route48); app.use("/api/script/updateScript", route48);
app.use("/api/setting/getTextModel", route49); app.use("/api/setting/agentDeploy/deployAgentModel", route49);
app.use("/api/setting/loginConfig/getUser", route50); app.use("/api/setting/agentDeploy/getAgentDeploy", route50);
app.use("/api/setting/loginConfig/updateUserPwd", route51); app.use("/api/setting/agentDeploy/updateKey", route51);
app.use("/api/setting/memoryConfig/getMemory", route52); app.use("/api/setting/dbConfig/clearData", route52);
app.use("/api/setting/memoryConfig/sureMemory", route53); app.use("/api/setting/getTextModel", route53);
app.use("/api/setting/vendorConfig/addVendor", route54); app.use("/api/setting/loginConfig/getUser", route54);
app.use("/api/setting/vendorConfig/deleteVendor", route55); app.use("/api/setting/loginConfig/updateUserPwd", route55);
app.use("/api/setting/vendorConfig/getVendorList", route56); app.use("/api/setting/memoryConfig/getMemory", route56);
app.use("/api/setting/vendorConfig/modelTest", route57); app.use("/api/setting/memoryConfig/sureMemory", route57);
app.use("/api/setting/vendorConfig/updateVendor", route58); app.use("/api/setting/vendorConfig/addVendor", route58);
app.use("/api/task/getMyTaskApi", route59); app.use("/api/setting/vendorConfig/deleteVendor", route59);
app.use("/api/task/getTaskCategories", route60); app.use("/api/setting/vendorConfig/getVendorList", route60);
app.use("/api/task/taskDetails", route61); app.use("/api/setting/vendorConfig/modelTest", route61);
app.use("/api/test/test", route62); app.use("/api/setting/vendorConfig/updateVendor", route62);
app.use("/api/task/getMyTaskApi", route63);
app.use("/api/task/getTaskCategories", route64);
app.use("/api/task/taskDetails", route65);
app.use("/api/test/test", route66);
} }

View File

@ -4,15 +4,82 @@ import { z } from "zod";
import { success } from "@/lib/responseFormat"; import { success } from "@/lib/responseFormat";
import { validateFields } from "@/middleware/middleware"; import { validateFields } from "@/middleware/middleware";
const router = express.Router(); const router = express.Router();
// 获取生产数据
export default router.post( export default router.post(
"/", "/",
validateFields({ validateFields({
projectId: z.number(), scriptId: z.number(),
}), }),
async (req, res) => { async (req, res) => {
const { projectId } = req.body; const { scriptId } = req.body;
res.status(200).send(success("123"));
// 1. 查出该剧本下所有分镜
const storyboards = await u
.db("o_storyboard")
.where("o_storyboard.scriptId", scriptId)
.select(
"o_storyboard.id",
"o_storyboard.name",
"o_storyboard.detail",
"o_storyboard.prompt",
"o_storyboard.seconds",
"o_storyboard.filePath",
"o_storyboard.frameType",
"o_storyboard.scriptId",
)
.orderBy("o_storyboard.createTime", "asc");
if (storyboards.length === 0) {
return res.status(200).send(success([]));
} }
const storyboardIds = storyboards.map((s) => s.id as number);
// 2. 批量查出所有相关视频
const videos = await u
.db("o_video")
.whereIn("o_video.storyboardId", storyboardIds)
.select("o_video.id", "o_video.storyboardId", "o_video.filePath", "o_video.state", "o_video.errorReason")
.orderBy("o_video.time", "desc");
// 3. 批量查出所有相关配置
const configs = await u
.db("o_videoConfig")
.whereIn("o_videoConfig.storyboardId", storyboardIds)
.select(
"o_videoConfig.id",
"o_videoConfig.storyboardId",
"o_videoConfig.videoId",
"o_videoConfig.prompt",
"o_videoConfig.model",
"o_videoConfig.mode",
"o_videoConfig.resolution",
"o_videoConfig.duration",
"o_videoConfig.audio",
"o_videoConfig.data",
);
// 4. 按 storyboardId 建立 Map 方便聚合
const videoMap = new Map<number, typeof videos>();
for (const video of videos) {
const sid = video.storyboardId as number;
if (!videoMap.has(sid)) videoMap.set(sid, []);
videoMap.get(sid)!.push(video);
}
const configMap = new Map(configs.map((c) => [c.storyboardId as number, c]));
// 5. 组装结果:分镜平铺 + config 对象 + videos 数组
const data = await Promise.all(
storyboards.map(async (storyboard) => {
const sid = storyboard.id as number;
return {
...storyboard,
filePath: storyboard.filePath && (await u.oss.getFileUrl(storyboard.filePath!)),
config: configMap.get(sid) ?? null,
videos: videoMap.get(sid) ?? [],
};
}),
);
return res.status(200).send(success(data));
},
); );

View File

@ -13,15 +13,79 @@ export default router.post(
projectId: z.number(), projectId: z.number(),
storyboardId: z.number(), storyboardId: z.number(),
prompt: z.string(), prompt: z.string(),
data: z.array(z.string()).optional(), data: z
.array(
z.object({
id: z.number(),
type: z.string(),
}),
)
.optional(),
model: z.string(), model: z.string(),
duration: z.number(), duration: z.string(),
resolution: z.string(), resolution: z.string(),
audio: z.boolean().optional(), audio: z.boolean().optional(),
modeData: z.string(), mode: z.string(),
}), }),
async (req, res) => { async (req, res) => {
const { scriptId, projectId, storyboardId, prompt, data, model, duration, resolution, audio, modeData } = req.body; const { scriptId, projectId, storyboardId, prompt, data, model, duration, resolution, audio, mode } = req.body;
const videoPath = `/${projectId}/video/${uuidv4()}.mp4`; //视频保存路径
//新增
const [videoId] = await u.db("o_video").insert({
filePath: videoPath,
time: Date.now(),
state: "生成中",
scriptId,
storyboardId,
});
//查询分镜是否已有配置
const config = await u.db("o_videoConfig").where({ storyboardId }).first();
//保存配置
if (config) {
await u
.db("o_videoConfig")
.update({ audio, model, mode, data: JSON.stringify(data), resolution, duration, prompt, updateTime: Date.now() })
.where({ id: config.id });
} else {
await u.db("o_videoConfig").insert({
storyboardId,
audio,
model,
mode,
data: JSON.stringify(data),
resolution,
duration,
prompt,
createTime: Date.now(),
updateTime: Date.now(),
});
}
//查询出图片数据
const images = await Promise.all(
data.map(async (item: { id: number; type: string }) => {
if (item.type === "storyboard") {
const filePath = await u.db("o_storyboard").where("id", item.id).select("filePath").first();
return filePath?.filePath;
}
if (item.type === "assets") {
const filePath = await u
.db("o_assets")
.where("o_assets.id", item.id)
.leftJoin("o_image", "o_assets.imageId", "o_image.id")
.select("o_image.filePath")
.first();
return filePath?.filePath;
}
}),
);
//把images里面的图片转成base64格式
const base64 = await Promise.all(
images.map(async (item) => {
if (!item) return null;
return await u.oss.getImageBase64(item);
}),
);
//开始生成
try { try {
const relatedObjects = { const relatedObjects = {
id: storyboardId, id: storyboardId,
@ -34,44 +98,33 @@ export default router.post(
3. 3.
4. 4.
5. `; 5. `;
const videoPath = `/${projectId}/video/${uuidv4()}.mp4`;
const aiVideo = u.Ai.Video(model); const aiVideo = u.Ai.Video(model);
await aiVideo.run({ await aiVideo.run({
systemPrompt, // 系统提示词 systemPrompt,
projectId: projectId, projectId,
storyboardId: storyboardId, storyboardId,
prompt: prompt, prompt,
data: data, imageBase64: base64.filter((item) => item !== null) as string[],
modeData: modeData, mode,
duration: duration, duration,
resolution: resolution, resolution,
audio: audio, audio,
taskClass: "视频生成", taskClass: "视频生成",
describe: "根据提示词生成视频", describe: "根据提示词生成视频",
relatedObjects: JSON.stringify(relatedObjects), relatedObjects: JSON.stringify(relatedObjects),
}); });
await aiVideo.save(videoPath); // 保存视频 await aiVideo.save(videoPath);
//保存视频信息到数据库 await u.db("o_video").where("id", videoId).update({ state: "生成成功" });
// await u.db("o_video").insert({ res.status(200).send(success({ videoId, message: "视频生成成功" }));
// resolution, } catch (error: any) {
// prompt, await u
// filePath: videoPath, .db("o_video")
// model, .where("id", videoId)
// time: Date.now(), .update({
// state: "生成成功", state: "生成失败",
// scriptId: scriptId, errorReason: error instanceof Error ? error.message : "未知错误",
// }); });
res.status(200).send(success("视频生成成功"));
} catch (error) {
// await u.db("o_video").insert({
// resolution,
// prompt,
// model,
// time: Date.now(),
// state: "生成失败",
// scriptId: scriptId,
// errorReason: error instanceof Error ? error.message : "未知错误",
// });
res.status(500).send({ error: "视频生成失败" }); res.status(500).send({ error: "视频生成失败" });
} }
}, },

View File

@ -1,4 +1,4 @@
// @db-hash f67609654b5467393c4809a3921d8fa4 // @db-hash f8ab3a7aee659c729c770f3555728f1b
//该文件由脚本自动生成,请勿手动修改 //该文件由脚本自动生成,请勿手动修改
export interface memories { export interface memories {
@ -110,8 +110,19 @@ export interface o_setting {
} }
export interface o_storyboard { export interface o_storyboard {
'createTime'?: number | null; 'createTime'?: number | null;
'detail'?: string | null;
'filePath'?: string | null;
'frameType'?: string | null;
'id'?: number; 'id'?: number;
'name'?: string | null; 'name'?: string | null;
'prompt'?: string | null;
'scriptId'?: number | null;
'seconds'?: string | null;
}
export interface o_storyboardFlow {
'flowData': string;
'id'?: number;
'stroryboardId': number;
} }
export interface o_tasks { export interface o_tasks {
'describe'?: string | null; 'describe'?: string | null;
@ -144,10 +155,6 @@ export interface o_video {
'errorReason'?: string | null; 'errorReason'?: string | null;
'filePath'?: string | null; 'filePath'?: string | null;
'id'?: number; 'id'?: number;
'mode'?: string | null;
'model'?: string | null;
'prompt'?: string | null;
'resolution'?: string | null;
'scriptId'?: number | null; 'scriptId'?: number | null;
'state'?: string | null; 'state'?: string | null;
'storyboardId'?: number | null; 'storyboardId'?: number | null;
@ -163,6 +170,7 @@ export interface o_videoConfig {
'model'?: string | null; 'model'?: string | null;
'prompt'?: string | null; 'prompt'?: string | null;
'resolution'?: string | null; 'resolution'?: string | null;
'storyboardId'?: number | null;
'updateTime'?: number | null; 'updateTime'?: number | null;
'videoId'?: number | null; 'videoId'?: number | null;
} }
@ -183,6 +191,7 @@ export interface DB {
"o_script": o_script; "o_script": o_script;
"o_setting": o_setting; "o_setting": o_setting;
"o_storyboard": o_storyboard; "o_storyboard": o_storyboard;
"o_storyboardFlow": o_storyboardFlow;
"o_tasks": o_tasks; "o_tasks": o_tasks;
"o_user": o_user; "o_user": o_user;
"o_vendorConfig": o_vendorConfig; "o_vendorConfig": o_vendorConfig;

View File

@ -117,8 +117,8 @@ interface VideoConfig {
storyboardId: number; // 关联的分镜ID storyboardId: number; // 关联的分镜ID
systemPrompt?: string; // 系统提示词 systemPrompt?: string; // 系统提示词
prompt: string; //视频提示词 prompt: string; //视频提示词
data: string[]; //输入的图片提示词 imageBase64: string[]; //输入的图片提示词
modeData: string; //模式 mode: string; //模式
duration: number; // 视频时长,单位秒 duration: number; // 视频时长,单位秒
resolution: string; // 视频分辨率 resolution: string; // 视频分辨率
audio: boolean; // 是否需要配音 audio: boolean; // 是否需要配音