构建工业级短视频生成流水线:Playwright + FFmpeg 自动化指南

完全自动化管线:TTS 旁白 → 精准字幕 → Playwright 录制 → FFmpeg 混音/字幕烧录/音量标准化

📑 流程目录

  • [🛠️ 环境准备](#🛠️ 环境准备)
  • [📝 阶段1 · 准备工作(旁白/音频/字幕)](#📝 阶段1 · 准备工作(旁白/音频/字幕))
  • [🎬 阶段2 · 录制无声视频(Playwright)](#🎬 阶段2 · 录制无声视频(Playwright))
  • [🎵 阶段3 · 混音(合并旁白+视频)](#🎵 阶段3 · 混音(合并旁白+视频))
  • [🎞️ 阶段4 · 烧录字幕(FFmpeg 硬字幕)](#🎞️ 阶段4 · 烧录字幕(FFmpeg 硬字幕))
  • [🔊 阶段5 · 音量标准化与防爆音](#🔊 阶段5 · 音量标准化与防爆音)
  • [📖 FFmpeg 命令参数详解](#📖 FFmpeg 命令参数详解)
  • [❓ 常见问题与解决方案](#❓ 常见问题与解决方案)
  • [✅ 录制前/后专业检查清单](#✅ 录制前/后专业检查清单)
  • [🔧 工具速查表](#🔧 工具速查表)
  • [📌 总结与关键成功因素](#📌 总结与关键成功因素)

🛠️ 环境准备

必需工具安装

复制代码
# edge-tts:微软免费神经网络语音,生成旁白音频
pip install edge-tts

# mutagen:精确读取MP3时长(毫秒级)
pip install mutagen

# playwright:无头浏览器录制HTML可视化动画为视频
pip install playwright
playwright install chromium

# imageio-ffmpeg:自动管理ffmpeg二进制,跨平台混音/字幕
pip install imageio-ffmpeg

标准目录结构

复制代码
project_directory/
├── visualization.html          # 可视化动画(含CSS+JS时间轴)
├── narration_script.md         # 旁白文本草稿(分段书写)
├── subtitle_001.mp3 ~ NNN.mp3  # TTS逐条生成
├── narration_all.mp3           # concat拼接后完整旁白
├── subtitle_timings.json       # mutagen测量每条真实时长
├── subtitles.srt               # 自动生成SRT字幕
├── record_video.py / mix_audio.py / burn_subtitles.py / boost_volume.py
├── video_nosub/                # playwright原始webm
├── video_mixed.mp4             # 混音后(无字幕)
├── video_final.mp4             # 烧录字幕后
└── video_final_loud.mp4        # 音量扩大最终版

📝 阶段1:准备工作

目标:生成旁白音频 + 毫秒级精确SRT字幕。

Step 1.1 编写旁白文本

每段对应一个可视化场景,时长≤30秒,旁白文本与字幕内容100%一致。示例:

复制代码
# 旁白脚本
## 第1段(0~5秒)
开场白文本。
## 第2段(5~12秒)
第二段旁白内容。

Step 1.2 生成旁白音频 (edge-tts)

推荐语音:zh-CN-YunxiNeural(男声沉稳) / zh-CN-XiaoxiaoNeural(女声自然)

复制代码
edge-tts --text "文本" --voice zh-CN-YunxiNeural --rate "+0%" --write-media subtitle_001.mp3

批量生成脚本(基于已准备的文本列表):

复制代码
import subprocess, json
with open('subtitle_timings.json', encoding='utf-8') as f:
    texts = json.load(f)['subtitles_text']
for i, txt in enumerate(texts, 1):
    subprocess.run(['edge-tts','--text',txt,'--voice','zh-CN-YunxiNeural','--write-media',f'subtitle_{i:03d}.mp3'])

Step 1.3 测量真实时长 (mutagen)

复制代码
from mutagen.mp3 import MP3
import json, glob
timings = []
for mp3 in sorted(glob.glob("subtitle_*.mp3")):
    timings.append(int(MP3(mp3).info.length * 1000))
data = {"subtitle_timings": timings, "subtitles_text": [], "total_duration_ms": sum(timings)}
with open("subtitle_timings.json", 'w', encoding='utf-8') as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

Step 1.4 生成SRT字幕

复制代码
import json
def ms_to_srt(ms):
    h = ms//3600000
    m = (ms%3600000)//60000
    s = (ms%60000)//1000
    mil = ms%1000
    return f"{h:02d}:{m:02d}:{s:02d},{mil:03d}"

with open("subtitle_timings.json", encoding='utf-8') as f:
    d = json.load(f)
durations, texts = d["subtitle_timings"], d["subtitles_text"]
cur = 0
lines = []
for idx, (dur, txt) in enumerate(zip(durations, texts), 1):
    start, end = cur, cur+dur
    lines.append(str(idx))
    lines.append(f"{ms_to_srt(start)} --> {ms_to_srt(end)}")
    lines.append(txt)
    lines.append('')
    cur += dur
with open("subtitles.srt", 'w', encoding='utf-8') as f:
    f.write('\n'.join(lines))

Step 1.5 抽听验证多音字

逐条播放MP3,修正错误后重新生成对应音频。


🎬 阶段2:录制无声视频 (Playwright)

HTML要求 :字幕容器必须隐藏 (display: none),时间轴驱动推荐 setInterval(50ms)+performance.now() 并设置 VISUAL_LEAD_MS=250

录制脚本 record_video.py

复制代码
import asyncio, os, json, sys
from pathlib import Path
from playwright.async_api import async_playwright

if hasattr(sys.stdout, 'reconfigure'): sys.stdout.reconfigure(encoding='utf-8')

async def record():
    out_dir = Path("video_nosub")
    out_dir.mkdir(exist_ok=True)
    for f in out_dir.glob("*.webm"): f.unlink()
    with open("subtitle_timings.json") as f:
        dur_sec = json.load(f)["total_duration_ms"] / 1000 + 2
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True, args=['--disable-gpu'])
        context = await browser.new_context(viewport={'width':1920,'height':1080},
                                            record_video_dir=str(out_dir),
                                            record_video_size={'width':1920,'height':1080})
        page = await context.new_page()
        await page.goto(f"file://{os.path.abspath('visualization.html')}", wait_until='networkidle')
        await asyncio.sleep(1)
        # 验证字幕容器隐藏
        hidden = await page.evaluate("() => window.getComputedStyle(document.getElementById('subtitleContainer')).display === 'none'")
        if not hidden: print("⚠️ 字幕容器未隐藏")
        await page.evaluate("window.startTimeline()")
        print(f"📹 录制中... {dur_sec:.1f} 秒")
        await asyncio.sleep(dur_sec)
        await context.close()
        await browser.close()
        videos = list(out_dir.glob("*.webm"))
        return str(videos[0]) if videos else None

if __name__ == "__main__":
    video_path = asyncio.run(record())
    print(f"无声视频: {video_path}")

🎵 阶段3:混音

3.1 合并旁白音频

复制代码
# 生成filelist.txt
for f in subtitle_*.mp3; do echo "file '$f'" >> filelist.txt; done
ffmpeg -f concat -safe 0 -i filelist.txt -c copy narration_all.mp3

3.2 混音到视频

复制代码
import subprocess, imageio_ffmpeg
def mix(silent_video):
    ffmpeg = imageio_ffmpeg.get_ffmpeg_exe()
    cmd = [ffmpeg, "-i", silent_video, "-i", "narration_all.mp3",
           "-c:v", "libx264", "-c:a", "aac", "-shortest", "video_mixed.mp4"]
    subprocess.run(cmd, check=True)
    print("✅ 混音完成: video_mixed.mp4")

mix("video_nosub/recorded.webm")  # 替换实际路径

🎞️ 阶段4:烧录字幕

复制代码
import subprocess, os, imageio_ffmpeg
def burn(input_video="video_mixed.mp4"):
    ffmpeg = imageio_ffmpeg.get_ffmpeg_exe()
    srt_path = os.path.abspath("subtitles.srt").replace('\\', '/')
    vf = f"subtitles='{srt_path}':force_style='FontSize=20,PrimaryColour=&Hffffff,OutlineColour=&H000000,BackColour=&H80000000,Outline=1,Shadow=0,MarginV=20'"
    cmd = [ffmpeg, "-i", input_video, "-vf", vf, "-c:a", "copy", "video_final.mp4"]
    subprocess.run(cmd, check=True)
    print("✅ 字幕烧录完成: video_final.mp4")
burn()

🔊 阶段5:音量标准化

复制代码
import subprocess, imageio_ffmpeg
def boost_volume(in_file="video_final.mp4"):
    ffmpeg = imageio_ffmpeg.get_ffmpeg_exe()
    out = in_file.replace(".mp4", "_loud.mp4")
    cmd = [ffmpeg, "-i", in_file, "-af", "volume=2.0,alimiter=limit=0.95", "-c:v", "copy", out]
    subprocess.run(cmd, check=True)
    print(f"✅ 最终响亮视频: {out}")
boost_volume()

📖 FFmpeg 命令参数详解

通用参数

参数 说明
-i file 指定输入文件
-c:v libx264 H.264视频编码
-c:a aac AAC音频编码
-c:v copy 视频流直接复制(避免二次编码)
-shortest 以最短输入流结束为止

常用视频滤镜 (-vf)

滤镜 用途
subtitles=file.srt 烧录SRT字幕
scale=1920:1080 强制分辨率

音频滤镜 (-af)

滤镜 作用
volume=2.0 音量放大2倍
alimiter=limit=0.95 限制峰值防爆音
loudnorm=I=-16 EBU R128响度标准化

❓ 常见问题与解决方案

  • 音画不同步 → 采用setInterval(50ms)轮询 + performance.now(),VISUAL_LEAD_MS微调(200~300ms),并用mutagen保证音频真实时长。
  • 双层字幕 → 确保HTML中字幕容器display:none;录制前用JS验证隐藏状态。
  • 多音字误读 → 同义替换或加入同音字注释,重新生成单条音频。
  • Windows GBK编码错误 → 脚本开头添加 sys.stdout.reconfigure(encoding='utf-8')

✅ 检查清单

📌 录制前必查项

  • 旁白文本结构化(每段≤30s)且已排查多音字
  • 旁白文本 = 字幕文本(100%匹配)
  • 所有 subtitle_xxx.mp3 已生成
  • mutagen 测量真实时长并更新 subtitle_timings.json
  • subtitles.srt 已基于真实时长生成
  • HTML 中 #subtitleContainer 样式为 display: none
  • 录制脚本无额外sleep(页面启动等待除外)
  • 可视化页面使用 setInterval+performance.now 驱动时间轴
  • 已配置 VISUAL_LEAD_MS 偏移量

📤 输出前最终校验

  • 混音阶段使用 -c:v libx264(确保视频流编码)
  • 字幕烧录使用FFmpeg subtitles滤镜,样式清晰
  • 验证字幕同步(播放video_final.mp4)
  • 音量扩大或 loudnorm 标准化,防爆音
  • 最终文件命名为 *_loud.mp4 便于区分

🔧 工具速查表

工具 用途 安装
edge-tts 高质量TTS(中文神经语音) pip install edge-tts
mutagen 读取音频时长 pip install mutagen
playwright 浏览器自动化录屏 pip install playwright && playwright install chromium
imageio-ffmpeg Python调用ffmpeg pip install imageio-ffmpeg
ffmpeg 混音/字幕烧录/音量 由imageio-ffmpeg自动提供

📌 总结与关键成功因素

五阶段自动化闭环: 准备(旁白+SRT) → 无声视频录制 → 混音 → 烧录字幕 → 音量标准化。

🔑 核心成功要素: mutagen绝对时长、setInterval时间轴驱动、VISUAL_LEAD_MS同步补偿、隐藏HTML原生字幕、FFmpeg滤镜烧录硬字幕。遵循本指南可达到工业级短视频批量生产质量。

相关推荐
Ameilide1 小时前
Linux 应用软件编程 文件编程(IO)
linux·运维·服务器
米核AI易山1 小时前
扣子工作流实战:多节点串联打造 AI 内容自动化流水线
人工智能·自动化·coze·扣子工作流·米核ai易山
枕星而眠1 小时前
Linux IO多路复用:select、poll、epoll 核心原理与进阶实战
linux·运维·服务器·c++·后端
Rain5091 小时前
GitLab-Runner + AI 代码审查服务 + 远程大模型 全套部署运维实战
linux·运维·人工智能·python·ci/cd·gitlab·ai编程
夏夏夏果1 小时前
部署视频生成模型-美团LongCat-Video
ai·音视频
Black蜡笔小新1 小时前
零代码自动化企业私有化AI训练推理一体工作站DLTM训推一体化助力企业自主掌控AI能力
运维·人工智能·自动化
txg6662 小时前
WildSync:通过Wild API 使用恢复实现自动化 Fuzzing Harness 合成
运维·深度学习·网络安全·自动化
zlinear数据采集卡2 小时前
模拟输入限流保护电路深度解析:从理论原理到ZLinear采集卡的实战设计
c语言·单片机·嵌入式硬件·fpga开发·自动化
信创工程师-小杨2 小时前
OpenEuler系统如何升级OpenSSh10.3P1版本
linux·运维·服务器