跳到主要内容

文档音频工作流 (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核心播放器组件,支持 manifestkeyPrefix props
src/components/DocsAudioPlayer/index.js文档音频薄包装,传入文档清单和前缀
src/utils/lookAroundDocs.jsLookAround 文档检测工具函数
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}.mp3Audio/docs/{slug}_001.mp3
英文文档音频Audio/docs/en/en_{slug}.mp3Audio/docs/en/en_{slug}_001.mp3
文档音频清单Audio/docs/manifest.json

与博客音频(Audio/blog/)完全独立,互不干扰。

生成音频

中文音频

cd ../tts-blog-generator
python generate.py --type docs

默认扫描 docs/LookAround/ 目录。脚本会:

  1. 提取每篇文章的纯文本(去除 frontmatter、代码块、markdown 格式等)
  2. 按 3500 字符限制分块
  3. 检测语言(CJK > 30% 为中文)
  4. 调用 MiMo-V2.5-TTS API 生成 WAV
  5. 通过 ffmpeg 转换为 MP3
  6. 上传到阿里云 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.mdomega-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 朗读的纯文本。处理步骤:

  1. 去除 YAML frontmatter
  2. 去除 import 语句和 export const
  3. 去除 JSX 组件标签
  4. 去除 HTML 注释
  5. 去除围栏代码块
  6. 处理行内代码(去除代码内容,保留可读文本如日期、版本号)
  7. 转换表格:每行单元格用中文逗号连接,跳过分隔行和纯链接行
  8. 将 markdown 链接转换为纯文本
  9. 去除图片和独立的斜体图注
  10. 去除 markdown 格式字符(#*_~>
  11. 合并多余空行和空格

对于 LookAround 文章中的复杂表格,转换后的效果示例:

| 场景 | 建议图片 | 链接 |
|---|---|---|
| 品牌起源 | 1894 年 OMEGA 名称 | [OMEGA Press](https://...) |

转换为:

场景,建议图片,链接
品牌起源,1894 年 OMEGA 名称,OMEGA Press

已知限制

  1. 英文音频未生成:当前只有中文音频,切换到英文 locale 时播放器不显示
  2. 分块丢失:如果某篇文章的某个 chunk TTS API 调用失败,该 chunk 会被跳过,播放时会缺少对应段落(omega 文章的第 5 chunk 即为这种情况)
  3. 表格转换:复杂嵌套表格的转换效果可能不够自然,需要人工抽查
  4. 代码块内容:代码块被完全去除,不会生成朗读内容

测试清单

新增或更新文档音频后,至少检查:

  • 中文音频已通过 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)