# 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 小时**