文档音频工作流 (Docs TTS)
文档分类下的文章(如 docs/LookAround/)支持 TTS 朗读音频播放。与博客音频不同,文档音频播放器自动注入到页面布局中,无需在每篇文章的 MDX 文件里手动引入组件。
当前已接入音频的文档分类:
| 分类 | 目录 | 文章数 | 中文音频 | 英文音频 |
|---|---|---|---|---|
| 东张西望 (LookAround) | docs/LookAround/ | 4 | 已生成 | 未生成 |
架构概览
文档音频系统复用了博客音频的基础设施,通过少量改动实现了多清单支持。
组件关系
BlogAudioPlayer (核心播放逻辑)
├── 博客直接调用:<BlogAudioPlayer slug="xxx" />
│ └── 读取 blogAudioManifest.json(默认清单)
│
└── DocsAudioPlayer (薄包装)
└── <DocsAudioPlayer slug="xxx" />
└── 传入 docsAudioManifest.json + keyPrefix="docs/"
关键文件
| 文件 | 作用 |
|---|---|
src/components/BlogAudioPlayer/index.js | 核心播放器组件,支持 manifest 和 keyPrefix props |
src/components/DocsAudioPlayer/index.js | 文档音频薄包装,传入文档清单和前缀 |
src/utils/lookAroundDocs.js | LookAround 文档检测工具函数 |
src/theme/DocItem/Layout/index.js | 文档布局,自动注入播放器 |
src/data/docsAudioManifest.json | 文档音频清单(静态 import) |
static/audio/docs/manifest.json | 文档音频清单(备用访问路径) |
清单文件结构
docsAudioManifest.json 的 key 格式:
- 中文:
docs/{slug},如docs/omega-horizontal-vertical-analysis - 英文:
en/docs/{slug},如en/docs/omega-horizontal-vertical-analysis
每个条目包含:
{
"docs/omega-horizontal-vertical-analysis": {
"urls": [
"https://picture.nevergpdzy.cn/Audio/docs/omega-horizontal-vertical-analysis_001.mp3",
"https://picture.nevergpdzy.cn/Audio/docs/omega-horizontal-vertical-analysis_002.mp3"
],
"voice": "茉莉",
"generatedAt": "2026-05-01T16:46:32.495301+00:00"
}
}
OSS 路径
| 内容 | OSS 路径 |
|---|---|
| 中文文档音频 | Audio/docs/{slug}.mp3 或 Audio/docs/{slug}_001.mp3 等 |
| 英文文档音频 | Audio/docs/en/en_{slug}.mp3 或 Audio/docs/en/en_{slug}_001.mp3 等 |
| 文档音频清单 | Audio/docs/manifest.json |
与博客音频(Audio/blog/)完全独立,互不干扰。
生成音频
中文音频
cd ../tts-blog-generator
python generate.py --type docs
默认扫描 docs/LookAround/ 目录。脚本会:
- 提取每篇文章的纯文本(去除 frontmatter、代码块、markdown 格式等)
- 按 3500 字符限制分块
- 检测语言(CJK > 30% 为中文)
- 调用 MiMo-V2.5-TTS API 生成 WAV
- 通过 ffmpeg 转换为 MP3
- 上传到阿里云 OSS
英文音频
python generate.py --type docs --lang en \
--blog-dir "../Dev-Knowledge-Base/i18n/en/docusaurus-plugin-content-docs/current/LookAround"
英文音频使用 Chloe 语音,文件名带 en_ 前缀。
常用参数
| 参数 | 说明 |
|---|---|
--type docs | 生成文档音频(默认扫描 docs/LookAround/) |
--lang zh / --lang en | 指定语言 |
--blog-dir <path> | 自定义扫描目录 |
--force | 强制重新生成已存在的音频 |
--dry-run | 预览模式,不调用 API |
--skip-upload | 跳过 OSS 上传 |
复制清单文件到项目
生成完成后,将清单文件复制到项目中:
# 从 tts-blog-generator 目录执行
cp output/manifest.json ../Dev-Knowledge-Base/src/data/docsAudioManifest.json
cp output/manifest.json ../Dev-Knowledge-Base/static/audio/docs/manifest.json
output/manifest.json 同时包含博客和文档的条目。复制到项目后,DocsAudioPlayer 组件只会读取 docs/ 前缀的条目,不会影响博客音频。
播放器集成方式
自动注入(当前方式)
文档音频播放器在 src/theme/DocItem/Layout/index.js 中自动注入,无需修改任何 .md 文件。
注入逻辑:
import {isLookAroundDocMetadata, getLookAroundDocSlug} from '@site/src/utils/lookAroundDocs';
import DocsAudioPlayer from '@site/src/components/DocsAudioPlayer';
// 在 DocItemLayout 组件内:
const isLookAround = isLookAroundDocMetadata(metadata);
const lookAroundSlug = isLookAround ? getLookAroundDocSlug(metadata) : null;
// 渲染位置:面包屑和正文之间
<DocBreadcrumbs />
<DocVersionBadge />
{isLookAround && lookAroundSlug && <DocsAudioPlayer slug={lookAroundSlug} />}
{docTOC.mobile}
检测函数 isLookAroundDocMetadata() 通过 metadata.sourceDirName === 'LookAround' 判断当前文档是否属于 LookAround 分类。
手动引入(备选方式)
如果需要在特定文档中手动控制播放器位置,也可以在 MDX 文件中直接引入:
import DocsAudioPlayer from '@site/src/components/DocsAudioPlayer';
<DocsAudioPlayer slug="your-doc-slug" />
slug 规则: 文件名去掉扩展名。例如 omega-horizontal-vertical-analysis.md → omega-horizontal-vertical-analysis。
添加新文档分类的音频支持
如果需要为其他文档分类(非 LookAround)添加音频支持,需要以下步骤:
1. 创建检测工具函数
在 src/utils/ 下新建文件,参照 lookAroundDocs.js 的模式:
// src/utils/yourCategoryDocs.js
export const YOUR_CATEGORY_DOC_SOURCE_DIR = 'YourCategory';
export const YOUR_CATEGORY_DOC_ID_PREFIX = `${YOUR_CATEGORY_DOC_SOURCE_DIR}/`;
export function isYourCategoryDocMetadata(metadata) {
if (!metadata) return false;
return metadata.sourceDirName === YOUR_CATEGORY_DOC_SOURCE_DIR;
}
export function getYourCategoryDocSlug(metadata) {
const id = metadata?.id || '';
if (id.startsWith(YOUR_CATEGORY_DOC_ID_PREFIX)) {
return id.slice(YOUR_CATEGORY_DOC_ID_PREFIX.length);
}
return id;
}
2. 修改 DocItem/Layout
在 src/theme/DocItem/Layout/index.js 中导入并添加条件渲染:
import {isYourCategoryDocMetadata, getYourCategoryDocSlug} from '@site/src/utils/yourCategoryDocs';
// 在 DocItemLayout 组件内:
const isYourCategory = isYourCategoryDocMetadata(metadata);
const yourCategorySlug = isYourCategory ? getYourCategoryDocSlug(metadata) : null;
// 在渲染位置添加:
{isYourCategory && yourCategorySlug && <DocsAudioPlayer slug={yourCategorySlug} />}
3. 生成音频
cd ../tts-blog-generator
python generate.py --type docs --blog-dir "../Dev-Knowledge-Base/docs/YourCategory"
4. 更新清单文件
将生成的条目合并到 docsAudioManifest.json 中。如果新分类使用不同的 OSS 路径前缀,需要在 generate.py 中调整 oss_audio_prefix 的逻辑。
文本提取处理
generate.py 中的 extract_text() 函数负责将 markdown 转换为适合 TTS 朗读的纯文本。处理步骤:
- 去除 YAML frontmatter
- 去除
import语句和export const块 - 去除 JSX 组件标签
- 去除 HTML 注释
- 去除围栏代码块
- 处理行内代码(去除代码内容,保留可读文本如日期、版本号)
- 转换表格:每行单元格用中文逗号连接,跳过分隔行和纯链接行
- 将 markdown 链接转换为纯文本
- 去除图片和独立的斜体图注
- 去除 markdown 格式字符(
#、*、_、~、>) - 合并多余空行和空格
对于 LookAround 文章中的复杂表格,转换后的效果示例:
| 场景 | 建议图片 | 链接 |
|---|---|---|
| 品牌起源 | 1894 年 OMEGA 名称 | [OMEGA Press](https://...) |
转换为:
场景,建议图片,链接
品牌起源,1894 年 OMEGA 名称,OMEGA Press
已知限制
- 英文音频未生成:当前只有中文音频,切换到英文 locale 时播放器不显示
- 分块丢失:如果某篇文章的某个 chunk TTS API 调用失败,该 chunk 会被跳过,播放时会缺少对应段落(omega 文章的第 5 chunk 即为这种情况)
- 表格转换:复杂嵌套表格的转换效果可能不够自然,需要人工抽查
- 代码块内容:代码块被完全去除,不会生成朗读内容
测试清单
新增或更新文档音频后,至少检查:
- 中文音频已通过
python generate.py --type docs生成 - 英文音频已通过
python generate.py --type docs --lang en ...生成(如需要) - 清单文件
docsAudioManifest.json已复制到src/data/和static/audio/docs/ -
npm run build通过 - 访问 LookAround 文章时播放器出现在面包屑下方
- 播放按钮可点击,音频正常播放
- 多段音频无缝衔接(如有)
- 进度条显示正确总时长
- 倍速切换正常
- 访问非 LookAround 文档时播放器不出现
- 切换 locale 后行为正确
- OSS 防盗链白名单包含当前域名(生产 + localhost)