- 选择文件夹/单文件 → SenseVoice 转录 → LLM 修正 → 多路线翻译 → 双语 SRT - 路线:直译中文 / 英转中 / 仅转录 - 并发流水线:转录(可调) + LLM 并发(可调),降噪拆锁并发 - 断点续跑:.subtitle-work/ 中间产物,三阶段独立续跑 + 翻译段级续跑 - 去重:history 跟随视频文件夹,防重复任务保护 - 实时进度:SSE 推送 + 耗时显示 + 子任务状态 + 重新生成按钮 - 时间戳调优:VAD silence_schedule + noisereduce 降噪 + 完整性校验
6.2 KiB
6.2 KiB
字幕生成系统 subtitle-studio — 架构设计
设计日期:2026-08-15 | 架构:纯 Windows 本地,自包含 | 目标:文件夹/单文件 → 多路线字幕
1. 总体架构
┌─────────────────────────── Windows 本机 ───────────────────────────┐
│ │
│ [Web 前端] 浏览器页面 │
│ - 选择文件夹 / 单个文件 │
│ - 选择翻译路线(直译 / 英转中 / 仅转录) │
│ - 实时查看任务进度(SSE 推送) │
│ - 已完成视频列表 + 字幕文件下载/打开 │
│ │ HTTP/SSE │
│ [FastAPI 后端] :8788 │
│ - 任务管理器(队列 + 并发控制,默认 1 任务串行防 GPU 争抢) │
│ - 进度状态机:pending → transcribing → fixing → translating → srt → done │
│ │ │
│ [管道核心] subsudio.pipeline │
│ - transcribe.py SenseVoice + fsmn-vad(GPU,16kHz wav) │
│ - fix.py LLM 分块并发修正日文/韩文转录 │
│ - translate.py LLM 全文翻译(路线1 直译 / 路线2 英转中) │
│ - srt.py 生成 .ja.zh.srt / .zh.srt / .zh.en.srt │
│ │ │
│ [运行时] py312_cuda(conda) + OCG Router (192.168.1.246:19878) │
└─────────────────────────────────────────────────────────────────────┘
2. 设计决策
D1. 复用 py312_cuda 环境,不新建 venv
- 转录需要 faster-whisper/funasr/torch-CUDA,已全部就绪于
D:\ProgramData\anaconda3\envs\py312_cuda - 后端 FastAPI/uvicorn 也装这个环境(检查缺包再补)
- 避免重复装 CUDA 依赖(虚拟环境铁律:不污染系统,但可复用专用环境)
D2. LLM 走 OCG Router(本地中转,自动选 key)
- base_url
http://192.168.1.246:19878/v1,apiKeyocg-router-local - 模型
deepseek-v4-flash(1M 上下文,全文一次请求) - 关键经验:不指定 max_tokens(推理模型会烧光截断);全文翻译 1746 段 105 秒
D3. 翻译路线(用户可选)
| 路线 | 流程 | 产物 |
|---|---|---|
| direct (默认) | 转录 → LLM修正 → 直译中文 | .ja.zh.srt + .zh.srt |
| via_en | 转录 → LLM修正 → 英转中 | .r2.zh.en.srt + .r2.zh.srt |
| transcribe_only | 只转录,不翻译 | _transcript.json |
D4. 任务队列串行执行
- GPU 转录是重资源操作,一次只跑 1 个任务
- 翻译阶段可并发分块(fix 用 4 worker)
- 进度通过 SSE 实时推送到前端
D5. 输出目录
- 默认:字幕文件生成到视频同目录(PotPlayer 自动加载)
- 同时复制一份到
output/<task_id>/便于管理/下载
3. 目录布局
subtitle-studio/
├── src/substudio/
│ ├── __init__.py
│ ├── main.py # FastAPI 入口(uvicorn)
│ ├── taskmanager.py # 任务队列 + 状态机 + SSE 广播
│ ├── pipeline/
│ │ ├── __init__.py
│ │ ├── transcribe.py # SenseVoice 转录(复用技能逻辑)
│ │ ├── fix.py # LLM 分块并发修正
│ │ ├── translate.py # LLM 全文翻译(路线选择)
│ │ └── srt.py # SRT 生成
│ ├── config.py # 路径/模型/API 配置
│ └── llm.py # OCG Router 客户端(流式兼容)
├── templates/index.html # 前端页面
├── static/app.js # 前端逻辑
├── output/ # 任务输出
├── scripts/start.bat # 启动脚本
└── README.md
4. 进度状态机
pending → transcribing → fixing → translating → srt → done
└────── error(失败即停,可重试)
每阶段有 progress(0-100)和 message(如 "转录中 320/1746 段")
SSE 事件:task_update 推送 {task_id, status, progress, message}
5. 关键技术点
5.1 转录(复用已验证逻辑)
- ffmpeg 提取 16kHz 单声道 wav
- 降噪(默认开启):noisereduce 频谱门控,prop_decrease=0.7(0.9 过度会字间隙),实测 RMS 降 64%,句子边界更清晰
- fsmn-vad 切段(时间戳精准关键):
max_single_segment_time参数无效(实测)- 必须传自定义
silence_schedule:[(8000,500),(12000,300),(20000,200),(inf,100)] - 效果:段长从默认 15.67s → 4.64s 上限,平均 1.59s,时间戳严重错位修复
- 根因:默认 schedule 产生长段 → 只能按字符比例估算句内时间 → 字幕与说话时间对不上
- SenseVoiceSmall 逐段转录,rich_transcription_postprocess 清理
- 输出句子级时间戳 JSON
5.2 修正(分块并发)
- 按字符量分块(块边界=段边界,不切断句子)
- 每块一个 worker 进程并发(4 worker)
- prompt:结合上下文修正 ASR 错误,保留 [idx]
5.3 翻译(全文一次请求)
- 不指定 max_tokens(1M 上下文)
- 全文 30K 字符一次请求,1746 段 105 秒
- 输出 [idx] 行式解析
5.4 OCG Router 兼容
- 兼容 SSE 流式返回(data: {...} 解析)
- 有限重试(3 次退避,不疯狂重试)
- 失败即停 + 进度保存(断点续传)
6. 安全与资源
- 无 Docker 操作,纯本地文件处理
- 只读视频,写入同目录 SRT + output/
- 一次一个任务(GPU 转录),翻译并发仅限 LLM API
- 不碰系统环境(复用 py312_cuda 专用环境)