Files

522 lines
18 KiB
Markdown

# Piano Highlight Generator App - 技术设计
> 设计日期:2026-05-02
> 版本:1.0
> 状态:Draft
---
## 1. 技术栈
| 层级 | 技术 | 选型理由 |
|------|------|----------|
| GUI 框架 | PySide6 (Qt for Python) | LGPL 许可,功能完备,信号槽机制适合异步更新 |
| 打包工具 | Nuitka | 编译为 C,性能好,体积小 |
| 状态持久化 | JSON 文件 | 简单,无需数据库依赖 |
| 核心模块 | 复用现有脚本 | video.py, subtitle.py, llm.py, corrections.py |
| 配置格式 | YAML/JSON | 用户友好,可读性好 |
---
## 2. 项目结构
```
piano-highlight-app/
├── src/
│ ├── __init__.py
│ ├── main.py # 应用入口
│ ├── app.py # QMainWindow 主窗口
│ ├── gui/ # GUI 组件
│ │ ├── __init__.py
│ │ ├── config_panel.py # 配置面板
│ │ ├── progress_view.py # 进度监控
│ │ ├── title_editor.py # 标题编辑器
│ │ └── log_view.py # 日志窗口
│ ├── logic/ # 业务逻辑
│ │ ├── __init__.py
│ │ ├── config_manager.py # 配置管理
│ │ ├── pipeline_controller.py # 流水线控制
│ │ ├── state_manager.py # 状态管理
│ │ └── worker.py # 后台工作线程
│ └── core/ # 核心模块(复用)
│ ├── __init__.py
│ ├── constants.py # 常量
│ ├── utils.py # 工具函数
│ ├── video.py # 视频处理
│ ├── subtitle.py # 字幕处理
│ ├── llm.py # LLM 调用
│ └── corrections.py # 纠错规则
├── assets/ # 资源文件
│ └── icons/
├── requirements.txt # 依赖
├── pyproject.toml # 项目配置
├── nuitka_options.py # Nuitka 打包配置
└── README.md
```
---
## 3. 核心类设计
### 3.1 StateManager(状态管理)
```python
class StateManager:
"""状态管理器 - 负责状态持久化"""
def __init__(self, state_file: str):
self.state_file = state_file
self.state = self._load()
def _load(self) -> dict:
"""从文件加载状态"""
def save(self):
"""保存状态到文件"""
def get_current_step(self) -> int:
"""获取当前步骤"""
def set_step_status(self, step: str, status: str):
"""设置步骤状态 (pending/in_progress/completed/failed)"""
def update_clip_status(self, clip_index: int, **kwargs):
"""更新 clip 状态"""
def get_clip_titles(self) -> list:
"""获取所有 clip 的标题(含用户修改)"""
```
### 3.2 PipelineController(流水线控制)
```python
class PipelineController:
"""流水线控制器 - 管理处理流程"""
# 步骤定义
STEPS = [
'ready',
'extracting',
'transcribing',
'title_correcting',
'generating_subtitles',
'merging',
'burning',
'completed'
]
def __init__(self, config: dict, state_manager: StateManager):
self.config = config
self.state = state_manager
self.is_paused = False
self.is_stopped = False
def run(self, worker: Worker):
"""运行流水线"""
def pause(self):
"""暂停流水线"""
def resume(self):
"""恢复流水线"""
def stop(self):
"""停止流水线"""
def step_extracting(self):
"""Step 1: 提取片段"""
def step_transcribing(self):
"""Step 2: 转录"""
def step_title_correcting(self) -> list:
"""Step 3: 标题纠正 - 返回需要用户确认的标题"""
# 返回标题列表,用户可以在此介入修改
def step_generating_subtitles(self):
"""Step 4: 生成字幕"""
def step_merging(self):
"""Step 5: 合并视频"""
def step_burning(self):
"""Step 6: 烧录字幕"""
```
### 3.3 Worker(后台工作线程)
```python
class Worker(QThread):
"""后台工作线程 - 在独立线程中执行流水线"""
progress_signal = pyqtSignal(str, int, str) # step, percent, message
clip_completed_signal = pyqtSignal(int) # clip_index
step_completed_signal = pyqtSignal(str) # step_name
titles_ready_signal = pyqtSignal(list) # 标题列表,等待用户确认
finished_signal = pyqtSignal(bool, str) # success, message
log_signal = pyqtSignal(str) # 日志消息
def __init__(self, controller: PipelineController):
super().__init__()
self.controller = controller
def run(self):
"""执行流水线(可暂停)"""
def request_pause(self):
"""请求暂停(由 UI 调用)"""
```
### 3.4 ConfigPanel(配置面板)
```python
class ConfigPanel(QWidget):
"""配置面板"""
config_changed_signal = pyqtSignal(dict)
def __init__(self):
super().__init__()
self._init_ui()
def _init_ui(self):
"""初始化 UI"""
# API 配置组
# - API Host (QLineEdit)
# - API Key (QLineEdit, 密码模式)
# - 模型选择 (QComboBox)
# 视频配置组
# - 视频文件选择 (QLineEdit + QPushButton)
# - 输出目录选择 (QLineEdit + QPushButton)
# Whisper 配置组
# - 模型选择 (QComboBox: base/small/medium/large)
# - 模型路径 (QLineEdit)
def load_config(self, config: dict):
"""加载配置到 UI"""
def get_config(self) -> dict:
"""从 UI 获取配置"""
def validate(self) -> tuple:
"""验证配置有效性"""
# 返回 (is_valid, error_message)
```
### 3.5 ProgressView(进度视图)
```python
class ProgressView(QWidget):
"""进度监控视图"""
def __init__(self):
super().__init__()
self._init_ui()
def _init_ui(self):
"""初始化 UI"""
# 当前步骤标签 (QLabel)
# 整体进度条 (QProgressBar)
# Clip 进度 (QLabel: "Clip 3/14")
# 日志文本框 (QTextEdit, 只读)
# 控制按钮 (开始/暂停/停止/继续)
def update_progress(self, step: str, percent: int, message: str):
"""更新进度显示"""
def append_log(self, message: str):
"""追加日志"""
def set_clip_progress(self, current: int, total: int):
"""设置 Clip 进度"""
def enable_controls(self, can_start: bool, can_pause: bool, can_stop: bool, can_resume: bool):
"""设置控制按钮状态"""
```
### 3.6 ContentEditor(内容编辑器 - 人工介入点)
```python
class ContentEditor(QWidget):
"""内容编辑器 - 用于人工介入修改标题和字幕内容"""
content_confirmed_signal = pyqtSignal(dict) # 用户确认的内容 {clip_index: {title, subtitles}}
def __init__(self):
super().__init__()
self._init_ui()
def _init_ui(self):
"""初始化 UI"""
# 标签页:标题编辑 / 字幕编辑
# 标题编辑:
# - Clip # | 原始标题 | LLM建议 | 用户修改 | 操作
# - 编辑按钮 (QPushButton)
# 字幕编辑:
# - 按Clip分页,每个Clip显示其字幕内容
# - 每个字幕段可编辑(原始文本 → 纠正后文本 → 用户修改)
# 确认按钮 (QPushButton)
def set_content(self, clips_data: dict):
"""设置内容供用户编辑
clips_data: {
clip_index: {
'title': {...},
'subtitles': [...]
}
}
"""
def get_user_content(self) -> dict:
"""获取用户修改后的内容"""
def edit_clip_title(self, clip_index: int):
"""编辑单个Clip的标题 - 弹出对话框"""
def edit_clip_subtitle(self, clip_index: int, subtitle_index: int):
"""编辑单个字幕段 - 弹出对话框"""
```
### 3.7 SubtitleSegmentEditor(字幕段编辑器)
```python
class SubtitleSegmentEditor(QWidget):
"""单个字幕片段的编辑器"""
def __init__(self, segment_data: dict, parent=None):
super().__init__(parent)
# 显示:时间范围、原始文本、规则纠正后、LLM纠正后、用户可编辑
def get_corrected_text(self) -> str:
"""获取用户修改后的文本"""
```
---
## 4. 流水线状态机
```
┌─────────┐
┌─────────►│ Ready │◄────────┐
│ └────┬────┘ │
│ │ start() │ reset()
│ ▼ │
│ ┌─────────┐ │
│ ┌─────│Extracting│─────┐ │
│ │ └────┬────┘ │ │
│ │ pause │ completed │ │
│ │ ▼ │ │
│ │ ┌───────────┐ │ │
│ └──►│Transcribing│◄────┘ │
│ └─────┬─────┘ │
│ pause │ │ completed │
│ ▼ │
│ ┌─────────────────┐ │
│ │Title Correcting │◄──┐ │ 人工介入点
│ └────────┬────────┘ │ │ 用户可暂停
│ │ completed │ │ 修改标题
│ ▼ │ │
│ ┌──────────────────┐ │ │
│ │Generating Subtitles│───┘ │
│ └─────────┬────────┘ │
│ pause │ │ completed │
│ ▼ │
│ ┌──────────────┐ │
│ │ Merging │◄──┘ │
│ └──────┬───────┘ │
│ pause│ │ completed │
│ ▼ │
│ ┌───────────┐ │
│ │ Burning │◄─────────────┘
│ └─────┬─────┘
│ pause│ │ completed
│ ▼
│ ┌───────────┐
└────│ Completed │
└───────────┘
```
---
## 5. 信号流设计
```
┌─────────────────────────────────────────────────────────────────┐
│ UI Layer │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ ConfigPanel │ │ ProgressView │ │ TitleEditor │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ │ config_changed │ titles_ready │ titles_confirmed │
└─────────┼──────────────────┼──────────────────┼──────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ Controller Layer │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ PipelineController │ │
│ │ - manage_workflow() │ │
│ │ - handle_pause() / handle_resume() │ │
│ │ - collect_user_titles() │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Worker Thread │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Worker (QThread) │ │
│ │ - runs pipeline steps │ │
│ │ - emits progress/titles signals │ │
│ │ - respects pause/stop flags │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### 信号定义
| 信号 | 方向 | 参数 | 说明 |
|------|------|------|------|
| `config_changed` | UI → Controller | dict | 配置变更 |
| `start_pipeline` | Controller → Worker | dict, StateManager | 启动流水线 |
| `progress_signal` | Worker → UI | step, percent, message | 进度更新 |
| `titles_ready_signal` | Worker → UI | list | 标题列表准备好,等待确认 |
| `titles_confirmed_signal` | UI → Controller | list | 用户确认的标题 |
| `pause_pipeline` | UI → Worker | - | 请求暂停 |
| `resume_pipeline` | UI → Worker | - | 请求恢复 |
| `stop_pipeline` | UI → Worker | - | 请求停止 |
---
## 6. 状态文件格式
```json
{
"version": 1,
"app_version": "1.0.0",
"project_name": "福田夜校-03月18日",
"created_at": "2026-05-02T10:00:00",
"updated_at": "2026-05-02T10:30:00",
"config": {
"video_src": "D:/path/to/video.mp4",
"output_dir": "D:/path/to/output",
"api_key": "xxx",
"api_host": "https://ark.cn-beijing.volces.com/api/coding/v3",
"whisper_model": "large",
"whisper_model_path": "D:/AI/LM-Models/faster-whisper/large-v3",
"video_params": {
"fade_duration": 1,
"title_fontsize": 90,
"title_color": "FFFF00",
"subtitle_fontsize": 24,
"subtitle_color": "FFFFFF"
}
},
"pipeline": {
"current_step": 3,
"steps": {
"extracting": {"status": "completed", "started_at": "...", "completed_at": "..."},
"transcribing": {"status": "completed", "started_at": "...", "completed_at": "..."},
"title_correcting": {"status": "in_progress", "started_at": "...", "completed_at": null},
"generating_subtitles": {"status": "pending", "started_at": null, "completed_at": null},
"merging": {"status": "pending", "started_at": null, "completed_at": null},
"burning": {"status": "pending", "started_at": null, "completed_at": null}
}
},
"clips": [
{
"index": 1,
"title_original": "弹奏",
"title_llm": "弹奏",
"title_user": null,
"title_final": "弹奏",
"start": 412,
"end": 442,
"status": "completed",
"clip_path": "intermediates/clip1_fade.mp4",
"json_path": "intermediates/clip1.json",
"transcription_completed_at": "..."
}
],
"outputs": {
"subtitle_title_path": "subs/v1_title.srt",
"subtitle_content_path": "subs/v1_content.srt",
"merged_video_path": "concat_merged.mp4",
"final_video_path": "v1_final.mp4"
}
}
```
---
## 7. 错误处理策略
| 错误类型 | 处理方式 |
|----------|----------|
| 配置无效 | 阻止开始,提示用户修正 |
| API 调用失败 | 重试 3 次,仍失败则暂停流水线,等待用户处理 |
| 视频文件不存在 | 暂停,提示用户选择其他文件 |
| 磁盘空间不足 | 暂停,提示用户清理空间 |
| 处理异常崩溃 | 状态已持久化,重启后可恢复 |
| 用户取消 | 保存当前进度,清理临时文件(可选) |
---
## 8. 打包配置 (Nuitka)
```python
# nuitka_options.py
import nuitka
nuitka.compile(
script="src/main.py",
mode="standalone",
output_dir="dist",
windows_icon="assets/icon.ico",
include_qt_plugins=["qt_plugins/styles", "qt_plugins/imageformats"],
data_files=[
("assets/icons", "assets/icons"),
],
remove_output_dir=True,
onefile=True, # 打包成单个 exe
company_name="Piano Tools",
product_name="Piano Highlight Generator",
product_version="1.0.0",
)
```
---
## 9. 依赖清单
```
PySide6>=6.6.0
pyyaml>=6.0
requests>=2.31.0
pypinyin>=0.50.0
faster-whisper>=1.0.0 # 可选,如需本地转录
```
---
## 10. 开发优先级
| 优先级 | 模块 | 工期估计 |
|--------|------|----------|
| P0 | 项目骨架 + ConfigPanel + StateManager | 2h |
| P0 | ProgressView + Worker 集成 | 2h |
| P0 | PipelineController 核心逻辑 | 2h |
| P1 | TitleEditor 标题编辑器 | 1.5h |
| P2 | 完善错误处理和边界情况 | 1h |
| P2 | 打包配置 + 测试 | 1.5h |
| P3 | README 和用户文档 | 0.5h |
**总工期估计:约 10 小时**