diff --git a/docs/IMPLEMENTATION_PLAN.md b/docs/IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000..c3fd7ce --- /dev/null +++ b/docs/IMPLEMENTATION_PLAN.md @@ -0,0 +1,260 @@ +# 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 错误