chore: tidy project layout

This commit is contained in:
kim
2026-03-30 11:35:00 +08:00
parent 4054cc87bb
commit 76fee612f5
2 changed files with 1 additions and 0 deletions
+699
View File
@@ -0,0 +1,699 @@
# TG Video Damage Detector Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 构建一个专门面向 Telegram 导出场景的批量视频损坏检测工具,重点识别“点击跳过后文件未完整下载”的半截视频,并在机械硬盘上尽量减少无效寻道和全量深度解码带来的吞吐损失。
**Architecture:** 采用独立的 Python CLI 工具,使用 `uv` 管理环境,通过 `ffprobe` 做第一轮容器级快速校验,通过 `ffmpeg` 做第二轮定向深度验证。执行模型采用“分阶段、低并发、可恢复”的流水线:目录枚举与阶段一尽量顺序化,阶段二只处理阶段一失败或可疑的子集,并将所有中间状态持久化到 SQLite 以支持断点续跑和大规模批处理。
**Tech Stack:** Python 3.12+, `uv`, `ffprobe`, `ffmpeg`, `sqlite3`, `typer`, `orjson`, `pytest`, `pytest-xdist`(仅测试时可选)
---
## 1. 问题背景与核心判断
目标场景不是通用“坏视频”检测,而是高度特化的 TG 导出异常场景:
- 用户在 Telegram 导出期间点击“跳过”。
- 目标目录中留下了扩展名正确、文件名正常、但内容不完整的视频文件。
- 损坏主因通常是文件截断,而不是编码器随机写坏数据。
- 数据位于机械硬盘,文件量较大,若对所有文件做完整解码,I/O 寻道与进程启动开销会迅速失控。
因此本项目的优化目标必须明确:
- 优先提升“单位时间筛出坏文件的数量”,而不是追求通用媒体法医级完整性验证。
- 优先减少不必要的全量 `ffmpeg` 深度扫描。
- 优先保证长时间批处理过程可恢复、可中断、可复跑,而不是一次性跑完后丢失上下文。
## 2. 方案比较与推荐
### 方案 A: 全量 `ffmpeg` 深度验证
思路:
- 对所有视频直接执行类似 `ffmpeg -v error -xerror -i <file> -f null -` 的验证。
优点:
- 结果直观。
- 发现中后段流损坏的能力最强。
缺点:
- 对机械硬盘最不友好。
- 对大量正常视频的成本极高。
- 进程启动和全量读取会成为主要瓶颈。
结论:
- 不作为默认方案。
### 方案 B: 两轮检测,第一轮 `ffprobe`,第二轮定向 `ffmpeg`
思路:
- 第一轮对所有视频做容器级快速校验。
- 第二轮只处理第一轮失败或被标记为可疑的文件。
- 第二轮不默认全量解码,而是优先做“定向采样验证”,必要时再升级为完整验证。
优点:
- 与实际损坏模式匹配。
- 能以较低成本抓住大量截断文件。
- 适合机械硬盘和大批量数据。
缺点:
- 需要认真设计“可疑”判定规则。
- 结果分类比单轮方案复杂。
结论:
- **推荐方案,也是本计划采用的方案。**
## 3. 范围与非目标
### 范围
- 选择一个或多个目录作为输入。
- 递归扫描常见视频扩展名。
- 第一轮对所有候选视频执行容器级快速校验。
- 第二轮只对剩余可疑文件做更重验证。
- 针对机械硬盘提供保守并发与顺序调度策略。
- 面向大量文件提供断点续跑、结果落盘、批量输出。
### 非目标
- 不做 GUI,首版只做 CLI。
- 不尝试修复视频。
- 不尝试恢复 Telegram 元数据。
- 不做跨机器分布式扫描。
- 不做全媒体库去重、转码、预览图生成。
## 4. 推荐系统架构
首版采用单机、单进程协调、多子进程调用 `ffprobe`/`ffmpeg` 的结构。
组件划分:
1. `cli`
- 解析参数。
- 加载 profile。
- 启动阶段流程。
2. `discovery`
- 递归枚举目录。
- 过滤扩展名。
- 产出基础元数据:路径、大小、修改时间、扩展名。
3. `store`
- 将扫描记录持久化到 SQLite。
- 记录阶段状态,支持恢复。
- 记录错误原因、命令返回码、耗时。
4. `stage1_probe`
- 调用 `ffprobe`。
- 生成容器级结果和可疑标记。
5. `stage2_verify`
- 调用 `ffmpeg` 做更重验证。
- 优先执行定向采样验证,而非对所有可疑文件完整解码。
6. `scheduler`
- 负责 HDD 友好的阶段切换与低并发调度。
- 控制 worker 数、队列长度、重试与中断。
7. `reporting`
- 输出 SQLite、JSONL、CSV 摘要。
- 提供按状态和原因分类的报告。
## 5. 数据模型与状态机
建议使用 SQLite 单文件数据库,例如 `scan_state.db`。
核心表:
- `scan_runs`
- `id`
- `created_at`
- `profile`
- `root_paths_json`
- `status`
- `files`
- `id`
- `run_id`
- `path`
- `size`
- `mtime_ns`
- `extension`
- `discovery_order`
- `stage1_status`
- `stage2_status`
- `final_status`
- `stage1_results`
- `file_id`
- `exit_code`
- `stderr_text`
- `format_name`
- `duration`
- `has_video_stream`
- `warning_flags_json`
- `elapsed_ms`
- `stage2_results`
- `file_id`
- `mode`
- `sample_points_json`
- `exit_code`
- `stderr_text`
- `reason_code`
- `elapsed_ms`
最终状态建议统一为:
- `confirmed_broken`
- `suspicious`
- `passed_stage1`
- `passed_stage2`
- `skipped`
- `command_error`
状态流转建议:
- 发现文件后进入 `pending_stage1`
- 第一轮失败直接进入 `confirmed_broken`
- 第一轮通过但命中可疑规则,进入 `pending_stage2`
- 第二轮通过进入 `passed_stage2`
- 第二轮失败进入 `confirmed_broken`
- 第二轮无法判定则保留 `suspicious`
## 6. 第一轮: 容器级快速校验设计
### 目标
- 用尽量低的 I/O 成本筛掉绝大多数明显坏文件。
- 尽量不做深度解码。
### 推荐命令
建议首版基于如下 `ffprobe` 级别的信息:
```bash
ffprobe -v error -show_entries format=format_name,duration,size:stream=index,codec_type,codec_name,width,height -of json <file>
```
说明:
- `-v error` 只保留错误输出。
- `show_entries` 只拿最小必要字段,避免无意义输出。
- 不启用 `-count_frames`、`-show_packets` 这类重操作。
### 第一轮判定规则
直接判定 `confirmed_broken`:
- `ffprobe` 非零退出。
- 无法识别容器格式。
- 没有视频流。
- 错误信息命中高置信度关键词,例如:
- `moov atom not found`
- `error reading header`
- `EBML header parsing failed`
- `Invalid data found when processing input`
判定 `pending_stage2`:
- `ffprobe` 成功,但 `stderr` 非空且命中软告警关键词。
- `duration` 缺失、为 `N/A`、非数字或 `<= 0`。
- 容器属于 TG 场景高风险集合,且启用了 `tg-hdd` profile。
建议首版高风险集合:
- `mp4`
- `mov`
- `m4v`
- `mkv`
- `webm`
判定 `passed_stage1`:
- `ffprobe` 成功。
- 存在视频流。
- 关键字段齐全。
- 未命中可疑规则。
### 为什么第一轮只做容器级校验
因为你的场景里,大量半截文件会在这一层就暴露:
- MP4/MOV 缺尾时常直接在 `moov`/header 层失败。
- MKV/WebM 容器不完整时常直接在 EBML 层失败。
这一步的价值是先把“最便宜能抓到的大头”清掉。
## 7. 第二轮: 更重验证设计
### 目标
- 不对所有文件做完整解码。
- 只对第一轮失败边界附近的文件做更重验证。
- 重点针对“头部还能读,但内容后段已截断”的视频。
### 推荐验证策略
第二轮默认不做全量解码,采用“定向采样 + 必要时升级”的两级策略。
#### 模式 1: 定向采样验证
推荐用 `ffmpeg` 在少数时间点解码短窗口,例如:
- 片头附近
- 中段
- 尾段附近
示例思路:
```bash
ffmpeg -v error -xerror -ss <offset> -i <file> -t 3 -map 0:v:0 -f null -
```
采样点建议:
- 时长 <= 60s: `10%`, `85%`
- 60s < 时长 <= 10min: `10%`, `50%`, `90%`
- 时长 > 10min: `10%`, `60%`, `92%`
尾段点位最关键,因为 TG 半截文件最常见的问题就是尾部缺失。
#### 模式 2: 完整解码确认
只对以下文件升级:
- 定向采样失败但错误原因不够稳定。
- 用户手动指定 `--full-decode-confirm`。
- 需要给出更高置信度结论的少量边界样本。
示例命令:
```bash
ffmpeg -v error -xerror -i <file> -f null -
```
### 第二轮判定规则
直接判定 `confirmed_broken`:
- 任一采样窗口退出非零。
- 错误信息命中以下关键词:
- `Truncating packet`
- `Invalid NAL unit size`
- `decode_slice_header error`
- `corrupt input packet in stream`
保留为 `suspicious`:
- 第二轮命令执行异常,但不是明确媒体损坏。
- 不同采样窗口结果相互矛盾。
判定 `passed_stage2`:
- 所有采样窗口通过。
## 8. 机械硬盘优化策略
这是本项目最重要的工程约束之一,不能事后再补。
### 调度原则
- 不让第一轮和第二轮并发运行。
- 默认采用阶段式执行:
- 目录发现 + 第一轮
- 第一轮结束后再跑第二轮
原因:
- 在同一块 HDD 上同时跑 `ffprobe` 和 `ffmpeg`,会把顺序读打碎成大量随机寻道。
### 默认并发策略
建议提供 `--storage-profile`:
- `hdd`
- `ssd`
- `auto`
首版默认建议:
- `hdd`: `stage1_workers=1`, `stage2_workers=1`
- `ssd`: `stage1_workers=4`, `stage2_workers=2`
- `auto`: Windows 下默认保守映射到 `hdd`
理由:
- HDD 上盲目开线程,经常只会让总吞吐下降。
- 多线程对 CPU 友好,不代表对磁盘友好。
### 队列与背压
- 使用有界队列。
- 不允许一次性把几十万条待处理任务全部压进内存。
- 队列深度建议默认不超过 `64`。
### 文件顺序
- 发现顺序和处理顺序尽量保持一致。
- 默认按目录遍历顺序写入 `discovery_order`。
- 阶段一按 `discovery_order` 处理。
- 阶段二按可疑文件的 `discovery_order` 再处理。
### 数据落盘
- SQLite 写入采用批量提交,例如每 `100` 或 `500` 条一次事务提交。
- 结果边写边落盘,不等全流程结束。
## 9. 大量视频处理优化
### 必做优化
1. **断点续跑**
- 再次运行时,若 `path + size + mtime` 未变化,直接复用上一轮结果。
2. **结果流式写入**
- 不把全部结果留在内存。
- 允许处理百万级文件清单。
3. **分阶段输出**
- 第一轮结束后立即产出“明确损坏 + 待二轮复核”列表。
4. **最小日志**
- 默认只输出阶段进度与统计摘要。
- 不为每个文件打印一行日志。
5. **可中断**
- 捕获 `Ctrl+C`,将状态安全落盘。
### 建议优化
1. **扩展名白名单**
- 首版只扫常见视频扩展名。
- 减少误把非视频文件交给 `ffprobe`。
2. **目录级切分**
- 对超大目录,允许后续增加“按一级目录分批执行”的操作模式。
3. **结果摘要缓存**
- 对重复查看的报告生成固定 JSONL/CSV,不每次重新聚合。
## 10. CLI 设计
建议提供如下命令:
```bash
uv run tgvideo-check scan D:\Exports\ChatA D:\Exports\ChatB
```
首版参数建议:
- `--profile tg-hdd`
- `--recursive / --no-recursive`
- `--extensions mp4,mov,mkv,webm,avi`
- `--db .\scan_state.db`
- `--resume`
- `--stage1-workers 1`
- `--stage2-workers 1`
- `--sample-duration 3`
- `--full-decode-confirm`
- `--export-jsonl .\results.jsonl`
- `--export-csv .\results.csv`
建议内置 profile:
- `tg-hdd`
- 针对当前用户场景的默认推荐档。
- 保守并发。
- 高风险容器进入第二轮。
- `tg-fast`
- 第一轮尽可能快。
- 第二轮更严格地缩小范围。
- `deep-verify`
- 面向人工复核的小样本。
- 允许完整解码确认。
## 11. 项目结构建议
建议在 `plan-for-tgvideo` 下实现为独立 Python 工具:
```text
plan-for-tgvideo/
pyproject.toml
uv.lock
README.md
src/
tgvideo_check/
__init__.py
cli.py
config.py
discovery.py
scheduler.py
store.py
models.py
ffprobe_runner.py
ffmpeg_runner.py
classifiers.py
reporting.py
profiles.py
tests/
test_profiles.py
test_stage1_classification.py
test_stage2_classification.py
test_resume_logic.py
test_integration_truncated_mp4.py
test_integration_truncated_mkv.py
samples/
valid/
broken/
```
## 12. 分任务实施计划
### Task 1: 初始化独立项目骨架
**Files:**
- Create: `plan-for-tgvideo/pyproject.toml`
- Create: `plan-for-tgvideo/README.md`
- Create: `plan-for-tgvideo/src/tgvideo_check/__init__.py`
- Create: `plan-for-tgvideo/src/tgvideo_check/cli.py`
**Steps:**
1. 用 `uv init` 初始化项目。
2. 设定 CLI 入口为 `tgvideo-check`。
3. 增加最小依赖:`typer`、`orjson`。
4. 跑 `uv run tgvideo-check --help`,确认骨架可执行。
### Task 2: 建立配置与 profile 系统
**Files:**
- Create: `plan-for-tgvideo/src/tgvideo_check/config.py`
- Create: `plan-for-tgvideo/src/tgvideo_check/profiles.py`
- Test: `plan-for-tgvideo/tests/test_profiles.py`
**Steps:**
1. 定义全局配置模型。
2. 实现 `tg-hdd`、`tg-fast`、`deep-verify` 三个 profile。
3. 明确 `hdd` 默认 worker 数与采样策略。
4. 编写配置解析测试。
### Task 3: 实现目录发现与候选文件过滤
**Files:**
- Create: `plan-for-tgvideo/src/tgvideo_check/discovery.py`
- Create: `plan-for-tgvideo/src/tgvideo_check/models.py`
- Test: `plan-for-tgvideo/tests/test_discovery.py`
**Steps:**
1. 实现多目录递归枚举。
2. 只保留目标扩展名。
3. 记录 `path / size / mtime / extension / discovery_order`。
4. 保证发现顺序稳定。
### Task 4: 实现 SQLite 状态存储与恢复
**Files:**
- Create: `plan-for-tgvideo/src/tgvideo_check/store.py`
- Test: `plan-for-tgvideo/tests/test_resume_logic.py`
**Steps:**
1. 初始化数据库 schema。
2. 支持批量 upsert 文件记录。
3. 以 `path + size + mtime` 作为恢复依据。
4. 确保中断后可恢复到上次阶段状态。
### Task 5: 实现第一轮 `ffprobe` 执行器
**Files:**
- Create: `plan-for-tgvideo/src/tgvideo_check/ffprobe_runner.py`
- Test: `plan-for-tgvideo/tests/test_stage1_runner.py`
**Steps:**
1. 封装 `ffprobe` 命令构建。
2. 限制输出字段。
3. 捕获 `stdout`、`stderr`、退出码与耗时。
4. 对超时和命令不存在给出明确错误状态。
### Task 6: 实现第一轮分类器
**Files:**
- Create: `plan-for-tgvideo/src/tgvideo_check/classifiers.py`
- Test: `plan-for-tgvideo/tests/test_stage1_classification.py`
**Steps:**
1. 定义高置信度错误关键词。
2. 定义软告警关键词和可疑规则。
3. 加入 TG 高风险容器升级规则。
4. 输出 `confirmed_broken / pending_stage2 / passed_stage1` 三种核心结果。
### Task 7: 实现第二轮 `ffmpeg` 定向验证
**Files:**
- Create: `plan-for-tgvideo/src/tgvideo_check/ffmpeg_runner.py`
- Test: `plan-for-tgvideo/tests/test_stage2_classification.py`
**Steps:**
1. 根据时长生成采样点。
2. 实现定向短窗口验证命令。
3. 支持按需升级到完整解码确认。
4. 归类典型流损坏错误。
### Task 8: 实现 HDD 友好的调度器
**Files:**
- Create: `plan-for-tgvideo/src/tgvideo_check/scheduler.py`
- Test: `plan-for-tgvideo/tests/test_scheduler_hdd.py`
**Steps:**
1. 强制阶段一和阶段二不重叠运行。
2. 用有界队列限制内存和并发。
3. 默认 `hdd` profile 使用单 worker。
4. 保证顺序处理和中断恢复。
### Task 9: 实现报告导出
**Files:**
- Create: `plan-for-tgvideo/src/tgvideo_check/reporting.py`
- Test: `plan-for-tgvideo/tests/test_reporting.py`
**Steps:**
1. 输出 JSONL。
2. 输出 CSV 摘要。
3. 支持按原因统计。
4. 支持导出“建议重新下载”清单。
### Task 10: 集成测试与样本验证
**Files:**
- Create: `plan-for-tgvideo/tests/test_integration_truncated_mp4.py`
- Create: `plan-for-tgvideo/tests/test_integration_truncated_mkv.py`
- Create: `plan-for-tgvideo/tests/test_integration_resume.py`
- Create: `plan-for-tgvideo/samples/README.md`
**Steps:**
1. 准备有效视频样本。
2. 人工截断生成坏样本。
3. 验证第一轮能抓住明显容器损坏。
4. 验证第二轮能抓住头部可读但中后段损坏的样本。
### Task 11: 性能基线与调优
**Files:**
- Create: `plan-for-tgvideo/tests/test_perf_smoke.py`
- Modify: `plan-for-tgvideo/README.md`
**Steps:**
1. 在 HDD 场景下跑小规模基线测试。
2. 比较 `stage1_workers=1` 与 `2` 的总耗时。
3. 比较“定向采样”与“完整解码”的吞吐差异。
4. 固化默认 profile 参数。
## 13. 测试策略
### 单元测试
- profile 解析
- SQLite 恢复逻辑
- 第一轮分类规则
- 第二轮错误归类
- 采样点计算
### 集成测试
- 正常 MP4
- 截断 MP4
- 正常 MKV
- 截断 MKV
- 命令缺失
- 中断后恢复
### 手工验证
- 在真实 TG 导出目录上跑 `tg-hdd` profile
- 抽样核对 `confirmed_broken`
- 抽样核对 `passed_stage2`
- 对边界文件再用播放器人工抽查
## 14. 风险与缓解
### 风险 1: 第二轮样本点过少,漏掉局部损坏
缓解:
- 保留 `--full-decode-confirm`。
- 允许 profile 提高采样点数量。
### 风险 2: 对 HDD 开过多并发导致更慢
缓解:
- `tg-hdd` 默认单 worker。
- 基线测试后再决定是否允许提高默认值。
### 风险 3: 结果解释不清,用户不知道哪些文件该重下
缓解:
- 报告中明确区分 `confirmed_broken` 和 `suspicious`。
- 输出可读原因码与建议动作。
### 风险 4: 边界格式太多,首版支持面过宽
缓解:
- 首版只优先优化 `mp4/mov/m4v/mkv/webm/avi`。
## 15. 验收标准
满足以下条件即可认为首版达标:
- 能选择一个或多个目录运行。
- 第一轮完成全量容器级快速校验。
- 第二轮只处理第一轮失败或可疑文件。
- 具备 `tg-hdd` profile,并默认采用 HDD 友好的保守并发。
- 支持 SQLite 落盘与断点续跑。
- 能输出明确的坏文件清单与原因分类。
- 在真实机械硬盘数据集上,默认模式明显快于“对所有文件完整解码”的朴素方案。
## 16. 首版执行建议
建议严格按以下顺序推进:
1. 先把 SQLite 恢复和第一轮 `ffprobe` 跑通。
2. 再实现分类器,确保“坏 / 可疑 / 通过”三分法稳定。
3. 然后实现第二轮定向采样验证。
4. 最后再做 HDD 调度细化和性能调优。
不要一开始就做“全量完整解码 + 高并发”,那是最容易把项目做成一个在真实机械硬盘上难用的慢工具的路线。