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 复用旧结果
  • stage1 使用 ffprobe 做容器级快速校验
  • scan 默认只执行 stage1,把需要详细复核的文件标记为 pending_stage2
  • verify-stage2 只对 pending_stage2 文件使用 ffmpeg 做定向采样或完整确认
  • 扫描过程中输出 discovery、stage1、stage2 的阶段进度;长耗时阶段会持续输出 heartbeat
  • 输出 JSONL / CSV 报告
  • 支持按扫描结果做 confirmed_broken 文件删除预览和真实删除

目录结构

当前项目已经放在仓库根目录,核心内容如下:

在别的机器上使用

最稳的方式是把整个项目目录复制到目标机器,然后在目标机器本地重新创建虚拟环境并安装项目。不要直接把本机 .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:控制是否复用旧的第一轮结果

命令边界:

  • scan:只做第一轮快速筛查
  • verify-stage2:只做第二轮详细复核

默认扩展名列表:

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 仍保持 Telegram 常见容器的保守高风险复核范围,避免因为格式表扩大导致 stage2 backlog 暴涨
  • 如果你希望对全部已支持格式都走更激进的二次校验,使用 --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 执行失败,不应直接视为媒体损坏

原因码说明

导出的 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 规则升级复核
passed_stage1 容器级快速校验通过 第一轮结束即可放行
passed_stage2 第二轮采样或确认校验通过 第二轮也没发现问题
command_error 外部命令执行异常 环境问题,不应直接删文件
missing_command 缺少 ffprobe 或 ffmpeg 外部命令 机器环境不完整
invalid_probe_output ffprobe 输出不是可解析的 JSON 外部命令输出异常
timeout 命令执行超时 文件过大、设备慢或进程卡住

CSV 与 Office

当前 CSV 导出采用 UTF-8 BOM,并附带中文说明列:

  • final_status_zh
  • reason_code_zh

这是为了尽量兼容 Windows 上的 Office / Excel 默认打开行为,减少中文乱码问题。

如果你已经打开过旧版 CSV,建议:

  1. 关闭旧文件
  2. 用当前版本重新导出 CSV
  3. 再重新用 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:本次实际删除成功的总大小

推荐删除流程:

  1. 先跑 prune --dry-run
  2. 看 matched 和 Reasons
  3. 必要时打开 CSV 抽查几条
  4. 确认没问题后再执行 --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 数据集人工复核。

S
Description
清理因点击跳过导致的tg视频损坏
Readme 266 KiB
Languages
Python 100%