Files
tps-dashboard/doco/note/doco_P2_完工快照.md
T
simonkoson 1372600d71 doco P3-C2: 讯飞ASR适配层接入 + 修复 doco/programs 忽略规则
- C2: asr_adapter 并入讯飞上传/轮询/解析,复用 extract_audio 分离音频,
  新增 doco asr 命令读 C1 热词;真转写 ep001 出 310 句带时间戳
- 补提交 C1 代码(llm.py / term_extract.py)与累积词典
- .gitignore: 挡 doco/programs 下 png/wav/mp4 及中间帧目录、本地 settings

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-17 10:19:08 +08:00

8.6 KiB

Doco 子项目 · P2 完工快照

模块定位:《军事科技》中台组成部分。将节目稿件半成品(A稿)、OCR 扒词文档(B稿)、节目纯净声音(ASR)交叉验证融合,产出最接近播出的终版文稿。 P2 阶段目标:本地部署免费 OCR + 重写字幕扒词流水线,产出高质量 B 稿。 状态:已完工。本期(ep001 现代防空反导大对决)B稿_v2.txt 定稿,743 行。 快照时间:2026-06-15


一、本阶段为什么存在 / 解决了什么

接手时,旧帧筛流水线把本期七百多条字幕压成了 308 张关键帧,悄无声息丢了约六成,且被"308 张 ≈ 300 条字幕,基本一对一"这个巧合骗过了验收。

经逐帧决策表(frame_analysis_debug.csv)定位,根因是用像素启发式去猜字幕变没变,两个独立 bug:

  • 空白过滤误杀:max_brightness>=240 AND white_ratio>=0.005 把字幕淡入/切换的暗帧判成空场(如"我是主持人蓝皓"被剔除)。
  • dHash 距离去重误并:字幕版式全相同(黑底白字底部居中),仅文字不同,感知哈希分不开"同一条的不同帧"与"两条不同字幕";阈值 5 把不同内容误判为重复(如"欢迎收看《军事科技》"被砍)。

结论:像素相似度无法承担"字幕是否改变"的裁判职责。改为 OCR 优先 + 按文本去重——把裁判权交给文本(唯一真相层)。


二、部署:Ollama + DeepSeek-OCR(可复制到离线小机房)

  • DeepSeek-OCR 已是 Ollama 官方库正式模型,无需第三方 GGUF。ollama pull deepseek-ocr
  • 模型 deepseek-ocr:latest = :3b,6.7GB(显存占用约 7.8GB),8K context,Text+Image。
  • 要求 Ollama ≥ v0.13.0;本机实测 0.30.8。
  • GPU:制片人主机 RTX 4090D 24GB,ollama ps 确认 100% GPU
  • 调用:POST http://localhost:11434/api/generate,body {"model":"deepseek-ocr","prompt":"Free OCR.","images":[<base64>],"stream":false,"keep_alive":-1},读 response 字段。
  • Prompt 用 Free OCR.;实测黑底白字单行字幕识别准、标点干净、无需图像预处理。禁用 <|grounding|>...markdown(会输出 bounding box / markdown 污染纯文本)。

部署踩坑结论(每条都是教训,务必保留)

  • keep_alive:-1:不设的话模型闲置 5 分钟被踢出显存,下个请求遇重载空档报 HTTP 503。设 -1 永久常驻。
  • Windows 控制台编码:脚本顶部 sys.stdout.reconfigure(encoding="utf-8", errors="replace");进度打印只打数字、不打中文,否则 GBK 控制台 UnicodeEncodeError 崩进程。
  • 写文件用 UTF-8:jsonl 写入显式 encoding="utf-8"(ensure_ascii=False 可选,转义不影响 json.loads 解回)。
  • GPU 利用率低是正常的:单张小图 OCR,GPU 算一下歇一下,空隙是读图/编码/HTTP/写盘。判断在不在 GPU 看 ollama ps 的 PROCESSOR 列和显存占用,不看利用率百分比

离线小机房搬运(待执行)

小机房无外网,不能 ollama pull。在主机 pull 后,整拷 C:\Users\<用户名>\.ollama\models(或经 OLLAMA_MODELS 指定的目录)到小机房同路径。


三、流水线架构:两阶段解耦

设计原则:Stage A(贵、慢、易中断)与 Stage B(纯文本、秒级、可反复重跑)完全解耦,中间用 ocr_raw.jsonl 缓存衔接。这套设计在本期实战中扛住了 Cline 反复掐进程,一帧 OCR 没有白跑

Stage A — stage_a_extract_ocr.py(抽帧 + OCR)

  • ffmpeg 1fps 抽帧 + crop 下方 20%(滤镜 fps=1,crop=iw:ih*0.2:0:ih*0.8),输出 frames_v2/frame_%04d.png(1-based,t_sec = NNNN-1)。
  • 每帧都 OCR,绝不做亮度判空、绝不做 dHash/IoU 过滤(这是硬约束,丢六成的元凶)。
  • 结果逐帧追加写 ocr_raw.jsonl:{"idx","t_sec","text"},异常帧写 {..,"text":"","error":...}
  • 健壮性三件套:① 启动读已有最大 idx 断点续跑,绝不从头/覆盖;② 单帧 try/except Exception + continue,异常绝不冒泡崩主循环;③ 503/超时指数退避重试。

Stage B — stage_b_dedup_output.py(文本去重 + 出稿)

  • 只读 ocr_raw.jsonl,可反复重跑调阈值。
  • 连续段折叠(裁判在此,基于文本不基于像素):只合并时间相邻帧,文本 difflib 相似度 ≥ 0.85 视为同一条;只折叠连续段,绝不全局去重(片头片尾都出现"军事科技"是两条合法记录)。段内多数投票取最终文本,取最早 t_sec 为时间戳。
  • 出稿前清 markdown 残留:re.sub(r'^[#*\->\s]+','',text).strip()(DeepSeek-OCR 偶发 # ` 行首标题)。
  • 输出:B稿_v2.txt([XmYs] 文本,格式同旧 B 稿)、dedup_debug.csv(逐帧判决)、blank_filtered.txt(被判空场的非空文本存档,供审计)。

is_blank_ocr(text) — 空场判定(三选一即空场)

DeepSeek-OCR 在空白/黑场帧会幻觉出固定的英文财报表 <table>...As of December 31...Total return...</table>,不返回空串。故空场判定基于文本:

  1. strip() 后为空串;
  2. 含 HTML 标签(<>);
  3. 不含任何汉字(\u4e00-\u9fff)。 真字幕是纯中文短句,三条都不会误伤。

四、本期验收数据(ep001)

指标 数值
抽帧总数 1620
OCR 帧数 / error 帧 1620 / 0
空场数 207
被判空场但非空(去重) 1 条(<table> HTML 幻觉,正确拦截)
B稿_v2.txt 行数 743(旧 B 稿 742,基本持平)
行首 # 残留 0

质量结论(对旧 B 稿):

  • 完整性:从灾难性的 308 → 743,捞回六成丢失字幕。两条标志性失踪字幕("欢迎收看《军事科技》""我是主持人蓝皓")均恢复。
  • 准确性:归一化后约 589 行与旧稿完全一致;约 130 条差异绝大多数是新流水线把旧 OCR 错字改对(盹→吨、肘空→时空、差导→差异、领士→领土、短柝→短板、交镎→交锋、范匡→范围、娈得→变得…),且中文引号更规整。新 B 稿质量明显高于旧版。

五、实战教训(开发期摩擦,非代码缺陷)

  • Cline 反复掐进程:Cline 作为开发期 agent,天性"检查→行动→再检查",每次"出报告/改加固/重启续跑"都会掐掉前台 OCR 进程,崩点一路从 idx 249→503→756 后移,误以为是 bug。解法:让 Cline 彻底松手,改由制片人自己在独立终端(.venv 激活后 python stage_a...py)运行,Cline 碰不到。→ 一次性稳定跑满 1620。
  • 进度监控靠文件、不靠终端:(Get-Content ocr_raw.jsonl).Count 数行数判进度;停在某数不动=进程停了,数字在涨=在跑。
  • "503"歧义:一度把"jsonl 写到 503 行"误读为"HTTP 503",白做一版重试补丁。教训:先看清是行数还是错误码。

关键认知:Cline 是脚手架不是运行时。脚本本身是确定性程序,产品化后无人值守运行不会再有被掐现象。本期"自己开终端跑"已提前演练了脱离 Cline 的运行方式。


六、待办(P2 收尾遗留,进 P3 后并行处理)

  • 并发版 Stage A 提速(制片人已提出):当前严格串行,30 分钟/期,GPU 大量空转。首选方案:并发 48 路请求(OLLAMA_NUM_PARALLEL,3B 模型在 24G 卡轻松扛),预计压到十分钟出头。可选叠加"像素完全相同(hash 距离=0)帧复用上帧结果"无损省 OCR。vLLM 原生批量是高上限方案但 Windows 难装、破坏可移植性,非瓶颈不碰。
  • 离线小机房模型搬运(见第二节)。
  • 空场清单/审计文件已落地(blank_filtered.txt)。

七、P3 衔接:三方交叉融合(下一阶段)

三路输入(本期实测体量与粒度):

  • A 稿(A稿_..._定稿.docx,实为 markdown 文本):138 段散文脚本,按【导视】【主持人N】【解说N】【专家N】【动画N】【隔断】分段。编辑书面版,与播出是改写关系(例:A稿"欢迎来到这一期的《军事科技》节目"→播出"欢迎收看《军事科技》")。不可逐行 diff,只宜段落/话题级对齐。
  • B 稿 v2(B稿_v2.txt):743 行逐条短字幕,屏幕实况。
  • ASR(asr_result_timed.txt):306 行句级口语实况(每行一时间戳起点,常含多句)。

三方可信维度(P3 融合的核心依据):

  • A 稿 → 管结构与书面措辞(分段、专有名词的规范写法、完整语义)。
  • ASR → 管口语实况(主持人/专家实际念了什么,含临场改词)。
  • B 稿 → 管屏幕术语拼写(型号/番号/武器名,如"箭-3""萨德""见证者-136",ASR 常听岔)。

P3 目标:以时间戳为轴对齐三方,按各自可信维度投票/择优,产出最接近播出的终版稿。比 P2 复杂,需单独设计。


P2 完工。下一步:P3 设计。