跳转至

已取代(2026-06-25) 由 00-roadmap-elevenlabs-parity.md 及规范 01–03 取代。保留作历史参考。

Studio / Projects — v1 规范

目标: 实现 ElevenLabs-Studio 对长篇叙述的同等能力。用户粘贴(或拖入)一份 10 页的脚本,应用将其拆分为多个块(block),用户为每个块分配音色,内联预览,然后点击“生成”得到一条拼接好的 WAV。

v1 不包含:视频同步、多轨混音、音乐衬底、音效(SFX)、未拼接音频的实时播放。这些将在后续版本加入。

1 — 数据模型

复用 studio_projects。为 state_json 增加一个严格结构:

interface ProjectState {
  kind: 'studio';           // discriminates from dub projects (same table)
  blocks: Block[];
  default_voice_id: string; // profile_id used for blocks that don't pin one
  default_lang?: string;
  created_by_version: string;
}

interface Block {
  id: string;               // uuid, stable across edits
  text: string;
  voice_id?: string;        // overrides project default; null → inherit
  pause_before_ms?: number; // inserted silence (0–5000)
  pause_after_ms?: number;
  // Generation state — server-owned, not user-edited
  gen?: {
    audio_path: string;     // absolute path to per-block WAV in scratch
    duration_ms: number;
    hash: string;           // SHA-256 of (text, voice_id, gen knobs) — cache key
    generated_at: number;
  };
}

需要两个迁移: - ALTER TABLE studio_projects ADD COLUMN kind TEXT DEFAULT 'dub' 这样我们就可以在列表视图中区分 studio 与 dub 项目。 - 一个以 hash 为键的 project_block_cache 表,这样重新打开项目时可无需重新生成而直接回放已有音频。(v1 可跳过,只需将路径存储在 state_json.blocks[*].gen 内 —— 单写入者,无并发编辑。)

2 — 后端端点

只有两个新路由;其余全部复用现有生成逻辑:

POST /studio/projects/{id}/blocks/{block_id}/generate
   body: { text, voice_id, knobs? }  (knobs = same FormData shape as /generate)
   response: { audio_path, duration_ms, hash }
   Internally calls the same TTS pipeline /generate does, just writes to
   scratch under projects/{id}/ instead of the global history dir.

POST /studio/projects/{id}/stitch
   body: { include_block_ids?: string[] }   // defaults to all
   response: { audio_path, duration_ms }
   Reads blocks in order, inserts silence for pause_before/after_ms,
   concatenates via ffmpeg, returns final WAV.

另外,扩展现有 PUT /projects/{id} 以接受新的 state_json 结构 —— 零代码改动,因为它已经是 JSON blob 透传。

其余一切(列表、创建、删除、用于音色选择器的 profiles 端点)均已构建完成。

3 — UI 形态

一个新路由:/studio/:projectId。像处理 CloneDesignTab 那样进行懒加载。

┌─────────────────────────────────────────────────────────────────┐
│  ← Projects    [project title, inline-edit]        [Export WAV] │
├────────────────┬────────────────────────────────────────────────┤
│                │                                                │
│  BLOCK LIST    │  BLOCK EDITOR (selected block)                 │
│  (left rail)   │                                                │
│                │   ┌─────────────────────────────────────────┐  │
│  ┌───────────┐ │   │  [textarea — the text for this block]   │  │
│  │ Block 1 ▸ │ │   │                                         │  │
│  │ "In a…"   │ │   └─────────────────────────────────────────┘  │
│  │ [voice:A] │ │                                                │
│  │ ▶ 0:12    │ │   Voice:  [SearchableSelect — profiles ▾]      │
│  └───────────┘ │   Pause before: [___] ms                       │
│  ┌───────────┐ │   Pause after:  [___] ms                       │
│  │ Block 2 ▸ │ │                                                │
│  │ "The…"    │ │   [Generate block] [Preview] [⚙ advanced ▾]    │
│  │ [voice:B] │ │                                                │
│  │ ▶ 0:08    │ │   ── Generated audio ──────────────────────    │
│  └───────────┘ │   [waveform] [▶ play] [hash: 3f2a…]            │
│  ┌───────────┐ │                                                │
│  │ + add     │ │                                                │
│  └───────────┘ │                                                │
│                │                                                │
├────────────────┴────────────────────────────────────────────────┤
│  TRANSPORT                                                      │
│  [▶ play all]  [Regenerate stale (3)]  [Stitch & export WAV]    │
│  ▓▓▓▓▓▓▓▓▓░░░░░░░░  block 3 of 12 · 1:42 / 6:10                │
└─────────────────────────────────────────────────────────────────┘

关键 UX 设计要点:

  • 粘贴即拆分:用户粘贴长脚本时,应自动按段落分界将其拆分为多个块(复用 backend/services/subtitle_segmenter.py —— 它已能处理句子边界)。v1 不提供手动创建块。
  • 过期指示器:如果 block.gen.hash ≠ hash(current text+voice+knobs),显示 🔄 徽章。传输栏中的“重新生成过期块”按钮会并行处理所有过期块(后端已通过 loop.create_task 实现并行安全)。
  • 音色继承:块中音色为空 = 使用项目默认值。下拉框显示“↳ 默认(Voice A)”以便一目了然。
  • v1 中没有时间线标尺。 块采用纵向列表而非横向时间线。带有每块时长可视化的横向时间线是 v2 的内容——这涉及大量 UI 工作,而且直到用户需要配合音乐衬底进行编排之前,都不需要它。

4 — 可复用原语(已构建)

组件 位置 复用方式
SearchableSelect frontend/src/components/ 音色选择器
WaveformTimeline frontend/src/components/ 单块播放
Profiles API (listProfiles) frontend/src/api/profiles.ts 音色下拉数据源
TTS generation backend/api/routers/generation.py (/generate) 逐块生成
Subtitle segmenter backend/services/subtitle_segmenter.py 粘贴即拆分
SSE 进度 backend/utils/hf_progress.py 流式返回生成状态
ffmpeg concat backend/services/ffmpeg_utils.py 拼接

5 — 黄金路径(用户操作流程)

  1. 首页 → 项目 → 新建 Studio 项目 → 自动创建 id,进入 /studio/:id
  2. 粘贴 5 段文本 → 自动拆分为 5 个块,全部指定为项目默认语音
  3. 点击块 3 → 将语音切换为第二个音色档案(叙述者 → 角色)
  4. 点击重新生成过期块 → 后端并行发起 5 个 /studio/.../generate 调用,进度流式返回
  5. 点击▶ 全部播放 → 客户端按顺序播放每个块的音频,并带静音间隔(预览无需拼接)
  6. 拼接并导出 WAV → 后端执行拼接,返回文件路径,Tauri 在 Finder 中显示

6 — 按周工作划分

天 工作内容
第 1 天 后端:schema 迁移、/studio/projects/{id}/blocks/{block_id}/generate 路由、/studio/projects/{id}/stitch 路由。复用 subtitle_segmenter 实现粘贴拆分。
第 2 天 前端:StudioPage.jsx 骨架 + 路由 + 新建项目功能。左侧栏块列表。右侧面板块编辑器(文本 + 语音 + 停顿)。
第 3 天 逐块生成逻辑接入 + 过期哈希检测 + 播放控制条 + 并行重新生成。复用 WaveformTimeline 进行预览。
第 4 天 全部播放序列处理(客户端 — Web Audio 队列)、拼接与导出。Toast 提示 + Finder 显示。
第 5 天 粘贴拆分、键盘快捷键(⌘↩ 重新生成,⌘S 保存,⌘E 导出)、空状态、细节打磨。
第 6 天 发布测试:在全新安装的 DMG 上,创建一个包含 10 个块的项目,切换语音,导出。修复所有出现的问题。
第 7 天 预留缓冲时间 / 文档 / 发布版本。

7 — 明确不在 v1 范围内

  • 水平时间标尺 / 波形拖拽时间线
  • 块间过渡(交叉淡化、闪避)
  • 音乐 / 音效垫层
  • 多轨(并行语音层)
  • 从 .docx / .fdx 导入(编剧格式)
  • 可共享的项目链接(本地优先 — 跳过)

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 om/VoiceStudio