跳转至

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 交付,包含环境驱动选择 + 注册表 + /engines HTTP 接口。保持了向后兼容——尚未迁移任何现有调用方。实际的免重启切换(实时重新配置)+ 设置界面引擎选择器是后续 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 中只有一行:

_num_step = 8 if regen_only is not None else req.num_step

结论: 服务器总计 = 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] 日志行):

total=4.02s  cache=0.01s  tts=3.97s  mix=0.00s  save=0.01s  segs=30  regen=1

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_ui localStorage 恢复逻辑仍然通过 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 落盘)。done SSE 事件会将这些指纹流式发送到客户端,客户端直接快照 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 损坏,该片段将变为静音,并触发一个 SSE warning 事件;混音仍然可以完成。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 个片段发生变化” 警告徽标;干净时则显示中性的“全部已是最新”。部分重生成的后端路径(生成器过滤到 stale ID,然后在现有轨道之上重组)仍是剩余的门槛——之所以推迟,是因为它需要按片段保留 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=N
  • GET /jobs/{id} 和 GET /jobs/{id}/events?after_seq=N
  • GET /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 与真实的 jobs API 对话。

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