# Piano Highlight Generator - GUI 编辑功能实现计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 实现 GUI 编辑功能,支持增删改知识点和字幕,CLI/GUI 共用项目文件体系,底层原子化复用。 **Architecture:** - `generated_config.yaml` = 项目文件(元信息 + clips 配置) - `config.ini` = 全局配置(API 密钥等),不跟项目 - 底层原子操作在 core/,CLI 和 GUI 共用 - 按需重生成(只重烧受影响的 clip) **Tech Stack:** PySide6 GUI, YAML, faster-whisper, FFmpeg --- ## Phase 0: 配置体系重构 ### Task 1: 项目文件增加元信息字段 **Files:** - Modify: `src/core/pipeline.py` — Pipeline.run() 结束时写 `video_src`、`ppt_path`、`max_total_duration` 到 generated_config.yaml - Modify: `src/core/ppt_parser.py` — `_create_config()` 写入 `video_src` 和 `ppt_path` **Steps:** - [ ] 在 `Pipeline.run()` 或 `step_generate_subtitles` 结束后,读取 config 中的 `video_src`/`ppt_path`,写入 generated_config.yaml - [ ] 确保 `_create_config()` 包含 `max_total_duration` 字段 - [ ] 验证:跑完 run.bat 后,generated_config.yaml 包含 video_src 和 ppt_path --- ### Task 2: config.py 重构(项目路径 vs 全局配置分离) **Files:** - Modify: `config.py` — 移除 VIDEO/PPT/OUTPUT,API 配置留在 config.ini - Modify: `run.py` — 从 config.py 只读 API 相关字段,项目路径由 CLI 参数指定 - Modify: `src/cli.py` — 确认 --video/--ppt/--output 参数能正确传入 pipeline **Steps:** - [ ] 修改 `config.py`:只保留 API_KEY/API_HOST/PYTHON/CLI_DIR,移除 VIDEO/PPT/OUTPUT/MAX_TOTAL_DURATION - [ ] 修改 `run.py`:从 config.py 读 API 配置,VIDEO/PPT/OUTPUT 通过 cli.py 参数传入(cli.py 已有 --video/--ppt/--output) - [ ] 验证:`run.bat` 能正常跑完整流程 --- ## Phase 1: 底层原子化 ### Task 3: 提取标题匹配函数 **Files:** - Modify: `src/core/ppt_parser.py` — 提取 `_find_title_in_transcript(title, transcript_segments)` 函数 - Create: `src/core/subtitle_matcher.py` (可选,如果逻辑复杂则独立) **Steps:** - [ ] 在 `ppt_parser.py` 中提取 `_find_title_in_transcript(title, corrected_segments)`: - 输入:标题文字 + corrected_transcript.json 的 segments 列表 - 处理:在每个 segment 的 text 中搜索标题关键词(子串匹配) - 返回:`(start, end)` 或 `None`(匹配不到) - [ ] 写单元测试验证:mock segments 数据,测试匹配返回正确时间戳,匹配不到返回 None - [ ] 验证:corrected_transcript.json 存在情况下,给定一个已知标题能找到对应时间段 --- ### Task 4: reextract_clip — 单标题重新匹配 **Files:** - Modify: `src/core/pipeline.py` — 增加 `reextract_clip(clip_index, new_title)` 方法 **Steps:** - [ ] 在 Pipeline 类中增加 `reextract_clip(self, clip_index, new_title)`: ```python def reextract_clip(self, clip_index, new_title): clip = self.clips[clip_index] # 加载 corrected_transcript.json # 调用 _find_title_in_transcript(new_title, segments) # 匹配到 → 更新 clip['start']/clip['end'] # 匹配不到 → clip['matched'] = False(或标记) # 调用 _merge_overlapping_clips 如有必要 # 保存 updated config to generated_config.yaml # 删除 clip_index 对应的 json(触发重生成) ``` - [ ] 写测试:用 lesson1 的 output,跑完后 reextract 某个 clip 改标题,验证 config 更新 --- ### Task 5: delete_clip — 删除 clip **Files:** - Modify: `src/core/pipeline.py` — 增加 `delete_clip(clip_index)` 方法 **Steps:** - [ ] 在 Pipeline 类中增加 `delete_clip(self, clip_index)`: ```python def delete_clip(self, clip_index): # 从 self.clips 删除该 clip # 删除 intermediates/clipN.json 和 clipN.mp4 # 保存 updated config to generated_config.yaml ``` - [ ] 写测试:跑完后删一个 clip,验证 json/mp4 删除、config 更新 --- ### Task 6: add_clip_by_title — 新增知识点 **Files:** - Modify: `src/core/pipeline.py` — 增加 `add_clip_by_title(new_title)` 方法 **Steps:** - [ ] 在 Pipeline 类中增加 `add_clip_by_title(self, new_title)`: ```python def add_clip_by_title(self, new_title): # 调用 _find_title_in_transcript 匹配时间段 # 匹配到 → 判断是否与现有 clip 重叠 → 合并处理 # 匹配不到 → 标记 matched=False,不加入 self.clips(或加到待确认列表) # 保存 updated config ``` - [ ] 写测试:加一个新标题,验证 config 更新、json 不生成(待确认状态) --- ### Task 7: reburn_titles / reburn_subtitles — 部分重烧 **Files:** - Modify: `src/core/pipeline.py` — 增加 `reburn_titles()` 和 `reburn_subtitles(user_texts=None)` 方法 - Modify: `src/core/subtitle.py` — 检查 `generate_from_clips` 能否跳过 LLM 校正直接用用户文本 **Steps:** - [ ] `reburn_titles(self)`: ```python def reburn_titles(self): # 用已有的 clip configs(不重生成 json) # 调用 subtitle_pipeline.generate_from_clips # 烧录标题轨到 subs/v1_title.srt ``` - [ ] `reburn_subtitles(self, user_texts=None)`: ```python def reburn_subtitles(self, user_texts=None): # user_texts: 可选,直接用用户文本烧字幕,跳过 LLM 校正 # 读取 v1_content.srt 或直接用传入的文本 # 烧录字幕轨到 subs/v1_content.srt ``` - [ ] 修改 `burn_only.py` 适配新的 Pipeline 方法 --- ## Phase 2: GUI 重构 ### Task 8: GUI 两种启动模式 **Files:** - Modify: `src/gui.py` — 重构为分步界面(启动页 → 处理/编辑页) **Steps:** - [ ] 重构 GUI 启动页:两个选项按钮 - "新建项目" → 跳转文件选择(视频+PPT+输出目录) - "打开已有项目" → 打开目录选择框 → 加载 generated_config.yaml - [ ] 实现"打开已有项目": ```python def load_project(self, output_dir): config = load_yaml(os.path.join(output_dir, 'generated_config.yaml')) # 设置 video_src, ppt_path, output_dir # 初始化 Pipeline(config) # 加载 clips 列表显示 # 加载字幕预览 ``` - [ ] 验证:打开 lesson1 output 目录,能正确显示 clip 列表 --- ### Task 9: GUI 编辑界面 **Files:** - Modify: `src/gui.py` — 增加编辑界面组件 **Steps:** - [ ] 左侧 clip 列表: - QListWidget 显示 clip 标题列表 - 双击编辑标题 - 右键菜单:删除、新增 - 未匹配 clip 显示红色/警告图标 - [ ] 右侧字幕预览: - QTextEdit 显示 v1_content.srt 内容 - 用户可直接编辑 - [ ] 底部"应用"按钮: - 收集所有修改 - 调用底层原子操作 - 进度显示 - 重烧后刷新预览 --- ### Task 10: 应用按钮 — 原子操作集成 **Files:** - Modify: `src/gui.py` — 应用按钮连接到 Pipeline 原子方法 **Steps:** - [ ] 应用按钮逻辑: ```python def on_apply(self): # 检测变化: # - clip 标题改了 → reextract_clip(i, new_title) # - clip 删了 → delete_clip(i) # - 新增知识点 → add_clip_by_title(title) # - 字幕改了 → reburn_subtitles(user_texts) # - 任一 clip 变了 → reburn_titles() # 最后调用 Pipeline.step_burn() 或 reburn 最终视频 ``` - [ ] 未匹配 clip 不参与重烧,显示提示 - [ ] 验证:改标题 → 点应用 → final.mp4 更新 --- ### Task 11: CLI/GUI 互操作测试 **Steps:** - [ ] CLI run.bat 跑 lesson1 → 生成完整 output - [ ] GUI 打开同一 output 目录 - [ ] 改 clip3 标题为"新标题" → 点应用 → 验证 config 更新、final.mp4 更新 - [ ] GUI 删 clip5 → 点应用 → 验证 config 更新、final.mp4 更新 - [ ] GUI 改字幕文本 → 点应用 → 验证 final.mp4 更新 --- ## Phase 3: 收尾 ### Task 12: 删除死代码 **Files:** - Identify: 检查 gui.py 中被替换掉的旧代码 - Remove: 删除不再使用的 UI 组件和逻辑 --- ### Task 13: 更新文档 **Files:** - Modify: `docs/USAGE.md` — 增加 GUI 编辑说明 --- ### Task 14: commit **Steps:** - [ ] git add -A - [ ] commit: "feat: GUI edit mode with clip/subtitle editing, config separation" - [ ] push --- ## 自检清单 - [ ] 所有修改文件有备份/可回滚 - [ ] 每个 task 后验证功能正常 - [ ] CLI 完整流程测试通过 - [ ] GUI 新建项目测试通过 - [ ] GUI 打开已有项目测试通过 - [ ] 改/删/增 clip 后 final.mp4 正确更新 - [ ] 字幕编辑后 final.mp4 正确更新 - [ ] 无新增警告或 lint 错误