用 AI 把小说变成短视频/漫画------全流程技术复盘
本文记录了一个从零到跑通的 AI 内容生产平台的技术实现过程。核心能力:输入一段小说文本,自动输出一集短视频或一页漫画。
背景与动机
现在 AI 生成视频和图像的能力已经非常成熟,但"把小说改编成可视化内容"这件事仍然高度依赖人工------你需要自己写分镜、描述每一帧画面、选择风格、保持人物一致性......
这个项目想把这个流程完全自动化:
小说文本 → Claude 理解情节 → 生成剧本 → 生成分镜 → AI 逐帧生成图/视频 → 合成输出
支持两种输出模式:短视频 (MP4 合成)和漫画(逐格 PNG)。
技术架构概览
vbnet
Next.js 16 (App Router)
├── 前端:React 19 + TailwindCSS v4
├── 后端:Next.js Route Handlers(全部 SSE 流式)
├── 数据库:PostgreSQL + Prisma ORM
├── AI 分析/编剧/分镜:Anthropic Claude
├── 图像生成:Stable Diffusion(本地)/ 豆包 Seedream(云端)
└── 视频生成:Seedance / Runway / Kling / Flow2API
没有独立的后端服务,全部跑在 Next.js 的 Route Handlers 里,长任务全部用 SSE 流式推送进度。
一、数据库设计:层级化内容模型
整个系统围绕一个五层数据模型运转:
css
Series(系列)
└── Project/Part(子项目,每集对应一个)
├── Analysis(内容分析结果)
├── Episode(集数,含完整剧本 JSON)
│ ├── Shot[](视频分镜)
│ └── MangaPanel[](漫画格)
└── CharacterModel[](角色/场景建模,系列级共享)
几个设计决策值得记录:
scriptContent 用 JSON 字段存全量剧本,而不是拆表。剧本是一次生成、整体消费的数据,不需要单独查询某一行台词,JSON 字段反而更灵活,也避免了过度设计。
CharacterModel 实现版本管理 :每次重新建模会创建新版本,isActive 标记当前激活版本。建模结果系列级共享(seriesId 字段),所有子项目都能用同一套角色参考图,保证跨集视觉一致性。
异步状态追踪 :Shot.videoStatus 和 MangaPanel.imageStatus 都是 pending / processing / completed / failed 四态状态机。前端通过 SSE 事件实时更新,不需要轮询。
prisma
model MangaPanel {
id String @id @default(cuid())
episodeId String
panelNumber Int
description String @db.Text // 中文画面描述
prompt String @db.Text // 英文生图提示词
dialogue String? @db.Text // 对话气泡
innerMonologue String? @db.Text // 心理独白
sfx String? // 音效拟声词
expressionType String? // 表情类型
panelSize String @default("medium") // big/medium/small
imageStatus String @default("pending")
imageUrl String?
}
二、Claude 多阶段调用流水线
整个内容生产流程分四个阶段,每个阶段独立调用 Claude:
阶段 1:内容分析(claude-opus-4-6)
这是整个流程的地基。Prompt 要求 Claude 从小说中提取:
- 角色:必须包含 8 个维度的外貌描述(脸型、眼型、鼻梁、嘴唇、肤色、身材比例、发型发色、标志性特征),这是后续建模的唯一依据
- 场景:标志性元素、氛围色调、叙事功能
- 情节要点:按节拍提取,用于剧本生成
一个关键工程细节:输入文本截断到 60,000 字符,避免超出 context window。输出用 jsonrepair 库自动修复 LLM 可能生成的格式破损 JSON(比如末尾多了逗号、括号不匹配等)。
阶段 2:剧本生成(claude-opus-4-6)
基于分析结果生成短剧结构剧本,要求"钩子→冲突升级→爽点→悬念"的四段式结构。支持多集生成,并对第一集和收尾集有特殊提示词(第一集需要更强的吸引力钩子,收尾集需要留悬念)。
阶段 3:分镜生成
这里视频和漫画有完全不同的 Prompt 策略:
视频分镜 Prompt 要求:
- 每集 12--24 个镜头,每镜 5 秒,总时长 60--120 秒
- 每镜必须包含:景别(远/中/近/特写)、运镜方式(推/拉/摇/跟)、光线描述、情绪标签
漫画分镜 Prompt 要求:
- Claude 被要求理解真正的漫画语言:格子大小权重(大格=情绪高潮,小格=快节奏过渡)
- 区分对话气泡(dialogue)和心理独白云(innerMonologue)
- 每格有表情类型标签和拟声词音效
- Prompt 里提供了 3 个完整示例格,显著提升了输出格式的稳定性
阶段 4:Prompt 翻译(claude-haiku-4-5)
角色/场景描述(中文)→ 图像生成提示词(SD 用英文,Seedream 用中文)。用更快的 Haiku 模型做这个翻译任务,节省成本和时间。
SD 有一个特殊处理:强制把 1girl, 或 1boy, 放在 prompt 最前面。SD 对词序很敏感,性别描述词如果被埋在长 prompt 中间,生成的性别经常是错的。
三、SSE 流式架构
所有耗时任务都用 Server-Sent Events,包括:内容分析、剧本生成、分镜生成、批量生图、批量生视频。
基本模式如下:
typescript
export async function POST(request: NextRequest) {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
const enqueue = (data: Record<string, unknown>) =>
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
// 长任务逻辑
enqueue({ type: "start", total });
for (const item of items) {
await processItem(item);
enqueue({ type: "done", id: item.id, result });
}
enqueue({ type: "complete" });
controller.close();
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
}
每个 SSE 事件都有 type 字段,前端根据 type 更新对应的 UI 状态。比如漫画批量生图会推送 { type: "done", panelId, panelNumber, imageUrl },前端收到后直接把对应格子的图片替换掉。
export const maxDuration = 300 把 Vercel 的超时上限拉到 5 分钟,对于批量生成任务基本够用。
四、图像生成:SD 本地 vs Seedream 云端
两套图像生成后端,通过 imageProvider 参数切换(值为 "sd" 或具体的 Seedream 模型 ID)。
Stable Diffusion(本地)
风格一致性是最大挑战。解决方案:锁死所有生成参数。
typescript
const MANGA_STYLE_PREFIX =
"masterpiece, best quality, anime style, manga illustration, cel shading, clean linework, flat color, 2d illustration,";
const MANGA_STEPS = 30;
const MANGA_CFG = 9.0;
const MANGA_SEED = 42; // 固定 seed 是关键
同时用 stripStylePrefix() 函数把 Claude 生成的 prompt 中的风格词(anime style、masterpiece 等)先剥离,再统一拼接上我们锁定的前缀。否则两套风格词叠加会互相干扰。
Seedream(云端)
豆包的 Seedream API 支持文生图和图生图,接口在 https://ark.cn-beijing.volces.com/api/v3/images/generations。
关键设计:通过是否传入 referenceImageUrl 自动判断用文生图还是图生图,不需要调用方显式指定模式。建模时,如果该角色已有激活版本,就用图生图(denoise=0.55)在原图基础上生成新版本,保持角色一致性。
typescript
export async function seedreamGenerate(
prompt: string,
opts: { referenceImageUrl?: string; size?: string; model?: string } = {}
): Promise<{ url: string }> {
const body: Record<string, unknown> = {
model: opts.model ?? DEFAULT_SEEDREAM_MODEL,
prompt,
size: opts.size ?? "1024x1024",
};
if (opts.referenceImageUrl) {
body.image = opts.referenceImageUrl; // 有参考图 → 图生图
}
// ...
}
Prompt 语言也分开处理:SD 用英文(对英文词更敏感),Seedream 用中文(豆包模型对中文理解更好)。
五、视频生成:多 Provider 适配层
视频生成支持 4 个服务商:Seedance、Runway、Kling、Flow2API。用工厂模式做统一适配:
typescript
function getProvider(name: string): VideoProvider {
switch (name) {
case "runway": return new RunwayProvider();
case "kling": return new KlingProvider();
case "flow2api": return new Flow2APIProvider();
default: return new SeedanceProvider();
}
}
每个 Provider 实现同一套接口:generate(prompt, options) → 返回任务 ID → poll(taskId) → 返回视频 URL。
批量生成时用 Promise.all() 并发推所有镜头,每个镜头完成后立即通过 SSE 推送结果,前端可以看到镜头一个个出现,而不是等全部完成才刷新。
六、前端:四步工作流
整个工作页面是一个四步状态机:
vbnet
Step 1: 上传文件(PDF/EPUB/TXT 解析)
Step 2: 内容分析 + 角色/场景建模
Step 3: 生成剧本
Step 4: 生成分镜 + 生成图/视频(视频/漫画模式分叉)
几个值得记录的细节:
- 乐观更新:SSE 事件到达时,直接更新本地 state,不重新请求 API,避免闪烁
- 客户端 JSON 修复 :流式 JSON 解析时也做了简单的格式修复
replace(/,(\s*[}\]])/g, "$1"),处理 Claude 偶尔生成的末尾多余逗号 - 漫画格布局 :
panelSize === "big"的格子会占两列(col-span-2),还原漫画的版式感 - Prisma + Turbopack 缓存坑 :每次修改
schema.prisma后,必须rm -rf .next再重启,否则 Turbopack 会缓存旧版 Prisma Client 导致运行时报Unknown argument错误
总结
几个核心决策回顾:
| 决策 | 理由 |
|---|---|
| 全 SSE,不用 WebSocket | Route Handler 里更简单,单向推送足够 |
| 视频/漫画共用前三步 | 降低维护成本,差异只在 Step 4 分叉 |
| 用模型 ID 作为 imageProvider 值 | 无需额外映射,新增模型只改一处 |
| 固定 seed/steps/cfg | 风格一致性比随机性更重要 |
| CharacterModel 系列级共享 | 建模是高成本操作,跨集复用是必须的 |
| jsonrepair 修复 LLM 输出 | LLM 输出 JSON 不稳定是常态,与其加重试不如加修复 |
整个项目大约两周从零跑通,主要时间花在 Prompt 调优上------特别是漫画分镜的 Prompt,经过多轮迭代才让 Claude 输出真正符合漫画语言规范的分镜结构。