216 lines
12 KiB
Markdown
216 lines
12 KiB
Markdown
# OCR Data - 视频字幕提取与识别系统
|
||
|
||
## 项目简介
|
||
|
||
本项目是一个自动化视频字幕提取系统,通过视频抽帧、OCR 文字识别、音频语音识别等技术,从视频中提取硬字幕并生成 SRT 字幕文件。支持多线程批量处理,并与腾讯云 COS 对象存储集成(上传/下载)。
|
||
|
||
OCR 主力来自内部 OCR 服务(Tencent Youtu,封装为 `tencentcloud_ocr_api`);Whisper 仅做辅助校验(识别首尾音频过滤片头片尾台标、与 OCR 文本做拼音匹配),不是字幕文字的主要来源,因此 Whisper 模型大小对最终字幕质量影响有限。
|
||
|
||
## 核心功能
|
||
|
||
- **视频抽帧**:用 FFmpeg `fps` 滤镜单次顺序解码,按固定频率抽帧(默认每秒 3 帧)
|
||
- **字幕区域检测**:通过 OCR 自动定位字幕区域,识别/过滤水印
|
||
- **图片拼接优化**:将多帧字幕区域裁剪后拼接为大图,大幅减少 OCR 调用次数
|
||
- **并发 OCR**:getsrt 阶段对拼接图/单独帧并发调用 OCR(`subtitle.ocr_workers` 控制并发数)
|
||
- **音频辅助校验**:用 Whisper 提取首尾音频文字,与 OCR 结果做拼音匹配
|
||
- **SRT 字幕生成**:合并、去重(处理多种 OCR 差异变体),生成标准 SRT
|
||
- **批量处理**:`ThreadPoolExecutor` 多视频并发;日志/计时按线程隔离
|
||
- **运行统计**:每个视频的起止时间、各阶段耗时、图片处理数量写入 `数据.jsonl`
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
ocr_data/
|
||
├── main.py # 主入口,批量任务调度
|
||
├── rerun_srt.py # 复用已有OCR数据重生成SRT(调试getsrt用,不重抽帧)
|
||
├── requirements.txt # Python 依赖
|
||
├── coscli.exe / coscli-linux-amd64 # 腾讯云 COS CLI 工具
|
||
├── config/
|
||
│ ├── config.json # 配置文件(OCR密钥、抽帧参数、模型、并发等)
|
||
│ └── config_utils.py # 配置管理器(单例)
|
||
├── tool/
|
||
│ ├── process.py # 单视频处理流程
|
||
│ ├── merge.py # 图片拼接、字幕区域检测、音频文字提取
|
||
│ ├── getsrt.py # OCR解析、字幕定位、去重、SRT生成(含并发OCR)
|
||
│ ├── whisper_manager.py # Whisper 管理器(线程安全单例)
|
||
│ ├── Ocr/tencentcloud_ocr.py # OCR API 封装(内部Youtu服务)
|
||
│ ├── VideoProcessing/multithread_algo.py # FFmpeg 抽帧
|
||
│ └── log/log_manager.py # 统一日志管理器(每视频状态线程本地)
|
||
└── video/ # 视频文件存放目录
|
||
```
|
||
|
||
## 处理流程
|
||
|
||
```
|
||
1. 从 COS 下载视频(可选)
|
||
↓
|
||
2. FFmpeg 抽帧(每秒 3 帧,fps=3.0 精确)
|
||
↓
|
||
3. OCR 检测字幕区域 & 水印过滤
|
||
↓
|
||
4. 字幕区域裁剪 & 多帧拼接为大图
|
||
↓
|
||
5. 对拼接图/单独帧并发调用 OCR
|
||
↓
|
||
6. Whisper 提取首尾音频文字(辅助校验)
|
||
↓
|
||
7. 字幕定位 & 文本质量评估 & 相似帧合并去重
|
||
↓
|
||
8. 生成 SRT 字幕文件
|
||
↓
|
||
9. 上传结果到 COS & 记录元数据/运行统计
|
||
```
|
||
|
||
## 环境要求
|
||
|
||
- Python 3.10+
|
||
- FFmpeg(`ffmpeg` + `ffprobe`,**抽帧和音频都依赖**,需加入 PATH)
|
||
- coscli(COS 命令行工具,配置 `coscli_path`)
|
||
- CUDA(可选,Whisper GPU 加速)
|
||
|
||
## 安装依赖
|
||
|
||
```bash
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
Linux 上若 `numpy`/`scikit-learn`/`av` 卡在源码编译(PyAV 编译需系统 ffmpeg 开发库),强制用预编译 wheel:
|
||
|
||
```bash
|
||
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple --only-binary=av,numpy,scikit-learn
|
||
```
|
||
|
||
首次运行会从 HuggingFace 下载 Whisper 模型;国内建议先设镜像:
|
||
|
||
```bash
|
||
export HF_ENDPOINT=https://hf-mirror.com
|
||
```
|
||
|
||
## 配置说明
|
||
|
||
编辑 `config/config.json`:
|
||
|
||
```json
|
||
{
|
||
"tencentcloud": {
|
||
"ocr": { "secret_id": "...", "secret_key": "...", "endpoints": ["http://IP1:PORT", "http://IP2:PORT"] },
|
||
"cos": { "secret_id": "...", "secret_key": "...", "region": "ap-chongqing", "bucket": "..." }
|
||
},
|
||
"audio": { "model_size": "small" },
|
||
"splitframe": {
|
||
"interval_seconds": 0.33,
|
||
"download": true,
|
||
"upload_path": "cos://your-bucket/path",
|
||
"delete_video": false,
|
||
"coscli_path": "./coscli-linux-amd64",
|
||
"cosconfig_path": ".cos.yaml"
|
||
},
|
||
"subtitle": {
|
||
"min_confidence": 50,
|
||
"similarity_threshold": 0.7,
|
||
"ocr_workers": 10
|
||
}
|
||
}
|
||
```
|
||
|
||
### 配置项说明
|
||
|
||
| 配置项 | 说明 |
|
||
|--------|------|
|
||
| `tencentcloud.ocr.{secret_id,secret_key}` | OCR 服务凭证 |
|
||
| `tencentcloud.ocr.endpoints` | OCR 服务 IP 列表(多个时按 QPS 加权分发,失败自动摘除重试);不填回退到内置单端点 |
|
||
| `tencentcloud.cos.{secret_id,secret_key,region,bucket}` | COS 凭证与桶信息 |
|
||
| `audio.model_size` | Whisper 模型:`tiny`/`base`/`small`/`medium`/`large-v3`,默认 `small` |
|
||
| `splitframe.interval_seconds` | 抽帧间隔,`0.33` 表示每秒 3 帧 |
|
||
| `splitframe.download` | 是否从 COS 下载视频 |
|
||
| `splitframe.delete_video` | 处理完是否删除本地视频 |
|
||
| `splitframe.coscli_path` / `cosconfig_path` | coscli 路径 / COS 配置文件 |
|
||
| `subtitle.min_confidence` | OCR 最低置信度阈值 |
|
||
| `subtitle.similarity_threshold` | 文本相似度阈值 |
|
||
| `subtitle.ocr_workers` | getsrt 阶段 OCR 并发线程数(默认 10) |
|
||
|
||
## 使用方法
|
||
|
||
### 批量处理
|
||
|
||
```bash
|
||
python main.py --source-list ./text2 --max-workers 10
|
||
```
|
||
|
||
### 命令行参数
|
||
|
||
| 参数 | 默认值 | 说明 |
|
||
|------|--------|------|
|
||
| `--source-list` | `./text2` | 任务 txt 文件所在目录 |
|
||
| `--max-workers` | 自动(按本机CPU核数) | 最大并发数;不填/传0则自动选(若本机IP在 `tencentcloud.ocr.endpoints` 里,判定为OCR共置机,自动打对折让资源;再按可用磁盘÷每视频预估占用(`dispatch.est_disk_gb_per_video`,默认2G)收紧一次,两者取小;最后还有个硬上限30,超高核数机器也不会自动选出离谱的并发数) |
|
||
| `--mem-guard-gb` | `2.0` | 可用内存低于此值(GB)时暂停启动新视频,防 OOM;设 `0` 关闭 |
|
||
| `--disk-guard-gb` | `10.0` | 本地磁盘可用空间低于此值(GB)时暂停启动新视频,给下载/抽帧/合成图预留空间,防止磁盘写满导致级联失败;设 `0` 关闭 |
|
||
|
||
启动时会打印一行机器资源(CPU 核数 / 可用内存 / 可用磁盘 / 负载)与当前并发设置,并对不合理的参数(内存/磁盘阈值过高、并发数过大/过小)给出告警,便于跑前判断。内存保护仅在 Linux 生效(读 `/proc/meminfo` 的 `MemAvailable`);磁盘保护跨平台(`shutil.disk_usage`)。机器无 swap 时内存保护尤其重要——可用内存触底会被暂停而非被 OOM 杀进程。
|
||
|
||
### 任务列表格式
|
||
|
||
在 `--source-list` 目录下创建 `.txt` 文件,**每行一个完整的 COS 路径**(上传目标目录由该路径自动推导,无需 `--cos-target`):
|
||
|
||
```
|
||
cos://bucket/0004/data/XXX/XXX_p1/XXX_p1.mp4
|
||
cos://bucket/0004/data/XXX/XXX_p2/XXX_p2.mp4
|
||
```
|
||
|
||
每个 txt 处理完会被删除;主循环持续监控目录直到没有 txt。**跨运行续传/去重**通过读取 `数据.jsonl` 的 `video_cos_path` 实现(不依赖 `./success/`)。
|
||
|
||
### 压测 OCR 端点 / 多 IP 负载均衡
|
||
|
||
OCR 服务有多个 IP 时,先压测每个 IP 的实际 QPS,运行时按 QPS 加权把请求分发到不同 IP:
|
||
|
||
```bash
|
||
# 1) 在 config.json 的 tencentcloud.ocr.endpoints 填上所有 IP
|
||
# 2) 压测各端点 QPS,结果写入 config/ocr_weights.json
|
||
python bench_ocr_endpoints.py [样例图片] --requests 60 --concurrency 10
|
||
# 3) 之后正常跑 main.py,OCR 请求会按权重自动分发;某端点连续失败会被冷却摘除并换端点重试
|
||
```
|
||
|
||
不跑压测时,所有端点等权轮询;压测产出的 `config/ocr_weights.json` 存在则按 QPS 加权(错误率>50% 的端点权重置 0,不参与分发)。
|
||
|
||
### 调试 SRT 逻辑
|
||
|
||
```bash
|
||
python tools/rerun_srt.py ./split/<video_name>/composite
|
||
```
|
||
|
||
复用已有 `ocr_result.jsonl` 重生成 SRT,不重新调用 OCR,用于快速迭代 `getsrt.py` 的去重/合并/时间轴逻辑。注意它**不重新抽帧**,无法验证抽帧相关改动。composite 目录现在处理完就随 `split_dir` 一起删了(不再上传/保留到 `./composite/`),所以这条命令只在处理还没走完清理那一步之前有效——比如直接 `import tool.process` 调用 `process_single_video()` 单独调试时(这条路径不经过 `main.py`/`run_worker.py` 的清理逻辑)。
|
||
|
||
## 输出说明
|
||
|
||
`<video>` 现在是 `tool/process.py::derive_video_name(cos_path)` 算出来的(`<原文件名>_<完整COS路径MD5前8位>`),不是纯文件名——见下面"关键实现要点"里的撞名说明。
|
||
|
||
| 目录/文件 | 说明 |
|
||
|-----------|------|
|
||
| `./video/<video>.mp4` | 下载的源视频(除非 `delete_video`) |
|
||
| `./split/<video>/` | 处理中的临时抽帧与 composite。composite(拼接大图、`ocr_result.jsonl` 等)不上传 COS,处理完这个视频后随 `split_dir` 一起整体删除本地 |
|
||
| `./srt/<video>.srt` | 最终 SRT(上传后移到此处;上传到COS的文件名是 `<video>.ocr.srt`) |
|
||
| `./info/<video>.json` | 处理成功的元数据(这个视频的 `数据.jsonl` 记录写完后立即删,不等整批跑完) |
|
||
| `./info_error/<video>.json` | 失败/过滤的元数据(同上,立即删) |
|
||
| `./log/<video>.json` | 每视频日志:`program_durations`(各阶段耗时)+ `custom_stats`(图片数量)(同上,立即删) |
|
||
| `数据.jsonl` | 每视频记录:状态、srt 路径、时长、起止时间、`elapsed_seconds`、`image_counts`、`stage_durations` |
|
||
| `batch_processor.log` | 批量运行日志(Python logging输出)。自动轮转:100M/份,最多留5份 |
|
||
| `app.log` | 进程整体stdout/stderr(`启动.sh`/`守护重启.sh`用`>>`重定向到这里,比`batch_processor.log`多包含`print()`输出)。不走Python logging,轮转在shell层做:`启动.sh`每次启动前若超过`LOG_MAX_MB`(默认200)就先轮转;`守护重启.sh`的`tick`(每分钟)用copytruncate方式(`cp`+原地截断)独立轮转,因为进程可能还在用append方式往这个文件写,直接`mv`会导致新文件一直是空的 |
|
||
|
||
## 过滤规则
|
||
|
||
以下情况会过滤视频(不生成字幕):
|
||
|
||
- 未检测到字幕区域
|
||
- 空白拼接图超过一半
|
||
- 冗余文字过多 / 水印占比过大
|
||
- 有效字幕数量过少
|
||
- 文本质量评分不达标(数字/特殊字符占比过高)
|
||
|
||
## 关键实现要点(勿回退)
|
||
|
||
- **视频本地/COS标识不能只用文件名**:源数据里大量不同父目录复用同样的数字文件名(如 `0021/17688/00046.mp4` 和 `0021/17690/00046.mp4`),如果本地工作目录只按文件名(不含父目录)命名,两个不同视频并发处理时会互相踩踏同一份本地文件(`./split/00046` 同时被两边写/删),引发目录未清空崩溃和连锁失败——实测占某台机器全部错误记录的 100%。现在 `main.py`/`tool/process.py` 都调用 `derive_video_name(cos_path)`(`<文件名>_<完整路径MD5前8位>`)作为唯一标识,两处必须算出同一个值。副作用:最终 SRT 文件名、composite 的 COS 目录名都会带上这个哈希后缀。
|
||
- **抽帧频率**:每秒 3 帧时 FFmpeg `fps` 必须是 `3.0`,不能用 `1/0.33=3.03`(会比真正 3fps 快约 1%,导致帧内容与 SRT 标注随帧号累积漂移,18 分钟处可差约 10 秒)。
|
||
- **SRT 合并的空串前缀**:`generate_srt_from_results` 的"渐进蹦字"判断要求 `current_text` 非空,否则空白间隔后的新字幕会继承空白段的起始时间。
|
||
- **OCR 变体去重**:`_similar` 合并同一句的不同 OCR 损失版本——支持前缀、子序列、标点归一化(`······` vs `......`)、去全部标点后的长公共前缀判定(处理开头稳定、中/尾各自丢失的闪烁)。
|
||
- **日志线程隔离**:`UnifiedLogger` 的每视频状态是线程本地的;嵌套子线程(如 OCR 并发池)不要直接调 `logger.log`,应把结果/错误回传给视频主线程统一记录。
|