基于 AI Agent 的童话编剧与绘本生成器(六)听故事 TTS 朗读
之前朗读能力只是占位:“StoryPage.audio_url”虽然指向 “/api/v1/assets/audio/{story_id}/{页码}.mp3”,但磁盘上没有 MP3,“assets.py” 只返回 404 占位 JSON;阅读页点击插图 “audio.play()” 也会静默失败。用户看得到「AI 朗读中」文案,却听不到声音。
本篇接入 DashScope CosyVoice,补齐创作流水线 narrating 阶段、按需合成、阅读页音色切换与翻页自动联播。
一、后端 TTS 合成:CosyVoice + 按音色落盘
1.1 改动方向
“TTSProvide” 仅有配置检查,没有调用 SDK 的逻辑。
音频路径约定是“data/audio/{story_id}/{页码}.mp3”,但从未写入文件。
“.env”里“TTS_VOICE_NAME=default-child-friendly”不是 CosyVoice 合法音色 ID。
1.2 改法:“story_tts.py”统一合成入口
新增 “backend/app/services/story_tts.py”,使用 “dashscope.audio.tts_v2.SpeechSynthesizer”非流式 “call()”,按页写入:
data/audio/{story_id}/{voice_id}/{页码}.mp3
核心逻辑:
def audio_page_path(*, story_id, voice_id, page_index):
return data_root() / "audio" / story_id / voice_id / f"{page_index}.mp3"
def synthesize_page_mp3(*, text, out_path, voice, api_key, speech_rate=0.95):
synthesizer = SpeechSynthesizer(
model=voice.model,
voice=voice.id,
format=AudioFormat.MP3_22050HZ_MONO_256KBPS,
speech_rate=speech_rate,
)
audio = synthesizer.call(text.strip())
out_path.write_bytes(audio)
1.3 Key 与默认音色
“TTSProvider”支持 Key 回退,与出图共用 DashScope 账号即可:
TTS_API_KEY → DASHSCOPE_API_KEY → IMAGE_API_KEY
默认音色改为“longxiaochun_v2”(活泼童声);占位值 “default-child-friendly”自动映射到该 ID。
env新增
TTS_API_KEY=(留空复用 IMAGE_API_KEY)
TTS_MODEL_NAME=cosyvoice-v3-flash
TTS_VOICE_NAME=longxiaochun_v2
二、音色目录与按需合成:切换偏好不必重跑全书
2.1 可选音色(本地 Key 实测可用)
“tts_voices.py”维护白名单目录,阅读页下拉与 API 校验共用:
|
音色 ID |
展示名 |
模型 |
适用场景 |
|
longxiaochun_v2 |
龙小春 · 活泼童声 |
cosyvoice-v2 |
儿童绘本默认 |
|
longwan_v2 |
龙婉 · 温柔女声 |
cosyvoice-v2 |
睡前故事 |
|
longanyang |
龙安阳 · 沉稳男声 |
cosyvoice-v3-flash |
讲故事男声 |
|
longxiaochun |
龙小春 · 经典童声 |
cosyvoice-v1 |
经典童声 |
不同音色使用不同 CosyVoice 模型版本;目录里为每个 ID 绑定了对应“model”,避免 voice/model 不匹配导致合成失败。
2.2 API
|
接口 |
作用 |
|
GET /api/v1/tts/voices |
返回音色列表、“default_voice”、“tts_configured” |
|
GET /api/v1/assets/audio/{story_id}/{page}.mp3?voice={id} |
读缓存;不存在则读 DB 取 “page.text”现场合成再返回 |
2.3 安全与路径
“voice” 参数必须在 “VOICE_CATALOG” 白名单内,否则 400。
目录名只允许 “a-z A-Z 0-9”,防止路径注入
三、阅读页:音色偏好 + 翻页联播
3.1新版“reader.html”交互
| 控件 | 行为 |
| 顶栏“音色”下拉 | 从“GET /tts/voices”加载;偏好存“localStorage”(“storybook.ttsVoice”) |
|
听故事按钮 |
开关听书模式;偏好存"storybook.listenMode" |
|
暂停 |
关闭听书模式并停止当前音频 |
|
点击插图 |
等同开启听故事并播放当前页 |


3.2 翻页自动联播
听故事模式开启时:
1. "renderPage()"结束自动 "playCurrentPage({ autoplay: true })"。
2. 当前页"audio.onended" → "currentPage += 1" → 渲染下一页 → 继续播放。
3. 最后一页播完提示「故事朗读完毕」。
4. 手动上一页 / 下一页会先 "stopAudio()",再渲染;若仍在听故事模式则对新页自动开播。
切换音色时:若正在朗读或听故事模式已开,立即用新音色重播当前页。
四、删书清理与磁盘布局
“story_assets.purge_story_image_assets”扩展为同时删除:
data/audio/{story_id}/
仍保留“page_attempts”审计与 RAG 语料。
更多推荐

所有评论(0)