首次提交

This commit is contained in:
2026-09-28 16:09:31 +08:00
commit 4a6839319a
71 changed files with 12144 additions and 0 deletions

142
tools/patch_config.py Normal file
View File

@@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
"""在【项目根目录】运行: 把本次更新需要的 config.json 配置键就地改好。
只动下面列出的键, 绝不碰你的密钥/COS路径/其它已调过的值。幂等, 可重复运行。
用法: python3 tools/patch_config.py
"""
import json
import os
import sys
P = "config/config.json"
if not os.path.exists(P):
print("✗ 找不到 config/config.json, 请 cd 到项目根目录再运行")
sys.exit(1)
d = json.load(open(P, encoding="utf-8"))
sf = d.setdefault("splitframe", {})
sub = d.setdefault("subtitle", {})
ocr = d.setdefault("tencentcloud", {}).setdefault("ocr", {})
dp = d.setdefault("dispatch", {})
changes = []
# -1) COS 任务分发(常驻 worker 认领任务 + 每日 数据.jsonl 备份)。缺失才加, 不动已有值。
# jobs_root=取任务地址; daily_backup_path=数据.jsonl 备份位置; machine_id 留空=用主机名/环境变量。
_bkt = (d.get("tencentcloud", {}).get("cos", {}) or {}).get("bucket", "asr-dig-data-1253181912")
_dispatch_defaults = [
("enabled", True),
("source", "cos"), # cos=从COS认领 / local=只读本地 ./text2(启动脚本据此选模式)
("run_long_video", False), # true=本机专跑超长队列(long_list_backup_path)
("auto_fallback_long_video", True), # false=主队列空闲时也不兼跑长视频队列,只等主队列
("global_dedup_on_startup", True), # false=启动时不拉全队去重清单,只按本机 数据.jsonl 去重
("machine_id", ""),
("jobs_root", "cos://%s/_jobs" % _bkt),
("shard_size", 2000),
("keep_alive", True),
("idle_poll_seconds", 60),
("claim_shuffle", True),
("claim_match", ""),
("heartbeat_seconds", 300),
("daily_backup_path", "cos://%s/_jobs/daily" % _bkt),
("long_list_backup_path", "cos://%s/_jobs/long_video" % _bkt),
("monitor_root", "cos://%s/_jobs/monitor" % _bkt),
("long_list_clear_after_upload", True),
("long_list_min_batch", 1000), # 领取长视频队列时凑够这么多条任务才开始处理一批
("daily_backup_hour", 10),
("stop_flag_key", "_jobs/STOP"),
]
for k, v in _dispatch_defaults:
if k not in dp:
dp[k] = v
changes.append("dispatch.%s -> %s (新增, COS任务分发)" % (k, v))
# 0) OCR 重试封顶 + 单次超时 (防"端点全超时时一张图卡~1小时"拖死worker)
if "max_attempts" not in ocr:
ocr["max_attempts"] = 4
changes.append("ocr.max_attempts -> 4 (新增, 重试封顶)")
# 单次OCR超时: 调到30s(大图负载下最慢~12s, 12s砍太狠; 有max_attempts封顶不会卡死)
if ocr.get("timeout", 0) < 30:
_old = ocr.get("timeout")
ocr["timeout"] = 30
changes.append("ocr.timeout %s -> 30 (大图留足余量)" % _old)
if "dispatch_log_every" not in ocr:
ocr["dispatch_log_every"] = 1000
changes.append("ocr.dispatch_log_every -> 1000 (新增, 每1000次派发落盘计数, 0=关)")
# 0b) 处理完删源视频(本来就在COS上, 本地是冗余副本)。防 ./video/ 堆积撑爆磁盘。
if sf.get("delete_video") is not True:
sf["delete_video"] = True
changes.append("delete_video -> True (处理完删源视频, 防磁盘爆)")
# 1) 抽帧 ffmpeg 线程数: 统一收敛到 "auto"(按 CPU核数÷并发进程数 自动摊分, GPU/CPU两条
# 抽帧路径现在都吃这个值了, 比固定数字更适合核数不一的机队)。已经是 "auto" 的跳过;
# 只升级明显偏低的字面值(<5)或完全没配的情况, 已手动调更高的字面值保留不动。
old = sf.get("ffmpeg_threads", 2)
_old_is_auto = isinstance(old, str) and old.strip().lower() == "auto"
if not _old_is_auto and (not isinstance(old, (int, float)) or old < 5):
sf["ffmpeg_threads"] = "auto"
changes.append(f"ffmpeg_threads {old!r} -> auto")
# 2) 超长视频补跑列表 (缺失才加)
if "long_video_list" not in sub:
sub["long_video_list"] = "./超长待重跑.txt"
changes.append("long_video_list -> ./超长待重跑.txt (新增)")
# 2b) 降低 OCR 拼接帧数 (大图OCR>12s会超时; 只往下调, 已更小的保留)
_mt = sub.get("max_tiles_per_composite", 40)
if _mt > 24:
sub["max_tiles_per_composite"] = 24
changes.append("max_tiles_per_composite %d -> 24 (拼接图减小, 防OCR超时)" % _mt)
# 2b2) 降全局OCR并发, 削掉过载突发尾延迟(端点平均快但突发会排队爆30s); 只往下调
_mg = sub.get("max_global_ocr", 80)
if _mg > 50:
sub["max_global_ocr"] = 50
changes.append("max_global_ocr %d -> 50 (降负载削尾延迟)" % _mg)
# 2b3) OCR图片像素上限: 超大合成图(区域过高)缩小再OCR, 防单张>30s超时
if "ocr_max_image_side" not in sub:
sub["ocr_max_image_side"] = 4000
changes.append("ocr_max_image_side -> 4000 (新增, 超大图缩小防超时)")
# 2c) 不丢帧: OCR多轮重试, 仍失败则整段视频判失败去重跑(不产出带洞SRT)
for k, v in [("ocr_no_drop", True), ("ocr_max_passes", 5),
("ocr_retry_sleep", 2), ("ocr_fail_list", "./OCR失败待重跑.txt")]:
if k not in sub:
sub[k] = v
changes.append("%s -> %s (新增, 不丢帧)" % (k, v))
# 2d) 低置信占比过滤: 幻灯片/编程视频OCR出垃圾(低置信50%+)且吃3-5倍OCR, 整段判非字幕跳过
for k, v in [("filter_lowconf", True), ("lowconf_thr", 80),
("lowconf_filter_frac", 0.6), ("lowconf_min_det", 200)]:
if k not in sub:
sub[k] = v
changes.append("%s -> %s (新增, 过滤幻灯片/代码类非字幕视频)" % (k, v))
# 2e) 状态归类清单: 这些原因是"无可用字幕的正常过滤", 应记 no_subtitle 而非 error。
# 旧逻辑把字幕太少/冗余/置信度/非字幕等错记成 error(线上 error 中~79%其实是它们)。缺失才加。
if "no_subtitle_reasons" not in sub:
sub["no_subtitle_reasons"] = [
"字幕太少", "冗余文字过多", "冗余文字太多", "无字幕区域",
"非字幕内容", "置信度太低", "未检测", "特殊字体",
"视频过短", "视频过长",
]
changes.append("no_subtitle_reasons -> [10项] (新增, 过滤型结论归 no_subtitle 不计 error)")
# 3) 抽帧自动选空闲GPU (避开被OCR服务占用的卡, 缺失才加)
if "gpu_auto_select" not in sf:
sf["gpu_auto_select"] = True
changes.append("gpu_auto_select -> True (新增, 抽帧自动避开被占用的GPU)")
if "gpu_busy_mem_mb" not in sf:
sf["gpu_busy_mem_mb"] = 4096
changes.append("gpu_busy_mem_mb -> 4096 (新增, 显存占用超此判为忙)")
sf.setdefault("gpu_skip_indices", []) # 手动跳过列表(默认空, 交给自动探测)
if changes:
json.dump(d, open(P, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
print("✓ config 已更新:")
for c in changes:
print(" -", c)
else:
print("✓ config 无需改动(已是最新)")