☰
AI短剧生成流水线拆解:从结构化剧本到成片的工程实践
2026/10/10 7:44:44 网站建设 项目流程

简介:一句话生成完整短剧/漫剧的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“写个剧本”,得到一篇散文。散文给读者看没问题,给流水线看是灾难。下一环节要按镜头生成画面,按对白合成语音,按时长拼接成片;没有字段就等于没有坐标。所以第一步要约定 schema:title、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_key=os.getenv("LLM_API_KEY"), base_url=os.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( model=os.getenv("LLM_MODEL", "qwen-plus"), messages=[ {"role": "system", "content": "你只输出 JSON,不要输出 Markdown 和解释。"}, {"role": "user", "content": build_prompt(one_line)}, ], temperature=0.7, top_p=0.9, max_tokens=2000, response_format={"type": "json_object"}, ) return json.loads(resp.choices[0].message.content) if __name__ == "__main__": data = generate_script("一个外卖员在雨夜接到一通匿名订单,送往地址是十年前已拆迁的老宅。") with open("script.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) 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_key=os.getenv("IMG_API_KEY"), base_url=os.getenv("IMG_BASE_URL"), ) with open("script.json", encoding="utf-8") as f: script = json.load(f) style = script.get("style", "国漫厚涂,电影感构图") base_seed = 20240701 os.makedirs("shots", exist_ok=True) for act in script["acts"]: for shot in act["shots"]: prompt = f"{style},{shot['subject']},{shot['action']},细节丰富,景深适中" negative = "多余肢体, 文字, 水印, 变形, 低清, 五官错位" resp = image_client.images.generate( model=os.getenv("IMG_MODEL", "flux-schnell"), prompt=prompt, n=1, size="768x432", quality="standard", ) 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_key=os.getenv("TTS_API_KEY"), base_url=os.getenv("TTS_BASE_URL"), ) with open("script.json", encoding="utf-8") as f: script = json.load(f) os.makedirs("audio", exist_ok=True) for act in script["acts"]: for shot in act["shots"]: if not shot["dialog"]: continue resp = tts_client.audio.speech.create( model=os.getenv("TTS_MODEL", "tts-1"), voice=os.getenv("TTS_VOICE", "alloy"), input=shot["dialog"], ) mp3_path = f"audio/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 format=duration -of csv=p=0 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", "format=duration", "-of", "csv=p=0", mp3_path, ]) return float(out.strip()) with open("script.json", encoding="utf-8") as f: script = json.load(f) for act in script["acts"]: for shot in act["shots"]: mp3 = f"audio/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.mp4

list.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_output=True, text=True) 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 "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2" output.png

5.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 seed=20240701 model=flux-schnell"

有了这个习惯,任意一版成片都能还原生成条件。这一条看起来跟 AI 关系不大,但在内容生产里就是后悔药:甲方说“上一版更好”时,你能一键切回去,而不是翻聊天记录找参数。

6.2 用 diff 做回归比对,而不是肉眼比对成片

我后来还加了一步:写一个比对脚本,把两版script.json的镜头数、对白序列、总时长打一个 diff。只要这些结构字段没变,画面风格变化就是可控的;如果对白序列变了,说明 LLM 对 prompt 的理解发生了漂移,需要回查输入。这其实就是 AI 测试开发的思路:把不确定性变成可回归的基线。

我第一次跑这类流程时,同一个故事隔了两天生成三版,脸不一样、配音不一样,谁也说不清哪版好。后来把 seed、模型名、prompt 全部写进 manifest,才真正敢批量做。这个习惯是交过学费换来的。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询