多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

AI短剧生成流水线拆解:从结构化剧本到成片的工程实践

AI短剧生成流水线拆解:从结构化剧本到成片的工程实践 简介一句话生成完整短剧/漫剧的AI自动化生成平台面向短剧创作者、内容团队与AI应用开发者解决从剧本、分镜到成片各环节手动制作效率低的问题。压缩包为zip格式共365个文件、约2.48MB核心以Go、TypeScript、Vue为主分别承担后端服务、业务逻辑与界面展示Less/SVG/PNG等文件覆盖样式与视觉资源json、sql、env等提供配置及部署支撑。平台源码包含AI提示词管理、剧情控制、分镜任务调度等模块能够展现从一句话创意到短剧/漫剧成片的完整自动化链路目录按功能分层便于对照源码理解AI短剧流水线的设计思路也适合作为二次开发的起点。已有805人学习下载推荐给希望借助AI工具批量产出短剧内容的研究者与开发者可用于快速部署体验、二次开发或作为AI内容生成项目的参考资料。1. 别被“一句话”骗了灵果短剧AI这类平台到底把短剧制作压缩成了哪几步最近在看“灵果短剧AI”这类基于 AI 的一站式短剧/漫剧生成平台卖点是一句话生成完整短剧/漫剧从剧本到成片全自动化。拿到 Lingg.zip我不会先双击运行而是先把它当一个黑匣子拆开看。拆开之后会发现所谓全自动化是五段工序串成一条流水线剧本结构、分镜画面、配音、时间轴拼接、版本校验。短剧和漫剧的差别只落在画面段——短剧走视频生成漫剧走漫画分镜加运镜。真正决定成败的不是某一环有多聪明而是环节之间的输入输出能不能对齐。好多项目就是没对齐最后成品只能靠人工修。适合两类人批量做短剧/漫剧的内容团队想把制作人效提上去做过模型部署的工程师想把同一套技术栈套进自己的业务。它值得投入前提是愿意管好中间产物。这是一次 AI 工程实践不是玄学。2. 剧本段让 AI 大模型把一句话扩写成带分镜的对白脚本含可跑通的最小脚本2.1 为什么要逼模型输出 JSON结构化剧本是整条流水线的地基很多人第一次用这类平台上来就让 AI“写个剧本”得到一篇散文。散文给读者看没问题给流水线看是灾难。下一环节要按镜头生成画面按对白合成语音按时长拼接成片没有字段就等于没有坐标。所以第一步要约定 schematitle、logline、acts、shots、subject、action、dialog、duration、sfx。这个 JSON 就是整条流水线的中枢数据结构后面每一段的输入都从它身上取。先给一个能跑通的最小脚本。它假设你已经有一个 OpenAI 兼容的接口地址和密钥不管模型跑在云端还是本地推理服务只要支持/v1/chat/completions就能用# stage1_script.py # 用通用 OpenAI 兼容接口把一句话需求扩写成短剧剧本 JSON import json import os from openai import OpenAI client OpenAI( api_keyos.getenv(LLM_API_KEY), base_urlos.getenv(LLM_BASE_URL), ) def build_prompt(one_line: str) - str: return f你是一个短剧编剧请把下面的故事梗概扩写成 3 幕短剧脚本输出 JSON不要输出其他内容。 故事梗概{one_line} JSON 结构 {{ title: 剧名, logline: 一句话梗概, acts: [ {{ scene: 1, location: 场景, summary: 本幕剧情, shots: [ {{no: 1, subject: 画面主体, action: 动作, dialog: 对白, duration: 3}} ], sfx: 音效提示 }} ] }} 要求每幕不少于 3 个镜头duration 只填 2-6 之间的整数对白口语化每句不超过 20 字。 def generate_script(one_line: str) - dict: resp client.chat.completions.create( modelos.getenv(LLM_MODEL, qwen-plus), messages[ {role: system, content: 你只输出 JSON不要输出 Markdown 和解释。}, {role: user, content: build_prompt(one_line)}, ], temperature0.7, top_p0.9, max_tokens2000, response_format{type: json_object}, ) return json.loads(resp.choices[0].message.content) if __name__ __main__: data generate_script(一个外卖员在雨夜接到一通匿名订单送往地址是十年前已拆迁的老宅。) with open(script.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) total_shots sum(len(act[shots]) for act in data[acts]) print(data[title], 镜头数:, total_shots)这段代码的核心不是调用模型而是把“自由发挥”压缩成一个固定 schema。response_format{type: json_object}是让模型直接返回 JSON 的关键参数如果服务端不支持这个字段就要在build_prompt里反复强调“只输出 JSON”并在解析时做一层兜底把返回内容里第一对花括号之间的部分截出来再json.loads。参数怎么调直接记这张表参数推荐范围作用与调整方向temperature0.6-0.8低于 0.5 剧情容易模式化高于 0.9 结构容易跑飞top_p0.8-0.9控制候选词范围和 temperature 二选一微调即可max_tokens1500-2500三幕剧本加对白2000 起步比较稳妥response_formatjson_object能开就开不能开就用提示词强制约束脚本跑完script.json就是后续所有环节的唯一输入。这里有一个容易忽略的点duration是编剧预估的镜头秒数它不代表真实成片时长。后面第 4 章会用真实音频时长把它覆盖掉这个预设值只用来做初步节奏估算。2.2 提示词里的隐性约定时长、对白长度和三幕边界提示词里那三条硬约束不是随便写的。短剧的观看场景大多是手机竖屏单镜头超过 6 秒观众就开始划走。duration限制在 2-6 秒是在逼模型按短视频节奏切分动作对白限 20 字以内是因为 TTS 合成一句超过 20 字的对白语速稍微一慢整条时间轴就会失控三幕结构则是为了确保短剧有“起、冲突、反转”的基本钩子而不是流水账。如果你做的是漫剧可以把duration上限放宽到 6-8 秒。漫剧本质是静态分镜加运镜镜头停留时间本来就比短剧长。对白 20 字的限制反而更要守住因为漫剧画面没有口型观众全靠字幕和配音理解节奏长句特别容易让人觉得拖。这一阶段有些团队会直接上“多AI协作”一个模型做剧情结构一个模型做对白润色一个模型抽关键词给画面段。我的建议是不要一开始就把编排做复杂。先把单一模型在固定 schema 下的输出跑到九成可靠再考虑拆模型。原因很简单一旦引入第二个模型中间又多一层解析和容错排查问题的成本会指数上升。2.3 输入侧也要给锚点一句话不是真的只有一句话平台宣传“一句话生成”但我的经验是这句话至少要包含“主角、目标、冲突、时空背景”。如果你只给“一个外卖员的奇幻经历”模型大概率会给你写出一部四不像。跑一遍上面脚本你就会发现输入越具体剧本的结构越紧。真正可用的输入长这样一个外卖员在雨夜接到匿名订单送往十年已拆迁的老宅他坚持送达发现收件人竟是十年前失踪的自己。悬疑风格三幕短剧。这里还有个更好的习惯把风格词也放进输入。比如“悬疑风格”“都市轻喜剧”“古风虐恋”它会直接影响后面画面段的 prompt 风格。script.json里最好把style字段一并存下第 3 章生成画面时直接引用避免两个环节各写各的。3. 画面段从分镜到短剧/漫剧画面文生图与视频生成的选型与参数3.1 短剧选图生视频、漫剧选文生图加运镜先定模态再选模型拿到结构化的script.json接下来要替每一镜生成画面。这里有个方向决策做短剧还是做漫剧。它不只是审美问题而是直接决定算力成本、生成速度和可控性。维度短剧视频生成漫剧分镜运镜画面模态动态视频片段静态漫画分镜缩放/平移主要模型图生视频/文生视频模型高质量文生图模型推理成本高单镜耗时明显低单图秒级到十几秒一致性难点帧间动作是否连贯角色脸和构图是否统一成品观感接近实拍类似动态漫画/有声条漫我一般会建议内容团队先按漫剧跑通全流程再挑高光镜头转视频生成。因为漫剧的每一镜是独立图片出问题可以单镜重做视频生成一旦动作变形重做的成本翻好几倍而且很难参数化修复。同一套分镜脚本两种做法输入的画面 prompt 也不一样。短剧写的是“镜头运动、人物动作、环境光照”这类动态描述漫剧写的是“构图、视角、漫画风格、细节层次”这类静态描述。无论哪种分辨率必须先定死。横屏试片用 768×432竖屏成片用 1080×1920后面拼接才不会出现黑边问题。3.2 角色一致性固定 seed 只是及格线角色 LoRA 才是行业方案画面段最坑的问题不是“画面丑”而是“角色长得不一样”。同一角色上一镜还算清秀下一镜直接换脸。原因是扩散模型每次生成都是一次全新采样单靠 prompt 里的文字描述约束不住人脸特征。先做一个保底动作给每个镜头固定 seed。下面的脚本按镜号线性递增 seed保证脚本不变时画面可复现# stage2_frames.py # 按分镜脚本生成漫剧分镜图开发期先固定 seed保证同一镜可复现 import json import os import time from openai import OpenAI image_client OpenAI( api_keyos.getenv(IMG_API_KEY), base_urlos.getenv(IMG_BASE_URL), ) with open(script.json, encodingutf-8) as f: script json.load(f) style script.get(style, 国漫厚涂电影感构图) base_seed 20240701 os.makedirs(shots, exist_okTrue) for act in script[acts]: for shot in act[shots]: prompt f{style}{shot[subject]}{shot[action]}细节丰富景深适中 negative 多余肢体, 文字, 水印, 变形, 低清, 五官错位 resp image_client.images.generate( modelos.getenv(IMG_MODEL, flux-schnell), promptprompt, n1, size768x432, qualitystandard, ) url resp.data[0].url # 实际使用时要再写一个下载函数把图片落盘到 shots/shot_{no:02d}.png print(shot[no], url) time.sleep(1) # 防止触发限流这段代码里seed是最重要的参数。同一个模型、同一个 prompt、同一个 seed画面高度可复现。negative提示词要写它能拦掉常见的畸形手、水印、文字干扰。size直接用 768×432后面 FFmpeg 拼接省掉一次缩放。但固定 seed 只能让“同一句话”稳定换一个镜头、换一个场景角色脸还是会变。行业里成熟的做法是给主角训练角色 LoRA准备同一个角色 20-30 张不同角度的设定图训练一个小规模 LoRA生成时在 prompt 里加触发词。这是角色一致性的真正解法平台方往往把它包装成“角色库”本质就是 LoRA 推理。如果坚持所有环节本地跑这就是一次完整的 AI 模型部署。我的建议是不要把一个文生图服务和一个 LLM 服务塞进同一块 GPU。图像推理吃显存LLM 推理吃内存带宽混在一起两边都慢。拆成两个独立服务用 HTTP 调用问题会少一半。3.3 画面元数据落盘每个镜头都要能回查生成条件画面生成完不要只留一张 PNG。我习惯在每个镜头旁边存一份同名的.json记录生成这张图时的完整条件{ shot: 1, prompt: 国漫厚涂外卖员在雨夜街道抬头看老宅细节丰富景深适中, negative: 多余肢体, 文字, 水印, 变形, 低清, 五官错位, model: flux-schnell, seed: 20240701, size: 768x432, created_at: 2025-04-16T10:32:11 }这看起来是额外工作但排查问题时它就是命根子。画面崩了先看.json里的seed和prompt能直接复现没有这份元数据就只能凭记忆猜参数。第 6 章讲的版本化也依赖这个文件。4. 声音与成片段TTS 配音、FFmpeg 拼接和时间轴对齐的三个关键4.1 逐句合成而不是整篇合成为什么对白文件要按镜号落盘配音段的任务是把script.json里的对白变成语音文件。常见做法是逐句合成不要整篇合成原因有三个某一镜对白出错了只重跑一镜不用重跑全集逐句文件天然带镜号拼接时按号对齐音色统一只靠同一个voice参数整篇合成反而容易断句出错。先看一个最小实现# stage3_tts.py # 逐句合成对白文件名直接带镜号 import json import os from openai import OpenAI tts_client OpenAI( api_keyos.getenv(TTS_API_KEY), base_urlos.getenv(TTS_BASE_URL), ) with open(script.json, encodingutf-8) as f: script json.load(f) os.makedirs(audio, exist_okTrue) for act in script[acts]: for shot in act[shots]: if not shot[dialog]: continue resp tts_client.audio.speech.create( modelos.getenv(TTS_MODEL, tts-1), voiceos.getenv(TTS_VOICE, alloy), inputshot[dialog], ) mp3_path faudio/shot_{shot[no]:02d}.mp3 resp.write_to_file(mp3_path) print(mp3_path)注意对白的预处理很多坑都藏在这里。中文 TTS 遇到数字、英文、特殊符号会读得莫名其妙所以正式跑之前要写一个预处理函数把“2024年”换成“二零二四年”把“100%”换成“百分之百”把人名和地名用书名号或逗号隔开。还有一个技巧对白结尾加句号会让停顿更长加逗号会让停顿更短。想控制节奏就是通过标点硬调。4.2 音频时长才是剪辑基准先用 ffprobe 读真实时长并重算画面长度这是整条流水线里最容易翻车的地方。剧本里的duration是编剧预估的TTS 合成之后你才知道这句对白到底念了多久。如果拿剧本时长去剪画面结果一定是字幕没念完就切走或者画面空转两秒。正确做法是先读音频时长再用真实时长去生成每一镜的视频。两条命令可以立刻用# 读取某一句对白的真实时长秒 ffprobe -v error -show_entries formatduration -of csvp0 audio/shot_01.mp3 # 用单张图片加一段音频生成一个视频画面长度以音频为准 ffmpeg -loop 1 -i shots/shot_01.png -i audio/shot_01.mp3 \ -c:v libx264 -tune stillimage -c:a aac -b:a 128k \ -shortest out/shot_01.mp4-loop 1让静态图持续循环-shortest表示输出在音频结束时停止-tune stillimage是专门针对静态图的编码优化。这一条命令就解决了“画面和配音对不上”的大半问题。在实际项目里我更推荐用 Python 把时长批量读出来写回script.json或单独的timeline.json后续拼接统一读这个文件# stage4_measure.py # 批量读取音频真实时长覆盖剧本里的预估 duration import json import subprocess def get_duration(mp3_path: str) - float: out subprocess.check_output([ ffprobe, -v, error, -show_entries, formatduration, -of, csvp0, mp3_path, ]) return float(out.strip()) with open(script.json, encodingutf-8) as f: script json.load(f) for act in script[acts]: for shot in act[shots]: mp3 faudio/shot_{shot[no]:02d}.mp3 if os.path.exists(mp3): shot[real_duration] round(get_duration(mp3), 2) print(shot[no], shot[real_duration])有了real_duration拼接才有依据。所有镜头都转成out/下的 mp4 之后用 concat 协议做整片拼接# 先保证所有镜头的编码参数一致再拼接 for f in out/*.mp4; do echo file $f list.txt; done ffmpeg -f concat -safe 0 -i list.txt -c copy final.mp4list.txt里的路径必须是相对路径文件编码不能带 BOM否则 FFmpeg 会报错。-c copy不重新编码速度快但前提是前面每个镜头视频的分辨率、帧率、像素格式完全一致不一致时老老实实做一次统一转码别硬拼。4.3 调度层一个人也能搭的 AI Agent 工作流剧本、画面、配音、拼接四个环节都有了单点脚本接下来要做的是把它们串起来。我见过很多人这里还在手动复制 URL、手动下载图片那长剧根本跑不动。常见做法是写一个顶层调度脚本把每个 stage 当作一个独立环节顺序执行。这就是一个轻量 AI Agent 工作流LLM 负责决策和产出元数据图像服务和 TTS 是执行器manifest.json是共享状态。用 Python 的subprocess串起来已经足够# run_pipeline.py # 从剧本到成片的全流程调度脚本 import subprocess import time stages [ (stage1_script.py, 生成剧本), (stage2_frames.py, 生成画面), (stage3_tts.py, 合成配音), (stage4_measure.py, 读取时长), ] manifest {started_at: time.time()} for script_name, desc in stages: print(f[pipeline] {desc} 开始) result subprocess.run([python, script_name], capture_outputTrue, textTrue) if result.returncode ! 0: print(f[pipeline] {desc} 失败) print(result.stderr) break manifest[script_name] {finished_at: time.time(), log_tail: result.stdout[-200:]} else: print([pipeline] 全部环节完成可执行拼接命令)不要小看这个调度脚本。它的价值不在技术含量而在可观测性每一步的耗时、日志、产物状态都被记录。批量跑上十集短剧时你能迅速说出卡在哪一步而不是盯着屏幕看半天不知道谁挂了。5. 避坑把流水线从“能出片”推到“能可靠出片”的排查清单能出一段样片不难难的是同一句话改三个字之后整条流水线还能按预期跑完。下面五条是我在类似流程里真正踩过的坑每条都按现象、原因、解决写清楚。5.1 镜头时长错位画面只有 3 秒配音却有 8 秒现象生成的成片里画面已经切到下一个镜头上一句对白还没念完。原因剪辑用的时长来自剧本duration电影编剧拍脑袋写的秒数和小助手TTS演员的实际语速没有关系。解决把 TTS 真实音频时长作为剪辑基准。先用ffprobe读出每个 mp3 的秒数再用ffmpeg -loop 1 -shortest按音频长度生成每镜视频最后再拼接。这条修完之后时长错位基本绝迹。5.2 角色脸每集都在“换演员”现象同一个角色上一集和下一集长得完全不一样甚至连同一集里不同镜头都有差异。原因扩散模型每次生成都是一次全新采样文字 prompt 约束不住具体人脸只靠固定 seed 能管住同一个镜头管不住跨镜头跨集。解决给主角训练角色 LoRA生成时在 prompt 里带触发词。训练数据 20-30 张角色设定图就够重点是角度丰富、表情克制、背景干净。LoRA 推理时权重不要太满0.7-0.8 比较自然太满会出现角色僵硬的“塑料脸”。5.3 长剧情逻辑崩坏人物动机前后矛盾现象第三幕里主角突然不认识第一幕救过他的人或者对白里的因果关系对不上。原因LLM 上下文窗口有限长剧本生成到后段早期事实被稀释模型开始用幻觉填补。解决分段生成加状态回填。每生成一幕额外让模型输出一个状态对象记录“谁在场、知道什么、当前目标”下一幕生成时把这个状态对象拼进 prompt。这样人物动机是逐步迭加的而不是让模型从零回忆。5.4 拼接黑边与分辨率不一致现象最终成片里一部分镜头被拉伸变形一部分镜头上下有黑边。原因不同模型或者不同参数的输出尺寸不统一FFmpeg concat 遇到分辨率不一的输入要么报错要么强行拉伸。解决生成阶段就把尺寸锁死拼接前统一做一次缩放和补边。竖屏用 1080×1920横屏用 768×432出现脏边时用下面这条命令统一处理ffmpeg -i input.png -vf scale1080:1920:force_original_aspect_ratiodecrease,pad1080:1920:(ow-iw)/2:(oh-ih)/2 output.png5.5 平台包启动后显存不够现象Lingg.zip 这类平台包解压后跑起来服务器直接卡死或者启动到一半进程被杀。原因平台把所有模型都塞进一键启动LLM、文生图、TTS 三套推理服务混在同一块 GPU 上显存叠加直接溢出。解决拆分部署按需启动。平台包通常有模型配置开关先只打开当前要用的模块本地 GPU 不够时把图像和 TTS 接到云端 API本地只跑调度和拼接。这个顺序很重要先用云端 API 把流程跑通再逐步把重模块搬回本地。6. 进阶给每次生成留下“后悔药”——中间产物版本化与回归比对6.1 每次生成都写一份 manifest把流水线跑通只是开始。真正让这个方案值钱的是你改了提示词之后能知道改了什么、影响在哪、能不能回滚。我的做法是给每次生成单独开一个目录目录里放一份manifest.json记录 prompt、模型名、seed、temperature、每个环节的产物 hash。目录用集数和时间戳命名。生成完马上提交一次 git图片、音频用 git-lfs 管理git init git lfs track *.png *.mp3 *.mp4 git add manifest.json script.json shots/ audio/ out/ git commit -m ep01 seed20240701 modelflux-schnell有了这个习惯任意一版成片都能还原生成条件。这一条看起来跟 AI 关系不大但在内容生产里就是后悔药甲方说“上一版更好”时你能一键切回去而不是翻聊天记录找参数。6.2 用 diff 做回归比对而不是肉眼比对成片我后来还加了一步写一个比对脚本把两版script.json的镜头数、对白序列、总时长打一个 diff。只要这些结构字段没变画面风格变化就是可控的如果对白序列变了说明 LLM 对 prompt 的理解发生了漂移需要回查输入。这其实就是 AI 测试开发的思路把不确定性变成可回归的基线。我第一次跑这类流程时同一个故事隔了两天生成三版脸不一样、配音不一样谁也说不清哪版好。后来把 seed、模型名、prompt 全部写进 manifest才真正敢批量做。这个习惯是交过学费换来的。希望帮到你。本文还有配套的精品资源点击获取
返回列表