Pixelle-Video 源码解析 #17:TTS 语音生成:Edge-TTS、Index-TTS 如何接入?

前面第 15 篇和第 16 篇,我们已经分析了 Pixelle-Video 的 AI 视频片段生成,以及图生视频、动作迁移、数字人口播这类复杂视频能力。

这一篇继续往下看一个非常关键的模块:

TTS 语音生成。

在 Pixelle-Video 中,TTS 不只是"把文字读出来"这么简单。它实际上决定了每个视频片段的节奏、时长和音画同步。

因为 Pixelle-Video 的每个分镜都是这样处理的:

text 复制代码
narration
    ↓
TTS 生成 audio_path
    ↓
读取音频时长 frame.duration
    ↓
生成图片或视频素材
    ↓
HTML 模板合成字幕画面
    ↓
音频 + 画面合成视频片段

所以,TTS 是连接"文本内容"和"视频节奏"的核心环节。

这一篇我们重点分析:

text 复制代码
Edge-TTS 如何本地接入?
Index-TTS 这类声音克隆方案如何通过 ComfyUI workflow 接入?
TTS 生成的 audio_path 如何影响后续视频时长?

一、TTS 在 Pixelle-Video 中负责什么?

Pixelle-Video 的完整视频生成流程可以简化成:

text 复制代码
文案生成
    ↓
分镜规划
    ↓
TTS 配音
    ↓
图片 / 视频素材生成
    ↓
模板渲染
    ↓
视频片段合成
    ↓
最终拼接

其中 TTS 负责把每个 StoryboardFrame 中的 narration 转成音频文件。

源码中 FrameProcessor 的注释明确说明,它处理单个 frame 的完整流程:TTS、图像生成、画面合成、视频片段生成;并且特别强调一个关键特性:TTS 音频时长会传给视频生成 workflow,用来保证音频和视频同步。

这说明 TTS 在 Pixelle-Video 里有两个作用:

text 复制代码
第一,生成旁白音频。
第二,决定当前分镜的视频时长。

如果没有 TTS,Pixelle-Video 仍然可以生成图片,但很难自动决定每个片段应该持续多久。

二、TTSService:语音生成的统一入口

Pixelle-Video 的 TTS 核心服务是:

text 复制代码
pixelle_video/services/tts_service.py

源码文件开头写得很清楚:TTSService 支持 local 和 ComfyUI 两种推理方式。local 模式主要走 Edge TTS;ComfyUI 模式则通过工作流执行,比如 Edge-TTS workflow、Index-TTS workflow 或其他自定义 TTS workflow。

它的核心调用入口是 __call__()

python 复制代码
async def __call__(
    self,
    text: str,
    workflow: Optional[str] = None,
    comfyui_url: Optional[str] = None,
    runninghub_api_key: Optional[str] = None,
    voice: Optional[str] = None,
    speed: Optional[float] = None,
    inference_mode: Optional[str] = None,
    output_path: Optional[str] = None,
    **params
) -> str:
    ...

这个接口说明,TTSService 支持的核心参数包括:

text 复制代码
text:要转语音的文本
workflow:ComfyUI TTS 工作流
voice:音色
speed:语速
inference_mode:local 或 comfyui
output_path:音频输出路径
params:额外 workflow 参数

所以,上层 FrameProcessor 不需要知道当前用的是 Edge-TTS 还是 Index-TTS。

它只需要调用:

python 复制代码
audio_path = await self.core.tts(**tts_params)

最后拿到一个音频路径即可。

这就是 TTSService 的价值:

把不同语音方案统一封装成 text → audio_path。

三、两种 TTS 路线:local 和 comfyui

Pixelle-Video 的 TTSService 里有一个非常关键的分支:

python 复制代码
mode = inference_mode or self.config.get("inference_mode", "local")

if mode == "local":
    return await self._call_local_tts(...)
else:
    return await self._call_comfyui_workflow(...)

源码中就是通过 inference_mode 判断当前走本地 Edge TTS,还是走 ComfyUI workflow。

这意味着 Pixelle-Video 的 TTS 有两条路线:

text 复制代码
local 模式:
    text
        ↓
    Edge-TTS
        ↓
    output.mp3

comfyui 模式:
    text
        ↓
    TTS workflow
        ↓
    ComfyKit
        ↓
    ComfyUI / RunningHub
        ↓
    output.mp3 / wav / flac

这两条路线最终都返回 audio_path

所以从后续视频合成角度看,它们是统一的。

四、配置层:TTS 配置放在哪里?

Pixelle-Video 的 TTS 配置主要放在 comfyui.tts 下。

config.example.yaml 中,TTS 默认工作流是:

yaml 复制代码
comfyui:
  tts:
    default_workflow: selfhost/tts_edge.json

同一个配置文件中还包含 comfyui_urlcomfyui_api_keyrunninghub_api_keyrunninghub_concurrent_limit 等字段。也就是说,如果 TTS 走 ComfyUI 或 RunningHub workflow,就会复用前面几篇讲过的 ComfyKit / workflow 执行体系。

不过在 schema 中,TTS 配置已经被进一步拆成两部分:

python 复制代码
class TTSLocalConfig(BaseModel):
    voice: str = "zh-CN-YunjianNeural"
    speed: float = 1.2

class TTSComfyUIConfig(BaseModel):
    default_workflow: Optional[str] = None

class TTSSubConfig(BaseModel):
    inference_mode: str = "local"
    local: TTSLocalConfig
    comfyui: TTSComfyUIConfig

源码中 TTSLocalConfig 定义了本地 Edge TTS 的默认音色和语速,TTSSubConfig 则定义了 inference_mode,取值思路是 local 或 comfyui。

这说明 Pixelle-Video 的 TTS 配置方向是:

text 复制代码
local:
    适合 Edge-TTS,配置 voice 和 speed。

comfyui:
    适合 Index-TTS、声音克隆、自定义 TTS workflow。

五、Edge-TTS:最轻量的本地语音路线

先看 local 模式。

TTSService._call_local_tts() 的职责是调用 Edge TTS。源码中它会先读取本地配置:

python 复制代码
local_config = self.config.get("local", {})
final_voice = voice or local_config.get("voice", "zh-CN-YunjianNeural")
final_speed = speed if speed is not None else local_config.get("speed", 1.2)
rate = speed_to_rate(final_speed)

然后调用 edge_tts(),把 textvoicerateoutput_path 传进去,最后返回生成的音频路径。

这条路线可以理解成:

text 复制代码
frame.narration
    ↓
TTSService._call_local_tts()
    ↓
voice + speed
    ↓
speed_to_rate()
    ↓
edge_tts()
    ↓
output.mp3

Edge-TTS 的好处是简单。

它不需要你自己部署语音模型,也不需要本地显卡。

对普通用户来说,它是最容易跑通的 TTS 方案。

六、speed_to_rate:Pixelle-Video 如何处理语速?

Edge-TTS 使用的语速参数不是 1.2 这种倍速,而是 +20% 这种 rate 字符串。

所以 Pixelle-Video 在 tts_voices.py 中提供了一个工具函数:

python 复制代码
def speed_to_rate(speed: float) -> str:
    percentage = int((speed - 1.0) * 100)
    sign = "+" if percentage >= 0 else ""
    return f"{sign}{percentage}%"

它会把倍速转换成 Edge-TTS 能理解的 rate。源码注释中给出的例子是:1.0 → +0%1.2 → +20%0.8 → -20%

所以:

text 复制代码
tts_speed = 1.0  →  rate = +0%
tts_speed = 1.2  →  rate = +20%
tts_speed = 0.8  →  rate = -20%

这个设计很实用。

因为对用户来说,1.2x 更容易理解;

对 Edge-TTS 来说,+20% 才是它需要的参数。

Pixelle-Video 在中间做了一层转换。

七、Edge-TTS 支持哪些音色?

Pixelle-Video 在 tts_voices.py 中内置了一批 Edge-TTS 音色配置。

其中包括中文、英文、韩语、法语、葡萄牙语、德语、俄语、土耳其语、西班牙语等音色。中文音色里包括 zh-CN-XiaoxiaoNeuralzh-CN-XiaoyiNeuralzh-CN-YunjianNeuralzh-CN-YunxiNeuralzh-CN-YunyangNeural 等;英文音色里包括 en-US-AriaNeuralen-US-JennyNeuralen-US-GuyNeuralen-GB-SoniaNeuralen-GB-RyanNeural 等。

这些音色配置的作用主要是给 WebUI 和配置系统使用。

可以理解成:

text 复制代码
voice_id:
    真正传给 TTS 的音色 ID。

label_key:
    用于界面多语言显示。

locale:
    语言地区。

gender:
    男声或女声。

这对 Pixelle-Video 很重要。

因为它不是只做中文视频,也不是只做英文视频。

如果文案是中文,可以选择中文音色。

如果文案是英文,可以选择英文音色。

如果后续要支持更多语言,也可以继续扩展这个音色列表。

八、Edge-TTS 工具函数做了哪些容错?

真正调用 Edge-TTS 的函数在:

text 复制代码
pixelle_video/utils/tts_util.py

这个文件中的 edge_tts() 会调用 edge_tts_sdk.Communicate(),收集音频流中的 audio chunk,然后把字节写入输出文件。源码注释中也说明,它返回 MP3 音频数据,并带有自动重试、指数退避、抖动、并发限制和请求间隔,用来处理 401、临时网络问题和 NoAudioReceived 等情况。

它的大致流程是:

text 复制代码
创建 request semaphore
    ↓
请求前等待一个小随机延迟
    ↓
调用 edge_tts_sdk.Communicate()
    ↓
stream 收集 audio chunks
    ↓
合并 audio bytes
    ↓
写入 output_path
    ↓
返回 audio data

这里有两个值得注意的点。

第一,它做了并发限制。

源码中 _MAX_CONCURRENT_REQUESTS = 3,并且每次请求前会等待 _REQUEST_DELAY 加随机抖动。

第二,它对临时错误做了重试。

遇到 401、NoAudioReceived 等情况时,会用指数退避重试。

这说明 Pixelle-Video 对 Edge-TTS 的处理不是简单调一下接口,而是考虑了真实生成中可能遇到的不稳定问题。

九、ComfyUI TTS:Index-TTS 这类方案的接入口

再看 comfyui 模式。

Pixelle-Video 的 README 明确提到,TTS 工作流支持 Edge-TTS、Index-TTS 等方案;语音设置中可以从下拉菜单选择 TTS workflow,系统会扫描 workflows/ 文件夹中的 TTS 工作流,并且支持上传参考音频用于声音克隆,适合 Index-TTS 这类支持声音克隆的 TTS workflow。

这说明 Pixelle-Video 接入 Index-TTS 的方式,不是像 Edge-TTS 那样直接在 Python 里写死调用逻辑,而是通过 ComfyUI workflow。

也就是说,Index-TTS 的定位更像:

text 复制代码
Pixelle-Video
    ↓
TTSService
    ↓
ComfyUI TTS workflow
    ↓
Index-TTS 节点 / 模型
    ↓
输出音频

这样做的好处是:

text 复制代码
Pixelle-Video 不需要直接适配 Index-TTS 的所有内部参数。
Index-TTS 的模型、节点和推理细节交给 ComfyUI workflow。
Pixelle-Video 只负责传 text、voice、speed、ref_audio 等参数。

这和前面讲 ComfyUI 生图、RunningHub 工作流是同一种设计思想。

十、ComfyUI TTS 的执行流程

inference_mode != "local" 时,TTSService 会进入 _call_comfyui_workflow()

源码中这一步会先通过 _resolve_workflow() 解析 workflow,然后构造:

python 复制代码
workflow_params = {"text": text}

如果传入了 voice,就加入:

python 复制代码
workflow_params["voice"] = voice

如果传入了非默认 speed,就加入:

python 复制代码
workflow_params["speed"] = speed

然后把额外参数 params 合并进去。

所以 ComfyUI TTS 的参数结构可以理解成:

text 复制代码
text:
    要朗读的文本。

voice:
    工作流支持的音色或说话人参数。

speed:
    工作流支持的语速参数。

ref_audio:
    参考音频,常用于声音克隆。

其他 params:
    由具体 TTS workflow 决定。

FrameProcessor 中,如果 TTS 模式是 comfyui,它会把 tts_workflowvoice_idtts_speedref_audio 都放入 tts_params,然后统一调用 self.core.tts(**tts_params)

这就是 Index-TTS 一类 workflow 能接入的关键。

十一、ComfyUI / RunningHub TTS 如何真正执行?

_call_comfyui_workflow() 中,Pixelle-Video 会通过核心服务拿到共享的 ComfyKit 实例:

python 复制代码
kit = await self.core._get_or_create_comfykit()

然后根据 workflow 来源决定传什么给 ComfyKit:

text 复制代码
runninghub:
    传 workflow_id

selfhost:
    传 workflow 文件路径

源码中就是这样判断的:如果 workflow_info["source"] == "runninghub" 且包含 workflow_id,就把 workflow_id 作为 workflow_input;否则就把本地 workflow 文件路径作为 workflow_input。最后统一调用 kit.execute(workflow_input, workflow_params)

所以 ComfyUI TTS 的完整执行链路是:

text 复制代码
TTSService
    ↓
_resolve_workflow()
    ↓
workflow_info
    ↓
selfhost:workflow path
runninghub:workflow_id
    ↓
ComfyKit.execute()
    ↓
ComfyUI / RunningHub 执行 TTS workflow
    ↓
返回音频结果

这和图片、视频 workflow 的执行方式是统一的。

十二、TTS workflow 输出如何被识别?

TTS workflow 执行完成后,Pixelle-Video 需要从结果里找到音频文件。

源码中 _call_comfyui_workflow() 会依次检查:

text 复制代码
result.audios
result.files
result.outputs

如果 result.audios 存在,就取第一项。

如果没有 audios,但有 files,就取第一项。

如果都没有,则会在 outputs 字典中查找以 .mp3.wav.flac 结尾的字符串。

如果仍然找不到,就抛出 No audio file generated by workflow

这个设计说明 Pixelle-Video 对 ComfyUI TTS 的结果做了兼容处理。

因为不同 workflow、不同节点、不同平台返回音频的字段可能不同。

有的结果在 audios

有的结果在 files

有的结果藏在 outputs 里。

Pixelle-Video 不要求所有 workflow 返回完全相同结构,而是做了一层兜底查找。

十三、远程音频如何保存到本地?

如果 workflow 返回的是远程 URL,而调用时指定了 output_path,Pixelle-Video 会把音频下载到本地路径。

源码中 _call_comfyui_workflow() 判断:

python 复制代码
if output_path and audio_path.startswith(("http://", "https://")):
    ...

然后用 httpx.AsyncClient() 下载音频,写入 output_path,最后返回本地文件路径。

这点很重要。

因为 RunningHub 这类云端 workflow 很可能返回的是 URL。

但后面的 FrameProcessorVideoService 更适合处理本地文件路径。

所以 Pixelle-Video 做了一步转换:

text 复制代码
云端音频 URL
    ↓
下载到当前任务目录
    ↓
本地 audio_path

这样后面的音频时长读取、视频合成都能统一处理。

十四、FrameProcessor 如何调用 TTS?

TTS 真正被触发的位置在 FrameProcessor._step_generate_audio()

在处理每个 frame 时,FrameProcessor.__call__() 的第 1 步就是生成音频。如果 frame.audio_path 为空,就发出 action="audio" 的进度事件,然后调用 _step_generate_audio();如果已经有音频,则复用已有音频。

_step_generate_audio() 会构造 tts_params

text 复制代码
text = frame.narration
output_path = 当前 frame 的音频输出路径
inference_mode = config.tts_inference_mode

如果是 local 模式,就传入:

text 复制代码
voice
speed

如果是 comfyui 模式,就传入:

text 复制代码
workflow
voice
speed
ref_audio

然后调用:

python 复制代码
audio_path = await self.core.tts(**tts_params)

最后写回:

python 复制代码
frame.audio_path = audio_path
frame.duration = await self._get_audio_duration(audio_path)

源码中可以看到,音频生成后会立即读取音频时长,并写入 frame.duration

这就是 Pixelle-Video 语音生成的核心闭环。

十五、为什么 TTS 要先于图片 / 视频生成?

从流程上看,Pixelle-Video 先生成音频,再生成媒体。

这不是偶然。

对于 image 模式来说,音频时长决定静态图片视频片段的持续时间。

对于 video 模式来说,音频时长还会传给视频生成 workflow,作为目标视频时长。

源码中 _step_generate_media() 会判断当前是 image 还是 video;如果是 video workflow,并且 frame.duration 已经存在,就把 duration 加入 media_params,日志中也说明这个 duration 来自 TTS audio。

所以顺序必须是:

text 复制代码
先生成 TTS
    ↓
拿到音频时长
    ↓
再生成视频素材或视频片段

如果顺序反过来,视频模型就不知道应该生成多长。

这就是 Pixelle-Video 的"旁白驱动节奏"设计。

十六、audio_path 如何影响最终视频片段?

一个 frame 在生成音频后,会进入后续三步:

text 复制代码
_step_generate_media()
_step_compose_frame()
_step_create_video_segment()

对于图片模式:

text 复制代码
composed_image_path + audio_path
    ↓
create_video_from_image()
    ↓
video_segment_path

对于视频模式:

text 复制代码
video_path + composed overlay + audio_path
    ↓
overlay_image_on_video()
    ↓
merge_audio_video(replace_audio=True)
    ↓
video_segment_path

第 15 篇已经讲过,视频模式下 Pixelle-Video 会用 TTS 旁白替换 AI 视频原音频。这样可以保证最终每个 segment 都使用当前 narration 对应的配音,而不是保留模型生成的杂音或无关声音。FrameProcessor 中的合成逻辑会根据 frame.media_type 使用图片或视频路径,并继续生成合成画面。

所以 audio_path 不是一个孤立文件,它会影响:

text 复制代码
当前片段时长
视频生成 duration
图片转视频时长
最终 segment 的声音
后续 BGM 混音基础

十七、Edge-TTS 和 Index-TTS 的核心区别

从 Pixelle-Video 源码设计看,Edge-TTS 和 Index-TTS 的区别不是"哪个声音更好"这么简单,而是接入方式不同。

1. Edge-TTS

Edge-TTS 在 Pixelle-Video 中可以走 local 模式:

text 复制代码
text
    ↓
TTSService._call_local_tts()
    ↓
edge_tts()
    ↓
mp3

特点是:

text 复制代码
部署简单
不需要 ComfyUI workflow
参数主要是 voice 和 speed
适合普通旁白
成本低,适合快速跑通

2. Index-TTS

Index-TTS 更适合通过 ComfyUI workflow 接入:

text 复制代码
text + ref_audio
    ↓
TTS workflow
    ↓
ComfyKit
    ↓
ComfyUI / RunningHub
    ↓
audio

特点是:

text 复制代码
适合声音克隆
可能需要参考音频
依赖具体 ComfyUI workflow
参数由 workflow 决定
更适合需要特定音色、人声风格的场景

README 中也明确提到,语音设置支持选择 TTS workflow,并且参考音频适用于支持声音克隆的 TTS workflow,比如 Index-TTS。

所以可以简单总结:

text 复制代码
Edge-TTS:
    轻量旁白方案。

Index-TTS:
    工作流式高级 TTS / 声音克隆方案。

十八、为什么 Index-TTS 更适合 workflow 方式?

Index-TTS 这类方案通常不是一个简单的文本接口。

它可能涉及:

text 复制代码
模型加载
说话人参考音频
语音克隆参数
音频采样率
推理节点
后处理节点
输出格式

如果 Pixelle-Video 直接适配每一种 TTS 模型,代码会越来越复杂:

text 复制代码
call_edge_tts()
call_index_tts()
call_chattts()
call_fish_audio()
call_custom_voice_clone()

而 workflow 方式可以把复杂度放到 ComfyUI 里。

Pixelle-Video 只需要统一传:

text 复制代码
text
voice
speed
ref_audio

具体这些参数如何连接到 Index-TTS 节点,由 workflow 决定。

这种设计和前面的媒体生成是一致的:

text 复制代码
Pixelle-Video:
    负责 pipeline 和参数传递。

ComfyUI workflow:
    负责具体模型和节点组合。

ComfyKit:
    负责执行 workflow。

TTSService:
    负责拿回 audio_path。

这就是扩展性的来源。

十九、参考音频 ref_audio 的作用

参考音频是声音克隆功能的核心输入。

FrameProcessor._step_generate_audio() 中,如果当前 TTS 模式是 comfyui,并且配置里有 ref_audio,就会把它加入 tts_params

python 复制代码
tts_params["ref_audio"] = config.ref_audio

然后这个参数会继续进入 TTSService._call_comfyui_workflow(),并通过 workflow_params.update(params) 合并到 workflow 参数中。

也就是说,ref_audio 的传递链路是:

text 复制代码
StoryboardConfig.ref_audio
    ↓
FrameProcessor._step_generate_audio()
    ↓
tts_params["ref_audio"]
    ↓
TTSService.__call__()
    ↓
_call_comfyui_workflow()
    ↓
workflow_params
    ↓
ComfyKit.execute()
    ↓
Index-TTS / 声音克隆 workflow

这个设计很灵活。

Pixelle-Video 并不规定 ref_audio 在 workflow 里怎么使用。

它只负责把参数传下去。

如果你的 workflow 支持参考音频,它就能用。

如果 workflow 不支持,这个参数可能会被忽略或导致 workflow 报错。

二十、TTS 和数字人口播的关系

第 16 篇我们讲过数字人口播。

数字人口播通常需要:

text 复制代码
人物参考图
    +
语音音频
    ↓
数字人视频

Pixelle-Video 的 TTS 流程正好提供了这个音频基础。

每个 frame 先生成:

text 复制代码
frame.audio_path
frame.duration

后续如果使用支持 audio-driven 或 digital human 的视频模型,就可以把这个 audio_path 作为驱动音频传给视频生成模型。

在复杂视频能力中,FrameProcessor._prepare_api_video_inputs() 和相关 API 视频参数可以使用 narration audio 作为 driving audio;前面第 16 篇也分析过,音频驱动图生视频、数字人口播都依赖这条链路。

所以 TTS 不只是给最终视频配音,也可能成为视频生成模型的输入。

对于数字人口播来说:

text 复制代码
TTS 生成的不是"后期声音"
而是"驱动人物说话的输入"

这也是为什么 TTS 要尽早生成。

二十一、TTS 和 BGM 的关系

TTS 生成的是人声旁白。

BGM 是后期背景音乐。

这两者不要混在一起。

Pixelle-Video 的每个 frame 会先生成 TTS 音频,并用它生成 segment。

等所有 segment 拼接完成后,再根据用户选择添加 BGM。

所以声音层可以理解成两层:

text 复制代码
第一层:
    每个 frame 的 narration audio

第二层:
    最终视频整体 BGM

这种设计比较合理。

因为旁白决定每个片段的长度和节奏。

BGM 是后期氛围增强,不应该反过来决定分镜时长。

二十二、常见问题:为什么 TTS 会失败?

理解源码后,TTS 问题可以按路线排查。

1. local Edge-TTS 失败

常见原因:

text 复制代码
网络访问失败
Edge-TTS 服务临时不可用
请求过于频繁
voice_id 写错
SSL 或证书问题
没有生成音频数据

tts_util.py 中已经针对 401、NoAudioReceived、临时网络问题做了重试、请求间隔和并发限制,但这不代表永远不会失败。

2. comfyui TTS workflow 找不到

常见原因:

text 复制代码
tts_workflow 配置错误
workflow 没有放到 workflows/ 目录
文件名或 key 写错
default_workflow 没有配置

TTSService 继承自 ComfyBaseService,workflow 前缀是 tts_,工作流目录是 workflows,因此 TTS workflow 通常需要遵守对应命名和目录约定。

3. Index-TTS 没有输出音频

常见原因:

text 复制代码
ComfyUI 节点缺失
模型文件没有下载
ref_audio 格式不支持
workflow 输出节点不对
result.audios / result.files / result.outputs 中没有音频

源码中如果找不到 .mp3.wav.flac 结果,就会抛出 No audio file generated by workflow

4. 远程音频下载失败

如果 RunningHub 或远程 workflow 返回 URL,Pixelle-Video 需要下载到本地 output_path。如果网络不通、URL 过期、权限不对,就可能失败。源码中这一步使用 httpx.AsyncClient() 下载音频。

5. 音频时长读取失败

Pixelle-Video 需要读取音频时长来设置 frame.durationtts_util.py 中有一个 get_audio_duration() 工具函数,优先使用 ffmpeg.probe() 读取时长,失败时会按文件大小做粗略估算。

如果 ffmpeg 不可用或音频文件损坏,后续视频时长可能不准确。

二十三、二次开发:如何新增一个 TTS 方案?

如果你想给 Pixelle-Video 增加新的 TTS 能力,有两种路线。

1. 直接代码接入

适合像 Edge-TTS 这样接口简单的方案。

你可以增加:

text 复制代码
_call_my_tts()

然后在 TTSService.__call__() 中增加新的 mode:

text 复制代码
local_edge
local_my_tts
comfyui

但这种方式会让 TTSService 变复杂。

如果每个 TTS 模型都这么做,后期维护压力会很大。

2. workflow 接入

更推荐。

把新的 TTS 模型封装成 ComfyUI workflow,比如:

text 复制代码
workflows/selfhost/tts_my_voice.json
workflows/runninghub/tts_my_voice.json

然后在配置中选择:

yaml 复制代码
comfyui:
  tts:
    default_workflow: selfhost/tts_my_voice.json

Pixelle-Video 只负责传:

text 复制代码
text
voice
speed
ref_audio

workflow 负责实际推理。

这条路线更适合:

text 复制代码
Index-TTS
ChatTTS
CosyVoice
Fish Speech
声音克隆模型
数字人口播前置语音模型

也更符合 Pixelle-Video 的整体架构。

二十四、二次开发:TTS 模块可以优化什么?

当前 TTS 模块已经能支撑视频生成,但还有一些可优化方向。

1. 增加语音预览缓存

用户调音色和语速时,可能会反复点击试听。

可以根据:

text 复制代码
text + voice + speed + ref_audio + workflow

生成 hash,缓存试听音频,避免重复生成。

2. 增加每段音频时长预览

在正式生成视频前,先显示:

text 复制代码
第几帧
旁白文本
预计音频时长
音色
语速

这样用户能提前判断节奏是否太慢或太快。

3. 增加文本长度检查

如果某段 narration 太长,TTS 会变长,视频片段也会变长。

可以在生成音频前提示:

text 复制代码
这一段旁白过长,建议拆成两段。

4. 增加音量标准化

不同 TTS workflow 输出音量可能不同。

可以在 audio_path 生成后加一步:

text 复制代码
音量检测
    ↓
响度标准化
    ↓
统一输出音频

这样最终视频的人声更稳定。

5. 增加字幕断句

当前 narration 通常直接作为字幕文本。

但 TTS 朗读和字幕显示最好能做更细的断句。

可以进一步生成:

text 复制代码
字幕分行
停顿点
关键词高亮
逐字字幕时间轴

这会明显提升短视频质感。

6. 增加数字人专用 TTS 模式

如果用于数字人口播,可以增加:

text 复制代码
voice_clone_mode
reference_voice
speaker_id
lip_sync_safe_speed

确保输出音频更适合后续驱动数字人。

二十五、源码阅读建议

如果你要读 Pixelle-Video 的 TTS 相关源码,建议按这个顺序:

text 复制代码
1. pixelle_video/services/frame_processor.py
   看 _step_generate_audio() 如何把 frame.narration 送入 TTS。

2. pixelle_video/services/tts_service.py
   看 TTSService 如何根据 inference_mode 分流 local 和 comfyui。

3. pixelle_video/utils/tts_util.py
   看 Edge-TTS 如何真正生成音频、重试、限流和保存文件。

4. pixelle_video/tts_voices.py
   看 Edge-TTS 音色列表和 speed_to_rate()。

5. pixelle_video/config/schema.py
   看 TTSLocalConfig、TTSComfyUIConfig、TTSSubConfig。

6. config.example.yaml
   看 comfyui.tts.default_workflow 和 ComfyUI / RunningHub 相关配置。

7. workflows/selfhost/ 或 workflows/runninghub/
   看具体 TTS workflow 如何接收 text、voice、speed、ref_audio。

8. pixelle_video/services/video.py
   看 audio_path 最后如何参与图片转视频、视频合成和音轨替换。

这样读,可以完整理解:

text 复制代码
narration
    ↓
TTS 参数
    ↓
Edge-TTS 或 TTS workflow
    ↓
audio_path
    ↓
duration
    ↓
video_segment_path

二十六、总结

这一篇我们分析了 Pixelle-Video 的 TTS 语音生成流程。

它的核心链路是:

text 复制代码
StoryboardFrame.narration
    ↓
FrameProcessor._step_generate_audio()
    ↓
构造 tts_params
    ↓
TTSService.__call__()
    ↓
local 模式:
    Edge-TTS 生成 mp3

comfyui 模式:
    ComfyKit 执行 TTS workflow
    Edge-TTS / Index-TTS / 其他语音模型输出音频
    ↓
frame.audio_path
    ↓
读取音频时长 frame.duration
    ↓
后续媒体生成和视频片段合成

从设计上看,Pixelle-Video 的 TTS 模块有几个关键点:

text 复制代码
Edge-TTS 适合轻量本地旁白。
Index-TTS 适合通过 ComfyUI workflow 做声音克隆。
TTSService 把不同语音方案统一成 text → audio_path。
FrameProcessor 会先生成音频,再生成媒体。
音频时长会写入 frame.duration,并影响后续视频长度。
ComfyUI TTS workflow 可以接收 ref_audio 等扩展参数。
RunningHub 返回的远程音频会被下载成本地文件。

一句话总结:

Pixelle-Video 的 TTS 设计,本质是把每段 narration 转换成可复用的 audio_path,并用音频时长驱动后续图片、视频和 segment 合成;Edge-TTS 走轻量本地路线,Index-TTS 这类高级语音方案则通过 ComfyUI / RunningHub workflow 接入。

相关推荐
鸽芷咕44 分钟前
异构数据同步,凭什么敢说数据不丢?——拆解金仓KFS的全周期一致性校验
数据库
海浪仙人掌1 小时前
账龄分析的定义是什么?账龄分析有什么作用?
数据库
ACP广源盛139246256731 小时前
M6/M5 Pro Mac mini 端侧 AI 落地@ACP#IX6024 PCIe2.0 交换芯片在轻量化 AI 服务中的机会与应用场景
大数据·数据库·人工智能·嵌入式硬件·macos·开源
晴天161 小时前
Tsup:TypeScript库打包的“零配置极速方案”
前端·javascript·typescript
ly76891 小时前
深入解析 MySQL 间隙锁:从加锁规则到死锁案例
数据库·mysql·死锁·间隙锁·next-key lock·事务隔离
LRL_1 小时前
7x24小时不停机:基于 Apache SeaTunnel 实现 Oracle to Oracle 实时 CDC 同步全实战
数据库·oracle·apache
不剪发的Tony老师1 小时前
Navop:一款工具搞定数据库、SSH、SFTP、远程桌面、AI Agent
运维·数据库·ssh
计算机魔术师1 小时前
OpenAI 回应智能体接管德语维基网站事件,称将改革 AI 误对齐事件披露机制
前端
程序员-Benothing1 小时前
MySQL 中 DELETE、DROP 和 TRUNCATE 的区别是什么?
数据库·mysql