AI短剧生成平台实战:从脚本到成片的pipeline搭建与调优

发布时间:2026/10/1 6:07:21
AI短剧生成平台实战:从脚本到成片的pipeline搭建与调优
简介面向AI视频创作者与短剧开发者的全栈源码包解决从一句话创意到成片输出的完整短剧制作难题。基于大语言模型解析剧本并自动提取角色、场景与分镜配合AI绘图生成角色形象和场景背景再通过图生视频、TTS配音与FFmpeg合成形成一条自动化工作流。压缩包共94个文件、约634KB文件类型以ts、vue、md、json为主ts为前后端逻辑源码vue为管理界面组件md为说明文档json为配置数据同时包含docker-compose与Dockerfile便于本地快速部署。已有305人学习/浏览适合需要系统了解AI短剧生成链路或进行二次开发的读者。资源内含完整的项目与剧集管理、AI剧本改写、角色与场景编辑、分镜拆解、音频生成及视频导出模块并附带角色形象批量生成、音色分配、帧类型选择等细节设计。部署后即可体验“一句话出短剧”的完整流程同时可参考skills目录中的脚本改写、分镜拆解与网格提示词生成逻辑快速接入自有业务。1. 一句话到成片AI短剧生成平台今天能跑到哪一步输入一句话自动吐出剧本、分镜、配音和成片——AI短剧生成平台是这两年里最让人“又爱又恨”的一类项目。爱的是它把整条生成链路串得很完整剧本编写、角色和场景提取、分镜生成、配音、视频合成每个环节都有对应模块恨的是它比单点AI工具难伺候得多模型、素材、编码、音画对齐全混在一起任何一个环节翻车整条流水线就停在半路。这类平台的本质不是某个大模型而是一条编排好的pipeline大模型负责剧本与分镜TTS负责台词配音ffmpeg负责把素材拼成带字幕的短片。它真正能替代的是从零写脚本、逐条配音、手工剪镜头这些繁琐工序它暂时替代不了的是导演对镜头节奏的判断。适合谁用想批量做AI漫剧、短视频解说、营销短片的团队以及想在自己项目里集成“文生视频”能力的开发者。下文我按自己实际搭过的方案把拆解、部署、调参和踩坑一次讲完。2. 剧本与分镜怎么把一句话变成一套可执行的拍摄脚本拿到一句话需求我不会让模型一口气输出“剧本分镜角色表”而是拆成三次独立的LLM调用第一次扩写剧本第二次抽角色和场景第三次切分镜。这种拆法看起来很笨但每一步都能单独校验出错了也能定位到具体环节。整条链路本质上是多个ai agent各管一段编剧agent负责扩写抽取agent负责结构化分镜agent负责把长文本切成可拍摄的单位。这样做还有个现实原因上下文一长大模型在单次输出里既要保持剧情连贯又要保证JSON结构合法非常容易顾此失彼。与其赌一个超长输出的稳定性不如把任务切小。下面三个小节分别讲这三步怎么做代码可以直接抄进你自己的服务里。2.1 剧本扩写提示词把20字的主旨扩成1200字的正文剧本扩写是整个平台里最“吃”提示词的一步。写提示词本身就是工程行业内现在常说的ai编程提示词也好、prompt engineering也好核心就一句话把约束写清楚把自由留给模型。我一般会定义一个多行字符串模板里面留好可替换的占位符比如剧名、题材、集数、每集字数。import os from openai import OpenAI client OpenAI( api_keyos.getenv(LLM_API_KEY), base_urlos.getenv(LLM_BASE_URL), # 兼容OpenAI协议的服务端点 ) def build_script_prompt(one_line_story: str, episodes: int 3, words_per_ep: int 800) - str: return f 你是一位短剧编剧。请根据下面这句话扩写出一部完整的短剧大纲并展开第一集的完整剧本。 一句话故事{one_line_story} 要求 1. 短剧共{episodes}集每集约{words_per_ep}字。 2. 输出顺序为剧名 / 一句话梗概 / 角色表姓名年龄性格口头禅/ 每集的剧情大纲 / 第一集完整剧本。 3. 剧本正文必须包含对白、动作描述、场景地点。 4. 对白口语化单句不超过40字。 5. 不要输出JSON用自然段落便于后续二次抽取。 第一集剧本控制在{words_per_ep}字左右结尾留一个钩子。 这段代码里有两个关键点一是用f-string把故事和集数传进提示词同一个函数可以复用于不同需求二是明确写了“不要输出JSON”——剧本这种长文本交给模型自由发挥就好强行让它这时候输出结构化内容很容易让剧本质量和JSON完整性互相拖累。参数上扩写任务我一般把temperature调到 0.8 左右max_tokens给到3000以上给足故事发挥的空间。2.2 角色与场景抽取从剧本到结构化JSON一次稳住的写法剧本扩写完成后第二步是把角色和场景抽成结构化数据。这一步严格要求输出合法JSON所以要用第二次LLM调用而不是让第一次扩写顺带完成。抽取时temperature要降到 0.1给模型的自由度越小越好。from pydantic import BaseModel, Field class Character(BaseModel): name: str Field(description角色姓名) gender: str Field(description性别) age: int Field(description年龄) personality: str Field(description性格特征不超过20字) catchphrase: str Field(description口头禅没有就填空字符串) class Scene(BaseModel): location: str Field(description场景地点如老宅客厅) time: str Field(description时间段如傍晚) atmosphere: str Field(description氛围关键词如压抑、温暖) def extract_roles_and_scenes(script_text: str): prompt f 从下面的剧本中抽取角色和场景只输出JSON不要解释。 剧本 {script_text[:6000]} 输出格式 {{ characters: [{{name: , gender: , age: 0, personality: , catchphrase: }}], scenes: [{{location: , time: , atmosphere: }}] }} resp client.chat.completions.create( modelos.getenv(LLM_MODEL), messages[{role: user, content: prompt}], temperature0.1, response_format{type: json_object}, ) data json.loads(resp.choices[0].message.content) # 用pydantic再做一次校验非法字段直接抛错避免脏数据流到下游 characters [Character(**c) for c in data[characters]] scenes [Scene(**s) for s in data[scenes]] return characters, scenes这里我用了response_format{type: json_object}让模型尽量输出合法JSON再用pydantic二次校验。不要只依赖模型的JSON mode它只能保证“格式像JSON”不能保证字段齐全。pydantic校验这一步相当于给下游加了一道保险角色表缺字段时会在这一层直接报错而不是等分镜阶段才暴露。2.3 分镜生成的边界怎么让AI别替你做剪辑决定分镜这一步最容易犯的错是让模型自由决定“镜头怎么切”。模型没有画面素材概念AI自由发挥出来的分镜往往天马行空下游根本找不到对应素材。我一般会把分镜任务限定为“语义切块画面描述补全”按场景段落、台词密度、节奏起伏把剧本切成若干分镜片段每段只输出固定的几个字段。def split_into_shots(episode_script: str, scene_info: dict, max_shots: int 12): prompt f 你是分镜师。请把下面的剧本片段切分成{max_shots}个以内分镜。 场景信息{scene_info} 每个分镜必须包含 - shot_id: 镜号 - duration_sec: 预估时长3到8秒 - shot_size: 景别从[远景, 全景, 中景, 近景, 特写]中选 - visual: 一句话画面描述要具体到主体动作和环境 - line: 该分镜内的台词没有就填空字符串 - sfx: 音效或背景音提示 只输出JSON数组不要输出其他内容。 剧本 {episode_script} resp client.chat.completions.create( modelos.getenv(LLM_MODEL), messages[{role: user, content: prompt}], temperature0.3, max_tokens1500, ) shots json.loads(resp.choices[0].message.content) # 一个常见兜底duration_sec总时长如果远小于目标时长自动按比例放大 total_sec sum(s[duration_sec] for s in shots) target_sec 60 * 3 # 假设一集3分钟 if 0 total_sec target_sec * 0.8: scale target_sec / total_sec for s in shots: s[duration_sec] round(s[duration_sec] * scale, 1) return shotsmax_tokens这里给到1500配合max_shots12的约束基本能保证一次输出完整JSON不会因为生成长数组被截断。把duration校验逻辑放在切分函数里是我习惯留的“后悔药”模型估算的镜头时长经常偏短与其后期剪辑时才发现不够用不如在分镜阶段就按目标总时长缩放。分镜得到的是纯文本描述真正的画面来自第三章要讲的素材库这两件事必须在设计上分开。3. 配音、素材与合成从分镜脚本到成片的三道工序分镜脚本出来后真正开始接触音视频的环节有三道TTS配音、场景素材匹配、ffmpeg合成。这一章是翻车高发区前三步在文本世界里跑得再顺到了音视频环节仍然可能因为一个参数没对齐就前功尽弃。3.1 TTS配音选型先定音色表再选引擎别让角色声音飘很多人在配音上踩的第一个坑是拿到角色表就急着调TTS接口结果发现同一个角色在不同分镜里声音忽高忽低。正确顺序是先定音色表再选引擎。所谓音色表就是把2.2抽出来的角色和TTS引擎的音色参数一一绑定整个生成过程中不再改动。我一般会为每个角色固定一组参数引擎、音色标识、语速倍率、音高偏移、音量增益。角色有多个时音色标识要提前分配好避免两个角色共用同一种声音。下面是基于edge-tts方案的一个示例它走的是公网TTS服务音质稳定部署时只要服务器能正常访问公网即可。import edge_tts import asyncio # 音色表角色名 - (engine, voice, rate, pitch, volume) VOICE_TABLE { 林小雨: {voice: zh-CN-XiaoxiaoNeural, rate: -8%, pitch: 2Hz, volume: 0%}, 陈默: {voice: zh-CN-YunxiNeural, rate: -4%, pitch: -3Hz, volume: 0%}, 旁白: {voice: zh-CN-YunyangNeural, rate: 0%, pitch: 0Hz, volume: 0%}, } async def synth_line(character: str, line_text: str, out_path: str): v VOICE_TABLE[character] tts edge_tts.Communicate( line_text, voicev[voice], ratev[rate], pitchv[pitch], volumev[volume], ) await tts.save(out_path)语音合成有一个经验逐句合成比整段合成更稳。整段合成时只要中间有一句读错整段音频就要重跑逐句合成则能单独替换配合分镜JSON里的line字段一条台词对应一个音频文件后期对齐字幕也方便。实际跑的时候我会先把所有分镜的台词一次性批量合成到一个audio/目录文件名用shot_{id}.mp3规则命名。如果你的环境网络受限也可以把edge_tts.Communicate换成本地TTS引擎的HTTP接口音色表的逻辑不用改。3.2 场景匹配打标签做粗筛向量检索做精排素材匹配这个环节最容易犯的错是只依赖文本向量检索。素材库几百条视频时纯向量召回经常匹配出风马牛不相及的片段。我的做法是两段式先用人工打的硬标签做粗筛再用向量相似度做精排。素材库里每段视频都配一个CSV清单字段包括素材ID、场景关键词、光线氛围、镜头运动方式、横竖比例、时长。粗筛时只用结构化条件过滤比如分镜要求“老宅客厅傍晚温暖”就先从CSV里挑出同时满足三个标签的素材硬筛结果为空时再降级只保留“客厅”标签给下游兜底。筛选到三五条候选后再用向量模型对“一句话画面描述”和素材描述算相似度取最高者。import csv def match_material(shot: dict, material_csv: str): with open(material_csv, encodingutf-8) as f: rows list(csv.DictReader(f)) def score(row): s 0 if row[location] in shot.get(location_tags, []): s 2 if row[atmosphere] shot.get(atmosphere_tags, ): s 2 if row[aspect] ! shot.get(aspect, 9:16): # 画幅不匹配直接否决 return -1 return s candidates [r for r in rows if score(r) 0] candidates.sort(keyscore, reverseTrue) return candidates[0] if candidates else None这段代码里我把画幅不匹配直接判为负分因为9:16竖屏素材和16:9横屏素材混进同一条片子后期很麻烦。粗筛的分数其实不用设计得太复杂我试下来两三个硬标签加权就够用关键是“先否决、后打分”的顺序不能反。向量精排那段代码和embedding模型耦合较深不同模型差异大这里就不贴通用实现你只需要知道粗筛后还剩三五个候选时向量排序才值得做。3.3 ffmpeg视频合成统一规格后再拼接字幕在最后烧ffmpeg是整条流水线的最后一道工序也是坑最多的地方。我在第一次搭的时候直接把素材片段按分镜顺序丢给ffmpeg concat结果成片里有的片段播得快、有的播得慢音画完全对不上。后来才意识到concat要求所有输入片段编码参数一致而素材库里不同来源的视频分辨率、帧率、码率经常全不一样。所以我在拼接之前会先做一次统一转码把每个片段都转成同一个分辨率、同一个帧率、同一个编码格式。竖屏短剧我一般统一到 1080x1920、25fps、H.264。转码命令如下。# 先统一规格再进拼接列表 for f in material_*.mp4; do ffmpeg -y -i $f \ -vf scale1080:1920:force_original_aspect_ratiodecrease,pad1080:1920:(ow-iw)/2:(oh-ih)/2 \ -r 25 -c:v libx264 -preset veryfast -crf 23 \ -an norm_${f} done # 生成拼接文件列表 ls norm_*.mp4 | sed s/^/file / concat_list.txt # 先拼视频轨字幕最后统一烧 ffmpeg -y -f concat -safe 0 -i concat_list.txt -c:v libx264 -r 25 video_noaudio.mp4三个参数需要说明。-vf scale...pad这段是核心scale先把素材等比缩放到能放进1080x1920的框里pad再把不足的部分用黑边补齐这样横屏素材不会变形。-an表示丢弃素材自带音频短剧的声音完全来自TTS生成的配音素材原声混进来会非常乱。-crf 23是画质与体积的平衡点数值越小画质越高文件越大我日常够用。字幕我不会在拼接阶段做而是等视频轨拼完、配音也混流之后一次性烧录。烧录字幕最稳的方式是把台词先写成ass字幕文件再重新编码一遍。# 生成ass后把配音和视频合成同时烧字幕 ffmpeg -y -i video_noaudio.mp4 -i TTS_audio.mp3 \ -vf subtitlessubtitle.ass \ -c:v libx264 -crf 20 -c:a aac -b:a 192k \ -shortest final_episode.mp4-shortest在这里的意思是输出在视频和音频中较短的那个结束时停止。之所以要在最后一步才烧字幕是因为字幕文本来自分镜JSON里的line字段如果你在拼接阶段就烧后面一旦发现某句台词配音读错了整个视频都要重新转码一遍。顺序反过来配音只需要重合成那一句再用上面的命令重跑最后一步。4. 拿到源码后的安装部署流程环境、配置与首次全链路跑通很多读者拿到源码后第一件事是直接python main.py然后在一堆ModuleNotFoundError里耗掉半天。这类AI短剧生成平台对运行环境的要求比普通Web项目高需要Python环境、ffmpeg、模型服务地址、素材目录缺一样都跑不通。我在部署时会固定按“环境准备→配置文件→跑通demo”的顺序来不跳步。4.1 环境准备Python版本、ffmpeg、依赖包一次装齐先说硬件结论短剧生成平台里最重的是LLM推理和TTS合成这两块如果都跑在本地显卡显存至少16G起步而且生成一集短剧可能要等十几分钟。所以我一般建议LLM走云端APITTS按3.1的方案走在线合成本地只跑素材检索和ffmpeg合成。这样一台8G内存的普通服务器也带得动。环境准备命令如下适用于Ubuntu或Debian系系统。# 1. 系统依赖 sudo apt update sudo apt install -y ffmpeg python3-venv python3-pip # 2. 项目依赖 python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install openai pydantic edge-tts python-dotenv # 3. 验证ffmpeg可用 ffmpeg -version | head -n 1依赖清单里我特意没有锁版本号因为这类项目的下游接口更新频繁锁死旧版本反而容易撞上接口不兼容。openai库不只是给OpenAI官方服务用的大部分国产大模型服务都提供兼容OpenAI协议的端点所以只需要改base_url和api_key代码不用动。这也是我在2.1里把客户端初始化单独写出来的原因——部署时只要填环境变量不用翻代码。要注意如果你拿到的那份源码里还有额外的依赖文件比如requirements.txt先看一遍里面的包名把跟音频、视频处理相关的都装上。遇到装不上的包优先检查Python版本我在Python 3.10和3.11上都跑过这类项目3.9以下很容易出现二进制包缺失。4.2 配置文件与路径约定API端点、素材库、输出目录这类项目的配置一般集中在settings.yaml或.env里。我习惯用settings.yaml管理业务参数用.env管理密钥前者的结构更清晰后者的密钥不会误提交到版本库。配置文件里最重要的三块LLM服务、素材库路径、输出参数。llm: model: your_model_name temperature: 0.3 # 默认温度各stage可在调用时覆盖 max_tokens: 3000 tts: engine: edge-tts rate: -6% pitch: 0Hz material: csv_path: ./materials/index.csv video_dir: ./materials/videos aspect: 9:16 output: resolution: 1080x1920 fps: 25 subtitle_font: 思源黑体material.csv_path指向3.2里的素材索引清单video_dir指向实际视频文件目录这两个路径一定要在启动前确认存在。多数人部署翻车就是栽在这一步源码里默认的素材路径是作者机器上的绝对路径你在自己机器上没建这个目录程序跑到素材匹配阶段才报FileNotFoundError。另外注意output.resolution要和素材匹配时的aspect保持一致竖屏短剧就用1080x1920如果还带fps: 25那第3.3里的转码参数就要跟着对齐任何一处不一致都会在最后合成时暴露。4.3 首次全链路跑通按stage日志逐段确认产物环境装好、配置填完终于可以跑第一次全链路。启动命令通常长这样具体以你手里源码的入口文件为准。source .venv/bin/activate export LLM_API_KEY你的密钥 export LLM_BASE_URLhttps://你的服务端点 python main.py --story 失恋的都市女孩回到乡下老宅在邻居男孩的帮助下修复旧钢琴 \ --output ./demo_out跑起来之后不要盯着屏幕等结果而是按stage日志逐段确认产物。一个设计合理的平台每个阶段结束时都会在输出目录留下中间文件我一般会一边跑一边对照下面的表格检查。阶段应出现的产物怎么确认剧本扩写script.json或script.md打开看是否有完整起承转合角色是否齐全角色场景抽取metadata.json角色表和场景表是否是非空数组分镜生成shots.json/shots.csv每个分镜都有画面描述和台词时长总和接近目标配音合成audio/shot_*.mp3随机播放一两句确认音色对应角色素材匹配materials_used.csv每个分镜都匹配到了素材没有空值视频合成final_episode.mp4完整播放一遍重点看音画是否同步字幕是否错位第一次跑通后立刻把整套流程固化下来。我见过不少团队在demo跑通后直接投入批量生成结果第二集就翻车原因往往是第一次跑的时候漏了某个手动步骤之后每次都手动补。正确做法是把涉及路径和密钥的手动操作全部写进配置文件和启动脚本让后续生成做到一条命令完成。5. 调参避坑与常见问题先看五个参数再对五个故障到这个阶段项目已经能产出成片但质量可能忽高忽低。接下来的问题不再是“能不能跑通”而是“怎么稳定地跑出能看的片子”。我把调试经验分成两类一类是LLM生成参数另一类是工程一致性参数。参数调不对输出的东西就充满玄学色彩同样的prompt今天能用明天就废。5.1 温度、top_p、max_tokens不同stage各用各的参数很多人在整个平台里只设置一个temperature值这是最常见的误解。剧本扩写、信息抽取、分镜切分三类任务的稳定性要求完全不同必须分开设置。我常用的参数组合如下。Stagetemperaturetop_pmax_tokens说明剧本扩写0.80.93000鼓励多样性给故事留发挥空间角色/场景抽取0.10.51000低随机性保证JSON字段稳定分镜切分0.30.71500既要结构化又要画面描述有变化TTS文本修正0.20.5500只做错别字和标点修正不乱改台词抽取类的temperature必须压低到0.1附近否则同一个剧本跑两遍抽出来的角色名可能都不一样。剧本扩写则相反temperature太低会让故事变得干巴巴。top_p的作用是限制候选词范围和temperature叠加使用两个值都调低会让输出更保守。我一般不会让temperature和top_p同时调很高否则长剧本很容易在结尾开始胡言乱语。5.2 一致性参数音色映射表、画幅规格与字幕模板一致性是短剧和单条AI短视频最本质的区别。单条视频可以追求单个镜头的精美短剧必须保证角色声音、画幅、字幕风格在全集里前后统一。我把这部分参数单独维护在一份consistency.yaml里每次生成前加载不改代码。character_voices: 林小雨: {voice: zh-CN-XiaoxiaoNeural, rate: -8%, pitch: 2Hz} 陈默: {voice: zh-CN-YunxiNeural, rate: -4%, pitch: -3Hz} video: width: 1080 height: 1920 fps: 25 aspect: 9:16 subtitle: margin_v: 48 font_size: 36 font_style: bold音色表的作用前面已经讲过这里不再重复。画幅和帧率参数必须和素材库保持一致如果你的素材库里混着横屏和竖屏宁可先花半天时间把所有素材统一转码也不要指望ffmpeg在合成时智能处理。字幕模板里的margin_v和font_size是经验值1080x1920的分辨率下margin_v: 48能让字幕避开底部进度条区域font_size: 36在手机上看刚好合适。这些参数定了之后整个项目周期内不要再改改一次就意味着所有已生成的片子都要重新烧字幕。5.3 五个必踩的坑现象、原因、解决下面这五个问题是我在搭这套平台和帮朋友排查时反复遇到的故障按“现象→原因→解决”记录。它们不是小概率事件几乎是每套这类系统都会撞上的坎。坑一分镜JSON解析失败脚本报JSONDecodeError。 现象是分镜阶段偶尔返回截断的JSON数组没闭合。原因通常是max_tokens不够模型输出到一半被截断。解决方法是先做一次“失败重试”把temperature临时再调低0.1重跑一次连续失败两次就把max_tokens从1500加到2000。不要在代码里用正则去“修复”半个JSON那只会制造更隐蔽的脏数据。坑二素材匹配结果为空或者匹配到完全无关的画面。 现象是“老宅客厅”匹配到了海边沙滩。原因是素材标签不全或者粗筛条件太苛刻。解决方法是先看3.2里的score函数确认分镜里的location_tags和素材CSV里的location字段用的是同一套词汇表其次检查画幅否决逻辑如果只有横屏素材却要求9:16结果必然为空。想省钱的做法是给匹配失败的分镜配一个通用转场素材而不是让整个流程直接崩溃。坑三成片音画不同步前面几秒正常后面越来越对不上。 现象是配音在说话画面已经切到下一个镜头或者反过来。原因基本可以锁定在素材源帧率不统一上。解决方法是严格执行3.3的“先转码再concat”每个片段进拼接前都过一遍-r 25的参数。不要偷懒省掉统一转码这一步concat对时间戳的要求非常严格哪怕只有一个素材是30fps整个时间轴都会漂移。坑四字幕和配音文本对不上TTS读的是A句字幕显示B句。 现象是画面里字幕明明写着“我回来了”配音念出来的却是“我到家了”。原因是台词在LLM生成分镜后又被某个后处理环节改写了一次两个模块各自拿着不同版本的文本。解决方法是全平台只从分镜JSON里的line字段读取台词文本TTS和字幕生成都引用同一个字段任何文本修正都写回line后再继续。坑五ass字幕显示乱码中文字幕变成方块。 现象是视频里字幕不显示或显示乱码英文正常中文全挂。原因是ass文件缺中文字体声明或文件编码不是UTF-8。解决方法是生成ass时强制用UTF-8编码写文件并在Style行里显式指定字体名比如Fontname: 思源黑体。如果你在生成ass时用了open()函数记得加上encodingutf-8Windows环境下漏了这个参数是必炸的。6. 进阶验证用一份输出自检清单守住成片质量底线平台能批量出片之后下一个问题是怎么在“没时间逐条看”的前提下保证质量。我的做法是在每次批量生成后跑一个自检脚本它不管剧情好不好看只负责核对三个可以量化的硬指标视频时长是否与分镜计划吻合、TTS音频总时长与视频时长误差是否在容忍范围内、字幕条数是否等于分镜台词条数。import json, os, subprocess def get_duration(path: str) - float: r subprocess.run( [ffprobe, -v, quiet, -print_format, json, -show_format, path], capture_outputTrue, textTrue, ) return float(json.loads(r.stdout)[format][duration]) def self_check(output_dir: str): shots json.load(open(os.path.join(output_dir, shots.json), encodingutf-8)) video_dur get_duration(os.path.join(output_dir, final_episode.mp4)) audio_dur get_duration(os.path.join(output_dir, tts_audio.mp3)) line_count sum(1 for s in shots if s.get(line)) checks { video_audio_diff_lt_1s: abs(video_dur - audio_dur) 1.0, subtitle_count_match: line_count len(os.listdir(os.path.join(output_dir, subtitle))), video_len_in_range: int(video_dur) 150, # 按实际目标时长调整 } for name, ok in checks.items(): print(f[{PASS if ok else FAIL}] {name})这三个指标只能筛掉“物理层面不达标”的片子剧情质量还是得靠人看。我习惯在批量生成前先跑一条30秒的demo片总时长比正式集短一大截但走完全部stage确认音色表、画幅、字幕样式都正常后再放开全量生成。这套流程帮我避免了太多次批量产出一堆废片的尴尬——一旦正式集用了新音色或者新字体自检脚本是查不出来的只有demo能暴露。希望帮到你。本文还有配套的精品资源点击获取