Files

18 KiB

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(状态管理)

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(流水线控制)

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(后台工作线程)

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(配置面板)

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(进度视图)

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(内容编辑器 - 人工介入点)

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(字幕段编辑器)

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. 状态文件格式

{
  "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)

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