Voice Gallery — 设计与实现规格¶
日期: 2026-05-31
分支: debpalash/voice-gallery
状态: 已批准(设计),实现中
发布版本: v0.3.0(依据 CLAUDE.md 版本命名规则 — 无版本闲聊)
1. 目标¶
一个可浏览的设计声音原型库 — 数百种即用型声音, 全部由 VoiceStudio 自身的声音设计引擎生成 — 采用 ElevenLabs 风格组织(顶部精选卡片,分面筛选以探索其余内容), 外加一个中立的 URL/文件导入器,供用户自带源音频。
项目作者零名人 / 具名真人目录。每个原型的“氛围” 来自 token 组合 + 示例脚本 + 策展式命名,绝不源于克隆某个可识别个体。
为何没有名人目录(决策记录)¶
用户粘贴的 URL 导入器是中立基础设施(类似 yt-dlp/ffmpeg)—— 用户提供来源并自行承担许可责任。但项目随附的 具名真人目录(名人 / 政客 / 迪士尼 / 漫威)是一种编辑行为,它:
- 将项目从中立工具转变为诱导 / 帮助侵权责任 (MGM v. Grokster 案一路线),
- 让维护者的名字暴露在形象权 / ELVIS-Act / NO-FAKES 风险之下,
- 是当前语音诈骗浪潮的主要推手。
因此:保留中立的导入器,删除策展式名人分类,以 合成原型为先。
2. 绑定的引擎约束¶
omnivoice/utils/voice_design.py 定义了完整的已验证指令
词汇表。model.generate(instruct=...) 运行 _resolve_instruct,
该函数会拒绝其中任何 token 并抛出 ValueError(这曾导致 issue #89)。
完整表达调色板:
| 轴 | 有效 token | 排他性 |
|---|---|---|
| 性别 | male、female |
单选 |
| 年龄 | child、teenager、young adult、middle-aged、elderly |
单选 |
| 音高 | very low pitch、low pitch、moderate pitch、high pitch、very high pitch |
单选 |
| 风格 | whisper(唯一风格 token) |
可选 |
| 口音(仅限 EN) | american、british、australian、chinese、canadian、indian、korean、portuguese、russian、japanese | 单选 |
| 方言(仅限 ZH) | 河南话、陕西话、四川话、贵州话、云南话、桂林话、济南话、石家庄话、甘肃话、宁夏话、青岛话、东北话 | 单选 |
关键点: 不存在情感/描述性风格轴(没有像 ElevenLabs
descriptive 那样的 “calm”、“raspy”、“warm”)。我们无法 1:1 导入 ElevenLabs
声音。我们借用其结构(用例类别 + 分面筛选器)和
人口统计分面(年龄/性别/口音直接映射),但不借用其风格
词汇表。口音(EN)和方言(ZH)互斥 — 绝不在同一条
instruct 中组合。
规模(我们如何达到“数百”)¶
合法英语空间 ≈ 性别(2) × 年龄(5) × 音高(5) × 口音(11 含中性) ≈
550,经 whisper 修饰符后约翻倍。修剪至合理组合 →
约 300–500 个英语原型。中文方言空间(性别 × 年龄 × 音高 ×
12 种方言)再增加数百个。这通过人口统计/嗓音多样性而非
情感多样性,达到 ElevenLabs 级别的广度。
3. 分类法(取代名人/政客/迪士尼/漫威)¶
七个用例类别(对标 ElevenLabs,不含真人):
narration 叙述与故事 · conversational 对话 ·
characters 角色与动画 · social 社交媒体 ·
entertainment 娱乐与电视 · advertisement 广告 ·
informative 信息与教育
分面筛选器: 性别 · 年龄 · 音高 · 口音 · Whisper · 语言(EN / ZH)。
精选原型带有策展式、人工分配的 use_case。浏览全部
原型则获得从分面派生的启发式主 use_case(文档中标注为近似值),
因此用例筛选在整个集合中仍然有效。
4. 架构¶
4.1 信息架构¶
Gallery 标签页有两个区域(顶部切换):
- Archetypes(原型)
- Featured(精选) — 约 24 个精选原型,预渲染预览 WAV,按用例分组。
- Browse all(浏览全部) — 生成数百个集合;可进行分面筛选;预览按需渲染 + 缓存。
- My Imports(我的导入) — 中性 URL/文件导入器(复用现有 yt-dlp +
AudioTrimmer流程),无策展类别。
4.2 原型模型(值对象 — 非 DB 行)¶
{
"id": str, # 稳定:短哈希(instruct + language)
"name": str, # “British · Middle-aged · Low — Narrator”(生成)或策展命名
"icon": str,
"use_case": str, # 7 个 id 之一
"instruct": str, # 逗号连接的合法 token — 保证通过验证器
"attrs": {Gender, Age, Pitch, Style, EnglishAccent, ChineseDialect}, # 驱动设计滑块
"facets": {gender, age, pitch, accent, whisper, lang}, # 驱动筛选器
"sample_script": str,
"preview_url": str | None, # 精选项已设置(预渲染);None => 按需渲染
"is_featured": bool,
"language": "English" | "Chinese",
}
4.3 后端¶
- 新增
backend/core/archetypes.py USE_CASES、FEATURED(约 24 个,扩展 personalities 模式)。generate_archetypes()— 遍历验证器自身的集合,通过 规则表修剪不合理组合,自动命名,分配启发式用例,构建 保证有效的 instruct 字符串。确定性顺序 + 稳定 ID。list_archetypes(filters)、get_archetype(id)、categories()。- 从
omnivoice/utils/voice_design.py导入词汇表(单一事实来源)。 - 新增
backend/api/routers/archetypes.py GET /archetypes/categoriesGET /archetypes?use_case=&gender=&age=&pitch=&accent=&whisper=&lang=&featured=&limit=&offset=GET /archetypes/{id}GET /archetypes/{id}/preview— 若存在预渲染 WAV 则直接提供;否则通过 声音设计引擎渲染并按 instruct 哈希键缓存到磁盘。POST /archetypes/{id}/use— 渲染示例 → 创建voice_profile(渲染 WAV 作为ref_audio,原型的instruct/language)→ 返回 profile id。- 在
backend/main.py中与其他 router 一并注册。 - 预览缓存 —
OUTPUTS_DIR/archetype_previews/<hash>.wav,通过新的 静态挂载或FileResponse提供。预渲染的精选 WAV 位于backend/assets/samples/voice_design/下(通过/demo_audio现有挂载提供)。 -
导入器(复用
gallery.py) — 删除名人CATEGORIES常量;voice_gallery表现在仅支持 “My Imports”。/gallery/search/youtube保留,但由用户提供的 URL/查询驱动(无名人默认值)。不添加 年龄门控 cookie 绕过。 -
约束:
POST /profiles要求ref_audio,因此“使用原型” 必须先渲染 WAV — 在/archetypes/{id}/use内处理。
5. 渲染管线(JSON → WAV)¶
5.1 架构 / 流¶
前端点击原型中的 ▶ 会提示后端渲染。完整流程:
- Browser – 获取
GET /archetypes/{id}/preview;返回{wav_url, cached}。 若cached == false,则触发PUT /archetypes/{id}/preview/render(幂等), 然后轮询 wav URL,直到 200 或出现错误。 - Backend
preview– 检查磁盘缓存 → 若非空则返回FileResponse。 若缓存未命中,返回202和{wav_url: "/preview/{id}"}(客户端 负责触发渲染并轮询)。 - Backend
render– 锁 + 原子重命名。空闲时渲染所需 GPU 时间约为 2–10 秒,具体取决于序列长度。
5.2 缓存语义¶
- 确定性输入:缓存键为原型
instruct+language+voice_design引擎 版本。 voice_design.py的 engine-version bump ⇒ 缓存失效。发布说明 注明 “voice_design v2 ⇒ 清空OUTPUTS_DIR/archetype_previews”。- 新旧键并存在磁盘上:旧的 WAV 会保留,但永远不会被 读取(列表按前缀枚举)。隐含容量上限:8 GB, 由磁盘使用率扫描处理,删除最旧条目。
5.3 渲染限制 (Lock + 常量内存运行)¶
默认情况下,任何渲染请求最多使用一个 GPU seconds。实际内存
占用通过对输出 strength 设置上限并调用 del + gc.collect()
来限制。单个共享进程范围锁保护全部渲染端点
(/profiles、/archetypes/*/preview、/archetypes/*/use — 但不是
ASR 转录),这样 3 个并发预览不会同时占满 3 个 GPU。
6. 前端¶
6.1 技术栈¶
- 与
StoriesEditor/AudiobookTab相同的 UI 技术栈。 - Gallery 是第 4 个标签页。
6.2 路由 / 组件¶
/gallery— 顶层切换器。/gallery?tab=archetypes(默认)- 部分 1:Featured — 水平滚动、卡片式、按
use_case分组。 -
部分 2:Browse all — 分面筛选器 + 虚拟化网格。
-
/gallery?tab=imports(现状:无策展;保留搜索/导入)
新 UI 组件:
FacetFilterBar— 固定顶部;gender、age、pitch、accent、whisper、lang。联动 — 不匹配的选项自动失去可选性。ArchetypeCard— 图标、名称、样本脚本工具提示(英文/中文字符串)、 ▶ 渲染带旋转图标和失败状态的按钮。ScrollToTopButton— 网格超大时的浮动返回顶部按钮。
可访问性: 卡片为 <button> 元素,完整键盘支持(箭头键 =
虚拟化网格导航),“渲染”事件通过 aria-live 播报。
7. API 参考¶
7.1 端点¶
原型
GET /archetypes/categories→{use_cases: [{id, label, count}]}GET /archetypes?use_case=&gender=&age=&pitch=&accent=&whisper=&lang=&featured=&limit=&offset=→{total, items: [ArchetypeSummary]}GET /archetypes/{id}→ArchetypeDetailGET /archetypes/{id}/preview→FileResponse(缓存命中)或202 {wav_url}POST /archetypes/{id}/render→202 {status: "pending"}POST /archetypes/{id}/use→{profile_id}
导入
POST /gallery/import(现有流程;不新增端点)GET /gallery/search/youtube(现有流程;请求时由用户输入驱动)
模型
ArchetypeSummary—id、name、icon、use_case、language、preview_urlArchetypeDetail— Summary +instruct、attrs、facets、sample_script
8. 数据与持久化¶
- 后端 未配置新 DB:原型 === 模块级常量。
- 仅画廊的 “My Imports” 表(已有
voice_gallery表)使用sqlite3。 - 迁移: 无。旧的
celebs种子文件仅存于backend/assets/samples/voice_design/,永久移除(改用 精选原型目录)。
9. 测试¶
| 层 | 文件 | 内容 |
|---|---|---|
| 单元测试 | backend/core/archetypes_test.py |
每个枚举组合成功;x(No Y) 对于 whisper/language 组合抛出 ValueError;ID 的确定性与稳定性;预览哈希针对击键注入进行测试。 |
| API 测试 | backend/api/routers/archetypes_test.py |
过滤器工作正常;分页返回正确的 total 与 items 长度;preview 缓存命中/未命中均正确返回。 |
| 安全测试 | backend/security |
ReDoS 防护测试:分面查询在硬编码拒绝列表中的恶意模式后,日志标记为 deny;不直接针对 LLM 测试。 |
| 冒烟测试 | CI | 生成 50 个 archetype → 渲染到 WAV → 解码。 |
| 回归测试 | UI | Gallery 负载 ≤ 2.0 s,筛选交互 ≤ 300 ms,GPU 在 use 渲染期间不超过 4 GB。 |
10. 性能 / 规模预算¶
| 操作 | 预算 |
|---|---|
| 启动时原型生成 | 后台 · ≤ 2 s(生成数百个常量)。 |
| 列表 + 分面筛选响应 | ≤ 300 ms(在 Python 中过滤常量列表;SQL 仅用于导入)。 |
| 磁盘缓存 | ≤ 8 GB(启用清理;超过上限删除最旧的 WAV)。 |
| 初次原型渲染 | ≤ 10 s(典型值 2–5 s)。 |
| 并发 | 每个进程 1 个渲染(进程范围锁)。 |
11. 安全与隐私¶
- 仅支持合成人声:根据项目可见性规则,无克隆模式。
- 用户导入需自行同意(现有流程)。库自身不再包含其 装饰性头像与姓名为虚构角色配音的人工智能生成条目的 “名人库”种子。
- 保存到磁盘的每个项目现均在编辑会话结束时销毁 — 预览缓存保存的 WAV 不含端到端标识符,文件名基于哈希。
- WAV 不会嵌入到画廊目录中。预览是 PROTECTED 媒体 — 不允许记住 cookie。
- 在实现过程中不做任何变更,而旧文件
gallery.py继续同时服务于“导入”功能(修订 Celeb 列表调用等)—— 仅删除celebs固定预设。
12. 发布说明(面向用户)¶
Voice Gallery 已上线,全部通过 VoiceStudio 自家引擎合成。 浏览激发灵感的分类与嗓音原型,试用不同口音、年龄和 音高 — 全部合成,零名人预设。自带任何音频,通过 中性导入器,自行负责许可。合成声音 + 你的创意,不被 冒充所玷污。使用基于文件的脚本,在上线前预览每个原型。
13. 超出范围(后续发布)¶
- 每个原型的沙盒预览脚本,整合至后续版本发布说明中
- 按需自定义:用户驱动的 “混合” 声音 fader
- 从用户导入的 WAV 中派生元数据(自动描述)
- “每日原型” 刷新计划(用户给定日期 → 合成亮灯)
- 未来支持其他语言的方言标签
- “我的收藏” 持久化存储 — 除免费层外,仅本地存储
- 原型 sitemap 为
robots.txt提供 SEO 条目(除 robots.txt 外)
14. 决策记录 (ADR)¶
ADR-01:为什么是原型,而不是可写样式快照¶
原型只是预设 —众多预设构型(性别 × 年龄 × 音高 × 口音/方言,
可选 whisper),由指令集定义并内置其中。用户还需要
“软” 版本 — 包含用户声音的参数,以及其对文本的影响 —
但这属于另一个工作项,暂不做。
ADR-02:为什么十一要素风格的结构(案例 → 筛选器)¶
刮削 “浏览” 页面,散落的 “千面”。这改变了他们的目录页面 — 来自每个用例、人口统计和风格滤波器的数据,一目了然。我们 使用相同的信号形状。
ADR-03:为什么不对 ent/actor 姓名进行预训练¶
已争论过。语音提示调用的行为模式(同一系统、整条提示的 几个 token)与直接合成其人声的姓氏在同等程度上暗示了 名人生成。这令我们更接近诱导责任线,而不是 远离它 — 正如边界一样。
ADR-04:独立 UI 和缓存优先于 DB 行¶
一个固定目录上没有 DB 行意味着更严格的封装。
ADR-05:通常情况下,唯一语言模型是遗嘱¶
在预设目录界面驱动下使用与数据库相同的设计语言 — 引擎自身负责。画布入口加至界面推出的音量配置 —— 保留其演变的可运行性。
ADR-06:与音乐审查相关的扩展¶
在 V 上为项目自身的声音中添加有谱的歌唱和旋律脚本。从 同一模型包推送记忆和谐的叙事。
4.4 前端¶
- 重写
frontend/src/pages/VoiceGallery.jsx— 分区切换、分面筛选栏、精选网格、带懒加载预览的浏览网格、卡片操作。移除名人CATEGORY_ICONS映射。 - 新增
frontend/src/api/archetypes.ts以及frontend/src/api/hooks.ts中的 hooks(useArchetypeCategories、useArchetypes(filters)),遵循现有的 react-queryqueryKeys/useQuery模式。 - 新增
frontend/src/store/gallerySlice.ts— 收藏的原型 ID(持久化)、当前筛选条件、当前分区、视图模式。注册于store/index.ts。(原型不在数据库中,因此收藏保存在客户端。) - 卡片操作: 预览(播放 WAV)· 使用语音(主要操作 →
POST /archetypes/{id}/use→ 保存为 profile,随处可用)· 在 Designer 中打开(次要操作 →setInstruct+setVdStates+ 切换到 Design 标签页)· 收藏。
4.5 国际化¶
所有新字符串 → archetypes.* 和现有 gallery.* 键;en.json 为权威文件,在翻译完成前,其余 20 个语言环境回退到英语。中文方言显示名称属于功能性模型词汇——将其加入 test_no_hardcoded_cjk.py 的允许列表并说明理由,不要在其他地方内联。
5. 已确定的决策¶
- 目录方案:C(混合式) — 约 24 个精选条目 + 生成的浏览全部列表。
- 分类:特质/用例,取代所有以真实人物命名的类别。
- 卡片主要操作:使用语音(保存为 profile);Designer 为次要操作。
- 浏览全部预览:按需渲染 + 磁盘缓存(不预渲染数百个)。
- 精选预览:通过
render_demos_omnivoice.py预渲染 WAV。
6. 跨平台 / 约束合规(CLAUDE.md)¶
- 默认功能对等(严格): 原型浏览 + 按需预览 + “使用语音”在 macOS/Windows/Linux 上行为一致(纯 Python 合成 + 静态资源——无平台相关代码)。yt-dlp 导入器是唯一的平台可变暴露面 → 以状态形式呈现;如果缺少 yt-dlp/ffmpeg,导入器会优雅降级。默认行为不会因平台而不同。
- 向后兼容的数据: 不对
voice_profiles做 schema 变更;voice_gallery表原样复用。无需 alembic 迁移。现有 profiles/导入不受影响。 - 本地优先: 除用户主动发起的导入器下载外,无网络调用。
- 版本管理: 随 v0.3.0 发布;不涉及版本讨论。
7. 分阶段实施计划¶
阶段 1 — 原型引擎(TDD)。 backend/core/archetypes.py + backend/tests/test_archetypes.py。先写测试:(a) 每个生成的 instruct 仅由验证器 _INSTRUCT_ALL_VALID 集合中的 token 组成,并遵循每类别唯一性;(b) 口音和方言不会同时出现;(c) 生成数量在数百级别;(d) 不合理的组合被剪除(不允许 child + very low pitch,不允许 elderly + very high pitch);(e) ID 唯一且跨运行稳定;(f) 精选条目有效且包含 use_case + script。
阶段 2 — 原型 API。 backend/api/routers/archetypes.py + 注册到 main.py + 预览缓存目录。测试:分类、列表 + 每个筛选条件、分页、404、针对预渲染精选 id 的 /preview、/use 正常路径(受模型门控/在缺少权重时跳过)。
阶段 3 — 去名人化导入器。 从 gallery.py 和前端中的 'celebs'/'famous voice' 默认值中移除名人 CATEGORIES;保留上传/裁剪/另存为 profile。更新受影响的测试。
阶段 4 — 前端数据层。 api/archetypes.ts、hooks、store/gallerySlice.ts,注册 slice。对 slice/util 进行类型检查 + 单元测试。
阶段 5 — 前端 UI。 重写 VoiceGallery.jsx:分区切换、分面栏、精选 + 浏览网格、懒加载预览、卡片操作、My Imports 导入器。
阶段 6 — i18n。 向 en.json 添加 archetypes.* 键;确认允许列表之外没有硬编码 CJK。
阶段 7 — 精选预览资源。 扩展 render_demos_omnivoice.py 以渲染约 24 个精选 WAV(开发机步骤,固定种子)。如果某个 WAV 缺失,精选项回退到按需渲染。
阶段 8 — 验证。 后端 pytest、前端 typecheck/test/lint、test_no_hardcoded_cjk.py、对图库标签页进行手动冒烟测试。
8. 不在范围内¶
- 项目自建的名人/角色目录。
- 用于抓取的自动年龄门槛 / cookie 绕过。
- 情感风格 instruct token(引擎不支持)。
- GitHub 应用自动提交 bug 报告的路径(无关)。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 om/VoiceStudio