一本几十万字的小说丢进去,出来的是分角色、带情绪、能直接听的有声书。这篇我把搭这套配音工作台的全流程写给你。
写在前面
有声书、广播剧这两年特别能打。但传统做法成本高得吓人——请配音演员、租录音棚、后期剪辑,一本书做下来动辄几万块。
AI 语音合成(TTS)这两年进步神速,音色自然到很难听出是机器。于是一个很现实的需求冒出来了:能不能把一段长文本,自动切分、识别角色、分配音色、批量合成,最后拼成一部多角色有声书?
能。而且没那么难。难点不在"会不会调 TTS 接口",而在怎么把一本书的复杂结构,拆成 TTS 能一句句吃下的任务 。这篇就带你把这套工作台搭起来,并在中途接入 4sapi.com 这个中转 API 统一调用语音模型。
一、先理清楚:一段文本变成有声书要过几关
整本文稿
↓
① 文本切分 长文 → 按段落/句子切成 TTS 能处理的小块
↓
② 角色识别 哪句是旁白?哪句是 A 说的?哪句是 B 说的?
↓
③ 音色分配 给旁白和每个角色各配一个固定音色
↓
④ 批量合成 每个文本块 → 对应音色的音频片段(TTS)
↓
⑤ 拼接成品 所有片段按顺序拼接,加停顿,输出整本音频 Copy
这里头 ②靠大语言模型 做角色归属判断,④靠语音合成(TTS)模型 。两类模型——又是分散在不同地方,这正是要引入中转 API 的原因。
二、架构设计:四个 Agent + 一个中转网关
┌──────────────────────────┐
上传文稿 ──→ │ Orchestrator 总调度 │
└─────────────┬────────────┘
│ 流水线派活
┌──────────────┬──────────┼──────────┬──────────────┐
↓ ↓ ↓ ↓ ↓
[切分Agent] [角色Agent] [配音Agent] [拼接Agent] 音色表
└──────────────┴──────────┼──────────┴──────────────┘
↓
┌──────────────────────────┐
│ 统一 API 网关 (4sapi) │ ← 文本/TTS 都走这
└──────────────────────────┘ Copy
关键还是最底层:所有 Agent 统一走中转网关,不直接对接各家官方接口。 对有声书这种"调用量极大"的场景,好处尤其实在:
文本模型和 TTS 模型一个 Key :角色识别要 Chat、合成要 TTS,一套凭证全搞定;
音色随便换 :4sapi 背后接了多家 TTS,想换更自然的音色,改一个 voice 参数;
批量调用算账方便 :一本书几千上万次合成调用,所有消耗在一个后台对账。
我选的网关是 4sapi.com ,兼容 OpenAI 格式,对话模型和语音合成模型都能统一调用。
三、中途接入 4sapi:三步把网关接通
第一步:拿 Key、记住 BaseURL
注册 4sapi.com,后台创建 API 令牌(sk-xxxxxxxx),记下接口地址:
放进环境变量,别硬编码:
bash
# .env
FOURSAPI_BASE_URL = https://4sapi.com/v1
FOURSAPI_KEY = sk-你的令牌 Copy
第二步:封装统一客户端
python
# gateway.py
import os
from openai import OpenAI
client = OpenAI(
api_key = os.environ[ "FOURSAPI_KEY" ],
base_url = os.environ[ "FOURSAPI_BASE_URL" ], # 指向中转网关
)
def chat (model, messages, ** kwargs):
resp = client.chat.completions.create( model = model, messages = messages, ** kwargs)
return resp.choices[ 0 ].message.content
def tts (text, voice, model = "tts-1" ):
"""语音合成,走同一个网关"""
resp = client.audio.speech.create( model = model, voice = voice, input = text)
return resp.content # 音频二进制 Copy
第三步:发请求验证连通
python
if __name__ == "__main__" :
audio = tts( "测试一下,网关通了。" , voice = "zh-female-1" )
open ( "test.mp3" , "wb" ).write(audio) # 能生成可播放的音频即成功 Copy
能出一个能播的 mp3,说明 TTS 这条路打通了。核心还是「改 base_url + 配 Key」。
四、逐个环节实现:四个 Agent 怎么写
4.1 切分 Agent —— 把长文切成 TTS 能吃的小块
TTS 接口对单次输入长度有限制,几十万字必须切。切的时候要尊重句子边界,别从句子中间切断:
python
import re
def split_agent (text, max_len = 300 ):
# 按中文句末标点切句,再合并到不超过 max_len 的块
sentences = re.split( r ' (?<= [。!? \n ] ) ' , text)
chunks, buf = [], ""
for s in sentences:
if len (buf) + len (s) > max_len:
chunks.append(buf); buf = s
else :
buf += s
if buf: chunks.append(buf)
return chunks Copy
4.2 角色 Agent —— 判断每一句是谁说的
这是有声书的灵魂。让大模型读上下文,给每个文本块标注说话者——是旁白,还是某个角色:
python
import json
def role_agent (chunks):
text = " \n " .join( f "[ { i } ] { c } " for i, c in enumerate (chunks))
prompt = f """下面是小说片段,每行前面是编号。判断每一段是"旁白"还是某个
角色在说话。输出 JSON 数组,元素含 idx 和 speaker(旁白/角色名)。
{ text } """
raw = chat(
model = "gpt-4o" ,
messages = [{ "role" : "user" , "content" : prompt}],
response_format = { "type" : "json_object" },
)
return json.loads(raw)[ "roles" ] Copy
4.3 配音 Agent —— 给每个角色配固定音色
先建一张"角色→音色"映射表,保证同一个角色全书音色统一(这是听感连贯的关键):
python
# 音色分配:旁白用沉稳男声,角色按性别/年龄分配不同音色
VOICE_MAP = {
"旁白" : "zh-male-narrator" ,
"林夏" : "zh-female-1" ,
"老陈" : "zh-male-2" ,
}
DEFAULT_VOICE = "zh-female-2" # 没预设的角色用默认
def voice_agent (chunk, speaker):
voice = VOICE_MAP .get(speaker, DEFAULT_VOICE )
return tts(chunk, voice = voice) # 合成,走网关 Copy
4.4 拼接 Agent —— 把碎片拼成整本
所有片段按原顺序拼接,角色切换处插入短停顿,听感更自然:
python
from pydub import AudioSegment
def merge_agent (audio_segments):
book = AudioSegment.empty()
pause = AudioSegment.silent( duration = 300 ) # 0.3 秒停顿
for seg_bytes in audio_segments:
seg = AudioSegment.from_file(io.BytesIO(seg_bytes))
book += seg + pause
return book # 导出 mp3 即整本有声书 Copy
五、串起来:总调度跑通一整本
python
def make_audiobook (full_text):
chunks = split_agent(full_text) # ① 切分
roles = role_agent(chunks) # ② 角色识别
speaker_of = {r[ "idx" ]: r[ "speaker" ] for r in roles}
segments = []
for i, chunk in enumerate (chunks):
speaker = speaker_of.get(i, "旁白" )
audio = voice_agent(chunk, speaker) # ③④ 配音+合成
segments.append(audio)
return merge_agent(segments) # ⑤ 拼接成品 Copy
文本判断、语音合成——全走同一个 4sapi 网关 。
六、前端:一个能上传文稿、试听、调音色的工作台
jsx
function AudiobookStudio () {
const [ text , setText ] = useState ( "" );
const [ progress , setProgress ] = useState ( 0 );
async function generate () {
const res = await fetch ( "/api/audiobook" , {
method: "POST" ,
body: JSON . stringify ({ text }),
});
// 用 SSE 回报"已合成 X / 共 Y 段",更新进度条
}
return (
< div >
< textarea value = {text} onChange = { e => setText (e.target.value)}
placeholder = "粘贴或上传文稿…" />
< VoiceConfigPanel /> { /* 给每个角色挑音色 */ }
< button onClick = {generate}>开始生成有声书</ button >
< Progress value = {progress} />
</ div >
);
} Copy
安全提醒:批量 TTS 是真烧钱的环节,一本长篇可能上万次调用。上线前务必做用量上限和频率限制 ,并给用户一个"预估消耗"提示。另外,上传的文稿要注意版权,别拿没授权的作品商用。
七、几条实战经验
切分务必尊重句子边界 。从句子中间切断,合成出来会有奇怪的断顿,听感立刻崩。
角色识别一次性批量做,别逐句调 。把整章带编号丢给模型一次标注,比一句句问省掉大量 token 和时间。
音色映射表抽成全局配置 。同一角色全书音色必须固定,靠映射表锁死,别让模型每段自由发挥。
合成做成可断点续传 。一本书几千段,跑到一半挂了别从头来——记录已完成的片段,重跑只补缺的。
小段并发合成 。TTS 各段相互独立,开适度并发能把几小时的活压到几十分钟,注意别超网关的速率限制。
写在最后
把一本书变成多角色有声书,难点不在 TTS 本身,而在怎么把一段没有结构的长文,拆成机器能一句句念好的任务流 。
切分、识角色、配音色、再拼接——四步走通,长文就能自动变成能听的广播剧。
中途接入 4sapi,把"文本模型 + 语音模型分头管理"的麻烦,收敛成了一个网关、一个 Key。业务只管编排,底层模型怎么换都不碰上层代码 ——这套统一网关的思路,省下的不只是对接成本,更是后续维护的心智负担。
本文代码为说明思路的简化示意,具体模型名称、接口字段请以 4sapi.com 官方文档为准。涉及付费接口,请做好用量控制与版权合规。