Files
vrsub/tests/test_integration_subtitle_ocr.py
T
cat-shark 4746e0363f feat: VRSub 单体应用(WOV 单机版)初始提交
为视频生成 VR 双眼字幕的单体实现:FastAPI 后端、调度器与全部节点
(提音/转写/翻译/ASS/抽帧/OCR/LLM 过滤)在单进程内运行。

- 节点协议(wov_sdk 数据模型)与分布式版保持一致,预留回退桥梁
- 工作流即数据:DAG 存于 workflows/*.json,模型/链路改动只改数据
- 调度器:拓扑顺序执行、断点续跑(产物重建)、任务暂停/继续
- 抽帧按帧间隔(select 按帧号精确取帧),VLM OCR 与 LLM 过滤使用
  自适应线程池弹性并发,并打印数据处理速度进度日志
- 100% 行覆盖率(pytest --cov-fail-under=100)
2026-08-16 23:58:25 +08:00

91 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""字幕 OCR 整链真实集成测试。
使用 testdata/subtitle_10s.mp4(烧录 SUB 001@1-4s、SUB 002@6-9s)与真实
glm-ocr 模型:抽帧(frame-extract)→ 逐帧 OCRsubtitle-ocr)→ 汇总 SRT
断言烧录文字与时间轴对齐。Ollama 服务或资产缺失时自动跳过。
"""
import json
import urllib.request
from pathlib import Path
import pytest
from nodes.frame_extract import invoke as frame_invoke
from nodes.subtitle_ocr import invoke as ocr_invoke
from wov_sdk.models import InvokeRequest
OLLAMA_HOST = "http://192.168.123.70:11434"
MODEL = "glm-ocr:latest"
# 单体根目录:tests/ 的上一级。
WORKSPACE = Path(__file__).resolve().parent.parent
VIDEO = WORKSPACE / "testdata" / "subtitle_10s.mp4"
def _register_nodes() -> None:
"""注册全部内置节点,供 subtitle-ocr 内部调 vlm-ocr 使用。"""
from wov_app import registry
registry.register_all()
def _ollama_reachable() -> bool:
"""探测 Ollama 服务与目标模型是否可用。"""
try:
req = urllib.request.Request(
f"{OLLAMA_HOST}/api/show",
data=b'{"model": "%s"}' % MODEL.encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=5) as resp:
return resp.status == 200
except (urllib.error.URLError, OSError):
return False
@pytest.mark.integration
def test_subtitle_ocr_full_chain(tmp_path) -> None:
"""抽帧→OCR→汇总:SRT 应含 SUB 001/SUB 002 且时间轴落在各自区间。"""
if not _ollama_reachable():
pytest.skip("Ollama 服务或 glm-ocr 模型不可用,跳过真实模型集成测试")
if not VIDEO.is_file():
pytest.skip("缺少 testdata/subtitle_10s.mp4 测试资产,跳过集成测试")
_register_nodes()
# 抽帧:1s 间隔,字幕在底部,裁切下半 30% 区域(y=0.7,h=0.3)。
frames_resp = frame_invoke(
InvokeRequest(
run_id="chain_fx",
node_instance_id="",
inputs={"video_uri": str(VIDEO)},
params={"interval_seconds": 1, "crop": [0, 0.7, 1, 0.3]},
output_dir=str(tmp_path / "frames"),
)
)
assert frames_resp.status == "completed", frames_resp.error
manifest = json.loads(Path(frames_resp.outputs["frames_manifest"]).read_text(encoding="utf-8"))
assert len(manifest) >= 8
# OCR 汇总:真实 glm-ocr 逐帧识别。
ocr_resp = ocr_invoke(
InvokeRequest(
run_id="chain_ocr",
node_instance_id="",
inputs={"frames_manifest": str(frames_resp.outputs["frames_manifest"])},
params={"model": MODEL, "ollama_host": OLLAMA_HOST, "min_chars": 2},
output_dir=str(tmp_path / "out"),
)
)
assert ocr_resp.status == "completed", ocr_resp.error
srt = Path(ocr_resp.outputs["srt_uri"]).read_text(encoding="utf-8")
# 两条烧录字幕都应被识别(文字可能带噪声,但至少含关键片段)。
assert "SUB" in srt
# 时间轴:SUB 001 应在 1-4sSUB 002 应在 6-9s(允许模型/抽帧容差)。
first_line = next(line for line in srt.splitlines() if "-->" in line)
start = first_line.split(" --> ")[0].replace(",", ".")
hours, minutes, seconds = start.split(":")
total = int(hours) * 3600 + int(minutes) * 60 + float(seconds)
assert total < 5