TG Video Check
针对 Telegram 导出场景的视频损坏检测 CLI,重点识别“下载被跳过或中断后留下的半截视频文件”,并支持后续统计、导出和批量删除。
项目目标
这个工具当前解决的是比较具体的一类问题,不是通用媒体法医工具:
- Telegram 导出目录里存在大量视频文件
- 一部分文件扩展名、文件名都正常,但内容其实已经不完整
- 目标是尽快筛出高置信度坏文件,而不是对所有视频做最重的完整解码
- 结果要能落盘、复跑、导出,并且最终支持批量删除
当前能力
- 递归扫描一个或多个目录中的常见视频扩展名,默认覆盖
mp4/mov/m4v/mkv/webm/avi/flv/f4v/ts/m2ts/mts/mpg/mpeg/mpe/mpv/m2v/wmv/asf/3gp/3g2/ogv/ogm/vob/qt/rm/rmvb/mxf/divx - 支持从
txt文件批量导入目录列表 - 使用 SQLite 落盘扫描状态,并基于
path + size + mtime复用旧结果 - 当同一组
roots + profile的扫描上次未完成时,scan --resume会直接续跑同一个 run,不再把 discovery 结果整轮复制一遍 - stage1 使用
ffprobe做容器级快速校验 scan默认只执行 stage1,把需要详细复核的文件标记为pending_stage2verify-stage2只对pending_stage2文件使用ffmpeg做定向采样或完整确认- 扫描过程中输出 discovery、stage1、stage2 的阶段进度;长耗时阶段会持续输出 heartbeat
- 输出 JSONL / CSV 报告
- 支持按扫描结果做
confirmed_broken文件删除预览和真实删除
目录结构
当前项目已经放在仓库根目录,核心内容如下:
- pyproject.toml:项目入口和依赖定义
- docs/2026-03-29-tgvideo-detection-plan.md:最初的开发计划文档
- src/tgvideo_check/cli.py:CLI 主入口
- src/tgvideo_check/store.py:SQLite 状态落盘
- src/tgvideo_check/reporting.py:JSONL / CSV 导出
- tests/:单元测试、集成测试、性能 smoke
在别的机器上使用
最稳的方式是把整个项目目录复制到目标机器,然后在目标机器本地重新创建虚拟环境并安装项目。不要直接把本机 .venv 原样复制过去继续用。
前置条件
目标机器至少需要:
- Windows
- Python
3.12+ ffprobe可用ffmpeg可用
先验证:
python --version
ffprobe -version
ffmpeg -version
如果上面任一命令不可用,先把运行环境补齐。
方式 1:复制项目目录后本地安装
把项目目录复制到目标机器,例如:
D:\tools\clean-broken-tgvideos
然后进入项目根目录执行:
python -m venv .venv
.\.venv\Scripts\python.exe -m ensurepip
.\.venv\Scripts\python.exe -m pip install -e .
验证入口是否正常:
.\.venv\Scripts\tgvideo-check.exe --help
.\.venv\Scripts\tgvideo-check.exe scan --help
方式 2:如果目标机器也装了 uv
uv sync
uv run tgvideo-check --help
这种方式更省事,但前提是目标机器本身已经能使用 uv。
迁移后的最小验收
不要一开始就扫正式目录,先用一个小目录做最小验收:
.\.venv\Scripts\tgvideo-check.exe scan "D:\small-test" --db ".\small-test.db" --export-jsonl ".\small-test-stage1.jsonl" --export-csv ".\small-test-stage1.csv"
.\.venv\Scripts\tgvideo-check.exe verify-stage2 --db ".\small-test.db" --export-jsonl ".\small-test-final.jsonl" --export-csv ".\small-test-final.csv"
如果能先正常看到 scan 的 discovery/stage1 进度和摘要,再正常执行 verify-stage2 并生成最终 db/jsonl/csv 文件,说明目标机器环境基本没问题。
基本扫描用法
直接传目录
tgvideo-check scan "D:\Exports\ChatA" "D:\Exports\ChatB" --profile tg-hdd
第一轮完成后,如果要继续做第二轮详细复核:
tgvideo-check verify-stage2 --db .\scan_state.db
用 txt 批量导入目录
tgvideo-check scan --paths-file .\dirs.txt --db .\scan_state.db --export-jsonl .\results.jsonl --export-csv .\results.csv
完成第一轮后输出第二轮最终结果:
tgvideo-check verify-stage2 --db .\scan_state.db --export-jsonl .\results.final.jsonl --export-csv .\results.final.csv
混合使用
tgvideo-check scan "D:\extra-folder" --paths-file .\dirs.txt --db .\scan_state.db
常用参数
--db:指定 SQLite 状态库--paths-file:从 txt 文件读取目录列表,一行一个目录--export-jsonl/--export-csv:导出结果报告scan --stage1-workers:覆盖第一轮 worker 数verify-stage2 --stage2-workers:覆盖第二轮 worker 数--extensions:覆盖默认视频扩展名列表--queue-limit:限制单阶段批次大小,避免把待处理文件一次性堆进内存verify-stage2 --full-decode-confirm:对第二轮强制使用完整解码确认scan --resume / --no-resume:控制是否复用旧的第一轮结果compact-db:清理同一组roots + profile下的重复历史 run,并可选执行VACUUM
命令边界:
scan:只做第一轮快速筛查verify-stage2:只做第二轮详细复核inspect-stage1:排查第一轮崩溃前最后写入的ffprobe原始输出
默认扩展名列表:
mp4,mov,m4v,mkv,webm,avi,flv,f4v,ts,m2ts,mts,mpg,mpeg,mpe,mpv,m2v,wmv,asf,3gp,3g2,ogv,ogm,vob,qt,rm,rmvb,mxf,divx
说明:
tg-hdd/tg-fast默认不再因为文件后缀直接升级到第二轮,只有ffprobe已经发现异常信号时才进入pending_stage2- 如果你希望对全部已支持格式都走更激进的二次校验,使用
--profile deep-verify
--paths-file 规则
- 按
UTF-8/UTF-8 BOM读取 - 一行一个目录
- 空行自动跳过
- 行内空格、中文、方括号、感叹号等字符按字面值保留
- 如果整行外面包了一层单引号或双引号,会自动去掉外层引号
示例 dirs.txt:
D:\TG Export A
"D:\带空格 和 [特殊字符] 的目录"
D:\普通目录
小目录测试建议
如果要先验证工具效果,不要一上来扫正式目录。更稳的做法是先准备一个小样本目录,例如:
- 3 到 5 个确认正常的视频
- 2 到 3 个你故意截断的坏视频
- 5 到 10 个真实导出样本
推荐测试命令:
tgvideo-check scan "D:\small-test" --db ".\small-test.db" --export-jsonl ".\small-test-stage1.jsonl" --export-csv ".\small-test-stage1.csv"
tgvideo-check verify-stage2 --db ".\small-test.db" --export-jsonl ".\small-test-final.jsonl" --export-csv ".\small-test-final.csv"
重点看:
confirmed_broken是否命中了你故意造坏的样本pending_stage2数量是否符合你对可疑样本的预期passed_stage2里是否混进了你明知已坏的文件- 是否误伤了你确认正常的视频
状态说明
导出的 final_status / final_status_zh 含义如下:
| 状态码 | 中文说明 | 含义 |
|---|---|---|
confirmed_broken |
明确损坏 | 高置信度确认文件损坏,适合进入删除候选 |
suspicious |
可疑 | 有异常迹象,但证据还不足以直接当作明确损坏 |
pending_stage2 |
待第二轮复核 | 第一轮认为值得进一步做详细验证 |
passed_stage1 |
第一轮通过 | 容器级快速校验通过,未进入第二轮 |
passed_stage2 |
第二轮通过 | 进入第二轮后仍通过采样或确认校验 |
skipped |
已跳过 | 预留状态 |
command_error |
命令异常 | ffprobe / ffmpeg 执行失败,不应直接视为媒体损坏 |
Stage1 崩溃排查
如果 scan 在第一轮中途抛异常,例如卡在某个文件的 ffprobe JSON 解析阶段,不需要手动打开 SQLite 客户端。直接用内建命令查看最近一次扫描里,已经写入 stage1_results 但仍停在 pending_stage1 的记录:
tgvideo-check inspect-stage1 --db "C:\Users\Administrator\Documents\clean-tgvideo-data\scan_state.db"
常用变体:
--run-id 12:指定要检查的扫描批次--limit 20:多看几条最近记录--all:如果默认模式没有命中,就直接查看最近保存的 Stage1 原始结果,不再只限pending_stage1
输出重点:
path=:最值得怀疑的文件路径stdout_len=:ffprobe标准输出长度;很短通常表示只吐出了半截 JSONstdout_head=:标准输出开头预览,便于快速看出是不是残缺 JSONstderr_head=:标准错误预览,常能直接看到moov atom not found、error reading header等真实媒体错误
压缩重复扫描数据库
如果你之前在同一批目录上反复 scan,导致同一组 roots + profile 下面堆积了多份历史 run,可以保留一个目标 run,把其余重复 run 删掉:
先预览:
tgvideo-check compact-db --db .\scan_state.db
真实执行:
tgvideo-check compact-db --db .\scan_state.db --no-dry-run
如果要明确保留某个 run:
tgvideo-check compact-db --db .\scan_state.db --run-id 12 --no-dry-run
说明:
- 默认保留最新 run;如果你在做中断恢复,通常就保留当前正在续跑的那个 run
- 这个命令只清理同一组
roots + profile下的重复历史 run,不会顺手删除别的扫描任务 - 默认会在真实删除后执行
VACUUM,把 SQLite 文件物理收缩下来
原因码说明
导出的 reason_code / reason_code_zh 含义如下:
| 原因码 | 中文说明 | 典型含义 |
|---|---|---|
ffprobe_exit_nonzero |
ffprobe 返回非零退出码,通常表示容器或头部校验失败 | 第一轮就明显坏了 |
ffmpeg_exit_nonzero |
ffmpeg 返回非零退出码,通常表示采样或解码失败 | 第二轮校验失败 |
invalid_nal_unit_size |
视频流 NAL 单元尺寸异常,常见于截断文件 | 码流被截断或破损 |
truncating_packet |
视频包被截断,常见于下载中断或文件不完整 | 明显的半截文件特征 |
decode_slice_header_error |
视频切片头解析失败,通常表示码流损坏 | 解码阶段发现流损坏 |
corrupt_input_packet |
输入数据包损坏,码流存在明显异常 | 包级别损坏 |
hard_error_keyword |
命中高置信度损坏错误关键词 | 第一轮错误信息直接确认坏 |
soft_warning_keyword |
命中软告警关键词,因此升级到第二轮验证 | 第一轮不够致命,但值得复核 |
missing_video_stream |
没有检测到视频流 | 文件不是有效视频 |
missing_format_name |
无法识别容器格式 | 容器头部或格式识别失败 |
invalid_duration |
时长字段异常,因此升级到第二轮验证 | 容器信息不完整或异常 |
high_risk_extension |
根据 profile 规则直接升级到第二轮验证 | 主要出现在显式使用 deep-verify 这类激进配置时 |
passed_stage1 |
容器级快速校验通过 | 第一轮结束即可放行 |
passed_stage2 |
第二轮采样或确认校验通过 | 第二轮也没发现问题 |
command_error |
外部命令执行异常 | 环境问题,不应直接删文件 |
missing_command |
缺少 ffprobe 或 ffmpeg 外部命令 | 机器环境不完整 |
invalid_probe_output |
ffprobe 输出不是可解析的 JSON | 外部命令输出异常 |
timeout |
命令执行超时 | 文件过大、设备慢或进程卡住 |
CSV 与 Office
当前 CSV 导出采用 UTF-8 BOM,并附带中文说明列:
final_status_zhreason_code_zh
这是为了尽量兼容 Windows 上的 Office / Excel 默认打开行为,减少中文乱码问题。
如果你已经打开过旧版 CSV,建议:
- 关闭旧文件
- 用当前版本重新导出 CSV
- 再重新用 Office 打开新文件
删除已确认损坏文件
先预览:
tgvideo-check prune --db .\scan_state.db
默认行为:
- 默认
--dry-run - 默认只处理
confirmed_broken - 输出匹配数量、删除数量、总字节数、原因统计
确认后执行真实删除:
tgvideo-check prune --db .\scan_state.db --no-dry-run
如果后续你明确要扩大删除范围,可以传:
tgvideo-check prune --db .\scan_state.db --delete-status confirmed_broken,suspicious
但这不是默认推荐,因为 suspicious 仍然代表边界文件,不应默认直接删。
删除输出字段说明:
matched:命中删除条件的文件数deleted:本次实际删除成功的文件数missing:数据库里有记录,但磁盘上已经不存在的文件数failed:尝试删除但失败的文件数bytes:命中的文件总大小Deleted bytes:本次实际删除成功的总大小
推荐删除流程:
- 先跑
prune --dry-run - 看
matched和Reasons - 必要时打开 CSV 抽查几条
- 确认没问题后再执行
--no-dry-run
性能 Smoke
仓库内提供了 tests/test_perf_smoke.py,默认跳过,不把环境波动硬编码成普通单测。
手动运行方式:
$env:TGVIDEO_RUN_PERF_SMOKE='1'
uv run pytest tests/test_perf_smoke.py -q
这个 smoke 目前只做两件事:
- 生成一批最小测试视频
- 分别跑
stage1_workers=1和stage1_workers=2,确认基线采集流程可执行
它不是严格 benchmark;真正的默认参数取舍,仍应基于真实 HDD 数据集人工复核。