Files
ocr_data/README.md
2026-09-28 16:09:31 +08:00

12 KiB
Raw Blame History

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 加速)

安装依赖

pip install -r requirements.txt

Linux 上若 numpy/scikit-learn/av 卡在源码编译(PyAV 编译需系统 ffmpeg 开发库),强制用预编译 wheel:

pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple --only-binary=av,numpy,scikit-learn

首次运行会从 HuggingFace 下载 Whisper 模型;国内建议先设镜像:

export HF_ENDPOINT=https://hf-mirror.com

配置说明

编辑 config/config.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)

使用方法

批量处理

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:

# 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 逻辑

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,应把结果/错误回传给视频主线程统一记录。