ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI短剧创作教程:用源码搭一条可复现的批量生产流水线

AI短剧创作教程:用源码搭一条可复现的批量生产流水线 简介面向希望借助AI工具完成短剧创作的创作者与开发者这是一份以AiPy自动化工作流为核心的源码包。资源将剧本生成、旁白与分镜头制作、人物一致性保持、音视频合成等环节串成完整链路即使没有专业编剧背景也能按教程引导产出短剧作品。压缩包共3个文件主要包括InsCode配置文件、HTML教程页面与Gitignore规则其中HTML页面承载操作指引与提示词示例InsCode配置则便于在云端直接运行相关工具。包体仅7KB轻量易用已有1207人学习。通过学习这份资源读者既能掌握从故事主题设定、镜头设计到最终成片的完整方法论也可通过源码了解AI短剧创作背后的调用逻辑与工程结构对于想将AI能力落地到内容创作的个人或开发者都具备参考价值。1. AI短剧创作教程源码不是装饰品是可复现的流水线AI短剧创作教程[源码]这个标题击中了一类真实需求用 AI 做短剧的门槛已经从「会不会用某个 AI 工具」变成了「能不能稳定批量产出几十集内容」。剧本可以靠大模型写画面可以靠扩散模型生成配音有现成的语音合成但把这些环节像流水线一样串起来、固定参数、批量跑通才是多数创作者真正卡住的地方。这也是「源码」出现在标题里的原因它代表一条可复现的流程而不是一次性演示。这套方案适合内容团队也适合没有 GPU、想用 API 先验证流程的个人开发者。读完你至少能搭建一条「剧本 → 分镜 → 剧照 → 配音 → 成片」的最小流水线并知道每个环节的参数边界和常见坑点。2. 把AI短剧创作拆成五个环节先定工作流再谈源码2.1 五个环节怎么串联先文字、后画面、再声音做 AI 短剧和传统短剧最大的区别是传统剧组靠人和场地把故事拍出来AI 短剧靠模型调度把素材生成出来。常见的做法是拆成五个环节剧本、分镜、画面生成、配音与字幕、合成。不管是叫 AI 短剧还是 AI 漫剧落脚点都一样把文本变成能投放到竖屏渠道的短视频。剧本环节不能省。短剧的剧本和电视剧剧本不同它追求的是前三秒留人、每 15 秒一个反转、结尾留钩子。AI 大模型写这种强冲突文本并不困难但你得在 prompt 里把短剧的节奏约束写清楚否则它会给出一堆描述性旁白而不是能直接拍的台词和动作。写好的剧本要单独存成文本文件不要直接丢进下一个环节因为后面分镜解析、提示词构建、合成全都要引用这一层文本。这个顺序最好不要颠倒。改文字的代价最低改画面的代价中等改成片的代价最高。如果你先把画面全部生成完再发现剧情冲突不成立或者台词逻辑对不上回炉重做一整集成本几乎翻倍。所以成熟的流程都把剧本和分镜作为最前端的强约束画面生成之前就要把故事彻底定下来。我一般会在每个环节之间插一道人工质检节点。分镜出来后看一眼节奏是否合理剧照出来后抽检人物一致性成片出来后完整过一遍。AI 短剧不是全自动印钞机它是「半自动流水线加质检员」的工种。纯无人值守在目前的模型稳定性下素材大概率有一半要返工这不是个人能力问题是扩散模型偶发失控决定的。画面生成和配音之间还有一个容易忽视的先后关系先有确定的台词才能通过语音合成生成音频有了音频实际时长才能决定每张剧照需要保持多久。所以配音实际决定了最终视频长度而画面生成的宽高比和构图必须在配音之前定死。你在启动批量生成之前先把规格全部定好后面才不用返工。2.2 为什么分镜必须用 JSON给后续环节留一条机器可跑的路在纯人工流程里分镜可以写成自然语言文档「镜头一近景女主愤怒地摔杯子」。但只要你打算用源码批量生产分镜就必须结构化。原因是图像生成模型不认识自然语言里的隐含语义你写「女主愤怒地摔杯子」它不知道谁是女主、什么景别、在什么场景。用 JSON 作为中间产物有两个不可替代的好处字段可校验和下游代码衔接直接。实操上分镜 JSON 按场景-镜头两层结构组织。一个场景包含多个镜头每个镜头字段包括镜头号、景别、画面描述、角色、动作、台词、时长。字段统一用英文命名和下游代码、配置文件的键名保持一致避免在中文和英文之间来回翻译。我常用的分镜结构长这样{ title: 首富的归来, scenes: [ { scene_id: 1, location: 高档写字楼, time: 夜, shots: [ { shot_id: 1, shot_type: 近景, scene_desc: 女主坐在办公桌前盯着电脑屏幕表情震惊, characters: [女主], action: 抬头放下咖啡杯, line: 这个数字……怎么可能, duration: 3 } ] } ] }这个 JSON 有两个关键点scene_desc是给图像模型看的画面描述要求写成实体描述句而不是带心理活动的文学语言line是最终要念出来的台词后续会直接传给语音合成所以不能写「她愤怒地质问他」这种舞台指示。把 schema 在分镜 prompt 里写清楚再让解析脚本校验一遍能挡掉大部分结构错误。2.3 画面、配音、字幕和运镜的选型先定边界再选工具画面生成选本地还是 API本质是成本、可控性和上手速度的权衡。本地推理用 Stable Diffusion 系开源权重适合需要调参、训练角色 LoRA、长期批量生产的团队代价是显卡内存和调试时间。API 按张计费适合个人创作者先验证流程代价是单张成本累积快而且无法自由换底模。配音方面优先选带情感控制的语音合成。短剧台词大多是争吵、告白、震惊这些高情绪强度的内容平铺直叙的合成音色会让观众一秒出戏。字幕我建议用独立字幕文件而不是把字烧死在画面里这样后期改台词不用重新压制视频。字幕时间轴由分镜 JSON 的 duration 字段和配音实际时长一起计算别用估算值这是第五章要展开的经典坑点。运镜方式也要提前想。目前多数低成本 AI 短剧用「静态剧照 缓慢推拉缩放」来模拟镜头运动这比直接用图生视频便宜得多出片也稳。图生视频生成动态片段贵且不稳定适合关键镜头点缀不适合每集几十个镜头全量铺。代码层面静态图加 zoompan 滤镜就能做出可接受的推镜效果第四章会给出具体命令。最后一个边界是素材授权。要做商业化投放剧照风格、配音音色、背景音乐都要落在已授权范围内。很多团队前期不查授权到投放环节才发现素材过不了审核。AI 生成的画面在部分渠道还要求标注 AI 生成这些规则动工前就要查清楚。3. 动手写剧本解析让大模型输出可校验的分镜 JSON3.1 最小可跑的剧本解析脚本这一节的目标输入一个纯文本故事输出一个结构化分镜 JSON。我用兼容 OpenAI 格式的 chat completions 接口不管是云厂商的大模型网关还是本地部署的推理服务只要支持这个协议代码不用改就能跑。# parse_script.py # 输入story.txt 纯文本剧本输出shots.json 结构化分镜 import json import os import requests API_URL os.getenv(LLM_API_URL) # 兼容 chat completions 协议的网关地址 API_KEY os.getenv(LLM_API_KEY) def parse_script_to_shots(raw_text: str) - dict: system_prompt ( 你是一名短剧分镜师。把输入的故事拆成适合竖屏短剧的分镜。 只输出 JSON不要输出任何解释文字。 ) user_prompt f 请把下面这个故事拆成分镜 JSON结构要求如下 {{ title: 短剧标题, scenes: [ {{ scene_id: 1, location: 场景地点, time: 日/夜, shots: [ {{ shot_id: 1, shot_type: 近景/中景/远景/特写, scene_desc: 画面描述包含人物、动作、环境, characters: [人物A], action: 人物肢体动作, line: 该镜头台词, duration: 3 }} ] }} ] }} 要求 1. 每个场景至少 2 个镜头整集 10~20 个镜头。 2. 总时长控制在 60~90 秒。 3. scene_desc 写成能直接转成图像提示词的描述句。 4. 台词口语化符合短剧强冲突风格。 故事内容 {raw_text} resp requests.post( API_URL, headers{Authorization: fBearer {API_KEY}}, json{ model: os.getenv(LLM_MODEL, gpt-4o-mini), messages: [ {role: system, content: system_prompt}, {role: user, content: user_prompt}, ], temperature: 0.3, response_format: {type: json_object}, }, timeout120, ) resp.raise_for_status() content resp.json()[choices][0][message][content].strip() return json.loads(content) if __name__ __main__: with open(story.txt, encodingutf-8) as f: raw f.read() shots parse_script_to_shots(raw) with open(shots.json, w, encodingutf-8) as f: json.dump(shots, f, ensure_asciiFalse, indent2)代码逻辑并不复杂把 schema 写进 user prompt让模型按结构返回最后 json.loads 解析。但有几个参数要解释。temperature0.3是在压低随机性分镜解析要的是稳定输出不是让模型发挥创意。response_format{type: json_object}强制模型返回 JSON 对象省去从 Markdown 代码块里捞 JSON 的麻烦。timeout120要给足长故事加多镜头在推理端会花不少时间本地部署的模型 60 秒超时很容易被慢推理打死。接口地址、密钥、模型名全部走环境变量不要把密钥写死在脚本里。不同项目间切换时只需要改环境变量代码本身不用动。还有一个小习惯拿到 content 后先 strip 再 json.loads因为部分网关会在 JSON 前后夹带换行或空白直接解析会抛异常。3.2 分镜 JSON 字段设计与校验规则分镜字段不是随便拍的每个字段都对应下游一个具体用途字段类型说明scene_idint场景序号locationstr场景地点会拼进图像提示词timestr日/夜/黄昏影响打光描述shot_idint镜头序号shot_typestr近景/中景/远景/特写scene_descstr画面描述核心提示词来源characterslist[str]在场角色名actionstr角色肢体动作linestr台词后续传给语音合成durationfloat建议时长秒字段设定背后的逻辑是scene_desc是图像提示词的主体所以要求写成描述性句子line在后续步骤会传给语音合成这里必须写最终念出来的台词duration直接决定成片长度先给初值等配音实际时长出来后再校正。脚本跑完不能直接拿去生成画面先过一遍校验# validate_shots.py import json import sys REQUIRED_FIELDS [shot_id, shot_type, scene_desc, characters, action, line, duration] def validate_shots(path: str) - None: with open(path, encodingutf-8) as f: data json.load(f) total_duration 0.0 for scene in data[scenes]: for shot in scene[shots]: missing [f for f in REQUIRED_FIELDS if f not in shot] if missing: raise ValueError( fscene {scene[scene_id]} shot {shot.get(shot_id, ?)} fmissing: {missing} ) if shot[duration] 0 or shot[duration] 15: raise ValueError( fshot {shot[shot_id]} duration 超范围: {shot[duration]} ) total_duration shot[duration] if not 60 total_duration 120: print(fwarning: 总时长 {total_duration:.1f}s不在 60~120s 区间) print(f校验通过总时长 {total_duration:.1f}s) if __name__ __main__: validate_shots(sys.argv[1] if len(sys.argv) 1 else shots.json)这道校验工序很多人会跳过但实际跑量时它非常值钱。大模型返回的 JSON 偶尔会缺字段、duration 写成负数或者 999一旦漏过校验直接进入图像生成后面所有环节全部错位。GPU 时间比 CPU 贵得多API 调用次数也有成本上限这道闸门把错误拦在最便宜的阶段。3.3 从 JSON 到图像提示词模板与负面词怎么拼分镜 JSON 不能直接丢给图像模型需要拼接成提示词。拼接规则我固定为风格前缀 画面描述 角色外观描述。角色外观用一个字典维护同一角色在全部镜头里使用同一段描述词这是不训练 LoRA 的情况下最廉价的一致性手段。# prompt_builder.py STYLE_PREFIX short drama still, cinematic lighting, vertical composition NEGATIVE_PROMPT ( lowres, bad anatomy, bad hands, extra fingers, deformed face, blurry, watermark, text, logo, nsfw ) CHAR_STYLES { 女主: asian young woman, long black hair, red coat, cold expression, 男主: asian young man, suit, sharp jawline, short black hair, } def shot_to_prompt(shot: dict) - tuple[str, str]: chars , .join(CHAR_STYLES.get(c, c) for c in shot[characters]) prompt f{STYLE_PREFIX}, {shot[scene_desc]}, {chars} return prompt, NEGATIVE_PROMPT拼接的关键在char_styles。这里定义的是一段可复用的英文外观描述不会随剧情变化。你可能会问为什么不直接在 scene_desc 里写「女主站在阳台上」因为图像模型会把「女主」当成一个角色描述来理解出图效果不可控。正确做法是 scene_desc 只写画面角色用 characters 字段单独传拼接时再替换成固定外观词条。负面提示词里的watermark和text要常驻。AI 生成画面里经常出现乱码水印和说明文字后期修补成本高不如在生成端就压掉。bad hands、extra fingers是角色特写镜头的高频翻车点镜头越近负面词越要写全。提示词拼接完成后建议把每个镜头的最终 prompt 写进日志方便后面排查时对照。4. 动手写画面生成与成片合成剧照、运镜、配音一个不少4.1 可切换后端的图像生成器封装图像生成这一步要同时面对两种场景本地有 GPU 的人用 diffusers 推理没有 GPU 的人走 API。为了避免把流程写死封装统一接口用环境变量切换后端# image_gen.py import base64 import os import requests def generate_image(prompt, negative_prompt, out_path, size(720, 1280), seed42): backend os.getenv(IMAGE_BACKEND, api) if backend api: _generate_via_api(prompt, negative_prompt, out_path, size, seed) elif backend local: _generate_via_diffusers(prompt, negative_prompt, out_path, size, seed) else: raise ValueError(funknown IMAGE_BACKEND: {backend}) def _generate_via_api(prompt, negative_prompt, out_path, size, seed): resp requests.post( os.getenv(SD_API_URL, http://127.0.0.1:7860) /sdapi/v1/txt2img, json{ prompt: prompt, negative_prompt: negative_prompt, width: size[0], height: size[1], seed: seed, steps: 25, sampler_name: DPM 2M Karras, cfg_scale: 7, }, timeout600, ) resp.raise_for_status() img_b64 resp.json()[images][0] with open(out_path, wb) as f: f.write(base64.b64decode(img_b64)) def _generate_via_diffusers(prompt, negative_prompt, out_path, size, seed): from diffusers import StableDiffusionPipeline import torch pipe StableDiffusionPipeline.from_pretrained( os.getenv(SD_MODEL_PATH, runwayml/stable-diffusion-v1-5), torch_dtypetorch.float16, ) pipe pipe.to(cuda) gen torch.Generator(devicecuda).manual_seed(seed) image pipe( promptprompt, negative_promptnegative_prompt, widthsize[0], heightsize[1], num_inference_steps25, generatorgen, ).images[0] image.save(out_path)后端切换的设计逻辑很直接。API 模式适合没显卡或者团队协作统一走服务本地模式适合跑角色 LoRA 和调底模。_generate_via_diffusers这段要提醒两点每次从from_pretrained加载权重非常慢实际工程里要把 pipe 做成模块级单例首镜初始化一次后续复用。如果显存吃紧加pipe.enable_model_cpu_offload()让部分层在 CPU 和 GPU 间换入换出速度慢一些但不再直接爆显存。API 模式下默认指向本机 7860 端口这是常见的 Stable Diffusion WebUI 默认地址。如果你用的是云厂商图片服务改环境变量指向对应网关即可。timeout600不是因为它慢而是因为排队机制一次请求等上几分钟是常事超时设短会误杀。4.2 固定种子与竖屏参数跑批前先定死的三件事短剧竖屏分辨率我固定用 720×1280不用 1080×1920 起步。1080×1920 出图慢、显存占用高、生成失败概率也更大720×1280 在手机上和 1080p 观感差别不大但单张成本几乎腰斩。参数建议值原因width720竖屏短视频基础分辨率height1280和宽度配套避免后期裁剪seed固定值同镜头重生成结果一致出错可排查steps25太低结构不稳太高只增加耗时samplerDPM 2M Karras收敛稳定出图失败率低cfg_scale7太低偏离提示词太高过饱和且发灰batch_size1跑批用循环不用多 batch防显存炸固定 seed 是最容易被新手忽略的点。不固定 seed同一镜头失败后重跑出来的图完全不同连排查依据都没有。每次生成时把 seed 写进文件名或日志例如shot_03_seed42.png想复现或者定位问题一眼就能找到对应记录。风格一致性还有另一个技巧所有镜头共用同一个STYLE_PREFIX。如果每个镜头各自发挥风格词整集会像不同导演拍的素材拼在一起。风格词控制在十个以内太多了互相打架模型注意力会被稀释。4.3 用 FFmpeg 把剧照和音频压成竖屏短片单张静态图加配音不做运动处理成品就像幻灯片。短剧观众对「图片加配音」的容忍度很低至少要给画面加一个缓慢推拉或平移的运镜。用 FFmpeg 的 zoompan 滤镜就能实现轻推镜不需要引入重型渲染工具。# compose.py import subprocess def get_audio_duration(audio_path: str) - float: out subprocess.check_output([ ffprobe, -v, error, -show_entries, formatduration, -of, csvp0, audio_path, ]) return float(out.decode().strip()) def compose_shot(image_path: str, audio_path: str, out_path: str, durationNone): if duration is None: duration get_audio_duration(audio_path) total_frames int(duration * 25) vf ( scale720:1280, fzoompanzmin(zoom0.0006,1.10):d{total_frames}: xiw/2-(iw/zoom/2):yih/2-(ih/zoom/2):s720x1280:fps25 ) cmd [ ffmpeg, -y, -loop, 1, -i, image_path, -i, audio_path, -vf, vf, -c:v, libx264, -c:a, aac, -t, str(duration), -shortest, out_path, ] subprocess.run(cmd, checkTrue)这段代码里最重要的参数是 zoompan 的d它表示这个滤镜总共渲染多少帧。total_frames duration * 2525 是帧率。zmin(zoom0.0006,1.10)的意思是每帧放大一点点直到 1.10 倍就停模拟缓慢推近的效果。-shortest让视频在音频或视频结束时截断防止音频比视频长时出现黑屏尾巴。这里有个实际坑zoompan 处理高分辨率输入时内存会涨得很夸张。我一般把输入图片统一压到 1080×1920 以内再进滤镜不然跑几集内存就满了。镜头片段全部合成后用 concat 拼接整集ffmpeg -y -f concat -safe 0 -i clips.txt -c copy final.mp4concat 命令要求所有片段编码参数完全一致分辨率、帧率、编码器都要相同。如果某个镜头用了不同尺寸-c copy拼接会花屏。所以批量合成阶段要先跑一遍 ffprobe 检查每个片段的宽高和帧率不一致的重新生成再进 concat。这个检查放在脚本里自动做不要人工一张张看。5. AI短剧创作的常见问题排查与避坑5.1 人物长相每集不一样现象第一集女主是黑色长发第二集变成棕色卷发到第三集连脸型都变了。观众在同一部剧里看到「换脸」这是劝退率最高的问题。原因不同集次的提示词里角色外观描述不一致或每次生成没有固定 seed。大模型对中文人名的理解不稳定直接把「女主」两个字写进提示词它会凭上下文发挥。解决用char_styles字典统一维护角色外观并让分镜解析脚本在每一集输出里都引用同一份描述。同时固定每个角色的 seed 区间和底模。如果对一致性要求更高就训练角色 LoRA出图时在提示词里加 LoRA 权重。前面 3.3 节的拼接代码已经预留了这个口子直接把CHAR_STYLES里的描述替换成 LoRA 触发的关键词即可。5.2 生成画面风格跑偏偏成欧美剧现象明明写的是中文都市剧生成的演员全是欧美面孔布景也带着西式风格。原因底层模型的训练数据以英文描述和欧美审美为主中文提示词里的「现代都市」「写字楼」很容易被理解成英文语境下的场景。解决正向提示词里显式写asian、east asian face、modern chinese city比写中文语义关键词更稳。更彻底的做法是换用中文优化的底模或者额外挂一个东方面孔 LoRA。负面词里不要强行加「欧美」相关词否则会把人物面部结构也一起带偏。风格偏移问题不要靠反复抽卡解决底模和关键词的问题换思路比换运气有效。5.3 字幕和配音错位现象字幕显示「你居然骗我」配音已经念到下一句了整段对不上。原因字幕时间轴用分镜 JSON 里的duration估算值生成但语音合成出来的实际时长和预估时长差 0.3 到 1 秒。单个镜头差半秒不明显连续几个镜头累积下来就是灾难。解决合成前先用ffprobe读音频真实时长再用真实时长生成字幕时间轴和视频片段长度。配音比预估短就补图比预估长就裁掉静音段。总之一切以音频实际时长为准不要用 JSON 里的初值。这个把duration当圣旨的毛病我在 4.3 节已经埋过伏笔实际工程里是最常见的时间轴错误来源。5.4 API 成本失控现象预算按 50 集算好结果跑到第 10 集费用已经超了三分之一。原因镜头失败后反复重试而失败镜头在复杂构图上能占三成。很多 API 按张计费重试三次就是三倍成本而且重试生成的图大概率还是错的。解决失败先查提示词和负面词不要盲目同参数重跑。批量铺量前先抽 3 到 5 个样式不同的镜头试跑确认风格正常、成功率可接受再全量生成。重试逻辑加上限比如单镜头最多重试两次两次失败直接标记「人工补图」由后期用局部重绘处理。生成日志里记录每个镜头的成功失败次数定期看一眼你会发现哪些镜头类型是翻车重灾区。5.5 本地显存不足程序直接崩溃现象720×1280 出图一切正常换成 1080×1920 后报 CUDA out of memory服务直接挂掉。原因分辨率提升带来显存占用非线性上涨加上 batch 数量一叠加8GB 显卡根本扛不住。很多人以为崩溃是系统内存不够拼命加内存条其实瓶颈在显存。解决规格回到 720×1280、batch 用 1先把流程跑通。显存低于 8GB 开启enable_model_cpu_offload牺牲速度换稳定。还有一个习惯很重要批量循环里每张图生成后调用torch.cuda.empty_cache()释放推理缓存避免同一进程连续生成时显存碎片累积。程序崩溃最难受的不是崩本身是没有日志。生成脚本一定在循环里写日志至少记录镜头号、prompt 前 50 字、seed、耗时、成功失败。否则一次批量跑完几个镜头失败却不知道失败在哪里只能全盘重来。6. 进阶把整条流水线收进一个配置文件6.1 用 YAML 管理生成参数与密钥位置整条流水线跑通之后下一步是收拢成可复用的配置体系。项目名、分辨率、模型名、seed、采样参数这些散落在脚本里的数值全部下沉到config.yamlproject: title: 首富的归来 total_episodes: 50 resolution: [720, 1280] fps: 25 llm: api_url: https://your-gateway.example.com/v1/chat/completions model: gpt-4o-mini temperature: 0.3 image: backend: api # 或 local sd_url: http://127.0.0.1:7860 seed: 42 steps: 25 cfg_scale: 7 sampler: DPM 2M Karras tts: voice: female_emotional_zh rate: 0%脚本启动时读取这份配置分镜、图像、合成各自从同一份配置取值。换一个新项目只需复制一份 config不用改任何代码。密钥不落盘配置文件依然走环境变量防止配置被传进公开仓库。配置里不要依赖魔法值每个环节都要有默认值兜底。6.2 量产前检查清单先用三集试跑别上来就铺五十集量产前至少过一遍分镜 JSON 校验通过试跑的三集里人物外观一致字幕时间轴由音频实际时长生成成片没有花屏和黑屏尾巴素材授权与分发渠道要求核对完毕失败重试机制有上限生成日志能定位到具体镜头。这些检查全做完再谈全量铺开。我自己的习惯是永远先跑三集跑完完整看一遍然后第二天再看一遍。AI 短剧这行头天晚上觉得没问题的片子第二天早上十有八九能挑出节奏上的毛病。把这份谨慎养成习惯比任何一键生成工具都重要。希望帮到你。本文还有配套的精品资源点击获取
返回列表