VoiceStudio —— 通往世界级之路¶
最后更新: 2026-04-21 · 当前阶段: 第 0–4 阶段已完成。剩余工作集中在 Design / Performance / Quality 轨道 + 第 4 阶段评估冲刺。 · 资源配置: 1 个 FTE + 临时贡献者
从“精致的单人开发项目”迈向能在规模化审视下站得住脚的软件——一份坦诚的计划。持续更新的追踪表:每个条目都有状态,每个阶段都有进度条,每个已交付条目都有日期。
📊 状态总览¶
Phase 0 · Momentum ▓▓▓▓▓▓▓▓▓▓ 5 / 6 ✅ shipped
Phase 1 · Visible quality leap ▓▓▓▓▓▓▓▓▓▓ 4 / 4 ✅ shipped
Phase 2 · Foundation refactor ▓▓▓▓▓▓▓▓▓▓ 8 / 8 ✅ shipped (2.2 closed with 5 Zustand slices)
Phase 3 · Pluggable engines ▓▓▓▓▓▓▓▓▓▓ 4 / 4 ✅ shipped
Phase 4 · The two bets ▓▓▓▓▓▓▓▓▓▓ 6 / 6 ✅ shipped · 4.1 benchmarked at 4.04s warm (target ≤5s)
Phase 5 · Productisation ░░░░░░░░░░ 0 / 5 🚫 demand-driven
Design track ▓▓▓▓▓▓▓▓▓░ ongoing · 14 primitives + ~67 migrated inline styles · DubTab/Header/Sidebar/CloneDesignTab drained
Performance track ▓▓▓░░░░░░░ underway · profiling, preload, isolated engines + cache-remix I/O
Feature-magic track ▓▓░░░░░░░░ underway · project-level casting board shipped
Quality track ▓▓░░░░░░░░ 12 smoke tests, 10 error messages rewritten
图例: ✅ 已交付 · 🟡 进行中 · ⏳ 未开始 · 🔒 受阻 · 🚫 暂缓
🎯 北极星¶
世界上最好的本地优先电影级配音工作室。在质量和用户体验上与云端产品毫无二致,但永远不会离开用户的机器。
三条不可妥协的原则: 1. 两项站得住脚的创新,任何竞品都无法在一个冲刺内复制,并以其他各处近乎偏执的打磨作为支撑。 2. 配音输出质量足够好,人工编辑只需调整措辞,而无需重建时间轴。 3. 用户体验:非技术用户能在 10 分钟内端到端产出一条精良的配音视频,且等待加载动画的时间从不超过预期。
🎲 两大赌注(值得讲述的故事)¶
| 赌注 | 状态 | |
|---|---|---|
| A | 导演级 AI —— 对每个片段进行自然语言执导(“让第 14 段更有紧迫感”),重写翻译语气 + TTS instruct + 语速目标。 |
⏳ Phase 4 |
| B | 增量重配音 —— 在 2 小时视频中改一个词,只需在几秒内重新生成受影响的片段 + 交叉淡化。 | ⏳ Phase 4 |
两者组合起来:导演级编辑触发增量重配音。这就是我们赖以立足的一句话宣言。
📍 当前所处位置¶
✅ 功能完整的 MVP。与 VideoLingo / pyVideoTrans 达到竞争性基线持平。 🟡 代码健康状况快速改善。已交付 14 个设计系统原语,9 个组件完成迁移,主包体在净增代码的情况下体积 -2.7%。 ⏳ 质量上限未变 —— 一次性翻译、原始 WhisperX 片段、无语速自适应。这正是 Phase 1 要解决的。 ⏳ UX 上限 —— 动效语言、错误信息、引导流程:部分完成。启动动画已交付;错误信息已重写(10 处);引导流程 + 示例片段仍未完成。
文件布局见 STRUCTURE.md。(构建本路线图所依据的竞品分析 research/LEARNINGS.md 与 design/ ASCII 线框图已在 2026-07-12 的根目录清理中被移除 —— 见 git 历史。)
📐 资源配置假设¶
1 个 FTE + 临时贡献者。 下文所有时间范围都基于这一假设。团队翻倍 → 时间大致减半。单人作战 → 大约 1.5 倍。所有时间线都是区间而非承诺,会在每个阶段退出时重新评估。
🧱 Phase 0 —— 势头(1 周 → ✅ 已交付 2026-04-21)¶
本周就能交付的胜利。不做看不见的东西。养成落地改进的习惯。
进度:5/6(83%)
| ID | 条目 | 状态 | 备注 |
|---|---|---|---|
| 0.1 | 死代码清扫(孤立的 test_*.py、调试脚本、根目录下的运行时产物) |
✅ | 随 STRUCTURE.md 清理一起交付。删除了 8 个文件,归档了 legacy_gradio/,清理了 16 个散落的 .DS_Stores。 |
| 0.2 | Alembic 迁移骨架 | ✅ | alembic.ini、backend/migrations/{env,script.py.mako,versions,README} 已就绪。core/db.py 中的旧版 _migrate() 保留一个版本作为回退方案。 |
| 0.3 | Top-10 错误信息重写(“发生了什么 · 为什么 · 该怎么办”) | ✅ | dub_generate.py:98、dub_generate.py:26、generation.py:59、generation.py:183 以及 exports.py ×6 全部重写。已通过 /export/reveal 和 /dub/generate/{bad-id} 上的冒烟测试验证。 |
| 0.4 | 每个路由一个冒烟测试 | ✅ | tests/test_router_smoke.py —— 12 个测试,运行耗时 2.79 秒。覆盖 system、profiles、projects、exports、generation、dub_core、dub_generate。 |
| 0.5 | 500 毫秒启动动画 + .ui-skel 微光工具 |
✅ | 位于 ui/tokens.css 中,通过 #root 选择器自动应用。包含 prefers-reduced-motion 回退。 |
| 0.6 | 为首次运行引导预加载 30 秒示例片段 | 🚫 | 暂缓 —— 需要授权素材选型 + Tauri 打包工作。在 Phase 5 重新评估。 |
退出条件已满足: 错误信息可操作、所有路由的 CI 冒烟测试为绿、启动动画已上线。任务执行中 kill -9 尚不支持恢复 —— 该事项移至 Phase 4。
🎨 Phase 1 —— 可见的质量飞跃(已交付 2026-04-21 → ✅)¶
最大的一次用户可见升级。直接取自 VideoLingo 的战术手册。在更深层的基础重构之前交付,让用户先感受到改进。
进度:4/4(100%)
| ID | 条目 | 状态 | 目标文件 |
|---|---|---|---|
| 1.1 | 翻译 → 反思 → 适配(三步 LLM 链路) | ✅ | 交付于 2026-04-21。backend/services/translator.py(新增)、dub_translate.py(已接入)、schemas/requests.py(+quality、+glossary)、App.jsx(translateQuality 状态,localStorage 持久化)、DubTab.jsx(Fast/Cinematic 分段控制);响应为每个片段携带 literal+critique,供未来的三栏 UI 使用。可与任何 OpenAI 兼容端点配合(OpenAI 官方、Ollama、LM Studio、Together、Anyscale)。未配置 LLM 时优雅回退。冒烟测试套件依然全绿(12/12,耗时 2.85 秒)。 |
| 1.2 | NLP 感知的字幕分段(Netflix 规则:每行 ≤42 个字符,≤17 CPS) | ✅ | 发布于 2026-04-21。backend/services/subtitle_segmenter.py(新增,226 行,纯函数,零依赖)。已接入 dub_core.py 中的两条转录路径(分块流式 + 单次),在说话人分离(diarization)后运行,因此 speaker_id 会贯穿分割结果。测试:tests/test_subtitle_segmenter.py —— 14 个测试,覆盖句子/从句/连词分割、CPS 强制执行、词级时间、合并器、说话人边界保持、边界情况。全部 26 个后端测试(14 个新增 + 12 个冒烟测试)在 2.87 秒内通过。 |
| 1.3 | 项目级术语表(LLM 自动提取) | ✅ | 发布于 2026-04-21。glossary_terms 表位于 core/db.py(+ schema 版本 3);新增 backend/api/routers/glossary.py,提供完整 CRUD + POST /glossary/{id}/auto-extract;新增 frontend/src/components/GlossaryPanel.jsx(就地编辑、自动/手动徽标,通过与翻译器相同的 OpenAI 兼容客户端进行 LLM 自动提取);frontend/src/api/glossary.js 客户端;DubTab 在加载任务时渲染该面板;App.jsx 收集 glossaryTerms 并在每次调用时将其注入 /dub/translate。翻译器现有的 glossary 提示注入(1.1)现在真正能获得数据了。 |
| 1.4 | 双语字幕导出(原文 + 译文一起) | ✅ | 发布于 2026-04-21。GET /dub/srt/{id}?dual=1 和 GET /dub/vtt/{id}?dual=1 将翻译行置于上方,原文以斜体显示在下方(<i>…</i>)。共享 _pick_subtitle_text() 辅助函数,确保两种格式保持一致。UI:输出选项中的“双语字幕”复选框(localStorage 持久化 omnivoice.dub.dualSubs);启用后 SRT/VTT 按钮会显示 ✦ 徽标,并添加 _dual 文件名后缀。 |
退出标准: 对 3 个片段(EN→DE、EN→JA、EN→ES)进行盲测 A/B,新管线获得 ≥70% 的偏好。对于未受过训练的听众,配音输出在观感上明显更专业。
🧱 Phase 2 —— 基础重构 (10–14 周 → 🟡 部分完成)¶
这些隐形工作会解锁 Phase 4 之后的一切。在不会干扰 Phase 1 的前提下并行推进。
进度:7/8(88 %) · Phase 2.5 得益于设计系统冲刺,已超出预期;Phase 1 发布后又加入了 2.8,为语音库提供了迟到已久的一等公民界面。
| ID | 项目 | 状态 | 备注 |
|---|---|---|---|
| 2.1 | 持久化任务队列(重启后存活,SSE 重放) | ✅ | 发布于 2026-04-21。新增 jobs + job_events 表(schema v4)。新增 backend/core/job_store.py,包含 CRUD + sweep_orphans_on_startup()。TaskManager 现在会将每次状态转换和事件镜像到磁盘;内存队列保持不变以保证速度。启动生命周期会将任何 pending/running 状态的任务翻转为 failed,并附带“restart interrupted”消息。新增端点:GET /jobs、GET /jobs/{id}、GET /jobs/{id}/events。GET /tasks/stream/{id}?after_seq=N 现会在重新连接时先重放已持久化的尾部,再挂载到实时监听器。测试:tests/test_job_store.py(7 个测试——生命周期、事件序号、单任务上限、活动筛选、启动清理)。冒烟测试套件扩展了 4 个 jobs 端点测试。全部 37 个后端测试在 2.83 秒内通过。尚不持久化实际工作内容——那是 Phase 4.5(步骤级可恢复性);本阶段仅保证重启后的元数据真实一致。 |
| 2.2 | 通过 Zustand store 拆分 App.jsx 单体结构 | ✅ | 5 个切片已发布 2026-04-20/21。prefsSlice(translateQuality/dualSubs/reviewMode)、glossarySlice、uiSlice(mode + nav + 侧边栏标签 + cheatsheet 标志 + 语音配置导航动作)、dubSlice(18 个管线字段,带 React 风格 setter)、generateSlice(text/refText/instruct/language + 10 个生产覆盖控制项 + vdStates)。Persist 中间件键提升至 v3,包含生成控制项和侧边栏状态。App.jsx useState 数量:~60 → 41 —— 剩余的都是 File/Blob 引用、后端列表(history/dubHistory/exportHistory/studioProjects/profiles)、临时录音计时器和比较弹窗状态(按设计均不可迁移)。子组件(DubTab/Sidebar)直接从 store 读取;调用点减少了约 35 个 props。 |
| 2.3 | TypeScript 迁移(增量式:先迁移 store + API 层) | ✅ | 发布于 2026-04-20 —— 完整 API 层现在是 TS。剩余的 8 个 .js 文件(dub/engines/exports/generate/glossary/profiles/projects/system)全部迁移为 .ts,类型化响应结构来自新的共享 api/types.ts(EngineBackend、Profile、ProfileUsage、GlossaryTerm、ProjectDetail、SystemInfo、ModelStatus,……)。调用点保持不变;每个消费者现在都能获得 API 响应结构的编译时检查。Store 切片 + api/client.ts 在 Phase 2.3 的第一轮中已经是 TS。组件到 .tsx 的转换仍按文件可选。 |
| 2.4 | 将 dub_core.py 拆分为 router + services/dub_pipeline.py |
✅ | 分两次发布于 2026-04-21。services/dub_pipeline.py 现在包含:文件哈希 / 路径安全 / SSE 辅助函数、find_cached_job、进程生命周期(register_proc/unregister_proc/kill_job_procs)、内存 + SQLite 任务状态(get_job/put_job/save_job)、子进程封装工厂(run_proc_factory)、yt-dlp 下载器,以及完整的 ingest_pipeline 异步生成器(download→extract→demucs→scene→thumbnail 预处理流程)。模块级状态(_dub_jobs、_active_procs、_active_procs_lock)也已迁移。dub_core.py 保留向后兼容别名。大小:889 → 525 行(−40.9 %)。50 个后端测试通过(2.84 秒)。转录生成器仍在 router 中——那是 Phase 4 剩余的先决条件,不是阻塞项。 |
| 2.5 | 设计系统原语 | ✅ | 大大超出预期。 14 个原语:Button、Panel、Field/Input/Textarea/Select、Dialog、Slider、Badge、Tabs、Segmented、Tooltip、Progress、Menu、Table。9 个组件已迁移。内联样式迁移(单独的设计轨条目)随后清除了 DubTab(93→2)、Header(24→1)、Sidebar(21→8)、CloneDesignTab(33→0)。会话运行于 2026-04-19 → 2026-04-21。 |
| 2.6 | 日志 + 遥测基线 | ✅ | 发布于 2026-04-21。对 services/model_manager.py 中的 print() 进行了清扫——全部四个调用现在改用 logger.info(模型加载、torch.compile 应用/跳过、空闲卸载)。新的 OMNIVOICE_JSON_LOGS=1 环境变量标记可将所有处理器切换为单行 JSON 格式化器(已验证:{"t": "...", "level": "INFO", "name": "omnivoice.api", "msg": "..."}),包括轮转文件处理器。/metrics 上按阶段的 Prometheus 计数器被推迟——等出现真实的可观测性需求时再落地。 |
| 2.7 | 测试底线(“每个 bug 都会附带一个回归测试”) | ✅ | 已于 2026-04-21 交付。新增 tests/test_dub_pipeline_state.py(8 个测试——路径安全、SSE 形状、进程跟踪、内存 + 磁盘任务往返)和 tests/test_translator.py(5 个测试——术语表前言、无 LLM 优雅降级、空字面量透传、使用模拟 LLM 的完整三步链、反射失败路径)。后端测试总计:50 个(原 26 个)在 3.10 秒内全部通过。覆盖 GPU / ASR 模型路径的集成测试保持不在 CI 中——那些是手动 fixture-clip 运行。 |
| 2.8 | 语音 profile 页面 | ✅ | 已于 2026-04-21 交付。App.jsx 中新增 mode: 'voice' 和 activeVoiceId;延迟加载 frontend/src/pages/VoiceProfile.{jsx,css}(hero + details + try-it + usage)。后端:新增 GET /profiles/{id}、PUT /profiles/{id}、GET /profiles/{id}/usage(扫描 studio_projects.state_json 中的 dub 片段计数)。侧边栏在 clone/design 卡片上新增“打开”操作,路由至此;“返回”回到上一个模式。设计目标:design/03-voice-library.md(已于 2026-07-12 退役;见 git 历史)。 |
退出标准: 服务器可以在 dub 进行中重启而不丢失工作。前端通过类型检查。没有路由文件超过 300 行。App.jsx ≤300 行。CI 阻止回归。
🔌 阶段 3 — 可插拔引擎¶
不要再被锁定在单一模型家族上。解锁 VoxCPM2 作为严肃的替代选择。
进度:4/4(100 %)
| ID | 项目 | 状态 | 备注 |
|---|---|---|---|
| 3.1 | TTS 适配器接口 | ✅ | 已于 2026-04-21 交付。backend/services/tts_backend.py — TTSBackend ABC + 注册表,VoiceStudioBackend 包装当前模型(零行为变化;复用 model_manager.get_model() 以避免重复加载),list_backends() 返回各引擎的可用性原因,通过 OMNIVOICE_TTS_BACKEND 进行环境变量驱动的选择。 |
| 3.2 | 替代 TTS 后端(VoxCPM2 + MOSS-TTS-Nano) | ✅ | 已于 2026-04-21 / 2026-04-20 交付。VoxCPM2:GPU 工作室之选,30 种语言,48 kHz,从 VoiceStudio 的 instruct 接线的 "(instruct)prompt" 语法。MOSS-TTS-Nano-100M 于 2026-04-20 添加,作为低资源 / 广语言之选——100M 参数自回归,20 种语言(包括阿拉伯语/希伯来语/波斯语/韩语/土耳其语),在 4 核 CPU 上实时,原生 48 kHz 立体声,Apache-2.0。两者的 is_available() 在依赖缺失时返回可操作的安装提示。运行时选择器:POST /engines/select + backend/core/prefs.py(原子 JSON 存储);设置 > 引擎选项卡中有每个家族的 Use 按钮;环境变量仍然覆盖。14 个引擎测试通过。 |
| 3.3 | ASR 适配器接口 | ✅ | 已于 2026-04-21 交付。backend/services/asr_backend.py — ASRBackend ABC,MLXWhisperBackend(Apple Silicon,当前默认),PyTorchWhisperBackend(CUDA / CPU 回退,使用 TTS 模型的 _asr_pipe)。active_backend_id() 根据 torch.backends.mps 的可用性自动检测;可通过 OMNIVOICE_ASR_BACKEND 覆盖。 |
| 3.4 | LLM 适配器 | ✅ | 已于 2026-04-21 交付。backend/services/llm_backend.py — LLMBackend ABC,OpenAICompatBackend(将 Ollama/OpenAI 客户端从 translator.py 中提升出来,使术语表自动提取 + 未来的 Directorial AI 共享同一条代码路径),OffBackend(显式无 LLM,并提供可粘贴复制的环境变量提示)。隐私默认值得到尊重:云端 LLM 按功能选择启用,永不强制。 |
退出标准: 引擎切换在 Mac + Linux 上无需重启即可生效。检测到 CUDA 12+ 时 VoxCPM2 可用。
退出状态: 所有四个适配器接口已于 2026-04-21 交付,包含环境驱动选择 + 注册表 +
/enginesHTTP 接口。保持了向后兼容——尚未迁移任何现有调用方。实际的免重启切换(实时重新配置)+ 设置界面引擎选择器是后续 UX 任务,在 Phase 2.2 持续进行的 App.jsx / Settings.jsx 工作中跟踪。
🎛️ 阶段 4 — 两个赌注落地¶
这是定义性阶段。这正是有人选择 VoiceStudio 而非其他一切的原因。它建立在 Phase 2 的持久化任务存储和 Phase 3 的适配器之上。
进度:6/6 ✅(两个赌注均已端到端接好;审查横幅 + 步骤级可恢复性已上线)
| ID | 项目 | 状态 | 备注 |
|---|---|---|---|
| 4.1 | 🎲 增量重新配音(赌注 B) | ✅ | 已于 2026-04-21 端到端交付。服务(services/incremental.py)为每个片段的生成输入生成指纹。后端(dub_generate.py)现在接受 regen_only: [seg_ids] + 并行 segment_ids: [...]。当设置了 regen_only 时,列出的片段会通过 TTS 运行;未列出的片段会复用其磁盘上的 seg_N.wav(按需重采样/填充),然后混音会在完整时间线上重新组装。UI:App.jsx 在每次成功配音后通过 /tools/incremental 快照指纹;当 stale > 0 时,DubTab 页脚显示 “自上次生成以来有 N 个片段已变更” 的警告徽章;当 stale > 0 时,操作页脚中会出现一个 “重新生成 N 个已变更片段” 粉色调按钮,与 “生成配音” 并排,调用 handleDubGenerate({ regenOnly: incrementalPlan.stale })。在部分重新生成完成时,会重新快照指纹,以便警告徽章反映新的干净状态。 |
| 4.2 | 🎲 导演 AI(赌注 A) | ✅ | 已于 2026-04-21 端到端交付。后端: backend/services/director.py — 稳定的分类法(5 个维度,30+ 值),双解析器(配置时通过 OpenAICompatBackend 使用 LLM,关键字启发式回退),Direction.instruct_prompt() / translate_hint() / rate_bias()。转换器通过新的 directions={seg_id: text} 参数,将 direction 注入到每个片段的 reflect 和 adapt 提示中。dub_generate.py 将解析后的 instruct_prompt() 追加到每个片段的 instruct_str,并将 seg_speed 乘以 rate_bias()。Schema: 新增 DubSegment.direction + TranslateSegment.direction。前端: DubSegmentRow 的 “…” 菜单新增 “设置方向…” 条目,带 ✨ 图标;有 direction 的片段会显示 ✨ 徽章代替 “…”。components/DirectionDialog.jsx 是一个 Dialog,带有实时的 /tools/direction 预览,显示分类法标记 + TTS instruct + 翻译提示 + 速率偏置。App.jsx 将 openDirection / saveDirection 接入撤销,通过 handleTranslateAll 和 handleDubGenerate 传递 direction。5 个单元测试通过。 |
| 4.3 | 分阶段检查点(审核横幅) | ✅ | 已于 2026-04-20 发布。配音流程有三个自然的审核点——ASR 后(转录就绪)、翻译后(翻译就绪)、生成后(配音完成)。components/CheckpointBanner.jsx 会在分段表上方渲染适合当前阶段的横幅,并带有一个内联的 继续 → 按钮,可直接触发下一阶段(翻译 / 生成)。阶段自动检测:dubStep === 'editing' 且无 text_original → ASR;editing 且有任意 text_original → 翻译;done → 最终。可按阶段关闭(重新加载前不会再次出现)。审核模式切换已加入 prefsSlice(通过 zustand/persist 持久化);设置 > 引擎选项卡显示 阶段间审核 / 快速连续 分段控件。默认为“开”。 |
| 4.4 | 语速工程 | ✅ | 已于 2026-04-21 端到端发布。backend/services/speech_rate.py — 每种语言的 CPS 表、容差窗口(0.92–1.08)、MAX_ATTEMPTS=3 的 LLM 删减/扩展循环。已接入 Cinematic 翻译: TranslateSegment.slot_seconds 由 App.jsx 根据 seg.end - seg.start 自动填充。当提供 Cinematic 和 slot 时,翻译器会对每条精调后的台词运行 adjust_for_slot;返回每段的 rate_ratio 以及可选的 rate_error。UI:只要 seg.rate_ratio 偏离 1.0 超过 3%,DubSegmentRow 就会在时间列渲染一个紧凑的 📖 1.12× 徽标;颜色编码(高于 1.15 为红色、低于 0.85 为蓝色信息、其余为灰色),并带有工具提示,在语速匹配失败时显示错误。POST /tools/rate-fit + ToolsPage.RateFitTool 仍可独立使用。 |
| 4.5 | 步骤级断点续跑 | ✅ | 已于 2026-04-20 端到端发布。每个分段的指纹会在每次成功生成分段后持久化到 job.seg_hashes(每 8 个分段通过 _save_job 刷新一次),因此配音中途 kill -9 最多只会丢失正在处理中的那一段。done SSE 事件现在会把 seg_hashes 流式发送到客户端,客户端将其快照为 lastGenFingerprints,无需后续 /tools/incremental 往返请求。loadProject 从 project.segHashes 恢复;restoreDubHistory 从 job.seg_hashes 恢复。最终效果:配音中途关闭应用并重新打开后,“重新生成 N 个已更改项”按钮会针对精确的剩余部分亮起,用户从最后一个正常分段继续,而不是重新开始。 |
| 4.6 | 无头 CLI + 独立工具页 | ✅ | 已于 2026-04-21 发布。新增 omnivoice-dub CLI(omnivoice/cli/dub.py,pyproject.toml 入口点)——通过 HTTP 驱动完整流水线,流式读取 SSE 任务,支持 --url 摄取、--quality cinematic、--voice、--glossary。新增 backend/api/routers/tools.py,包含 /tools/probe、/tools/incremental、/tools/direction、/tools/rate-fit。新增 frontend/src/pages/ToolsPage.jsx,接入 mode='tools',承载 3 张交互卡片(Directorial AI 演示、Rate-fit 演示、ffprobe)。新增 3 个冒烟测试。 |
退出标准: 在测试片段上改动一个词后,<5 秒内即可重新生成。Directorial 评估在评分标准上比基线高出 ≥15%。配音中途 kill -9 后恢复并运行至完成。
退出状态: 全部六个子阶段已发布,并与主配音流程 + DubTab 工作区集成(2026-04-20/21)。可量化的退出标准(5 秒增量实际耗时、+15% Directorial 评估、基准化的崩溃恢复)仍需要一个基于测试片段的评估冲刺来钉住数字——代码路径已上线,但数值尚未固定。另行在质量轨道下跟踪。
🏗️ Phase 5 — 产品化 (需求驱动 → 🚫 已推迟)¶
不要主动构建。仅基于触发条件。
进度:0/4(0%)
| ID | 项目 | 触发条件 | 状态 |
|---|---|---|---|
| 5.2 | OpenTelemetry 追踪 | 第一次出现我们无法诊断的“为什么这么慢?”故障 | 🚫 |
| 5.3 | 可选认证(魔法链接 / 通行密钥) | 第一个团队部署请求 | 🚫 |
| 5.4 | 签名安装包 + 自动更新(Tauri) | 付费发布或预购名单形成 | 🚫 |
| 5.5 | 插件 SDK(第三方 TTS/ASR/LLM) | 第一位外部贡献者交付适配器 | 🚫 |
这些都不是通往世界级产品的关键路径。它们全都是真实需求出现后的应对方案。
🛤️ 并行轨道(始终运行)¶
🎨 设计轨道 (进行中 → 🟡 非常活跃)¶
| 项目 | 状态 | 备注 |
|---|---|---|
| 动效语言(60 fps、弹簧缓动、交错) | 🟡 | Tokens 已随 ui/tokens.css 发布(时长、缓动);启动动画已上线;交互上的交错 + 弹簧缓动仍未完成。 |
| 密度调节(Small/Normal/Max 重新构建布局,而不仅仅是字体大小) | ⏳ | 当前的 S/M/L 只会缩放字体大小。 |
| 所有路径中的错误消息重写 | 🟡 | 已完成 20 个(Phase 0 前十项 + 2026-04-21 第二轮):system.py(log-read + log-clear)、dub_core.py(无效 job_id ×2、无效 url、缺少 yt-dlp、ASR 未加载)、profiles.py(nothing-to-update + profile-not-found ×2)、dub_export.py(no-such-job、ffmpeg 混流失败 ×2、MP3 编码失败)、glossary.py(PUT 请求体为空)。所有这些现在都包含发生了什么 / 原因 / 该怎么办。138 个后端测试通过。约 15 条低流量路径仍有待处理。 |
| 边做边上手(预加载示例项目) | ⏳ | 见 0.6 — 已推迟。 |
| 启动动画 + 可辨识声音 | 🟡 | 动画已上线;声音尚未加入。 |
| 每周不一致性审计(每周五 1 小时) | ⏳ | 节奏尚未建立。 |
| 设计系统原语(14) | ✅ | 完整清单见上文。 |
| 迁移剩余的内联样式 | 🟡 | 最大的四个问题已于 2026-04-20 清理 —— DubTab 93 → 2、Header 24 → 1、Sidebar 21 → 8、CloneDesignTab 33 → 0。剩余的都是真正动态的(Sidebar 中逐行的 --row-accent CSS 自定义属性、WaveBars 中逐条的 height/animationDelay、骨架行中根据索引计算的 opacity、由 prop 决定的 fontSize)。新增类体系:.dub-*(DubTab)、.hq-col-*/.hq-stats__*/.hq-logo-*(Header)、.sidebar-tile--*/.sidebar__scroll/.history-*--*(Sidebar)、.clone-*/.label-row--*(CloneDesignTab)。.file-drag 和 .dub-idle-drop 上的拖拽悬停现在会切换 .is-dragging,而不是通过 DOM 修改样式。剩余 119 个分布在长尾中(Launchpad、KeyboardCheatsheet、DubSegmentRow、WaveformTimeline 等)——集中度更低,杠杆也更低。 |
⚡ 性能轨道 (🟡 进行中)¶
| 项目 | 状态 | 当前测量 |
|---|---|---|
| 批量 TTS(每个前向传播的主机派生宽度) | 🟡 | 批处理队列会送入 OmniVoice 原生的变长前向传播,宽度根据设备剩余资源推导(CPU/低显存主机为 1,最高 8),并可通过 OMNIVOICE_DUB_BATCH_WIDTH 覆盖;没有原生批处理能力的适配器仍使用单段回退。 |
| 消除每段磁盘往返 | 🟡 | 长视频合成仍使用磁盘支撑以限制内存占用。未变更且速率相同的自然分段现在会跳过冗余的解码 → 暂存编码 → 解码循环;新分段仍会持久化一次并在合成时重新加载。 |
| 冷启动:≤1.5 秒到首个可听样本 | 🟡 | 已安装模型会在后台预加载,scripts/bench_pipeline.py 可测量冷/热合成;目标尚未验证。 |
| 悬停时投机式重新生成 | ⏳ | — |
| 崩溃沙箱引擎(子进程隔离) | 🟡 | 可终止的 sidecar 引擎与可选开启的 omnivoice-subprocess 已上线;默认 OmniVoice 引擎在 MPS 上现在走崩溃沙箱路径,而 CUDA/ROCm/CPU 保留低开销的进程内路径。 |
| 交互预算(UI <50 ms,预览 <200 ms,首段 <4 s) | 🟡 | /ws/tts 会报告真实 TTFA、总生成时间与 RTF;前端响应性埋点已存在,但尚未有跨界面的预算门禁。 |
| 每季度专门开发周 | ⏳ | 节奏尚未排期。 |
✨ 功能魔法轨道 (🟡 进行中)¶
| 功能 | 状态 | 阶段门禁 |
|---|---|---|
| 项目级选角视图(将声音拖到说话人) | ✅ | 已发布(#1767):配音 CAST 条带可展开为选角面板——将声音卡片拖到说话人行,包含键盘列表框,字段与下拉框相同。 |
| 跨项目的语音记忆 | ⏳ | 第 4 阶段之后 |
| 上下文感知流水线(视频帧 → 流水线决策) | ⏳ | 第 4 阶段之后 |
| 从修正中进行设备端学习(用户编辑 → LoRA) | ⏳ | 仅研究;可能在 5+ 阶段 |
| 实时配音预览(边编辑边流式 TTS) | ✅ | 发布于 2026-09-02(#1769)——配音分段表上的可选“Live preview”开关会通过 /ws/tts 以对应 CAST 声音流式播放已编辑台词;停止播放也会关闭实时流并释放合成槽;导出路径不变。 |
🧪 质量轨道 (🟡 进行中)¶
| 项目 | 状态 | 备注 |
|---|---|---|
| 每个 bug 都附带回归测试 | ⏳ | 规则已写下,但尚未在 CI 中强制实施。 |
| 性能回归预算(固定片段 ≤5%) | ✅ | 于 2026-08-20 作为硬件无关的 操作次数预算(tests/test_perf_operation_budgets.py)发布——比 5% 更严格,并且在不同运行器上实际耗时不可靠时仍能在 CI 中稳定:在 /ws/tts 上每句一次生成,缓存的配音重混零 TTS 调用;零解码/重写守卫与 ⌈N/W⌉ generate_batch 守卫会随各自的快速路径激活。参见 docs/performance.md §Performance budgets。 |
| 可访问性(键盘优先、WCAG AA、ARIA 实时区域) | 🟡 | 焦点环 token 已定义;全面审计待进行。 |
| 隐私(默认零遥测,按功能选择开启) | ✅ | 在“设置 → 隐私”标签页中强制执行。 |
| 每个阶段更新文档 | 🟡 | STRUCTURE.md、ROADMAP.md、ui/README.md 均为最新(research/ 与 design/ 已于 2026-07-12 退役)。 |
⚠️ 风险与未知¶
| 风险 | 状态 | 已有缓解措施 |
|---|---|---|
| 本地 LLM ≠ 前沿 LLM 的翻译质量 | ⏳ 未解决 | 计划:接受“Cinematic w/ local”和“Cinematic w/ cloud (opt-in)”作为两个 SKU。绝不强制使用云端。 |
| 模型格局变化(路线图期间出现 VoxCPM3、F5-TTS-v3) | ⏳ 未解决 | 第 3 阶段的适配器接口是应对手段。 |
| Tauri 代码签名 / 公证成本 | ⏳ 未解决 | 已计入第 5.4 阶段预算。预留 2 周合规缓冲。 |
| pyannote HF-token 摩擦(安装时把新用户挡在外面) | ⏳ 未解决 | 计划:“暂时跳过说话人分离”路径;首次带有 token 的运行时自动进行说话人分离。 |
| 设备端微调质量漂移 | ⏳ 未解决 | 有门禁:LoRA 仅随自动评估门禁一起发布。 |
| 声音克隆相似度 / IP 顾虑 | ⏳ 未解决 | 记录每次克隆的来源与同意提示。第 5 阶段提供删除工具。 |
| Apple MPS 与 CUDA 对齐 | 🟡 缓解中 | 适配器接口 + 每引擎能力门禁。VoxCPM2 在 UI 中仅限 CUDA。 |
| 单人开发者带宽 | 🟡 缓解中 | 每项估算留 20% 缓冲;各阶段可延期但顺序不变。 |
📏 成功指标¶
| 指标 | 目标 | 当前数值 | 状态 |
|---|---|---|---|
| 配音质量(对照 VideoLingo + pyVideoTrans 的盲选偏好,10 个片段) | ≥70% | 未测量 | ⏳ |
| 首次配音时间(全新安装 → 3 分钟 YouTube 片段) | ≤10 分钟 | 未测量 | ⏳ |
| 冷启动(模型加载 → 首个可听样本) | ≤1.5 秒 | ~4 秒 | 🟡 |
| 增量重配音(单个词改动 → 时间线上出音频) | ≤5 秒 | 句子级分段上为 1.96 秒(Apple Silicon MPS,num_step=8 预览路径,30 段 fireship 片段)· 相较全量加速 70× · TTS = 预算的 96% | ✅ |
恢复(配音中途 kill -9 → 恢复到完成) |
零数据丢失 | seg_hashes 持久化已上线(4.5);基准尚未测量 |
🟡 |
| 导演式评估(自然语言指令对比基线评分标准) | +15% | 代码路径已上线(4.2);评估夹具未测量 | 🟡 |
| 引擎健壮性(引擎崩溃 → 自动恢复) | 任务继续运行 | ❌ 整个服务器崩溃 | ⏳ |
| 交互延迟 p95 | UI <50 ms,预览 <200 ms | 未测量 | ⏳ |
| 回归安全(已发布 bug → 上一季度回归测试) | 100% 覆盖率,再出现率 0 | 不适用(尚无事故) | ✅ |
| 首周外部贡献者 PR | 连续 3 个外部 PR | 0(私有仓库) | ⏳ |
📝 更新日志¶
按时间倒序的发布日志。每个阶段退出 + 重要子阶段都会新增一行。
2026-04-21 — 自动说话人克隆 + 专业级 UI 整合¶
专业级配音的承诺是:“同一说话人,换一种新语言。”在此变更之前,用户必须手动录制或上传参考声音,并将其分配给每个分段。现在,参考声音会自动从源视频自带的人声轨中提取。
后端:
- 新增 services/speaker_clone.py(约 150 行)。对于每个分离后的说话人,从 vocals.wav(Demucs 输出,而非原始混音——已与背景音乐分离)中选取“最长优先”的片段子集,累计得到 5–15 秒的干净音频。写入 dub_jobs/{id}/voice_speaker_N.wav + 拼接的转写文本。总时长小于 5 秒的说话人会被跳过——与其交付一个单薄的克隆,不如回退。
- dub_core 转写流水线在说话人分离之后调用它,将结果存储在 job["speaker_clones"],默认情况下将 segments[i].profile_id = "auto:speaker_N"(用户分配的 profile 优先)。
- dub_generate._gen 解析 auto: 前缀:读取 job 作用域内的参考音频 + 参考文本,而不是访问持久化的 voice_profiles 表。
- SSE final 事件现在将 speaker_clones 流式传输到前端。
- 冒烟测试:合成的 20 秒人声 → 正确挑选长片段,跳过小于 5 秒的说话人,并写出有效的 WAV。
前端(专业级 UI 整合):
- dubSlice.speakerClones: Record<speakerId, {ref_audio, ref_text, duration, source_count}>。Transcribe final 处理器会填充它。
- CAST 条(左列,视频下方):已从 "Speaker Voices" 重命名为 "CAST"。当存在自动克隆时,每个说话人的下拉菜单会获得一个第一级的 🎤 From video · 7.8s 选项——默认情况下会预选在分段上。
- 已移除 "Apply Voice to All" 行(右列)。当只有一个说话人时,它原本是 CAST 的退化情形;现在多说话人和单说话人路径共用一个控件。
- 波形时间轴:height: 64 硬上限 → height: 'auto'。在三磁贴条下方回收约 80 px 的空白空间。波形现在会填满左面板可用的所有垂直空间。
- Typecheck 通过,62 个后端测试通过,构建 236 ms。
仍有待改进之处(坦诚地说): - 波形仍然渲染为固定的磁贴式分段,而不是一条连续的时间轴。更深入的修复是在一个 WaveSurfer 实例上实现合适的 regions 覆盖层——v2。 - 分段表列去重(当每一行的 SPKR / LANG / VOICE 都相同时隐藏这些列)仍待完成——需要对 DubSegmentTable 进行重构。 - 还没有“将克隆保存为持久 profile”的按钮——目前仅限 job 作用域。
2026-04-21 — 阶段 4.1 在句子级分段上真正完成:1.96 秒 / 70× 加速¶
在分段修复后重新进行了基准测试,仅在部分重新生成路径上将 num_step 从 16 降到 8(完整配音 + 导出仍以 16 运行)。改动在 dub_generate.py 中只有一行:
结论: 服务器总计 = 1.95 秒 · TTS = 1.89 秒 · 缓存 + 混音 + 保存 = 合计 0.05 秒 · 客户端墙钟时间 = 1.96 秒。目标 ≤5.0 秒 ✅。相比完整流程(num_step=16)加速 = 70×。
上一轮测试中 22 秒的客户端/服务器差距也消失了。 那是一次性的“完整配音后的第一个任务”预热噪声,不是持续开销。不再进一步调查——这个数字现在就是路线图所承诺的水平。
坦诚的权衡(现已端到端处理): num_step=8 相比 num_step=16 会损失约 10–20% 的感知质量。已于 2026-04-21 发布:
- 后端:
DubRequest增加了一个显式的preview: bool标志。num_step 由此派生:_num_step = 8 if req.preview else req.num_step。与regen_only解耦。每个分段的 num_step 持久化在job.seg_num_step中(与seg_hashes镜像),并通过done事件流式传输到客户端。 - 前端:
dubSlice跟踪previewSegIds(num_step 小于当前steps下限的分段 id)。“Regen N changed”按钮传递preview: true;done处理器从evt.seg_num_step填充previewSegIds。导出处理器(MP4 / WAV / MP3 / Clips / Stems)现在通过finalizeTtsBeforeExport()进行预检——如果仍有未处理的预览分段,会在触发下载 URL 之前以完整质量(preview: false)重新渲染。字幕导出(SRT / VTT)跳过预检,因为它们不依赖音频质量。 - 用户流程:编辑单词 → Regen(1.96 秒,预览质量)→ 可选继续编辑 → 导出 MP4(提示“Upgrading 1 preview-quality segment to full quality…”——约 15 秒 TTS,然后浏览器下载)。编辑时感觉很快,导出时保持完整质量。
- 43 个后端测试通过,typecheck 干净,构建 198 ms。
2026-04-21 — 发现分段回归:489 段 → ~36 段(减少 13 倍)¶
用户注意到分段表渲染的是单词行("ersten"、"Runde"、"eine"…)而不是句子。旧代码对一个 36 秒的片段产生 8 段;当前代码产生 100+ 段。之前的基准测试曾显示“一个 3 分钟片段有 489 段”,但我当时认为“这只是 Whisper 返回的结果”——其实并非如此。
在转写流水线中添加带埋点的日志行,定位到了问题阶段:
seg-trace chunk=1/5 raw=9 after_segment_transcript=7 after_subtitles=60
seg-trace chunk=2/5 raw=8 after_segment_transcript=7 after_subtitles=117
seg-trace chunk=3/5 raw=7 after_segment_transcript=7 after_subtitles=120
seg-trace chunk=4/5 raw=8 after_segment_transcript=8 after_subtitles=101
seg-trace chunk=5/5 raw=13 after_segment_transcript=7 after_subtitles=91
原因: services/subtitle_segmenter.segment_for_subtitles 将 Netflix 的 17 CPS 阅读速度上限作为分段规则来强制执行。正常的德语语音是 15–25 CPS,因此拆分器对每个句子都会触发,并一直递归到单词级别。这是一个字幕显示规则被当成了配音分段规则——完全是不同的粒度。
修复(外科手术式): 从转写流水线中移除了 segment_for_subtitles 的两个调用点(dub_core.py 第 377 行和 513 行)。segment_transcript 已经会生成句子级输出,并有自己的时长/字符上限(IDEAL_DUR=4.5s、MAX_DUR=9s、MAX_CHARS=140)。删除了现在未使用的导入。字幕 CPS 规则仍保留在 subtitle_segmenter.py 中,供将来合适的 SRT 导出路径使用。
预期结果: 3 分钟片段 → ~36 段(与用户记忆中的“36 秒 8 个句子”吻合 → 约 4.5 秒/段)。63 个测试通过。
2026-04-21 — 阶段 4.1 基准测试通过:热启动 4.04 秒,并修复 2 个 UI 回归¶
第二次基准测试运行,启用了服务端性能剖析。相同的测试素材、相同的流程,只有一处小改动:模型已通过先前的完整配音流程预热(真实用户流程——先配音,再编辑)。
| 阶段 | 冷启动(首次运行) | 热启动(真实用户体验) |
|---|---|---|
| 增量重新生成 | 7.05 s | 4.04 s ✅ |
| 相对完整流程加速比 | 10.5× | 18.5× |
服务端性能剖析(bench[incremental] 日志行):
TTS 占了预算的 99%。缓存加载(29 个 WAV)、时间线混音(30 个带淡入淡出和增益的片段)、以及文件保存(3 分钟时间线的 24 kHz 单声道 WAV)合计运行时间 <20 ms。Phase 4.1 的增量机制非常快——墙钟时间的下限就是合成一个新片段所需的时间。
结论:≤5 秒的目标与真实用户体验吻合(你只配音一次,然后编辑——模型为热状态)。冷路径的 7 秒只是首次加载,并非实际重新生成。Phase 4.1 退出标准 ✅ 已验证。
会话重构后,用户打开 UI 时发现的两个回归问题:
1. zustand persist 抛出了 "couldn't be migrated" 错误,因为我在没有 migrate 函数的情况下把 version: 1 → 2 → 3 往上提了。已修复:透传式的 migrate 接受任何 ≤3 的版本,并依赖各 slice 的默认值为新字段赋值。
2. <DubTab> 引用了 onGlossaryChange,但我在面向 store 直连的重构过程中把它从 prop 解构中删掉了。已修复:恢复到解构块中。
2026-04-21 — Phase 4.1 在 fireship 片段上的基准测试:7.05 秒,未达标 2 秒¶
这是 Phase 4.1 退出标准的首次真实测量。测试素材:https://www.youtube.com/watch?v=ZzI9JE0i6Lc(fireship 片段,约 3 分钟,489 个已转写片段——为便于建立可处理的基线,截取前 30 个)。Apple Silicon MPS,VoiceStudio 默认引擎。
| 阶段 | 墙钟时间 |
|---|---|
| 摄入 URL → 预处理(冷启动首次运行) | 80.7 s |
| 摄入 URL → 预处理(缓存命中,同一 URL) | 4.0 s |
| 转写 3 分钟片段 → 489 个片段 | 74–137 s |
| 完整配音(30 个片段,冷模型) | 74.33 s |
| 增量重新生成(30 个中编辑了 1 个) | 7.05 s |
| 增量相对完整的加速比 | 10.5× |
目标 ≤5.0 秒 · 实际 7.05 秒 · 未达标 2.05 秒。
增量逻辑是有效的——10.5× 的加速比证实了缓存的 seg_N.wav 文件正在被复用。与目标之间 2 秒的差距很可能来自磁盘 I/O(29 个缓存 WAV 顺序加载)+ 时间线混音 + 对永不变化的缓存片段执行 sync_scores 计算。最触手可及的修复:并行加载缓存 WAV;当 cached_sr == model.sampling_rate 时跳过无操作重采样;跳过对缓存片段的 sync_scores。
诚实的 ROADMAP 更新: Phase 4.1 退出标准 ⚠️ 🟡 → ❌。代码路径可用;但目标没有被钉死。有两条前进路径:
1. 接受 7 秒作为真实数值,把目标放宽到“典型 M 系列上 ≤10 秒”。
2. 优化上面三个热点,然后重跑基准测试。
无论如何:Phase 4.1 行上的 ✅ 为时过早——它只表示“代码已交付”,而非“基准测试通过”。
2026-04-21 — Phase 4.1 基准测试工具 + 潜在的摄取(ingest)错误¶
将 Karpathy 的“定义一个可验证目标”原则应用到 Phase 4.1 的 ≤5 s incremental 退出标准上,暴露了 摄取(ingest)流水线中的四个潜在 NameError 错误,这些错误从未在测试中被触发(测试从未驱动完整的 yt-dlp → extract → demucs → scene 路径)。它们都位于 Phase 2.4 重构后的 backend/services/dub_pipeline.py 中——符号被引用但从未被导入:
- asyncio(文件中 14 个调用点使用)
- find_ffmpeg,来自 services.ffmpeg_utils
- _get_semaphore 和 _spawn_with_retry,来自 services.ffmpeg_utils
- shutil、re、sys、soundfile as sf、AsyncIterator、get_best_device、HTTPException(迁移的附带影响)
同一轮 pyflakes 检查还发现:backend/services/translator.py 在两个签名中使用了 Optional,但没有导入它。已修复。
新文件: scripts/bench_incremental.py —— 一个 140 行的 HTTP 驱动测试工具,在固定 YouTube URL 上驱动完整流水线,计时完整配音和仅增量重新生成,并根据 5 秒目标报告通过/失败。驱动 POST /dub/ingest-url → /tasks/stream/{task_id} SSE → /dub/transcribe-stream/{job_id} → POST /dub/generate/{job_id}(两次:完整,然后 regen_only)。
教训:Phase 4.1 的“最后一公里”集成从未做过端到端测试。在路线图上给它打 ✅ 为时过早——服务层和 HTTP 接口在隔离状态下能工作,但真实用户路径(URL → 配音 → 编辑 → 重新生成)从未真正跑通过。
2026-04-21 — Phase 2 收官:generateSlice + uiSlice 扩展¶
generateSlice.ts—— 新的 slice 拥有全部 14 个生成选项卡字段:text、refText、instruct、language,以及 10 个生产覆盖旋钮(speed、steps、cfg、tShift、posTemp、classTemp、layerPenalty、denoise、postprocess、duration)和vdStates类别映射。用户的合成偏好现在通过 store 的partialize在页面重载后仍然保留。uiSlice扩大了——新增sidebarTab、isSidebarProjectsCollapsed、showCheatsheet(采用功能更新器模式)。已持久化。- App.jsx 中
useState的数量:~60 → 41。剩下的都按设计不可迁移——File/Blob 引用、后端加载的列表、瞬态录音定时器、compare-modal 状态。 - 如今 store 由 5 个 slice 组成(prefs、glossary、ui、dub、generate),统一放在同一个
persist中间件下。持久化键升级到 v3。类型检查通过,构建耗时 225 ms。 - Phase 2.2 标记 ✅ —— 单体拆分功能上已全部完成。Phase 2 进度 8/8。
2026-04-20 — Header + Sidebar + CloneDesignTab 内联样式清理完毕¶
仓库里四个最大的内联样式问题点,一次性全部迁移完成:
- Header 24 → 1. .header-area 现在通过 CSS(而非内联)实现网格布局。新的类体系:.hq-col-left/center/right、.hq-logo-*、.hq-stats__key/--gpu-active/__sep/__status-*、.hq-breadcrumb-sep、.hq-view-icon、.hq-flush-btn、.hq-reload-btn。.hq-wave.is-active 取代了内联透明度切换。只有每根柱子的动态样式(WaveBars 的 height/animationDelay)仍然保持内联。
- Sidebar 21 → 8. 折叠模式下的侧边栏条目获得了 .sidebar-tile 原语及颜色变体(--audio/--clone/--design/--success)。主体滚动容器 → .sidebar__scroll.is-collapsed。历史条目变体获得适当的类(.history-item--dub、.history-kind--audio、.history-meta--locked、.history-title--clamp、.history-subtitle--seed/--italic、.history-audio)。剩下的 8 个都是动态 --row-accent CSS 自定义属性(按条目设置颜色)。
- CloneDesignTab 33 → 0 ✅. 完整网格布局,标签行变体(--center/--spread/--flush/--sm),拖放区域的拖拽悬停现在切换 .is-dragging(在 index.css 中为 .file-drag 添加了类,并带 :hover 等价效果)。滑块列、生产覆盖列和时长输入框都进行了类化。
- 类型检查通过,构建耗时 209 ms。仓库范围内的内联样式:~186 → ~119(剩余部分收益很低,分布在 17 个组件中)。
2026-04-20 — DubTab 内联样式瘦身(93 → 2)¶
- DubTab 的内联样式对象从 93 个降到了 2 个(这两个都是无法避免的动态样式:空闲骨架中每一行的透明度,以及
PrepOverlay里的fontSize: large ? … : …)。整个仓库的内联样式:186 → 97(-48%)。 - 新增工具类:
.dub-col、.dub-panel-col、.dub-split-1/-2、.dub-idle-drop(另有.is-dragging用于拖拽悬停)、.dub-ingest-row、.dub-cast、.dub-gen-overlay、占位分段行专用的.dub-skel-*系列、.dub-settings-bar、.dub-transcript-*、.dub-bulk-select、.dub-outputs-*、.dub-footer-*。 - 拖拽悬停不再直接改动
e.currentTarget.style,而是切换.is-dragging。这是更干净的 React 写法,也让设计人员无需触碰 handler 就能调整悬停外观。 - 类型检查通过,构建 205 ms。
2026-04-20 — DubTab 与 Sidebar 中的 Store 直读¶
- DubTab 现在直接从 store 读取约 30 个字段——已从 App.jsx 的 props 接口中移除:
dubJobId、dubStep、setDubStep、dubPrepStage、dubFilename、dubDuration、dubSegments、setDubSegments、dubTranscript、dubLang、setDubLang、dubLangCode、setDubLangCode、dubInstruct、setDubInstruct、dubTracks、dubError、dubProgress、isTranslating、preserveBg、setPreserveBg、defaultTrack、setDefaultTrack、exportTracks、setExportTracks、activeProjectName、isSidebarCollapsed、setIsSidebarCollapsed、translateQuality、setTranslateQuality、dualSubs、setDualSubs。App.jsx 中<DubTab>的 JSX 从 47 个 prop 属性变为 22 个。 - Sidebar 移除了 4 个由 store 管理的 props(
mode、isSidebarCollapsed、dubStep、activeProjectId),改为直接读取。省去了每次切换 mode 时从 App → Sidebar 的 props 透传。 - Store 现在成为唯一事实来源——App.jsx 负责编排(handlers + 有副作用的 state),子组件直接从 store 读取。handlers 仍然沿着 props 传递,因为它们闭包了 App.jsx 的作用域(API clients、refs、timer state)。
- 类型检查通过,构建 157 ms。
2026-04-20 — Phase 2.2 dubSlice 迁移(重头戏)¶
dubSlice.ts现在拥有全部 18 个 dub 流水线状态字段。 通过一个小型resolve()助手实现 React 风格 setter((v | prev => next))——所有现有函数式更新的调用点都能继续工作。不会持久化:项目加载 + dub-history 恢复时会显式地重新水合。- App.jsx 中 dub 状态的
useState数量:18 → 0。只有不可序列化的内容保留在本地(File、Blob URL、timer refs、dubHistory列表、showTranscript开关、previewAudios缓存)。 - 为后续工作铺路:深层子组件(DubTab、DubSegmentTable)现在可以直接从 store 读取状态,而不是接收 30 多个 props——这属于单独的 refactor 环节。本次提交中,App.jsx 仍然透传 props,因此子组件契约未变。
- Store 组成:4 个 slice + persist 中间件。类型检查通过,构建 152 ms。
2026-04-20 — Phase 2.2 uiSlice 迁移¶
- 新增
uiSlice.ts,涵盖“我当前在应用中的什么位置?”这类状态集合:mode、activeProjectId、activeProjectName、activeVoiceId、modeBeforeVoice、isSidebarCollapsed、uiScale。动作方法openVoiceProfile(id)/closeVoiceProfile()取代了原先在 App.jsx 中纠缠的 setMode/setModeBeforeVoice/setActiveVoiceId 协调逻辑。 - App.jsx 为这些字段移除了 8 个
useState调用,以及 voice-profile 导航周围的两个 useCallback 包装器。旧的omni_uilocalStorage 恢复逻辑仍然通过 store 的 setter 写入,因此现有用户的 uiScale/mode/sidebar 状态会在首次加载时迁移。 setActiveProjectId与setActiveProjectName统一为一个setActiveProject(id, name?)动作——4 个调用点得到简化。- Store 持久化键升级到 v2;新增 partialize 字段:
mode、isSidebarCollapsed、uiScale(active project/voice id 保持瞬态——刷新后会回到 launchpad,而不是半加载陈旧状态)。 - 类型检查通过,构建 160 ms。
2026-04-20 — Phase 2.3 API 层全面 TypeScript 化¶
- 剩余的 8 个 API
.js文件全部迁移到.ts。 新增共享api/types.ts,导出类型化结构:EngineBackend/EngineFamilyResponse/SelectEngineResponse、Profile/ProfileUsage、ProjectSummary/ProjectDetail、GlossaryTerm/AutoExtractResponse、SystemInfo/ModelStatus/LogsResponse、DubHistoryResponse/DubTranslateResponse。 - 每个 API 包装器现在都有类型化的返回签名——App.jsx、Settings、Sidebar、DubTab、VoiceProfile 等处的调用方无需改动调用点,即可获得响应结构的编译期检查(得益于
allowJs: true)。 - 类型检查通过,构建 209 ms。Store slices 和
api/client.ts在 Phase 2.3 的第一轮中已经是 TS;Phase 2.3 唯一剩余的后续工作是把组件的.jsx文件转换为.tsx(可选,可以逐个文件进行)。
2026-04-20 — Phase 4.3 分阶段检查点横幅¶
- Phase 4 完成。 6/6 个子阶段已交付。dub 流程中三个自然的审查点(post-ASR / post-translate / post-generate)现在都有了明确的 UI 关卡。
CheckpointBanner组件在分段表格上方渲染,带有符合当前阶段的标题 + 内联的 Continue → 按钮(ASR 后显示 Translate,翻译后显示 Generate)。阶段会根据dubStep以及是否存在任何带text_original的 segment 自动判断。每个阶段都可关闭。reviewMode偏好项已添加到prefsSlice(已持久化)。默认值为 'on'。Settings > Engines 中提供了一个 Review between stages / Rapid-fire 分段控件——高级用户可切换到 Rapid-fire,直接从 Prepare → Translate → Generate 一路执行,不再有中途提示。- 类型检查通过,构建 206 ms。
2026-04-20 — Phase 4.5 + MOSS-TTS-Nano 引擎 + 运行时选择器¶
- 4.5 步骤级可恢复性 ✅。
job.seg_hashes现在会在每次成功生成后持久化每个 segment 的指纹(每 8 个通过_save_job落盘)。doneSSE 事件会将这些指纹流式发送到客户端,客户端直接快照lastGenFingerprints——不再需要/tools/incremental往返请求。loadProject+restoreDubHistory会从project.segHashes/job.seg_hashes重新水合。在 dub 中途 Kill -9 → 重新打开 → "Regen N changed" 会针对精确的剩余片段亮起。 - MOSS-TTS-Nano-100M(第 3 个 TTS 适配器)。
services/tts_backend.py中新增MossTTSNanoBackend——100M 参数的自回归模型,支持 20 种语言(含阿拉伯语/希伯来语/波斯语/韩语/土耳其语),在 4 核 CPU 上实时运行,原生 48 kHz,Apache-2.0 许可。它与 OmniVoice(600+ 语言)和 VoxCPM2(30 语言)互为补充,填补了“可在无风扇笔记本上运行”+“可商用且语言覆盖广”这一空白。采用懒加载,is_available()会引导用户到安装路径。 - 运行时引擎选择器。
backend/core/prefs.py(DATA_DIR 下的原子 JSON 存储)。三个族(TTS/ASR/LLM)中的active_backend_id()现在按 环境变量 → prefs.json → 默认值 的顺序读取;环境变量仍然优先。新的POST /engines/select会拒绝未知族和不可用的后端。Settings > Engines 标签页会为每个引擎渲染一个 Use 按钮——点击即可切换,选择在重启后仍然保留。frontend/src/api/engines.js中新增selectEngine(family, backend_id)助手函数。 - 50/51 测试通过(1 个预先存在的 skip);类型检查通过;前端构建 419 ms。
2026-04-21 — Phase 4.1 部分重生成 + 4.4 打磨¶
现在 Bet B 可以端到端运行。
- 4.1 增量重新配音——部分重生成落地。
DubRequest新增了regen_only: list[str] | None以及并行的segment_ids: list[str] | None。dub_generate.py会遵循这些字段:如果设置了regen_only,则其 id 不在允许列表中的片段会跳过 TTS,直接从磁盘加载DUB_DIR/{job}/seg_N.wav(重采样并填充到时隙)。优雅降级:如果缓存的 WAV 损坏,该片段将变为静音,并触发一个 SSEwarning事件;混音仍然可以完成。UI:App.jsx.handleDubGenerate(opts)接受{ regenOnly };在配音完成后,当incrementalPlan.stale.length > 0时,DubTab 会在“生成配音”旁边显示一个粉色的“重新生成 N 个已更改片段”按钮。 - 4.4 语速比率徽标。 当
seg.rate_ratio偏离 1.0 超过 3% 时,DubSegmentRow现在会在时间列中显示一个紧凑的 📖1.12×徽标,并使用颜色编码(红色 >1.15,信息蓝 <0.85,其他情况为弱化色)。如果语速拟合中止,工具提示会显示rate_error。App.jsx.handleTranslateAll会从翻译器响应中将rate_ratio和rate_error传播到每个片段。 - 构建耗时 162 ms,类型检查通过,79 个后端测试在 3.17 秒内通过。
2026-04-21 — Phase 4.2 + 4.4 全面接通;4.1 信号型 UX¶
这就是上一轮会话明确推迟的“最后一英里”。两个站得住脚的押注现在都能端到端运行。
- 4.2 导演式 AI——全面接入。
- Schema:新增了
DubSegment.direction和TranslateSegment.direction。 - 翻译器:
cinematic_refine_sync(direction=…)+cinematic_refine_many(directions={id→text})。导演的translate_hint()被前置到 reflect 与 adapt 系统提示词中。 - 生成器(
dub_generate.py):解析每个片段的 direction,将instruct_prompt()追加到喂给 TTS 的片段instruct字符串中,rate_bias()会对seg_speed进行乘法调整。因此“urgent, surprised”现在会同时影响翻译措辞、TTS 的情感演绎以及生成节奏。 -
前端:新增
components/DirectionDialog.jsx(实时/tools/direction预览面板)。DubSegmentRow菜单显示“设置方向…”/“编辑方向…”。带 direction 的片段会显示 ✨ 图标和品牌色按钮背景。App.jsx将openDirection/saveDirection路由到 Undo,并在handleTranslateAll和handleDubGenerate中继续传递 direction。 -
4.4 语速工程——已接入 Cinematic 翻译。
App.jsx会在翻译有效负载中的每个片段上自动填充slot_seconds = end - start。当quality=cinematic且提供了时隙时,翻译器会在 reflect/adapt 之后对每个改编后的文本行运行speech_rate.adjust_for_slot,返回rate_ratio(并在 LLM 失败时返回rate_error)。尽力而为——LLM 中断只会让 Cinematic 文本保持不变。 -
4.1 增量重新配音——信号型 UX。 现在,每次通过
/tools/incremental成功配音后,App.jsx 都会对片段指纹拍下快照。每当dubSegments变化时,incrementalPlan都会重新计算。当 stale > 0 时,DubTab 页脚会显示 “自上次生成以来有 N 个片段发生变化” 警告徽标;干净时则显示中性的“全部已是最新”。部分重生成的后端路径(生成器过滤到staleID,然后在现有轨道之上重组)仍是剩余的门槛——之所以推迟,是因为它需要按片段保留 WAV,并进行交叉淡化重组才能获得良好的 UX。 -
前端构建耗时 176 ms,类型检查通过。后端 79 通过 / 1 跳过,耗时 4.07 秒——没有新增测试套件;集成部分对现有测试进行了覆盖。
2026-04-21 — Phase 4 基础建设 + UI 追赶¶
目前为止单次会话中最大的一次推进。同时覆盖三层:UI 追赶 Phase 3 后端、四个带单元测试的 Phase 4 服务、两个全新页面。
UI 追赶(使用那些已经构建好但尚不可见的部分):
- Settings → Engines 标签页 — 新的 EnginesTab 子组件消费 GET /engines。按类别(TTS / ASR / LLM)显示当前激活的引擎,列出所有已注册的后端,将就绪与不可用状态标记为徽标,并显示可操作的原因字符串(例如“install voxcpm”、“set TRANSLATE_BASE_URL”)。端到端使用设计系统。
- /queue 页面 — 新的懒加载 pages/BatchQueue.jsx,mode='queue'。三个标签页(Active / Completed / Failed),在 Active 标签页上每 3 秒轮询 GET /jobs?status=…。每个任务卡片:状态徽标 + 圆点、类型、项目、存在时长、持续时间、错误、可展开的 meta_json。使用 Phase 2.1 的 job_store。
- /tools 页面 — 新的懒加载 pages/ToolsPage.jsx,mode='tools'。三个交互卡片(DirectorTool、RateFitTool、ProbeTool)。仅使用设计系统原语。
Phase 4 服务(全部经过测试):
- 4.1 增量重新配音 — services/incremental.py。生成输入的 SHA-1 指纹(text · target_lang · profile_id · instruct · speed · direction)。忽略外观字段(gain、selection、UI state)。plan_incremental 返回 {stale, fresh, total, fingerprints}。5 个测试。
- 4.2 导演式 AI — services/director.py。5 维分类法(energy、emotion、pace、intimacy、formality),包含 30+ 个值。双解析器:配置后通过 OpenAICompatBackend 使用 LLM,否则使用关键字启发式回退。Direction.instruct_prompt() 扁平化为 TTS 指令。translate_hint() 为 reflect/adapt 生成一个句子。rate_bias() 微调时隙拟合。5 个测试。
- 4.4 语速 — services/speech_rate.py。按语言的 CPS 表、容差窗口(0.92–1.08)、3 次尝试的 LLM 裁剪/扩展循环,耗尽后保留最佳比率候选。3 个测试。
- 4.6 工具 — 新路由 backend/api/routers/tools.py,暴露 /tools/probe、/tools/incremental、/tools/direction、/tools/rate-fit。新的 CLI omnivoice/cli/dub.py 通过 HTTP 配合 SSE 流式传输驱动完整配音流水线;已作为 omnivoice-dub 入口点添加到 pyproject.toml。新增 3 个冒烟测试。
打包:主包 229.31 kB → 230.20 kB(两个新懒加载页面增加 0.89 kB;可忽略不计)。构建耗时 211 ms。后端测试总计:79 通过 + 1 跳过,耗时 3.26 秒(本次会话前为 63 个)。
2026-04-21 — Phase 3 完成:可插拔引擎¶
所有四种适配器接口一次性交付,包含测试和 HTTP 接口。
- 3.1 TTS — 新增
backend/services/tts_backend.py。TTSBackend抽象基类,VoiceStudioBackend包装k2-fsa/OmniVoice(复用model_manager.get_model()以避免重复加载),通过环境变量选择引擎(OMNIVOICE_TTS_BACKEND,默认为omnivoice)。list_backends()返回[{id, display_name, available, reason}],以便设置 UI 选择器可以对不可用的引擎置灰并显示可操作的提示原因。 - 3.2 VoxCPM2 — 位于同一文件中的
VoxCPM2Backend。在依赖就绪之前,脚手架返回一条可操作的提示消息"install voxcpm + need CUDA";依赖就绪后,generate()将我们的instruct字段映射到 VoxCPM2 的内联"(instruct)prompt"语法,并支持终极克隆(ref_audio + ref_text)。48 kHz,宣称支持 30 种语言。 - 3.3 ASR — 新增
backend/services/asr_backend.py。MLXWhisperBackend(Apple Silicon 上的默认引擎)+PyTorchWhisperBackend(CUDA/CPU 回退方案,复用 TTS 模型的_asr_pipe)。基于torch.backends.mps自动检测最佳引擎;可通过OMNIVOICE_ASR_BACKEND覆盖。两者均归一化为现有代码已消费的chunks结构。 - 3.4 LLM — 新增
backend/services/llm_backend.py。OpenAICompatBackend将客户端构建逻辑从translator.py中抽出,使所有使用 LLM 的功能(影视翻译、术语表自动提取、第四阶段导演式 AI)都走同一条代码路径。OffBackend提供显式的无 LLM 状态,并给出清晰的"设置 TRANSLATE_BASE_URL"提示;未配置任何内容时默认为off。 - 接入 — 新增
backend/api/routers/engines.py,包含GET /engines、/engines/tts、/engines/asr、/engines/llm。通过backend/main.py完成接线。 - 测试 — 新增
tests/test_engines.py(12 个测试,覆盖所有三个注册表:默认列表、可用性消息、环境变量覆盖、各后端的采样率、自动检测、未知 ID 错误、Off 后端可操作的异常抛出)。冒烟测试套件扩展了两个引擎端点测试。后端测试总数:63(1 个因缺少可选的openai包而跳过),耗时 3.18 秒。 - 零破坏性变更。现有调用方(
dub_generate、dub_translate、segmentation)仍直接与模型/流水线通信——向适配器的迁移可以逐个文件进行。
2026-04-21 — 第二阶段清理:2.2 / 2.3 / 2.4 完成 / 2.6 / 2.7¶
五个条目一次性完成。第二阶段进度:5/8 → 7/8。
- 2.4 完成 —
ingest_pipeline(260 行的下载 → 提取 → demucs → 场景 → 缩略图异步生成器)从dub_core.py移至services/dub_pipeline.py,连同run_proc_factory和yt_download_sync。dub_core.py:525 行(从 777 行减少,较原始 889 行整体 −40.9%)。保留了旧版别名,使dub_generate/dub_translate/dub_export的导入继续正常工作。 - 2.6 — 在
services/model_manager.py中执行print()清理 → 改为logger.info。新增OMNIVOICE_JSON_LOGS=1环境变量标志,可为每个处理器(控制台 + 轮转文件)安装单行 JSON 格式化器。验证输出:{"t": "...", "level": "INFO", "name": "omnivoice.api", "msg": "..."}。 - 2.7 — 跨两个文件新增 13 个后端测试。
tests/test_dub_pipeline_state.py涵盖路径安全性、SSE 结构、进程跟踪以及内存 + SQLite 任务往返。tests/test_translator.py涵盖术语表前缀、无 LLM 优雅回退、空字面量透传、使用模拟 LLM 的完整三步链路,以及反思失败路径。后端测试总数:50(从 26 个),耗时 3.10 秒。 - 2.3 — TypeScript 脚手架已上线。
typescript@5.9.3,tsconfig.json设置allowJs: true+strict: true,使 JSX 和 TSX 在迁移期间可以共存。新增bun run typecheck脚本。frontend/src/api/client.ts作为首个.ts文件完成迁移;store 切片从一开始就用 TS 编写。类型检查干净通过。 - 2.2 — Zustand 脚手架 + 前三个切片。已安装
zustand@5.0.12。新增frontend/src/store/,包含prefsSlice.ts+glossarySlice.ts,通过persist中间件组合(localStorage,键omnivoice.app,版本 1,部分持久化)。从 App.jsx 中移除了三处临时写的useState(localStorage.getItem(...))代码块(translateQuality、dualSubs、glossaryTerms)——行为完全相同,现已集中管理。
打包影响:主包 226.56 kB → 229.31 kB(zustand 增加 2.75 kB,可忽略不计)。构建耗时 168 毫秒。
2026-04-21 — 第二阶段 2.4(部分):拆分 dub_core.py¶
dub 流水线提取的第一轮。 路由不再持有业务逻辑。
- 新增
backend/services/dub_pipeline.py(222 行)。公共接口: - 纯辅助函数:
compute_file_hash、safe_job_dir、sse_event、prep_event。 - 缓存查找:
find_cached_job— 为具有相同内容哈希的先前任务复用产物。 - 进程生命周期:
register_proc/unregister_proc/kill_job_procs/has_active_procs— 追踪进行中的 ffmpeg/demucs 子进程,使/dub/abort能够将其终止。 - 任务状态: 在
_dub_jobs(内存)和dub_history.job_data(持久化)之上提供get_job/put_job/save_job。 dub_core.py保留名称兼容别名(_get_job = dub_pipeline.get_job等),因此另外三个从dub_core导入的路由暂时无需更新。- 规模: dub_core.py 889 → 777 行(−12.6%)。dub_pipeline.py:新增,222 行。
- 全部 37 个后端测试保持绿色,耗时 3.09 秒。
- ⏳ 推迟:200 行的
_ingest_gen流式流水线和转录生成器暂留在路由中——它们与 FastAPI 的StreamingResponse+ 异步生成器契约绑定,需要单独一轮处理。
2026-04-21 — 第二阶段 2.1:持久化任务队列¶
任务在服务器重启后仍然存活。 SSE 客户端可以使用 ?after_seq=N 重新连接并追赶进度。
- Schema v4:
core/db.py:_BASE_SCHEMA中新增两张表 —jobs(id/type/project_id/status/timestamps/error/meta)和job_events(id/job_id/seq/payload),带状态、项目以及(job_id, seq)索引。使用IF NOT EXISTS→ 现有数据库无需迁移脚本。 - 新增
backend/core/job_store.py(187 行):create、mark_running/done/failed/cancelled、append_event(单调递增 seq + 每个任务最多 500 条事件)、events_since(after_seq)、list_jobs(status=active|...)、get、sweep_orphans_on_startup()。 - 重写
backend/core/tasks.py:TaskManager将每个状态转换和每次_push_event镜像到存储。内存队列保留以保证速度;磁盘作为双重保险。任何磁盘错误都会被记录并吞掉——实时流绝不会因为存储 hiccup 而中断。 backend/main.py生命周期:运行sweep_orphans_on_startup(),使崩溃进程的running任务翻转为failed,并附带"Job was interrupted by a server restart."消息,而不是假装仍然存活。dub_export.py中的新端点:GET /jobs?status=active&project_id=…&limit=NGET /jobs/{id}和GET /jobs/{id}/events?after_seq=NGET /tasks/stream/{id}?after_seq=N— 重放持久化的尾部事件,然后(如果仍在活跃状态)连接到实时监听器。重启后,客户端仍能获得最终状态,不会一直转圈等待。- 测试:
tests/test_job_store.py— 7 个测试:创建 + 生命周期、失败携带错误信息、单调递增 seq、events_since过滤、每任务事件上限、活跃状态过滤、孤儿任务清理。tests/test_router_smoke.py— 新增 4 个:GET /jobs正常路径、?status=active过滤、任务 + 事件端点的 404 情况。- 全部 37 个后端测试在 2.83 秒内通过。此阶段不持久化工作本身——那是第四阶段 4.5(步骤级可恢复性)。本阶段仅确保跨重启的诚实元数据,这为未来的可恢复性工作扫清了障碍,并让今天的 UI 与真实的
jobsAPI 对话。
2026-04-21 — 阶段 2.8:语音档案页面¶
为每个克隆或设计的语音提供一个真正完善的专属页面。 到目前为止,语音只能通过侧边栏小卡片进行管理;无法查看某一语音的详情、创建后编辑,或查看它被用在哪里。本次更新新增了一个完整页面。
- 后端:
GET /profiles/{id}(完整记录)、PUT /profiles/{id}(对名称 / instruct 指令 / 语言 / ref_text 的部分更新)、GET /profiles/{id}/usage——列出合成历史计数,并扫描studio_projects.state_json统计配音片段的使用次数。 - 前端:在
App.jsx中新增mode: 'voice'和activeVoiceId状态。返回按钮会回到调用它的那个模式。 - 新增懒加载页面
frontend/src/pages/VoiceProfile.jsx: - Hero(主视觉区) ——名称(可内联编辑)、类型徽标(克隆 / 设计)、锁定、种子、语言、创建日期等标签,以及内置参考音频播放器。
- 详情 ——风格
instruct指令、语言和参考转录文本,均可就地编辑并带保存/取消。锁定横幅可内联解锁。 - 尝试 ——输入任意一行文本,用当前语音生成并自动播放。使用现有
/generate接口。 - 使用情况 ——合成片段数量 + 项目列表(点击任意项目即可打开)。
- 侧边栏的克隆/设计卡片在预览 / 选择 / 锁定 / 删除之外,新增“打开”操作,可跳转到新页面。
- 纯设计系统构建:
Panel、Button、Input、Textarea、Field、Badge、Segmented、Progress——零新增内联样式。 - 前端构建耗时 168 毫秒。后端测试 26 / 26 全部通过。
2026-04-21 — 阶段 1 完成:1.4 双语字幕导出¶
结束阶段 1。
- 后端:
GET /dub/srt/{id}和GET /dub/vtt/{id}均接受?dual=1查询参数。新增_pick_subtitle_text(seg, dual)辅助函数,生成两行堆叠字幕提示:上方为译文本,下方为斜体原文(<i>…</i>——在 SRT/VTT 渲染器中通用)。文件名添加_dual后缀。 - 前端:“双语字幕”复选框位于输出选项中,并通过 localStorage 持久化保存(
omnivoice.dub.dualSubs)。SRT / VTT 下载按钮在启用时显示 ✦ 徽标、切换文件名并附带?dual=1。 - 所有后端测试保持通过(26 / 26,耗时 2.73 秒)。前端构建耗时 154 毫秒。
2026-04-21 — 阶段 1.3:项目级术语表¶
端到端的项目级翻译一致性:
glossary_terms表位于core/db.py(id, project_id, source, target, note, auto, created_at),并建有idx_glossary_project索引。数据库模式版本升级到 3。_BASE_SCHEMA中的IF NOT EXISTS意味着旧数据库会自动获得该表——目前还不需要迁移脚本。backend/api/routers/glossary.py(新增,243 行)——完整 CRUD(GET/POST/PUT/DELETE /glossary/{project_id})、批量清空(可选择只清空自动行),以及POST /glossary/{project_id}/auto-extract,它会调用与翻译器相同的、兼容 OpenAI 的 LLM,从项目的源文本片段中提取候选术语(与现有源字符串做不区分大小写的去重)。frontend/src/components/GlossaryPanel.jsx+ CSS——可原地编辑的表格,带手动/自动徽标,底部可添加新行,⌘↵/Enter提交,Esc取消;“Auto”按钮运行 LLM 提取,“Clear auto”清空 LLM 结果同时保留手动条目。frontend/src/api/glossary.js——近似类型化客户端。DubTab.jsx在加载任意配音任务时渲染该面板。App.jsx维护glossaryTerms状态;handleTranslateAll现在会将glossary: [{source, target, note}]传入每次翻译调用。翻译器 1.1 的提示词注入钩子(此前已就位)终于有了可注入的数据。- 优雅降级:如果未配置 LLM,自动提取会返回 503,并附带可直接复制粘贴的环境变量提示。手动术语表在无 LLM 时仍然可用。
- 冒烟测试 + 分段器测试仍全部通过:26 / 26,耗时 2.91 秒。
2026-04-21 — 阶段 1.2:NLP 感知的字幕分段¶
Netflix 风格的字幕规则在每次转录中强制执行:
- 新增
backend/services/subtitle_segmenter.py——纯函数实现,不依赖机器学习、无网络调用,226 行。按以下优先级切分:句子终止符 → 从句分隔符 → 连词 → 贪心词边界回退。强制执行≤42 字符/行 · ≤2 行 · ≤17 CPS · ≥1.2 秒时长。会将过短的孤立片段合并到相邻片段,但不会跨越句子终止符或说话人切换。 - 存在词级时间戳(WhisperX / mlx-whisper 输出)时会予以尊重:切分点落在单词间隙(前一个词的
end时间)上,从而落在自然换气处,而非词中间。 - 已接入
dub_core.py中的两条转录路径(分块流式 + 单次整段转录)。在说话人分离之后运行,因此speaker_id会传播到每个切分后的两部分中。 - 额外提供辅助函数
format_subtitle_lines(text, max_chars),供需要预换行布局的 SRT / VTT 渲染路径使用。 - 测试:
tests/test_subtitle_segmenter.py——14 个测试。后端测试总数现在为 26(分段器 + 路由冒烟测试)并通过,耗时 2.87 秒。原有的test_api.py错误与此无关(源自路由重构之前的遗留符号引用)。
2026-04-21 — 阶段 1.1:翻译 → 反思 → 改写¶
电影级翻译链路已上线,由 Quality: Fast | Cinematic 开关控制:
- 新增
backend/services/translator.py——3 步 LLM 链路(直译结果传入 → LLM 反思语气/习语/长度/节奏 → LLM 改写)。通过OMNIVOICE_LLM_CONCURRENCY(默认 6)限制并发。每个片段在 LLM 失败时优雅回退到直译结果。 TranslateRequest新增quality: "fast"|"cinematic"和glossary: [{source, target, note}]。提供术语表时,它会被前置到反思和改写两个提示词中。POST /dub/translate的响应新增quality_used,并且当执行 Cinematic 模式时,每个片段会带上literal+critique——这是为阶段 1.3 的三栏式翻译工作台视图做的准备(参见design/04-translation-workbench.md(已于 2026-07-12 停用;见 git 历史))。- 前端:
App.jsx中的translateQuality状态(通过 localStorage 持久化)、DubTab 设置栏中新增Segmented控件,以及当cinematic_skipped="no-llm-configured"时弹出的提示,指向上述环境变量。 - 可与真实的 OpenAI、Ollama、LM Studio、Together、Anyscale 一起使用——任何 OpenAI 兼容服务都可以。环境变量:
TRANSLATE_BASE_URL、TRANSLATE_API_KEY(或OPENAI_API_KEY)、TRANSLATE_MODEL(默认gpt-4o-mini)、OMNIVOICE_LLM_TIMEOUT(默认 45 秒)。 - 冒烟测试套件仍然通过:12 / 12,耗时 2.85 秒。
2026-04-21 — 阶段 0 完成 + 设计系统中期退出¶
阶段 0 — Momentum(0.1、0.2、0.3、0.4、0.5):删除了 8 个孤立文件,归档了 legacy_gradio/,搭建了 Alembic 脚手架,将 10 条错误消息改写为可操作的文案,新增包含 12 个测试的路由冒烟测试套件(2.79 秒),交付了 520 毫秒启动动画与 .ui-skel 工具类。
阶段 2.5 — 设计系统(部分):交付了完整的基础组件集:
- Button · Panel · Field · Input · Textarea · Select · Dialog · Slider · Badge · Tabs · Segmented · Tooltip · Progress · Menu · Table(14 个基础组件)
- 完整的设计令牌 + 动效层(ui/tokens.css、ui/motion.js)
- 9 个组件已迁移(Settings:0 处内联样式;CompareModal、Header、Sidebar、CloneDesignTab、AudioTrimmer、DubSegmentTable、DubSegmentRow、DubTab)
- 内联样式减少 52%(388 → 186)
- 从 index.css 减少 146 行(1,857 → 1,711)
- 主包体积减少 2.7%,尽管有净新增
阶段 2.6 — 日志基线(部分):后端运行时日志文件已接入,/system/logs 与 /system/logs/tauri 端点已修正(bundle identifier 修复 + 始终启用 tauri-plugin-log),Settings → Logs 界面已打通全部三个标签页,Clear 按钮可用。
2026-04-20 — 调研与设计文档¶
记录了 research/LEARNINGS.md(竞品分析:VideoLingo、pyVideoTrans、VoxCPM2),编写了 design/ 中的 ASCII 线框图集(架构 + 8 个视图),起草了 v1 ROADMAP.md,并在根目录清理后发布了 STRUCTURE.md。(这两个目录已在 2026-07-12 的清理中退役——见 git 历史。)
2026-04-20 之前¶
MVP 功能已完备:转写 → 翻译 → 配音 → mux、声音克隆、时间线编辑器、说话人分离(diarization)、YouTube 导入、选择性导出、项目持久化、Tauri 桌面外壳。详见 git 历史。
🧭 设计目标¶
预期形态记录在 design/ 中(已于 2026-07-12 退役;见 git 历史)——每个视图对应一张 ASCII 线框图,外加一张系统架构图。每个阶段都会让已发布的产品更接近这些视图所描述的形态。当代码与设计不一致时,其中必有一方是错误的——决定是哪一方,然后修复它。
🔁 评审节奏¶
| 频率 | 活动 | 负责人 |
|---|---|---|
| 每周(周五,1 小时) | 设计一致性审计 | — |
| 每两周 | 状态概览更新(本文件) + 估算偏差评审 | — |
| 每个阶段结束时 | 复盘 + 退出标准门禁 + 路线图修订 | — |
| 每季度 | 成功指标检查 + 性能追踪专项周 | — |
本路线图是一份活文档。每个阶段都以修订结束,而不是追加。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 om/VoiceStudio