# -*- 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 无需改动(已是最新)")