首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >零积分本地搭建短视频自动生产线:edge-tts + PIL + ffmpeg 实战

零积分本地搭建短视频自动生产线:edge-tts + PIL + ffmpeg 实战

原创
作者头像
不着急的老伙计
修改2026-08-13 19:26:40
修改2026-08-13 19:26:40
1000
举报
文章被收录于专栏:WorkBuddy实战WorkBuddy实战

本文记录我用 WorkBuddy 把"竖屏短视频 / 图文视频 / 口播混剪"做成一条可批量生产的本地流水线的完整过程。输入一份 JSON 脚本,输出指定比例(1080×1920 竖屏或 1920×1080 横屏)的 MP4。全程零积分、不依赖 ComfyUI,核心依赖只有三个免费组件:edge-tts(配音)、Pillow(程序化背景与文字)、ffmpeg(合成转场)。包含关键代码、架构设计,以及几个真实踩坑(转场时长夹紧、配音 rate 格式、BGM 空壳 bug)。适合想用本地方案做视频自动化的开发者参考。

一、为什么不用现成工具

做批量短视频内容(竖屏口播、图文视频、产品混剪等),主流方案要么上 ComfyUI 出图再剪,要么买云端配音和剪辑服务。我的约束是两个:

  • 零积分:本机 WorkBuddy 的积分很紧,不能默认调任何积分生图 / 云端 TTS;
  • 可批量、可换类型:一次准备几十个脚本,能一键全转成 MP4;同一套代码要能产出竖屏、横屏、不同风格的视频,而不是写死某一种。

于是思路变成:把"出图、配音、合成"三步全用免费本地组件拼起来,任何外部渠道失败就回落到程序化兜底,保证出片不中断。通过 JSON 字段切换比例、字体、音色、背景来源,实现"一套流水线、全类型输出"。

最终架构是一条极简的流水线:

JSON 脚本 → 逐 scene 出背景图(PIL/免费图源) + 叠加标题/副标题文字 → 逐 scene 生成配音(edge-tts) → 每条 scene 用 zoompan 做 Ken Burns 运镜 → ffmpeg xfade 串联转场 + 混 BGM/音效 → 输出 指定比例 MP4(竖屏 1080×1920 / 横屏 1920×1080)

全类型视频自动生产线架构总览:JSON → Scene → PIL/edge-tts → zoompan → xfade → MP4,底部为 bg 路由键的多源降级策略。

二、核心设计:用一份 JSON 驱动一切

整个流水线由一份 JSON 脚本驱动,每条 scene 生成"一张背景图 + 一段配音 + 一组文字层",scene 之间用转场串联。脚本示例:

代码语言:txt
复制
{
  "badge": "系列名 · 第01期",
  "tag": "分类标签 · 一句话定位",
  "voice": "zh-CN-YunxiNeural",
  "ratio": "vertical",
  "scenes": [
    {
      "big": "主标题文字",
      "sub": "副标题 / 补充说明",
      "voice": "这里是这一屏要配音的文案……",
      "bg": "ink",
      "big_font": "li"
    }
  ]
}

ratio 字段控制输出比例(vertical 竖屏 / horizontal 横屏),分辨率由它推导,改一行就能换画幅。

bg 字段是整套系统设计的重点——它不是一个单纯的文件路径,而是一个路由键,背后是一套可降级的多源取图策略:

  1. plain / gradient / ink / paper —— 直接用 Pillow 程序化生成风格化背景(纯色 / 渐变 / 水墨晕染 / 宣纸纹理等,零依赖、永不失联,作为默认兜底);
  2. pexels:关键词 —— 调 Pexels 免费 API 取真实摄影素材;
  3. shetu:关键词 —— 走摄图网 VIP 图源;
  4. gen:关键词 —— 调本地 SD 出图;
  5. 任意本地图片绝对路径 —— 用自有素材。

关键约束:任何外部渠道失败都自动回落到 Pillow 程序化背景。这样哪怕 Pexels 限流、摄图网 cookie 过期,流水线也不会断。把"兜底"设为默认、把"增强"设为可选,是这套系统能稳定跑起来的设计根基。

水墨晕染风格(bg=ink)实际渲染效果:暖色调渐变背景 + 山峦剪影 + 红日 + 书法大字,由 Pillow 程序化生成。

青绿山水风格(bg=gradient)实际渲染效果:同一套代码仅改 bg 路由键即可切换背景风格。

三、关键实现片段

1. 配音:edge-tts 的参数透传与重试

edge-tts 的 Communicate 原生支持 rate / volume / pitch,但免费端点偶发 NoAudioReceived 抖动。下面是实际封装(含递增退避重试)——这部分看似简单,却是后面做多音色和情感化的基础:

代码语言:txt
复制
async def tts(text, out_path, voice, rate="+0%", volume="+0%", pitch="+0Hz", style=None):
    import edge_tts
    backoff = [1, 2, 3, 5, 8]
    for attempt in range(len(backoff)):
        try:
            if style:
                ssml = build_ssml(text, voice, style, rate, volume, pitch)
                comm = edge_tts.Communicate(ssml, voice=voice)
            else:
                comm = edge_tts.Communicate(text, voice=voice,
                                            rate=rate, volume=volume, pitch=pitch)
            await comm.save(out_path)
            return
        except Exception as e:
            if "NoAudioReceived" not in str(e):
                raise
            await asyncio.sleep(backoff[attempt])
    raise last_err

情感风格(SSML 的 mstts:express-as)需要套一层 SSML 包裹,且中文音色支持度差异很大,后面踩坑章节会讲怎么优雅降级。

2. 运镜:用 zoompan + 余弦缓动做 Ken Burns

静帧背景要"有呼吸感",靠 ffmpeg 的 zoompan 做缓慢推进 + 轻微视差平移。难点是缓动——线性推近很机械,改用 sine ease-in-out:

代码语言:txt
复制
eased = f"(0.5-0.5*cos(PI*on/{frames}))"   # 先慢→快→慢
z_expr = f"1.0+0.14*{eased}"               # 镜头推近 1.0 → 1.14
x_expr = f"iw/2-(iw/zoom/2)+({eased})*50-25"
y_expr = f"ih/2-(ih/zoom/2)+({eased})*30-15"
vf = (f"zoompan=z='{z_expr}':d={frames}:s={W}x{H}:fps={FPS}:"
      f"x='{x_expr}':y='{y_expr}'")

0.5-0.5*cos(PI*p) 这条表达式同时用在运镜和转场上,是整套"电影感"的来源。W×H 由 JSON 的 ratio 字段推导,竖屏取 1080×1920、横屏取 1920×1080,代码里一处切换即可。

3. 合成:xfade 串联 + 自动拧紧转场时长

多条 scene 用 ffmpeg xfade 串联。这里有个必须处理的实际问题:转场时长不能大于相邻任意一条 scene 的时长,否则 ffmpeg 直接报错。所以每条转场时长都要被相邻短镜头"夹紧":

代码语言:txt
复制
xfades = []
for k in range(n - 1):
    xf = min(xfade, min(durs[k], durs[k + 1]) - 0.15)
    xfades.append(max(0.2, xf))

offset 也要随拧紧后的转场时长重新累积,否则画面和配音会对不上。最后音频用 concat 串起来,BGM / 音效通过 amix 叠加。

整套命令最终是一个 -filter_complex 字符串,把视频 xfade 链、音频 concat 链、可选的 BGM 链和音效链拼在一起。

四、踩坑实录

坑 1:edge-tts 的 rate 只认带符号百分比

文档里写"支持语速",但 edge-tts 7.2.8 实际只接受 +N% / -N% 格式。写 "fast"、"slow" 这类标签,或者裸写 "50%",都会直接 ValueError。JSON 里必须严格写 "+20%"、"-15%"。这个坑第一次踩的时候,批量脚本整批挂掉,排查了半天才定位到是格式问题。

坑 2:BGM 是"空壳"——声明了但没实现

早期 concat_xfade 的函数签名声明了 bgm 参数,函数体却从未引用它——音频链只做了 concat 把各 scene 配音串起来,BGM 字段写了也完全无声。修复方式是真正接入 ffmpeg 混音链:

代码语言:txt
复制
[bgm] aloop=loop=-1[bgml];
[bgml] atrim=0=TOTAL[bgmt];
[bgmt] volume=0.3[bgmm];
[bgmm] aformat=sample_rates=44100:channel_layouts=mono[bgmma];
[aout][bgmma] amix=inputs=2:duration=first[aout2]

注意两个细节:① duration=first 以配音长度为准,BGM 不足则循环、超出则裁剪;② 配音是 44100 mono,BGM 进 amix 前必须 aformat 重采样归一,否则报错或音质异常。

坑 3:情感风格中文支持度不统一

SSML 的 mstts:express-as 不是所有音色都支持。实测(用脚本逐一对音色跑单句探测)结果很分裂:

  • 晓晓 / 云扬 / 云间 / 小艺 / 云霞:18/18 全支持
  • 云希:缺 empathetic / angry / affectionate
  • 云野 / 小莫:完全不支持 express-as

如果盲信文档给不支持的音色指定风格,要么报错要么静默忽略。所以代码里做了一张支持度映射表,遇到不支持的组合直接 WARN 退回普通配音,而不是中断整条流水线。这对"同一套流水线要适配不同语种 / 不同音色"的场景尤其重要。

坑 4:转场不能太"硬"

xfade 自带 slideup / wipeleft 这类方向性擦除,但用在图文静帧 / 口播截图上很生硬。最终默认只用一组"柔化类"交叉溶解(dissolve / fade / distance)循环混搭,方向性转场只在单条 scene 显式指定时才启用。视觉上顺滑很多,也更适合大多数短视频节奏。

五、免费成图的降级优先级

再次强调设计根基:背景取图严格免费优先,绝不默认调积分生图。优先级从高到低:

  1. Pillow 程序化(零依赖兜底,默认,永不失联)
  2. Pexels 免费 API(真实摄影素材)
  3. 摄图网 VIP(各类素材丰富)
  4. 本地 SD 出图(需配置本地生图服务地址)

任意一级失败自动回落上一级,最差情况也能用 Pillow 出片。把兜底做成默认,增强做成可选,是本地自动化系统稳定性最高的设计原则——这比追求"每一帧都精美"务实得多。

宣纸纹理风格(bg=paper)实际渲染效果:最轻量的纯色程序化背景,适合内容密度高的场景。

六、批量工作流与合规

一条命令批量转出整个目录(竖屏、横屏脚本混在同一个目录也能分别推导比例):

代码语言:txt
复制
python pipeline.py batch -i templates/ -o output/

合规上也有红线:标题/配音避免"最 / 第一 / 绝对"等极限词;不宣称医疗功效、金融收益、算命改运等需资质的内容;涉及品牌 / 人物需确认授权。把内容定位在"知识分享 / 生活记录 / 产品介绍"等合规类目,规避需要特殊认证的赛道。

七、经验总结

这套生产线的核心设计有三条,按重要性排序:

  1. JSON 驱动 + 路由键:把"出图来源、音色、转场、语速、输出比例"全部变成脚本字段,改效果不用改代码,一套流水线覆盖竖屏 / 横屏 / 多风格;
  2. 兜底即默认:任何外部依赖都可能失败,把免费 / 本地 / 程序化的方案设为默认兜底,增强项做成可选项;
  3. 真实边界优先于完美:转场时长夹紧、采样率归一、风格白名单降级——这些"不优雅但必须"的处理,决定了系统能不能稳定批量跑,比花哨功能重要。

如果你也想搭类似的本地视频流水线,建议先只用 Pillow + edge-tts + ffmpeg 三个组件把单条跑通(含竖屏和横屏各一条),确认出片没问题,再加 Pexels / SD / BGM 这些增强层。一层一层加,比一步到位稳。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、为什么不用现成工具
  • 二、核心设计:用一份 JSON 驱动一切
  • 三、关键实现片段
    • 1. 配音:edge-tts 的参数透传与重试
    • 2. 运镜:用 zoompan + 余弦缓动做 Ken Burns
    • 3. 合成:xfade 串联 + 自动拧紧转场时长
  • 四、踩坑实录
    • 坑 1:edge-tts 的 rate 只认带符号百分比
    • 坑 2:BGM 是"空壳"——声明了但没实现
    • 坑 3:情感风格中文支持度不统一
    • 坑 4:转场不能太"硬"
  • 五、免费成图的降级优先级
  • 六、批量工作流与合规
  • 七、经验总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档